PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.15
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.15
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / admin / js / metasync-html-editor.js

metasync-html-editor.js in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.15, at admin/js/metasync-html-editor.js

1,000 lines 38.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * MetaSync HTML Visual Editor
3 *
4 * Initializes GrapesJS editor with custom configuration
5 * for editing raw HTML pages with full visual capabilities.
6 *
7 * @global grapesjs - Loaded from GrapesJS library script
8 * @global metasyncEditor - Injected via wp_localize_script
9 */
10 /* eslint-env browser */
11 /* global grapesjs, metasyncEditor */
12
13 (function($) {
14 'use strict';
15
16 console.log('MetaSync HTML Editor script loaded');
17 console.log('GrapesJS available:', typeof grapesjs !== 'undefined');
18 console.log('jQuery available:', typeof $ !== 'undefined');
19
20 let editor;
21 let hasUnsavedChanges = false;
22
23 $(document).ready(function() {
24 console.log('Document ready, initializing editor...');
25 initializeEditor();
26 initializeEventHandlers();
27 preventAccidentalExit();
28 });
29
30 /**
31 * Initialize GrapesJS editor
32 */
33 function initializeEditor() {
34 const htmlContent = $('#metasync-html-content').val();
35
36 editor = grapesjs.init({
37 container: '#metasync-gjs-editor',
38 fromElement: false,
39 height: 'calc(100vh - 112px)',
40 width: 'auto',
41 storageManager: false, // Disable built-in storage
42
43 // Plugins
44 plugins: ['gjs-blocks-basic'],
45 pluginsOpts: {
46 'gjs-blocks-basic': {}
47 },
48
49 // Enable double-click to edit text
50 allowScripts: 0,
51 showOffsets: 1,
52 noticeOnUnload: 0,
53
54 // Make text components editable
55 richTextEditor: {
56 actions: ['bold', 'italic', 'underline', 'strikethrough', 'link']
57 },
58
59 // Canvas settings
60 canvas: {
61 styles: [],
62 scripts: []
63 },
64
65 // Block Manager
66 blockManager: {
67 blocks: [
68 {
69 id: 'section',
70 label: '<div class="gjs-block-label">Section</div>',
71 content: '<section class="section"><h2>Section</h2><p>Add your content here</p></section>',
72 category: 'Basic',
73 attributes: { class: 'gjs-block-section' }
74 },
75 {
76 id: 'text',
77 label: '<div class="gjs-block-label">Text</div>',
78 content: '<div data-gjs-type="text">Insert your text here</div>',
79 category: 'Basic'
80 },
81 {
82 id: 'image',
83 label: '<div class="gjs-block-label">Image</div>',
84 content: { type: 'image' },
85 category: 'Basic',
86 activate: true
87 },
88 {
89 id: 'button',
90 label: '<div class="gjs-block-label">Button</div>',
91 content: '<a class="button">Button</a>',
92 category: 'Basic'
93 },
94 {
95 id: 'divider',
96 label: '<div class="gjs-block-label">Divider</div>',
97 content: '<hr/>',
98 category: 'Basic'
99 }
100 ]
101 },
102
103 // Style Manager
104 styleManager: {
105 sectors: [
106 {
107 name: 'Colors',
108 open: true,
109 properties: [
110 {
111 name: 'Text Color',
112 property: 'color',
113 type: 'color',
114 defaults: '#000000',
115 list: [
116 { value: '#000000', name: 'Black' },
117 { value: '#ffffff', name: 'White' },
118 { value: '#e53e3e', name: 'Red' },
119 { value: '#dd6b20', name: 'Orange' },
120 { value: '#d69e2e', name: 'Yellow' },
121 { value: '#38a169', name: 'Green' },
122 { value: '#3182ce', name: 'Blue' },
123 { value: '#805ad5', name: 'Purple' },
124 { value: '#d53f8c', name: 'Pink' },
125 { value: '#718096', name: 'Gray' }
126 ]
127 },
128 {
129 name: 'Background Color',
130 property: 'background-color',
131 type: 'color',
132 defaults: 'transparent',
133 list: [
134 { value: 'transparent', name: 'Transparent' },
135 { value: '#ffffff', name: 'White' },
136 { value: '#f7fafc', name: 'Gray 50' },
137 { value: '#edf2f7', name: 'Gray 100' },
138 { value: '#e2e8f0', name: 'Gray 200' },
139 { value: '#cbd5e0', name: 'Gray 300' },
140 { value: '#000000', name: 'Black' },
141 { value: '#e53e3e', name: 'Red' },
142 { value: '#dd6b20', name: 'Orange' },
143 { value: '#d69e2e', name: 'Yellow' },
144 { value: '#38a169', name: 'Green' },
145 { value: '#3182ce', name: 'Blue' },
146 { value: '#805ad5', name: 'Purple' }
147 ]
148 },
149 {
150 name: 'Border Color',
151 property: 'border-color',
152 type: 'color',
153 defaults: '#e2e8f0'
154 },
155 {
156 name: 'Gradient Background',
157 property: 'background',
158 type: 'stack',
159 layerLabel: function (layer, {values}) {
160 const type = values.type || '';
161 return type.charAt(0).toUpperCase() + type.slice(1);
162 },
163 list: [
164 { value: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', name: 'Purple Bliss' },
165 { value: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', name: 'Pink Passion' },
166 { value: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)', name: 'Blue Sky' },
167 { value: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)', name: 'Green Beach' },
168 { value: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)', name: 'Sunset' },
169 { value: 'linear-gradient(135deg, #30cfd0 0%, #330867 100%)', name: 'Deep Ocean' },
170 { value: 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)', name: 'Pastel Dream' },
171 { value: 'linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%)', name: 'Pink Lady' },
172 { value: 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)', name: 'Peach' },
173 { value: 'linear-gradient(135deg, #ff6e7f 0%, #bfe9ff 100%)', name: 'Cool Sunset' }
174 ]
175 },
176 {
177 name: 'Opacity',
178 property: 'opacity',
179 type: 'slider',
180 defaults: 1,
181 step: 0.01,
182 max: 1,
183 min: 0
184 }
185 ]
186 },
187 {
188 name: 'General',
189 open: false,
190 properties: [
191 'display',
192 'position',
193 'top',
194 'right',
195 'left',
196 'bottom'
197 ]
198 },
199 {
200 name: 'Dimensions',
201 open: false,
202 properties: [
203 'width',
204 'height',
205 'max-width',
206 'min-width',
207 'max-height',
208 'min-height',
209 'margin',
210 'padding'
211 ]
212 },
213 {
214 name: 'Typography',
215 open: false,
216 properties: [
217 {
218 name: 'Font Family',
219 property: 'font-family',
220 type: 'select',
221 defaults: 'Arial, sans-serif',
222 list: [
223 { value: 'Arial, sans-serif', name: 'Arial' },
224 { value: 'Georgia, serif', name: 'Georgia' },
225 { value: 'Impact, sans-serif', name: 'Impact' },
226 { value: 'Tahoma, sans-serif', name: 'Tahoma' },
227 { value: '"Times New Roman", serif', name: 'Times New Roman' },
228 { value: 'Verdana, sans-serif', name: 'Verdana' },
229 { value: '"Courier New", monospace', name: 'Courier New' },
230 { value: '"Lucida Console", monospace', name: 'Lucida Console' },
231 { value: '"Trebuchet MS", sans-serif', name: 'Trebuchet MS' },
232 { value: '"Helvetica Neue", Helvetica, Arial, sans-serif', name: 'Helvetica' }
233 ]
234 },
235 'font-size',
236 'font-weight',
237 'letter-spacing',
238 'line-height',
239 'text-align',
240 'text-decoration',
241 {
242 name: 'Text Shadow',
243 property: 'text-shadow',
244 type: 'stack',
245 layerLabel: function (layer, {values}) {
246 return 'Shadow';
247 },
248 list: [
249 { value: '2px 2px 4px rgba(0,0,0,0.3)', name: 'Soft Shadow' },
250 { value: '0 0 10px rgba(0,0,0,0.5)', name: 'Glow' },
251 { value: '3px 3px 0px rgba(0,0,0,1)', name: 'Hard Shadow' },
252 { value: '0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb', name: '3D Text' }
253 ]
254 }
255 ]
256 },
257 {
258 name: 'Decorations',
259 open: false,
260 properties: [
261 {
262 name: 'Border Radius',
263 property: 'border-radius',
264 type: 'composite',
265 defaults: '0',
266 properties: [
267 { name: 'Top Left', property: 'border-top-left-radius', type: 'integer', units: ['px', '%'], defaults: '0' },
268 { name: 'Top Right', property: 'border-top-right-radius', type: 'integer', units: ['px', '%'], defaults: '0' },
269 { name: 'Bottom Right', property: 'border-bottom-right-radius', type: 'integer', units: ['px', '%'], defaults: '0' },
270 { name: 'Bottom Left', property: 'border-bottom-left-radius', type: 'integer', units: ['px', '%'], defaults: '0' }
271 ],
272 list: [
273 { value: '0', name: 'None' },
274 { value: '4px', name: 'Small' },
275 { value: '8px', name: 'Medium' },
276 { value: '16px', name: 'Large' },
277 { value: '50%', name: 'Circle' }
278 ]
279 },
280 'border',
281 {
282 name: 'Box Shadow',
283 property: 'box-shadow',
284 type: 'stack',
285 layerLabel: function (layer, {values}) {
286 return 'Shadow';
287 },
288 list: [
289 { value: 'none', name: 'None' },
290 { value: '0 1px 3px 0 rgba(0, 0, 0, 0.1)', name: 'Small' },
291 { value: '0 4px 6px -1px rgba(0, 0, 0, 0.1)', name: 'Medium' },
292 { value: '0 10px 15px -3px rgba(0, 0, 0, 0.1)', name: 'Large' },
293 { value: '0 20px 25px -5px rgba(0, 0, 0, 0.1)', name: 'X-Large' },
294 { value: '0 0 0 3px rgba(66, 153, 225, 0.5)', name: 'Outline Blue' },
295 { value: '0 0 15px rgba(0, 0, 0, 0.2)', name: 'Glow' },
296 { value: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)', name: 'Inner' }
297 ]
298 }
299 ]
300 },
301 {
302 name: 'Effects',
303 open: false,
304 properties: [
305 {
306 name: 'Transition',
307 property: 'transition',
308 type: 'stack',
309 list: [
310 { value: 'all 0.3s ease', name: 'Fast' },
311 { value: 'all 0.5s ease', name: 'Normal' },
312 { value: 'all 0.8s ease', name: 'Slow' },
313 { value: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', name: 'Smooth' }
314 ]
315 },
316 'perspective',
317 {
318 name: 'Transform',
319 property: 'transform',
320 type: 'composite',
321 properties: [
322 { name: 'Rotate', property: 'rotate', type: 'integer', units: ['deg'], defaults: '0' },
323 { name: 'Scale X', property: 'scale-x', type: 'number', defaults: '1' },
324 { name: 'Scale Y', property: 'scale-y', type: 'number', defaults: '1' }
325 ]
326 },
327 {
328 name: 'Filter',
329 property: 'filter',
330 type: 'composite',
331 properties: [
332 { name: 'Blur', property: 'blur', type: 'integer', units: ['px'], defaults: '0' },
333 { name: 'Brightness', property: 'brightness', type: 'integer', units: ['%'], defaults: '100' },
334 { name: 'Contrast', property: 'contrast', type: 'integer', units: ['%'], defaults: '100' },
335 { name: 'Grayscale', property: 'grayscale', type: 'integer', units: ['%'], defaults: '0' },
336 { name: 'Hue Rotate', property: 'hue-rotate', type: 'integer', units: ['deg'], defaults: '0' },
337 { name: 'Saturate', property: 'saturate', type: 'integer', units: ['%'], defaults: '100' }
338 ]
339 }
340 ]
341 },
342 {
343 name: 'Flex',
344 open: false,
345 properties: [
346 'flex-direction',
347 'flex-wrap',
348 'justify-content',
349 'align-items',
350 'align-content',
351 'order',
352 'flex-basis',
353 'flex-grow',
354 'flex-shrink',
355 'align-self'
356 ]
357 }
358 ]
359 },
360
361 // Layer Manager
362 layerManager: {},
363
364 // Traits Manager - for editing element properties
365 traitManager: {},
366
367 // Panels
368 panels: {
369 defaults: [
370 {
371 id: 'basic-actions',
372 el: '.gjs-pn-options',
373 buttons: [
374 {
375 id: 'visibility',
376 active: true,
377 className: 'btn-toggle-borders',
378 label: '<i class="fa fa-clone"></i>',
379 command: 'sw-visibility'
380 }
381 ]
382 },
383 {
384 id: 'panel-devices',
385 el: '.gjs-pn-devices',
386 buttons: [
387 {
388 id: 'device-desktop',
389 label: '<i class="fa fa-television"></i>',
390 command: 'set-device-desktop',
391 active: true,
392 togglable: false
393 },
394 {
395 id: 'device-tablet',
396 label: '<i class="fa fa-tablet"></i>',
397 command: 'set-device-tablet',
398 togglable: false
399 },
400 {
401 id: 'device-mobile',
402 label: '<i class="fa fa-mobile"></i>',
403 command: 'set-device-mobile',
404 togglable: false
405 }
406 ]
407 }
408 ]
409 },
410
411 // Device Manager
412 deviceManager: {
413 devices: [
414 {
415 name: 'Desktop',
416 width: ''
417 },
418 {
419 name: 'Tablet',
420 width: '768px',
421 widthMedia: '992px'
422 },
423 {
424 name: 'Mobile',
425 width: '375px',
426 widthMedia: '480px'
427 }
428 ]
429 },
430
431 // Selector Manager
432 selectorManager: {
433 appendTo: ''
434 }
435 });
436
437 // Create and show the right sidebar panel container
438 const editorEl = editor.getContainer();
439 let viewsContainer = editorEl.querySelector('.gjs-pn-views-container');
440
441 if (!viewsContainer) {
442 viewsContainer = document.createElement('div');
443 viewsContainer.className = 'gjs-pn-views-container';
444 editorEl.appendChild(viewsContainer);
445
446 const viewsInner = document.createElement('div');
447 viewsInner.className = 'gjs-pn-views';
448 viewsContainer.appendChild(viewsInner);
449 }
450
451 // Add sectors to Style Manager first
452 const sm = editor.StyleManager;
453 sm.addSector('colors', {
454 name: 'Colors',
455 open: true,
456 properties: [
457 {
458 name: 'Text Color',
459 property: 'color',
460 type: 'color'
461 },
462 {
463 name: 'Background Color',
464 property: 'background-color',
465 type: 'color'
466 },
467 {
468 name: 'Border Color',
469 property: 'border-color',
470 type: 'color'
471 },
472 {
473 name: 'Opacity',
474 property: 'opacity',
475 type: 'slider',
476 defaults: 1,
477 step: 0.01,
478 max: 1,
479 min: 0
480 }
481 ]
482 });
483
484 sm.addSector('typography', {
485 name: 'Typography',
486 open: false,
487 properties: [
488 'font-family',
489 'font-size',
490 'font-weight',
491 'letter-spacing',
492 'line-height',
493 'text-align'
494 ]
495 });
496
497 sm.addSector('decorations', {
498 name: 'Decorations',
499 open: false,
500 properties: [
501 'border-radius',
502 'border',
503 'box-shadow'
504 ]
505 });
506
507 sm.addSector('dimensions', {
508 name: 'Dimensions',
509 open: false,
510 properties: [
511 'width',
512 'height',
513 'max-width',
514 'min-width',
515 'padding',
516 'margin'
517 ]
518 });
519
520 console.log('Style Manager sectors added:', sm.getSectors().length);
521
522 // Render the Style Manager immediately after adding sectors
523 const smEl = sm.render().el;
524 console.log('Style Manager rendered, sectors in element:', $(smEl).find('.gjs-sm-sector').length);
525
526 // Append panels to the container
527 setTimeout(function() {
528 const $views = $('.gjs-pn-views');
529 if ($views.length) {
530 console.log('Views container found, appending panels...');
531
532 // Append already-rendered Style Manager
533 $(smEl).show().css({'display': 'block', 'visibility': 'visible'});
534 $views.append(smEl);
535 console.log('Style Manager appended to sidebar');
536
537 // Append Trait Manager (initially hidden)
538 const tmEl = editor.TraitManager.render().el;
539 $(tmEl).hide();
540 $views.append(tmEl);
541 console.log('Trait Manager appended');
542
543 // Append Layer Manager (initially hidden)
544 const lmEl = editor.LayerManager.render().el;
545 $(lmEl).hide();
546 $views.append(lmEl);
547 console.log('Layer Manager appended');
548
549 // Append Block Manager (initially hidden)
550 const bmEl = editor.BlockManager.render().el;
551 $(bmEl).hide();
552 $views.append(bmEl);
553 console.log('Block Manager appended');
554
555 console.log('All panels appended to sidebar');
556 } else {
557 console.error('Views container not found!');
558 }
559 }, 300);
560
561 // Load HTML content
562 editor.setComponents(htmlContent);
563
564 // Extract and load styles
565 const styleMatch = htmlContent.match(/<style[^>]*>([\s\S]*?)<\/style>/gi);
566 if (styleMatch) {
567 let allStyles = '';
568 styleMatch.forEach(style => {
569 allStyles += style.replace(/<\/?style[^>]*>/gi, '');
570 });
571 editor.setStyle(allStyles);
572 }
573
574 // Add panel switcher buttons after editor initializes
575 const panelManager = editor.Panels;
576 const viewsPanel = panelManager.addPanel({
577 id: 'panel-switcher'
578 });
579
580 viewsPanel.get('buttons').add([
581 {
582 id: 'show-style',
583 active: true,
584 label: '<i class="fa fa-paint-brush"></i><div class="gjs-pn-label">Styles</div>',
585 command: 'show-styles',
586 togglable: false
587 },
588 {
589 id: 'show-traits',
590 label: '<i class="fa fa-cog"></i><div class="gjs-pn-label">Settings</div>',
591 command: 'show-traits',
592 togglable: false
593 },
594 {
595 id: 'show-layers',
596 label: '<i class="fa fa-bars"></i><div class="gjs-pn-label">Layers</div>',
597 command: 'show-layers',
598 togglable: false
599 },
600 {
601 id: 'show-blocks',
602 label: '<i class="fa fa-th-large"></i><div class="gjs-pn-label">Blocks</div>',
603 command: 'show-blocks',
604 togglable: false
605 }
606 ]);
607
608 // Move panel to the right sidebar
609 setTimeout(function() {
610 const $viewsContainer = $('.gjs-pn-views');
611 if ($viewsContainer.length) {
612 $('#panel-switcher').prependTo($viewsContainer);
613 }
614 }, 100);
615
616 // Enhance component types with better traits
617 editor.DomComponents.addType('text', {
618 model: {
619 defaults: {
620 traits: [
621 {
622 type: 'text',
623 name: 'content',
624 label: 'Text Content',
625 changeProp: 1
626 }
627 ]
628 }
629 }
630 });
631
632 editor.DomComponents.addType('link', {
633 model: {
634 defaults: {
635 traits: [
636 {
637 type: 'text',
638 name: 'href',
639 label: 'URL',
640 placeholder: 'https://example.com'
641 },
642 {
643 type: 'text',
644 name: 'title',
645 label: 'Title'
646 },
647 {
648 type: 'select',
649 name: 'target',
650 label: 'Target',
651 options: [
652 { value: '', name: 'Same Window' },
653 { value: '_blank', name: 'New Window' },
654 { value: '_parent', name: 'Parent Frame' },
655 { value: '_top', name: 'Top Frame' }
656 ]
657 }
658 ]
659 }
660 }
661 });
662
663 editor.DomComponents.addType('image', {
664 model: {
665 defaults: {
666 traits: [
667 {
668 type: 'text',
669 name: 'src',
670 label: 'Image URL',
671 placeholder: 'https://example.com/image.jpg'
672 },
673 {
674 type: 'text',
675 name: 'alt',
676 label: 'Alt Text',
677 placeholder: 'Image description'
678 },
679 {
680 type: 'text',
681 name: 'title',
682 label: 'Title'
683 }
684 ]
685 }
686 }
687 });
688
689 // Add device switching commands
690 editor.Commands.add('set-device-desktop', {
691 run: function(editor) {
692 editor.setDevice('Desktop');
693 }
694 });
695
696 editor.Commands.add('set-device-tablet', {
697 run: function(editor) {
698 editor.setDevice('Tablet');
699 }
700 });
701
702 editor.Commands.add('set-device-mobile', {
703 run: function(editor) {
704 editor.setDevice('Mobile');
705 }
706 });
707
708 // Add custom commands for panel switching
709 editor.Commands.add('show-styles', {
710 run: function(editor) {
711 const pnl = editor.Panels.getPanel('panel-switcher');
712 if (pnl) {
713 pnl.get('buttons').each(function(btn) {
714 btn.set('active', btn.id === 'show-style');
715 });
716 }
717
718 const sm = editor.StyleManager;
719 const tm = editor.TraitManager;
720 const lm = editor.LayerManager;
721 const bm = editor.BlockManager;
722
723 sm.render();
724 $('.gjs-pn-views .gjs-sm-sectors').show();
725 $('.gjs-pn-views .gjs-trt-traits').hide();
726 $('.gjs-pn-views .gjs-layers').hide();
727 $('.gjs-pn-views .gjs-blocks-c').hide();
728 }
729 });
730
731 editor.Commands.add('show-traits', {
732 run: function(editor) {
733 const pnl = editor.Panels.getPanel('panel-switcher');
734 if (pnl) {
735 pnl.get('buttons').each(function(btn) {
736 btn.set('active', btn.id === 'show-traits');
737 });
738 }
739
740 const tm = editor.TraitManager;
741 tm.render();
742 $('.gjs-pn-views .gjs-sm-sectors').hide();
743 $('.gjs-pn-views .gjs-trt-traits').show();
744 $('.gjs-pn-views .gjs-layers').hide();
745 $('.gjs-pn-views .gjs-blocks-c').hide();
746 }
747 });
748
749 editor.Commands.add('show-layers', {
750 run: function(editor) {
751 const pnl = editor.Panels.getPanel('panel-switcher');
752 if (pnl) {
753 pnl.get('buttons').each(function(btn) {
754 btn.set('active', btn.id === 'show-layers');
755 });
756 }
757
758 const lm = editor.LayerManager;
759 lm.render();
760 $('.gjs-pn-views .gjs-sm-sectors').hide();
761 $('.gjs-pn-views .gjs-trt-traits').hide();
762 $('.gjs-pn-views .gjs-layers').show();
763 $('.gjs-pn-views .gjs-blocks-c').hide();
764 }
765 });
766
767 editor.Commands.add('show-blocks', {
768 run: function(editor) {
769 const pnl = editor.Panels.getPanel('panel-switcher');
770 if (pnl) {
771 pnl.get('buttons').each(function(btn) {
772 btn.set('active', btn.id === 'show-blocks');
773 });
774 }
775
776 const bm = editor.BlockManager;
777 bm.render();
778 $('.gjs-pn-views .gjs-sm-sectors').hide();
779 $('.gjs-pn-views .gjs-trt-traits').hide();
780 $('.gjs-pn-views .gjs-layers').hide();
781 $('.gjs-pn-views .gjs-blocks-c').show();
782 }
783 });
784
785 // Add selected element indicator
786 setTimeout(function() {
787 const $viewsContainer = $('.gjs-pn-views');
788 if ($viewsContainer.length) {
789 $viewsContainer.prepend('<div id="metasync-selected-element" style="display: none; padding: 16px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #ffffff; font-size: 13px; font-weight: 600; border-bottom: 1px solid #1a1e23;"><div style="font-size: 11px; opacity: 0.8; margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.5px;">Editing Element</div><div id="metasync-element-name"></div></div>');
790 }
791 }, 200);
792
793 // When an element is selected, automatically show the Styles panel
794 editor.on('component:selected', function(component) {
795 // Show selected element indicator
796 const elementType = component.get('type') || 'div';
797 const elementName = component.getName() || elementType;
798 $('#metasync-selected-element').show();
799 $('#metasync-element-name').text(elementName.charAt(0).toUpperCase() + elementName.slice(1));
800
801 // Automatically switch to Styles panel when selecting an element
802 editor.runCommand('show-styles');
803
804 // Open the Colors section by default
805 setTimeout(function() {
806 const $colorsSector = $('.gjs-sm-sector').first();
807 if ($colorsSector.length && !$colorsSector.hasClass('gjs-sm-open')) {
808 $colorsSector.find('.gjs-sm-sector-title').click();
809 }
810 }, 100);
811 });
812
813 // Hide indicator when no element is selected
814 editor.on('component:deselected', function() {
815 $('#metasync-selected-element').hide();
816 });
817
818 // Track changes
819 editor.on('change:changesCount', function() {
820 hasUnsavedChanges = true;
821 updateStatus('unsaved');
822 });
823
824 // Custom image upload
825 editor.on('asset:upload:start', handleImageUpload);
826
827 // Force render all managers to ensure panels appear
828 setTimeout(function() {
829 editor.StyleManager.render();
830 editor.TraitManager.render();
831 editor.LayerManager.render();
832 editor.BlockManager.render();
833
834 // Show the views container
835 $('.gjs-pn-views-container, .gjs-pn-views').show().css({
836 'display': 'block',
837 'visibility': 'visible'
838 });
839
840 // Initialize with styles panel
841 editor.runCommand('show-styles');
842
843 console.log('Panels rendered and displayed');
844 }, 500);
845
846 console.log('MetaSync HTML Editor initialized');
847 }
848
849 /**
850 * Initialize event handlers
851 */
852 function initializeEventHandlers() {
853 // Save button
854 $('.metasync-save-button').on('click', saveHTML);
855
856 // Preview button
857 $('.metasync-preview-button').on('click', openPreview);
858
859 // Keyboard shortcuts
860 $(document).on('keydown', function(e) {
861 // Ctrl/Cmd + S to save
862 if ((e.ctrlKey || e.metaKey) && e.key === 's') {
863 e.preventDefault();
864 saveHTML();
865 }
866 });
867 }
868
869 /**
870 * Save HTML via AJAX
871 */
872 function saveHTML() {
873 const $button = $('.metasync-save-button');
874 const originalText = $button.text();
875
876 // Get HTML and CSS from editor
877 const html = editor.getHtml();
878 const css = editor.getCss();
879
880 // Combine HTML with CSS
881 let fullHTML = html;
882 if (css) {
883 fullHTML = `<style>${css}</style>\n${html}`;
884 }
885
886 // Update button state
887 $button.prop('disabled', true).text(metasyncEditor.i18n.saving);
888 updateStatus('saving');
889
890 // Send AJAX request
891 $.ajax({
892 url: metasyncEditor.ajax_url,
893 type: 'POST',
894 data: {
895 action: 'metasync_save_html',
896 nonce: metasyncEditor.nonce,
897 post_id: metasyncEditor.post_id,
898 html: fullHTML
899 },
900 success: function(response) {
901 if (response.success) {
902 hasUnsavedChanges = false;
903 updateStatus('saved');
904 $button.text(metasyncEditor.i18n.saved);
905
906 setTimeout(function() {
907 $button.text(originalText);
908 updateStatus('ready');
909 }, 2000);
910 } else {
911 alert(response.data.message || metasyncEditor.i18n.error);
912 updateStatus('error');
913 }
914 },
915 error: function() {
916 alert(metasyncEditor.i18n.error);
917 updateStatus('error');
918 },
919 complete: function() {
920 $button.prop('disabled', false);
921 }
922 });
923 }
924
925 /**
926 * Open preview in new tab
927 */
928 function openPreview() {
929 if (hasUnsavedChanges) {
930 if (!confirm('You have unsaved changes. Preview will show the last saved version. Continue?')) {
931 return;
932 }
933 }
934 window.open(metasyncEditor.preview_url, '_blank');
935 }
936
937 /**
938 * Handle image upload
939 */
940 function handleImageUpload(e) {
941 const file = e.target.files[0];
942 if (!file) return;
943
944 const formData = new FormData();
945 formData.append('action', 'metasync_upload_image');
946 formData.append('nonce', metasyncEditor.nonce);
947 formData.append('file', file);
948
949 $.ajax({
950 url: metasyncEditor.ajax_url,
951 type: 'POST',
952 data: formData,
953 processData: false,
954 contentType: false,
955 success: function(response) {
956 if (response.success) {
957 editor.AssetManager.add({ src: response.data.url });
958 } else {
959 alert(response.data.message || 'Upload failed');
960 }
961 }
962 });
963 }
964
965 /**
966 * Update status indicator
967 */
968 function updateStatus(status) {
969 const $indicator = $('.metasync-status-indicator');
970 const $text = $('.metasync-status-text');
971
972 $indicator.removeClass('unsaved saving saved error ready');
973 $indicator.addClass(status);
974
975 const statusText = {
976 ready: 'Ready',
977 unsaved: 'Unsaved changes',
978 saving: 'Saving...',
979 saved: 'Saved!',
980 error: 'Error'
981 };
982
983 $text.text(statusText[status] || 'Ready');
984 }
985
986 /**
987 * Prevent accidental exit with unsaved changes
988 */
989 function preventAccidentalExit() {
990 $(window).on('beforeunload', function(e) {
991 if (hasUnsavedChanges) {
992 const message = metasyncEditor.i18n.confirm_exit;
993 e.returnValue = message;
994 return message;
995 }
996 });
997 }
998
999 })(jQuery);
1000