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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +318 -0
- data/Rakefile +6 -0
- data/app/assets/javascripts/active_canvas/editor/ai_panel.js +1607 -0
- data/app/assets/javascripts/active_canvas/editor/asset_manager.js +498 -0
- data/app/assets/javascripts/active_canvas/editor/blocks.js +1083 -0
- data/app/assets/javascripts/active_canvas/editor/code_panel.js +572 -0
- data/app/assets/javascripts/active_canvas/editor/component_toolbar.js +394 -0
- data/app/assets/javascripts/active_canvas/editor/panels.js +460 -0
- data/app/assets/javascripts/active_canvas/editor/utils.js +56 -0
- data/app/assets/javascripts/active_canvas/editor.js +295 -0
- data/app/assets/stylesheets/active_canvas/application.css +15 -0
- data/app/assets/stylesheets/active_canvas/editor.css +2929 -0
- data/app/controllers/active_canvas/admin/ai_controller.rb +181 -0
- data/app/controllers/active_canvas/admin/application_controller.rb +56 -0
- data/app/controllers/active_canvas/admin/media_controller.rb +61 -0
- data/app/controllers/active_canvas/admin/page_types_controller.rb +57 -0
- data/app/controllers/active_canvas/admin/page_versions_controller.rb +23 -0
- data/app/controllers/active_canvas/admin/pages_controller.rb +133 -0
- data/app/controllers/active_canvas/admin/partials_controller.rb +88 -0
- data/app/controllers/active_canvas/admin/settings_controller.rb +256 -0
- data/app/controllers/active_canvas/application_controller.rb +20 -0
- data/app/controllers/active_canvas/pages_controller.rb +18 -0
- data/app/controllers/concerns/active_canvas/current_user.rb +12 -0
- data/app/controllers/concerns/active_canvas/rate_limitable.rb +75 -0
- data/app/controllers/concerns/active_canvas/tailwind_compilation.rb +39 -0
- data/app/helpers/active_canvas/application_helper.rb +4 -0
- data/app/jobs/active_canvas/application_job.rb +4 -0
- data/app/jobs/active_canvas/compile_tailwind_job.rb +64 -0
- data/app/mailers/active_canvas/application_mailer.rb +6 -0
- data/app/models/active_canvas/ai_model.rb +136 -0
- data/app/models/active_canvas/application_record.rb +5 -0
- data/app/models/active_canvas/media.rb +141 -0
- data/app/models/active_canvas/page.rb +85 -0
- data/app/models/active_canvas/page_type.rb +22 -0
- data/app/models/active_canvas/page_version.rb +80 -0
- data/app/models/active_canvas/partial.rb +73 -0
- data/app/models/active_canvas/setting.rb +292 -0
- data/app/services/active_canvas/ai_configuration.rb +40 -0
- data/app/services/active_canvas/ai_models.rb +128 -0
- data/app/services/active_canvas/ai_service.rb +289 -0
- data/app/services/active_canvas/content_sanitizer.rb +112 -0
- data/app/services/active_canvas/tailwind_compiler.rb +156 -0
- data/app/views/active_canvas/admin/media/index.html.erb +401 -0
- data/app/views/active_canvas/admin/media/show.html.erb +297 -0
- data/app/views/active_canvas/admin/page_types/_form.html.erb +25 -0
- data/app/views/active_canvas/admin/page_types/edit.html.erb +13 -0
- data/app/views/active_canvas/admin/page_types/index.html.erb +29 -0
- data/app/views/active_canvas/admin/page_types/new.html.erb +9 -0
- data/app/views/active_canvas/admin/page_types/show.html.erb +18 -0
- data/app/views/active_canvas/admin/page_versions/show.html.erb +469 -0
- data/app/views/active_canvas/admin/pages/_form.html.erb +62 -0
- data/app/views/active_canvas/admin/pages/content.html.erb +139 -0
- data/app/views/active_canvas/admin/pages/edit.html.erb +335 -0
- data/app/views/active_canvas/admin/pages/editor.html.erb +710 -0
- data/app/views/active_canvas/admin/pages/index.html.erb +149 -0
- data/app/views/active_canvas/admin/pages/new.html.erb +19 -0
- data/app/views/active_canvas/admin/pages/show.html.erb +258 -0
- data/app/views/active_canvas/admin/pages/versions.html.erb +333 -0
- data/app/views/active_canvas/admin/partials/edit.html.erb +182 -0
- data/app/views/active_canvas/admin/partials/editor.html.erb +703 -0
- data/app/views/active_canvas/admin/partials/index.html.erb +131 -0
- data/app/views/active_canvas/admin/settings/show.html.erb +1864 -0
- data/app/views/active_canvas/pages/no_homepage.html.erb +45 -0
- data/app/views/active_canvas/pages/show.html.erb +113 -0
- data/app/views/layouts/active_canvas/admin/application.html.erb +960 -0
- data/app/views/layouts/active_canvas/admin/editor.html.erb +826 -0
- data/app/views/layouts/active_canvas/application.html.erb +55 -0
- data/config/routes.rb +48 -0
- data/db/migrate/20260202000001_create_active_canvas_tables.rb +113 -0
- data/db/migrate/20260202000002_create_active_canvas_ai_models.rb +26 -0
- data/lib/active_canvas/configuration.rb +232 -0
- data/lib/active_canvas/engine.rb +44 -0
- data/lib/active_canvas/version.rb +3 -0
- data/lib/active_canvas.rb +26 -0
- data/lib/generators/active_canvas/install/install_generator.rb +263 -0
- data/lib/generators/active_canvas/install/templates/initializer.rb.tt +163 -0
- data/lib/tasks/active_canvas_tasks.rake +69 -0
- metadata +150 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActiveCanvas Editor - Panel Controls, Device Switching, Save
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
(function() {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
window.ActiveCanvasEditor = window.ActiveCanvasEditor || {};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Setup panel controls (toggle left/right/AI panels, tabs)
|
|
12
|
+
* @param {Object} editor - GrapeJS editor instance
|
|
13
|
+
*/
|
|
14
|
+
function setupPanelControls(editor) {
|
|
15
|
+
const panelLeft = document.getElementById('panel-left');
|
|
16
|
+
const panelRight = document.getElementById('panel-right');
|
|
17
|
+
const panelAi = document.getElementById('panel-ai');
|
|
18
|
+
const btnToggleLeft = document.getElementById('btn-toggle-left');
|
|
19
|
+
const btnToggleRight = document.getElementById('btn-toggle-right');
|
|
20
|
+
const btnToggleAi = document.getElementById('btn-toggle-ai');
|
|
21
|
+
const btnCloseAi = document.getElementById('btn-close-ai');
|
|
22
|
+
|
|
23
|
+
function refreshEditor() {
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
editor.refresh();
|
|
26
|
+
}, 250);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Toggle left panel (blocks/assets/layers)
|
|
30
|
+
if (btnToggleLeft && panelLeft) {
|
|
31
|
+
btnToggleLeft.addEventListener('click', function() {
|
|
32
|
+
const isOpening = panelLeft.classList.contains('collapsed');
|
|
33
|
+
|
|
34
|
+
if (isOpening && panelAi) {
|
|
35
|
+
// Close AI panel when opening left panel
|
|
36
|
+
panelAi.classList.add('collapsed');
|
|
37
|
+
if (btnToggleAi) btnToggleAi.classList.remove('active');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
panelLeft.classList.toggle('collapsed');
|
|
41
|
+
this.classList.toggle('active', !panelLeft.classList.contains('collapsed'));
|
|
42
|
+
refreshEditor();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Toggle AI panel
|
|
47
|
+
if (btnToggleAi && panelAi) {
|
|
48
|
+
btnToggleAi.addEventListener('click', function() {
|
|
49
|
+
const isOpening = panelAi.classList.contains('collapsed');
|
|
50
|
+
|
|
51
|
+
if (isOpening && panelLeft) {
|
|
52
|
+
// Close left panel when opening AI panel
|
|
53
|
+
panelLeft.classList.add('collapsed');
|
|
54
|
+
if (btnToggleLeft) btnToggleLeft.classList.remove('active');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
panelAi.classList.toggle('collapsed');
|
|
58
|
+
this.classList.toggle('active', !panelAi.classList.contains('collapsed'));
|
|
59
|
+
refreshEditor();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Close AI panel button
|
|
64
|
+
if (btnCloseAi && panelAi) {
|
|
65
|
+
btnCloseAi.addEventListener('click', function() {
|
|
66
|
+
panelAi.classList.add('collapsed');
|
|
67
|
+
if (btnToggleAi) btnToggleAi.classList.remove('active');
|
|
68
|
+
refreshEditor();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Toggle right panel
|
|
73
|
+
if (btnToggleRight && panelRight) {
|
|
74
|
+
btnToggleRight.addEventListener('click', function() {
|
|
75
|
+
panelRight.classList.toggle('collapsed');
|
|
76
|
+
this.classList.toggle('active', !panelRight.classList.contains('collapsed'));
|
|
77
|
+
refreshEditor();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Panel tab switching
|
|
82
|
+
document.querySelectorAll('.panel-tab').forEach(tab => {
|
|
83
|
+
tab.addEventListener('click', function() {
|
|
84
|
+
const panel = this.dataset.panel;
|
|
85
|
+
const parent = this.closest('.editor-panel-left, .editor-panel-right');
|
|
86
|
+
|
|
87
|
+
// Update active tab
|
|
88
|
+
parent.querySelectorAll('.panel-tab').forEach(t => t.classList.remove('active'));
|
|
89
|
+
this.classList.add('active');
|
|
90
|
+
|
|
91
|
+
// Show/hide content
|
|
92
|
+
if (parent.classList.contains('editor-panel-left')) {
|
|
93
|
+
const blocksContainer = document.getElementById('blocks-container');
|
|
94
|
+
const assetsContainer = document.getElementById('assets-container');
|
|
95
|
+
const layersContainer = document.getElementById('layers-container');
|
|
96
|
+
|
|
97
|
+
if (blocksContainer) blocksContainer.style.display = panel === 'blocks' ? 'block' : 'none';
|
|
98
|
+
if (assetsContainer) assetsContainer.style.display = panel === 'assets' ? 'block' : 'none';
|
|
99
|
+
if (layersContainer) layersContainer.style.display = panel === 'layers' ? 'block' : 'none';
|
|
100
|
+
} else {
|
|
101
|
+
const stylesContainer = document.getElementById('styles-container');
|
|
102
|
+
const traitsContainer = document.getElementById('traits-container');
|
|
103
|
+
|
|
104
|
+
if (stylesContainer) stylesContainer.style.display = panel === 'styles' ? 'block' : 'none';
|
|
105
|
+
if (traitsContainer) traitsContainer.style.display = panel === 'settings' ? 'block' : 'none';
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Setup device switching for responsive preview
|
|
113
|
+
* @param {Object} editor - GrapeJS editor instance
|
|
114
|
+
*/
|
|
115
|
+
function setupDeviceSwitching(editor) {
|
|
116
|
+
document.querySelectorAll('.device-btn').forEach(btn => {
|
|
117
|
+
btn.addEventListener('click', function() {
|
|
118
|
+
const device = this.dataset.device;
|
|
119
|
+
document.querySelectorAll('.device-btn').forEach(b => b.classList.remove('active'));
|
|
120
|
+
this.classList.add('active');
|
|
121
|
+
|
|
122
|
+
const deviceMap = {
|
|
123
|
+
'desktop': 'Desktop',
|
|
124
|
+
'tablet': 'Tablet',
|
|
125
|
+
'mobile': 'Mobile'
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Set the device in GrapeJS
|
|
129
|
+
editor.setDevice(deviceMap[device]);
|
|
130
|
+
|
|
131
|
+
// Refresh the canvas to ensure proper rendering
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
editor.refresh();
|
|
134
|
+
}, 100);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Set initial device to Desktop on load
|
|
139
|
+
editor.on('load', () => {
|
|
140
|
+
editor.setDevice('Desktop');
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Setup undo/redo buttons
|
|
146
|
+
* @param {Object} editor - GrapeJS editor instance
|
|
147
|
+
*/
|
|
148
|
+
function setupUndoRedo(editor) {
|
|
149
|
+
const undoBtn = document.getElementById('btn-undo');
|
|
150
|
+
const redoBtn = document.getElementById('btn-redo');
|
|
151
|
+
|
|
152
|
+
if (undoBtn) {
|
|
153
|
+
undoBtn.addEventListener('click', () => {
|
|
154
|
+
editor.UndoManager.undo();
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (redoBtn) {
|
|
159
|
+
redoBtn.addEventListener('click', () => {
|
|
160
|
+
editor.UndoManager.redo();
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Setup save functionality
|
|
167
|
+
* @param {Object} editor - GrapeJS editor instance
|
|
168
|
+
* @param {Object} config - Editor configuration
|
|
169
|
+
* @param {string} csrfToken - CSRF token for requests
|
|
170
|
+
*/
|
|
171
|
+
function setupSave(editor, config, csrfToken) {
|
|
172
|
+
const { showToast, showLoading } = window.ActiveCanvasEditor;
|
|
173
|
+
|
|
174
|
+
const saveBtn = document.getElementById('btn-save');
|
|
175
|
+
if (saveBtn) {
|
|
176
|
+
saveBtn.addEventListener('click', () => saveContent(false));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Keyboard shortcuts
|
|
180
|
+
document.addEventListener('keydown', function(e) {
|
|
181
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
|
182
|
+
e.preventDefault();
|
|
183
|
+
saveContent(false);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Auto-save every 60 seconds
|
|
188
|
+
setInterval(() => {
|
|
189
|
+
saveContent(true);
|
|
190
|
+
}, 60000);
|
|
191
|
+
|
|
192
|
+
function saveContent(isAutoSave) {
|
|
193
|
+
const saveBtn = document.getElementById('btn-save');
|
|
194
|
+
if (saveBtn) saveBtn.disabled = true;
|
|
195
|
+
|
|
196
|
+
const html = editor.getHtml();
|
|
197
|
+
const css = editor.getCss();
|
|
198
|
+
const js = window.ActiveCanvasEditor.getJs ? window.ActiveCanvasEditor.getJs() : '';
|
|
199
|
+
const components = JSON.stringify(editor.getComponents());
|
|
200
|
+
|
|
201
|
+
// Use entityType from config (defaults to 'page' for backwards compatibility)
|
|
202
|
+
const entityType = config.entityType || 'page';
|
|
203
|
+
const payload = {};
|
|
204
|
+
payload[entityType] = {
|
|
205
|
+
content: html,
|
|
206
|
+
content_css: css,
|
|
207
|
+
content_js: js,
|
|
208
|
+
content_components: components
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
fetch(config.saveUrl, {
|
|
212
|
+
method: 'PATCH',
|
|
213
|
+
headers: {
|
|
214
|
+
'Content-Type': 'application/json',
|
|
215
|
+
'X-CSRF-Token': csrfToken,
|
|
216
|
+
'Accept': 'application/json'
|
|
217
|
+
},
|
|
218
|
+
body: JSON.stringify(payload)
|
|
219
|
+
})
|
|
220
|
+
.then(response => response.json())
|
|
221
|
+
.then(result => {
|
|
222
|
+
if (result.success) {
|
|
223
|
+
// Build save message with Tailwind compilation info
|
|
224
|
+
let message = isAutoSave ? 'Auto-saved' : 'Page saved successfully';
|
|
225
|
+
|
|
226
|
+
if (result.tailwind && result.tailwind.compiled) {
|
|
227
|
+
if (result.tailwind.success) {
|
|
228
|
+
const sizeKb = (result.tailwind.css_size / 1024).toFixed(1);
|
|
229
|
+
message += ` · Tailwind compiled (${sizeKb}KB in ${result.tailwind.elapsed_ms}ms)`;
|
|
230
|
+
} else {
|
|
231
|
+
showToast('Page saved, but Tailwind compilation failed: ' + result.tailwind.error, 'warning');
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
showToast(message, 'success');
|
|
237
|
+
} else {
|
|
238
|
+
showToast(result.errors ? result.errors.join(', ') : 'Save failed', 'error');
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
.catch(error => {
|
|
242
|
+
showToast('Save failed', 'error');
|
|
243
|
+
console.error('Save error:', error);
|
|
244
|
+
})
|
|
245
|
+
.finally(() => {
|
|
246
|
+
if (saveBtn) saveBtn.disabled = false;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Setup add section button
|
|
253
|
+
* @param {Object} editor - GrapeJS editor instance
|
|
254
|
+
*/
|
|
255
|
+
function setupAddSection(editor) {
|
|
256
|
+
const btnAddSection = document.getElementById('btn-add-section');
|
|
257
|
+
|
|
258
|
+
if (!btnAddSection) return;
|
|
259
|
+
|
|
260
|
+
btnAddSection.addEventListener('click', function() {
|
|
261
|
+
// Add a new empty section at the end of the page
|
|
262
|
+
const wrapper = editor.getWrapper();
|
|
263
|
+
|
|
264
|
+
const section = wrapper.append({
|
|
265
|
+
tagName: 'section',
|
|
266
|
+
classes: ['ac-section'],
|
|
267
|
+
style: {
|
|
268
|
+
'min-height': '100px',
|
|
269
|
+
'padding': '2rem'
|
|
270
|
+
},
|
|
271
|
+
content: ''
|
|
272
|
+
})[0];
|
|
273
|
+
|
|
274
|
+
// Select the new section
|
|
275
|
+
editor.select(section);
|
|
276
|
+
|
|
277
|
+
// Scroll to the new section in the canvas
|
|
278
|
+
const frame = editor.Canvas.getFrameEl();
|
|
279
|
+
if (frame && frame.contentWindow) {
|
|
280
|
+
const el = section.getEl();
|
|
281
|
+
if (el) {
|
|
282
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Setup canvas injection for global CSS/JS
|
|
290
|
+
* @param {Object} editor - GrapeJS editor instance
|
|
291
|
+
* @param {Object} config - Editor configuration
|
|
292
|
+
*/
|
|
293
|
+
function setupCanvasInjection(editor, config) {
|
|
294
|
+
editor.on('load', () => {
|
|
295
|
+
setTimeout(() => {
|
|
296
|
+
const frame = editor.Canvas.getFrameEl();
|
|
297
|
+
if (!frame || !frame.contentDocument) return;
|
|
298
|
+
|
|
299
|
+
// Inject Tailwind config before the CDN script (if using Tailwind)
|
|
300
|
+
if (config.cssFramework === 'tailwind' && config.tailwindConfig) {
|
|
301
|
+
const tailwindConfigScript = frame.contentDocument.createElement('script');
|
|
302
|
+
tailwindConfigScript.id = 'active-canvas-tailwind-config';
|
|
303
|
+
tailwindConfigScript.textContent = 'tailwind.config = ' + JSON.stringify(config.tailwindConfig) + ';';
|
|
304
|
+
// Insert at the beginning of head so it's available before Tailwind CDN loads
|
|
305
|
+
frame.contentDocument.head.insertBefore(tailwindConfigScript, frame.contentDocument.head.firstChild);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Inject editor-specific CSS (for empty sections, etc.)
|
|
309
|
+
const editorStyle = frame.contentDocument.createElement('style');
|
|
310
|
+
editorStyle.id = 'active-canvas-editor-css';
|
|
311
|
+
editorStyle.textContent = `
|
|
312
|
+
/* Empty section placeholder styling */
|
|
313
|
+
section:empty,
|
|
314
|
+
.ac-section:empty,
|
|
315
|
+
[data-gjs-type="section"]:empty {
|
|
316
|
+
min-height: 100px !important;
|
|
317
|
+
background-color: #fef2f2 !important;
|
|
318
|
+
border: 2px dashed #fca5a5 !important;
|
|
319
|
+
display: flex !important;
|
|
320
|
+
align-items: center !important;
|
|
321
|
+
justify-content: center !important;
|
|
322
|
+
position: relative;
|
|
323
|
+
}
|
|
324
|
+
section:empty::after,
|
|
325
|
+
.ac-section:empty::after,
|
|
326
|
+
[data-gjs-type="section"]:empty::after {
|
|
327
|
+
content: "Empty section - drag content here";
|
|
328
|
+
color: #f87171;
|
|
329
|
+
font-size: 14px;
|
|
330
|
+
font-weight: 500;
|
|
331
|
+
}
|
|
332
|
+
`;
|
|
333
|
+
frame.contentDocument.head.appendChild(editorStyle);
|
|
334
|
+
|
|
335
|
+
// Inject global CSS
|
|
336
|
+
if (config.globalCss && config.globalCss.trim()) {
|
|
337
|
+
const globalStyle = frame.contentDocument.createElement('style');
|
|
338
|
+
globalStyle.id = 'active-canvas-global-css';
|
|
339
|
+
globalStyle.textContent = config.globalCss;
|
|
340
|
+
frame.contentDocument.head.appendChild(globalStyle);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Inject global JS
|
|
344
|
+
if (config.globalJs && config.globalJs.trim()) {
|
|
345
|
+
const globalScript = frame.contentDocument.createElement('script');
|
|
346
|
+
globalScript.id = 'active-canvas-global-js';
|
|
347
|
+
globalScript.textContent = config.globalJs;
|
|
348
|
+
frame.contentDocument.body.appendChild(globalScript);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Inject page-specific JS
|
|
352
|
+
if (config.contentJs && config.contentJs.trim()) {
|
|
353
|
+
const script = frame.contentDocument.createElement('script');
|
|
354
|
+
script.id = 'active-canvas-custom-js';
|
|
355
|
+
script.textContent = config.contentJs;
|
|
356
|
+
frame.contentDocument.body.appendChild(script);
|
|
357
|
+
}
|
|
358
|
+
}, 500);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Setup RTE toolbar visibility management
|
|
364
|
+
* Hides the toolbar when not actively editing text
|
|
365
|
+
* @param {Object} editor - GrapeJS editor instance
|
|
366
|
+
*/
|
|
367
|
+
function setupRteToolbar(editor) {
|
|
368
|
+
let rteActive = false;
|
|
369
|
+
|
|
370
|
+
// Function to hide RTE toolbar
|
|
371
|
+
const hideRteToolbar = () => {
|
|
372
|
+
rteActive = false;
|
|
373
|
+
const rteToolbar = document.querySelector('.gjs-rte-toolbar');
|
|
374
|
+
if (rteToolbar) {
|
|
375
|
+
rteToolbar.classList.add('ac-rte-hidden');
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// Function to show RTE toolbar
|
|
380
|
+
const showRteToolbar = () => {
|
|
381
|
+
const rteToolbar = document.querySelector('.gjs-rte-toolbar');
|
|
382
|
+
if (rteToolbar) {
|
|
383
|
+
rteToolbar.classList.remove('ac-rte-hidden');
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// Check if component is a text element
|
|
388
|
+
const isTextComponent = (component) => {
|
|
389
|
+
if (!component) return false;
|
|
390
|
+
const type = component.get('type');
|
|
391
|
+
const tagName = (component.get('tagName') || '').toLowerCase();
|
|
392
|
+
|
|
393
|
+
// Text component types
|
|
394
|
+
const textTypes = ['text', 'textnode', 'label'];
|
|
395
|
+
if (textTypes.includes(type)) return true;
|
|
396
|
+
|
|
397
|
+
// Text-like HTML tags
|
|
398
|
+
const textTags = ['p', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'a', 'label', 'li', 'td', 'th', 'blockquote', 'cite', 'em', 'strong', 'b', 'i', 'u'];
|
|
399
|
+
if (textTags.includes(tagName)) return true;
|
|
400
|
+
|
|
401
|
+
return false;
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// Track RTE state
|
|
405
|
+
editor.on('rte:enable', () => {
|
|
406
|
+
rteActive = true;
|
|
407
|
+
showRteToolbar();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
editor.on('rte:disable', () => {
|
|
411
|
+
hideRteToolbar();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Hide when selecting a non-text component
|
|
415
|
+
editor.on('component:selected', (component) => {
|
|
416
|
+
if (!isTextComponent(component)) {
|
|
417
|
+
hideRteToolbar();
|
|
418
|
+
// Also disable RTE if it was active
|
|
419
|
+
if (rteActive) {
|
|
420
|
+
editor.RichTextEditor.disable();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Hide when deselecting
|
|
426
|
+
editor.on('component:deselected', () => {
|
|
427
|
+
hideRteToolbar();
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// Initial hide after editor loads
|
|
431
|
+
editor.on('load', () => {
|
|
432
|
+
setTimeout(hideRteToolbar, 100);
|
|
433
|
+
|
|
434
|
+
// Set up a MutationObserver to catch the toolbar being created
|
|
435
|
+
const observer = new MutationObserver((mutations) => {
|
|
436
|
+
mutations.forEach((mutation) => {
|
|
437
|
+
mutation.addedNodes.forEach((node) => {
|
|
438
|
+
if (node.classList && node.classList.contains('gjs-rte-toolbar')) {
|
|
439
|
+
if (!rteActive) {
|
|
440
|
+
node.classList.add('ac-rte-hidden');
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Expose functions
|
|
452
|
+
window.ActiveCanvasEditor.setupPanelControls = setupPanelControls;
|
|
453
|
+
window.ActiveCanvasEditor.setupDeviceSwitching = setupDeviceSwitching;
|
|
454
|
+
window.ActiveCanvasEditor.setupUndoRedo = setupUndoRedo;
|
|
455
|
+
window.ActiveCanvasEditor.setupSave = setupSave;
|
|
456
|
+
window.ActiveCanvasEditor.setupAddSection = setupAddSection;
|
|
457
|
+
window.ActiveCanvasEditor.setupCanvasInjection = setupCanvasInjection;
|
|
458
|
+
window.ActiveCanvasEditor.setupRteToolbar = setupRteToolbar;
|
|
459
|
+
|
|
460
|
+
})();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActiveCanvas Editor - Utility Functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
(function() {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
window.ActiveCanvasEditor = window.ActiveCanvasEditor || {};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Show a toast notification
|
|
12
|
+
* @param {string} message - The message to display
|
|
13
|
+
* @param {string} type - 'success' or 'error'
|
|
14
|
+
*/
|
|
15
|
+
function showToast(message, type) {
|
|
16
|
+
const toast = document.getElementById('editor-toast');
|
|
17
|
+
if (!toast) return;
|
|
18
|
+
|
|
19
|
+
toast.textContent = message;
|
|
20
|
+
toast.className = 'editor-toast show ' + type;
|
|
21
|
+
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
toast.classList.remove('show');
|
|
24
|
+
}, 3000);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Show or hide the loading overlay
|
|
29
|
+
* @param {boolean} show - Whether to show the loading overlay
|
|
30
|
+
*/
|
|
31
|
+
function showLoading(show) {
|
|
32
|
+
const loading = document.getElementById('editor-loading');
|
|
33
|
+
if (!loading) return;
|
|
34
|
+
|
|
35
|
+
if (show) {
|
|
36
|
+
loading.classList.remove('hidden');
|
|
37
|
+
} else {
|
|
38
|
+
loading.classList.add('hidden');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get CSRF token from meta tag
|
|
44
|
+
* @returns {string} The CSRF token
|
|
45
|
+
*/
|
|
46
|
+
function getCsrfToken() {
|
|
47
|
+
const meta = document.querySelector('meta[name="csrf-token"]');
|
|
48
|
+
return meta ? meta.getAttribute('content') : '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Expose utilities
|
|
52
|
+
window.ActiveCanvasEditor.showToast = showToast;
|
|
53
|
+
window.ActiveCanvasEditor.showLoading = showLoading;
|
|
54
|
+
window.ActiveCanvasEditor.getCsrfToken = getCsrfToken;
|
|
55
|
+
|
|
56
|
+
})();
|