| 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 |
|