active_canvas 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +318 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/javascripts/active_canvas/editor/ai_panel.js +1607 -0
  6. data/app/assets/javascripts/active_canvas/editor/asset_manager.js +498 -0
  7. data/app/assets/javascripts/active_canvas/editor/blocks.js +1083 -0
  8. data/app/assets/javascripts/active_canvas/editor/code_panel.js +572 -0
  9. data/app/assets/javascripts/active_canvas/editor/component_toolbar.js +394 -0
  10. data/app/assets/javascripts/active_canvas/editor/panels.js +460 -0
  11. data/app/assets/javascripts/active_canvas/editor/utils.js +56 -0
  12. data/app/assets/javascripts/active_canvas/editor.js +295 -0
  13. data/app/assets/stylesheets/active_canvas/application.css +15 -0
  14. data/app/assets/stylesheets/active_canvas/editor.css +2929 -0
  15. data/app/controllers/active_canvas/admin/ai_controller.rb +181 -0
  16. data/app/controllers/active_canvas/admin/application_controller.rb +56 -0
  17. data/app/controllers/active_canvas/admin/media_controller.rb +61 -0
  18. data/app/controllers/active_canvas/admin/page_types_controller.rb +57 -0
  19. data/app/controllers/active_canvas/admin/page_versions_controller.rb +23 -0
  20. data/app/controllers/active_canvas/admin/pages_controller.rb +133 -0
  21. data/app/controllers/active_canvas/admin/partials_controller.rb +88 -0
  22. data/app/controllers/active_canvas/admin/settings_controller.rb +256 -0
  23. data/app/controllers/active_canvas/application_controller.rb +20 -0
  24. data/app/controllers/active_canvas/pages_controller.rb +18 -0
  25. data/app/controllers/concerns/active_canvas/current_user.rb +12 -0
  26. data/app/controllers/concerns/active_canvas/rate_limitable.rb +75 -0
  27. data/app/controllers/concerns/active_canvas/tailwind_compilation.rb +39 -0
  28. data/app/helpers/active_canvas/application_helper.rb +4 -0
  29. data/app/jobs/active_canvas/application_job.rb +4 -0
  30. data/app/jobs/active_canvas/compile_tailwind_job.rb +64 -0
  31. data/app/mailers/active_canvas/application_mailer.rb +6 -0
  32. data/app/models/active_canvas/ai_model.rb +136 -0
  33. data/app/models/active_canvas/application_record.rb +5 -0
  34. data/app/models/active_canvas/media.rb +141 -0
  35. data/app/models/active_canvas/page.rb +85 -0
  36. data/app/models/active_canvas/page_type.rb +22 -0
  37. data/app/models/active_canvas/page_version.rb +80 -0
  38. data/app/models/active_canvas/partial.rb +73 -0
  39. data/app/models/active_canvas/setting.rb +292 -0
  40. data/app/services/active_canvas/ai_configuration.rb +40 -0
  41. data/app/services/active_canvas/ai_models.rb +128 -0
  42. data/app/services/active_canvas/ai_service.rb +289 -0
  43. data/app/services/active_canvas/content_sanitizer.rb +112 -0
  44. data/app/services/active_canvas/tailwind_compiler.rb +156 -0
  45. data/app/views/active_canvas/admin/media/index.html.erb +401 -0
  46. data/app/views/active_canvas/admin/media/show.html.erb +297 -0
  47. data/app/views/active_canvas/admin/page_types/_form.html.erb +25 -0
  48. data/app/views/active_canvas/admin/page_types/edit.html.erb +13 -0
  49. data/app/views/active_canvas/admin/page_types/index.html.erb +29 -0
  50. data/app/views/active_canvas/admin/page_types/new.html.erb +9 -0
  51. data/app/views/active_canvas/admin/page_types/show.html.erb +18 -0
  52. data/app/views/active_canvas/admin/page_versions/show.html.erb +469 -0
  53. data/app/views/active_canvas/admin/pages/_form.html.erb +62 -0
  54. data/app/views/active_canvas/admin/pages/content.html.erb +139 -0
  55. data/app/views/active_canvas/admin/pages/edit.html.erb +335 -0
  56. data/app/views/active_canvas/admin/pages/editor.html.erb +710 -0
  57. data/app/views/active_canvas/admin/pages/index.html.erb +149 -0
  58. data/app/views/active_canvas/admin/pages/new.html.erb +19 -0
  59. data/app/views/active_canvas/admin/pages/show.html.erb +258 -0
  60. data/app/views/active_canvas/admin/pages/versions.html.erb +333 -0
  61. data/app/views/active_canvas/admin/partials/edit.html.erb +182 -0
  62. data/app/views/active_canvas/admin/partials/editor.html.erb +703 -0
  63. data/app/views/active_canvas/admin/partials/index.html.erb +131 -0
  64. data/app/views/active_canvas/admin/settings/show.html.erb +1864 -0
  65. data/app/views/active_canvas/pages/no_homepage.html.erb +45 -0
  66. data/app/views/active_canvas/pages/show.html.erb +113 -0
  67. data/app/views/layouts/active_canvas/admin/application.html.erb +960 -0
  68. data/app/views/layouts/active_canvas/admin/editor.html.erb +826 -0
  69. data/app/views/layouts/active_canvas/application.html.erb +55 -0
  70. data/config/routes.rb +48 -0
  71. data/db/migrate/20260202000001_create_active_canvas_tables.rb +113 -0
  72. data/db/migrate/20260202000002_create_active_canvas_ai_models.rb +26 -0
  73. data/lib/active_canvas/configuration.rb +232 -0
  74. data/lib/active_canvas/engine.rb +44 -0
  75. data/lib/active_canvas/version.rb +3 -0
  76. data/lib/active_canvas.rb +26 -0
  77. data/lib/generators/active_canvas/install/install_generator.rb +263 -0
  78. data/lib/generators/active_canvas/install/templates/initializer.rb.tt +163 -0
  79. data/lib/tasks/active_canvas_tasks.rake +69 -0
  80. metadata +150 -0
