dbXapp 2.0
RAD, CMS, Module und Runtime-IDE fuer dbXapp
Loading...
Searching...
No Matches
ace.js
Go to the documentation of this file.
1(function () {
2
3
4 // --------------------------------------------------
5
6 if (!window.dbx) {
7 console.error('[ace] dbx not found');
8 return;
9 }
10
11 const dbx = window.dbx;
12
13 function log(...args) { dbx.log('[ace]', ...args); }
14 function warn(...args) { dbx.warn('[ace]', ...args); }
15 function error(...args) { dbx.error('[ace]', ...args); }
16
17 log('lib loaded');
18
19 dbx.feature.register('ace', {
20
21 scope: "element", // 🔥 FIX (einzige Änderung)
22
23 prio: 'last',
24
25 css: [
26 ['css', 'design', 'c-ace.css']
27 ],
28
29 js: [
30 ['js', 'lib', 'ajax.js']
31 ],
32
33 init: init
34 });
35
36 function init(el, cfg) {
37
38 log('init START', el, cfg);
39
40 if (el.__aceInitialized || el.__aceInitializing) {
41 log('already initialized');
42 return;
43 }
44
45 el.__aceInitializing = true;
46
47 function resetInitState() {
48 el.__aceInitializing = false;
49 el.__aceInitialized = false;
50 }
51
52 loadAce(function (ok) {
53
54 log('loadAce callback');
55
56 if (!ok || !window.ace || typeof window.ace.edit !== 'function') {
57 resetInitState();
58 error('Ace ist nicht geladen.');
59 return;
60 }
61
62 if (!el || !el.isConnected) {
63 resetInitState();
64 warn('init skipped: element is no longer connected');
65 return;
66 }
67
68 const file = cfg.file || '';
69
70 const container = el.closest('.c-ace');
71 log('container (.c-ace):', container);
72
73 const textarea = container
74 ? container.querySelector('textarea')
75 : null;
76
77 if (!textarea) {
78 resetInitState();
79 error('textarea not found in container');
80 return;
81 }
82
83 textarea.style.display = 'none';
84
85 if (cfg.height) el.style.height = cfg.height;
86 if (cfg.width) el.style.width = cfg.width;
87
88 log('element size BEFORE init', {
89 offsetHeight: el.offsetHeight,
90 offsetWidth: el.offsetWidth,
91 clientHeight: el.clientHeight,
92 styleHeight: el.style.height
93 });
94
95 try {
96
97 const editor = ace.edit(el);
98 el.__aceEditor = editor;
99 el.__aceInitialized = true;
100 el.__aceInitializing = false;
101
102 log('editor created', editor);
103
104 // --------------------------------------------------
105 // THEME
106 // --------------------------------------------------
107
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';
113 return t;
114 }
115
116 try {
117 editor.setTheme("ace/theme/" + resolveTheme(cfg.theme));
118 } catch {
119 editor.setTheme("ace/theme/monokai");
120 }
121
122 editor.session.setMode(getMode(file));
123
124 const dirtyEl = container ? container.querySelector('.editor-dirty') : null;
125
126 function setDirty(state) {
127
128 log('setDirty:', state, dirtyEl);
129
130 if (dirtyEl) {
131 dirtyEl.dataset.state = state ? 'dirty' : '';
132 } else {
133 warn('dirtyEl not found');
134 }
135 }
136
137 // --------------------------------------------------
138 // CONTENT INIT
139 // --------------------------------------------------
140
141 editor.setValue(textarea.value || '', -1);
142 setDirty(false);
143
144 log('AFTER setValue size', {
145 offsetHeight: el.offsetHeight,
146 clientHeight: el.clientHeight
147 });
148
149 // --------------------------------------------------
150 // 🔥 FIX: LIVE RESIZE (OHNE DELAY)
151 // --------------------------------------------------
152
153 function doResize() {
154 editor.resize();
155 }
156
157 // initial
158 doResize();
159
160 // window resize
161 window.addEventListener('resize', doResize);
162
163 // 🔥 wichtig: window resize (drag/resize von openWin)
164 const win = el.closest('.dbx-window');
165
166 if (win && window.ResizeObserver) {
167
168 let raf;
169
170 const ro = new ResizeObserver(() => {
171 cancelAnimationFrame(raf);
172 raf = requestAnimationFrame(() => editor.resize());
173 });
174
175 ro.observe(win);
176 }
177
178 // fallback (falls ResizeObserver fehlt)
179 else {
180 setTimeout(() => {
181 editor.resize();
182 }, 0);
183 }
184
185 // --------------------------------------------------
186 // REGISTRY
187 // --------------------------------------------------
188
189 window.__dbxEditors = window.__dbxEditors || [];
190
191 const entry = {
192 editor,
193 container,
194 textarea,
195 cfg,
196 save: null
197 };
198
199 window.__dbxEditors.push(entry);
200
201 editor.on('focus', () => window.__dbxActiveEditor = entry);
202 el.addEventListener('mousedown', () => window.__dbxActiveEditor = entry);
203
204 // --------------------------------------------------
205 // CHANGE
206 // --------------------------------------------------
207
208 editor.session.on('change', function () {
209
210 const val = editor.getValue();
211 textarea.value = val;
212
213 setDirty(true);
214
215 });
216
217 // SAVE BUTTON
218 addSaveButton(editor, file, entry, setDirty);
219
220 } catch (e) {
221 resetInitState();
222 error('editor init failed', e);
223 }
224
225 });
226 }
227
228 // --------------------------------------------------
229 // ACE LOADER
230 // --------------------------------------------------
231
232 function loadAce(callback) {
233
234 const libPath = dbx.config.libPath || '';
235 const root = libPath.replace(/js\/lib\/?$/, '');
236 const acePath = root + 'add_ons/ace/';
237
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);
244 }
245 }
246
247 if (window.ace && typeof window.ace.edit === 'function') {
248 configureAcePaths();
249 return callback(true);
250 }
251
252 if (window.__dbxAceQueue) {
253 window.__dbxAceQueue.push(callback);
254 return;
255 }
256
257 window.__dbxAceQueue = [callback];
258
259 const script = document.createElement('script');
260
261 script.src = acePath + 'ace.js';
262
263 script.onload = function () {
264
265 configureAcePaths();
266
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));
271 };
272
273 script.onerror = function () {
274 error('FAILED:', script.src);
275 const q = window.__dbxAceQueue || [];
276 window.__dbxAceQueue = null;
277 q.forEach(fn => fn(false));
278 };
279
280 document.head.appendChild(script);
281 }
282
283 // --------------------------------------------------
284 // MODE
285 // --------------------------------------------------
286
287 function getMode(file) {
288
289 if (!file) return "ace/mode/text";
290
291 const ext = file.split('.').pop().toLowerCase();
292
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";
297
298 return "ace/mode/text";
299 }
300
301 // --------------------------------------------------
302 // SAVE BUTTON (unverändert)
303 // --------------------------------------------------
304
305 function addSaveButton(editor, file, entry, setDirty) {
306
307 const container = editor.container.closest('.c-ace');
308 log('addSaveButton container:', container);
309
310 if (!container) {
311 warn('no editor container found');
312 return;
313 }
314
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');
320
321 if (!btnSave) {
322 warn('no save button found');
323 return;
324 }
325
326 function showMsg(txt, type='ok') {
327
328 const el = document.createElement('div');
329 el.className = 'dbx-msg';
330 el.textContent = txt;
331
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';
343
344 document.body.appendChild(el);
345
346 requestAnimationFrame(() => el.style.opacity = '1');
347
348 setTimeout(() => {
349 el.style.opacity = '0';
350 setTimeout(() => el.remove(), 300);
351 }, 1200);
352 }
353
354 if (input) input.value = file;
355
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?');
359
360 function doConfirm(text, msg) {
361 if (text === '-') return true;
362 return confirm(text + '\n' + msg);
363 }
364
365 function setIcon(state) {
366
367 const icon = btnSave.querySelector('i');
368 if (!icon) return;
369
370 icon.className = 'bi';
371
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');
376 }
377
378 setIcon();
379
380 function requestJson(url, options = {}) {
381 if (!dbx.ajax || typeof dbx.ajax.request !== 'function') {
382 return Promise.reject(new Error('ajax.js nicht geladen.'));
383 }
384 return dbx.ajax.request(Object.assign({
385 url: url,
386 method: 'GET',
387 mode: 'json',
388 timeout: 30000
389 }, options));
390 }
391
392 function doSave() {
393
394 if (btnSave.dataset.busy === '1') return;
395 btnSave.dataset.busy = '1';
396
397 const content = editor.getValue();
398 const currentFile = input ? input.value : file;
399
400 btnSave.dataset.state = 'saving';
401 setIcon('saving');
402
403 requestJson('?dbx_modul=dbxEditor&dbx_run1=save&file=' + encodeURIComponent(currentFile) + '&dbx_ajax=1', {
404 method: 'POST',
405 headers: {'Content-Type': 'application/x-www-form-urlencoded'},
406 body: 'content=' + encodeURIComponent(content)
407 })
408 .then(res => {
409
410 if (res.ok) {
411
412 btnSave.dataset.state = 'saved';
413 setIcon('saved');
414 setDirty(false);
415
416 showMsg('Datei gespeichert');
417
418 setTimeout(() => {
419 btnSave.dataset.state = '';
420 setIcon();
421 }, 1200);
422
423 } else {
424 btnSave.dataset.state = 'error';
425 setIcon('error');
426 showMsg('Speichern fehlgeschlagen', 'error');
427 }
428
429 btnSave.dataset.busy = '0';
430 })
431 .catch(() => {
432 btnSave.dataset.state = 'error';
433 setIcon('error');
434 showMsg('Speichern fehlgeschlagen', 'error');
435 btnSave.dataset.busy = '0';
436 });
437 }
438
439 btnSave.onclick = doSave;
440 entry.save = doSave;
441
442 if (btnDelete) {
443
444 btnDelete.onclick = function () {
445
446 const currentFile = input ? input.value : file;
447
448 if (!doConfirm(confirmDeleteText, currentFile)) return;
449
450 requestJson('?dbx_modul=dbxEditor&dbx_run1=delete&file=' + encodeURIComponent(currentFile) + '&dbx_ajax=1')
451 .then(res => {
452
453 if (res.ok) {
454
455 showMsg('Datei gelöscht');
456
457 const win = container.closest('.dbx-window');
458 if (win) win.remove();
459
460 } else {
461 showMsg('Löschen fehlgeschlagen', 'error');
462 }
463 });
464 };
465 }
466
467 let oldValue = file;
468
469 if (btnRename && input) {
470
471 btnRename.onclick = function () {
472
473 const newFile = input.value.trim();
474
475 if (!newFile || newFile === oldValue) {
476 showMsg('Dateiname unverändert');
477 return;
478 }
479
480 if (!doConfirm(confirmRenameText, oldValue + ' → ' + newFile)) return;
481
482 requestJson('?dbx_modul=dbxEditor&dbx_run1=rename' +
483 '&old=' + encodeURIComponent(oldValue) +
484 '&new=' + encodeURIComponent(newFile) +
485 '&dbx_ajax=1')
486 .then(res => {
487
488 if (res.ok) {
489
490 oldValue = newFile;
491 showMsg('Datei umbenannt');
492
493 } else {
494 showMsg('Umbenennen fehlgeschlagen', 'error');
495 input.value = oldValue;
496 }
497 });
498 };
499 }
500
501 if (btnCopy && input) {
502
503 btnCopy.onclick = function () {
504
505 const newFile = input.value.trim();
506
507 if (!newFile || newFile === oldValue) {
508 showMsg('Dateiname unverändert');
509 return;
510 }
511
512 if (!doConfirm(confirmCopyText, oldValue + ' → ' + newFile)) return;
513
514 requestJson('?dbx_modul=dbxEditor&dbx_run1=copy' +
515 '&old=' + encodeURIComponent(oldValue) +
516 '&new=' + encodeURIComponent(newFile) +
517 '&dbx_ajax=1')
518 .then(res => {
519
520 if (res.ok) {
521
522 showMsg('Datei kopiert');
523
524 } else {
525 showMsg('Kopieren fehlgeschlagen', 'error');
526 }
527 });
528 };
529 }
530
531 log('save/delete/rename/copy wired');
532 }
533
534 if (!window.__dbxSaveHandlerInstalled) {
535
536 window.__dbxSaveHandlerInstalled = true;
537
538 document.addEventListener('keydown', function (e) {
539
540 const isSave = (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's';
541 if (!isSave) return;
542
543 e.preventDefault();
544
545 if (window.__dbxActiveEditor?.save) {
546 window.__dbxActiveEditor.save();
547 return;
548 }
549
550 if (window.__dbxEditors?.length) {
551 window.__dbxEditors[0].save();
552 }
553 });
554 }
555
556})();