@myissue/vue-website-page-builder 3.3.1 → 3.3.12
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 +134 -135
- package/dist/vue-website-page-builder.css +1 -1
- package/dist/vue-website-page-builder.js +4210 -4124
- package/dist/vue-website-page-builder.umd.cjs +37 -37
- package/package.json +1 -1
- package/src/Components/PageBuilder/EditorMenu/Editables/ComponentTopMenu.vue +4 -4
- package/src/Components/PageBuilder/EditorMenu/Editables/DeleteElement.vue +2 -2
- package/src/Components/PageBuilder/EditorMenu/Editables/ElementEditor.vue +2 -2
- package/src/Components/PageBuilder/EditorMenu/EditorAccordion.vue +1 -1
- package/src/Components/PageBuilder/ToolbarOption/ToolbarOption.vue +4 -4
- package/src/DemoComponents/HomeSection.vue +3 -2
- package/src/PageBuilder/PageBuilder.vue +3 -6
- package/src/composables/PageBuilderService.ts +333 -182
- package/src/helpers/isEmptyObject.ts +1 -1
- package/src/tailwind-safelist.html +1 -1
- package/src/types/index.ts +13 -1
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
// Type definitions
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
BuilderResourceData,
|
|
4
|
+
ComponentObject,
|
|
5
|
+
ImageObject,
|
|
6
|
+
PageBuilderConfig,
|
|
7
|
+
StartBuilderResult,
|
|
8
|
+
} from '../types'
|
|
3
9
|
|
|
4
10
|
import type { usePageBuilderStateStore } from '../stores/page-builder-state'
|
|
5
11
|
|
|
@@ -18,11 +24,7 @@ import { isEmptyObject } from '../helpers/isEmptyObject'
|
|
|
18
24
|
|
|
19
25
|
export class PageBuilderService {
|
|
20
26
|
// Class properties with types
|
|
21
|
-
private nextTick: Promise<void>
|
|
22
|
-
private containsPagebuilder: Element | null
|
|
23
|
-
// private pageBuilder: Element | null
|
|
24
27
|
private pageBuilderStateStore: ReturnType<typeof usePageBuilderStateStore>
|
|
25
|
-
private getTextAreaVueModel: ComputedRef<string | null>
|
|
26
28
|
private getLocalStorageItemName: ComputedRef<string | null>
|
|
27
29
|
private getApplyImageToSelection: ComputedRef<ImageObject>
|
|
28
30
|
private getHyberlinkEnable: ComputedRef<boolean>
|
|
@@ -42,12 +44,9 @@ export class PageBuilderService {
|
|
|
42
44
|
private pendingMountData: string | null = null
|
|
43
45
|
|
|
44
46
|
constructor(pageBuilderStateStore: ReturnType<typeof usePageBuilderStateStore>) {
|
|
45
|
-
this.nextTick = nextTick()
|
|
46
47
|
this.hasStartedEditing = false
|
|
47
|
-
this.containsPagebuilder = document.querySelector('#contains-pagebuilder')
|
|
48
48
|
this.pageBuilderStateStore = pageBuilderStateStore
|
|
49
49
|
|
|
50
|
-
this.getTextAreaVueModel = computed(() => this.pageBuilderStateStore.getTextAreaVueModel)
|
|
51
50
|
this.getLocalStorageItemName = computed(
|
|
52
51
|
() => this.pageBuilderStateStore.getLocalStorageItemName,
|
|
53
52
|
)
|
|
@@ -176,6 +175,38 @@ export class PageBuilderService {
|
|
|
176
175
|
}
|
|
177
176
|
}
|
|
178
177
|
|
|
178
|
+
#validateUserProvidedComponents(components: BuilderResourceData) {
|
|
179
|
+
// Must be an array
|
|
180
|
+
if (!Array.isArray(components)) {
|
|
181
|
+
return {
|
|
182
|
+
error: true,
|
|
183
|
+
reason: "'components' must be an array.",
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// If empty array, that's acceptable
|
|
188
|
+
if (components.length === 0) {
|
|
189
|
+
return { error: false }
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Check that the first item looks like a component
|
|
193
|
+
const first = components[0]
|
|
194
|
+
|
|
195
|
+
const isObject = typeof first === 'object' && first !== null
|
|
196
|
+
const hasHtmlCodeKey = 'html_code' in first
|
|
197
|
+
|
|
198
|
+
if (!isObject || !hasHtmlCodeKey) {
|
|
199
|
+
return {
|
|
200
|
+
error: true,
|
|
201
|
+
reason: "Each component must be an object and include an 'html_code' key.",
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
message: 'Everything looks good. Components structure is valid.',
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
179
210
|
#validateConfig(config: PageBuilderConfig): void {
|
|
180
211
|
const defaultConfigValues = {
|
|
181
212
|
updateOrCreate: {
|
|
@@ -204,7 +235,19 @@ export class PageBuilderService {
|
|
|
204
235
|
*
|
|
205
236
|
* @param config - The configuration object for the Page Builder.
|
|
206
237
|
*/
|
|
207
|
-
async startBuilder(
|
|
238
|
+
async startBuilder(
|
|
239
|
+
config: PageBuilderConfig,
|
|
240
|
+
components?: BuilderResourceData,
|
|
241
|
+
): Promise<StartBuilderResult> {
|
|
242
|
+
console.log('start builder ran..', components)
|
|
243
|
+
if (components) {
|
|
244
|
+
this.#validateUserProvidedComponents(components)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
message: 'Page builder started successfully with valid components.',
|
|
249
|
+
}
|
|
250
|
+
|
|
208
251
|
// Reactive flag signals to the UI that the builder has been successfully initialized
|
|
209
252
|
// Prevents builder actions to prevent errors caused by missing DOM .
|
|
210
253
|
this.pageBuilderStateStore.setBuilderStarted(true)
|
|
@@ -223,10 +266,14 @@ export class PageBuilderService {
|
|
|
223
266
|
// Update the localStorage key name based on the config/resource
|
|
224
267
|
this.#updateLocalStorageItemName()
|
|
225
268
|
|
|
226
|
-
|
|
269
|
+
const formType = config.updateOrCreate && config.updateOrCreate.formType
|
|
270
|
+
if (formType === 'create') {
|
|
271
|
+
await this.mountComponentsToDOM('')
|
|
272
|
+
}
|
|
227
273
|
}
|
|
228
274
|
|
|
229
|
-
async completeBuilderInitialization() {
|
|
275
|
+
async #completeBuilderInitialization() {
|
|
276
|
+
console.log('complete builder..')
|
|
230
277
|
const pagebuilder = document.querySelector('#pagebuilder')
|
|
231
278
|
if (!pagebuilder) return
|
|
232
279
|
|
|
@@ -432,7 +479,7 @@ export class PageBuilderService {
|
|
|
432
479
|
this.pageBuilderStateStore.setIsSaving(true)
|
|
433
480
|
// Deselect any selected or hovered elements in the builder UI
|
|
434
481
|
//
|
|
435
|
-
|
|
482
|
+
this.#saveDomComponentsToLocalStorage()
|
|
436
483
|
await this.delay(500)
|
|
437
484
|
} catch (err) {
|
|
438
485
|
console.error('Error trying auto save.', err)
|
|
@@ -444,7 +491,7 @@ export class PageBuilderService {
|
|
|
444
491
|
if (passedConfig && !passedConfig.userSettings) {
|
|
445
492
|
try {
|
|
446
493
|
this.pageBuilderStateStore.setIsSaving(true)
|
|
447
|
-
|
|
494
|
+
this.#saveDomComponentsToLocalStorage()
|
|
448
495
|
await this.delay(300)
|
|
449
496
|
} catch (err) {
|
|
450
497
|
console.error('Error trying saving.', err)
|
|
@@ -470,7 +517,7 @@ export class PageBuilderService {
|
|
|
470
517
|
passedConfig.userSettings.autoSave)
|
|
471
518
|
) {
|
|
472
519
|
this.pageBuilderStateStore.setIsSaving(true)
|
|
473
|
-
|
|
520
|
+
this.#saveDomComponentsToLocalStorage()
|
|
474
521
|
await this.delay(300)
|
|
475
522
|
|
|
476
523
|
this.pageBuilderStateStore.setIsSaving(false)
|
|
@@ -478,7 +525,7 @@ export class PageBuilderService {
|
|
|
478
525
|
}
|
|
479
526
|
if (passedConfig && !passedConfig.userSettings) {
|
|
480
527
|
this.pageBuilderStateStore.setIsSaving(true)
|
|
481
|
-
|
|
528
|
+
this.#saveDomComponentsToLocalStorage()
|
|
482
529
|
await this.delay(300)
|
|
483
530
|
|
|
484
531
|
this.pageBuilderStateStore.setIsSaving(false)
|
|
@@ -489,15 +536,16 @@ export class PageBuilderService {
|
|
|
489
536
|
// Deep clone clone component
|
|
490
537
|
const clonedComponent = { ...componentObject }
|
|
491
538
|
|
|
539
|
+
const pageBuilder = document.querySelector('#contains-pagebuilder')
|
|
492
540
|
// scoll to top or bottom # end
|
|
493
|
-
if (
|
|
541
|
+
if (pageBuilder) {
|
|
494
542
|
if (
|
|
495
543
|
this.getComponentArrayAddMethod.value === 'unshift' ||
|
|
496
544
|
this.getComponentArrayAddMethod.value === 'push'
|
|
497
545
|
) {
|
|
498
546
|
// push to top
|
|
499
547
|
if (this.getComponentArrayAddMethod.value === 'unshift') {
|
|
500
|
-
|
|
548
|
+
pageBuilder.scrollTo({
|
|
501
549
|
top: 0,
|
|
502
550
|
behavior: 'smooth',
|
|
503
551
|
})
|
|
@@ -505,9 +553,8 @@ export class PageBuilderService {
|
|
|
505
553
|
|
|
506
554
|
// push to bottom
|
|
507
555
|
if (this.getComponentArrayAddMethod.value === 'push') {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
top: maxHeight,
|
|
556
|
+
pageBuilder.scrollTo({
|
|
557
|
+
top: 0,
|
|
511
558
|
behavior: 'smooth',
|
|
512
559
|
})
|
|
513
560
|
}
|
|
@@ -601,80 +648,6 @@ export class PageBuilderService {
|
|
|
601
648
|
}
|
|
602
649
|
}
|
|
603
650
|
|
|
604
|
-
handleRemoveClasses(userSelectedClass: string): void {
|
|
605
|
-
// remove selected class from element
|
|
606
|
-
if (this.getElement.value?.classList.contains(userSelectedClass)) {
|
|
607
|
-
this.getElement.value?.classList.remove(userSelectedClass)
|
|
608
|
-
|
|
609
|
-
this.pageBuilderStateStore.setElement(this.getElement.value)
|
|
610
|
-
this.pageBuilderStateStore.removeClass(userSelectedClass)
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
handleDeleteElement() {
|
|
615
|
-
// Get the element to be deleted
|
|
616
|
-
const element = this.getElement.value
|
|
617
|
-
|
|
618
|
-
if (!element) return
|
|
619
|
-
|
|
620
|
-
if (!element?.parentNode) {
|
|
621
|
-
this.pageBuilderStateStore.setComponent(null)
|
|
622
|
-
this.pageBuilderStateStore.setElement(null)
|
|
623
|
-
return
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// Store the parent of the deleted element
|
|
627
|
-
// if parent element tag is section remove the hole component
|
|
628
|
-
if (element.parentElement?.tagName !== 'SECTION') {
|
|
629
|
-
this.pageBuilderStateStore.setParentElement(element.parentNode as HTMLElement)
|
|
630
|
-
|
|
631
|
-
// Store the outerHTML of the deleted element
|
|
632
|
-
this.pageBuilderStateStore.setRestoredElement(element.outerHTML)
|
|
633
|
-
|
|
634
|
-
// Store the next sibling of the deleted element
|
|
635
|
-
this.pageBuilderStateStore.setNextSibling(element.nextSibling as HTMLElement | null)
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// if parent element tag is section remove the hole component
|
|
639
|
-
if (element.parentElement?.tagName === 'SECTION') {
|
|
640
|
-
this.deleteSelectedComponent()
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// Remove the element from the DOM
|
|
644
|
-
element.remove()
|
|
645
|
-
this.pageBuilderStateStore.setComponent(null)
|
|
646
|
-
this.pageBuilderStateStore.setElement(null)
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
async handleRestoreElement() {
|
|
650
|
-
// Get the stored deleted element and its parent
|
|
651
|
-
if (this.getRestoredElement.value && this.getParentElement.value) {
|
|
652
|
-
// Create a new element from the stored outerHTML
|
|
653
|
-
const newElement = document.createElement('div')
|
|
654
|
-
// Fixed type conversion issue
|
|
655
|
-
newElement.innerHTML = this.getRestoredElement.value
|
|
656
|
-
|
|
657
|
-
// Append the restored element to its parent
|
|
658
|
-
// Insert the restored element before its next sibling in its parent
|
|
659
|
-
if (newElement.firstChild && this.getParentElement.value) {
|
|
660
|
-
// insertBefore can accept null as second parameter (will append to end)
|
|
661
|
-
this.getParentElement.value.insertBefore(newElement.firstChild, this.getNextSibling.value)
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
// Clear
|
|
666
|
-
this.pageBuilderStateStore.setParentElement(null)
|
|
667
|
-
this.pageBuilderStateStore.setRestoredElement(null)
|
|
668
|
-
this.pageBuilderStateStore.setNextSibling(null)
|
|
669
|
-
this.pageBuilderStateStore.setComponent(null)
|
|
670
|
-
this.pageBuilderStateStore.setElement(null)
|
|
671
|
-
|
|
672
|
-
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
673
|
-
await nextTick()
|
|
674
|
-
// Attach event listeners to all editable elements in the Builder
|
|
675
|
-
await this.#addListenersToEditableElements()
|
|
676
|
-
}
|
|
677
|
-
|
|
678
651
|
handleFontWeight(userSelectedFontWeight?: string): void {
|
|
679
652
|
this.#applyElementClassChanges(
|
|
680
653
|
userSelectedFontWeight,
|
|
@@ -835,15 +808,40 @@ export class PageBuilderService {
|
|
|
835
808
|
this.#applyElementClassChanges(opacity, tailwindOpacities.opacities, 'setOpacity')
|
|
836
809
|
}
|
|
837
810
|
|
|
838
|
-
|
|
811
|
+
/**
|
|
812
|
+
* Removes all components from both the builder state and the DOM.
|
|
813
|
+
*
|
|
814
|
+
* - First clears the components array in the store.
|
|
815
|
+
* - Then, as a defensive measure, also manually removes all sections elements from the DOM.
|
|
816
|
+
*
|
|
817
|
+
* This manual DOM clearing is only optional
|
|
818
|
+
*
|
|
819
|
+
*/
|
|
820
|
+
|
|
821
|
+
deleteAllComponentsFromDOM() {
|
|
822
|
+
// Clear the store
|
|
839
823
|
this.pageBuilderStateStore.setComponents([])
|
|
824
|
+
|
|
825
|
+
// Also clear the DOM
|
|
826
|
+
const pagebuilder = document.querySelector('#pagebuilder')
|
|
827
|
+
if (pagebuilder) {
|
|
828
|
+
// Remove all section elements (assuming each component is a <section>)
|
|
829
|
+
pagebuilder
|
|
830
|
+
.querySelectorAll('section[data-componentid]')
|
|
831
|
+
.forEach((section) => section.remove())
|
|
832
|
+
}
|
|
840
833
|
}
|
|
841
834
|
|
|
842
|
-
async
|
|
843
|
-
|
|
835
|
+
async deleteComponentFromDOM() {
|
|
836
|
+
this.#syncDomToStoreOnly()
|
|
837
|
+
await nextTick()
|
|
838
|
+
|
|
839
|
+
const components = this.getComponents.value
|
|
840
|
+
|
|
841
|
+
if (!components) return
|
|
844
842
|
|
|
845
843
|
// Find the index of the component to delete
|
|
846
|
-
const indexToDelete =
|
|
844
|
+
const indexToDelete = components.findIndex(
|
|
847
845
|
(component) => component.id === this.getComponent.value?.id,
|
|
848
846
|
)
|
|
849
847
|
|
|
@@ -852,17 +850,99 @@ export class PageBuilderService {
|
|
|
852
850
|
return
|
|
853
851
|
}
|
|
854
852
|
|
|
855
|
-
//
|
|
856
|
-
|
|
857
|
-
|
|
853
|
+
// Create a new array without the deleted component (avoid mutating original array)
|
|
854
|
+
const newComponents = [
|
|
855
|
+
...components.slice(0, indexToDelete),
|
|
856
|
+
...components.slice(indexToDelete + 1),
|
|
857
|
+
]
|
|
858
858
|
|
|
859
|
-
|
|
859
|
+
this.pageBuilderStateStore.setComponents(newComponents)
|
|
860
|
+
|
|
861
|
+
// Remove the section from the DOM as well
|
|
862
|
+
const pagebuilder = document.querySelector('#pagebuilder')
|
|
863
|
+
if (pagebuilder && this.getComponent.value?.id) {
|
|
864
|
+
const section = pagebuilder.querySelector(
|
|
865
|
+
`section[data-componentid="${this.getComponent.value.id}"]`,
|
|
866
|
+
)
|
|
867
|
+
if (section) section.remove()
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Wait for Vue to finish DOM updates before attaching event listeners.
|
|
860
871
|
await nextTick()
|
|
861
|
-
// Attach event listeners to all editable elements in the Builder
|
|
862
872
|
await this.#addListenersToEditableElements()
|
|
863
873
|
|
|
864
874
|
this.pageBuilderStateStore.setComponent(null)
|
|
865
875
|
this.pageBuilderStateStore.setElement(null)
|
|
876
|
+
|
|
877
|
+
// Optional: auto-save after deletion
|
|
878
|
+
await this.handleAutoSave()
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
async deleteElementFromDOM() {
|
|
882
|
+
const element = this.getElement.value
|
|
883
|
+
if (!element) return
|
|
884
|
+
|
|
885
|
+
if (!element.parentNode) {
|
|
886
|
+
this.pageBuilderStateStore.setComponent(null)
|
|
887
|
+
this.pageBuilderStateStore.setElement(null)
|
|
888
|
+
return
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// If the element is inside a section, but not the section itself, store info for undo/restore
|
|
892
|
+
if (element.parentElement?.tagName !== 'SECTION') {
|
|
893
|
+
this.pageBuilderStateStore.setParentElement(element.parentNode as HTMLElement)
|
|
894
|
+
this.pageBuilderStateStore.setRestoredElement(element.outerHTML)
|
|
895
|
+
this.pageBuilderStateStore.setNextSibling(element.nextSibling as HTMLElement | null)
|
|
896
|
+
// Remove only the element itself from the DOM
|
|
897
|
+
element.remove()
|
|
898
|
+
} else {
|
|
899
|
+
// If the element's parent is a section, remove the whole component (section)
|
|
900
|
+
await this.deleteComponentFromDOM()
|
|
901
|
+
// No need to call element.remove() here, as the section is already removed
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Clear selection state
|
|
905
|
+
this.pageBuilderStateStore.setComponent(null)
|
|
906
|
+
this.pageBuilderStateStore.setElement(null)
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
async restoreDeletedElementToDOM() {
|
|
910
|
+
// Restore the previously deleted element to the DOM
|
|
911
|
+
const restoredHTML = this.getRestoredElement.value
|
|
912
|
+
const parent = this.getParentElement.value
|
|
913
|
+
const nextSibling = this.getNextSibling.value
|
|
914
|
+
|
|
915
|
+
if (restoredHTML && parent) {
|
|
916
|
+
// Create a container and parse the HTML
|
|
917
|
+
const container = document.createElement('div')
|
|
918
|
+
container.innerHTML = restoredHTML
|
|
919
|
+
|
|
920
|
+
// Insert the restored element before its next sibling (or append if null)
|
|
921
|
+
if (container.firstChild) {
|
|
922
|
+
parent.insertBefore(container.firstChild, nextSibling)
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Clear restore-related state
|
|
927
|
+
this.pageBuilderStateStore.setParentElement(null)
|
|
928
|
+
this.pageBuilderStateStore.setRestoredElement(null)
|
|
929
|
+
this.pageBuilderStateStore.setNextSibling(null)
|
|
930
|
+
this.pageBuilderStateStore.setComponent(null)
|
|
931
|
+
this.pageBuilderStateStore.setElement(null)
|
|
932
|
+
|
|
933
|
+
// Wait for Vue to finish DOM updates before attaching event listeners
|
|
934
|
+
await nextTick()
|
|
935
|
+
await this.#addListenersToEditableElements()
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
handleRemoveClasses(userSelectedClass: string): void {
|
|
939
|
+
// remove selected class from element
|
|
940
|
+
if (this.getElement.value?.classList.contains(userSelectedClass)) {
|
|
941
|
+
this.getElement.value?.classList.remove(userSelectedClass)
|
|
942
|
+
|
|
943
|
+
this.pageBuilderStateStore.setElement(this.getElement.value)
|
|
944
|
+
this.pageBuilderStateStore.removeClass(userSelectedClass)
|
|
945
|
+
}
|
|
866
946
|
}
|
|
867
947
|
|
|
868
948
|
// move component
|
|
@@ -927,7 +1007,7 @@ export class PageBuilderService {
|
|
|
927
1007
|
}
|
|
928
1008
|
|
|
929
1009
|
if (typeof this.getElement.value.innerHTML === 'string') {
|
|
930
|
-
await
|
|
1010
|
+
await nextTick()
|
|
931
1011
|
|
|
932
1012
|
// Update text content
|
|
933
1013
|
this.getElement.value.textContent = textContentVueModel
|
|
@@ -1199,11 +1279,31 @@ export class PageBuilderService {
|
|
|
1199
1279
|
}
|
|
1200
1280
|
|
|
1201
1281
|
/**
|
|
1202
|
-
*
|
|
1203
|
-
*
|
|
1204
|
-
|
|
1282
|
+
* Syncs the current DOM state into the in-memory store (getComponents),
|
|
1283
|
+
* but does NOT save to localStorage.
|
|
1284
|
+
*/
|
|
1285
|
+
#syncDomToStoreOnly() {
|
|
1286
|
+
const pagebuilder = document.querySelector('#pagebuilder')
|
|
1287
|
+
if (!pagebuilder) return
|
|
1288
|
+
|
|
1289
|
+
const componentsToSave: { html_code: string; id: string | null; title: string }[] = []
|
|
1290
|
+
|
|
1291
|
+
pagebuilder.querySelectorAll('section[data-componentid]').forEach((section) => {
|
|
1292
|
+
const sanitizedSection = this.#cloneAndRemoveSelectionAttributes(section as HTMLElement)
|
|
1293
|
+
componentsToSave.push({
|
|
1294
|
+
html_code: sanitizedSection.outerHTML,
|
|
1295
|
+
id: sanitizedSection.getAttribute('data-componentid'),
|
|
1296
|
+
title: sanitizedSection.getAttribute('data-component-title') || 'Untitled Component',
|
|
1297
|
+
})
|
|
1298
|
+
})
|
|
1299
|
+
|
|
1300
|
+
this.pageBuilderStateStore.setComponents(componentsToSave)
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
/**
|
|
1304
|
+
* Saves the current DOM state (components) to localStorage.
|
|
1205
1305
|
*/
|
|
1206
|
-
#
|
|
1306
|
+
#saveDomComponentsToLocalStorage() {
|
|
1207
1307
|
const pagebuilder = document.querySelector('#pagebuilder')
|
|
1208
1308
|
if (!pagebuilder) return
|
|
1209
1309
|
|
|
@@ -1235,23 +1335,14 @@ export class PageBuilderService {
|
|
|
1235
1335
|
if (keyForSavingFromDomToLocal && typeof keyForSavingFromDomToLocal === 'string') {
|
|
1236
1336
|
localStorage.setItem(keyForSavingFromDomToLocal, JSON.stringify(dataToSave))
|
|
1237
1337
|
}
|
|
1238
|
-
|
|
1239
|
-
// No DOM mutation here!
|
|
1240
|
-
await new Promise<void>((resolve) => resolve())
|
|
1241
1338
|
}
|
|
1242
1339
|
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
await this.nextTick
|
|
1246
|
-
|
|
1247
|
-
await this.#domToComponentsSync()
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
async removeItemComponentsLocalStorage() {
|
|
1251
|
-
await this.nextTick
|
|
1340
|
+
async removeCurrentComponentsFromLocalStorage() {
|
|
1341
|
+
await nextTick()
|
|
1252
1342
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1343
|
+
const key = this.getLocalStorageItemName.value
|
|
1344
|
+
if (key) {
|
|
1345
|
+
localStorage.removeItem(key)
|
|
1255
1346
|
}
|
|
1256
1347
|
}
|
|
1257
1348
|
|
|
@@ -1436,7 +1527,7 @@ export class PageBuilderService {
|
|
|
1436
1527
|
|
|
1437
1528
|
// Only apply if an image is staged
|
|
1438
1529
|
if (this.getApplyImageToSelection.value && this.getApplyImageToSelection.value.src) {
|
|
1439
|
-
await
|
|
1530
|
+
await nextTick()
|
|
1440
1531
|
this.pageBuilderStateStore.setBasePrimaryImage(`${this.getApplyImageToSelection.value.src}`)
|
|
1441
1532
|
|
|
1442
1533
|
await this.handleAutoSave()
|
|
@@ -1767,7 +1858,7 @@ export class PageBuilderService {
|
|
|
1767
1858
|
await this.#addListenersToEditableElements()
|
|
1768
1859
|
} catch (error) {
|
|
1769
1860
|
console.error('Error parsing JSON components:', error)
|
|
1770
|
-
this.
|
|
1861
|
+
this.deleteAllComponentsFromDOM()
|
|
1771
1862
|
}
|
|
1772
1863
|
}
|
|
1773
1864
|
// Private method to parse HTML components
|
|
@@ -1822,86 +1913,146 @@ export class PageBuilderService {
|
|
|
1822
1913
|
await this.#addListenersToEditableElements()
|
|
1823
1914
|
} catch (error) {
|
|
1824
1915
|
console.error('Error parsing HTML components:', error)
|
|
1825
|
-
this.
|
|
1916
|
+
this.deleteAllComponentsFromDOM()
|
|
1826
1917
|
}
|
|
1827
1918
|
}
|
|
1828
1919
|
|
|
1829
|
-
/**
|
|
1830
|
-
* Mount Components to DOM
|
|
1831
|
-
* @param passedData - HTML/JSON string to inject (optional)
|
|
1832
|
-
* @param preferLocalStorage - if true, always try localStorage first
|
|
1833
|
-
*/
|
|
1834
1920
|
async mountComponentsToDOM(passedData: string): Promise<void> {
|
|
1835
|
-
const
|
|
1921
|
+
const config = this.pageBuilderStateStore.getPageBuilderConfig
|
|
1922
|
+
const formType = config && config.updateOrCreate && config.updateOrCreate.formType
|
|
1836
1923
|
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1924
|
+
if (formType) {
|
|
1925
|
+
const pagebuilder = document.querySelector('#pagebuilder')
|
|
1926
|
+
const localStorageData = this.loadStoredComponentsFromStorage()
|
|
1927
|
+
|
|
1928
|
+
if (!pagebuilder) {
|
|
1929
|
+
await this.#handlePageBuilderNotPresent(passedData, formType)
|
|
1930
|
+
return
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
this.#handleOriginalComponentsForUpdate(passedData, formType)
|
|
1934
|
+
|
|
1935
|
+
if (this.#isCreateFormType(formType)) {
|
|
1936
|
+
await this.#handleCreateFormType(passedData, localStorageData)
|
|
1937
|
+
return
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
if (this.#isUpdateFormType(formType)) {
|
|
1941
|
+
await this.#handleUpdateFormType(passedData, localStorageData)
|
|
1942
|
+
return
|
|
1943
|
+
}
|
|
1841
1944
|
}
|
|
1945
|
+
}
|
|
1842
1946
|
|
|
1843
|
-
|
|
1844
|
-
this.pendingMountData = null
|
|
1947
|
+
// --- Private helpers ---
|
|
1845
1948
|
|
|
1846
|
-
|
|
1949
|
+
async #handlePageBuilderNotPresent(passedData: string, formType: string) {
|
|
1950
|
+
if (formType === 'create') {
|
|
1951
|
+
console.log('mountComponentsToDOM ran: m0')
|
|
1952
|
+
this.pendingMountData = ''
|
|
1953
|
+
return
|
|
1954
|
+
}
|
|
1955
|
+
console.log('mountComponentsToDOM ran: m1:')
|
|
1956
|
+
this.pendingMountData = passedData
|
|
1957
|
+
}
|
|
1847
1958
|
|
|
1848
|
-
|
|
1849
|
-
if (
|
|
1850
|
-
|
|
1851
|
-
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
1852
|
-
typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
|
|
1853
|
-
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'update' &&
|
|
1854
|
-
passedData &&
|
|
1855
|
-
!this.originalComponents
|
|
1856
|
-
) {
|
|
1959
|
+
#handleOriginalComponentsForUpdate(passedData: string, formType: string) {
|
|
1960
|
+
if (formType === 'update' && passedData && !this.originalComponents) {
|
|
1961
|
+
console.log('mountComponentsToDOM ran: m3')
|
|
1857
1962
|
this.originalComponents = passedData
|
|
1858
1963
|
}
|
|
1964
|
+
}
|
|
1859
1965
|
|
|
1860
|
-
|
|
1861
|
-
if (
|
|
1862
|
-
|
|
1863
|
-
this
|
|
1864
|
-
|
|
1865
|
-
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'update'
|
|
1866
|
-
) {
|
|
1867
|
-
if (passedData) {
|
|
1868
|
-
await this.#setComponentsFromData(passedData)
|
|
1869
|
-
return
|
|
1870
|
-
}
|
|
1966
|
+
async #handleUpdateFormType(passedData: string, localStorageData: string | false) {
|
|
1967
|
+
if (passedData) {
|
|
1968
|
+
console.log('mountComponentsToDOM ran: m4')
|
|
1969
|
+
await this.#setComponentsFromData(passedData)
|
|
1970
|
+
return
|
|
1871
1971
|
}
|
|
1972
|
+
if (localStorageData) {
|
|
1973
|
+
console.log('mountComponentsToDOM ran: m5')
|
|
1974
|
+
await this.#setComponentsFromData(localStorageData)
|
|
1975
|
+
return
|
|
1976
|
+
}
|
|
1977
|
+
// If nothing, clear components
|
|
1978
|
+
console.log('mountComponentsToDOM ran: m6')
|
|
1979
|
+
this.deleteAllComponentsFromDOM()
|
|
1980
|
+
}
|
|
1872
1981
|
|
|
1873
|
-
|
|
1874
|
-
|
|
1982
|
+
async #handleCreateFormType(passedData: string, localStorageData: string | false) {
|
|
1983
|
+
if (localStorageData) {
|
|
1984
|
+
console.log('mountComponentsToDOM ran: m7')
|
|
1985
|
+
await this.#setComponentsFromData(localStorageData)
|
|
1986
|
+
return
|
|
1987
|
+
}
|
|
1988
|
+
if (passedData) {
|
|
1989
|
+
console.log('mountComponentsToDOM ran: m8')
|
|
1990
|
+
await this.#setComponentsFromData(passedData)
|
|
1991
|
+
return
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1875
1994
|
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
await this.#setComponentsFromData(localStorageData)
|
|
1884
|
-
return
|
|
1885
|
-
}
|
|
1995
|
+
#isCreateFormType(formType: string): boolean {
|
|
1996
|
+
return formType === 'create'
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
#isUpdateFormType(formType: string): boolean {
|
|
2000
|
+
return formType === 'update'
|
|
2001
|
+
}
|
|
1886
2002
|
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
2003
|
+
async ensureBuilderInitializedForCreate() {
|
|
2004
|
+
const pagebuilder = document.querySelector('#pagebuilder')
|
|
2005
|
+
if (!pagebuilder) return
|
|
2006
|
+
|
|
2007
|
+
const config = this.pageBuilderStateStore.getPageBuilderConfig
|
|
2008
|
+
console.log('den er:', config)
|
|
2009
|
+
const formType = config && config.updateOrCreate && config.updateOrCreate.formType
|
|
2010
|
+
|
|
2011
|
+
if (formType === 'create') {
|
|
2012
|
+
this.#completeBuilderInitialization()
|
|
2013
|
+
await nextTick()
|
|
2014
|
+
|
|
2015
|
+
if (
|
|
2016
|
+
formType === 'create' &&
|
|
2017
|
+
(!this.getComponents.value ||
|
|
2018
|
+
(Array.isArray(this.getComponents.value) && this.getComponents.value.length === 0))
|
|
2019
|
+
) {
|
|
2020
|
+
console.log('ensureBuilderInitializedForCreate e1')
|
|
2021
|
+
await this.mountComponentsToDOM('')
|
|
2022
|
+
this.pendingMountData = null
|
|
1890
2023
|
return
|
|
1891
2024
|
}
|
|
2025
|
+
|
|
2026
|
+
console.log('ensureBuilderInitializedForCreate e2:')
|
|
2027
|
+
await this.mountComponentsToDOM('')
|
|
2028
|
+
await nextTick()
|
|
2029
|
+
// Attach event listeners to all editable elements in the Builder
|
|
2030
|
+
await this.#addListenersToEditableElements()
|
|
1892
2031
|
}
|
|
1893
2032
|
}
|
|
1894
2033
|
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
2034
|
+
async ensureBuilderInitializedForUpdate() {
|
|
2035
|
+
const pagebuilder = document.querySelector('#pagebuilder')
|
|
2036
|
+
if (!pagebuilder) return
|
|
2037
|
+
|
|
2038
|
+
const config = this.pageBuilderStateStore.getPageBuilderConfig
|
|
2039
|
+
const formType = config && config.updateOrCreate && config.updateOrCreate.formType
|
|
1898
2040
|
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
this.pendingMountData
|
|
1904
|
-
|
|
2041
|
+
if (formType === 'update') {
|
|
2042
|
+
this.#completeBuilderInitialization()
|
|
2043
|
+
|
|
2044
|
+
// Only for update/draft/demo: mount if pendingMountData is a non-empty string
|
|
2045
|
+
if (this.pendingMountData && typeof this.pendingMountData === 'string') {
|
|
2046
|
+
console.log('ensureBuilderInitializedForUpdate t1:')
|
|
2047
|
+
await this.mountComponentsToDOM(this.pendingMountData)
|
|
2048
|
+
this.pendingMountData = null
|
|
2049
|
+
return
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
console.log('ensureBuilderInitializedForUpdate t2:')
|
|
2053
|
+
await nextTick()
|
|
2054
|
+
// Always try to load latest from localStorage or fallback
|
|
2055
|
+
await this.mountComponentsToDOM('')
|
|
1905
2056
|
}
|
|
1906
2057
|
}
|
|
1907
2058
|
|