@@ -0,0 +1,572 @@
1
+ /**
2
+ * ActiveCanvas Editor - Code Panel (Monaco Editor)
3
+ */
4
+
5
+ (function() {
6
+ 'use strict';
7
+
8
+ window.ActiveCanvasEditor = window.ActiveCanvasEditor || {};
9
+
10
+ /**
11
+ * Setup the code panel with Monaco editors
12
+ * @param {Object} editor - GrapeJS editor instance
13
+ * @param {Object} config - Editor configuration
14
+ */
15
+ function setupCodePanel(editor, config) {
16
+ const { showToast } = window.ActiveCanvasEditor;
17
+
18
+ const codePanel = document.getElementById('code-panel');
19
+ const btnCode = document.getElementById('btn-code');
20
+ const statusEl = document.getElementById('code-status');
21
+ const codePanelMode = document.getElementById('code-panel-mode');
22
+ const componentNameEl = document.getElementById('component-name');
23
+
24
+ if (!codePanel || !btnCode) return;
25
+
26
+ let currentCodeTab = 'html';
27
+ let codeDebounceTimer = null;
28
+ let htmlMonacoEditor = null;
29
+ let cssMonacoEditor = null;
30
+ let jsMonacoEditor = null;
31
+ let monacoInitialized = false;
32
+
33
+ // Component editing mode
34
+ let editingComponent = null;
35
+ let isComponentMode = false;
36
+
37
+ // Initialize Monaco editors
38
+ function initMonacoEditors() {
39
+ if (monacoInitialized) return;
40
+
41
+ require(['vs/editor/editor.main'], function() {
42
+ // Define custom themes for Monaco
43
+ defineMonacoThemes();
44
+
45
+ // Get Monaco theme based on editor theme
46
+ function getMonacoTheme() {
47
+ const editorTheme = document.documentElement.getAttribute('data-theme') || 'dark';
48
+ const themeMap = {
49
+ 'dark': 'ac-dark',
50
+ 'midnight': 'ac-midnight',
51
+ 'ocean': 'ac-ocean',
52
+ 'charcoal': 'ac-charcoal',
53
+ 'light': 'ac-light'
54
+ };
55
+ return themeMap[editorTheme] || 'ac-dark';
56
+ }
57
+
58
+ // Expose theme setter for external use
59
+ window.ActiveCanvasEditor.setMonacoTheme = function(theme) {
60
+ const themeMap = {
61
+ 'dark': 'ac-dark',
62
+ 'midnight': 'ac-midnight',
63
+ 'ocean': 'ac-ocean',
64
+ 'charcoal': 'ac-charcoal',
65
+ 'light': 'ac-light'
66
+ };
67
+ const monacoTheme = themeMap[theme] || 'ac-dark';
68
+ monaco.editor.setTheme(monacoTheme);
69
+ };
70
+
71
+ // Common editor options
72
+ const editorOptions = {
73
+ theme: getMonacoTheme(),
74
+ fontSize: 13,
75
+ lineNumbers: 'on',
76
+ minimap: { enabled: false },
77
+ scrollBeyondLastLine: false,
78
+ automaticLayout: true,
79
+ tabSize: 2,
80
+ wordWrap: 'on',
81
+ folding: true,
82
+ lineDecorationsWidth: 10,
83
+ lineNumbersMinChars: 3,
84
+ renderLineHighlight: 'line',
85
+ scrollbar: {
86
+ verticalScrollbarSize: 10,
87
+ horizontalScrollbarSize: 10
88
+ }
89
+ };
90
+
91
+ // Create HTML editor
92
+ htmlMonacoEditor = monaco.editor.create(document.getElementById('monaco-html-container'), {
93
+ value: editor.getHtml(),
94
+ language: 'html',
95
+ ...editorOptions
96
+ });
97
+
98
+ // Create CSS editor
99
+ cssMonacoEditor = monaco.editor.create(document.getElementById('monaco-css-container'), {
100
+ value: editor.getCss(),
101
+ language: 'css',
102
+ ...editorOptions
103
+ });
104
+
105
+ // Create JS editor
106
+ jsMonacoEditor = monaco.editor.create(document.getElementById('monaco-js-container'), {
107
+ value: config.contentJs || '',
108
+ language: 'javascript',
109
+ ...editorOptions
110
+ });
111
+
112
+ // Live update on content change
113
+ htmlMonacoEditor.onDidChangeModelContent(scheduleCodeUpdate);
114
+ cssMonacoEditor.onDidChangeModelContent(scheduleCodeUpdate);
115
+ jsMonacoEditor.onDidChangeModelContent(scheduleJsUpdate);
116
+
117
+ // Add keyboard shortcuts to Monaco
118
+ addMonacoCommands(htmlMonacoEditor);
119
+ addMonacoCommands(cssMonacoEditor);
120
+ addMonacoCommands(jsMonacoEditor);
121
+
122
+ monacoInitialized = true;
123
+
124
+ // Auto-format on first open
125
+ setTimeout(formatAllCode, 100);
126
+ });
127
+ }
128
+
129
+ function defineMonacoThemes() {
130
+ monaco.editor.defineTheme('ac-dark', {
131
+ base: 'vs-dark',
132
+ inherit: true,
133
+ rules: [],
134
+ colors: {
135
+ 'editor.background': '#1e293b',
136
+ 'editor.foreground': '#f8fafc',
137
+ 'editorLineNumber.foreground': '#64748b',
138
+ 'editorCursor.foreground': '#f8fafc',
139
+ 'editor.selectionBackground': '#334155',
140
+ 'editor.lineHighlightBackground': '#334155'
141
+ }
142
+ });
143
+
144
+ monaco.editor.defineTheme('ac-midnight', {
145
+ base: 'vs-dark',
146
+ inherit: true,
147
+ rules: [],
148
+ colors: {
149
+ 'editor.background': '#1a1730',
150
+ 'editor.foreground': '#f5f3ff',
151
+ 'editorLineNumber.foreground': '#7c75a8',
152
+ 'editorCursor.foreground': '#8b5cf6',
153
+ 'editor.selectionBackground': '#2d2750',
154
+ 'editor.lineHighlightBackground': '#2d2750'
155
+ }
156
+ });
157
+
158
+ monaco.editor.defineTheme('ac-ocean', {
159
+ base: 'vs-dark',
160
+ inherit: true,
161
+ rules: [],
162
+ colors: {
163
+ 'editor.background': '#0f2930',
164
+ 'editor.foreground': '#ecfeff',
165
+ 'editorLineNumber.foreground': '#5eaab8',
166
+ 'editorCursor.foreground': '#06b6d4',
167
+ 'editor.selectionBackground': '#1a3d47',
168
+ 'editor.lineHighlightBackground': '#1a3d47'
169
+ }
170
+ });
171
+
172
+ monaco.editor.defineTheme('ac-charcoal', {
173
+ base: 'vs-dark',
174
+ inherit: true,
175
+ rules: [],
176
+ colors: {
177
+ 'editor.background': '#262626',
178
+ 'editor.foreground': '#fafafa',
179
+ 'editorLineNumber.foreground': '#737373',
180
+ 'editorCursor.foreground': '#f97316',
181
+ 'editor.selectionBackground': '#363636',
182
+ 'editor.lineHighlightBackground': '#363636'
183
+ }
184
+ });
185
+
186
+ monaco.editor.defineTheme('ac-light', {
187
+ base: 'vs',
188
+ inherit: true,
189
+ rules: [],
190
+ colors: {
191
+ 'editor.background': '#ffffff',
192
+ 'editor.foreground': '#1e293b',
193
+ 'editorLineNumber.foreground': '#94a3b8',
194
+ 'editorCursor.foreground': '#6366f1',
195
+ 'editor.selectionBackground': '#e0e7ff',
196
+ 'editor.lineHighlightBackground': '#f8fafc'
197
+ }
198
+ });
199
+ }
200
+
201
+ // Sync GrapeJS content to Monaco editors
202
+ function syncGrapeJSToMonaco() {
203
+ if (!monacoInitialized) return;
204
+
205
+ if (isComponentMode && editingComponent) {
206
+ htmlMonacoEditor.setValue(editingComponent.toHTML());
207
+ } else {
208
+ htmlMonacoEditor.setValue(editor.getHtml());
209
+ }
210
+ cssMonacoEditor.setValue(editor.getCss());
211
+ setTimeout(formatAllCode, 50);
212
+ }
213
+
214
+ // Update GrapeJS preview from Monaco
215
+ function updatePreviewFromCode() {
216
+ if (!monacoInitialized) return;
217
+
218
+ try {
219
+ if (isComponentMode && editingComponent) {
220
+ const newHtml = htmlMonacoEditor.getValue();
221
+ updateComponentFromHtml(editingComponent, newHtml);
222
+ } else {
223
+ const newHtml = htmlMonacoEditor.getValue();
224
+ const newCss = cssMonacoEditor.getValue();
225
+ editor.setComponents(newHtml);
226
+ editor.setStyle(newCss);
227
+ }
228
+ if (statusEl) {
229
+ statusEl.textContent = 'Synced';
230
+ statusEl.className = 'code-status synced';
231
+ }
232
+ } catch (e) {
233
+ if (statusEl) {
234
+ statusEl.textContent = 'Error';
235
+ statusEl.className = 'code-status modified';
236
+ }
237
+ console.error('Code parse error:', e);
238
+ }
239
+ }
240
+
241
+ // Update a component from HTML string
242
+ function updateComponentFromHtml(component, html) {
243
+ if (!component) return;
244
+
245
+ try {
246
+ const newComponents = component.replaceWith(html);
247
+
248
+ if (newComponents && newComponents.length > 0) {
249
+ editingComponent = newComponents[0];
250
+ editor.select(editingComponent);
251
+ }
252
+ } catch (e) {
253
+ console.error('Error replacing component:', e);
254
+ try {
255
+ component.components(html);
256
+ } catch (e2) {
257
+ console.error('Error updating component content:', e2);
258
+ }
259
+ }
260
+ }
261
+
262
+ // Open code panel in component editing mode
263
+ function openComponentCodeEditor(component) {
264
+ editingComponent = component;
265
+ isComponentMode = true;
266
+
267
+ if (codePanelMode) codePanelMode.style.display = 'flex';
268
+ if (componentNameEl) componentNameEl.textContent = getComponentName(component);
269
+
270
+ // Hide CSS/JS tabs in component mode
271
+ document.querySelectorAll('.code-panel-tab').forEach(t => {
272
+ if (t.dataset.type === 'html') {
273
+ t.style.display = '';
274
+ t.classList.add('active');
275
+ } else {
276
+ t.style.display = 'none';
277
+ t.classList.remove('active');
278
+ }
279
+ });
280
+ currentCodeTab = 'html';
281
+
282
+ codePanel.classList.add('open');
283
+ btnCode.classList.add('active');
284
+
285
+ if (!monacoInitialized) {
286
+ initMonacoEditors();
287
+ setTimeout(() => {
288
+ htmlMonacoEditor.setValue(component.toHTML());
289
+ setTimeout(() => htmlMonacoEditor.getAction('editor.action.formatDocument').run(), 50);
290
+
291
+ document.getElementById('monaco-html-container').style.display = 'block';
292
+ document.getElementById('monaco-css-container').style.display = 'none';
293
+ document.getElementById('monaco-js-container').style.display = 'none';
294
+
295
+ htmlMonacoEditor.layout();
296
+ htmlMonacoEditor.focus();
297
+ }, 200);
298
+ } else {
299
+ htmlMonacoEditor.setValue(component.toHTML());
300
+ setTimeout(() => htmlMonacoEditor.getAction('editor.action.formatDocument').run(), 50);
301
+
302
+ document.getElementById('monaco-html-container').style.display = 'block';
303
+ document.getElementById('monaco-css-container').style.display = 'none';
304
+ document.getElementById('monaco-js-container').style.display = 'none';
305
+
306
+ htmlMonacoEditor.layout();
307
+ htmlMonacoEditor.focus();
308
+ }
309
+
310
+ setTimeout(() => editor.refresh(), 50);
311
+ }
312
+
313
+ // Expose for external use
314
+ window.ActiveCanvasEditor.openComponentCodeEditor = openComponentCodeEditor;
315
+
316
+ // Get a friendly name for a component
317
+ function getComponentName(component) {
318
+ const type = component.get('type');
319
+ const tagName = component.get('tagName') || 'div';
320
+ const classes = component.getClasses().slice(0, 2).join('.');
321
+ const id = component.getId();
322
+
323
+ if (id) return `#${id}`;
324
+ if (classes) return `${tagName}.${classes}`;
325
+ if (type && type !== 'default') return type;
326
+ return tagName;
327
+ }
328
+
329
+ // Exit component mode
330
+ function exitComponentMode() {
331
+ const wasInComponentMode = isComponentMode;
332
+ isComponentMode = false;
333
+ editingComponent = null;
334
+ if (codePanelMode) codePanelMode.style.display = 'none';
335
+
336
+ document.querySelectorAll('.code-panel-tab').forEach(t => {
337
+ t.style.display = '';
338
+ });
339
+
340
+ if (wasInComponentMode && monacoInitialized) {
341
+ syncGrapeJSToMonaco();
342
+ }
343
+ }
344
+
345
+ function scheduleCodeUpdate() {
346
+ if (statusEl) {
347
+ statusEl.textContent = 'Modified...';
348
+ statusEl.className = 'code-status modified';
349
+ }
350
+
351
+ if (codeDebounceTimer) {
352
+ clearTimeout(codeDebounceTimer);
353
+ }
354
+ codeDebounceTimer = setTimeout(updatePreviewFromCode, 200);
355
+ }
356
+
357
+ async function formatAllCode() {
358
+ if (!monacoInitialized) return;
359
+ await htmlMonacoEditor.getAction('editor.action.formatDocument').run();
360
+ await cssMonacoEditor.getAction('editor.action.formatDocument').run();
361
+ await jsMonacoEditor.getAction('editor.action.formatDocument').run();
362
+ }
363
+
364
+ function scheduleJsUpdate() {
365
+ if (statusEl) {
366
+ statusEl.textContent = 'Modified...';
367
+ statusEl.className = 'code-status modified';
368
+ }
369
+
370
+ if (codeDebounceTimer) {
371
+ clearTimeout(codeDebounceTimer);
372
+ }
373
+ codeDebounceTimer = setTimeout(() => {
374
+ injectJsIntoCanvas();
375
+ if (statusEl) {
376
+ statusEl.textContent = 'Synced';
377
+ statusEl.className = 'code-status synced';
378
+ }
379
+ }, 200);
380
+ }
381
+
382
+ function injectJsIntoCanvas() {
383
+ if (!monacoInitialized) return;
384
+
385
+ const jsCode = jsMonacoEditor.getValue();
386
+ const frame = editor.Canvas.getFrameEl();
387
+ if (!frame || !frame.contentDocument) return;
388
+
389
+ const existingScript = frame.contentDocument.getElementById('active-canvas-custom-js');
390
+ if (existingScript) {
391
+ existingScript.remove();
392
+ }
393
+
394
+ if (jsCode.trim()) {
395
+ const script = frame.contentDocument.createElement('script');
396
+ script.id = 'active-canvas-custom-js';
397
+ script.textContent = jsCode;
398
+ frame.contentDocument.body.appendChild(script);
399
+ }
400
+ }
401
+
402
+ function addMonacoCommands(monacoEditor) {
403
+ monacoEditor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, async () => {
404
+ if (codeDebounceTimer) {
405
+ clearTimeout(codeDebounceTimer);
406
+ }
407
+ await formatAllCode();
408
+ updatePreviewFromCode();
409
+ showToast('Formatted and applied', 'success');
410
+ });
411
+
412
+ monacoEditor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, () => {
413
+ monacoEditor.getAction('editor.action.formatDocument').run();
414
+ });
415
+ }
416
+
417
+ function toggleCodePanel() {
418
+ const isOpen = codePanel.classList.toggle('open');
419
+ btnCode.classList.toggle('active', isOpen);
420
+
421
+ if (isOpen) {
422
+ exitComponentMode();
423
+
424
+ if (!monacoInitialized) {
425
+ initMonacoEditors();
426
+ } else {
427
+ syncGrapeJSToMonaco();
428
+ }
429
+ setTimeout(() => {
430
+ if (htmlMonacoEditor) htmlMonacoEditor.layout();
431
+ if (cssMonacoEditor) cssMonacoEditor.layout();
432
+ if (jsMonacoEditor) jsMonacoEditor.layout();
433
+ editor.refresh();
434
+ }, 50);
435
+ } else {
436
+ exitComponentMode();
437
+ editor.refresh();
438
+ }
439
+ }
440
+
441
+ // Event listeners
442
+ btnCode.addEventListener('click', toggleCodePanel);
443
+
444
+ // Keyboard shortcut: Ctrl+E / Cmd+E to toggle code editor
445
+ document.addEventListener('keydown', function(e) {
446
+ if ((e.ctrlKey || e.metaKey) && e.key === 'e') {
447
+ e.preventDefault();
448
+ toggleCodePanel();
449
+ }
450
+ });
451
+
452
+ const closeBtn = document.getElementById('code-close');
453
+ if (closeBtn) {
454
+ closeBtn.addEventListener('click', () => {
455
+ exitComponentMode();
456
+ codePanel.classList.remove('open');
457
+ btnCode.classList.remove('active');
458
+ editor.refresh();
459
+ });
460
+ }
461
+
462
+ const fullPageBtn = document.getElementById('code-full-page');
463
+ if (fullPageBtn) {
464
+ fullPageBtn.addEventListener('click', () => {
465
+ exitComponentMode();
466
+ });
467
+ }
468
+
469
+ document.querySelectorAll('.code-panel-tab').forEach(tab => {
470
+ tab.addEventListener('click', function() {
471
+ currentCodeTab = this.dataset.type;
472
+ document.querySelectorAll('.code-panel-tab').forEach(t => t.classList.remove('active'));
473
+ this.classList.add('active');
474
+
475
+ const htmlContainer = document.getElementById('monaco-html-container');
476
+ const cssContainer = document.getElementById('monaco-css-container');
477
+ const jsContainer = document.getElementById('monaco-js-container');
478
+
479
+ if (htmlContainer) htmlContainer.style.display = 'none';
480
+ if (cssContainer) cssContainer.style.display = 'none';
481
+ if (jsContainer) jsContainer.style.display = 'none';
482
+
483
+ if (currentCodeTab === 'html' && htmlContainer) {
484
+ htmlContainer.style.display = 'block';
485
+ if (htmlMonacoEditor) {
486
+ htmlMonacoEditor.layout();
487
+ htmlMonacoEditor.focus();
488
+ }
489
+ } else if (currentCodeTab === 'css' && cssContainer) {
490
+ cssContainer.style.display = 'block';
491
+ if (cssMonacoEditor) {
492
+ cssMonacoEditor.layout();
493
+ cssMonacoEditor.focus();
494
+ }
495
+ } else if (currentCodeTab === 'js' && jsContainer) {
496
+ jsContainer.style.display = 'block';
497
+ if (jsMonacoEditor) {
498
+ jsMonacoEditor.layout();
499
+ jsMonacoEditor.focus();
500
+ }
501
+ }
502
+ });
503
+ });
504
+
505
+ const applyBtn = document.getElementById('code-apply');
506
+ if (applyBtn) {
507
+ applyBtn.addEventListener('click', async () => {
508
+ if (codeDebounceTimer) {
509
+ clearTimeout(codeDebounceTimer);
510
+ }
511
+ await formatAllCode();
512
+ updatePreviewFromCode();
513
+ showToast('Formatted and applied', 'success');
514
+ });
515
+ }
516
+
517
+ const formatBtn = document.getElementById('code-format');
518
+ if (formatBtn) {
519
+ formatBtn.addEventListener('click', async () => {
520
+ await formatAllCode();
521
+ });
522
+ }
523
+
524
+ // Resize functionality
525
+ setupResizeHandle();
526
+
527
+ function setupResizeHandle() {
528
+ const resizeHandle = document.getElementById('code-panel-resize');
529
+ if (!resizeHandle) return;
530
+
531
+ let isResizing = false;
532
+ let startY = 0;
533
+ let startHeight = 0;
534
+
535
+ resizeHandle.addEventListener('mousedown', (e) => {
536
+ isResizing = true;
537
+ startY = e.clientY;
538
+ startHeight = codePanel.offsetHeight;
539
+ document.body.style.cursor = 'ns-resize';
540
+ document.body.style.userSelect = 'none';
541
+ });
542
+
543
+ document.addEventListener('mousemove', (e) => {
544
+ if (!isResizing) return;
545
+ const deltaY = startY - e.clientY;
546
+ const newHeight = Math.min(Math.max(startHeight + deltaY, 150), window.innerHeight * 0.7);
547
+ codePanel.style.height = newHeight + 'px';
548
+ if (htmlMonacoEditor) htmlMonacoEditor.layout();
549
+ if (cssMonacoEditor) cssMonacoEditor.layout();
550
+ if (jsMonacoEditor) jsMonacoEditor.layout();
551
+ editor.refresh();
552
+ });
553
+
554
+ document.addEventListener('mouseup', () => {
555
+ if (isResizing) {
556
+ isResizing = false;
557
+ document.body.style.cursor = '';
558
+ document.body.style.userSelect = '';
559
+ }
560
+ });
561
+ }
562
+
563
+ // Expose getters for save function
564
+ window.ActiveCanvasEditor.getJs = function() {
565
+ return jsMonacoEditor ? jsMonacoEditor.getValue() : (config.contentJs || '');
566
+ };
567
+ }
568
+
569
+ // Expose function
570
+ window.ActiveCanvasEditor.setupCodePanel = setupCodePanel;
571
+
572
+ })();