@myissue/vue-website-page-builder 3.3.63 → 3.3.65
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.
- package/README.md +147 -126
- package/dist/logo/mybuilder_new_lowercase.svg +17558 -0
- package/dist/vue-website-page-builder.css +1 -1
- package/dist/vue-website-page-builder.js +7326 -6943
- package/dist/vue-website-page-builder.umd.cjs +54 -51
- package/package.json +2 -2
- package/src/Components/DemoUnsplash.vue +1 -4
- package/src/Components/PageBuilder/EditorMenu/Editables/BackgroundColorEditor.vue +2 -1
- package/src/Components/PageBuilder/EditorMenu/Editables/BorderRadius.vue +18 -5
- package/src/Components/PageBuilder/EditorMenu/Editables/Borders.vue +6 -4
- package/src/Components/PageBuilder/EditorMenu/Editables/ClassEditor.vue +2 -1
- package/src/Components/PageBuilder/EditorMenu/Editables/EditGetElement.vue +5 -5
- package/src/Components/PageBuilder/EditorMenu/Editables/ManageBackgroundOpacity.vue +7 -8
- package/src/Components/PageBuilder/EditorMenu/Editables/ManageOpacity.vue +2 -2
- package/src/Components/PageBuilder/EditorMenu/Editables/Margin.vue +4 -2
- package/src/Components/PageBuilder/EditorMenu/Editables/Padding.vue +4 -2
- package/src/Components/PageBuilder/EditorMenu/Editables/StyleEditor.vue +115 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/TextColorEditor.vue +2 -1
- package/src/Components/PageBuilder/EditorMenu/Editables/Typography.vue +14 -7
- package/src/Components/PageBuilder/EditorMenu/RightSidebarEditor.vue +56 -64
- package/src/Components/PageBuilder/ToolbarOption/ToolbarOption.vue +10 -7
- package/src/PageBuilder/PageBuilder.vue +89 -63
- package/src/PageBuilder/Preview.vue +25 -9
- package/src/composables/extractCleanHTMLFromPageBuilder.ts +4 -3
- package/src/css/app.css +10 -70
- package/src/services/LocalStorageManager.ts +1 -162
- package/src/services/PageBuilderService.ts +586 -265
- package/src/stores/page-builder-state.ts +8 -0
- package/src/tests/PageBuilderTest.vue +20 -19
- package/src/tests/componentsArray.test.json +3 -3
- package/src/types/index.ts +10 -2
- package/src/utils/html-elements/component.ts +10 -10
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { LocalStorageManager } from './LocalStorageManager'
|
|
2
|
-
|
|
3
|
-
// Type definitions
|
|
4
1
|
import type {
|
|
5
2
|
BuilderResourceData,
|
|
6
3
|
ComponentObject,
|
|
7
4
|
ImageObject,
|
|
8
5
|
PageBuilderConfig,
|
|
6
|
+
PageSettings,
|
|
9
7
|
StartBuilderResult,
|
|
10
8
|
} from '../types'
|
|
11
9
|
import type { usePageBuilderStateStore } from '../stores/page-builder-state'
|
|
@@ -44,26 +42,20 @@ export class PageBuilderService {
|
|
|
44
42
|
private hasStartedEditing: boolean = false
|
|
45
43
|
// Hold data from Database or Backend for updated post
|
|
46
44
|
private originalComponents: BuilderResourceData | undefined = undefined
|
|
47
|
-
// Holds data to be mounted when
|
|
48
|
-
private
|
|
45
|
+
// Holds data to be mounted when pagebuilder is not yet present in the DOM
|
|
46
|
+
private savedMountComponents: BuilderResourceData | null = null
|
|
47
|
+
private pendingMountComponents: BuilderResourceData | null = null
|
|
49
48
|
private isPageBuilderMissingOnStart: boolean = false
|
|
50
|
-
private localStorageManager: LocalStorageManager
|
|
51
49
|
|
|
52
50
|
constructor(pageBuilderStateStore: ReturnType<typeof usePageBuilderStateStore>) {
|
|
53
|
-
this.localStorageManager = new LocalStorageManager(
|
|
54
|
-
pageBuilderStateStore,
|
|
55
|
-
this.sanitizeForLocalStorage,
|
|
56
|
-
)
|
|
57
51
|
this.hasStartedEditing = false
|
|
58
52
|
this.pageBuilderStateStore = pageBuilderStateStore
|
|
59
|
-
|
|
60
|
-
this.getLocalStorageItemName = computed(
|
|
61
|
-
() => this.pageBuilderStateStore.getLocalStorageItemName,
|
|
62
|
-
)
|
|
63
|
-
|
|
64
53
|
this.getApplyImageToSelection = computed(
|
|
65
54
|
() => this.pageBuilderStateStore.getApplyImageToSelection,
|
|
66
55
|
)
|
|
56
|
+
this.getLocalStorageItemName = computed(
|
|
57
|
+
() => this.pageBuilderStateStore.getLocalStorageItemName,
|
|
58
|
+
)
|
|
67
59
|
this.getHyberlinkEnable = computed(() => this.pageBuilderStateStore.getHyberlinkEnable)
|
|
68
60
|
this.getComponents = computed(() => this.pageBuilderStateStore.getComponents)
|
|
69
61
|
|
|
@@ -97,6 +89,14 @@ export class PageBuilderService {
|
|
|
97
89
|
'SPAN',
|
|
98
90
|
'BLOCKQUOTE',
|
|
99
91
|
'BR',
|
|
92
|
+
'PRE',
|
|
93
|
+
'CODE',
|
|
94
|
+
'MARK',
|
|
95
|
+
'DEL',
|
|
96
|
+
'INS',
|
|
97
|
+
'U',
|
|
98
|
+
'FIGURE',
|
|
99
|
+
'FIGCAPTION',
|
|
100
100
|
]
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -104,10 +104,10 @@ export class PageBuilderService {
|
|
|
104
104
|
async clearHtmlSelection(): Promise<void> {
|
|
105
105
|
this.pageBuilderStateStore.setComponent(null)
|
|
106
106
|
this.pageBuilderStateStore.setElement(null)
|
|
107
|
-
await this
|
|
107
|
+
await this.removeHoveredAndSelected()
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
private ensureUpdateOrCreateConfig(config: PageBuilderConfig): void {
|
|
111
111
|
// Case A: updateOrCreate is missing or an empty object
|
|
112
112
|
if (!config.updateOrCreate || (config.updateOrCreate && isEmptyObject(config.updateOrCreate))) {
|
|
113
113
|
const updatedConfig = {
|
|
@@ -183,7 +183,7 @@ export class PageBuilderService {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
|
|
186
|
+
private validateUserProvidedComponents(components: unknown) {
|
|
187
187
|
const formType =
|
|
188
188
|
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
189
189
|
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
@@ -235,7 +235,7 @@ export class PageBuilderService {
|
|
|
235
235
|
return
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
|
|
238
|
+
private validateConfig(config: PageBuilderConfig): void {
|
|
239
239
|
const defaultConfigValues = {
|
|
240
240
|
updateOrCreate: {
|
|
241
241
|
formType: 'create',
|
|
@@ -249,7 +249,7 @@ export class PageBuilderService {
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
if (config && Object.keys(config).length !== 0 && config.constructor === Object) {
|
|
252
|
-
this
|
|
252
|
+
this.ensureUpdateOrCreateConfig(config)
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
|
|
@@ -272,24 +272,26 @@ export class PageBuilderService {
|
|
|
272
272
|
this.pageBuilderStateStore.setBuilderStarted(true)
|
|
273
273
|
const pagebuilder = document.querySelector('#pagebuilder')
|
|
274
274
|
let validation
|
|
275
|
-
|
|
276
275
|
try {
|
|
277
276
|
this.originalComponents = passedComponentsArray
|
|
278
277
|
this.pageBuilderStateStore.setPageBuilderConfig(config)
|
|
279
278
|
// Validate and normalize the config (ensure required fields are present)
|
|
280
|
-
this
|
|
279
|
+
this.validateConfig(config)
|
|
281
280
|
|
|
282
|
-
validation = this
|
|
281
|
+
validation = this.validateUserProvidedComponents(passedComponentsArray)
|
|
283
282
|
|
|
284
283
|
// Update the localStorage key name based on the config/resource
|
|
285
|
-
this.
|
|
284
|
+
this.updateLocalStorageItemName()
|
|
286
285
|
|
|
286
|
+
if (passedComponentsArray) {
|
|
287
|
+
this.savedMountComponents = passedComponentsArray
|
|
288
|
+
}
|
|
287
289
|
// Page Builder is not Present in the DOM but Components have been passed to the Builder
|
|
288
290
|
if (!pagebuilder) {
|
|
289
291
|
this.isPageBuilderMissingOnStart = true
|
|
290
292
|
}
|
|
291
293
|
if (passedComponentsArray && !pagebuilder) {
|
|
292
|
-
this.
|
|
294
|
+
this.pendingMountComponents = passedComponentsArray
|
|
293
295
|
}
|
|
294
296
|
// Page Builder is Present in the DOM & Components have been passed to the Builder
|
|
295
297
|
if (pagebuilder) {
|
|
@@ -297,9 +299,7 @@ export class PageBuilderService {
|
|
|
297
299
|
}
|
|
298
300
|
|
|
299
301
|
// result to end user
|
|
300
|
-
|
|
301
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
302
|
-
const result: any = {
|
|
302
|
+
const result: StartBuilderResult = {
|
|
303
303
|
message: 'Page builder started successfully.',
|
|
304
304
|
}
|
|
305
305
|
|
|
@@ -307,7 +307,7 @@ export class PageBuilderService {
|
|
|
307
307
|
result.validation = validation
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
//
|
|
310
|
+
// PassedComponentsArray
|
|
311
311
|
if (Array.isArray(passedComponentsArray) && passedComponentsArray.length >= 0) {
|
|
312
312
|
result.passedComponentsArray = passedComponentsArray
|
|
313
313
|
}
|
|
@@ -323,7 +323,10 @@ export class PageBuilderService {
|
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
async completeBuilderInitialization(
|
|
326
|
+
async completeBuilderInitialization(
|
|
327
|
+
passedComponentsArray?: BuilderResourceData,
|
|
328
|
+
internalPageBuilderCall?: boolean,
|
|
329
|
+
): Promise<void> {
|
|
327
330
|
this.pageBuilderStateStore.setIsLoadingGlobal(true)
|
|
328
331
|
await delay(400)
|
|
329
332
|
|
|
@@ -333,63 +336,78 @@ export class PageBuilderService {
|
|
|
333
336
|
const config = this.pageBuilderStateStore.getPageBuilderConfig
|
|
334
337
|
const formType = config && config.updateOrCreate && config.updateOrCreate.formType
|
|
335
338
|
|
|
336
|
-
const localStorageData = this.
|
|
339
|
+
const localStorageData = this.getSavedPageHtml()
|
|
337
340
|
|
|
338
341
|
// Deselect any selected or hovered elements in the builder UI
|
|
339
342
|
await this.clearHtmlSelection()
|
|
340
343
|
|
|
341
|
-
//
|
|
342
344
|
if (formType === 'update' || formType === 'create') {
|
|
343
|
-
if (!this.
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
await this
|
|
345
|
+
if (!this.pendingMountComponents) {
|
|
346
|
+
// Page Builder Is initially present in DOM
|
|
347
|
+
if (!passedComponentsArray && this.isPageBuilderMissingOnStart && localStorageData) {
|
|
348
|
+
console.log('1111:', internalPageBuilderCall)
|
|
349
|
+
await this.completeMountProcess(localStorageData)
|
|
348
350
|
return
|
|
349
351
|
}
|
|
350
352
|
if (passedComponentsArray && !localStorageData) {
|
|
351
|
-
|
|
353
|
+
console.log('2222:', internalPageBuilderCall)
|
|
354
|
+
await this.completeMountProcess(JSON.stringify(passedComponentsArray), true)
|
|
355
|
+
this.saveDomComponentsToLocalStorage()
|
|
352
356
|
return
|
|
353
357
|
}
|
|
354
358
|
|
|
355
359
|
if (passedComponentsArray && localStorageData) {
|
|
360
|
+
console.log('3333:', internalPageBuilderCall)
|
|
356
361
|
this.pageBuilderStateStore.setHasLocalDraftForUpdate(true)
|
|
357
|
-
|
|
358
|
-
await this.#completeMountProcess(JSON.stringify(passedComponentsArray), true)
|
|
362
|
+
await this.completeMountProcess(JSON.stringify(passedComponentsArray), true)
|
|
359
363
|
return
|
|
360
364
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
await this
|
|
365
|
+
if (!passedComponentsArray && localStorageData && !this.savedMountComponents) {
|
|
366
|
+
console.log('4444:', internalPageBuilderCall)
|
|
367
|
+
await this.completeMountProcess(localStorageData)
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
if (!passedComponentsArray && this.savedMountComponents && localStorageData) {
|
|
371
|
+
console.log('5555:', internalPageBuilderCall)
|
|
372
|
+
await this.completeMountProcess(JSON.stringify(this.savedMountComponents))
|
|
364
373
|
return
|
|
365
374
|
}
|
|
366
375
|
|
|
367
376
|
if (!passedComponentsArray && !localStorageData && this.isPageBuilderMissingOnStart) {
|
|
368
|
-
|
|
377
|
+
console.log('6666:', internalPageBuilderCall)
|
|
378
|
+
await this.completeMountProcess(JSON.stringify([]))
|
|
369
379
|
return
|
|
370
380
|
}
|
|
371
381
|
|
|
372
382
|
if (!this.isPageBuilderMissingOnStart && !localStorageData && !passedComponentsArray) {
|
|
373
|
-
|
|
383
|
+
console.log('7777:', internalPageBuilderCall)
|
|
384
|
+
await this.completeMountProcess(JSON.stringify([]))
|
|
374
385
|
return
|
|
375
386
|
}
|
|
376
387
|
}
|
|
377
388
|
|
|
378
|
-
// FOCUS ON:
|
|
379
|
-
if (this.
|
|
389
|
+
// FOCUS ON: pendingMountComponents
|
|
390
|
+
if (this.pendingMountComponents) {
|
|
391
|
+
// No Page Builder Is present in DOM initially
|
|
380
392
|
if (localStorageData && this.isPageBuilderMissingOnStart) {
|
|
393
|
+
console.log('8888:', internalPageBuilderCall)
|
|
394
|
+
await this.completeMountProcess(JSON.stringify(this.pendingMountComponents), true)
|
|
395
|
+
await delay(3000)
|
|
381
396
|
this.pageBuilderStateStore.setHasLocalDraftForUpdate(true)
|
|
382
|
-
|
|
383
|
-
this.pendingMountData = null
|
|
397
|
+
this.pendingMountComponents = null
|
|
384
398
|
return
|
|
385
399
|
}
|
|
386
400
|
if (!localStorageData && passedComponentsArray && this.isPageBuilderMissingOnStart) {
|
|
387
|
-
|
|
401
|
+
console.log('9999:', internalPageBuilderCall)
|
|
402
|
+
await this.completeMountProcess(JSON.stringify(this.pendingMountComponents), true)
|
|
403
|
+
this.saveDomComponentsToLocalStorage()
|
|
388
404
|
return
|
|
389
405
|
}
|
|
390
406
|
|
|
391
407
|
if (!passedComponentsArray && !localStorageData && this.isPageBuilderMissingOnStart) {
|
|
392
|
-
|
|
408
|
+
console.log('10000:', internalPageBuilderCall)
|
|
409
|
+
await this.completeMountProcess(JSON.stringify(this.pendingMountComponents), true)
|
|
410
|
+
this.saveDomComponentsToLocalStorage()
|
|
393
411
|
return
|
|
394
412
|
}
|
|
395
413
|
}
|
|
@@ -397,21 +415,22 @@ export class PageBuilderService {
|
|
|
397
415
|
//
|
|
398
416
|
}
|
|
399
417
|
|
|
400
|
-
async
|
|
401
|
-
await this
|
|
402
|
-
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
403
|
-
await nextTick()
|
|
404
|
-
// Attach event listeners to all editable elements in the Builder
|
|
405
|
-
await this.#addListenersToEditableElements()
|
|
418
|
+
private async completeMountProcess(html: string, usePassedPageSettings?: boolean) {
|
|
419
|
+
await this.mountComponentsToDOM(html, usePassedPageSettings)
|
|
406
420
|
|
|
407
421
|
// Clean up any old localStorage items related to previous builder sessions
|
|
408
422
|
this.deleteOldPageBuilderLocalStorage()
|
|
409
423
|
|
|
410
424
|
this.pageBuilderStateStore.setIsRestoring(false)
|
|
411
425
|
this.pageBuilderStateStore.setIsLoadingGlobal(false)
|
|
426
|
+
|
|
427
|
+
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
428
|
+
await nextTick()
|
|
429
|
+
// Attach event listeners to all editable elements in the Builder
|
|
430
|
+
await this.addListenersToEditableElements()
|
|
412
431
|
}
|
|
413
432
|
|
|
414
|
-
|
|
433
|
+
private applyElementClassChanges(
|
|
415
434
|
cssUserSelection: string | undefined,
|
|
416
435
|
CSSArray: string[],
|
|
417
436
|
mutationName: string,
|
|
@@ -461,7 +480,7 @@ export class PageBuilderService {
|
|
|
461
480
|
return currentCSS
|
|
462
481
|
}
|
|
463
482
|
|
|
464
|
-
async clearClassesFromPage() {
|
|
483
|
+
public async clearClassesFromPage() {
|
|
465
484
|
const pagebuilder = document.querySelector('#pagebuilder')
|
|
466
485
|
if (!pagebuilder) return
|
|
467
486
|
|
|
@@ -470,7 +489,7 @@ export class PageBuilderService {
|
|
|
470
489
|
this.initializeElementStyles()
|
|
471
490
|
await nextTick()
|
|
472
491
|
}
|
|
473
|
-
async clearInlineStylesFromPagee() {
|
|
492
|
+
public async clearInlineStylesFromPagee() {
|
|
474
493
|
const pagebuilder = document.querySelector('#pagebuilder')
|
|
475
494
|
if (!pagebuilder) return
|
|
476
495
|
|
|
@@ -480,7 +499,7 @@ export class PageBuilderService {
|
|
|
480
499
|
await nextTick()
|
|
481
500
|
}
|
|
482
501
|
|
|
483
|
-
async globalPageStyles() {
|
|
502
|
+
public async globalPageStyles() {
|
|
484
503
|
const pagebuilder = document.querySelector('#pagebuilder')
|
|
485
504
|
if (!pagebuilder) return
|
|
486
505
|
|
|
@@ -496,19 +515,19 @@ export class PageBuilderService {
|
|
|
496
515
|
await nextTick()
|
|
497
516
|
}
|
|
498
517
|
|
|
499
|
-
handleFontWeight(userSelectedFontWeight?: string): void {
|
|
500
|
-
this
|
|
518
|
+
public handleFontWeight(userSelectedFontWeight?: string): void {
|
|
519
|
+
this.applyElementClassChanges(
|
|
501
520
|
userSelectedFontWeight,
|
|
502
521
|
tailwindFontStyles.fontWeight,
|
|
503
522
|
'setFontWeight',
|
|
504
523
|
)
|
|
505
524
|
}
|
|
506
525
|
|
|
507
|
-
handleFontSizeBase(userSelectedFontSize?: string): void {
|
|
508
|
-
this
|
|
526
|
+
public handleFontSizeBase(userSelectedFontSize?: string): void {
|
|
527
|
+
this.applyElementClassChanges(userSelectedFontSize, tailwindFontSizes.fontBase, 'setFontBase')
|
|
509
528
|
}
|
|
510
529
|
|
|
511
|
-
handleFontSizeDesktop(userSelectedFontSize?: string): void {
|
|
530
|
+
public handleFontSizeDesktop(userSelectedFontSize?: string): void {
|
|
512
531
|
const currentHTMLElement = this.getElement.value
|
|
513
532
|
if (!currentHTMLElement) return
|
|
514
533
|
|
|
@@ -563,12 +582,8 @@ export class PageBuilderService {
|
|
|
563
582
|
}
|
|
564
583
|
}
|
|
565
584
|
|
|
566
|
-
|
|
567
|
-
this
|
|
568
|
-
|
|
569
|
-
if (element.tagName === 'IMG') {
|
|
570
|
-
element.classList.add('smooth-transition')
|
|
571
|
-
}
|
|
585
|
+
private applyHelperCSSToElements(element: HTMLElement): void {
|
|
586
|
+
this.wrapElementInDivIfExcluded(element)
|
|
572
587
|
|
|
573
588
|
// If this is a DIV and its only/main child is a heading, apply font size classes to the DIV
|
|
574
589
|
if (
|
|
@@ -594,20 +609,20 @@ export class PageBuilderService {
|
|
|
594
609
|
}
|
|
595
610
|
}
|
|
596
611
|
|
|
597
|
-
async toggleTipTapModal(status: boolean): Promise<void> {
|
|
612
|
+
public async toggleTipTapModal(status: boolean): Promise<void> {
|
|
598
613
|
this.pageBuilderStateStore.setShowModalTipTap(status)
|
|
599
614
|
|
|
600
615
|
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
601
616
|
await nextTick()
|
|
602
617
|
// Attach event listeners to all editable elements in the Builder
|
|
603
|
-
await this
|
|
618
|
+
await this.addListenersToEditableElements()
|
|
604
619
|
|
|
605
620
|
if (!status) {
|
|
606
621
|
await this.handleAutoSave()
|
|
607
622
|
}
|
|
608
623
|
}
|
|
609
624
|
|
|
610
|
-
|
|
625
|
+
private wrapElementInDivIfExcluded(element: HTMLElement): void {
|
|
611
626
|
if (!element) return
|
|
612
627
|
|
|
613
628
|
if (
|
|
@@ -621,7 +636,7 @@ export class PageBuilderService {
|
|
|
621
636
|
}
|
|
622
637
|
}
|
|
623
638
|
|
|
624
|
-
|
|
639
|
+
private handleElementClick = async (e: Event, element: HTMLElement): Promise<void> => {
|
|
625
640
|
e.preventDefault()
|
|
626
641
|
e.stopPropagation()
|
|
627
642
|
|
|
@@ -645,7 +660,7 @@ export class PageBuilderService {
|
|
|
645
660
|
this.pageBuilderStateStore.setElement(element)
|
|
646
661
|
}
|
|
647
662
|
|
|
648
|
-
|
|
663
|
+
private handleMouseOver = (e: Event, element: HTMLElement): void => {
|
|
649
664
|
e.preventDefault()
|
|
650
665
|
e.stopPropagation()
|
|
651
666
|
|
|
@@ -663,7 +678,7 @@ export class PageBuilderService {
|
|
|
663
678
|
}
|
|
664
679
|
}
|
|
665
680
|
|
|
666
|
-
|
|
681
|
+
private handleMouseLeave = (e: Event): void => {
|
|
667
682
|
e.preventDefault()
|
|
668
683
|
e.stopPropagation()
|
|
669
684
|
|
|
@@ -676,7 +691,7 @@ export class PageBuilderService {
|
|
|
676
691
|
}
|
|
677
692
|
}
|
|
678
693
|
|
|
679
|
-
isEditableElement(el: Element | null): boolean {
|
|
694
|
+
public isEditableElement(el: Element | null): boolean {
|
|
680
695
|
if (!el) return false
|
|
681
696
|
return !this.NoneListernesTags.includes(el.tagName)
|
|
682
697
|
}
|
|
@@ -685,7 +700,7 @@ export class PageBuilderService {
|
|
|
685
700
|
* The function is used to
|
|
686
701
|
* attach event listeners to each element within a 'section'
|
|
687
702
|
*/
|
|
688
|
-
|
|
703
|
+
private addListenersToEditableElements = async () => {
|
|
689
704
|
const elementsWithListeners = new WeakSet<Element>()
|
|
690
705
|
|
|
691
706
|
const pagebuilder = document.querySelector('#pagebuilder')
|
|
@@ -702,9 +717,9 @@ export class PageBuilderService {
|
|
|
702
717
|
// Type assertion to HTMLElement since we know these are DOM elements
|
|
703
718
|
const htmlElement = element as HTMLElement
|
|
704
719
|
// Attach event listeners directly to individual elements
|
|
705
|
-
htmlElement.addEventListener('click', (e) => this
|
|
706
|
-
htmlElement.addEventListener('mouseover', (e) => this
|
|
707
|
-
htmlElement.addEventListener('mouseleave', (e) => this
|
|
720
|
+
htmlElement.addEventListener('click', (e) => this.handleElementClick(e, htmlElement))
|
|
721
|
+
htmlElement.addEventListener('mouseover', (e) => this.handleMouseOver(e, htmlElement))
|
|
722
|
+
htmlElement.addEventListener('mouseleave', (e) => this.handleMouseLeave(e))
|
|
708
723
|
}
|
|
709
724
|
}
|
|
710
725
|
|
|
@@ -712,7 +727,7 @@ export class PageBuilderService {
|
|
|
712
727
|
})
|
|
713
728
|
}
|
|
714
729
|
|
|
715
|
-
handleAutoSave = async () => {
|
|
730
|
+
public handleAutoSave = async () => {
|
|
716
731
|
this.startEditing()
|
|
717
732
|
const passedConfig = this.pageBuilderStateStore.getPageBuilderConfig
|
|
718
733
|
|
|
@@ -730,7 +745,7 @@ export class PageBuilderService {
|
|
|
730
745
|
this.pageBuilderStateStore.setIsSaving(true)
|
|
731
746
|
// Deselect any selected or hovered elements in the builder UI
|
|
732
747
|
//
|
|
733
|
-
this
|
|
748
|
+
this.saveDomComponentsToLocalStorage()
|
|
734
749
|
await delay(400)
|
|
735
750
|
} catch (err) {
|
|
736
751
|
console.error('Error trying auto save.', err)
|
|
@@ -742,7 +757,7 @@ export class PageBuilderService {
|
|
|
742
757
|
if (passedConfig && !passedConfig.userSettings) {
|
|
743
758
|
try {
|
|
744
759
|
this.pageBuilderStateStore.setIsSaving(true)
|
|
745
|
-
this
|
|
760
|
+
this.saveDomComponentsToLocalStorage()
|
|
746
761
|
await delay(400)
|
|
747
762
|
} catch (err) {
|
|
748
763
|
console.error('Error trying saving.', err)
|
|
@@ -752,7 +767,7 @@ export class PageBuilderService {
|
|
|
752
767
|
}
|
|
753
768
|
}
|
|
754
769
|
|
|
755
|
-
handleManualSave = async () => {
|
|
770
|
+
public handleManualSave = async () => {
|
|
756
771
|
this.startEditing()
|
|
757
772
|
const passedConfig = this.pageBuilderStateStore.getPageBuilderConfig
|
|
758
773
|
|
|
@@ -768,7 +783,7 @@ export class PageBuilderService {
|
|
|
768
783
|
passedConfig.userSettings.autoSave)
|
|
769
784
|
) {
|
|
770
785
|
this.pageBuilderStateStore.setIsSaving(true)
|
|
771
|
-
this
|
|
786
|
+
this.saveDomComponentsToLocalStorage()
|
|
772
787
|
await delay(400)
|
|
773
788
|
|
|
774
789
|
this.pageBuilderStateStore.setIsSaving(false)
|
|
@@ -776,19 +791,19 @@ export class PageBuilderService {
|
|
|
776
791
|
}
|
|
777
792
|
if (passedConfig && !passedConfig.userSettings) {
|
|
778
793
|
this.pageBuilderStateStore.setIsSaving(true)
|
|
779
|
-
this
|
|
794
|
+
this.saveDomComponentsToLocalStorage()
|
|
780
795
|
await delay(400)
|
|
781
796
|
|
|
782
797
|
this.pageBuilderStateStore.setIsSaving(false)
|
|
783
798
|
}
|
|
784
799
|
}
|
|
785
800
|
|
|
786
|
-
cloneCompObjForDOMInsertion(componentObject: ComponentObject): ComponentObject {
|
|
801
|
+
public cloneCompObjForDOMInsertion(componentObject: ComponentObject): ComponentObject {
|
|
787
802
|
// Deep clone clone component
|
|
788
803
|
const clonedComponent = { ...componentObject }
|
|
789
804
|
|
|
790
|
-
const pageBuilder = document.querySelector('#
|
|
791
|
-
// scoll to top or bottom
|
|
805
|
+
const pageBuilder = document.querySelector('#pagebuilder')
|
|
806
|
+
// scoll to top or bottom
|
|
792
807
|
if (pageBuilder) {
|
|
793
808
|
// push to top
|
|
794
809
|
if (this.getComponentArrayAddMethod.value === 'unshift') {
|
|
@@ -809,7 +824,7 @@ export class PageBuilderService {
|
|
|
809
824
|
const elements = doc.querySelectorAll('*')
|
|
810
825
|
|
|
811
826
|
elements.forEach((element) => {
|
|
812
|
-
this
|
|
827
|
+
this.applyHelperCSSToElements(element as HTMLElement)
|
|
813
828
|
})
|
|
814
829
|
|
|
815
830
|
// Add the component id to the section element
|
|
@@ -819,7 +834,7 @@ export class PageBuilderService {
|
|
|
819
834
|
section.querySelectorAll('[class]').forEach((el) => {
|
|
820
835
|
el.setAttribute(
|
|
821
836
|
'class',
|
|
822
|
-
this
|
|
837
|
+
this.addTailwindPrefixToClasses(el.getAttribute('class') || '', 'pbx-'),
|
|
823
838
|
)
|
|
824
839
|
})
|
|
825
840
|
|
|
@@ -843,7 +858,7 @@ export class PageBuilderService {
|
|
|
843
858
|
return clonedComponent
|
|
844
859
|
}
|
|
845
860
|
|
|
846
|
-
async
|
|
861
|
+
private async removeHoveredAndSelected() {
|
|
847
862
|
const pagebuilder = document.querySelector('#pagebuilder')
|
|
848
863
|
if (!pagebuilder) return
|
|
849
864
|
|
|
@@ -859,7 +874,7 @@ export class PageBuilderService {
|
|
|
859
874
|
}
|
|
860
875
|
}
|
|
861
876
|
|
|
862
|
-
async
|
|
877
|
+
private async syncCurrentClasses() {
|
|
863
878
|
// convert classList to array
|
|
864
879
|
const classListArray = Array.from(this.getElement.value?.classList || [])
|
|
865
880
|
|
|
@@ -867,7 +882,17 @@ export class PageBuilderService {
|
|
|
867
882
|
this.pageBuilderStateStore.setCurrentClasses(classListArray)
|
|
868
883
|
}
|
|
869
884
|
|
|
870
|
-
|
|
885
|
+
private async syncCurrentStyles() {
|
|
886
|
+
const style = this.getElement.value?.getAttribute('style')
|
|
887
|
+
if (style) {
|
|
888
|
+
const stylesObject = this.parseStyleString(style)
|
|
889
|
+
this.pageBuilderStateStore.setCurrentStyles(stylesObject)
|
|
890
|
+
} else {
|
|
891
|
+
this.pageBuilderStateStore.setCurrentStyles({})
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
public handleAddClasses(userSelectedClass: string): void {
|
|
871
896
|
if (
|
|
872
897
|
typeof userSelectedClass === 'string' &&
|
|
873
898
|
userSelectedClass.trim() !== '' &&
|
|
@@ -886,66 +911,84 @@ export class PageBuilderService {
|
|
|
886
911
|
this.pageBuilderStateStore.setClass(prefixedClass)
|
|
887
912
|
}
|
|
888
913
|
}
|
|
889
|
-
|
|
890
|
-
|
|
914
|
+
|
|
915
|
+
public handleAddStyle(property: string, value: string): void {
|
|
916
|
+
const element = this.getElement.value
|
|
917
|
+
if (!element || !property || !value) return
|
|
918
|
+
|
|
919
|
+
element.style.setProperty(property, value)
|
|
920
|
+
this.pageBuilderStateStore.setElement(element)
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
public handleRemoveStyle(property: string): void {
|
|
924
|
+
console.log('come her:', property)
|
|
925
|
+
const element = this.getElement.value
|
|
926
|
+
if (!element || !property) return
|
|
927
|
+
|
|
928
|
+
element.style.removeProperty(property)
|
|
929
|
+
this.pageBuilderStateStore.setElement(element)
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
public handleFontFamily(userSelectedFontFamily?: string): void {
|
|
933
|
+
this.applyElementClassChanges(
|
|
891
934
|
userSelectedFontFamily,
|
|
892
935
|
tailwindFontStyles.fontFamily,
|
|
893
936
|
'setFontFamily',
|
|
894
937
|
)
|
|
895
938
|
}
|
|
896
|
-
handleFontStyle(userSelectedFontStyle?: string): void {
|
|
897
|
-
this
|
|
939
|
+
public handleFontStyle(userSelectedFontStyle?: string): void {
|
|
940
|
+
this.applyElementClassChanges(
|
|
898
941
|
userSelectedFontStyle,
|
|
899
942
|
tailwindFontStyles.fontStyle,
|
|
900
943
|
'setFontStyle',
|
|
901
944
|
)
|
|
902
945
|
}
|
|
903
|
-
handleVerticalPadding(userSelectedVerticalPadding?: string): void {
|
|
904
|
-
this
|
|
946
|
+
public handleVerticalPadding(userSelectedVerticalPadding?: string): void {
|
|
947
|
+
this.applyElementClassChanges(
|
|
905
948
|
userSelectedVerticalPadding,
|
|
906
949
|
tailwindPaddingAndMargin.verticalPadding,
|
|
907
950
|
'setFontVerticalPadding',
|
|
908
951
|
)
|
|
909
952
|
}
|
|
910
|
-
handleHorizontalPadding(userSelectedHorizontalPadding?: string): void {
|
|
911
|
-
this
|
|
953
|
+
public handleHorizontalPadding(userSelectedHorizontalPadding?: string): void {
|
|
954
|
+
this.applyElementClassChanges(
|
|
912
955
|
userSelectedHorizontalPadding,
|
|
913
956
|
tailwindPaddingAndMargin.horizontalPadding,
|
|
914
957
|
'setFontHorizontalPadding',
|
|
915
958
|
)
|
|
916
959
|
}
|
|
917
960
|
|
|
918
|
-
handleVerticalMargin(userSelectedVerticalMargin?: string): void {
|
|
919
|
-
this
|
|
961
|
+
public handleVerticalMargin(userSelectedVerticalMargin?: string): void {
|
|
962
|
+
this.applyElementClassChanges(
|
|
920
963
|
userSelectedVerticalMargin,
|
|
921
964
|
tailwindPaddingAndMargin.verticalMargin,
|
|
922
965
|
'setFontVerticalMargin',
|
|
923
966
|
)
|
|
924
967
|
}
|
|
925
|
-
handleHorizontalMargin(userSelectedHorizontalMargin?: string): void {
|
|
926
|
-
this
|
|
968
|
+
public handleHorizontalMargin(userSelectedHorizontalMargin?: string): void {
|
|
969
|
+
this.applyElementClassChanges(
|
|
927
970
|
userSelectedHorizontalMargin,
|
|
928
971
|
tailwindPaddingAndMargin.horizontalMargin,
|
|
929
972
|
'setFontHorizontalMargin',
|
|
930
973
|
)
|
|
931
974
|
}
|
|
932
975
|
|
|
933
|
-
handleBorderStyle(borderStyle?: string): void {
|
|
934
|
-
this
|
|
976
|
+
public handleBorderStyle(borderStyle?: string): void {
|
|
977
|
+
this.applyElementClassChanges(
|
|
935
978
|
borderStyle,
|
|
936
979
|
tailwindBorderStyleWidthPlusColor.borderStyle,
|
|
937
980
|
'setBorderStyle',
|
|
938
981
|
)
|
|
939
982
|
}
|
|
940
|
-
handleBorderWidth(borderWidth?: string): void {
|
|
941
|
-
this
|
|
983
|
+
public handleBorderWidth(borderWidth?: string): void {
|
|
984
|
+
this.applyElementClassChanges(
|
|
942
985
|
borderWidth,
|
|
943
986
|
tailwindBorderStyleWidthPlusColor.borderWidth,
|
|
944
987
|
'setBorderWidth',
|
|
945
988
|
)
|
|
946
989
|
}
|
|
947
|
-
handleBorderColor(borderColor?: string): void {
|
|
948
|
-
this
|
|
990
|
+
public handleBorderColor(borderColor?: string): void {
|
|
991
|
+
this.applyElementClassChanges(
|
|
949
992
|
borderColor,
|
|
950
993
|
tailwindBorderStyleWidthPlusColor.borderColor,
|
|
951
994
|
'setBorderColor',
|
|
@@ -953,48 +996,48 @@ export class PageBuilderService {
|
|
|
953
996
|
}
|
|
954
997
|
// border color, style & width / end
|
|
955
998
|
|
|
956
|
-
handleBackgroundColor(color?: string): void {
|
|
957
|
-
this
|
|
999
|
+
public handleBackgroundColor(color?: string): void {
|
|
1000
|
+
this.applyElementClassChanges(
|
|
958
1001
|
color,
|
|
959
1002
|
tailwindColors.backgroundColorVariables,
|
|
960
1003
|
'setBackgroundColor',
|
|
961
1004
|
)
|
|
962
1005
|
}
|
|
963
1006
|
|
|
964
|
-
handleTextColor(color?: string): void {
|
|
965
|
-
this
|
|
1007
|
+
public handleTextColor(color?: string): void {
|
|
1008
|
+
this.applyElementClassChanges(color, tailwindColors.textColorVariables, 'setTextColor')
|
|
966
1009
|
}
|
|
967
1010
|
|
|
968
1011
|
handleBorderRadiusGlobal(borderRadiusGlobal?: string): void {
|
|
969
|
-
this
|
|
1012
|
+
this.applyElementClassChanges(
|
|
970
1013
|
borderRadiusGlobal,
|
|
971
1014
|
tailwindBorderRadius.roundedGlobal,
|
|
972
1015
|
'setBorderRadiusGlobal',
|
|
973
1016
|
)
|
|
974
1017
|
}
|
|
975
1018
|
handleBorderRadiusTopLeft(borderRadiusTopLeft?: string): void {
|
|
976
|
-
this
|
|
1019
|
+
this.applyElementClassChanges(
|
|
977
1020
|
borderRadiusTopLeft,
|
|
978
1021
|
tailwindBorderRadius.roundedTopLeft,
|
|
979
1022
|
'setBorderRadiusTopLeft',
|
|
980
1023
|
)
|
|
981
1024
|
}
|
|
982
1025
|
handleBorderRadiusTopRight(borderRadiusTopRight?: string): void {
|
|
983
|
-
this
|
|
1026
|
+
this.applyElementClassChanges(
|
|
984
1027
|
borderRadiusTopRight,
|
|
985
1028
|
tailwindBorderRadius.roundedTopRight,
|
|
986
1029
|
'setBorderRadiusTopRight',
|
|
987
1030
|
)
|
|
988
1031
|
}
|
|
989
1032
|
handleBorderRadiusBottomleft(borderRadiusBottomleft?: string): void {
|
|
990
|
-
this
|
|
1033
|
+
this.applyElementClassChanges(
|
|
991
1034
|
borderRadiusBottomleft,
|
|
992
1035
|
tailwindBorderRadius.roundedBottomLeft,
|
|
993
1036
|
'setBorderRadiusBottomleft',
|
|
994
1037
|
)
|
|
995
1038
|
}
|
|
996
1039
|
handleBorderRadiusBottomRight(borderRadiusBottomRight?: string): void {
|
|
997
|
-
this
|
|
1040
|
+
this.applyElementClassChanges(
|
|
998
1041
|
borderRadiusBottomRight,
|
|
999
1042
|
tailwindBorderRadius.roundedBottomRight,
|
|
1000
1043
|
'setBorderRadiusBottomRight',
|
|
@@ -1003,14 +1046,14 @@ export class PageBuilderService {
|
|
|
1003
1046
|
// border radius / end
|
|
1004
1047
|
|
|
1005
1048
|
handleFontSizeTablet(userSelectedFontSize?: string): void {
|
|
1006
|
-
this
|
|
1049
|
+
this.applyElementClassChanges(
|
|
1007
1050
|
userSelectedFontSize,
|
|
1008
1051
|
tailwindFontSizes.fontTablet,
|
|
1009
1052
|
'setFontTablet',
|
|
1010
1053
|
)
|
|
1011
1054
|
}
|
|
1012
1055
|
handleFontSizeMobile(userSelectedFontSize?: string): void {
|
|
1013
|
-
this
|
|
1056
|
+
this.applyElementClassChanges(
|
|
1014
1057
|
userSelectedFontSize,
|
|
1015
1058
|
tailwindFontSizes.fontMobile,
|
|
1016
1059
|
'setFontMobile',
|
|
@@ -1018,14 +1061,14 @@ export class PageBuilderService {
|
|
|
1018
1061
|
}
|
|
1019
1062
|
|
|
1020
1063
|
handleBackgroundOpacity(opacity?: string): void {
|
|
1021
|
-
this
|
|
1064
|
+
this.applyElementClassChanges(
|
|
1022
1065
|
opacity,
|
|
1023
1066
|
tailwindOpacities.backgroundOpacities,
|
|
1024
1067
|
'setBackgroundOpacity',
|
|
1025
1068
|
)
|
|
1026
1069
|
}
|
|
1027
1070
|
handleOpacity(opacity?: string): void {
|
|
1028
|
-
this
|
|
1071
|
+
this.applyElementClassChanges(opacity, tailwindOpacities.opacities, 'setOpacity')
|
|
1029
1072
|
}
|
|
1030
1073
|
|
|
1031
1074
|
/**
|
|
@@ -1038,7 +1081,7 @@ export class PageBuilderService {
|
|
|
1038
1081
|
*
|
|
1039
1082
|
*/
|
|
1040
1083
|
|
|
1041
|
-
deleteAllComponentsFromDOM() {
|
|
1084
|
+
private deleteAllComponentsFromDOM() {
|
|
1042
1085
|
// Clear the store
|
|
1043
1086
|
this.pageBuilderStateStore.setComponents([])
|
|
1044
1087
|
|
|
@@ -1052,8 +1095,8 @@ export class PageBuilderService {
|
|
|
1052
1095
|
}
|
|
1053
1096
|
}
|
|
1054
1097
|
|
|
1055
|
-
async deleteComponentFromDOM() {
|
|
1056
|
-
this
|
|
1098
|
+
public async deleteComponentFromDOM() {
|
|
1099
|
+
this.syncDomToStoreOnly()
|
|
1057
1100
|
await nextTick()
|
|
1058
1101
|
|
|
1059
1102
|
const components = this.getComponents.value
|
|
@@ -1089,7 +1132,7 @@ export class PageBuilderService {
|
|
|
1089
1132
|
|
|
1090
1133
|
// Wait for Vue to finish DOM updates before attaching event listeners.
|
|
1091
1134
|
await nextTick()
|
|
1092
|
-
await this
|
|
1135
|
+
await this.addListenersToEditableElements()
|
|
1093
1136
|
|
|
1094
1137
|
this.pageBuilderStateStore.setComponent(null)
|
|
1095
1138
|
this.pageBuilderStateStore.setElement(null)
|
|
@@ -1098,7 +1141,7 @@ export class PageBuilderService {
|
|
|
1098
1141
|
await this.handleAutoSave()
|
|
1099
1142
|
}
|
|
1100
1143
|
|
|
1101
|
-
async deleteElementFromDOM() {
|
|
1144
|
+
public async deleteElementFromDOM() {
|
|
1102
1145
|
const element = this.getElement.value
|
|
1103
1146
|
if (!element) return
|
|
1104
1147
|
|
|
@@ -1129,10 +1172,10 @@ export class PageBuilderService {
|
|
|
1129
1172
|
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
1130
1173
|
await nextTick()
|
|
1131
1174
|
// Attach event listeners to all editable elements in the Builder
|
|
1132
|
-
await this
|
|
1175
|
+
await this.addListenersToEditableElements()
|
|
1133
1176
|
}
|
|
1134
1177
|
|
|
1135
|
-
async restoreDeletedElementToDOM() {
|
|
1178
|
+
public async restoreDeletedElementToDOM() {
|
|
1136
1179
|
// Restore the previously deleted element to the DOM
|
|
1137
1180
|
const restoredHTML = this.getRestoredElement.value
|
|
1138
1181
|
const parent = this.getParentElement.value
|
|
@@ -1158,10 +1201,10 @@ export class PageBuilderService {
|
|
|
1158
1201
|
|
|
1159
1202
|
// Wait for Vue to finish DOM updates before attaching event listeners
|
|
1160
1203
|
await nextTick()
|
|
1161
|
-
await this
|
|
1204
|
+
await this.addListenersToEditableElements()
|
|
1162
1205
|
}
|
|
1163
1206
|
|
|
1164
|
-
handleRemoveClasses(userSelectedClass: string): void {
|
|
1207
|
+
public handleRemoveClasses(userSelectedClass: string): void {
|
|
1165
1208
|
// remove selected class from element
|
|
1166
1209
|
if (this.getElement.value?.classList.contains(userSelectedClass)) {
|
|
1167
1210
|
this.getElement.value?.classList.remove(userSelectedClass)
|
|
@@ -1173,7 +1216,7 @@ export class PageBuilderService {
|
|
|
1173
1216
|
|
|
1174
1217
|
// move component
|
|
1175
1218
|
// runs when html components are rearranged
|
|
1176
|
-
reorderComponent(direction: number): void {
|
|
1219
|
+
public reorderComponent(direction: number): void {
|
|
1177
1220
|
if (!this.getComponents.value || !this.getComponent.value) return
|
|
1178
1221
|
|
|
1179
1222
|
if (this.getComponents.value.length <= 1) return
|
|
@@ -1203,7 +1246,7 @@ export class PageBuilderService {
|
|
|
1203
1246
|
this.getComponents.value.splice(newIndex, 0, componentToMove)
|
|
1204
1247
|
}
|
|
1205
1248
|
|
|
1206
|
-
ensureTextAreaHasContent = () => {
|
|
1249
|
+
public ensureTextAreaHasContent = () => {
|
|
1207
1250
|
if (!this.getElement.value) return
|
|
1208
1251
|
|
|
1209
1252
|
// text content
|
|
@@ -1227,7 +1270,7 @@ export class PageBuilderService {
|
|
|
1227
1270
|
}
|
|
1228
1271
|
}
|
|
1229
1272
|
|
|
1230
|
-
handleTextInput = async (textContentVueModel: string): Promise<void> => {
|
|
1273
|
+
public handleTextInput = async (textContentVueModel: string): Promise<void> => {
|
|
1231
1274
|
if (typeof this.getElement.value?.innerHTML !== 'string') {
|
|
1232
1275
|
return
|
|
1233
1276
|
}
|
|
@@ -1248,7 +1291,7 @@ export class PageBuilderService {
|
|
|
1248
1291
|
|
|
1249
1292
|
//
|
|
1250
1293
|
//
|
|
1251
|
-
ElOrFirstChildIsIframe() {
|
|
1294
|
+
public ElOrFirstChildIsIframe() {
|
|
1252
1295
|
if (
|
|
1253
1296
|
this.getElement.value?.tagName === 'IFRAME' ||
|
|
1254
1297
|
this.getElement.value?.firstElementChild?.tagName === 'IFRAME'
|
|
@@ -1261,7 +1304,7 @@ export class PageBuilderService {
|
|
|
1261
1304
|
//
|
|
1262
1305
|
//
|
|
1263
1306
|
//
|
|
1264
|
-
isSelectedElementValidText() {
|
|
1307
|
+
public isSelectedElementValidText() {
|
|
1265
1308
|
let reachedElseStatement = false
|
|
1266
1309
|
|
|
1267
1310
|
// Get all child elements of the parentDiv
|
|
@@ -1287,7 +1330,7 @@ export class PageBuilderService {
|
|
|
1287
1330
|
return reachedElseStatement
|
|
1288
1331
|
}
|
|
1289
1332
|
|
|
1290
|
-
previewCurrentDesign() {
|
|
1333
|
+
public previewCurrentDesign() {
|
|
1291
1334
|
this.pageBuilderStateStore.setElement(null)
|
|
1292
1335
|
|
|
1293
1336
|
const pagebuilder = document.querySelector('#pagebuilder')
|
|
@@ -1324,7 +1367,7 @@ export class PageBuilderService {
|
|
|
1324
1367
|
* removed from itself and all descendants. Does NOT mutate the live DOM.
|
|
1325
1368
|
* @param element The HTMLElement to clone and sanitize
|
|
1326
1369
|
*/
|
|
1327
|
-
|
|
1370
|
+
private cloneAndRemoveSelectionAttributes(element: HTMLElement): HTMLElement {
|
|
1328
1371
|
// Deep clone the element
|
|
1329
1372
|
const clone = element.cloneNode(true) as HTMLElement
|
|
1330
1373
|
|
|
@@ -1342,14 +1385,14 @@ export class PageBuilderService {
|
|
|
1342
1385
|
* Syncs the current DOM state into the in-memory store (getComponents),
|
|
1343
1386
|
* but does NOT save to localStorage.
|
|
1344
1387
|
*/
|
|
1345
|
-
|
|
1388
|
+
private syncDomToStoreOnly() {
|
|
1346
1389
|
const pagebuilder = document.querySelector('#pagebuilder')
|
|
1347
1390
|
if (!pagebuilder) return
|
|
1348
1391
|
|
|
1349
1392
|
const componentsToSave: { html_code: string; id: string | null; title: string }[] = []
|
|
1350
1393
|
|
|
1351
1394
|
pagebuilder.querySelectorAll('section[data-componentid]').forEach((section) => {
|
|
1352
|
-
const sanitizedSection = this
|
|
1395
|
+
const sanitizedSection = this.cloneAndRemoveSelectionAttributes(section as HTMLElement)
|
|
1353
1396
|
componentsToSave.push({
|
|
1354
1397
|
html_code: sanitizedSection.outerHTML,
|
|
1355
1398
|
id: sanitizedSection.getAttribute('data-componentid'),
|
|
@@ -1363,8 +1406,8 @@ export class PageBuilderService {
|
|
|
1363
1406
|
/**
|
|
1364
1407
|
* Saves the current DOM state (components) to localStorage.
|
|
1365
1408
|
*/
|
|
1366
|
-
|
|
1367
|
-
this.
|
|
1409
|
+
private saveDomComponentsToLocalStorage() {
|
|
1410
|
+
this.updateLocalStorageItemName()
|
|
1368
1411
|
const pagebuilder = document.querySelector('#pagebuilder')
|
|
1369
1412
|
if (!pagebuilder) return
|
|
1370
1413
|
|
|
@@ -1373,14 +1416,13 @@ export class PageBuilderService {
|
|
|
1373
1416
|
hoveredElement.removeAttribute('hovered')
|
|
1374
1417
|
}
|
|
1375
1418
|
|
|
1376
|
-
const componentsToSave: { html_code: string;
|
|
1419
|
+
const componentsToSave: { html_code: string; title: string }[] = []
|
|
1377
1420
|
|
|
1378
1421
|
pagebuilder.querySelectorAll('section[data-componentid]').forEach((section) => {
|
|
1379
|
-
const sanitizedSection = this
|
|
1422
|
+
const sanitizedSection = this.cloneAndRemoveSelectionAttributes(section as HTMLElement)
|
|
1380
1423
|
|
|
1381
1424
|
componentsToSave.push({
|
|
1382
1425
|
html_code: sanitizedSection.outerHTML,
|
|
1383
|
-
id: sanitizedSection.getAttribute('data-componentid'),
|
|
1384
1426
|
title: sanitizedSection.getAttribute('data-component-title') || 'Untitled Component',
|
|
1385
1427
|
})
|
|
1386
1428
|
})
|
|
@@ -1402,8 +1444,8 @@ export class PageBuilderService {
|
|
|
1402
1444
|
localStorage.setItem(keyForSavingFromDomToLocal, JSON.stringify(dataToSave))
|
|
1403
1445
|
}
|
|
1404
1446
|
}
|
|
1405
|
-
async removeCurrentComponentsFromLocalStorage() {
|
|
1406
|
-
this.
|
|
1447
|
+
private async removeCurrentComponentsFromLocalStorage() {
|
|
1448
|
+
this.updateLocalStorageItemName()
|
|
1407
1449
|
await nextTick()
|
|
1408
1450
|
|
|
1409
1451
|
const key = this.getLocalStorageItemName.value
|
|
@@ -1412,7 +1454,26 @@ export class PageBuilderService {
|
|
|
1412
1454
|
}
|
|
1413
1455
|
}
|
|
1414
1456
|
|
|
1415
|
-
|
|
1457
|
+
public async handleFormSubmission() {
|
|
1458
|
+
await this.removeCurrentComponentsFromLocalStorage()
|
|
1459
|
+
this.deleteAllComponentsFromDOM()
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
private parseStyleString(style: string): Record<string, string> {
|
|
1463
|
+
return style
|
|
1464
|
+
.split(';')
|
|
1465
|
+
.map((s) => s.trim())
|
|
1466
|
+
.filter(Boolean)
|
|
1467
|
+
.reduce(
|
|
1468
|
+
(acc, rule) => {
|
|
1469
|
+
const [key, value] = rule.split(':').map((str) => str.trim())
|
|
1470
|
+
if (key && value) acc[key] = value
|
|
1471
|
+
return acc
|
|
1472
|
+
},
|
|
1473
|
+
{} as Record<string, string>,
|
|
1474
|
+
)
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1416
1477
|
deleteOldPageBuilderLocalStorage(): void {
|
|
1417
1478
|
const config = this.pageBuilderStateStore.getPageBuilderConfig
|
|
1418
1479
|
const formType = config && config.updateOrCreate && config.updateOrCreate.formType
|
|
@@ -1466,89 +1527,87 @@ export class PageBuilderService {
|
|
|
1466
1527
|
}
|
|
1467
1528
|
|
|
1468
1529
|
// Call this when the user starts editing (e.g., on first change or when resuming a draft)
|
|
1469
|
-
startEditing() {
|
|
1530
|
+
public startEditing() {
|
|
1470
1531
|
this.hasStartedEditing = true
|
|
1471
1532
|
}
|
|
1472
1533
|
|
|
1473
1534
|
//
|
|
1474
|
-
async resumeEditingFromDraft() {
|
|
1475
|
-
this.
|
|
1535
|
+
public async resumeEditingFromDraft() {
|
|
1536
|
+
this.updateLocalStorageItemName()
|
|
1476
1537
|
|
|
1477
|
-
const localStorageData = this.
|
|
1538
|
+
const localStorageData = this.getSavedPageHtml()
|
|
1478
1539
|
|
|
1479
1540
|
if (localStorageData) {
|
|
1480
|
-
this.pageBuilderStateStore.setIsLoadingResumeEditing(true)
|
|
1481
1541
|
await delay(400)
|
|
1482
|
-
|
|
1542
|
+
this.pageBuilderStateStore.setIsLoadingResumeEditing(true)
|
|
1543
|
+
await this.mountComponentsToDOM(localStorageData)
|
|
1483
1544
|
this.pageBuilderStateStore.setIsLoadingResumeEditing(false)
|
|
1484
1545
|
}
|
|
1485
1546
|
|
|
1486
1547
|
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
1487
1548
|
await nextTick()
|
|
1488
1549
|
// Attach event listeners to all editable elements in the Builder
|
|
1489
|
-
await this
|
|
1550
|
+
await this.addListenersToEditableElements()
|
|
1490
1551
|
// set loading to false
|
|
1491
1552
|
this.pageBuilderStateStore.setIsLoadingResumeEditing(false)
|
|
1492
1553
|
}
|
|
1493
1554
|
|
|
1494
|
-
async restoreOriginalContent() {
|
|
1495
|
-
this.
|
|
1555
|
+
public async restoreOriginalContent() {
|
|
1556
|
+
this.updateLocalStorageItemName()
|
|
1496
1557
|
|
|
1497
1558
|
this.pageBuilderStateStore.setIsRestoring(true)
|
|
1498
1559
|
await delay(400)
|
|
1499
1560
|
|
|
1500
1561
|
// Restore the original content if available
|
|
1501
1562
|
if (Array.isArray(this.originalComponents)) {
|
|
1502
|
-
await this
|
|
1563
|
+
await this.clearClassesFromPage()
|
|
1564
|
+
await this.clearInlineStylesFromPagee()
|
|
1565
|
+
await this.mountComponentsToDOM(JSON.stringify(this.originalComponents), true)
|
|
1503
1566
|
this.removeCurrentComponentsFromLocalStorage()
|
|
1504
1567
|
}
|
|
1505
1568
|
|
|
1506
1569
|
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
1507
1570
|
await nextTick()
|
|
1508
1571
|
// Attach event listeners to all editable elements in the Builder
|
|
1509
|
-
await this
|
|
1572
|
+
await this.addListenersToEditableElements()
|
|
1510
1573
|
|
|
1511
1574
|
this.pageBuilderStateStore.setIsRestoring(false)
|
|
1512
1575
|
}
|
|
1513
1576
|
|
|
1514
|
-
getStorageItemNameForResource(): string | null {
|
|
1577
|
+
public getStorageItemNameForResource(): string | null {
|
|
1515
1578
|
return this.getLocalStorageItemName.value
|
|
1516
1579
|
}
|
|
1517
1580
|
|
|
1518
|
-
|
|
1519
|
-
this.localStorageManager.updateLocalStorageItemName()
|
|
1581
|
+
public getSavedPageHtml() {
|
|
1520
1582
|
if (!this.getLocalStorageItemName.value) return false
|
|
1521
1583
|
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
localStorage.getItem(this.getLocalStorageItemName.value)
|
|
1525
|
-
) {
|
|
1526
|
-
const savedCurrentDesign = localStorage.getItem(this.getLocalStorageItemName.value)
|
|
1584
|
+
const key = this.getLocalStorageItemName.value
|
|
1585
|
+
if (!key) return false
|
|
1527
1586
|
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1587
|
+
const raw = localStorage.getItem(key)
|
|
1588
|
+
if (!raw) return false
|
|
1532
1589
|
|
|
1533
|
-
|
|
1534
|
-
}
|
|
1590
|
+
const parsed = JSON.parse(raw)
|
|
1535
1591
|
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1592
|
+
// Object with components and pageSettings
|
|
1593
|
+
if (parsed && Array.isArray(parsed.components)) {
|
|
1594
|
+
const classes = (parsed.pageSettings && parsed.pageSettings.classes) || ''
|
|
1595
|
+
const style = (parsed.pageSettings && parsed.pageSettings.style) || ''
|
|
1596
|
+
|
|
1597
|
+
const sectionsHtml = parsed.components.map((c: ComponentObject) => c.html_code).join('\n')
|
|
1598
|
+
return `<div id="pagebuilder" class="${classes}" style="${style}">\n${sectionsHtml}\n</div>`
|
|
1599
|
+
}
|
|
1600
|
+
return false
|
|
1544
1601
|
}
|
|
1545
1602
|
|
|
1546
1603
|
/**
|
|
1547
|
-
* Applies the staged image
|
|
1604
|
+
* Applies the staged image to the currently selected element.
|
|
1548
1605
|
* This updates the builder state and triggers an auto-save.
|
|
1549
1606
|
* If no element is selected or no image is staged, nothing happens.
|
|
1550
1607
|
*/
|
|
1551
|
-
async
|
|
1608
|
+
public async applySelectedImage(image: ImageObject): Promise<void> {
|
|
1609
|
+
this.pageBuilderStateStore.setApplyImageToSelection(image)
|
|
1610
|
+
|
|
1552
1611
|
if (!this.getElement.value) return
|
|
1553
1612
|
|
|
1554
1613
|
// Only apply if an image is staged
|
|
@@ -1565,7 +1624,7 @@ export class PageBuilderService {
|
|
|
1565
1624
|
* sets that image's src as the base primary image in the builder state.
|
|
1566
1625
|
* If the element does not meet these criteria, clears the base primary image.
|
|
1567
1626
|
*/
|
|
1568
|
-
setBasePrimaryImageFromSelectedElement() {
|
|
1627
|
+
private setBasePrimaryImageFromSelectedElement() {
|
|
1569
1628
|
if (!this.getElement.value) return
|
|
1570
1629
|
|
|
1571
1630
|
const currentImageContainer = document.createElement('div')
|
|
@@ -1585,7 +1644,7 @@ export class PageBuilderService {
|
|
|
1585
1644
|
this.pageBuilderStateStore.setBasePrimaryImage(null)
|
|
1586
1645
|
}
|
|
1587
1646
|
|
|
1588
|
-
|
|
1647
|
+
private addHyperlinkToElement(
|
|
1589
1648
|
hyperlinkEnable: boolean,
|
|
1590
1649
|
urlInput: string | null,
|
|
1591
1650
|
openHyperlinkInNewTab: boolean,
|
|
@@ -1681,7 +1740,7 @@ export class PageBuilderService {
|
|
|
1681
1740
|
}
|
|
1682
1741
|
}
|
|
1683
1742
|
|
|
1684
|
-
|
|
1743
|
+
private checkForHyperlink() {
|
|
1685
1744
|
if (!this.getElement.value) return
|
|
1686
1745
|
|
|
1687
1746
|
const hyperlink = this.getElement.value.querySelector('a')
|
|
@@ -1709,7 +1768,7 @@ export class PageBuilderService {
|
|
|
1709
1768
|
this.pageBuilderStateStore.setHyberlinkEnable(false)
|
|
1710
1769
|
}
|
|
1711
1770
|
|
|
1712
|
-
handleHyperlink(
|
|
1771
|
+
public handleHyperlink(
|
|
1713
1772
|
hyperlinkEnable?: boolean,
|
|
1714
1773
|
urlInput?: string | null,
|
|
1715
1774
|
openHyperlinkInNewTab?: boolean,
|
|
@@ -1747,15 +1806,15 @@ export class PageBuilderService {
|
|
|
1747
1806
|
}
|
|
1748
1807
|
|
|
1749
1808
|
if (hyperlinkEnable === undefined) {
|
|
1750
|
-
this
|
|
1809
|
+
this.checkForHyperlink()
|
|
1751
1810
|
return
|
|
1752
1811
|
}
|
|
1753
1812
|
|
|
1754
|
-
this
|
|
1813
|
+
this.addHyperlinkToElement(hyperlinkEnable, urlInput || null, openHyperlinkInNewTab || false)
|
|
1755
1814
|
}
|
|
1756
1815
|
|
|
1757
1816
|
// Helper method for custom components to easily add components
|
|
1758
|
-
async addComponent(componentObject: ComponentObject): Promise<void> {
|
|
1817
|
+
public async addComponent(componentObject: ComponentObject): Promise<void> {
|
|
1759
1818
|
try {
|
|
1760
1819
|
const clonedComponent = this.cloneCompObjForDOMInsertion({
|
|
1761
1820
|
html_code: componentObject.html_code,
|
|
@@ -1770,8 +1829,8 @@ export class PageBuilderService {
|
|
|
1770
1829
|
: 'push',
|
|
1771
1830
|
})
|
|
1772
1831
|
|
|
1773
|
-
const pageBuilder = document.querySelector('#
|
|
1774
|
-
// scoll to top or bottom
|
|
1832
|
+
const pageBuilder = document.querySelector('#pagebuilder')
|
|
1833
|
+
// scoll to top or bottom
|
|
1775
1834
|
if (pageBuilder) {
|
|
1776
1835
|
// push to bottom
|
|
1777
1836
|
if (this.getComponentArrayAddMethod.value === 'push') {
|
|
@@ -1785,7 +1844,7 @@ export class PageBuilderService {
|
|
|
1785
1844
|
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
1786
1845
|
await nextTick()
|
|
1787
1846
|
// Attach event listeners to all editable elements in the Builder
|
|
1788
|
-
await this
|
|
1847
|
+
await this.addListenersToEditableElements()
|
|
1789
1848
|
|
|
1790
1849
|
await this.handleAutoSave()
|
|
1791
1850
|
} catch (error) {
|
|
@@ -1798,7 +1857,7 @@ export class PageBuilderService {
|
|
|
1798
1857
|
* process each element’s class attribute and update the classes accordingly.
|
|
1799
1858
|
*/
|
|
1800
1859
|
|
|
1801
|
-
|
|
1860
|
+
private addTailwindPrefixToClasses(classList: string, prefix = 'pbx-'): string {
|
|
1802
1861
|
return classList
|
|
1803
1862
|
.split(/\s+/)
|
|
1804
1863
|
.map((cls) => {
|
|
@@ -1812,7 +1871,7 @@ export class PageBuilderService {
|
|
|
1812
1871
|
.join(' ')
|
|
1813
1872
|
}
|
|
1814
1873
|
|
|
1815
|
-
|
|
1874
|
+
private convertStyleObjectToString(
|
|
1816
1875
|
styleObj: string | Record<string, string> | null | undefined,
|
|
1817
1876
|
): string {
|
|
1818
1877
|
if (!styleObj) return ''
|
|
@@ -1826,6 +1885,114 @@ export class PageBuilderService {
|
|
|
1826
1885
|
.join(' ')
|
|
1827
1886
|
}
|
|
1828
1887
|
|
|
1888
|
+
/**
|
|
1889
|
+
* Parses a string of HTML and extracts builder components and global page settings.
|
|
1890
|
+
*
|
|
1891
|
+
* ⚠️ **Important:**
|
|
1892
|
+
* - This method expects an **HTML string** containing one or more `<section>...</section>` elements (such as the output from `getSavedPageHtml()` or a previously saved builder HTML string).
|
|
1893
|
+
* - **Do NOT pass a JSON string** (such as the result of `JSON.stringify(componentsArray)`) to this method. Passing a JSON string to `DOMParser.parseFromString(..., 'text/html')` will not produce valid DOM nodes. Instead, it will treat the JSON as plain text, resulting in a `<html><head></head><body>{...json...}</body></html>` structure, not real HTML elements.
|
|
1894
|
+
* - If you pass a JSON string, you will see lots of `\n` and strange HTML, because the parser is just wrapping your JSON in a `<body>` tag as text.
|
|
1895
|
+
*
|
|
1896
|
+
* Why only HTML?
|
|
1897
|
+
* - It enforces a single source of truth for builder state (HTML).
|
|
1898
|
+
* - It prevents misuse (e.g., passing JSON to a DOM parser, which is always a bug).
|
|
1899
|
+
* - It makes your documentation and support much simpler.
|
|
1900
|
+
*
|
|
1901
|
+
* @param htmlString - The HTML string to parse (must contain `<section>...</section>` elements, not JSON).
|
|
1902
|
+
* @returns An object with `components` (array of builder components) and `pageSettings` (global styles for the page).
|
|
1903
|
+
*/
|
|
1904
|
+
public parsePageBuilderHTML(htmlString: string): {
|
|
1905
|
+
components: ComponentObject[]
|
|
1906
|
+
pageSettings: PageSettings
|
|
1907
|
+
} {
|
|
1908
|
+
const parser = new DOMParser()
|
|
1909
|
+
const doc = parser.parseFromString(htmlString, 'text/html')
|
|
1910
|
+
|
|
1911
|
+
// Prefix all classes in the document
|
|
1912
|
+
doc.querySelectorAll('[class]').forEach((element) => {
|
|
1913
|
+
const currentClasses = element.getAttribute('class') || ''
|
|
1914
|
+
const prefixedClasses = this.addTailwindPrefixToClasses(currentClasses)
|
|
1915
|
+
element.setAttribute('class', prefixedClasses)
|
|
1916
|
+
})
|
|
1917
|
+
|
|
1918
|
+
const pagebuilderDiv = doc.querySelector('#pagebuilder')
|
|
1919
|
+
let pageSettings: PageSettings = {
|
|
1920
|
+
classes: '',
|
|
1921
|
+
style: {},
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
if (pagebuilderDiv) {
|
|
1925
|
+
const rawStyle = pagebuilderDiv.getAttribute('style') || ''
|
|
1926
|
+
pageSettings = {
|
|
1927
|
+
classes: pagebuilderDiv.className || '',
|
|
1928
|
+
style: this.parseStyleString(rawStyle),
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// Always assign sectionNodes before use
|
|
1933
|
+
let sectionNodes: NodeListOf<HTMLElement> = doc.querySelectorAll('section')
|
|
1934
|
+
if (pagebuilderDiv) {
|
|
1935
|
+
sectionNodes = pagebuilderDiv.querySelectorAll('section')
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
// Only use top-level (non-nested) sections as components
|
|
1939
|
+
const topLevelSections = Array.from(sectionNodes).filter(
|
|
1940
|
+
(section) =>
|
|
1941
|
+
!section.parentElement || section.parentElement.tagName.toLowerCase() !== 'section',
|
|
1942
|
+
)
|
|
1943
|
+
|
|
1944
|
+
let components: ComponentObject[] = []
|
|
1945
|
+
|
|
1946
|
+
if (topLevelSections.length > 0) {
|
|
1947
|
+
components = topLevelSections.map((section) => ({
|
|
1948
|
+
id: null,
|
|
1949
|
+
html_code: section.outerHTML.trim(),
|
|
1950
|
+
title:
|
|
1951
|
+
section.getAttribute('data-component-title') ||
|
|
1952
|
+
section.getAttribute('title') ||
|
|
1953
|
+
'Untitled Component',
|
|
1954
|
+
}))
|
|
1955
|
+
}
|
|
1956
|
+
if (topLevelSections.length === 0) {
|
|
1957
|
+
// No <section> found: treat each first-level child as a component, wrapped in a section
|
|
1958
|
+
const parent = pagebuilderDiv || doc.body
|
|
1959
|
+
const children = Array.from(parent.children)
|
|
1960
|
+
if (children.length > 0) {
|
|
1961
|
+
components = children.map((child) => {
|
|
1962
|
+
// Wrap in a section with data-componentid and data-component-title
|
|
1963
|
+
const section = doc.createElement('section')
|
|
1964
|
+
section.setAttribute('data-component-title', 'Untitled Component')
|
|
1965
|
+
// Optionally: generate a uuid for data-componentid if needed
|
|
1966
|
+
// section.setAttribute('data-componentid', uuidv4())
|
|
1967
|
+
section.innerHTML = child.outerHTML.trim()
|
|
1968
|
+
return {
|
|
1969
|
+
id: null,
|
|
1970
|
+
html_code: section.outerHTML.trim(),
|
|
1971
|
+
title: 'Untitled Component',
|
|
1972
|
+
}
|
|
1973
|
+
})
|
|
1974
|
+
}
|
|
1975
|
+
if (children.length === 0) {
|
|
1976
|
+
// No children: wrap the entire content in a <section> as a single component
|
|
1977
|
+
const section = doc.createElement('section')
|
|
1978
|
+
section.setAttribute('data-component-title', 'Untitled Component')
|
|
1979
|
+
section.innerHTML = parent.innerHTML.trim()
|
|
1980
|
+
components = [
|
|
1981
|
+
{
|
|
1982
|
+
id: null,
|
|
1983
|
+
html_code: section.outerHTML.trim(),
|
|
1984
|
+
title: 'Untitled Component',
|
|
1985
|
+
},
|
|
1986
|
+
]
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
return {
|
|
1991
|
+
components,
|
|
1992
|
+
pageSettings,
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1829
1996
|
/**
|
|
1830
1997
|
* Parse and set components from JSON or HTML data
|
|
1831
1998
|
*
|
|
@@ -1838,56 +2005,74 @@ export class PageBuilderService {
|
|
|
1838
2005
|
* @param data - JSON string (e.g., '[{"html_code":"...","id":"123","title":"..."}]')
|
|
1839
2006
|
* OR HTML string (e.g., '<section data-componentid="123">...</section>')
|
|
1840
2007
|
*/
|
|
1841
|
-
async
|
|
1842
|
-
|
|
2008
|
+
private async mountComponentsToDOM(
|
|
2009
|
+
htmlString: string,
|
|
2010
|
+
usePassedPageSettings?: boolean,
|
|
2011
|
+
): Promise<void> {
|
|
2012
|
+
/**
|
|
2013
|
+
* Mounts builder components to the DOM from either JSON or HTML input.
|
|
2014
|
+
*
|
|
2015
|
+
* Input format detection:
|
|
2016
|
+
* - If the input starts with `[` or `{`, it is treated as JSON (array or object).
|
|
2017
|
+
* - If the input starts with `<`, it is treated as HTML.
|
|
2018
|
+
*
|
|
2019
|
+
* When to use which format:
|
|
2020
|
+
*
|
|
2021
|
+
* 1. JSON input (from localStorage, API, or internal state like pina):
|
|
2022
|
+
* - Use when restoring builder state from localStorage, an API, or a previously saved draft.
|
|
2023
|
+
* - Example: `localStorage.getItem(...)` or API returns a JSON stringified array/object of components.
|
|
2024
|
+
* - This is the most common format for drafts, autosave, and programmatic state management.
|
|
2025
|
+
* - Example usage:
|
|
2026
|
+
* await this.mountComponentsToDOM(JSON.stringify(getComponents))
|
|
2027
|
+
*
|
|
2028
|
+
* 2. HTML input (from HTML snapshot, import, or published output):
|
|
2029
|
+
* - Use when restoring from a published HTML snapshot, importing a static HTML export, or loading the builder from a previously published HTML string.
|
|
2030
|
+
* - Example: output from `getSavedPageHtml()` or a static HTML export.
|
|
2031
|
+
* - This is used for restoring the builder from a published state, importing, or previewing published content.
|
|
2032
|
+
* - Example usage:
|
|
2033
|
+
* await this.mountComponentsToDOM(savedHtmlString)
|
|
2034
|
+
*
|
|
2035
|
+
* Best practice:
|
|
2036
|
+
* - Use JSON for local storage drafts, autosave, and API-driven workflows.
|
|
2037
|
+
* - Use HTML for published/imported content from DB or when restoring from a static HTML snapshot.
|
|
2038
|
+
*
|
|
2039
|
+
* The method auto-detects the format and calls the appropriate parser.
|
|
2040
|
+
*/
|
|
1843
2041
|
const trimmedData = htmlString.trim()
|
|
1844
2042
|
|
|
1845
2043
|
if (trimmedData.startsWith('[') || trimmedData.startsWith('{')) {
|
|
1846
|
-
//
|
|
1847
|
-
await this
|
|
2044
|
+
// JSON input: Use this when restoring from localStorage, API, or internal builder state (drafts, autosave, etc.)
|
|
2045
|
+
await this.parseJSONComponents(trimmedData, usePassedPageSettings)
|
|
1848
2046
|
return
|
|
1849
2047
|
}
|
|
1850
2048
|
if (trimmedData.startsWith('<')) {
|
|
1851
|
-
//
|
|
1852
|
-
await this
|
|
2049
|
+
// HTML input: Use this when restoring from a published HTML snapshot, import, or static HTML export
|
|
2050
|
+
await this.parseHTMLComponents(trimmedData, usePassedPageSettings)
|
|
1853
2051
|
return
|
|
1854
2052
|
}
|
|
1855
2053
|
|
|
1856
|
-
|
|
2054
|
+
// Fallback: If format is unknown, default to JSON parser (defensive)
|
|
2055
|
+
await this.parseJSONComponents(trimmedData, usePassedPageSettings)
|
|
1857
2056
|
}
|
|
1858
2057
|
|
|
1859
2058
|
// Private method to parse JSON components and save pageBuilderContentSavedAt to localStorage
|
|
1860
|
-
async
|
|
1861
|
-
|
|
2059
|
+
private async parseJSONComponents(
|
|
2060
|
+
jsonData: string,
|
|
2061
|
+
usePassedPageSettings?: boolean,
|
|
2062
|
+
): Promise<void> {
|
|
2063
|
+
const pageSettings =
|
|
1862
2064
|
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
1863
2065
|
this.pageBuilderStateStore.getPageBuilderConfig.pageSettings
|
|
1864
2066
|
|
|
2067
|
+
const userPageSettings = usePassedPageSettings ? pageSettings : null
|
|
1865
2068
|
try {
|
|
1866
2069
|
const parsedData = JSON.parse(jsonData)
|
|
1867
2070
|
let componentsArray: ComponentObject[] = []
|
|
1868
2071
|
|
|
1869
|
-
// Decide which pageSettings to use
|
|
1870
|
-
const pageSettings = usePassedPageSettings
|
|
1871
|
-
? storedPageSettings
|
|
1872
|
-
: parsedData && parsedData.pageSettings
|
|
1873
|
-
? parsedData.pageSettings
|
|
1874
|
-
: null
|
|
1875
|
-
|
|
1876
|
-
// Restore page-level settings like class and style
|
|
1877
|
-
if (pageSettings) {
|
|
1878
|
-
const pagebuilder = document.querySelector('#pagebuilder') as HTMLElement
|
|
1879
|
-
if (pagebuilder) {
|
|
1880
|
-
pagebuilder.removeAttribute('class')
|
|
1881
|
-
pagebuilder.removeAttribute('style')
|
|
1882
|
-
pagebuilder.className = pageSettings.classes || ''
|
|
1883
|
-
pagebuilder.setAttribute('style', this.#convertStyleObjectToString(pageSettings.style))
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
// Support both old and new structure
|
|
1888
2072
|
if (Array.isArray(parsedData)) {
|
|
1889
2073
|
componentsArray = parsedData
|
|
1890
|
-
}
|
|
2074
|
+
}
|
|
2075
|
+
if (parsedData && Array.isArray(parsedData.components)) {
|
|
1891
2076
|
componentsArray = parsedData.components
|
|
1892
2077
|
}
|
|
1893
2078
|
|
|
@@ -1904,7 +2089,7 @@ export class PageBuilderService {
|
|
|
1904
2089
|
section.querySelectorAll('[class]').forEach((el) => {
|
|
1905
2090
|
el.setAttribute(
|
|
1906
2091
|
'class',
|
|
1907
|
-
this
|
|
2092
|
+
this.addTailwindPrefixToClasses(el.getAttribute('class') || '', 'pbx-'),
|
|
1908
2093
|
)
|
|
1909
2094
|
})
|
|
1910
2095
|
|
|
@@ -1931,14 +2116,28 @@ export class PageBuilderService {
|
|
|
1931
2116
|
this.pageBuilderStateStore.setComponents(savedCurrentDesign)
|
|
1932
2117
|
|
|
1933
2118
|
await nextTick()
|
|
1934
|
-
await this
|
|
2119
|
+
await this.addListenersToEditableElements()
|
|
2120
|
+
|
|
2121
|
+
if (userPageSettings && pageSettings) {
|
|
2122
|
+
const pagebuilder = document.querySelector('#pagebuilder') as HTMLElement
|
|
2123
|
+
if (pagebuilder) {
|
|
2124
|
+
pagebuilder.removeAttribute('class')
|
|
2125
|
+
pagebuilder.removeAttribute('style')
|
|
2126
|
+
pagebuilder.className = pageSettings.classes || ''
|
|
2127
|
+
pagebuilder.setAttribute('style', this.convertStyleObjectToString(pageSettings.style))
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
1935
2130
|
} catch (error) {
|
|
1936
2131
|
console.error('Error parsing JSON components:', error)
|
|
1937
2132
|
this.deleteAllComponentsFromDOM()
|
|
1938
2133
|
}
|
|
1939
2134
|
}
|
|
2135
|
+
|
|
1940
2136
|
// Private method to parse HTML components
|
|
1941
|
-
async
|
|
2137
|
+
private async parseHTMLComponents(
|
|
2138
|
+
htmlData: string,
|
|
2139
|
+
usePassedPageSettings?: boolean,
|
|
2140
|
+
): Promise<void> {
|
|
1942
2141
|
try {
|
|
1943
2142
|
const parser = new DOMParser()
|
|
1944
2143
|
const doc = parser.parseFromString(htmlData, 'text/html')
|
|
@@ -1969,10 +2168,7 @@ export class PageBuilderService {
|
|
|
1969
2168
|
livePageBuilder.removeAttribute('class')
|
|
1970
2169
|
livePageBuilder.removeAttribute('style')
|
|
1971
2170
|
livePageBuilder.className = pageSettings.classes || ''
|
|
1972
|
-
livePageBuilder.setAttribute(
|
|
1973
|
-
'style',
|
|
1974
|
-
this.#convertStyleObjectToString(pageSettings.style),
|
|
1975
|
-
)
|
|
2171
|
+
livePageBuilder.setAttribute('style', this.convertStyleObjectToString(pageSettings.style))
|
|
1976
2172
|
}
|
|
1977
2173
|
}
|
|
1978
2174
|
|
|
@@ -1985,7 +2181,7 @@ export class PageBuilderService {
|
|
|
1985
2181
|
section.querySelectorAll('[class]').forEach((el) => {
|
|
1986
2182
|
el.setAttribute(
|
|
1987
2183
|
'class',
|
|
1988
|
-
this
|
|
2184
|
+
this.addTailwindPrefixToClasses(el.getAttribute('class') || '', 'pbx-'),
|
|
1989
2185
|
)
|
|
1990
2186
|
})
|
|
1991
2187
|
|
|
@@ -2017,67 +2213,192 @@ export class PageBuilderService {
|
|
|
2017
2213
|
// Clear selections and re-bind events
|
|
2018
2214
|
await this.clearHtmlSelection()
|
|
2019
2215
|
await nextTick()
|
|
2020
|
-
await this
|
|
2216
|
+
await this.addListenersToEditableElements()
|
|
2021
2217
|
} catch (error) {
|
|
2022
2218
|
console.error('Error parsing HTML components:', error)
|
|
2023
2219
|
this.deleteAllComponentsFromDOM()
|
|
2024
2220
|
}
|
|
2025
2221
|
}
|
|
2026
2222
|
|
|
2027
|
-
|
|
2223
|
+
private updateLocalStorageItemName(): void {
|
|
2224
|
+
const formtype =
|
|
2225
|
+
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
2226
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
2227
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType
|
|
2228
|
+
|
|
2229
|
+
const formname =
|
|
2230
|
+
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
2231
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
2232
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formName
|
|
2233
|
+
|
|
2234
|
+
const resourceData =
|
|
2235
|
+
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
2236
|
+
this.pageBuilderStateStore.getPageBuilderConfig.resourceData
|
|
2237
|
+
|
|
2238
|
+
// Logic for create resource
|
|
2239
|
+
if (formtype === 'create') {
|
|
2240
|
+
if (formname && formname.length > 0) {
|
|
2241
|
+
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
2242
|
+
`page-builder-create-resource-${this.sanitizeForLocalStorage(formname)}`,
|
|
2243
|
+
)
|
|
2244
|
+
return
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
this.pageBuilderStateStore.setLocalStorageItemName(`page-builder-create-resource`)
|
|
2248
|
+
return
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
// Logic for create
|
|
2252
|
+
// Logic for update and with resource form name
|
|
2253
|
+
if (formtype === 'update') {
|
|
2254
|
+
if (typeof formname === 'string' && formname.length > 0) {
|
|
2255
|
+
//
|
|
2256
|
+
//
|
|
2257
|
+
if (resourceData && resourceData != null && !resourceData.title) {
|
|
2258
|
+
// Check if id is missing, null, undefined, or an empty string (after trimming)
|
|
2259
|
+
if (!resourceData.id || typeof resourceData.id === 'string') {
|
|
2260
|
+
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
2261
|
+
`page-builder-update-resource-${this.sanitizeForLocalStorage(formname)}`,
|
|
2262
|
+
)
|
|
2263
|
+
return
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
// Runs when resourceData has title but no ID
|
|
2268
|
+
if (resourceData && resourceData != null) {
|
|
2269
|
+
if (
|
|
2270
|
+
resourceData.title &&
|
|
2271
|
+
typeof resourceData.title === 'string' &&
|
|
2272
|
+
resourceData.title.length > 0
|
|
2273
|
+
) {
|
|
2274
|
+
if (!resourceData.id || typeof resourceData.id === 'string') {
|
|
2275
|
+
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
2276
|
+
`page-builder-update-resource-${this.sanitizeForLocalStorage(formname)}-${this.sanitizeForLocalStorage(resourceData.title)}`,
|
|
2277
|
+
)
|
|
2278
|
+
return
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
// Runs when resourceData has ID but no title
|
|
2284
|
+
if (resourceData && resourceData != null) {
|
|
2285
|
+
if (!resourceData.title && typeof resourceData.title !== 'string') {
|
|
2286
|
+
if (resourceData.id || typeof resourceData.id === 'number') {
|
|
2287
|
+
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
2288
|
+
`page-builder-update-resource-${this.sanitizeForLocalStorage(formname)}-${this.sanitizeForLocalStorage(String(resourceData.id))}`,
|
|
2289
|
+
)
|
|
2290
|
+
return
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
// Runs when resourceData has both title and ID
|
|
2296
|
+
if (resourceData && resourceData != null) {
|
|
2297
|
+
if (
|
|
2298
|
+
resourceData.title &&
|
|
2299
|
+
typeof resourceData.title === 'string' &&
|
|
2300
|
+
resourceData.title.length > 0
|
|
2301
|
+
) {
|
|
2302
|
+
if (resourceData.id || typeof resourceData.id === 'number') {
|
|
2303
|
+
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
2304
|
+
`page-builder-update-resource-${this.sanitizeForLocalStorage(formname)}-${this.sanitizeForLocalStorage(resourceData.title)}-${this.sanitizeForLocalStorage(String(resourceData.id))}`,
|
|
2305
|
+
)
|
|
2306
|
+
return
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
// Logic for update without without formname
|
|
2313
|
+
if (!formname || (typeof formname === 'string' && formname.length === 0)) {
|
|
2314
|
+
//
|
|
2315
|
+
//
|
|
2316
|
+
if (resourceData && resourceData != null && !resourceData.title) {
|
|
2317
|
+
// Check if id is missing, null, undefined, or an empty string (after trimming)
|
|
2318
|
+
if (!resourceData.id || typeof resourceData.id === 'string') {
|
|
2319
|
+
this.pageBuilderStateStore.setLocalStorageItemName(`page-builder-update-resource`)
|
|
2320
|
+
return
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
// Runs when resourceData has title but no ID
|
|
2325
|
+
if (resourceData && resourceData != null) {
|
|
2326
|
+
if (
|
|
2327
|
+
resourceData.title &&
|
|
2328
|
+
typeof resourceData.title === 'string' &&
|
|
2329
|
+
resourceData.title.length > 0
|
|
2330
|
+
) {
|
|
2331
|
+
if (!resourceData.id || typeof resourceData.id === 'string') {
|
|
2332
|
+
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
2333
|
+
`page-builder-update-resource-${this.sanitizeForLocalStorage(resourceData.title)}`,
|
|
2334
|
+
)
|
|
2335
|
+
return
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
// Runs when resourceData has ID but no title
|
|
2341
|
+
if (resourceData && resourceData != null) {
|
|
2342
|
+
if (!resourceData.title && typeof resourceData.title !== 'string') {
|
|
2343
|
+
if (resourceData.id || typeof resourceData.id === 'number') {
|
|
2344
|
+
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
2345
|
+
`page-builder-update-resource-${this.sanitizeForLocalStorage(String(resourceData.id))}`,
|
|
2346
|
+
)
|
|
2347
|
+
return
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
// Runs when resourceData has both title and ID
|
|
2353
|
+
if (resourceData && resourceData != null) {
|
|
2354
|
+
if (
|
|
2355
|
+
resourceData.title &&
|
|
2356
|
+
typeof resourceData.title === 'string' &&
|
|
2357
|
+
resourceData.title.length > 0
|
|
2358
|
+
) {
|
|
2359
|
+
if (resourceData.id || typeof resourceData.id === 'number') {
|
|
2360
|
+
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
2361
|
+
`page-builder-update-resource-${this.sanitizeForLocalStorage(resourceData.title)}-${this.sanitizeForLocalStorage(String(resourceData.id))}`,
|
|
2362
|
+
)
|
|
2363
|
+
return
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
public async initializeElementStyles(): Promise<void> {
|
|
2028
2372
|
// Wait for Vue to finish DOM updates before attaching event listeners.
|
|
2029
2373
|
// This ensure elements exist in the DOM.
|
|
2030
2374
|
await nextTick()
|
|
2031
2375
|
|
|
2032
|
-
// handle custom URL
|
|
2033
2376
|
this.handleHyperlink(undefined, null, false)
|
|
2034
|
-
// handle opacity
|
|
2035
2377
|
this.handleOpacity(undefined)
|
|
2036
|
-
// handle BG opacity
|
|
2037
2378
|
this.handleBackgroundOpacity(undefined)
|
|
2038
|
-
// displayed image
|
|
2039
2379
|
this.setBasePrimaryImageFromSelectedElement()
|
|
2040
|
-
// border style
|
|
2041
2380
|
this.handleBorderStyle(undefined)
|
|
2042
|
-
// border width
|
|
2043
2381
|
this.handleBorderWidth(undefined)
|
|
2044
|
-
// border color
|
|
2045
2382
|
this.handleBorderColor(undefined)
|
|
2046
|
-
// border radius
|
|
2047
2383
|
this.handleBorderRadiusGlobal(undefined)
|
|
2048
|
-
// border radius
|
|
2049
2384
|
this.handleBorderRadiusTopLeft(undefined)
|
|
2050
|
-
// border radius
|
|
2051
2385
|
this.handleBorderRadiusTopRight(undefined)
|
|
2052
|
-
// border radius
|
|
2053
2386
|
this.handleBorderRadiusBottomleft(undefined)
|
|
2054
|
-
// border radius
|
|
2055
2387
|
this.handleBorderRadiusBottomRight(undefined)
|
|
2056
|
-
// handle font size
|
|
2057
2388
|
this.handleFontSizeBase(undefined)
|
|
2058
2389
|
this.handleFontSizeDesktop(undefined)
|
|
2059
2390
|
this.handleFontSizeTablet(undefined)
|
|
2060
2391
|
this.handleFontSizeMobile(undefined)
|
|
2061
|
-
// handle font weight
|
|
2062
2392
|
this.handleFontWeight(undefined)
|
|
2063
|
-
// handle font family
|
|
2064
|
-
|
|
2065
2393
|
this.handleFontFamily(undefined)
|
|
2066
|
-
// handle font style
|
|
2067
2394
|
this.handleFontStyle(undefined)
|
|
2068
|
-
// handle vertical padding
|
|
2069
2395
|
this.handleVerticalPadding(undefined)
|
|
2070
|
-
// handle horizontal padding
|
|
2071
2396
|
this.handleHorizontalPadding(undefined)
|
|
2072
|
-
// handle vertical margin
|
|
2073
2397
|
this.handleVerticalMargin(undefined)
|
|
2074
|
-
// handle horizontal margin
|
|
2075
2398
|
this.handleHorizontalMargin(undefined)
|
|
2076
|
-
// handle color
|
|
2077
2399
|
this.handleBackgroundColor(undefined)
|
|
2078
|
-
// handle text color
|
|
2079
2400
|
this.handleTextColor(undefined)
|
|
2080
|
-
|
|
2081
|
-
await this
|
|
2401
|
+
await this.syncCurrentClasses()
|
|
2402
|
+
await this.syncCurrentStyles()
|
|
2082
2403
|
}
|
|
2083
2404
|
}
|