@myissue/vue-website-page-builder 3.2.91 → 3.2.93
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 +122 -84
- package/dist/vue-website-page-builder.css +1 -1
- package/dist/vue-website-page-builder.js +5221 -5286
- package/dist/vue-website-page-builder.umd.cjs +52 -52
- package/package.json +1 -1
- package/src/Components/Loaders/GlobalLoader.vue +11 -0
- package/src/Components/Modals/DynamicModalBuilder.vue +41 -245
- package/src/Components/Modals/ModalBuilder.vue +29 -4
- package/src/Components/PageBuilder/DefaultComponents/DefaultBuilderComponents.vue +3 -8
- package/src/Components/PageBuilder/EditorMenu/Editables/BackgroundColorEditor.vue +5 -4
- package/src/Components/PageBuilder/EditorMenu/Editables/BorderRadius.vue +12 -13
- package/src/Components/PageBuilder/EditorMenu/Editables/Borders.vue +8 -8
- package/src/Components/PageBuilder/EditorMenu/Editables/ClassEditor.vue +7 -6
- package/src/Components/PageBuilder/EditorMenu/Editables/ComponentTopMenu.vue +6 -10
- package/src/Components/PageBuilder/EditorMenu/Editables/DeleteElement.vue +4 -4
- package/src/Components/PageBuilder/EditorMenu/Editables/EditGetElement.vue +10 -11
- package/src/Components/PageBuilder/EditorMenu/Editables/ElementEditor.vue +4 -5
- package/src/Components/PageBuilder/EditorMenu/Editables/ImageEditor.vue +0 -9
- package/src/Components/PageBuilder/EditorMenu/Editables/LinkEditor.vue +5 -5
- package/src/Components/PageBuilder/EditorMenu/Editables/ManageBackgroundOpacity.vue +4 -4
- package/src/Components/PageBuilder/EditorMenu/Editables/ManageOpacity.vue +4 -4
- package/src/Components/PageBuilder/EditorMenu/Editables/Margin.vue +8 -8
- package/src/Components/PageBuilder/EditorMenu/Editables/Padding.vue +8 -8
- package/src/Components/PageBuilder/EditorMenu/Editables/TextColorEditor.vue +4 -4
- package/src/Components/PageBuilder/EditorMenu/Editables/Typography.vue +16 -16
- package/src/Components/PageBuilder/EditorMenu/RightSidebarEditor.vue +3 -7
- package/src/Components/PageBuilder/Settings/PageBuilderSettings.vue +55 -58
- package/src/Components/PageBuilder/ToolbarOption/ToolbarOption.vue +33 -40
- package/src/Components/TipTap/TipTap.vue +4 -9
- package/src/Components/TipTap/TipTapInput.vue +8 -8
- package/src/DemoComponents/DemoUnsplash.vue +11 -12
- package/src/DemoComponents/HomeSection.vue +9 -30
- package/src/PageBuilder/PageBuilder.vue +194 -96
- package/src/composables/{PageBuilderClass.ts → PageBuilderService.ts} +292 -112
- package/src/composables/builderInstance.ts +25 -0
- package/src/css/app.css +15 -0
- package/src/css/dev-global.css +7 -0
- package/src/index.ts +4 -2
- package/src/main.ts +3 -0
- package/src/stores/page-builder-state.ts +39 -10
- package/src/types/index.ts +100 -10
- package/src/helpers/passedPageBuilderConfig.ts +0 -71
|
@@ -14,8 +14,9 @@ import { computed, ref, nextTick } from 'vue'
|
|
|
14
14
|
import type { ComputedRef } from 'vue'
|
|
15
15
|
import { v4 as uuidv4 } from 'uuid'
|
|
16
16
|
import { delay } from './delay'
|
|
17
|
+
import { isEmptyObject } from '../helpers/isEmptyObject'
|
|
17
18
|
|
|
18
|
-
class
|
|
19
|
+
export class PageBuilderService {
|
|
19
20
|
// Class properties with types
|
|
20
21
|
private nextTick: Promise<void>
|
|
21
22
|
private containsPagebuilder: Element | null
|
|
@@ -23,7 +24,7 @@ class PageBuilderClass {
|
|
|
23
24
|
private pageBuilderStateStore: ReturnType<typeof usePageBuilderStateStore>
|
|
24
25
|
private getTextAreaVueModel: ComputedRef<string | null>
|
|
25
26
|
private getLocalStorageItemName: ComputedRef<string | null>
|
|
26
|
-
private
|
|
27
|
+
private getApplyImageToSelection: ComputedRef<ImageObject>
|
|
27
28
|
private getHyberlinkEnable: ComputedRef<boolean>
|
|
28
29
|
private getComponents: ComputedRef<ComponentObject[] | null>
|
|
29
30
|
private getComponent: ComputedRef<ComponentObject | null>
|
|
@@ -35,10 +36,10 @@ class PageBuilderClass {
|
|
|
35
36
|
private NoneListernesTags: string[]
|
|
36
37
|
private delay: (ms?: number) => Promise<void>
|
|
37
38
|
private hasStartedEditing: boolean = false
|
|
39
|
+
private originalComponents: string | null = null
|
|
38
40
|
|
|
39
41
|
constructor(pageBuilderStateStore: ReturnType<typeof usePageBuilderStateStore>) {
|
|
40
42
|
this.nextTick = nextTick()
|
|
41
|
-
|
|
42
43
|
this.hasStartedEditing = false
|
|
43
44
|
this.containsPagebuilder = document.querySelector('#contains-pagebuilder')
|
|
44
45
|
this.pageBuilderStateStore = pageBuilderStateStore
|
|
@@ -48,7 +49,9 @@ class PageBuilderClass {
|
|
|
48
49
|
() => this.pageBuilderStateStore.getLocalStorageItemName,
|
|
49
50
|
)
|
|
50
51
|
|
|
51
|
-
this.
|
|
52
|
+
this.getApplyImageToSelection = computed(
|
|
53
|
+
() => this.pageBuilderStateStore.getApplyImageToSelection,
|
|
54
|
+
)
|
|
52
55
|
this.getHyberlinkEnable = computed(() => this.pageBuilderStateStore.getHyberlinkEnable)
|
|
53
56
|
this.getComponents = computed(() => this.pageBuilderStateStore.getComponents)
|
|
54
57
|
|
|
@@ -93,9 +96,138 @@ class PageBuilderClass {
|
|
|
93
96
|
this.pageBuilderStateStore.setElement(null)
|
|
94
97
|
await this.#removeHoveredAndSelected()
|
|
95
98
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
|
|
100
|
+
#ensureUpdateOrCreateConfig(config: PageBuilderConfig): void {
|
|
101
|
+
// Case A: updateOrCreate is missing or an empty object
|
|
102
|
+
if (!config.updateOrCreate || (config.updateOrCreate && isEmptyObject(config.updateOrCreate))) {
|
|
103
|
+
const updatedConfig = {
|
|
104
|
+
...config,
|
|
105
|
+
updateOrCreate: {
|
|
106
|
+
formType: 'create',
|
|
107
|
+
formName: 'post',
|
|
108
|
+
},
|
|
109
|
+
} as const
|
|
110
|
+
|
|
111
|
+
this.pageBuilderStateStore.setPageBuilderConfig(updatedConfig)
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Case B: formType is valid ('create' or 'update'), but formName is missing or an empty string
|
|
116
|
+
if (
|
|
117
|
+
(config.updateOrCreate &&
|
|
118
|
+
typeof config.updateOrCreate.formType === 'string' &&
|
|
119
|
+
(config.updateOrCreate.formType === 'create' ||
|
|
120
|
+
config.updateOrCreate.formType === 'update') &&
|
|
121
|
+
typeof config.updateOrCreate.formName !== 'string') ||
|
|
122
|
+
(typeof config.updateOrCreate.formName === 'string' &&
|
|
123
|
+
config.updateOrCreate.formName.length === 0)
|
|
124
|
+
) {
|
|
125
|
+
const updatedConfig = {
|
|
126
|
+
...config,
|
|
127
|
+
updateOrCreate: {
|
|
128
|
+
formType: config.updateOrCreate.formType,
|
|
129
|
+
formName: 'post',
|
|
130
|
+
},
|
|
131
|
+
} as const
|
|
132
|
+
this.pageBuilderStateStore.setPageBuilderConfig(updatedConfig)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Case C: formType is missing or not a valid string like ('create' or 'update') but formName is valid string
|
|
136
|
+
if (
|
|
137
|
+
(config.updateOrCreate && typeof config.updateOrCreate.formType !== 'string') ||
|
|
138
|
+
(typeof config.updateOrCreate.formType === 'string' &&
|
|
139
|
+
config.updateOrCreate.formType !== 'create' &&
|
|
140
|
+
config.updateOrCreate.formType !== 'update' &&
|
|
141
|
+
typeof config.updateOrCreate.formName === 'string' &&
|
|
142
|
+
config.updateOrCreate.formName.length !== 0)
|
|
143
|
+
) {
|
|
144
|
+
const updatedConfig = {
|
|
145
|
+
...config,
|
|
146
|
+
updateOrCreate: {
|
|
147
|
+
formType: 'create',
|
|
148
|
+
formName: config.updateOrCreate.formName,
|
|
149
|
+
},
|
|
150
|
+
} as const
|
|
151
|
+
|
|
152
|
+
this.pageBuilderStateStore.setPageBuilderConfig(updatedConfig)
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Case D: formType exists but is not 'create' or 'update', and formName is missing or invalid
|
|
157
|
+
if (
|
|
158
|
+
config.updateOrCreate &&
|
|
159
|
+
typeof config.updateOrCreate.formType === 'string' &&
|
|
160
|
+
config.updateOrCreate.formType !== 'create' &&
|
|
161
|
+
config.updateOrCreate.formType !== 'update' &&
|
|
162
|
+
typeof config.formName !== 'string'
|
|
163
|
+
) {
|
|
164
|
+
const updatedConfig = {
|
|
165
|
+
...config,
|
|
166
|
+
updateOrCreate: {
|
|
167
|
+
formType: 'create',
|
|
168
|
+
formName: 'post',
|
|
169
|
+
},
|
|
170
|
+
} as const
|
|
171
|
+
|
|
172
|
+
this.pageBuilderStateStore.setPageBuilderConfig(updatedConfig)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
#validateConfig(config: PageBuilderConfig): void {
|
|
177
|
+
const defaultConfigValues = {
|
|
178
|
+
updateOrCreate: {
|
|
179
|
+
formType: 'create',
|
|
180
|
+
formName: 'post',
|
|
181
|
+
},
|
|
182
|
+
} as const
|
|
183
|
+
|
|
184
|
+
// Set config for page builder if not set by user
|
|
185
|
+
if (!config || (config && Object.keys(config).length === 0 && config.constructor === Object)) {
|
|
186
|
+
this.pageBuilderStateStore.setPageBuilderConfig(defaultConfigValues)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (config && Object.keys(config).length !== 0 && config.constructor === Object) {
|
|
190
|
+
this.#ensureUpdateOrCreateConfig(config)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Initializes the Page Builder with the provided configuration.
|
|
196
|
+
* Handles config validation, local storage, and sets up the builder state.
|
|
197
|
+
*/
|
|
198
|
+
async startBuilder(config: PageBuilderConfig): Promise<void> {
|
|
199
|
+
// Show a global loading indicator while initializing
|
|
200
|
+
this.pageBuilderStateStore.setIsLoadingGlobal(true)
|
|
201
|
+
|
|
202
|
+
// Wait briefly to ensure UI updates and async processes settle
|
|
203
|
+
await this.delay(300)
|
|
204
|
+
|
|
205
|
+
// Store the provided config in the builder's state store
|
|
206
|
+
this.pageBuilderStateStore.setPageBuilderConfig(config)
|
|
207
|
+
|
|
208
|
+
// Validate and normalize the config (ensure required fields are present)
|
|
209
|
+
this.#validateConfig(config)
|
|
210
|
+
|
|
211
|
+
// Update the localStorage key name based on the config/resource
|
|
212
|
+
this.#updateLocalStorageItemName()
|
|
213
|
+
|
|
214
|
+
// If there is a local draft for this resource, mark it in the state
|
|
215
|
+
if (await this.#hasLocalDraftForUpdate()) {
|
|
216
|
+
this.pageBuilderStateStore.setHasLocalDraftForUpdate(true)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Clean up any old localStorage items related to previous builder sessions
|
|
220
|
+
this.deleteOldPageBuilderLocalStorage()
|
|
221
|
+
|
|
222
|
+
// Clear any selected HTML elements in the builder UI
|
|
223
|
+
await this.clearHtmlSelection()
|
|
224
|
+
|
|
225
|
+
// Attach event listeners to all editable elements in the builder
|
|
226
|
+
await this.addListenersToEditableElements()
|
|
227
|
+
|
|
228
|
+
// Hide the global loading indicator and mark the builder as started
|
|
229
|
+
this.pageBuilderStateStore.setIsLoadingGlobal(false)
|
|
230
|
+
this.pageBuilderStateStore.setBuilderStarted(true)
|
|
99
231
|
}
|
|
100
232
|
|
|
101
233
|
#applyElementClassChanges(
|
|
@@ -265,7 +397,7 @@ class PageBuilderClass {
|
|
|
265
397
|
|
|
266
398
|
handleAutoSave = async () => {
|
|
267
399
|
this.startEditing()
|
|
268
|
-
const passedConfig = this.pageBuilderStateStore.
|
|
400
|
+
const passedConfig = this.pageBuilderStateStore.getPageBuilderConfig
|
|
269
401
|
|
|
270
402
|
// Check if config is set
|
|
271
403
|
if (passedConfig && passedConfig.userSettings) {
|
|
@@ -280,7 +412,7 @@ class PageBuilderClass {
|
|
|
280
412
|
try {
|
|
281
413
|
this.pageBuilderStateStore.setIsSaving(true)
|
|
282
414
|
await this.saveComponentsLocalStorage()
|
|
283
|
-
await this.delay(
|
|
415
|
+
await this.delay(500)
|
|
284
416
|
} catch (err) {
|
|
285
417
|
console.error('Error trying auto save.', err)
|
|
286
418
|
} finally {
|
|
@@ -303,7 +435,7 @@ class PageBuilderClass {
|
|
|
303
435
|
|
|
304
436
|
handleManualSave = async () => {
|
|
305
437
|
this.startEditing()
|
|
306
|
-
const passedConfig = this.pageBuilderStateStore.
|
|
438
|
+
const passedConfig = this.pageBuilderStateStore.getPageBuilderConfig
|
|
307
439
|
|
|
308
440
|
// Check if config is set
|
|
309
441
|
if (passedConfig && passedConfig.userSettings) {
|
|
@@ -575,7 +707,6 @@ class PageBuilderClass {
|
|
|
575
707
|
)
|
|
576
708
|
}
|
|
577
709
|
|
|
578
|
-
// border color, style & width / start
|
|
579
710
|
handleBorderStyle(borderStyle?: string): void {
|
|
580
711
|
this.#applyElementClassChanges(
|
|
581
712
|
borderStyle,
|
|
@@ -611,7 +742,6 @@ class PageBuilderClass {
|
|
|
611
742
|
this.#applyElementClassChanges(color, tailwindColors.textColorVariables, 'setTextColor')
|
|
612
743
|
}
|
|
613
744
|
|
|
614
|
-
// border radius / start
|
|
615
745
|
handleBorderRadiusGlobal(borderRadiusGlobal?: string): void {
|
|
616
746
|
this.#applyElementClassChanges(
|
|
617
747
|
borderRadiusGlobal,
|
|
@@ -690,7 +820,7 @@ class PageBuilderClass {
|
|
|
690
820
|
this.pageBuilderStateStore.setComponents([])
|
|
691
821
|
}
|
|
692
822
|
|
|
693
|
-
deleteSelectedComponent() {
|
|
823
|
+
async deleteSelectedComponent() {
|
|
694
824
|
if (!this.getComponents.value || !this.getComponent.value) return
|
|
695
825
|
|
|
696
826
|
// Find the index of the component to delete
|
|
@@ -706,6 +836,8 @@ class PageBuilderClass {
|
|
|
706
836
|
// Remove the component from the array
|
|
707
837
|
this.getComponents.value.splice(indexToDelete, 1)
|
|
708
838
|
this.pageBuilderStateStore.setComponents(this.getComponents.value)
|
|
839
|
+
await nextTick()
|
|
840
|
+
await this.addListenersToEditableElements()
|
|
709
841
|
|
|
710
842
|
this.pageBuilderStateStore.setComponent(null)
|
|
711
843
|
this.pageBuilderStateStore.setElement(null)
|
|
@@ -879,16 +1011,16 @@ class PageBuilderClass {
|
|
|
879
1011
|
.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
|
|
880
1012
|
}
|
|
881
1013
|
|
|
882
|
-
updateLocalStorageItemName(): void {
|
|
1014
|
+
#updateLocalStorageItemName(): void {
|
|
883
1015
|
const updateOrCreate =
|
|
884
|
-
this.pageBuilderStateStore.
|
|
885
|
-
this.pageBuilderStateStore.
|
|
886
|
-
this.pageBuilderStateStore.
|
|
1016
|
+
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
1017
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
1018
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType
|
|
887
1019
|
|
|
888
|
-
const resourceData = this.pageBuilderStateStore.
|
|
1020
|
+
const resourceData = this.pageBuilderStateStore.getPageBuilderConfig?.resourceData
|
|
889
1021
|
|
|
890
1022
|
const resourceFormName =
|
|
891
|
-
this.pageBuilderStateStore.
|
|
1023
|
+
this.pageBuilderStateStore.getPageBuilderConfig?.updateOrCreate?.formName
|
|
892
1024
|
|
|
893
1025
|
// Logic for create resource
|
|
894
1026
|
if (updateOrCreate === 'create') {
|
|
@@ -906,9 +1038,6 @@ class PageBuilderClass {
|
|
|
906
1038
|
// Logic for create
|
|
907
1039
|
// Logic for update and with resource form name
|
|
908
1040
|
if (updateOrCreate === 'update') {
|
|
909
|
-
//
|
|
910
|
-
//
|
|
911
|
-
//
|
|
912
1041
|
if (typeof resourceFormName === 'string' && resourceFormName.length > 0) {
|
|
913
1042
|
//
|
|
914
1043
|
//
|
|
@@ -1053,14 +1182,16 @@ class PageBuilderClass {
|
|
|
1053
1182
|
})
|
|
1054
1183
|
})
|
|
1055
1184
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1185
|
+
// Save to localStorage with pageBuilderContentSavedAt using the correct key
|
|
1186
|
+
const dataToSave = {
|
|
1187
|
+
components: componentsToSave,
|
|
1188
|
+
pageBuilderContentSavedAt: new Date().toISOString(),
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
const keyForSavingFromDomToLocal = this.getLocalStorageItemName.value
|
|
1192
|
+
|
|
1193
|
+
if (keyForSavingFromDomToLocal && typeof keyForSavingFromDomToLocal === 'string') {
|
|
1194
|
+
localStorage.setItem(keyForSavingFromDomToLocal, JSON.stringify(dataToSave))
|
|
1064
1195
|
}
|
|
1065
1196
|
|
|
1066
1197
|
// No DOM mutation here!
|
|
@@ -1085,10 +1216,10 @@ class PageBuilderClass {
|
|
|
1085
1216
|
//
|
|
1086
1217
|
deleteOldPageBuilderLocalStorage(): void {
|
|
1087
1218
|
if (
|
|
1088
|
-
this.pageBuilderStateStore.
|
|
1089
|
-
this.pageBuilderStateStore.
|
|
1090
|
-
typeof this.pageBuilderStateStore.
|
|
1091
|
-
this.pageBuilderStateStore.
|
|
1219
|
+
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
1220
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
1221
|
+
typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
|
|
1222
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'update'
|
|
1092
1223
|
) {
|
|
1093
1224
|
let oldCountLocalStorages = 0
|
|
1094
1225
|
const deletedItemsLog: { Number: number; Key: string; SavedAt: string }[] = []
|
|
@@ -1137,14 +1268,14 @@ class PageBuilderClass {
|
|
|
1137
1268
|
}
|
|
1138
1269
|
}
|
|
1139
1270
|
|
|
1140
|
-
async hasLocalDraftForUpdate(): Promise<boolean> {
|
|
1271
|
+
async #hasLocalDraftForUpdate(): Promise<boolean> {
|
|
1141
1272
|
if (this.hasStartedEditing) return false
|
|
1142
1273
|
|
|
1143
1274
|
if (
|
|
1144
|
-
this.pageBuilderStateStore.
|
|
1145
|
-
this.pageBuilderStateStore.
|
|
1146
|
-
typeof this.pageBuilderStateStore.
|
|
1147
|
-
this.pageBuilderStateStore.
|
|
1275
|
+
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
1276
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
1277
|
+
typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
|
|
1278
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'update'
|
|
1148
1279
|
) {
|
|
1149
1280
|
const key = this.getLocalStorageItemName.value
|
|
1150
1281
|
if (typeof key === 'string') {
|
|
@@ -1152,10 +1283,13 @@ class PageBuilderClass {
|
|
|
1152
1283
|
if (draft) {
|
|
1153
1284
|
try {
|
|
1154
1285
|
await this.delay(500)
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1286
|
+
|
|
1287
|
+
return true
|
|
1288
|
+
// const dbComponents = this.getComponents.value
|
|
1289
|
+
// const draftParsed = JSON.parse(draft)
|
|
1290
|
+
// return JSON.stringify(draftParsed.components) !== JSON.stringify(dbComponents)
|
|
1291
|
+
} catch (err) {
|
|
1292
|
+
console.error('Unable to mount components to DOM.', err)
|
|
1159
1293
|
return false
|
|
1160
1294
|
}
|
|
1161
1295
|
}
|
|
@@ -1169,34 +1303,51 @@ class PageBuilderClass {
|
|
|
1169
1303
|
this.hasStartedEditing = true
|
|
1170
1304
|
}
|
|
1171
1305
|
|
|
1306
|
+
//
|
|
1172
1307
|
async resumeEditingForUpdate() {
|
|
1173
1308
|
if (
|
|
1174
|
-
this.pageBuilderStateStore.
|
|
1175
|
-
this.pageBuilderStateStore.
|
|
1176
|
-
typeof this.pageBuilderStateStore.
|
|
1177
|
-
this.pageBuilderStateStore.
|
|
1309
|
+
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
1310
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
1311
|
+
typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
|
|
1312
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'update'
|
|
1178
1313
|
) {
|
|
1179
1314
|
const key = this.getLocalStorageItemName.value
|
|
1315
|
+
|
|
1180
1316
|
if (typeof key === 'string') {
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
await nextTick()
|
|
1189
|
-
await this.addListenersToEditableElements()
|
|
1190
|
-
await this.handleAutoSave()
|
|
1191
|
-
}
|
|
1192
|
-
} catch (e) {
|
|
1193
|
-
console.error('Failed to parse local draft:', e)
|
|
1194
|
-
}
|
|
1317
|
+
const updateDraftFromLocalStorage = localStorage.getItem(key)
|
|
1318
|
+
|
|
1319
|
+
if (typeof updateDraftFromLocalStorage === 'string') {
|
|
1320
|
+
this.pageBuilderStateStore.setIsResumeEditing(true)
|
|
1321
|
+
await delay(500)
|
|
1322
|
+
this.mountComponentsToDOM(updateDraftFromLocalStorage)
|
|
1323
|
+
this.pageBuilderStateStore.setIsResumeEditing(false)
|
|
1195
1324
|
}
|
|
1196
1325
|
}
|
|
1197
1326
|
}
|
|
1198
1327
|
}
|
|
1199
1328
|
|
|
1329
|
+
async restoreOriginalContent() {
|
|
1330
|
+
if (
|
|
1331
|
+
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
1332
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
1333
|
+
typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
|
|
1334
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'update'
|
|
1335
|
+
) {
|
|
1336
|
+
this.pageBuilderStateStore.setIsRestoring(true)
|
|
1337
|
+
await this.delay(300)
|
|
1338
|
+
|
|
1339
|
+
// Restore the original content if available
|
|
1340
|
+
if (this.originalComponents) {
|
|
1341
|
+
this.mountComponentsToDOM(this.originalComponents)
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
await nextTick()
|
|
1345
|
+
await this.addListenersToEditableElements()
|
|
1346
|
+
|
|
1347
|
+
this.pageBuilderStateStore.setIsRestoring(false)
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1200
1351
|
getStorageItemNameForResource(): string | null {
|
|
1201
1352
|
return this.getLocalStorageItemName.value
|
|
1202
1353
|
}
|
|
@@ -1217,39 +1368,56 @@ class PageBuilderClass {
|
|
|
1217
1368
|
|
|
1218
1369
|
return false
|
|
1219
1370
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1371
|
+
|
|
1372
|
+
/**
|
|
1373
|
+
* Sets the image selected from the media library as the "pending" image
|
|
1374
|
+
* to be applied to the currently selected element in the builder.
|
|
1375
|
+
* This does not update the DOM immediately—call `applyPendingImageToSelectedElement` to commit.
|
|
1376
|
+
* @param image - The image object to be staged for application
|
|
1377
|
+
*/
|
|
1378
|
+
stageImageForSelectedElement(image: ImageObject) {
|
|
1379
|
+
this.pageBuilderStateStore.setApplyImageToSelection(image)
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
/**
|
|
1383
|
+
* Applies the staged image (set by `stageImageForSelectedElement`) to the currently selected element.
|
|
1384
|
+
* This updates the builder state and triggers an auto-save.
|
|
1385
|
+
* If no element is selected or no image is staged, nothing happens.
|
|
1386
|
+
*/
|
|
1387
|
+
async applyPendingImageToSelectedElement(): Promise<void> {
|
|
1223
1388
|
if (!this.getElement.value) return
|
|
1224
1389
|
|
|
1225
|
-
//
|
|
1226
|
-
if (this.
|
|
1390
|
+
// Only apply if an image is staged
|
|
1391
|
+
if (this.getApplyImageToSelection.value && this.getApplyImageToSelection.value.src) {
|
|
1227
1392
|
await this.nextTick
|
|
1228
|
-
this.pageBuilderStateStore.setBasePrimaryImage(`${this.
|
|
1393
|
+
this.pageBuilderStateStore.setBasePrimaryImage(`${this.getApplyImageToSelection.value.src}`)
|
|
1394
|
+
|
|
1229
1395
|
await this.handleAutoSave()
|
|
1230
1396
|
}
|
|
1231
1397
|
}
|
|
1232
1398
|
|
|
1233
|
-
|
|
1399
|
+
/**
|
|
1400
|
+
* Inspects the currently selected element and, if it contains exactly one <img> and no <div>,
|
|
1401
|
+
* sets that image's src as the base primary image in the builder state.
|
|
1402
|
+
* If the element does not meet these criteria, clears the base primary image.
|
|
1403
|
+
*/
|
|
1404
|
+
setBasePrimaryImageFromSelectedElement() {
|
|
1234
1405
|
if (!this.getElement.value) return
|
|
1235
1406
|
|
|
1236
1407
|
const currentImageContainer = document.createElement('div')
|
|
1237
|
-
|
|
1238
1408
|
currentImageContainer.innerHTML = this.getElement.value.outerHTML
|
|
1239
1409
|
|
|
1240
1410
|
// Get all img and div within the current image container
|
|
1241
1411
|
const imgElements = currentImageContainer.getElementsByTagName('img')
|
|
1242
1412
|
const divElements = currentImageContainer.getElementsByTagName('div')
|
|
1243
1413
|
|
|
1244
|
-
//
|
|
1414
|
+
// If exactly one img and no div, set as base primary image
|
|
1245
1415
|
if (imgElements.length === 1 && divElements.length === 0) {
|
|
1246
|
-
// Return the source of the only img
|
|
1247
|
-
|
|
1248
1416
|
this.pageBuilderStateStore.setBasePrimaryImage(imgElements[0].src)
|
|
1249
|
-
|
|
1250
1417
|
return
|
|
1251
1418
|
}
|
|
1252
1419
|
|
|
1420
|
+
// Otherwise, clear the base primary image
|
|
1253
1421
|
this.pageBuilderStateStore.setBasePrimaryImage(null)
|
|
1254
1422
|
}
|
|
1255
1423
|
|
|
@@ -1476,9 +1644,9 @@ class PageBuilderClass {
|
|
|
1476
1644
|
* @param data - JSON string (e.g., '[{"html_code":"...","id":"123","title":"..."}]')
|
|
1477
1645
|
* OR HTML string (e.g., '<section data-componentid="123">...</section>')
|
|
1478
1646
|
*/
|
|
1479
|
-
setComponentsFromData(
|
|
1647
|
+
#setComponentsFromData(htmlString: string): void {
|
|
1480
1648
|
// Auto-detect if input is JSON or HTML
|
|
1481
|
-
const trimmedData =
|
|
1649
|
+
const trimmedData = htmlString.trim()
|
|
1482
1650
|
|
|
1483
1651
|
if (trimmedData.startsWith('[') || trimmedData.startsWith('{')) {
|
|
1484
1652
|
// Looks like JSON - parse as JSON
|
|
@@ -1492,7 +1660,7 @@ class PageBuilderClass {
|
|
|
1492
1660
|
}
|
|
1493
1661
|
|
|
1494
1662
|
// Private method to parse JSON components and save pageBuilderContentSavedAt to localStorage
|
|
1495
|
-
#parseJSONComponents(jsonData: string): void {
|
|
1663
|
+
async #parseJSONComponents(jsonData: string): Promise<void> {
|
|
1496
1664
|
try {
|
|
1497
1665
|
const parsedData = JSON.parse(jsonData)
|
|
1498
1666
|
let componentsArray: ComponentObject[] = []
|
|
@@ -1544,18 +1712,8 @@ class PageBuilderClass {
|
|
|
1544
1712
|
|
|
1545
1713
|
this.pageBuilderStateStore.setComponents(savedCurrentDesign)
|
|
1546
1714
|
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
pageBuilderContentSavedAt: parsedData.pageBuilderContentSavedAt || new Date().toISOString(),
|
|
1550
|
-
components: savedCurrentDesign,
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
if (
|
|
1554
|
-
this.getLocalStorageItemName.value &&
|
|
1555
|
-
typeof this.getLocalStorageItemName.value === 'string'
|
|
1556
|
-
) {
|
|
1557
|
-
localStorage.setItem(this.getLocalStorageItemName.value, JSON.stringify(dataToSave))
|
|
1558
|
-
}
|
|
1715
|
+
await this.clearHtmlSelection()
|
|
1716
|
+
await this.addListenersToEditableElements()
|
|
1559
1717
|
} catch (error) {
|
|
1560
1718
|
console.error('Error parsing JSON components:', error)
|
|
1561
1719
|
this.pageBuilderStateStore.setComponents([])
|
|
@@ -1602,37 +1760,60 @@ class PageBuilderClass {
|
|
|
1602
1760
|
}
|
|
1603
1761
|
}
|
|
1604
1762
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1763
|
+
/**
|
|
1764
|
+
* Mount Components to DOM
|
|
1765
|
+
* @param passedData - HTML/JSON string to inject (optional)
|
|
1766
|
+
* @param preferLocalStorage - if true, always try localStorage first
|
|
1767
|
+
*/
|
|
1768
|
+
mountComponentsToDOM(passedData: string): void {
|
|
1769
|
+
// Save the original content only once, in update mode, and only if passedData is provided
|
|
1770
|
+
// Form type Update
|
|
1771
|
+
if (
|
|
1772
|
+
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
1773
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
1774
|
+
typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
|
|
1775
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'update' &&
|
|
1776
|
+
passedData &&
|
|
1777
|
+
!this.originalComponents
|
|
1778
|
+
) {
|
|
1779
|
+
this.originalComponents = passedData
|
|
1613
1780
|
}
|
|
1614
1781
|
|
|
1615
|
-
|
|
1782
|
+
this.pageBuilderStateStore.setComponents([])
|
|
1783
|
+
|
|
1784
|
+
if (!this.pageBuilderStateStore.getPageBuilderConfig) return
|
|
1616
1785
|
|
|
1786
|
+
// Form type Update
|
|
1617
1787
|
if (
|
|
1618
|
-
this.pageBuilderStateStore.
|
|
1619
|
-
this.pageBuilderStateStore.
|
|
1620
|
-
typeof this.pageBuilderStateStore.
|
|
1621
|
-
this.pageBuilderStateStore.
|
|
1788
|
+
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
1789
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
1790
|
+
typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
|
|
1791
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'update'
|
|
1622
1792
|
) {
|
|
1623
|
-
if (
|
|
1624
|
-
this
|
|
1793
|
+
if (passedData) {
|
|
1794
|
+
this.#setComponentsFromData(passedData)
|
|
1795
|
+
return
|
|
1625
1796
|
}
|
|
1626
1797
|
}
|
|
1627
1798
|
|
|
1799
|
+
// Form type Create
|
|
1800
|
+
const localStorageData = this.loadStoredComponentsFromStorage()
|
|
1801
|
+
|
|
1628
1802
|
if (
|
|
1629
|
-
this.pageBuilderStateStore.
|
|
1630
|
-
this.pageBuilderStateStore.
|
|
1631
|
-
typeof this.pageBuilderStateStore.
|
|
1632
|
-
this.pageBuilderStateStore.
|
|
1803
|
+
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
1804
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
1805
|
+
typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
|
|
1806
|
+
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'create'
|
|
1633
1807
|
) {
|
|
1634
|
-
if (
|
|
1635
|
-
this
|
|
1808
|
+
if (localStorageData) {
|
|
1809
|
+
this.#setComponentsFromData(localStorageData)
|
|
1810
|
+
return
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
// If no localStorage, but passedData exists (for demo), use it
|
|
1814
|
+
if (passedData) {
|
|
1815
|
+
this.#setComponentsFromData(passedData)
|
|
1816
|
+
return
|
|
1636
1817
|
}
|
|
1637
1818
|
}
|
|
1638
1819
|
}
|
|
@@ -1646,6 +1827,7 @@ class PageBuilderClass {
|
|
|
1646
1827
|
}
|
|
1647
1828
|
|
|
1648
1829
|
async initializeElementStyles(): Promise<void> {
|
|
1830
|
+
if (!this.pageBuilderStateStore.getPageBuilderConfig) return
|
|
1649
1831
|
await new Promise((resolve) => requestAnimationFrame(resolve))
|
|
1650
1832
|
|
|
1651
1833
|
// handle custom URL
|
|
@@ -1655,7 +1837,7 @@ class PageBuilderClass {
|
|
|
1655
1837
|
// handle BG opacity
|
|
1656
1838
|
this.handleBackgroundOpacity(undefined)
|
|
1657
1839
|
// displayed image
|
|
1658
|
-
this.
|
|
1840
|
+
this.setBasePrimaryImageFromSelectedElement()
|
|
1659
1841
|
// border style
|
|
1660
1842
|
this.handleBorderStyle(undefined)
|
|
1661
1843
|
// border width
|
|
@@ -1699,5 +1881,3 @@ class PageBuilderClass {
|
|
|
1699
1881
|
await this.#syncCurrentClasses()
|
|
1700
1882
|
}
|
|
1701
1883
|
}
|
|
1702
|
-
|
|
1703
|
-
export default PageBuilderClass
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// builderInstance.ts
|
|
2
|
+
import { PageBuilderService } from '../composables/PageBuilderService'
|
|
3
|
+
import { sharedPageBuilderStore } from '../stores/shared-store'
|
|
4
|
+
|
|
5
|
+
// Singleton instance
|
|
6
|
+
let instance: PageBuilderService | null = null
|
|
7
|
+
|
|
8
|
+
// Used to create and store the single instance
|
|
9
|
+
export function initPageBuilder(): PageBuilderService {
|
|
10
|
+
if (!instance) {
|
|
11
|
+
const pageBuilderStateStore = sharedPageBuilderStore
|
|
12
|
+
instance = new PageBuilderService(pageBuilderStateStore)
|
|
13
|
+
}
|
|
14
|
+
return instance
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Used to access the existing instance anywhere else
|
|
18
|
+
export function getPageBuilder(): PageBuilderService {
|
|
19
|
+
if (!instance) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
'PageBuilderService has not been created. Please call createPageBuilder() first in your App.vue or setup file.',
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
return instance
|
|
25
|
+
}
|
package/src/css/app.css
CHANGED
|
@@ -357,3 +357,18 @@
|
|
|
357
357
|
@apply pbx-aspect-square pbx-border pbx-border-gray-200 pbx-cursor-pointer pbx-min-h-[1.5rem] pbx-rounded-sm;
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
|
+
|
|
361
|
+
/* p {
|
|
362
|
+
@apply pbx-font-normal lg:pbx-text-base pbx-text-base;
|
|
363
|
+
} */
|
|
364
|
+
|
|
365
|
+
a {
|
|
366
|
+
@apply pbx-text-myPrimaryLinkColor;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
h2 {
|
|
370
|
+
@apply pbx-text-3xl pbx-mt-4 pbx-mb-3 pbx-font-medium;
|
|
371
|
+
}
|
|
372
|
+
h3 {
|
|
373
|
+
@apply pbx-text-2xl pbx-mt-4 pbx-mb-3 pbx-font-medium;
|
|
374
|
+
}
|