4 // --------------------------------------------------
7 console.error('[ace] dbx not found');
11 const dbx = window.dbx;
13 function log(...args) { dbx.log('[ace]', ...args); }
14 function warn(...args) { dbx.warn('[ace]', ...args); }
15 function error(...args) { dbx.error('[ace]', ...args); }
19 dbx.feature.register('ace', {
21 scope: "element", // 🔥 FIX (einzige Änderung)
26 ['css', 'design', 'c-ace.css']
30 ['js', 'lib', 'ajax.js']
36 function init(el, cfg) {
38 log('init START', el, cfg);
40 if (el.__aceInitialized || el.__aceInitializing) {
41 log('already initialized');
45 el.__aceInitializing = true;
47 function resetInitState() {
48 el.__aceInitializing = false;
49 el.__aceInitialized = false;
52 loadAce(function (ok) {
54 log('loadAce callback');
56 if (!ok || !window.ace || typeof window.ace.edit !== 'function') {
58 error('Ace ist nicht geladen.');
62 if (!el || !el.isConnected) {
64 warn('init skipped: element is no longer connected');
68 const file = cfg.file || '';
70 const container = el.closest('.c-ace');
71 log('container (.c-ace):', container);
73 const textarea = container
74 ? container.querySelector('textarea')
79 error('textarea not found in container');
83 textarea.style.display = 'none';
85 if (cfg.height) el.style.height = cfg.height;
86 if (cfg.width) el.style.width = cfg.width;
88 log('element size BEFORE init', {
89 offsetHeight: el.offsetHeight,
90 offsetWidth: el.offsetWidth,
91 clientHeight: el.clientHeight,
92 styleHeight: el.style.height
97 const editor = ace.edit(el);
98 el.__aceEditor = editor;
99 el.__aceInitialized = true;
100 el.__aceInitializing = false;
102 log('editor created', editor);
104 // --------------------------------------------------
106 // --------------------------------------------------
108 function resolveTheme(name) {
109 if (!name) return 'monokai';
110 const t = name.toLowerCase();
111 if (t === 'dark') return 'monokai';
112 if (t === 'light') return 'github';
117 editor.setTheme("ace/theme/" + resolveTheme(cfg.theme));
119 editor.setTheme("ace/theme/monokai");
122 editor.session.setMode(getMode(file));
124 const dirtyEl = container ? container.querySelector('.editor-dirty') : null;
126 function setDirty(state) {
128 log('setDirty:', state, dirtyEl);
131 dirtyEl.dataset.state = state ? 'dirty' : '';
133 warn('dirtyEl not found');
137 // --------------------------------------------------
139 // --------------------------------------------------
141 editor.setValue(textarea.value || '', -1);
144 log('AFTER setValue size', {
145 offsetHeight: el.offsetHeight,
146 clientHeight: el.clientHeight
149 // --------------------------------------------------
150 // 🔥 FIX: LIVE RESIZE (OHNE DELAY)
151 // --------------------------------------------------
153 function doResize() {
161 window.addEventListener('resize', doResize);
163 // 🔥 wichtig: window resize (drag/resize von openWin)
164 const win = el.closest('.dbx-window');
166 if (win && window.ResizeObserver) {
170 const ro = new ResizeObserver(() => {
171 cancelAnimationFrame(raf);
172 raf = requestAnimationFrame(() => editor.resize());
178 // fallback (falls ResizeObserver fehlt)
185 // --------------------------------------------------
187 // --------------------------------------------------
189 window.__dbxEditors = window.__dbxEditors || [];
199 window.__dbxEditors.push(entry);
201 editor.on('focus', () => window.__dbxActiveEditor = entry);
202 el.addEventListener('mousedown', () => window.__dbxActiveEditor = entry);
204 // --------------------------------------------------
206 // --------------------------------------------------
208 editor.session.on('change', function () {
210 const val = editor.getValue();
211 textarea.value = val;
218 addSaveButton(editor, file, entry, setDirty);
222 error('editor init failed', e);
228 // --------------------------------------------------
230 // --------------------------------------------------
232 function loadAce(callback) {
234 const libPath = dbx.config.libPath || '';
235 const root = libPath.replace(/js\/lib\/?$/, '');
236 const acePath = root + 'add_ons/ace/';
238 function configureAcePaths() {
239 if (window.ace && ace.config) {
240 ace.config.set("basePath", acePath);
241 ace.config.set("modePath", acePath);
242 ace.config.set("themePath", acePath);
243 ace.config.set("workerPath", acePath);
247 if (window.ace && typeof window.ace.edit === 'function') {
249 return callback(true);
252 if (window.__dbxAceQueue) {
253 window.__dbxAceQueue.push(callback);
257 window.__dbxAceQueue = [callback];
259 const script = document.createElement('script');
261 script.src = acePath + 'ace.js';
263 script.onload = function () {
267 const q = window.__dbxAceQueue;
268 window.__dbxAceQueue = null;
269 const ok = !!(window.ace && typeof window.ace.edit === 'function');
270 q.forEach(fn => fn(ok));
273 script.onerror = function () {
274 error('FAILED:', script.src);
275 const q = window.__dbxAceQueue || [];
276 window.__dbxAceQueue = null;
277 q.forEach(fn => fn(false));
280 document.head.appendChild(script);
283 // --------------------------------------------------
285 // --------------------------------------------------
287 function getMode(file) {
289 if (!file) return "ace/mode/text";
291 const ext = file.split('.').pop().toLowerCase();
293 if (ext === 'css') return "ace/mode/css";
294 if (ext === 'htm' || ext === 'html') return "ace/mode/html";
295 if (ext === 'js') return "ace/mode/javascript";
296 if (ext === 'php') return "ace/mode/php";
298 return "ace/mode/text";
301 // --------------------------------------------------
302 // SAVE BUTTON (unverändert)
303 // --------------------------------------------------
305 function addSaveButton(editor, file, entry, setDirty) {
307 const container = editor.container.closest('.c-ace');
308 log('addSaveButton container:', container);
311 warn('no editor container found');
315 const btnSave = container.querySelector('.editor-save');
316 const btnDelete = container.querySelector('.editor-delete');
317 const btnRename = container.querySelector('.editor-rename');
318 const btnCopy = container.querySelector('.editor-copy');
319 const input = container.querySelector('.editor-filename');
322 warn('no save button found');
326 function showMsg(txt, type='ok') {
328 const el = document.createElement('div');
329 el.className = 'dbx-msg';
330 el.textContent = txt;
332 el.style.position = 'fixed';
333 el.style.top = '20px';
334 el.style.right = '20px';
335 el.style.zIndex = 999999;
336 el.style.padding = '6px 10px';
337 el.style.borderRadius = '4px';
338 el.style.background = (type === 'error') ? '#dc3545' : '#198754';
339 el.style.color = '#fff';
340 el.style.fontSize = '12px';
341 el.style.boxShadow = '0 2px 6px rgba(0,0,0,0.2)';
342 el.style.opacity = '0';
344 document.body.appendChild(el);
346 requestAnimationFrame(() => el.style.opacity = '1');
349 el.style.opacity = '0';
350 setTimeout(() => el.remove(), 300);
354 if (input) input.value = file;
356 const confirmDeleteText = (entry?.cfg?.confirm_delete ?? 'Datei wirklich löschen?');
357 const confirmRenameText = (entry?.cfg?.confirm_rename ?? 'Datei wirklich umbenennen?');
358 const confirmCopyText = (entry?.cfg?.confirm_copy ?? 'Datei wirklich kopieren?');
360 function doConfirm(text, msg) {
361 if (text === '-') return true;
362 return confirm(text + '\n' + msg);
365 function setIcon(state) {
367 const icon = btnSave.querySelector('i');
370 icon.className = 'bi';
372 if (state === 'saving') icon.classList.add('bi-arrow-repeat');
373 else if (state === 'saved') icon.classList.add('bi-check');
374 else if (state === 'error') icon.classList.add('bi-x');
375 else icon.classList.add('bi-floppy');
380 function requestJson(url, options = {}) {
381 if (!dbx.ajax || typeof dbx.ajax.request !== 'function') {
382 return Promise.reject(new Error('ajax.js nicht geladen.'));
384 return dbx.ajax.request(Object.assign({
394 if (btnSave.dataset.busy === '1') return;
395 btnSave.dataset.busy = '1';
397 const content = editor.getValue();
398 const currentFile = input ? input.value : file;
400 btnSave.dataset.state = 'saving';
403 requestJson('?dbx_modul=dbxEditor&dbx_run1=save&file=' + encodeURIComponent(currentFile) + '&dbx_ajax=1', {
405 headers: {'Content-Type': 'application/x-www-form-urlencoded'},
406 body: 'content=' + encodeURIComponent(content)
412 btnSave.dataset.state = 'saved';
416 showMsg('Datei gespeichert');
419 btnSave.dataset.state = '';
424 btnSave.dataset.state = 'error';
426 showMsg('Speichern fehlgeschlagen', 'error');
429 btnSave.dataset.busy = '0';
432 btnSave.dataset.state = 'error';
434 showMsg('Speichern fehlgeschlagen', 'error');
435 btnSave.dataset.busy = '0';
439 btnSave.onclick = doSave;
444 btnDelete.onclick = function () {
446 const currentFile = input ? input.value : file;
448 if (!doConfirm(confirmDeleteText, currentFile)) return;
450 requestJson('?dbx_modul=dbxEditor&dbx_run1=delete&file=' + encodeURIComponent(currentFile) + '&dbx_ajax=1')
455 showMsg('Datei gelöscht');
457 const win = container.closest('.dbx-window');
458 if (win) win.remove();
461 showMsg('Löschen fehlgeschlagen', 'error');
469 if (btnRename && input) {
471 btnRename.onclick = function () {
473 const newFile = input.value.trim();
475 if (!newFile || newFile === oldValue) {
476 showMsg('Dateiname unverändert');
480 if (!doConfirm(confirmRenameText, oldValue + ' → ' + newFile)) return;
482 requestJson('?dbx_modul=dbxEditor&dbx_run1=rename' +
483 '&old=' + encodeURIComponent(oldValue) +
484 '&new=' + encodeURIComponent(newFile) +
491 showMsg('Datei umbenannt');
494 showMsg('Umbenennen fehlgeschlagen', 'error');
495 input.value = oldValue;
501 if (btnCopy && input) {
503 btnCopy.onclick = function () {
505 const newFile = input.value.trim();
507 if (!newFile || newFile === oldValue) {
508 showMsg('Dateiname unverändert');
512 if (!doConfirm(confirmCopyText, oldValue + ' → ' + newFile)) return;
514 requestJson('?dbx_modul=dbxEditor&dbx_run1=copy' +
515 '&old=' + encodeURIComponent(oldValue) +
516 '&new=' + encodeURIComponent(newFile) +
522 showMsg('Datei kopiert');
525 showMsg('Kopieren fehlgeschlagen', 'error');
531 log('save/delete/rename/copy wired');
534 if (!window.__dbxSaveHandlerInstalled) {
536 window.__dbxSaveHandlerInstalled = true;
538 document.addEventListener('keydown', function (e) {
540 const isSave = (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's';
545 if (window.__dbxActiveEditor?.save) {
546 window.__dbxActiveEditor.save();
550 if (window.__dbxEditors?.length) {
551 window.__dbxEditors[0].save();