@myissue/vue-website-page-builder 3.4.20 → 3.4.21
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 +22 -24
- package/dist/vue-website-page-builder.js +1 -1
- package/dist/vue-website-page-builder.umd.cjs +1 -1
- package/package.json +2 -3
- package/src/App.vue +0 -28
- package/src/Components/Homepage/Footer.vue +0 -32
- package/src/Components/Homepage/Navbar.vue +0 -25
- package/src/Components/Layouts/FullWidthElement.vue +0 -34
- package/src/Components/Loaders/GlobalLoader.vue +0 -18
- package/src/Components/Modals/BuilderComponents.vue +0 -53
- package/src/Components/Modals/DynamicModalBuilder.vue +0 -209
- package/src/Components/Modals/MediaLibraryModal.vue +0 -61
- package/src/Components/Modals/ModalBuilder.vue +0 -117
- package/src/Components/PageBuilder/EditorMenu/Editables/BackgroundColorEditor.vue +0 -121
- package/src/Components/PageBuilder/EditorMenu/Editables/BorderRadius.vue +0 -188
- package/src/Components/PageBuilder/EditorMenu/Editables/Borders.vue +0 -176
- package/src/Components/PageBuilder/EditorMenu/Editables/ClassEditor.vue +0 -112
- package/src/Components/PageBuilder/EditorMenu/Editables/ComponentTopMenu.vue +0 -108
- package/src/Components/PageBuilder/EditorMenu/Editables/EditGetElement.vue +0 -541
- package/src/Components/PageBuilder/EditorMenu/Editables/HTMLEditor.vue +0 -203
- package/src/Components/PageBuilder/EditorMenu/Editables/ImageEditor.vue +0 -96
- package/src/Components/PageBuilder/EditorMenu/Editables/LinkEditor.vue +0 -315
- package/src/Components/PageBuilder/EditorMenu/Editables/ManageBackgroundOpacity.vue +0 -107
- package/src/Components/PageBuilder/EditorMenu/Editables/ManageOpacity.vue +0 -106
- package/src/Components/PageBuilder/EditorMenu/Editables/Margin.vue +0 -116
- package/src/Components/PageBuilder/EditorMenu/Editables/OpacityEditor.vue +0 -19
- package/src/Components/PageBuilder/EditorMenu/Editables/Padding.vue +0 -117
- package/src/Components/PageBuilder/EditorMenu/Editables/StyleEditor.vue +0 -123
- package/src/Components/PageBuilder/EditorMenu/Editables/TextColorEditor.vue +0 -123
- package/src/Components/PageBuilder/EditorMenu/Editables/Typography.vue +0 -237
- package/src/Components/PageBuilder/EditorMenu/EditorAccordion.vue +0 -46
- package/src/Components/PageBuilder/EditorMenu/RightSidebarEditor.vue +0 -368
- package/src/Components/PageBuilder/Settings/AdvancedPageBuilderSettings.vue +0 -476
- package/src/Components/PageBuilder/Settings/PageBuilderSettings.vue +0 -581
- package/src/Components/PageBuilder/ToolbarOption/ToolbarOption.vue +0 -248
- package/src/Components/PageBuilder/UndoRedo/UndoRedo.vue +0 -90
- package/src/Components/TipTap/TipTap.vue +0 -25
- package/src/Components/TipTap/TipTapInput.vue +0 -341
- package/src/PageBuilder/PageBuilder.vue +0 -1025
- package/src/PageBuilder/Preview.vue +0 -64
- package/src/composables/builderInstance.ts +0 -47
- package/src/composables/delay.ts +0 -5
- package/src/composables/extractCleanHTMLFromPageBuilder.ts +0 -59
- package/src/composables/preloadImage.ts +0 -8
- package/src/composables/useDebounce.ts +0 -8
- package/src/composables/usePageBuilderModal.ts +0 -25
- package/src/composables/useTranslations.ts +0 -28
- package/src/css/dev-global.css +0 -24
- package/src/css/style.css +0 -600
- package/src/helpers/isEmptyObject.ts +0 -5
- package/src/index.ts +0 -28
- package/src/locales/ar.json +0 -170
- package/src/locales/de.json +0 -171
- package/src/locales/en.json +0 -171
- package/src/locales/es.json +0 -171
- package/src/locales/fr.json +0 -171
- package/src/locales/hi.json +0 -172
- package/src/locales/ja.json +0 -171
- package/src/locales/pt.json +0 -171
- package/src/locales/ru.json +0 -171
- package/src/locales/zh-Hans.json +0 -171
- package/src/main.ts +0 -14
- package/src/plugin.ts +0 -16
- package/src/services/LocalStorageManager.ts +0 -25
- package/src/services/PageBuilderService.ts +0 -3191
- package/src/stores/page-builder-state.ts +0 -498
- package/src/stores/shared-store.ts +0 -13
- package/src/tailwind-safelist.html +0 -3
- package/src/tests/DefaultComponents/DefaultBuilderComponents.vue +0 -284
- package/src/tests/DefaultComponents/DefaultMediaLibraryComponent.vue +0 -10
- package/src/tests/PageBuilderTest.vue +0 -82
- package/src/tests/TestComponents/DemoBuilderComponentsTest.vue +0 -5
- package/src/tests/TestComponents/DemoMediaLibraryComponentTest.vue +0 -8
- package/src/tests/TestComponents/DemoUnsplash.vue +0 -389
- package/src/tests/componentsArray.test.json +0 -62
- package/src/tests/pageBuilderService.test.ts +0 -165
- package/src/types/global.d.ts +0 -11
- package/src/types/index.ts +0 -306
- package/src/utils/builder/html-doc-declaration-with-components.ts +0 -7
- package/src/utils/builder/tailwaind-colors.ts +0 -503
- package/src/utils/builder/tailwind-border-radius.ts +0 -67
- package/src/utils/builder/tailwind-border-style-width-color.ts +0 -272
- package/src/utils/builder/tailwind-font-sizes.ts +0 -75
- package/src/utils/builder/tailwind-font-styles.ts +0 -56
- package/src/utils/builder/tailwind-opacities.ts +0 -45
- package/src/utils/builder/tailwind-padding-margin.ts +0 -159
- package/src/utils/html-elements/component.ts +0 -485
- package/src/utils/html-elements/componentHelpers.ts +0 -98
- package/src/utils/html-elements/themes.ts +0 -85
|
@@ -1,3191 +0,0 @@
|
|
|
1
|
-
import { LocalStorageManager } from './LocalStorageManager'
|
|
2
|
-
import type {
|
|
3
|
-
BuilderResourceData,
|
|
4
|
-
ComponentObject,
|
|
5
|
-
ImageObject,
|
|
6
|
-
PageBuilderConfig,
|
|
7
|
-
PageSettings,
|
|
8
|
-
StartBuilderResult,
|
|
9
|
-
} from '../types'
|
|
10
|
-
import type { usePageBuilderStateStore } from '../stores/page-builder-state'
|
|
11
|
-
|
|
12
|
-
import tailwindFontSizes from '../utils/builder/tailwind-font-sizes'
|
|
13
|
-
import tailwindColors from '../utils/builder/tailwaind-colors'
|
|
14
|
-
import tailwindOpacities from '../utils/builder/tailwind-opacities'
|
|
15
|
-
import tailwindFontStyles from '../utils/builder/tailwind-font-styles'
|
|
16
|
-
import tailwindPaddingAndMargin from '../utils/builder/tailwind-padding-margin'
|
|
17
|
-
import tailwindBorderRadius from '../utils/builder/tailwind-border-radius'
|
|
18
|
-
import tailwindBorderStyleWidthPlusColor from '../utils/builder/tailwind-border-style-width-color'
|
|
19
|
-
import { computed, ref, nextTick } from 'vue'
|
|
20
|
-
import type { ComputedRef } from 'vue'
|
|
21
|
-
import { v4 as uuidv4 } from 'uuid'
|
|
22
|
-
import { delay } from '../composables/delay'
|
|
23
|
-
import { isEmptyObject } from '../helpers/isEmptyObject'
|
|
24
|
-
import { extractCleanHTMLFromPageBuilder } from '../composables/extractCleanHTMLFromPageBuilder'
|
|
25
|
-
|
|
26
|
-
// Define available languages as a type and an array for easy iteration and type safety
|
|
27
|
-
export type AvailableLanguage =
|
|
28
|
-
| 'en'
|
|
29
|
-
| 'zh-Hans'
|
|
30
|
-
| 'fr'
|
|
31
|
-
| 'ja'
|
|
32
|
-
| 'ru'
|
|
33
|
-
| 'es'
|
|
34
|
-
| 'pt'
|
|
35
|
-
| 'de'
|
|
36
|
-
| 'ar'
|
|
37
|
-
| 'hi'
|
|
38
|
-
|
|
39
|
-
export const AVAILABLE_LANGUAGES: AvailableLanguage[] = [
|
|
40
|
-
'en',
|
|
41
|
-
'zh-Hans',
|
|
42
|
-
'fr',
|
|
43
|
-
'ja',
|
|
44
|
-
'ru',
|
|
45
|
-
'es',
|
|
46
|
-
'pt',
|
|
47
|
-
'de',
|
|
48
|
-
'ar',
|
|
49
|
-
'hi',
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
export class PageBuilderService {
|
|
53
|
-
// Class properties with types
|
|
54
|
-
private fontSizeRegex =
|
|
55
|
-
/^(sm:|md:|lg:|xl:|2xl:)?pbx-text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)$/
|
|
56
|
-
|
|
57
|
-
protected pageBuilderStateStore: ReturnType<typeof usePageBuilderStateStore>
|
|
58
|
-
private getLocalStorageItemName: ComputedRef<string | null>
|
|
59
|
-
private getApplyImageToSelection: ComputedRef<ImageObject>
|
|
60
|
-
private getHyberlinkEnable: ComputedRef<boolean>
|
|
61
|
-
private getComponents: ComputedRef<ComponentObject[] | null>
|
|
62
|
-
private getComponent: ComputedRef<ComponentObject | null>
|
|
63
|
-
private getElement: ComputedRef<HTMLElement | null>
|
|
64
|
-
private getComponentArrayAddMethod: ComputedRef<string | null>
|
|
65
|
-
private NoneListernesTags: string[]
|
|
66
|
-
private hasStartedEditing: boolean = false
|
|
67
|
-
// Hold data from Database or Backend for updated post
|
|
68
|
-
private originalComponents: BuilderResourceData | undefined = undefined
|
|
69
|
-
// Holds data to be mounted when pagebuilder is not yet present in the DOM
|
|
70
|
-
private savedMountComponents: BuilderResourceData | null = null
|
|
71
|
-
private pendingMountComponents: BuilderResourceData | null = null
|
|
72
|
-
private isPageBuilderMissingOnStart: boolean = false
|
|
73
|
-
|
|
74
|
-
// Add a class-level WeakMap to track elements and their listeners
|
|
75
|
-
// Use class-level WeakMap from being a local variable inside addListenersToEditableElements to a private class-level property.
|
|
76
|
-
// This ensures that the map persists across multiple calls to the method and retains knowledge of
|
|
77
|
-
// which elements already have listeners.
|
|
78
|
-
// This prevents multiple event listeners being attached to the same HTML elements
|
|
79
|
-
private elementsWithListeners = new WeakMap<
|
|
80
|
-
Element,
|
|
81
|
-
{ click: EventListener; mouseover: EventListener; mouseleave: EventListener }
|
|
82
|
-
>()
|
|
83
|
-
|
|
84
|
-
constructor(pageBuilderStateStore: ReturnType<typeof usePageBuilderStateStore>) {
|
|
85
|
-
this.hasStartedEditing = false
|
|
86
|
-
this.pageBuilderStateStore = pageBuilderStateStore
|
|
87
|
-
this.getApplyImageToSelection = computed(
|
|
88
|
-
() => this.pageBuilderStateStore.getApplyImageToSelection,
|
|
89
|
-
)
|
|
90
|
-
this.getLocalStorageItemName = computed(
|
|
91
|
-
() => this.pageBuilderStateStore.getLocalStorageItemName,
|
|
92
|
-
)
|
|
93
|
-
this.getHyberlinkEnable = computed(() => this.pageBuilderStateStore.getHyberlinkEnable)
|
|
94
|
-
this.getComponents = computed(() => this.pageBuilderStateStore.getComponents)
|
|
95
|
-
|
|
96
|
-
this.getComponent = computed(() => this.pageBuilderStateStore.getComponent)
|
|
97
|
-
|
|
98
|
-
this.getElement = computed(() => this.pageBuilderStateStore.getElement)
|
|
99
|
-
|
|
100
|
-
this.getComponentArrayAddMethod = computed(
|
|
101
|
-
() => this.pageBuilderStateStore.getComponentArrayAddMethod,
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
this.NoneListernesTags = [
|
|
105
|
-
'P',
|
|
106
|
-
'H1',
|
|
107
|
-
'H2',
|
|
108
|
-
'H3',
|
|
109
|
-
'H4',
|
|
110
|
-
'H5',
|
|
111
|
-
'H6',
|
|
112
|
-
'IFRAME',
|
|
113
|
-
'UL',
|
|
114
|
-
'OL',
|
|
115
|
-
'LI',
|
|
116
|
-
'EM',
|
|
117
|
-
'STRONG',
|
|
118
|
-
'B',
|
|
119
|
-
'A',
|
|
120
|
-
'SPAN',
|
|
121
|
-
'BLOCKQUOTE',
|
|
122
|
-
'BR',
|
|
123
|
-
'PRE',
|
|
124
|
-
'CODE',
|
|
125
|
-
'MARK',
|
|
126
|
-
'DEL',
|
|
127
|
-
'INS',
|
|
128
|
-
'U',
|
|
129
|
-
'FIGURE',
|
|
130
|
-
'FIGCAPTION',
|
|
131
|
-
]
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Returns an array of available languages.
|
|
136
|
-
* @returns {AvailableLanguage[]} An array of available language codes.
|
|
137
|
-
*/
|
|
138
|
-
public availableLanguage(): AvailableLanguage[] {
|
|
139
|
-
return AVAILABLE_LANGUAGES
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Sets the current language in the page builder state.
|
|
144
|
-
* @param {string} lang - The language code to set.
|
|
145
|
-
*/
|
|
146
|
-
public changeLanguage(lang: string) {
|
|
147
|
-
this.pageBuilderStateStore.setCurrentLanguage(lang)
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Deselects any selected or hovered elements in the builder UI.
|
|
151
|
-
* @returns {Promise<void>}
|
|
152
|
-
*/
|
|
153
|
-
async clearHtmlSelection(): Promise<void> {
|
|
154
|
-
this.pageBuilderStateStore.setComponent(null)
|
|
155
|
-
this.pageBuilderStateStore.setElement(null)
|
|
156
|
-
await this.removeHoveredAndSelected()
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Ensures that the `updateOrCreate` configuration is valid and sets default values if necessary.
|
|
161
|
-
* @param {PageBuilderConfig} config - The page builder configuration.
|
|
162
|
-
* @private
|
|
163
|
-
*/
|
|
164
|
-
private ensureUpdateOrCreateConfig(config: PageBuilderConfig): void {
|
|
165
|
-
// Case A: updateOrCreate is missing or an empty object
|
|
166
|
-
if (!config.updateOrCreate || (config.updateOrCreate && isEmptyObject(config.updateOrCreate))) {
|
|
167
|
-
const updatedConfig = {
|
|
168
|
-
...config,
|
|
169
|
-
updateOrCreate: {
|
|
170
|
-
formType: 'create',
|
|
171
|
-
formName: 'post',
|
|
172
|
-
},
|
|
173
|
-
} as const
|
|
174
|
-
|
|
175
|
-
this.pageBuilderStateStore.setPageBuilderConfig(updatedConfig)
|
|
176
|
-
return
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Case B: formType is valid ('create' or 'update'), but formName is missing or an empty string
|
|
180
|
-
if (
|
|
181
|
-
(config.updateOrCreate &&
|
|
182
|
-
typeof config.updateOrCreate.formType === 'string' &&
|
|
183
|
-
(config.updateOrCreate.formType === 'create' ||
|
|
184
|
-
config.updateOrCreate.formType === 'update') &&
|
|
185
|
-
typeof config.updateOrCreate.formName !== 'string') ||
|
|
186
|
-
(typeof config.updateOrCreate.formName === 'string' &&
|
|
187
|
-
config.updateOrCreate.formName.length === 0)
|
|
188
|
-
) {
|
|
189
|
-
const updatedConfig = {
|
|
190
|
-
...config,
|
|
191
|
-
updateOrCreate: {
|
|
192
|
-
formType: config.updateOrCreate.formType,
|
|
193
|
-
formName: 'post',
|
|
194
|
-
},
|
|
195
|
-
} as const
|
|
196
|
-
this.pageBuilderStateStore.setPageBuilderConfig(updatedConfig)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Case C: formType is missing or not a valid string like ('create' or 'update') but formName is valid string
|
|
200
|
-
if (
|
|
201
|
-
(config.updateOrCreate && typeof config.updateOrCreate.formType !== 'string') ||
|
|
202
|
-
(typeof config.updateOrCreate.formType === 'string' &&
|
|
203
|
-
config.updateOrCreate.formType !== 'create' &&
|
|
204
|
-
config.updateOrCreate.formType !== 'update' &&
|
|
205
|
-
typeof config.updateOrCreate.formName === 'string' &&
|
|
206
|
-
config.updateOrCreate.formName.length !== 0)
|
|
207
|
-
) {
|
|
208
|
-
const updatedConfig = {
|
|
209
|
-
...config,
|
|
210
|
-
updateOrCreate: {
|
|
211
|
-
formType: 'create',
|
|
212
|
-
formName: config.updateOrCreate.formName,
|
|
213
|
-
},
|
|
214
|
-
} as const
|
|
215
|
-
|
|
216
|
-
this.pageBuilderStateStore.setPageBuilderConfig(updatedConfig)
|
|
217
|
-
return
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Case D: formType exists but is not 'create' or 'update', and formName is missing or invalid
|
|
221
|
-
if (
|
|
222
|
-
config.updateOrCreate &&
|
|
223
|
-
typeof config.updateOrCreate.formType === 'string' &&
|
|
224
|
-
config.updateOrCreate.formType !== 'create' &&
|
|
225
|
-
config.updateOrCreate.formType !== 'update' &&
|
|
226
|
-
typeof config.formName !== 'string'
|
|
227
|
-
) {
|
|
228
|
-
const updatedConfig = {
|
|
229
|
-
...config,
|
|
230
|
-
updateOrCreate: {
|
|
231
|
-
formType: 'create',
|
|
232
|
-
formName: 'post',
|
|
233
|
-
},
|
|
234
|
-
} as const
|
|
235
|
-
|
|
236
|
-
this.pageBuilderStateStore.setPageBuilderConfig(updatedConfig)
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Validates the user-provided components array.
|
|
242
|
-
* @param {unknown} components - The components data to validate.
|
|
243
|
-
* @returns {{error: true, warning: string, status: string} | {error: true, reason: string} | undefined} An error object if validation fails, otherwise undefined.
|
|
244
|
-
* @private
|
|
245
|
-
*/
|
|
246
|
-
private validateUserProvidedComponents(components: unknown) {
|
|
247
|
-
const formType =
|
|
248
|
-
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
249
|
-
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
250
|
-
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType
|
|
251
|
-
|
|
252
|
-
if (
|
|
253
|
-
Array.isArray(components) &&
|
|
254
|
-
components.length >= 1 &&
|
|
255
|
-
formType === 'create' &&
|
|
256
|
-
components
|
|
257
|
-
) {
|
|
258
|
-
return {
|
|
259
|
-
error: true as const,
|
|
260
|
-
warning:
|
|
261
|
-
'You cannot set formType to create in your configuration while also passing a components data array to the Page Builder. Please set formType to update.',
|
|
262
|
-
status: 'validation_failed',
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Must be an array
|
|
267
|
-
if (!Array.isArray(components)) {
|
|
268
|
-
return {
|
|
269
|
-
error: true as const,
|
|
270
|
-
reason: 'Components data must be an array.',
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Check that the first item looks like a component
|
|
275
|
-
const first = components[0]
|
|
276
|
-
|
|
277
|
-
if (first && 'html_code' in first && typeof first.html_code !== 'string') {
|
|
278
|
-
return {
|
|
279
|
-
error: true as const,
|
|
280
|
-
reason: "The 'html_code' property in the first object must be a string.",
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Check that the first item has an 'html_code' key
|
|
285
|
-
if (Array.isArray(components) && components.length >= 1) {
|
|
286
|
-
if (!first || !('html_code' in first)) {
|
|
287
|
-
return {
|
|
288
|
-
error: true as const,
|
|
289
|
-
reason: "The first object in the array must include an 'html_code' key.",
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// No errors found
|
|
295
|
-
return
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Ensures that the language configuration is valid and sets default values if necessary.
|
|
300
|
-
* @param {PageBuilderConfig} config - The page builder configuration.
|
|
301
|
-
* @private
|
|
302
|
-
*/
|
|
303
|
-
private ensureLanguage(config: PageBuilderConfig): void {
|
|
304
|
-
// Set default language config if missing, empty, or language missing/empty
|
|
305
|
-
const defaultLang = 'en'
|
|
306
|
-
const defaultEnable = ['en', 'zh-Hans', 'fr', 'ja', 'ru', 'es', 'pt', 'de', 'ar', 'hi'] as const
|
|
307
|
-
|
|
308
|
-
let needsDefault = false
|
|
309
|
-
const userSettings = config.userSettings
|
|
310
|
-
const language = userSettings && userSettings.language
|
|
311
|
-
|
|
312
|
-
if (!userSettings || isEmptyObject(userSettings)) {
|
|
313
|
-
needsDefault = true
|
|
314
|
-
} else if (!language || isEmptyObject(language)) {
|
|
315
|
-
needsDefault = true
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (needsDefault) {
|
|
319
|
-
const updatedLanguage = {
|
|
320
|
-
...config,
|
|
321
|
-
userSettings: {
|
|
322
|
-
...userSettings,
|
|
323
|
-
language: {
|
|
324
|
-
default: defaultLang,
|
|
325
|
-
enable: defaultEnable as typeof defaultEnable,
|
|
326
|
-
},
|
|
327
|
-
},
|
|
328
|
-
} as const
|
|
329
|
-
this.pageBuilderStateStore.setPageBuilderConfig(updatedLanguage)
|
|
330
|
-
return
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Ensure default is in enable array
|
|
334
|
-
if (language && Array.isArray(language.enable) && language.default) {
|
|
335
|
-
if (!language.enable.includes(language.default)) {
|
|
336
|
-
const updatedEnable = [...language.enable, language.default]
|
|
337
|
-
const updatedLanguage = {
|
|
338
|
-
...config,
|
|
339
|
-
userSettings: {
|
|
340
|
-
...userSettings,
|
|
341
|
-
language: {
|
|
342
|
-
...language,
|
|
343
|
-
enable: updatedEnable,
|
|
344
|
-
},
|
|
345
|
-
},
|
|
346
|
-
} as const
|
|
347
|
-
this.pageBuilderStateStore.setPageBuilderConfig(updatedLanguage)
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Validates the entire page builder configuration.
|
|
354
|
-
* @param {PageBuilderConfig} config - The page builder configuration.
|
|
355
|
-
* @private
|
|
356
|
-
*/
|
|
357
|
-
private validateConfig(config: PageBuilderConfig): void {
|
|
358
|
-
const defaultConfigValues = {
|
|
359
|
-
updateOrCreate: {
|
|
360
|
-
formType: 'create',
|
|
361
|
-
formName: 'post',
|
|
362
|
-
},
|
|
363
|
-
} as const
|
|
364
|
-
|
|
365
|
-
// Set config for page builder if not set by user
|
|
366
|
-
if (!config || (config && Object.keys(config).length === 0 && config.constructor === Object)) {
|
|
367
|
-
this.pageBuilderStateStore.setPageBuilderConfig(defaultConfigValues)
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (config && Object.keys(config).length !== 0 && config.constructor === Object) {
|
|
371
|
-
this.ensureUpdateOrCreateConfig(config)
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
this.ensureLanguage(config)
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Saves user settings to local storage.
|
|
379
|
-
* @param {string} newLang - The new language to save.
|
|
380
|
-
*/
|
|
381
|
-
public saveUserSettingsStorage(newLang: string) {
|
|
382
|
-
localStorage.setItem(
|
|
383
|
-
'userSettingsPageBuilder',
|
|
384
|
-
JSON.stringify({ userSettings: { lang: newLang } }),
|
|
385
|
-
)
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Initializes the Page Builder.
|
|
390
|
-
* @param {PageBuilderConfig} config - The configuration object for the Page Builder.
|
|
391
|
-
* @param {BuilderResourceData} [passedComponentsArray] - Optional array of components to load.
|
|
392
|
-
* @returns {Promise<StartBuilderResult>} A result object indicating success or failure.
|
|
393
|
-
*/
|
|
394
|
-
async startBuilder(
|
|
395
|
-
config: PageBuilderConfig,
|
|
396
|
-
passedComponentsArray?: BuilderResourceData,
|
|
397
|
-
): Promise<StartBuilderResult> {
|
|
398
|
-
// Reactive flag signals to the UI that the builder has been successfully initialized
|
|
399
|
-
// Prevents builder actions to prevent errors caused by missing DOM .
|
|
400
|
-
this.pageBuilderStateStore.setBuilderStarted(true)
|
|
401
|
-
const pagebuilder = document.querySelector('#pagebuilder')
|
|
402
|
-
|
|
403
|
-
let validation
|
|
404
|
-
try {
|
|
405
|
-
this.originalComponents = passedComponentsArray
|
|
406
|
-
this.pageBuilderStateStore.setPageBuilderConfig(config)
|
|
407
|
-
// Validate and normalize the config (ensure required fields are present)
|
|
408
|
-
this.validateConfig(config)
|
|
409
|
-
|
|
410
|
-
validation = this.validateUserProvidedComponents(passedComponentsArray)
|
|
411
|
-
|
|
412
|
-
// Update the localStorage key name based on the config/resource
|
|
413
|
-
this.updateLocalStorageItemName()
|
|
414
|
-
this.initializeHistory()
|
|
415
|
-
|
|
416
|
-
if (passedComponentsArray) {
|
|
417
|
-
this.savedMountComponents = passedComponentsArray
|
|
418
|
-
}
|
|
419
|
-
// Page Builder is not Present in the DOM but Components have been passed to the Builder
|
|
420
|
-
if (!pagebuilder) {
|
|
421
|
-
this.isPageBuilderMissingOnStart = true
|
|
422
|
-
}
|
|
423
|
-
if (passedComponentsArray && !pagebuilder) {
|
|
424
|
-
this.pendingMountComponents = passedComponentsArray
|
|
425
|
-
}
|
|
426
|
-
// Page Builder is Present in the DOM & Components have been passed to the Builder
|
|
427
|
-
if (pagebuilder) {
|
|
428
|
-
this.completeBuilderInitialization(passedComponentsArray)
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// result to end user
|
|
432
|
-
const result: StartBuilderResult = {
|
|
433
|
-
message: 'Page builder started successfully.',
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (validation) {
|
|
437
|
-
result.validation = validation
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// PassedComponentsArray
|
|
441
|
-
if (Array.isArray(passedComponentsArray) && passedComponentsArray.length >= 0) {
|
|
442
|
-
result.passedComponentsArray = passedComponentsArray
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// Return messages, validation info if present etc.
|
|
446
|
-
return result
|
|
447
|
-
} catch (err) {
|
|
448
|
-
console.error('Not able to start the Page Builder', err)
|
|
449
|
-
return {
|
|
450
|
-
error: true as const,
|
|
451
|
-
reason: 'Failed to start the Page Builder due to an unexpected error.',
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* Completes the builder initialization process once the DOM is ready.
|
|
458
|
-
* @param {BuilderResourceData} [passedComponentsArray] - Optional array of components to load.
|
|
459
|
-
* @returns {Promise<void>}
|
|
460
|
-
*/
|
|
461
|
-
async completeBuilderInitialization(passedComponentsArray?: BuilderResourceData): Promise<void> {
|
|
462
|
-
this.pageBuilderStateStore.setIsLoadingGlobal(true)
|
|
463
|
-
await delay(400)
|
|
464
|
-
|
|
465
|
-
// Always clear DOM and store before mounting new resource
|
|
466
|
-
this.deleteAllComponentsFromDOM()
|
|
467
|
-
|
|
468
|
-
const config = this.pageBuilderStateStore.getPageBuilderConfig
|
|
469
|
-
const formType = config && config.updateOrCreate && config.updateOrCreate.formType
|
|
470
|
-
|
|
471
|
-
const localStorageData = this.getSavedPageHtml()
|
|
472
|
-
|
|
473
|
-
// Deselect any selected or hovered elements in the builder UI
|
|
474
|
-
await this.clearHtmlSelection()
|
|
475
|
-
|
|
476
|
-
if (formType === 'update' || formType === 'create') {
|
|
477
|
-
// Page Builder is initially present in the DOM
|
|
478
|
-
if (!this.pendingMountComponents) {
|
|
479
|
-
if (!passedComponentsArray && this.isPageBuilderMissingOnStart && localStorageData) {
|
|
480
|
-
await this.completeMountProcess(localStorageData)
|
|
481
|
-
return
|
|
482
|
-
}
|
|
483
|
-
if (passedComponentsArray && !localStorageData) {
|
|
484
|
-
const htmlString = this.renderComponentsToHtml(passedComponentsArray)
|
|
485
|
-
await this.completeMountProcess(htmlString, true)
|
|
486
|
-
this.saveDomComponentsToLocalStorage()
|
|
487
|
-
return
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
if (passedComponentsArray && localStorageData) {
|
|
491
|
-
const htmlString = this.renderComponentsToHtml(passedComponentsArray)
|
|
492
|
-
await this.completeMountProcess(htmlString, true)
|
|
493
|
-
await delay(500)
|
|
494
|
-
this.pageBuilderStateStore.setHasLocalDraftForUpdate(true)
|
|
495
|
-
return
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
if (!passedComponentsArray && localStorageData && !this.savedMountComponents) {
|
|
499
|
-
await this.completeMountProcess(localStorageData)
|
|
500
|
-
return
|
|
501
|
-
}
|
|
502
|
-
if (!passedComponentsArray && this.savedMountComponents && localStorageData) {
|
|
503
|
-
const htmlString = this.renderComponentsToHtml(this.savedMountComponents)
|
|
504
|
-
await this.completeMountProcess(htmlString)
|
|
505
|
-
return
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
if (!passedComponentsArray && !localStorageData && this.isPageBuilderMissingOnStart) {
|
|
509
|
-
const htmlString = this.renderComponentsToHtml([])
|
|
510
|
-
await this.completeMountProcess(htmlString)
|
|
511
|
-
|
|
512
|
-
return
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
if (!this.isPageBuilderMissingOnStart && !localStorageData && !passedComponentsArray) {
|
|
516
|
-
const htmlString = this.renderComponentsToHtml([])
|
|
517
|
-
await this.completeMountProcess(htmlString)
|
|
518
|
-
return
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Page Builder is not initially present in the DOM
|
|
523
|
-
if (this.pendingMountComponents) {
|
|
524
|
-
if (localStorageData && this.isPageBuilderMissingOnStart) {
|
|
525
|
-
const htmlString = this.renderComponentsToHtml(this.pendingMountComponents)
|
|
526
|
-
await this.completeMountProcess(htmlString, true)
|
|
527
|
-
await delay(500)
|
|
528
|
-
this.pageBuilderStateStore.setHasLocalDraftForUpdate(true)
|
|
529
|
-
this.pendingMountComponents = null
|
|
530
|
-
return
|
|
531
|
-
}
|
|
532
|
-
if (!localStorageData && passedComponentsArray && this.isPageBuilderMissingOnStart) {
|
|
533
|
-
const htmlString = this.renderComponentsToHtml(this.pendingMountComponents)
|
|
534
|
-
await this.completeMountProcess(htmlString, true)
|
|
535
|
-
this.saveDomComponentsToLocalStorage()
|
|
536
|
-
return
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
if (!passedComponentsArray && !localStorageData && this.isPageBuilderMissingOnStart) {
|
|
540
|
-
const htmlString = this.renderComponentsToHtml(this.pendingMountComponents)
|
|
541
|
-
await this.completeMountProcess(htmlString, true)
|
|
542
|
-
this.saveDomComponentsToLocalStorage()
|
|
543
|
-
return
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/**
|
|
550
|
-
* Converts an array of ComponentObject into a single HTML string.
|
|
551
|
-
*
|
|
552
|
-
* @returns {string} A single HTML string containing all components.
|
|
553
|
-
*/
|
|
554
|
-
private renderComponentsToHtml(componentsArray: BuilderResourceData): string {
|
|
555
|
-
// If the componentsArray is empty or invalid, return a default HTML structure
|
|
556
|
-
if (!componentsArray || (Array.isArray(componentsArray) && componentsArray.length === 0)) {
|
|
557
|
-
return `<div id="pagebuilder" class="pbx-text-black pbx-font-sans"></div>`
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
const sectionsHtml = componentsArray
|
|
561
|
-
.map((component) => {
|
|
562
|
-
return component.html_code // Fallback in case section is not found
|
|
563
|
-
})
|
|
564
|
-
.join('\n')
|
|
565
|
-
|
|
566
|
-
// Return the combined HTML string
|
|
567
|
-
return sectionsHtml
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
/**
|
|
571
|
-
* Completes the mounting process by loading components into the DOM and setting up listeners.
|
|
572
|
-
* @param {string} html - The HTML string of components to mount.
|
|
573
|
-
* @param {boolean} [useConfigPageSettings] - Whether to use page settings from the passed data.
|
|
574
|
-
* @private
|
|
575
|
-
*/
|
|
576
|
-
private async completeMountProcess(html: string, useConfigPageSettings?: boolean) {
|
|
577
|
-
await this.mountComponentsToDOM(html, useConfigPageSettings)
|
|
578
|
-
|
|
579
|
-
// Clean up any old localStorage items related to previous builder sessions
|
|
580
|
-
this.deleteOldPageBuilderLocalStorage()
|
|
581
|
-
this.pageBuilderStateStore.setIsRestoring(false)
|
|
582
|
-
this.pageBuilderStateStore.setIsLoadingGlobal(false)
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
/**
|
|
586
|
-
* Applies CSS class changes to the currently selected element.
|
|
587
|
-
* @param {string | undefined} cssUserSelection - The user's CSS class selection.
|
|
588
|
-
* @param {string[]} CSSArray - The array of possible CSS classes for this property.
|
|
589
|
-
* @param {string} mutationName - The name of the store mutation to call.
|
|
590
|
-
* @returns {string | undefined} The previously applied CSS class.
|
|
591
|
-
* @private
|
|
592
|
-
*/
|
|
593
|
-
private applyElementClassChanges(
|
|
594
|
-
cssUserSelection: string | undefined,
|
|
595
|
-
CSSArray: string[],
|
|
596
|
-
mutationName: string,
|
|
597
|
-
): string | undefined {
|
|
598
|
-
const currentHTMLElement = this.getElement.value
|
|
599
|
-
|
|
600
|
-
if (!currentHTMLElement) return
|
|
601
|
-
|
|
602
|
-
const currentCSS = CSSArray.find((CSS) => {
|
|
603
|
-
return currentHTMLElement.classList.contains(CSS)
|
|
604
|
-
})
|
|
605
|
-
|
|
606
|
-
// set to 'none' if undefined
|
|
607
|
-
let elementClass = currentCSS || 'none'
|
|
608
|
-
|
|
609
|
-
// If cssUserSelection is undefined, just set the current state and return
|
|
610
|
-
if (cssUserSelection === undefined) {
|
|
611
|
-
if (typeof mutationName === 'string' && mutationName.length > 2) {
|
|
612
|
-
// Use a type-safe approach to handle mutationName
|
|
613
|
-
if (
|
|
614
|
-
mutationName in this.pageBuilderStateStore &&
|
|
615
|
-
typeof this.pageBuilderStateStore[
|
|
616
|
-
mutationName as keyof typeof this.pageBuilderStateStore
|
|
617
|
-
] === 'function'
|
|
618
|
-
) {
|
|
619
|
-
const mutationFunction = this.pageBuilderStateStore[
|
|
620
|
-
mutationName as keyof typeof this.pageBuilderStateStore
|
|
621
|
-
] as (arg: string) => void
|
|
622
|
-
mutationFunction(elementClass)
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
return currentCSS
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// cssUserSelection examples: bg-zinc-200, px-10, rounded-full etc.
|
|
629
|
-
if (typeof cssUserSelection === 'string' && cssUserSelection !== 'none') {
|
|
630
|
-
if (elementClass && currentHTMLElement.classList.contains(elementClass)) {
|
|
631
|
-
currentHTMLElement.classList.remove(elementClass)
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
currentHTMLElement.classList.add(cssUserSelection)
|
|
635
|
-
elementClass = cssUserSelection
|
|
636
|
-
} else if (
|
|
637
|
-
typeof cssUserSelection === 'string' &&
|
|
638
|
-
cssUserSelection === 'none' &&
|
|
639
|
-
elementClass
|
|
640
|
-
) {
|
|
641
|
-
currentHTMLElement.classList.remove(elementClass)
|
|
642
|
-
elementClass = cssUserSelection
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// Only call store mutations after all DOM manipulation is complete
|
|
646
|
-
if (typeof mutationName === 'string' && mutationName.length > 2) {
|
|
647
|
-
// Use a type-safe approach to handle mutationName
|
|
648
|
-
if (
|
|
649
|
-
mutationName in this.pageBuilderStateStore &&
|
|
650
|
-
typeof this.pageBuilderStateStore[
|
|
651
|
-
mutationName as keyof typeof this.pageBuilderStateStore
|
|
652
|
-
] === 'function'
|
|
653
|
-
) {
|
|
654
|
-
const mutationFunction = this.pageBuilderStateStore[
|
|
655
|
-
mutationName as keyof typeof this.pageBuilderStateStore
|
|
656
|
-
] as (arg: string) => void
|
|
657
|
-
mutationFunction(elementClass)
|
|
658
|
-
this.pageBuilderStateStore.setElement(currentHTMLElement)
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
return currentCSS
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
/**
|
|
666
|
-
* Removes all CSS classes from the main page builder container.
|
|
667
|
-
* @returns {Promise<void>}
|
|
668
|
-
*/
|
|
669
|
-
public async clearClassesFromPage() {
|
|
670
|
-
const pagebuilder = document.querySelector('#pagebuilder')
|
|
671
|
-
if (!pagebuilder) return
|
|
672
|
-
|
|
673
|
-
pagebuilder.removeAttribute('class')
|
|
674
|
-
|
|
675
|
-
this.initializeElementStyles()
|
|
676
|
-
await nextTick()
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* Removes all inline styles from the main page builder container.
|
|
680
|
-
* @returns {Promise<void>}
|
|
681
|
-
*/
|
|
682
|
-
public async clearInlineStylesFromPage() {
|
|
683
|
-
const pagebuilder = document.querySelector('#pagebuilder')
|
|
684
|
-
if (!pagebuilder) return
|
|
685
|
-
|
|
686
|
-
pagebuilder.removeAttribute('style')
|
|
687
|
-
|
|
688
|
-
this.initializeElementStyles()
|
|
689
|
-
await nextTick()
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
/**
|
|
693
|
-
* Selects the main page builder container for global styling.
|
|
694
|
-
* @returns {Promise<void>}
|
|
695
|
-
*/
|
|
696
|
-
public async globalPageStyles() {
|
|
697
|
-
const pagebuilder = document.querySelector('#pagebuilder')
|
|
698
|
-
if (!pagebuilder) return
|
|
699
|
-
|
|
700
|
-
// Deselect any selected or hovered elements in the builder UI
|
|
701
|
-
await this.clearHtmlSelection()
|
|
702
|
-
//
|
|
703
|
-
// Set the element in the store
|
|
704
|
-
this.pageBuilderStateStore.setElement(pagebuilder as HTMLElement)
|
|
705
|
-
|
|
706
|
-
// Add the data attribute for styling
|
|
707
|
-
pagebuilder.setAttribute('data-global-selected', 'true')
|
|
708
|
-
|
|
709
|
-
await nextTick()
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
/**
|
|
713
|
-
* Handles changes to the font weight of the selected element.
|
|
714
|
-
* @param {string} [userSelectedFontWeight] - The selected font weight class.
|
|
715
|
-
*/
|
|
716
|
-
public handleFontWeight(userSelectedFontWeight?: string): void {
|
|
717
|
-
this.applyElementClassChanges(
|
|
718
|
-
userSelectedFontWeight,
|
|
719
|
-
tailwindFontStyles.fontWeight,
|
|
720
|
-
'setFontWeight',
|
|
721
|
-
)
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
/**
|
|
725
|
-
* Handles changes to the base font size of the selected element.
|
|
726
|
-
* @param {string} [userSelectedFontSize] - The selected font size class.
|
|
727
|
-
*/
|
|
728
|
-
public handleFontSizeBase(userSelectedFontSize?: string): void {
|
|
729
|
-
this.applyElementClassChanges(userSelectedFontSize, tailwindFontSizes.fontBase, 'setFontBase')
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
/**
|
|
733
|
-
* Handles changes to the desktop font size of the selected element.
|
|
734
|
-
* @param {string} [userSelectedFontSize] - The selected font size class for desktop.
|
|
735
|
-
*/
|
|
736
|
-
public handleFontSizeDesktop(userSelectedFontSize?: string): void {
|
|
737
|
-
const currentHTMLElement = this.getElement.value
|
|
738
|
-
if (!currentHTMLElement) return
|
|
739
|
-
|
|
740
|
-
// Hardcoded mapping: selected => base
|
|
741
|
-
const fontSizeBaseMap: Record<string, string> = {
|
|
742
|
-
'pbx-text-9xl': 'pbx-text-6xl',
|
|
743
|
-
'pbx-text-8xl': 'pbx-text-5xl',
|
|
744
|
-
'pbx-text-7xl': 'pbx-text-4xl',
|
|
745
|
-
'pbx-text-6xl': 'pbx-text-3xl',
|
|
746
|
-
'pbx-text-5xl': 'pbx-text-3xl',
|
|
747
|
-
'pbx-text-4xl': 'pbx-text-2xl',
|
|
748
|
-
'pbx-text-3xl': 'pbx-text-1xl',
|
|
749
|
-
'pbx-text-2xl': 'pbx-text-lg',
|
|
750
|
-
'pbx-text-xl': 'pbx-text-base',
|
|
751
|
-
'pbx-text-lg': 'pbx-text-sm',
|
|
752
|
-
'pbx-text-base': 'pbx-text-xs',
|
|
753
|
-
'pbx-text-sm': 'pbx-text-xs',
|
|
754
|
-
'pbx-text-xs': 'pbx-text-xs',
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
if (userSelectedFontSize) {
|
|
758
|
-
// Remove all existing font size classes first
|
|
759
|
-
Array.from(currentHTMLElement.classList).forEach((cls) => {
|
|
760
|
-
if (this.fontSizeRegex.test(cls)) {
|
|
761
|
-
currentHTMLElement.classList.remove(cls)
|
|
762
|
-
}
|
|
763
|
-
})
|
|
764
|
-
|
|
765
|
-
// Extract the font size class (remove 'lg:' if present)
|
|
766
|
-
const fontSizeClass = userSelectedFontSize.replace(/^lg:/, '')
|
|
767
|
-
|
|
768
|
-
const baseClass = fontSizeBaseMap[fontSizeClass] || fontSizeClass
|
|
769
|
-
const lgClass = `lg:${fontSizeClass}`
|
|
770
|
-
|
|
771
|
-
if (baseClass !== fontSizeClass) {
|
|
772
|
-
currentHTMLElement.classList.add(baseClass, lgClass)
|
|
773
|
-
} else {
|
|
774
|
-
currentHTMLElement.classList.add(baseClass)
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
const currentCSS = tailwindFontSizes.fontDesktop.find((CSS) => {
|
|
779
|
-
return currentHTMLElement.classList.contains(CSS)
|
|
780
|
-
})
|
|
781
|
-
|
|
782
|
-
if (!userSelectedFontSize) {
|
|
783
|
-
this.pageBuilderStateStore.setFontDesktop('none')
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
if (currentCSS && !userSelectedFontSize) {
|
|
787
|
-
this.pageBuilderStateStore.setFontDesktop(currentCSS)
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
/**
|
|
792
|
-
* Applies helper CSS classes to elements, such as wrapping them or adding responsive text classes.
|
|
793
|
-
* @param {HTMLElement} element - The element to process.
|
|
794
|
-
* @private
|
|
795
|
-
*/
|
|
796
|
-
private applyHelperCSSToElements(element: HTMLElement): void {
|
|
797
|
-
this.wrapElementInDivIfExcluded(element)
|
|
798
|
-
|
|
799
|
-
// If this is a DIV and its only/main child is a heading, apply font size classes to the DIV
|
|
800
|
-
if (
|
|
801
|
-
element.tagName === 'DIV' &&
|
|
802
|
-
element.children.length === 1 &&
|
|
803
|
-
['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(element.children[0].tagName)
|
|
804
|
-
) {
|
|
805
|
-
const heading = element.children[0] as HTMLElement
|
|
806
|
-
|
|
807
|
-
// Only add default font size classes if none exist
|
|
808
|
-
const hasFontSizeClass = Array.from(element.classList).some((cls) =>
|
|
809
|
-
this.fontSizeRegex.test(cls),
|
|
810
|
-
)
|
|
811
|
-
|
|
812
|
-
if (!hasFontSizeClass) {
|
|
813
|
-
// Apply responsive font size classes based on heading type
|
|
814
|
-
if (heading.tagName === 'H2') {
|
|
815
|
-
element.classList.add('pbx-text-2xl', 'lg:pbx-text-4xl', 'pbx-font-medium')
|
|
816
|
-
}
|
|
817
|
-
if (heading.tagName === 'H3') {
|
|
818
|
-
element.classList.add('pbx-text-1xl', 'lg:pbx-text-3xl', 'pbx-font-medium')
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
/**
|
|
825
|
-
* Toggles the visibility of the TipTap modal for rich text editing.
|
|
826
|
-
* @param {boolean} status - Whether to show or hide the modal.
|
|
827
|
-
* @returns {Promise<void>}
|
|
828
|
-
*/
|
|
829
|
-
public async toggleTipTapModal(status: boolean): Promise<void> {
|
|
830
|
-
this.pageBuilderStateStore.setShowModalTipTap(status)
|
|
831
|
-
|
|
832
|
-
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
833
|
-
await nextTick()
|
|
834
|
-
// Attach event listeners to all editable elements in the Builder
|
|
835
|
-
await this.addListenersToEditableElements()
|
|
836
|
-
|
|
837
|
-
if (!status) {
|
|
838
|
-
await this.handleAutoSave()
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
/**
|
|
843
|
-
* Wraps an element in a div if it's an excluded tag and adjacent to an image.
|
|
844
|
-
* @param {HTMLElement} element - The element to potentially wrap.
|
|
845
|
-
* @private
|
|
846
|
-
*/
|
|
847
|
-
private wrapElementInDivIfExcluded(element: HTMLElement): void {
|
|
848
|
-
if (!element) return
|
|
849
|
-
|
|
850
|
-
if (
|
|
851
|
-
this.NoneListernesTags.includes(element.tagName) &&
|
|
852
|
-
((element.previousElementSibling && element.previousElementSibling.tagName === 'IMG') ||
|
|
853
|
-
(element.nextElementSibling && element.nextElementSibling.tagName === 'IMG'))
|
|
854
|
-
) {
|
|
855
|
-
const divWrapper = document.createElement('div')
|
|
856
|
-
element.parentNode?.insertBefore(divWrapper, element)
|
|
857
|
-
divWrapper.appendChild(element)
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
/**
|
|
862
|
-
* Handles the mouseover event for editable elements, showing a hover state.
|
|
863
|
-
* @param {Event} e - The mouse event.
|
|
864
|
-
* @param {HTMLElement} element - The element being hovered over.
|
|
865
|
-
* @private
|
|
866
|
-
*/
|
|
867
|
-
private handleMouseOver = (e: Event, element: HTMLElement): void => {
|
|
868
|
-
e.preventDefault()
|
|
869
|
-
e.stopPropagation()
|
|
870
|
-
|
|
871
|
-
const pagebuilder = document.querySelector('#pagebuilder')
|
|
872
|
-
|
|
873
|
-
if (!pagebuilder) return
|
|
874
|
-
|
|
875
|
-
const hoveredElement = pagebuilder.querySelector('[hovered]')
|
|
876
|
-
if (hoveredElement) {
|
|
877
|
-
hoveredElement.removeAttribute('hovered')
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
if (!element.hasAttribute('selected')) {
|
|
881
|
-
element.setAttribute('hovered', '')
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
/**
|
|
886
|
-
* Handles the mouseleave event for editable elements, removing the hover state.
|
|
887
|
-
* @param {Event} e - The mouse event.
|
|
888
|
-
* @private
|
|
889
|
-
*/
|
|
890
|
-
private handleMouseLeave = (e: Event): void => {
|
|
891
|
-
e.preventDefault()
|
|
892
|
-
e.stopPropagation()
|
|
893
|
-
|
|
894
|
-
const pagebuilder = document.querySelector('#pagebuilder')
|
|
895
|
-
if (!pagebuilder) return
|
|
896
|
-
|
|
897
|
-
const hoveredElement = pagebuilder.querySelector('[hovered]')
|
|
898
|
-
if (hoveredElement) {
|
|
899
|
-
hoveredElement.removeAttribute('hovered')
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
/**
|
|
904
|
-
* Checks if an element is editable based on its tag name.
|
|
905
|
-
* @param {Element | null} el - The element to check.
|
|
906
|
-
* @returns {boolean} True if the element is editable, false otherwise.
|
|
907
|
-
*/
|
|
908
|
-
public isEditableElement(el: Element | null): boolean {
|
|
909
|
-
if (!el) return false
|
|
910
|
-
return !this.NoneListernesTags.includes(el.tagName)
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
/**
|
|
914
|
-
* Attaches click, mouseover, and mouseleave event listeners to all editable elements in the page builder.
|
|
915
|
-
* @private
|
|
916
|
-
*/
|
|
917
|
-
private addListenersToEditableElements = async () => {
|
|
918
|
-
const pagebuilder = document.querySelector('#pagebuilder')
|
|
919
|
-
if (!pagebuilder) return
|
|
920
|
-
|
|
921
|
-
// Wait for the next DOM update cycle to ensure all elements are rendered.
|
|
922
|
-
await nextTick()
|
|
923
|
-
|
|
924
|
-
pagebuilder.querySelectorAll('section *').forEach((element) => {
|
|
925
|
-
if (this.isEditableElement(element)) {
|
|
926
|
-
const htmlElement = element as HTMLElement
|
|
927
|
-
|
|
928
|
-
// If the element already has listeners, remove them to avoid duplicates.
|
|
929
|
-
if (this.elementsWithListeners.has(htmlElement)) {
|
|
930
|
-
const listeners = this.elementsWithListeners.get(htmlElement)
|
|
931
|
-
if (listeners) {
|
|
932
|
-
htmlElement.removeEventListener('click', listeners.click)
|
|
933
|
-
htmlElement.removeEventListener('mouseover', listeners.mouseover)
|
|
934
|
-
htmlElement.removeEventListener('mouseleave', listeners.mouseleave)
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
// Define new listener functions.
|
|
939
|
-
const clickListener = (e: Event) => this.handleElementClick(e, htmlElement)
|
|
940
|
-
const mouseoverListener = (e: Event) => this.handleMouseOver(e, htmlElement)
|
|
941
|
-
const mouseleaveListener = (e: Event) => this.handleMouseLeave(e)
|
|
942
|
-
|
|
943
|
-
// Add the new event listeners.
|
|
944
|
-
htmlElement.addEventListener('click', clickListener)
|
|
945
|
-
htmlElement.addEventListener('mouseover', mouseoverListener)
|
|
946
|
-
htmlElement.addEventListener('mouseleave', mouseleaveListener)
|
|
947
|
-
|
|
948
|
-
// Store the new listeners in the WeakMap to track them.
|
|
949
|
-
this.elementsWithListeners.set(htmlElement, {
|
|
950
|
-
click: clickListener,
|
|
951
|
-
mouseover: mouseoverListener,
|
|
952
|
-
mouseleave: mouseleaveListener,
|
|
953
|
-
})
|
|
954
|
-
}
|
|
955
|
-
})
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
/**
|
|
959
|
-
* Handles the click event for editable elements, setting the element as selected.
|
|
960
|
-
* @param {Event} e - The click event.
|
|
961
|
-
* @param {HTMLElement} element - The clicked element.
|
|
962
|
-
* @private
|
|
963
|
-
*/
|
|
964
|
-
private handleElementClick = async (e: Event, element: HTMLElement): Promise<void> => {
|
|
965
|
-
e.preventDefault()
|
|
966
|
-
e.stopPropagation()
|
|
967
|
-
|
|
968
|
-
const pagebuilder = document.querySelector('#pagebuilder')
|
|
969
|
-
|
|
970
|
-
if (!pagebuilder) return
|
|
971
|
-
|
|
972
|
-
this.pageBuilderStateStore.setMenuRight(true)
|
|
973
|
-
|
|
974
|
-
const selectedElement = pagebuilder.querySelector('[selected]')
|
|
975
|
-
if (selectedElement) {
|
|
976
|
-
selectedElement.removeAttribute('selected')
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
element.removeAttribute('hovered')
|
|
980
|
-
|
|
981
|
-
element.setAttribute('selected', '')
|
|
982
|
-
|
|
983
|
-
this.pageBuilderStateStore.setElement(element)
|
|
984
|
-
|
|
985
|
-
await this.handleAutoSave()
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
private getHistoryBaseKey(): string | null {
|
|
989
|
-
return this.getLocalStorageItemName.value
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
private initializeHistory() {
|
|
993
|
-
const baseKey = this.getHistoryBaseKey()
|
|
994
|
-
if (baseKey) {
|
|
995
|
-
const history = LocalStorageManager.getHistory(baseKey)
|
|
996
|
-
this.pageBuilderStateStore.setHistoryIndex(history.length - 1)
|
|
997
|
-
this.pageBuilderStateStore.setHistoryLength(history.length)
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
/**
|
|
1002
|
-
* Triggers an auto-save of the current page builder content to local storage if enabled.
|
|
1003
|
-
*/
|
|
1004
|
-
public handleAutoSave = async () => {
|
|
1005
|
-
this.startEditing()
|
|
1006
|
-
const passedConfig = this.pageBuilderStateStore.getPageBuilderConfig
|
|
1007
|
-
|
|
1008
|
-
// Check if config is set
|
|
1009
|
-
if (passedConfig && passedConfig.userSettings) {
|
|
1010
|
-
//
|
|
1011
|
-
// Enabled auto save
|
|
1012
|
-
if (
|
|
1013
|
-
typeof passedConfig.userSettings.autoSave === 'boolean' &&
|
|
1014
|
-
passedConfig.userSettings.autoSave !== false
|
|
1015
|
-
) {
|
|
1016
|
-
if (this.pageBuilderStateStore.getIsSaving) return
|
|
1017
|
-
|
|
1018
|
-
try {
|
|
1019
|
-
this.pageBuilderStateStore.setIsSaving(true)
|
|
1020
|
-
// Deselect any selected or hovered elements in the builder UI
|
|
1021
|
-
//
|
|
1022
|
-
this.saveDomComponentsToLocalStorage()
|
|
1023
|
-
await delay(400)
|
|
1024
|
-
} catch (err) {
|
|
1025
|
-
console.error('Error trying auto save.', err)
|
|
1026
|
-
} finally {
|
|
1027
|
-
this.pageBuilderStateStore.setIsSaving(false)
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
if (passedConfig && !passedConfig.userSettings) {
|
|
1032
|
-
try {
|
|
1033
|
-
this.pageBuilderStateStore.setIsSaving(true)
|
|
1034
|
-
this.saveDomComponentsToLocalStorage()
|
|
1035
|
-
await delay(400)
|
|
1036
|
-
} catch (err) {
|
|
1037
|
-
console.error('Error trying saving.', err)
|
|
1038
|
-
} finally {
|
|
1039
|
-
this.pageBuilderStateStore.setIsSaving(false)
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
/**
|
|
1045
|
-
* Manually saves the current page builder content to local storage.
|
|
1046
|
-
*/
|
|
1047
|
-
public handleManualSave = async (doNoClearHTML?: boolean) => {
|
|
1048
|
-
this.pageBuilderStateStore.setIsSaving(true)
|
|
1049
|
-
if (!doNoClearHTML) {
|
|
1050
|
-
this.clearHtmlSelection()
|
|
1051
|
-
}
|
|
1052
|
-
this.startEditing()
|
|
1053
|
-
this.saveDomComponentsToLocalStorage()
|
|
1054
|
-
await delay(300)
|
|
1055
|
-
this.pageBuilderStateStore.setIsSaving(false)
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
/**
|
|
1059
|
-
* Clones a component object and prepares it for insertion into the DOM by adding unique IDs and prefixes.
|
|
1060
|
-
* @param {ComponentObject} componentObject - The component object to clone.
|
|
1061
|
-
* @returns {ComponentObject} The cloned and prepared component object.
|
|
1062
|
-
*/
|
|
1063
|
-
public cloneCompObjForDOMInsertion(componentObject: ComponentObject): ComponentObject {
|
|
1064
|
-
// Deep clone clone component
|
|
1065
|
-
const clonedComponent = { ...componentObject }
|
|
1066
|
-
|
|
1067
|
-
const pageBuilderWrapper = document.querySelector('#page-builder-wrapper')
|
|
1068
|
-
// scoll to top or bottom
|
|
1069
|
-
if (pageBuilderWrapper) {
|
|
1070
|
-
// push to top
|
|
1071
|
-
if (this.getComponentArrayAddMethod.value === 'unshift') {
|
|
1072
|
-
pageBuilderWrapper.scrollTo({
|
|
1073
|
-
top: 0,
|
|
1074
|
-
behavior: 'smooth',
|
|
1075
|
-
})
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
// Create a DOMParser instance
|
|
1080
|
-
const parser = new DOMParser()
|
|
1081
|
-
|
|
1082
|
-
// Parse the HTML content of the clonedComponent using the DOMParser
|
|
1083
|
-
const doc = parser.parseFromString(clonedComponent.html_code || '', 'text/html')
|
|
1084
|
-
|
|
1085
|
-
// Selects all elements within the HTML document, including elements like:
|
|
1086
|
-
const elements = doc.querySelectorAll('*')
|
|
1087
|
-
|
|
1088
|
-
elements.forEach((element) => {
|
|
1089
|
-
this.applyHelperCSSToElements(element as HTMLElement)
|
|
1090
|
-
})
|
|
1091
|
-
|
|
1092
|
-
// Add the component id to the section element
|
|
1093
|
-
const section = doc.querySelector('section')
|
|
1094
|
-
if (section) {
|
|
1095
|
-
// Prefix all classes inside the section
|
|
1096
|
-
section.querySelectorAll('[class]').forEach((el) => {
|
|
1097
|
-
el.setAttribute(
|
|
1098
|
-
'class',
|
|
1099
|
-
this.addTailwindPrefixToClasses(el.getAttribute('class') || '', 'pbx-'),
|
|
1100
|
-
)
|
|
1101
|
-
})
|
|
1102
|
-
|
|
1103
|
-
// Generate a unique ID using uuidv4() and assign it to the section
|
|
1104
|
-
section.dataset.componentid = uuidv4()
|
|
1105
|
-
|
|
1106
|
-
// Set the title attribute if present
|
|
1107
|
-
if (clonedComponent.title) {
|
|
1108
|
-
section.setAttribute('data-component-title', clonedComponent.title)
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
// Update the clonedComponent id with the newly generated unique ID
|
|
1112
|
-
clonedComponent.id = section.dataset.componentid
|
|
1113
|
-
|
|
1114
|
-
// Update the HTML content of the clonedComponent with the modified HTML
|
|
1115
|
-
clonedComponent.html_code = section.outerHTML
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
// return to the cloned element to be dropped
|
|
1119
|
-
return clonedComponent
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
/**
|
|
1123
|
-
* Removes the 'hovered' and 'selected' attributes from all elements in the page builder.
|
|
1124
|
-
* @private
|
|
1125
|
-
*/
|
|
1126
|
-
private async removeHoveredAndSelected() {
|
|
1127
|
-
const pagebuilder = document.querySelector('#pagebuilder')
|
|
1128
|
-
if (!pagebuilder) return
|
|
1129
|
-
|
|
1130
|
-
const hoveredElement = pagebuilder.querySelector('[hovered]')
|
|
1131
|
-
if (hoveredElement) {
|
|
1132
|
-
hoveredElement.removeAttribute('hovered')
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
const selectedElement = pagebuilder.querySelector('[selected]')
|
|
1136
|
-
|
|
1137
|
-
if (selectedElement) {
|
|
1138
|
-
selectedElement.removeAttribute('selected')
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
/**
|
|
1143
|
-
* Syncs the CSS classes of the currently selected element to the state store.
|
|
1144
|
-
* @private
|
|
1145
|
-
*/
|
|
1146
|
-
private async syncCurrentClasses() {
|
|
1147
|
-
// convert classList to array
|
|
1148
|
-
const classListArray = Array.from(this.getElement.value?.classList || [])
|
|
1149
|
-
|
|
1150
|
-
// commit array to store
|
|
1151
|
-
this.pageBuilderStateStore.setCurrentClasses(classListArray)
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
/**
|
|
1155
|
-
* Syncs the inline styles of the currently selected element to the state store.
|
|
1156
|
-
* @private
|
|
1157
|
-
*/
|
|
1158
|
-
private async syncCurrentStyles() {
|
|
1159
|
-
const style = this.getElement.value?.getAttribute('style')
|
|
1160
|
-
if (style) {
|
|
1161
|
-
const stylesObject = this.parseStyleString(style)
|
|
1162
|
-
this.pageBuilderStateStore.setCurrentStyles(stylesObject)
|
|
1163
|
-
} else {
|
|
1164
|
-
this.pageBuilderStateStore.setCurrentStyles({})
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
/**
|
|
1169
|
-
* Adds a CSS class to the currently selected element.
|
|
1170
|
-
* @param {string} userSelectedClass - The class to add.
|
|
1171
|
-
*/
|
|
1172
|
-
public handleAddClasses(userSelectedClass: string): void {
|
|
1173
|
-
if (
|
|
1174
|
-
typeof userSelectedClass === 'string' &&
|
|
1175
|
-
userSelectedClass.trim() !== '' &&
|
|
1176
|
-
!userSelectedClass.includes(' ') &&
|
|
1177
|
-
// Check if class (with prefix) already exists
|
|
1178
|
-
!this.getElement.value?.classList.contains('pbx-' + userSelectedClass.trim())
|
|
1179
|
-
) {
|
|
1180
|
-
const cleanedClass = userSelectedClass.trim()
|
|
1181
|
-
|
|
1182
|
-
// Add prefix if missing
|
|
1183
|
-
const prefixedClass = cleanedClass.startsWith('pbx-') ? cleanedClass : 'pbx-' + cleanedClass
|
|
1184
|
-
|
|
1185
|
-
this.getElement.value?.classList.add(prefixedClass)
|
|
1186
|
-
|
|
1187
|
-
this.pageBuilderStateStore.setElement(this.getElement.value)
|
|
1188
|
-
this.pageBuilderStateStore.setClass(prefixedClass)
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
/**
|
|
1193
|
-
* Adds or updates an inline style property on the currently selected element.
|
|
1194
|
-
* @param {string} property - The CSS property to add/update.
|
|
1195
|
-
* @param {string} value - The value of the CSS property.
|
|
1196
|
-
*/
|
|
1197
|
-
public handleAddStyle(property: string, value: string): void {
|
|
1198
|
-
const element = this.getElement.value
|
|
1199
|
-
if (!element || !property || !value) return
|
|
1200
|
-
|
|
1201
|
-
element.style.setProperty(property, value)
|
|
1202
|
-
this.pageBuilderStateStore.setElement(element)
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
/**
|
|
1206
|
-
* Removes an inline style property from the currently selected element.
|
|
1207
|
-
* @param {string} property - The CSS property to remove.
|
|
1208
|
-
*/
|
|
1209
|
-
public handleRemoveStyle(property: string): void {
|
|
1210
|
-
const element = this.getElement.value
|
|
1211
|
-
if (!element || !property) return
|
|
1212
|
-
|
|
1213
|
-
element.style.removeProperty(property)
|
|
1214
|
-
this.pageBuilderStateStore.setElement(element)
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
/**
|
|
1218
|
-
* Handles changes to the font family of the selected element.
|
|
1219
|
-
* @param {string} [userSelectedFontFamily] - The selected font family class.
|
|
1220
|
-
*/
|
|
1221
|
-
public handleFontFamily(userSelectedFontFamily?: string): void {
|
|
1222
|
-
this.applyElementClassChanges(
|
|
1223
|
-
userSelectedFontFamily,
|
|
1224
|
-
tailwindFontStyles.fontFamily,
|
|
1225
|
-
'setFontFamily',
|
|
1226
|
-
)
|
|
1227
|
-
}
|
|
1228
|
-
/**
|
|
1229
|
-
* Handles changes to the font style of the selected element.
|
|
1230
|
-
* @param {string} [userSelectedFontStyle] - The selected font style class.
|
|
1231
|
-
*/
|
|
1232
|
-
public handleFontStyle(userSelectedFontStyle?: string): void {
|
|
1233
|
-
this.applyElementClassChanges(
|
|
1234
|
-
userSelectedFontStyle,
|
|
1235
|
-
tailwindFontStyles.fontStyle,
|
|
1236
|
-
'setFontStyle',
|
|
1237
|
-
)
|
|
1238
|
-
}
|
|
1239
|
-
/**
|
|
1240
|
-
* Handles changes to the vertical padding of the selected element.
|
|
1241
|
-
* @param {string} [userSelectedVerticalPadding] - The selected vertical padding class.
|
|
1242
|
-
*/
|
|
1243
|
-
public handleVerticalPadding(userSelectedVerticalPadding?: string): void {
|
|
1244
|
-
this.applyElementClassChanges(
|
|
1245
|
-
userSelectedVerticalPadding,
|
|
1246
|
-
tailwindPaddingAndMargin.verticalPadding,
|
|
1247
|
-
'setFontVerticalPadding',
|
|
1248
|
-
)
|
|
1249
|
-
}
|
|
1250
|
-
/**
|
|
1251
|
-
* Handles changes to the horizontal padding of the selected element.
|
|
1252
|
-
* @param {string} [userSelectedHorizontalPadding] - The selected horizontal padding class.
|
|
1253
|
-
*/
|
|
1254
|
-
public handleHorizontalPadding(userSelectedHorizontalPadding?: string): void {
|
|
1255
|
-
this.applyElementClassChanges(
|
|
1256
|
-
userSelectedHorizontalPadding,
|
|
1257
|
-
tailwindPaddingAndMargin.horizontalPadding,
|
|
1258
|
-
'setFontHorizontalPadding',
|
|
1259
|
-
)
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
/**
|
|
1263
|
-
* Handles changes to the vertical margin of the selected element.
|
|
1264
|
-
* @param {string} [userSelectedVerticalMargin] - The selected vertical margin class.
|
|
1265
|
-
*/
|
|
1266
|
-
public handleVerticalMargin(userSelectedVerticalMargin?: string): void {
|
|
1267
|
-
this.applyElementClassChanges(
|
|
1268
|
-
userSelectedVerticalMargin,
|
|
1269
|
-
tailwindPaddingAndMargin.verticalMargin,
|
|
1270
|
-
'setFontVerticalMargin',
|
|
1271
|
-
)
|
|
1272
|
-
}
|
|
1273
|
-
/**
|
|
1274
|
-
* Handles changes to the horizontal margin of the selected element.
|
|
1275
|
-
* @param {string} [userSelectedHorizontalMargin] - The selected horizontal margin class.
|
|
1276
|
-
*/
|
|
1277
|
-
public handleHorizontalMargin(userSelectedHorizontalMargin?: string): void {
|
|
1278
|
-
this.applyElementClassChanges(
|
|
1279
|
-
userSelectedHorizontalMargin,
|
|
1280
|
-
tailwindPaddingAndMargin.horizontalMargin,
|
|
1281
|
-
'setFontHorizontalMargin',
|
|
1282
|
-
)
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
/**
|
|
1286
|
-
* Handles changes to the border style of the selected element.
|
|
1287
|
-
* @param {string} [borderStyle] - The selected border style class.
|
|
1288
|
-
*/
|
|
1289
|
-
public handleBorderStyle(borderStyle?: string): void {
|
|
1290
|
-
this.applyElementClassChanges(
|
|
1291
|
-
borderStyle,
|
|
1292
|
-
tailwindBorderStyleWidthPlusColor.borderStyle,
|
|
1293
|
-
'setBorderStyle',
|
|
1294
|
-
)
|
|
1295
|
-
}
|
|
1296
|
-
/**
|
|
1297
|
-
* Handles changes to the border width of the selected element.
|
|
1298
|
-
* @param {string} [borderWidth] - The selected border width class.
|
|
1299
|
-
*/
|
|
1300
|
-
public handleBorderWidth(borderWidth?: string): void {
|
|
1301
|
-
this.applyElementClassChanges(
|
|
1302
|
-
borderWidth,
|
|
1303
|
-
tailwindBorderStyleWidthPlusColor.borderWidth,
|
|
1304
|
-
'setBorderWidth',
|
|
1305
|
-
)
|
|
1306
|
-
}
|
|
1307
|
-
/**
|
|
1308
|
-
* Handles changes to the border color of the selected element.
|
|
1309
|
-
* @param {string} [borderColor] - The selected border color class.
|
|
1310
|
-
*/
|
|
1311
|
-
public handleBorderColor(borderColor?: string): void {
|
|
1312
|
-
this.applyElementClassChanges(
|
|
1313
|
-
borderColor,
|
|
1314
|
-
tailwindBorderStyleWidthPlusColor.borderColor,
|
|
1315
|
-
'setBorderColor',
|
|
1316
|
-
)
|
|
1317
|
-
}
|
|
1318
|
-
// border color, style & width / end
|
|
1319
|
-
|
|
1320
|
-
/**
|
|
1321
|
-
* Handles changes to the background color of the selected element.
|
|
1322
|
-
* @param {string} [color] - The selected background color class.
|
|
1323
|
-
*/
|
|
1324
|
-
public handleBackgroundColor(color?: string): void {
|
|
1325
|
-
this.applyElementClassChanges(
|
|
1326
|
-
color,
|
|
1327
|
-
tailwindColors.backgroundColorVariables,
|
|
1328
|
-
'setBackgroundColor',
|
|
1329
|
-
)
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
/**
|
|
1333
|
-
* Handles changes to the text color of the selected element.
|
|
1334
|
-
* @param {string} [color] - The selected text color class.
|
|
1335
|
-
*/
|
|
1336
|
-
public handleTextColor(color?: string): void {
|
|
1337
|
-
this.applyElementClassChanges(color, tailwindColors.textColorVariables, 'setTextColor')
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
/**
|
|
1341
|
-
* Handles changes to the global border radius of the selected element.
|
|
1342
|
-
* @param {string} [borderRadiusGlobal] - The selected global border radius class.
|
|
1343
|
-
*/
|
|
1344
|
-
handleBorderRadiusGlobal(borderRadiusGlobal?: string): void {
|
|
1345
|
-
this.applyElementClassChanges(
|
|
1346
|
-
borderRadiusGlobal,
|
|
1347
|
-
tailwindBorderRadius.roundedGlobal,
|
|
1348
|
-
'setBorderRadiusGlobal',
|
|
1349
|
-
)
|
|
1350
|
-
}
|
|
1351
|
-
/**
|
|
1352
|
-
* Handles changes to the top-left border radius of the selected element.
|
|
1353
|
-
* @param {string} [borderRadiusTopLeft] - The selected top-left border radius class.
|
|
1354
|
-
*/
|
|
1355
|
-
handleBorderRadiusTopLeft(borderRadiusTopLeft?: string): void {
|
|
1356
|
-
this.applyElementClassChanges(
|
|
1357
|
-
borderRadiusTopLeft,
|
|
1358
|
-
tailwindBorderRadius.roundedTopLeft,
|
|
1359
|
-
'setBorderRadiusTopLeft',
|
|
1360
|
-
)
|
|
1361
|
-
}
|
|
1362
|
-
/**
|
|
1363
|
-
* Handles changes to the top-right border radius of the selected element.
|
|
1364
|
-
* @param {string} [borderRadiusTopRight] - The selected top-right border radius class.
|
|
1365
|
-
*/
|
|
1366
|
-
handleBorderRadiusTopRight(borderRadiusTopRight?: string): void {
|
|
1367
|
-
this.applyElementClassChanges(
|
|
1368
|
-
borderRadiusTopRight,
|
|
1369
|
-
tailwindBorderRadius.roundedTopRight,
|
|
1370
|
-
'setBorderRadiusTopRight',
|
|
1371
|
-
)
|
|
1372
|
-
}
|
|
1373
|
-
/**
|
|
1374
|
-
* Handles changes to the bottom-left border radius of the selected element.
|
|
1375
|
-
* @param {string} [borderRadiusBottomleft] - The selected bottom-left border radius class.
|
|
1376
|
-
*/
|
|
1377
|
-
handleBorderRadiusBottomleft(borderRadiusBottomleft?: string): void {
|
|
1378
|
-
this.applyElementClassChanges(
|
|
1379
|
-
borderRadiusBottomleft,
|
|
1380
|
-
tailwindBorderRadius.roundedBottomLeft,
|
|
1381
|
-
'setBorderRadiusBottomleft',
|
|
1382
|
-
)
|
|
1383
|
-
}
|
|
1384
|
-
/**
|
|
1385
|
-
* Handles changes to the bottom-right border radius of the selected element.
|
|
1386
|
-
* @param {string} [borderRadiusBottomRight] - The selected bottom-right border radius class.
|
|
1387
|
-
*/
|
|
1388
|
-
handleBorderRadiusBottomRight(borderRadiusBottomRight?: string): void {
|
|
1389
|
-
this.applyElementClassChanges(
|
|
1390
|
-
borderRadiusBottomRight,
|
|
1391
|
-
tailwindBorderRadius.roundedBottomRight,
|
|
1392
|
-
'setBorderRadiusBottomRight',
|
|
1393
|
-
)
|
|
1394
|
-
}
|
|
1395
|
-
// border radius / end
|
|
1396
|
-
|
|
1397
|
-
/**
|
|
1398
|
-
* Handles changes to the tablet font size of the selected element.
|
|
1399
|
-
* @param {string} [userSelectedFontSize] - The selected font size class for tablet.
|
|
1400
|
-
*/
|
|
1401
|
-
handleFontSizeTablet(userSelectedFontSize?: string): void {
|
|
1402
|
-
this.applyElementClassChanges(
|
|
1403
|
-
userSelectedFontSize,
|
|
1404
|
-
tailwindFontSizes.fontTablet,
|
|
1405
|
-
'setFontTablet',
|
|
1406
|
-
)
|
|
1407
|
-
}
|
|
1408
|
-
/**
|
|
1409
|
-
* Handles changes to the mobile font size of the selected element.
|
|
1410
|
-
* @param {string} [userSelectedFontSize] - The selected font size class for mobile.
|
|
1411
|
-
*/
|
|
1412
|
-
handleFontSizeMobile(userSelectedFontSize?: string): void {
|
|
1413
|
-
this.applyElementClassChanges(
|
|
1414
|
-
userSelectedFontSize,
|
|
1415
|
-
tailwindFontSizes.fontMobile,
|
|
1416
|
-
'setFontMobile',
|
|
1417
|
-
)
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
/**
|
|
1421
|
-
* Handles changes to the background opacity of the selected element.
|
|
1422
|
-
* @param {string} [opacity] - The selected background opacity class.
|
|
1423
|
-
*/
|
|
1424
|
-
handleBackgroundOpacity(opacity?: string): void {
|
|
1425
|
-
this.applyElementClassChanges(
|
|
1426
|
-
opacity,
|
|
1427
|
-
tailwindOpacities.backgroundOpacities,
|
|
1428
|
-
'setBackgroundOpacity',
|
|
1429
|
-
)
|
|
1430
|
-
}
|
|
1431
|
-
/**
|
|
1432
|
-
* Handles changes to the opacity of the selected element.
|
|
1433
|
-
* @param {string} [opacity] - The selected opacity class.
|
|
1434
|
-
*/
|
|
1435
|
-
handleOpacity(opacity?: string): void {
|
|
1436
|
-
this.applyElementClassChanges(opacity, tailwindOpacities.opacities, 'setOpacity')
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
/**
|
|
1440
|
-
* Removes all components from both the builder state and the DOM.
|
|
1441
|
-
* @private
|
|
1442
|
-
*/
|
|
1443
|
-
private deleteAllComponentsFromDOM() {
|
|
1444
|
-
// Clear the store
|
|
1445
|
-
this.pageBuilderStateStore.setComponents([])
|
|
1446
|
-
|
|
1447
|
-
// Also clear the DOM
|
|
1448
|
-
const pagebuilder = document.querySelector('#pagebuilder')
|
|
1449
|
-
if (pagebuilder) {
|
|
1450
|
-
// Remove all section elements (assuming each component is a <section>)
|
|
1451
|
-
pagebuilder
|
|
1452
|
-
.querySelectorAll('section[data-componentid]')
|
|
1453
|
-
.forEach((section) => section.remove())
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
public async undo() {
|
|
1458
|
-
this.pageBuilderStateStore.setIsLoadingGlobal(true)
|
|
1459
|
-
await delay(300)
|
|
1460
|
-
const baseKey = this.getHistoryBaseKey()
|
|
1461
|
-
if (!baseKey) return
|
|
1462
|
-
|
|
1463
|
-
const history = LocalStorageManager.getHistory(baseKey)
|
|
1464
|
-
if (history.length > 1 && this.pageBuilderStateStore.getHistoryIndex > 0) {
|
|
1465
|
-
this.pageBuilderStateStore.setHistoryIndex(this.pageBuilderStateStore.getHistoryIndex - 1)
|
|
1466
|
-
const data = history[this.pageBuilderStateStore.getHistoryIndex]
|
|
1467
|
-
const htmlString = this.renderComponentsToHtml(data.components)
|
|
1468
|
-
await this.mountComponentsToDOM(htmlString, false, data.pageSettings)
|
|
1469
|
-
}
|
|
1470
|
-
this.pageBuilderStateStore.setIsLoadingGlobal(false)
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
public async redo() {
|
|
1474
|
-
this.pageBuilderStateStore.setIsLoadingGlobal(true)
|
|
1475
|
-
await delay(300)
|
|
1476
|
-
const baseKey = this.getHistoryBaseKey()
|
|
1477
|
-
if (!baseKey) return
|
|
1478
|
-
|
|
1479
|
-
const history = LocalStorageManager.getHistory(baseKey)
|
|
1480
|
-
if (history.length > 0 && this.pageBuilderStateStore.getHistoryIndex < history.length - 1) {
|
|
1481
|
-
this.pageBuilderStateStore.setHistoryIndex(this.pageBuilderStateStore.getHistoryIndex + 1)
|
|
1482
|
-
const data = history[this.pageBuilderStateStore.getHistoryIndex]
|
|
1483
|
-
const htmlString = this.renderComponentsToHtml(data.components)
|
|
1484
|
-
await this.mountComponentsToDOM(htmlString, false, data.pageSettings)
|
|
1485
|
-
}
|
|
1486
|
-
this.pageBuilderStateStore.setIsLoadingGlobal(false)
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
private hasVisibleContent(element: HTMLElement): boolean {
|
|
1490
|
-
if (!element) return false
|
|
1491
|
-
|
|
1492
|
-
// Check for meaningful elements
|
|
1493
|
-
const meaningfulContentSelector =
|
|
1494
|
-
'img, video, iframe, input, button, a, h1, h2, h3, h4, h5, h6, p, li, blockquote, pre, code, table'
|
|
1495
|
-
if (element.querySelector(meaningfulContentSelector)) return true
|
|
1496
|
-
|
|
1497
|
-
// Check for non-empty text nodes
|
|
1498
|
-
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null)
|
|
1499
|
-
while (walker.nextNode()) {
|
|
1500
|
-
if (walker.currentNode.nodeValue && walker.currentNode.nodeValue.trim() !== '') {
|
|
1501
|
-
return true
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
return false
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
private isSectionEmpty(section: HTMLElement): boolean {
|
|
1509
|
-
return !this.hasVisibleContent(section)
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
|
-
/**
|
|
1513
|
-
* Duplicate the currently selected component from the DOM and the state.
|
|
1514
|
-
* @returns {Promise<void>}
|
|
1515
|
-
*/
|
|
1516
|
-
public async duplicateComponent() {
|
|
1517
|
-
// Sync latest DOM changes to the store
|
|
1518
|
-
this.syncDomToStoreOnly()
|
|
1519
|
-
await nextTick()
|
|
1520
|
-
|
|
1521
|
-
const components = this.pageBuilderStateStore.getComponents
|
|
1522
|
-
const selectedComponent = this.getComponent.value
|
|
1523
|
-
|
|
1524
|
-
if (!components || !selectedComponent) return
|
|
1525
|
-
|
|
1526
|
-
// Find the index of the selected component
|
|
1527
|
-
const index = components.findIndex(
|
|
1528
|
-
(component: ComponentObject) => component.id === selectedComponent.id,
|
|
1529
|
-
)
|
|
1530
|
-
if (index === -1) return
|
|
1531
|
-
|
|
1532
|
-
// Clone the component and generate a new id
|
|
1533
|
-
const clonedComponent = this.cloneCompObjForDOMInsertion(components[index])
|
|
1534
|
-
|
|
1535
|
-
// Insert the cloned component right after the selected one
|
|
1536
|
-
const newComponents = [
|
|
1537
|
-
...components.slice(0, index + 1),
|
|
1538
|
-
clonedComponent,
|
|
1539
|
-
...components.slice(index + 1),
|
|
1540
|
-
]
|
|
1541
|
-
|
|
1542
|
-
this.pageBuilderStateStore.setComponents(newComponents)
|
|
1543
|
-
|
|
1544
|
-
// Wait for DOM update and re-attach listeners
|
|
1545
|
-
await nextTick()
|
|
1546
|
-
await this.addListenersToEditableElements()
|
|
1547
|
-
|
|
1548
|
-
// Optionally, select the new duplicated component
|
|
1549
|
-
this.pageBuilderStateStore.setComponent(clonedComponent)
|
|
1550
|
-
this.pageBuilderStateStore.setElement(null)
|
|
1551
|
-
|
|
1552
|
-
// Auto-save after duplication
|
|
1553
|
-
await this.handleAutoSave()
|
|
1554
|
-
}
|
|
1555
|
-
/**
|
|
1556
|
-
* Deletes the currently selected component from the DOM and the state.
|
|
1557
|
-
* @returns {Promise<void>}
|
|
1558
|
-
*/
|
|
1559
|
-
public async deleteComponentFromDOM() {
|
|
1560
|
-
this.syncDomToStoreOnly()
|
|
1561
|
-
await nextTick()
|
|
1562
|
-
|
|
1563
|
-
const components = this.pageBuilderStateStore.getComponents
|
|
1564
|
-
|
|
1565
|
-
if (!components) return
|
|
1566
|
-
|
|
1567
|
-
// Find the index of the component to be deleted.
|
|
1568
|
-
const indexToDelete = components.findIndex((component: ComponentObject) =>
|
|
1569
|
-
this.getComponent.value ? component.id === this.getComponent.value.id : false,
|
|
1570
|
-
)
|
|
1571
|
-
|
|
1572
|
-
if (indexToDelete === -1) {
|
|
1573
|
-
// If the component is not found, do nothing.
|
|
1574
|
-
return
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
// Create a new array excluding the component to be deleted.
|
|
1578
|
-
const newComponents = [
|
|
1579
|
-
...components.slice(0, indexToDelete),
|
|
1580
|
-
...components.slice(indexToDelete + 1),
|
|
1581
|
-
]
|
|
1582
|
-
|
|
1583
|
-
this.pageBuilderStateStore.setComponents(newComponents)
|
|
1584
|
-
|
|
1585
|
-
// Wait for the DOM to update before re-attaching event listeners.
|
|
1586
|
-
await nextTick()
|
|
1587
|
-
await this.addListenersToEditableElements()
|
|
1588
|
-
|
|
1589
|
-
this.pageBuilderStateStore.setComponent(null)
|
|
1590
|
-
this.pageBuilderStateStore.setElement(null)
|
|
1591
|
-
|
|
1592
|
-
// Trigger an auto-save after deletion.
|
|
1593
|
-
await this.handleAutoSave()
|
|
1594
|
-
}
|
|
1595
|
-
|
|
1596
|
-
/**
|
|
1597
|
-
* Deletes the currently selected element from the DOM and stores it for potential restoration.
|
|
1598
|
-
* @returns {Promise<void>}
|
|
1599
|
-
*/
|
|
1600
|
-
public async deleteElementFromDOM() {
|
|
1601
|
-
const element = this.getElement.value
|
|
1602
|
-
if (!element) return
|
|
1603
|
-
|
|
1604
|
-
// Remove the 'selected' attribute before deletion to avoid visual artifacts.
|
|
1605
|
-
element.removeAttribute('selected')
|
|
1606
|
-
|
|
1607
|
-
if (!element.parentNode) {
|
|
1608
|
-
this.pageBuilderStateStore.setComponent(null)
|
|
1609
|
-
this.pageBuilderStateStore.setElement(null)
|
|
1610
|
-
return
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
const parentSection = element.closest('section')
|
|
1614
|
-
|
|
1615
|
-
// If the element to be deleted is the section itself
|
|
1616
|
-
if (element.tagName === 'SECTION') {
|
|
1617
|
-
await this.deleteComponentFromDOM()
|
|
1618
|
-
} else {
|
|
1619
|
-
// If the element is inside a section
|
|
1620
|
-
element.remove()
|
|
1621
|
-
// --- Sync DOM to store after DOM mutation ---
|
|
1622
|
-
this.syncDomToStoreOnly()
|
|
1623
|
-
|
|
1624
|
-
if (parentSection && this.isSectionEmpty(parentSection)) {
|
|
1625
|
-
const componentId = parentSection.getAttribute('data-componentid')
|
|
1626
|
-
if (componentId) {
|
|
1627
|
-
const components = this.pageBuilderStateStore.getComponents
|
|
1628
|
-
if (components) {
|
|
1629
|
-
const indexToDelete = components.findIndex((c: ComponentObject) => c.id === componentId)
|
|
1630
|
-
if (indexToDelete !== -1) {
|
|
1631
|
-
const newComponents = [
|
|
1632
|
-
...components.slice(0, indexToDelete),
|
|
1633
|
-
...components.slice(indexToDelete + 1),
|
|
1634
|
-
]
|
|
1635
|
-
this.pageBuilderStateStore.setComponents(newComponents)
|
|
1636
|
-
parentSection.remove() // Directly remove from DOM
|
|
1637
|
-
// --- Sync DOM to store again after removing section ---
|
|
1638
|
-
this.syncDomToStoreOnly()
|
|
1639
|
-
// Save after removing the section
|
|
1640
|
-
this.saveDomComponentsToLocalStorage()
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
} else if (parentSection) {
|
|
1645
|
-
// If the section is not empty, update its HTML content in the store
|
|
1646
|
-
const componentId = parentSection.getAttribute('data-componentid')
|
|
1647
|
-
if (componentId) {
|
|
1648
|
-
const components = this.pageBuilderStateStore.getComponents
|
|
1649
|
-
if (components) {
|
|
1650
|
-
const componentIndex = components.findIndex(
|
|
1651
|
-
(c: ComponentObject) => c.id === componentId,
|
|
1652
|
-
)
|
|
1653
|
-
if (componentIndex !== -1) {
|
|
1654
|
-
const updatedComponent = {
|
|
1655
|
-
...components[componentIndex],
|
|
1656
|
-
html_code: parentSection.outerHTML,
|
|
1657
|
-
}
|
|
1658
|
-
const newComponents = [
|
|
1659
|
-
...components.slice(0, componentIndex),
|
|
1660
|
-
updatedComponent,
|
|
1661
|
-
...components.slice(componentIndex + 1),
|
|
1662
|
-
]
|
|
1663
|
-
this.pageBuilderStateStore.setComponents(newComponents)
|
|
1664
|
-
// --- Sync DOM to store after updating section ---
|
|
1665
|
-
this.syncDomToStoreOnly()
|
|
1666
|
-
// Save after updating the section
|
|
1667
|
-
this.saveDomComponentsToLocalStorage()
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
} else {
|
|
1672
|
-
// If no parentSection, still sync and save
|
|
1673
|
-
this.syncDomToStoreOnly()
|
|
1674
|
-
this.saveDomComponentsToLocalStorage()
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
// Clear the selection state.
|
|
1679
|
-
this.pageBuilderStateStore.setComponent(null)
|
|
1680
|
-
this.pageBuilderStateStore.setElement(null)
|
|
1681
|
-
|
|
1682
|
-
// Deselect any selected or hovered elements in the builder UI.
|
|
1683
|
-
await this.clearHtmlSelection()
|
|
1684
|
-
// Wait for the DOM to update before re-attaching event listeners.
|
|
1685
|
-
await nextTick()
|
|
1686
|
-
// Re-attach event listeners to all editable elements.
|
|
1687
|
-
await this.addListenersToEditableElements()
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
/**
|
|
1691
|
-
* Removes a CSS class from the currently selected element.
|
|
1692
|
-
* @param {string} userSelectedClass - The class to remove.
|
|
1693
|
-
*/
|
|
1694
|
-
public handleRemoveClasses(userSelectedClass: string): void {
|
|
1695
|
-
// remove selected class from element
|
|
1696
|
-
if (this.getElement.value?.classList.contains(userSelectedClass)) {
|
|
1697
|
-
this.getElement.value?.classList.remove(userSelectedClass)
|
|
1698
|
-
|
|
1699
|
-
this.pageBuilderStateStore.setElement(this.getElement.value)
|
|
1700
|
-
this.pageBuilderStateStore.removeClass(userSelectedClass)
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
|
|
1704
|
-
/**
|
|
1705
|
-
* Reorders the currently selected component up or down in the component list.
|
|
1706
|
-
* @param {number} direction - The direction to move the component (-1 for up, 1 for down).
|
|
1707
|
-
*/
|
|
1708
|
-
public async reorderComponent(direction: number): Promise<void> {
|
|
1709
|
-
if (!this.getComponents.value || !this.getComponent.value) return
|
|
1710
|
-
|
|
1711
|
-
if (this.getComponents.value.length <= 1) return
|
|
1712
|
-
|
|
1713
|
-
// Find the component to move.
|
|
1714
|
-
const componentToMove = this.getComponent.value
|
|
1715
|
-
|
|
1716
|
-
// Determine the current index of the component.
|
|
1717
|
-
const currentIndex = this.getComponents.value.findIndex(
|
|
1718
|
-
(component) => component.id === componentToMove.id,
|
|
1719
|
-
)
|
|
1720
|
-
|
|
1721
|
-
if (currentIndex === -1) {
|
|
1722
|
-
// Component not found in the array.
|
|
1723
|
-
return
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
const newIndex = currentIndex + direction
|
|
1727
|
-
|
|
1728
|
-
// Ensure the new index is within the bounds of the array.
|
|
1729
|
-
if (newIndex < 0 || newIndex >= this.getComponents.value.length) {
|
|
1730
|
-
return
|
|
1731
|
-
}
|
|
1732
|
-
|
|
1733
|
-
// Move the component to the new position in the array.
|
|
1734
|
-
this.getComponents.value.splice(currentIndex, 1)
|
|
1735
|
-
this.getComponents.value.splice(newIndex, 0, componentToMove)
|
|
1736
|
-
|
|
1737
|
-
// Wait for the DOM to update after reordering
|
|
1738
|
-
await nextTick()
|
|
1739
|
-
|
|
1740
|
-
// Scroll to the moved component
|
|
1741
|
-
const pageBuilderWrapper = document.querySelector('#page-builder-wrapper') as HTMLElement | null
|
|
1742
|
-
const movedComponentElement = pageBuilderWrapper?.querySelector(
|
|
1743
|
-
`section[data-componentid="${componentToMove.id}"]`,
|
|
1744
|
-
) as HTMLElement
|
|
1745
|
-
|
|
1746
|
-
if (movedComponentElement) {
|
|
1747
|
-
// Apply highlight to the moved element
|
|
1748
|
-
movedComponentElement.classList.add('pbx-reorder-highlight')
|
|
1749
|
-
|
|
1750
|
-
// Highlight its new neighbors (if they exist)
|
|
1751
|
-
const prevSibling = movedComponentElement.previousElementSibling as HTMLElement
|
|
1752
|
-
const nextSibling = movedComponentElement.nextElementSibling as HTMLElement
|
|
1753
|
-
|
|
1754
|
-
if (prevSibling && prevSibling.tagName === 'SECTION') {
|
|
1755
|
-
prevSibling.classList.add('pbx-sibling-highlight')
|
|
1756
|
-
}
|
|
1757
|
-
if (nextSibling && nextSibling.tagName === 'SECTION') {
|
|
1758
|
-
nextSibling.classList.add('pbx-sibling-highlight')
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
if (pageBuilderWrapper) {
|
|
1762
|
-
// Scroll to the moved component
|
|
1763
|
-
const topPos = movedComponentElement.offsetTop - pageBuilderWrapper.offsetTop
|
|
1764
|
-
pageBuilderWrapper.scrollTop = topPos - pageBuilderWrapper.clientHeight / 2
|
|
1765
|
-
|
|
1766
|
-
// Remove highlights after a delay
|
|
1767
|
-
setTimeout(() => {
|
|
1768
|
-
movedComponentElement.classList.remove('pbx-reorder-highlight')
|
|
1769
|
-
if (prevSibling && prevSibling.tagName === 'SECTION') {
|
|
1770
|
-
prevSibling.classList.remove('pbx-sibling-highlight')
|
|
1771
|
-
}
|
|
1772
|
-
if (nextSibling && nextSibling.tagName === 'SECTION') {
|
|
1773
|
-
nextSibling.classList.remove('pbx-sibling-highlight')
|
|
1774
|
-
}
|
|
1775
|
-
}, 200)
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
|
|
1780
|
-
/**
|
|
1781
|
-
* Checks if the currently selected component can be moved up.
|
|
1782
|
-
* @returns {boolean} True if the component can be moved up, false otherwise.
|
|
1783
|
-
*/
|
|
1784
|
-
public canMoveUp(): boolean {
|
|
1785
|
-
if (!this.getComponents.value || !this.getComponent.value) return false
|
|
1786
|
-
const currentIndex = this.getComponents.value.findIndex(
|
|
1787
|
-
(component) => component.id === this.getComponent.value?.id,
|
|
1788
|
-
)
|
|
1789
|
-
return currentIndex > 0
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
/**
|
|
1793
|
-
* Checks if the currently selected component can be moved down.
|
|
1794
|
-
* @returns {boolean} True if the component can be moved down, false otherwise.
|
|
1795
|
-
*/
|
|
1796
|
-
public canMoveDown(): boolean {
|
|
1797
|
-
if (!this.getComponents.value || !this.getComponent.value) return false
|
|
1798
|
-
const currentIndex = this.getComponents.value.findIndex(
|
|
1799
|
-
(component) => component.id === this.getComponent.value?.id,
|
|
1800
|
-
)
|
|
1801
|
-
return currentIndex < this.getComponents.value.length - 1
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
/**
|
|
1805
|
-
* Ensures that a text area element has content, adding a visual indicator if it's empty.
|
|
1806
|
-
*/
|
|
1807
|
-
public ensureTextAreaHasContent = () => {
|
|
1808
|
-
if (!this.getElement.value) return
|
|
1809
|
-
|
|
1810
|
-
// text content
|
|
1811
|
-
if (typeof this.getElement.value.innerHTML !== 'string') {
|
|
1812
|
-
return
|
|
1813
|
-
}
|
|
1814
|
-
const element = this.getElement.value
|
|
1815
|
-
const elementTag = element.tagName
|
|
1816
|
-
|
|
1817
|
-
if (
|
|
1818
|
-
['DIV'].includes(elementTag) &&
|
|
1819
|
-
element.tagName.toLowerCase() !== 'img' &&
|
|
1820
|
-
element.textContent &&
|
|
1821
|
-
Number(element.textContent.length) === 0
|
|
1822
|
-
) {
|
|
1823
|
-
element.classList.add('h-6')
|
|
1824
|
-
element.classList.add('bg-red-50')
|
|
1825
|
-
} else {
|
|
1826
|
-
element.classList.remove('h-6')
|
|
1827
|
-
element.classList.remove('bg-red-50')
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
/**
|
|
1832
|
-
* Handles text input for an element, updating its content.
|
|
1833
|
-
* @param {string} textContentVueModel - The new text content from the Vue model.
|
|
1834
|
-
* @returns {Promise<void>}
|
|
1835
|
-
*/
|
|
1836
|
-
public handleTextInput = async (textContentVueModel: string): Promise<void> => {
|
|
1837
|
-
if (typeof this.getElement.value?.innerHTML !== 'string') {
|
|
1838
|
-
return
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
if (typeof this.getElement.value.innerHTML === 'string') {
|
|
1842
|
-
await nextTick()
|
|
1843
|
-
|
|
1844
|
-
// Update text content
|
|
1845
|
-
this.getElement.value.textContent = textContentVueModel
|
|
1846
|
-
|
|
1847
|
-
this.pageBuilderStateStore.setTextAreaVueModel(this.getElement.value.innerHTML)
|
|
1848
|
-
|
|
1849
|
-
this.getElement.value.innerHTML = textContentVueModel
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
|
-
this.ensureTextAreaHasContent()
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
/**
|
|
1856
|
-
* Checks if the selected element or its first child is an iframe.
|
|
1857
|
-
* @returns {boolean} True if it is an iframe, false otherwise.
|
|
1858
|
-
*/
|
|
1859
|
-
public ElOrFirstChildIsIframe() {
|
|
1860
|
-
if (
|
|
1861
|
-
this.getElement.value?.tagName === 'IFRAME' ||
|
|
1862
|
-
this.getElement.value?.firstElementChild?.tagName === 'IFRAME'
|
|
1863
|
-
) {
|
|
1864
|
-
return true
|
|
1865
|
-
} else {
|
|
1866
|
-
return false
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
/**
|
|
1870
|
-
* Checks if the selected element is a valid text container (i.e., does not contain images or divs).
|
|
1871
|
-
* @returns {boolean | undefined} True if it's a valid text element, otherwise undefined.
|
|
1872
|
-
*/
|
|
1873
|
-
public isSelectedElementValidText() {
|
|
1874
|
-
let reachedElseStatement = false
|
|
1875
|
-
|
|
1876
|
-
// Get all child elements of the parentDiv
|
|
1877
|
-
const childElements = this.getElement.value?.children
|
|
1878
|
-
if (
|
|
1879
|
-
this.getElement.value?.tagName === 'IMG' ||
|
|
1880
|
-
this.getElement.value?.firstElementChild?.tagName === 'IFRAME'
|
|
1881
|
-
) {
|
|
1882
|
-
return
|
|
1883
|
-
}
|
|
1884
|
-
if (!childElements) {
|
|
1885
|
-
return
|
|
1886
|
-
}
|
|
1887
|
-
|
|
1888
|
-
Array.from(childElements).forEach((element) => {
|
|
1889
|
-
if (element?.tagName === 'IMG' || element?.tagName === 'DIV') {
|
|
1890
|
-
reachedElseStatement = false
|
|
1891
|
-
} else {
|
|
1892
|
-
reachedElseStatement = true
|
|
1893
|
-
}
|
|
1894
|
-
})
|
|
1895
|
-
|
|
1896
|
-
return reachedElseStatement
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
/**
|
|
1900
|
-
* Generates a preview of the current page design.
|
|
1901
|
-
*/
|
|
1902
|
-
public previewCurrentDesign() {
|
|
1903
|
-
this.pageBuilderStateStore.setElement(null)
|
|
1904
|
-
|
|
1905
|
-
const pagebuilder = document.querySelector('#pagebuilder')
|
|
1906
|
-
if (!pagebuilder) return
|
|
1907
|
-
|
|
1908
|
-
if (pagebuilder) {
|
|
1909
|
-
// Get cleaned HTML from entire builder
|
|
1910
|
-
const cleanedHTML = extractCleanHTMLFromPageBuilder(
|
|
1911
|
-
pagebuilder as HTMLElement,
|
|
1912
|
-
this.pageBuilderStateStore.getPageBuilderConfig
|
|
1913
|
-
? this.pageBuilderStateStore.getPageBuilderConfig
|
|
1914
|
-
: undefined,
|
|
1915
|
-
)
|
|
1916
|
-
|
|
1917
|
-
// Store as array with one string (as your preview expects an array)
|
|
1918
|
-
const previewData = JSON.stringify([cleanedHTML])
|
|
1919
|
-
|
|
1920
|
-
this.pageBuilderStateStore.setCurrentLayoutPreview(previewData)
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
/**
|
|
1924
|
-
* Sanitizes a string to be used as a key in local storage.
|
|
1925
|
-
* @param {string} input - The string to sanitize.
|
|
1926
|
-
* @returns {string} The sanitized string.
|
|
1927
|
-
*/
|
|
1928
|
-
public sanitizeForLocalStorage(input: string): string {
|
|
1929
|
-
return input
|
|
1930
|
-
.trim() // Remove leading/trailing spaces
|
|
1931
|
-
.toLowerCase() // Convert to lowercase
|
|
1932
|
-
.replace(/\s+/g, '-') // Replace one or more spaces with single hyphen
|
|
1933
|
-
.replace(/[^a-z0-9-]/g, '') // Remove all non-alphanumeric characters except hyphens
|
|
1934
|
-
.replace(/-+/g, '-') // Replace multiple consecutive hyphens with single hyphen
|
|
1935
|
-
.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
/**
|
|
1939
|
-
* Clones an element and removes selection-related attributes from the clone.
|
|
1940
|
-
* @param {HTMLElement} element - The element to clone.
|
|
1941
|
-
* @returns {HTMLElement} The sanitized clone.
|
|
1942
|
-
* @private
|
|
1943
|
-
*/
|
|
1944
|
-
private cloneAndRemoveSelectionAttributes(element: HTMLElement): HTMLElement {
|
|
1945
|
-
// Deep clone the element
|
|
1946
|
-
const clone = element.cloneNode(true) as HTMLElement
|
|
1947
|
-
|
|
1948
|
-
// Remove [hovered] and [selected] from the clone and all descendants
|
|
1949
|
-
clone.querySelectorAll('[hovered]').forEach((el) => el.removeAttribute('hovered'))
|
|
1950
|
-
clone.querySelectorAll('[selected]').forEach((el) => el.removeAttribute('selected'))
|
|
1951
|
-
// Also remove from the root element itself if present
|
|
1952
|
-
clone.removeAttribute('hovered')
|
|
1953
|
-
clone.removeAttribute('selected')
|
|
1954
|
-
|
|
1955
|
-
return clone
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
/**
|
|
1959
|
-
* Syncs the current DOM state of components to the in-memory store.
|
|
1960
|
-
* @private
|
|
1961
|
-
*/
|
|
1962
|
-
public syncDomToStoreOnly() {
|
|
1963
|
-
const pagebuilder = document.querySelector('#pagebuilder')
|
|
1964
|
-
if (!pagebuilder) return
|
|
1965
|
-
|
|
1966
|
-
const componentsToSave: { html_code: string; id: string | null; title: string }[] = []
|
|
1967
|
-
|
|
1968
|
-
pagebuilder.querySelectorAll('section[data-componentid]').forEach((section) => {
|
|
1969
|
-
const sanitizedSection = this.cloneAndRemoveSelectionAttributes(section as HTMLElement)
|
|
1970
|
-
componentsToSave.push({
|
|
1971
|
-
html_code: sanitizedSection.outerHTML,
|
|
1972
|
-
id: sanitizedSection.getAttribute('data-componentid'),
|
|
1973
|
-
title: sanitizedSection.getAttribute('data-component-title') || 'Untitled Component',
|
|
1974
|
-
})
|
|
1975
|
-
})
|
|
1976
|
-
|
|
1977
|
-
this.pageBuilderStateStore.setComponents(componentsToSave)
|
|
1978
|
-
}
|
|
1979
|
-
|
|
1980
|
-
public async generateHtmlFromComponents(): Promise<string> {
|
|
1981
|
-
this.syncDomToStoreOnly()
|
|
1982
|
-
await nextTick()
|
|
1983
|
-
|
|
1984
|
-
const components = this.pageBuilderStateStore.getComponents
|
|
1985
|
-
|
|
1986
|
-
if (!Array.isArray(components)) {
|
|
1987
|
-
return ''
|
|
1988
|
-
}
|
|
1989
|
-
|
|
1990
|
-
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
1991
|
-
await nextTick()
|
|
1992
|
-
// Attach event listeners to all editable elements in the Builder
|
|
1993
|
-
await this.addListenersToEditableElements()
|
|
1994
|
-
|
|
1995
|
-
return components
|
|
1996
|
-
.map((comp) => {
|
|
1997
|
-
return comp.html_code
|
|
1998
|
-
.replace(/data-componentid="[^"]*"/g, '') // remove data-componentid
|
|
1999
|
-
.replace(/\s{2,}/g, ' ') // optional: clean up excess spaces
|
|
2000
|
-
})
|
|
2001
|
-
.join('\n')
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
/**
|
|
2005
|
-
* Saves the current DOM state of components to local storage.
|
|
2006
|
-
* @private
|
|
2007
|
-
*/
|
|
2008
|
-
private saveDomComponentsToLocalStorage() {
|
|
2009
|
-
this.updateLocalStorageItemName()
|
|
2010
|
-
const pagebuilder = document.querySelector('#pagebuilder')
|
|
2011
|
-
if (!pagebuilder) return
|
|
2012
|
-
|
|
2013
|
-
const hoveredElement = pagebuilder.querySelector('[hovered]')
|
|
2014
|
-
if (hoveredElement) {
|
|
2015
|
-
hoveredElement.removeAttribute('hovered')
|
|
2016
|
-
}
|
|
2017
|
-
|
|
2018
|
-
const componentsToSave: { html_code: string; title: string }[] = []
|
|
2019
|
-
|
|
2020
|
-
pagebuilder.querySelectorAll('section[data-componentid]').forEach((section) => {
|
|
2021
|
-
const sanitizedSection = this.cloneAndRemoveSelectionAttributes(section as HTMLElement)
|
|
2022
|
-
|
|
2023
|
-
// Remove the data-componentid attribute
|
|
2024
|
-
sanitizedSection.removeAttribute('data-componentid')
|
|
2025
|
-
|
|
2026
|
-
componentsToSave.push({
|
|
2027
|
-
html_code: sanitizedSection.outerHTML,
|
|
2028
|
-
title: sanitizedSection.getAttribute('data-component-title') || 'Untitled Component',
|
|
2029
|
-
})
|
|
2030
|
-
})
|
|
2031
|
-
|
|
2032
|
-
const pageSettings = {
|
|
2033
|
-
classes: pagebuilder.className || '',
|
|
2034
|
-
style: pagebuilder.getAttribute('style') || (pagebuilder as HTMLElement).style.cssText || '',
|
|
2035
|
-
}
|
|
2036
|
-
|
|
2037
|
-
const dataToSave = {
|
|
2038
|
-
components: componentsToSave,
|
|
2039
|
-
pageBuilderContentSavedAt: new Date().toISOString(),
|
|
2040
|
-
pageSettings,
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
const baseKey = this.getHistoryBaseKey()
|
|
2044
|
-
|
|
2045
|
-
if (baseKey) {
|
|
2046
|
-
const currentDataRaw = localStorage.getItem(baseKey)
|
|
2047
|
-
|
|
2048
|
-
if (!currentDataRaw) {
|
|
2049
|
-
localStorage.setItem(baseKey, JSON.stringify(dataToSave))
|
|
2050
|
-
}
|
|
2051
|
-
if (currentDataRaw) {
|
|
2052
|
-
const currentData = JSON.parse(currentDataRaw)
|
|
2053
|
-
|
|
2054
|
-
// Compare components
|
|
2055
|
-
const currentComponents = currentData.components || []
|
|
2056
|
-
const newComponents = dataToSave.components || []
|
|
2057
|
-
|
|
2058
|
-
const hasChanges =
|
|
2059
|
-
newComponents.length !== currentComponents.length ||
|
|
2060
|
-
newComponents.some((newComponent, index) => {
|
|
2061
|
-
const currentComponent = currentComponents[index]
|
|
2062
|
-
return (
|
|
2063
|
-
// New component added
|
|
2064
|
-
!currentComponent ||
|
|
2065
|
-
// Component HTML changed
|
|
2066
|
-
currentComponent.html_code !== newComponent.html_code
|
|
2067
|
-
)
|
|
2068
|
-
})
|
|
2069
|
-
|
|
2070
|
-
// Compare pageSettings
|
|
2071
|
-
const hasPageSettingsChanges =
|
|
2072
|
-
(currentData.pageSettings &&
|
|
2073
|
-
currentData.pageSettings.classes !== dataToSave.pageSettings.classes) ||
|
|
2074
|
-
(currentData.pageSettings &&
|
|
2075
|
-
currentData.pageSettings.style !== dataToSave.pageSettings.style)
|
|
2076
|
-
|
|
2077
|
-
// Only save to local storage if there's a difference between the existing saved data and the current DOM data
|
|
2078
|
-
if (hasChanges || hasPageSettingsChanges) {
|
|
2079
|
-
localStorage.setItem(baseKey, JSON.stringify(dataToSave))
|
|
2080
|
-
let history = LocalStorageManager.getHistory(baseKey)
|
|
2081
|
-
|
|
2082
|
-
const lastState = history[history.length - 1]
|
|
2083
|
-
if (lastState) {
|
|
2084
|
-
const lastComponents = JSON.stringify(lastState.components)
|
|
2085
|
-
const newComponents = JSON.stringify(dataToSave.components)
|
|
2086
|
-
const lastSettings = JSON.stringify(lastState.pageSettings)
|
|
2087
|
-
const newSettings = JSON.stringify(dataToSave.pageSettings)
|
|
2088
|
-
if (lastComponents === newComponents && lastSettings === newSettings) {
|
|
2089
|
-
return // Do not save duplicate state
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
|
|
2093
|
-
if (this.pageBuilderStateStore.getHistoryIndex < history.length - 1) {
|
|
2094
|
-
history = history.slice(0, this.pageBuilderStateStore.getHistoryIndex + 1)
|
|
2095
|
-
}
|
|
2096
|
-
history.push(dataToSave)
|
|
2097
|
-
if (history.length > 10) {
|
|
2098
|
-
history = history.slice(history.length - 10)
|
|
2099
|
-
}
|
|
2100
|
-
localStorage.setItem(baseKey + '-history', JSON.stringify(history))
|
|
2101
|
-
this.pageBuilderStateStore.setHistoryIndex(history.length - 1)
|
|
2102
|
-
this.pageBuilderStateStore.setHistoryLength(history.length)
|
|
2103
|
-
return
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
|
-
/**
|
|
2109
|
-
* Removes the current page's components from local storage.
|
|
2110
|
-
* @private
|
|
2111
|
-
*/
|
|
2112
|
-
private async removeCurrentComponentsFromLocalStorage() {
|
|
2113
|
-
this.updateLocalStorageItemName()
|
|
2114
|
-
await nextTick()
|
|
2115
|
-
|
|
2116
|
-
const key = this.getLocalStorageItemName.value
|
|
2117
|
-
if (key) {
|
|
2118
|
-
localStorage.removeItem(key)
|
|
2119
|
-
}
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2122
|
-
/**
|
|
2123
|
-
* Handles the form submission process, clearing local storage and the DOM.
|
|
2124
|
-
* @returns {Promise<void>}
|
|
2125
|
-
*/
|
|
2126
|
-
public async handleFormSubmission() {
|
|
2127
|
-
await this.removeCurrentComponentsFromLocalStorage()
|
|
2128
|
-
this.deleteAllComponentsFromDOM()
|
|
2129
|
-
this.pageBuilderStateStore.setComponents([])
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
/**
|
|
2133
|
-
* Parses a CSS style string into a key-value object.
|
|
2134
|
-
* @param {string} style - The style string to parse.
|
|
2135
|
-
* @returns {Record<string, string>} The parsed style object.
|
|
2136
|
-
* @private
|
|
2137
|
-
*/
|
|
2138
|
-
private parseStyleString(style: string): Record<string, string> {
|
|
2139
|
-
return style
|
|
2140
|
-
.split(';')
|
|
2141
|
-
.map((s) => s.trim())
|
|
2142
|
-
.filter(Boolean)
|
|
2143
|
-
.reduce(
|
|
2144
|
-
(acc, rule) => {
|
|
2145
|
-
const [key, value] = rule.split(':').map((str) => str.trim())
|
|
2146
|
-
if (key && value) acc[key] = value
|
|
2147
|
-
return acc
|
|
2148
|
-
},
|
|
2149
|
-
{} as Record<string, string>,
|
|
2150
|
-
)
|
|
2151
|
-
}
|
|
2152
|
-
|
|
2153
|
-
/**
|
|
2154
|
-
* Deletes old page builder data from local storage (older than 2 weeks).
|
|
2155
|
-
*/
|
|
2156
|
-
deleteOldPageBuilderLocalStorage(): void {
|
|
2157
|
-
const config = this.pageBuilderStateStore.getPageBuilderConfig
|
|
2158
|
-
const formType = config && config.updateOrCreate && config.updateOrCreate.formType
|
|
2159
|
-
|
|
2160
|
-
if (formType === 'update') {
|
|
2161
|
-
let oldCountLocalStorages = 0
|
|
2162
|
-
const deletedItemsLog: { Number: number; Key: string; SavedAt: string }[] = []
|
|
2163
|
-
|
|
2164
|
-
// const pastTime = new Date(Date.now() - 1 * 60 * 1000) // 1 minute
|
|
2165
|
-
const pastTime = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000) // 2 weeks
|
|
2166
|
-
|
|
2167
|
-
for (let i = 0; i < localStorage.length; i++) {
|
|
2168
|
-
const key = localStorage.key(i)
|
|
2169
|
-
|
|
2170
|
-
if (!key) continue
|
|
2171
|
-
if (!key.startsWith('page-builder-update-resource-')) continue
|
|
2172
|
-
|
|
2173
|
-
try {
|
|
2174
|
-
const storeComponents = localStorage.getItem(key)
|
|
2175
|
-
if (!storeComponents) continue
|
|
2176
|
-
|
|
2177
|
-
const storeComponentsParsed = JSON.parse(storeComponents)
|
|
2178
|
-
const savedAt = storeComponentsParsed.pageBuilderContentSavedAt
|
|
2179
|
-
if (savedAt) {
|
|
2180
|
-
const savedAtDate = new Date(savedAt)
|
|
2181
|
-
|
|
2182
|
-
if (savedAtDate < pastTime) {
|
|
2183
|
-
oldCountLocalStorages++
|
|
2184
|
-
deletedItemsLog.push({
|
|
2185
|
-
Number: oldCountLocalStorages,
|
|
2186
|
-
Key: key,
|
|
2187
|
-
SavedAt: savedAt,
|
|
2188
|
-
})
|
|
2189
|
-
|
|
2190
|
-
// Delete old items
|
|
2191
|
-
localStorage.removeItem(key)
|
|
2192
|
-
}
|
|
2193
|
-
}
|
|
2194
|
-
} catch {
|
|
2195
|
-
// Ignore parse errors for unrelated keys
|
|
2196
|
-
}
|
|
2197
|
-
}
|
|
2198
|
-
|
|
2199
|
-
if (deletedItemsLog.length > 0) {
|
|
2200
|
-
console.info(
|
|
2201
|
-
`Deleted ${deletedItemsLog.length} localStorage item(s) older than ${pastTime} days:`,
|
|
2202
|
-
)
|
|
2203
|
-
console.table(deletedItemsLog)
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
|
-
|
|
2208
|
-
/**
|
|
2209
|
-
* Sets a flag to indicate that the user has started editing.
|
|
2210
|
-
*/
|
|
2211
|
-
public startEditing() {
|
|
2212
|
-
this.hasStartedEditing = true
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
/**
|
|
2216
|
-
* Resumes editing from a draft saved in local storage.
|
|
2217
|
-
* @returns {Promise<void>}
|
|
2218
|
-
*/
|
|
2219
|
-
public async resumeEditingFromDraft() {
|
|
2220
|
-
this.updateLocalStorageItemName()
|
|
2221
|
-
|
|
2222
|
-
const localStorageData = this.getSavedPageHtml()
|
|
2223
|
-
|
|
2224
|
-
if (localStorageData) {
|
|
2225
|
-
await delay(400)
|
|
2226
|
-
this.pageBuilderStateStore.setIsLoadingResumeEditing(true)
|
|
2227
|
-
await this.mountComponentsToDOM(localStorageData)
|
|
2228
|
-
this.pageBuilderStateStore.setIsLoadingResumeEditing(false)
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
2232
|
-
await nextTick()
|
|
2233
|
-
// Attach event listeners to all editable elements in the Builder
|
|
2234
|
-
await this.addListenersToEditableElements()
|
|
2235
|
-
// set loading to false
|
|
2236
|
-
this.pageBuilderStateStore.setIsLoadingResumeEditing(false)
|
|
2237
|
-
}
|
|
2238
|
-
|
|
2239
|
-
/**
|
|
2240
|
-
* Restores the original content that was loaded when the builder started.
|
|
2241
|
-
* @returns {Promise<void>}
|
|
2242
|
-
*/
|
|
2243
|
-
public async restoreOriginalContent() {
|
|
2244
|
-
this.updateLocalStorageItemName()
|
|
2245
|
-
|
|
2246
|
-
this.pageBuilderStateStore.setIsRestoring(true)
|
|
2247
|
-
await delay(400)
|
|
2248
|
-
|
|
2249
|
-
// Restore the original content if available
|
|
2250
|
-
if (Array.isArray(this.originalComponents)) {
|
|
2251
|
-
await this.clearClassesFromPage()
|
|
2252
|
-
await this.clearInlineStylesFromPage()
|
|
2253
|
-
const htmlString = this.renderComponentsToHtml(this.originalComponents)
|
|
2254
|
-
await this.mountComponentsToDOM(htmlString)
|
|
2255
|
-
this.removeCurrentComponentsFromLocalStorage()
|
|
2256
|
-
}
|
|
2257
|
-
|
|
2258
|
-
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
2259
|
-
await nextTick()
|
|
2260
|
-
// Attach event listeners to all editable elements in the Builder
|
|
2261
|
-
await this.addListenersToEditableElements()
|
|
2262
|
-
|
|
2263
|
-
this.pageBuilderStateStore.setIsRestoring(false)
|
|
2264
|
-
}
|
|
2265
|
-
|
|
2266
|
-
public async returnLatestComponents() {
|
|
2267
|
-
this.syncDomToStoreOnly()
|
|
2268
|
-
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
2269
|
-
await nextTick()
|
|
2270
|
-
// Attach event listeners to all editable elements in the Builder
|
|
2271
|
-
await this.addListenersToEditableElements()
|
|
2272
|
-
|
|
2273
|
-
return this.pageBuilderStateStore.getComponents
|
|
2274
|
-
}
|
|
2275
|
-
/**
|
|
2276
|
-
* Gets the local storage key for the current resource.
|
|
2277
|
-
* @returns {string | null} The local storage key.
|
|
2278
|
-
*/
|
|
2279
|
-
public getStorageItemNameForResource(): string | null {
|
|
2280
|
-
return this.getLocalStorageItemName.value
|
|
2281
|
-
}
|
|
2282
|
-
|
|
2283
|
-
/**
|
|
2284
|
-
* Retrieves the saved page HTML from local storage.
|
|
2285
|
-
* @returns {string | false} The HTML string or false if not found.
|
|
2286
|
-
*/
|
|
2287
|
-
public getSavedPageHtml() {
|
|
2288
|
-
if (!this.getLocalStorageItemName.value) return false
|
|
2289
|
-
|
|
2290
|
-
const key = this.getLocalStorageItemName.value
|
|
2291
|
-
if (!key) return false
|
|
2292
|
-
|
|
2293
|
-
const raw = localStorage.getItem(key)
|
|
2294
|
-
if (!raw) return false
|
|
2295
|
-
|
|
2296
|
-
const parsed = JSON.parse(raw)
|
|
2297
|
-
|
|
2298
|
-
// Object with components and pageSettings
|
|
2299
|
-
if (parsed && Array.isArray(parsed.components)) {
|
|
2300
|
-
const classes = (parsed.pageSettings && parsed.pageSettings.classes) || ''
|
|
2301
|
-
const style = (parsed.pageSettings && parsed.pageSettings.style) || ''
|
|
2302
|
-
|
|
2303
|
-
const sectionsHtml = parsed.components
|
|
2304
|
-
.map((c: ComponentObject) => {
|
|
2305
|
-
const parser = new DOMParser()
|
|
2306
|
-
const doc = parser.parseFromString(c.html_code, 'text/html')
|
|
2307
|
-
const section = doc.querySelector('section')
|
|
2308
|
-
|
|
2309
|
-
if (section) {
|
|
2310
|
-
section.removeAttribute('data-componentid') // Remove the data-componentid attribute
|
|
2311
|
-
return section.outerHTML
|
|
2312
|
-
}
|
|
2313
|
-
|
|
2314
|
-
return c.html_code // Fallback in case section is not found
|
|
2315
|
-
})
|
|
2316
|
-
.join('\n')
|
|
2317
|
-
|
|
2318
|
-
return `<div id="pagebuilder" class="${classes}" style="${style}">\n${sectionsHtml}\n</div>`
|
|
2319
|
-
}
|
|
2320
|
-
|
|
2321
|
-
return false
|
|
2322
|
-
}
|
|
2323
|
-
|
|
2324
|
-
/**
|
|
2325
|
-
* Applies a selected image to the current element.
|
|
2326
|
-
* @param {ImageObject} image - The image object to apply.
|
|
2327
|
-
* @returns {Promise<void>}
|
|
2328
|
-
*/
|
|
2329
|
-
public async applySelectedImage(image: ImageObject): Promise<void> {
|
|
2330
|
-
this.pageBuilderStateStore.setApplyImageToSelection(image)
|
|
2331
|
-
|
|
2332
|
-
if (!this.getElement.value) return
|
|
2333
|
-
|
|
2334
|
-
// Only apply if an image is staged
|
|
2335
|
-
if (this.getApplyImageToSelection.value && this.getApplyImageToSelection.value.src) {
|
|
2336
|
-
await nextTick()
|
|
2337
|
-
this.pageBuilderStateStore.setBasePrimaryImage(`${this.getApplyImageToSelection.value.src}`)
|
|
2338
|
-
|
|
2339
|
-
await this.handleAutoSave()
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
|
|
2343
|
-
/**
|
|
2344
|
-
* Sets the base primary image from the currently selected element if it's an image.
|
|
2345
|
-
* @private
|
|
2346
|
-
*/
|
|
2347
|
-
private setBasePrimaryImageFromSelectedElement() {
|
|
2348
|
-
if (!this.getElement.value) return
|
|
2349
|
-
|
|
2350
|
-
const currentImageContainer = document.createElement('div')
|
|
2351
|
-
currentImageContainer.innerHTML = this.getElement.value.outerHTML
|
|
2352
|
-
|
|
2353
|
-
// Get all img and div within the current image container
|
|
2354
|
-
const imgElements = currentImageContainer.getElementsByTagName('img')
|
|
2355
|
-
const divElements = currentImageContainer.getElementsByTagName('div')
|
|
2356
|
-
|
|
2357
|
-
// If exactly one img and no div, set as base primary image
|
|
2358
|
-
if (imgElements.length === 1 && divElements.length === 0) {
|
|
2359
|
-
this.pageBuilderStateStore.setBasePrimaryImage(imgElements[0].src)
|
|
2360
|
-
return
|
|
2361
|
-
}
|
|
2362
|
-
|
|
2363
|
-
// Otherwise, clear the base primary image
|
|
2364
|
-
this.pageBuilderStateStore.setBasePrimaryImage(null)
|
|
2365
|
-
}
|
|
2366
|
-
|
|
2367
|
-
/**
|
|
2368
|
-
* Adds or removes a hyperlink from the selected element.
|
|
2369
|
-
* @param {boolean} hyperlinkEnable - Whether to enable or disable the hyperlink.
|
|
2370
|
-
* @param {string | null} urlInput - The URL for the hyperlink.
|
|
2371
|
-
* @param {boolean} openHyperlinkInNewTab - Whether the link should open in a new tab.
|
|
2372
|
-
* @private
|
|
2373
|
-
*/
|
|
2374
|
-
private addHyperlinkToElement(
|
|
2375
|
-
hyperlinkEnable: boolean,
|
|
2376
|
-
urlInput: string | null,
|
|
2377
|
-
openHyperlinkInNewTab: boolean,
|
|
2378
|
-
) {
|
|
2379
|
-
if (!this.getElement.value) return
|
|
2380
|
-
|
|
2381
|
-
// Check if element is a proper DOM element and has closest method
|
|
2382
|
-
if (
|
|
2383
|
-
!(this.getElement.value instanceof HTMLElement) ||
|
|
2384
|
-
typeof this.getElement.value.closest !== 'function'
|
|
2385
|
-
)
|
|
2386
|
-
return
|
|
2387
|
-
|
|
2388
|
-
const parentHyperlink = this.getElement.value.closest('a')
|
|
2389
|
-
const hyperlink = this.getElement.value.querySelector('a')
|
|
2390
|
-
|
|
2391
|
-
this.pageBuilderStateStore.setHyperlinkError(null)
|
|
2392
|
-
|
|
2393
|
-
// url validation
|
|
2394
|
-
const urlRegex = /^https?:\/\//
|
|
2395
|
-
|
|
2396
|
-
const isValidURL = ref(true)
|
|
2397
|
-
|
|
2398
|
-
if (hyperlinkEnable === true && urlInput !== null) {
|
|
2399
|
-
isValidURL.value = urlRegex.test(urlInput)
|
|
2400
|
-
}
|
|
2401
|
-
|
|
2402
|
-
if (isValidURL.value === false) {
|
|
2403
|
-
this.pageBuilderStateStore.setHyperlinkMessage(null)
|
|
2404
|
-
|
|
2405
|
-
this.pageBuilderStateStore.setHyperlinkError('URL is not valid')
|
|
2406
|
-
return
|
|
2407
|
-
}
|
|
2408
|
-
|
|
2409
|
-
if (hyperlinkEnable === true && typeof urlInput === 'string') {
|
|
2410
|
-
// check if element contains child hyperlink tag
|
|
2411
|
-
// updated existing url
|
|
2412
|
-
if (hyperlink !== null && urlInput.length !== 0) {
|
|
2413
|
-
hyperlink.href = urlInput
|
|
2414
|
-
|
|
2415
|
-
// Conditionally set the target attribute if openHyperlinkInNewTab is true
|
|
2416
|
-
if (openHyperlinkInNewTab === true) {
|
|
2417
|
-
hyperlink.target = '_blank'
|
|
2418
|
-
}
|
|
2419
|
-
if (openHyperlinkInNewTab === false) {
|
|
2420
|
-
hyperlink.removeAttribute('target')
|
|
2421
|
-
}
|
|
2422
|
-
|
|
2423
|
-
hyperlink.textContent = this.getElement.value.textContent
|
|
2424
|
-
|
|
2425
|
-
this.pageBuilderStateStore.setHyperlinkMessage('Succesfully updated element hyperlink')
|
|
2426
|
-
|
|
2427
|
-
this.pageBuilderStateStore.setElementContainsHyperlink(true)
|
|
2428
|
-
|
|
2429
|
-
return
|
|
2430
|
-
}
|
|
2431
|
-
|
|
2432
|
-
// check if element contains child a tag
|
|
2433
|
-
if (hyperlink === null && urlInput.length !== 0) {
|
|
2434
|
-
// add a href
|
|
2435
|
-
if (parentHyperlink === null) {
|
|
2436
|
-
const link = document.createElement('a')
|
|
2437
|
-
link.href = urlInput
|
|
2438
|
-
|
|
2439
|
-
// Conditionally set the target attribute if openHyperlinkInNewTab is true
|
|
2440
|
-
if (openHyperlinkInNewTab === true) {
|
|
2441
|
-
link.target = '_blank'
|
|
2442
|
-
}
|
|
2443
|
-
|
|
2444
|
-
link.textContent = this.getElement.value.textContent
|
|
2445
|
-
this.getElement.value.textContent = ''
|
|
2446
|
-
this.getElement.value.appendChild(link)
|
|
2447
|
-
|
|
2448
|
-
this.pageBuilderStateStore.setHyperlinkMessage('Successfully added hyperlink to element')
|
|
2449
|
-
|
|
2450
|
-
this.pageBuilderStateStore.setElementContainsHyperlink(true)
|
|
2451
|
-
|
|
2452
|
-
return
|
|
2453
|
-
}
|
|
2454
|
-
}
|
|
2455
|
-
//
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
if (hyperlinkEnable === false && urlInput === 'removeHyperlink') {
|
|
2459
|
-
// To remove the added hyperlink tag
|
|
2460
|
-
const originalText = this.getElement.value.textContent || ''
|
|
2461
|
-
const textNode = document.createTextNode(originalText)
|
|
2462
|
-
this.getElement.value.textContent = ''
|
|
2463
|
-
this.getElement.value.appendChild(textNode)
|
|
2464
|
-
|
|
2465
|
-
this.pageBuilderStateStore.setHyberlinkEnable(false)
|
|
2466
|
-
this.pageBuilderStateStore.setElementContainsHyperlink(false)
|
|
2467
|
-
}
|
|
2468
|
-
}
|
|
2469
|
-
|
|
2470
|
-
/**
|
|
2471
|
-
* Checks if the selected element contains a hyperlink and updates the state accordingly.
|
|
2472
|
-
* @private
|
|
2473
|
-
*/
|
|
2474
|
-
private checkForHyperlink() {
|
|
2475
|
-
if (!this.getElement.value) return
|
|
2476
|
-
|
|
2477
|
-
const hyperlink = this.getElement.value.querySelector('a')
|
|
2478
|
-
if (hyperlink !== null) {
|
|
2479
|
-
this.pageBuilderStateStore.setHyberlinkEnable(true)
|
|
2480
|
-
this.pageBuilderStateStore.setElementContainsHyperlink(true)
|
|
2481
|
-
this.pageBuilderStateStore.setHyperlinkInput(hyperlink.href)
|
|
2482
|
-
this.pageBuilderStateStore.setHyperlinkMessage(null)
|
|
2483
|
-
this.pageBuilderStateStore.setHyperlinkError(null)
|
|
2484
|
-
|
|
2485
|
-
if (hyperlink.target === '_blank') {
|
|
2486
|
-
this.pageBuilderStateStore.setOpenHyperlinkInNewTab(true)
|
|
2487
|
-
}
|
|
2488
|
-
if (hyperlink.target !== '_blank') {
|
|
2489
|
-
this.pageBuilderStateStore.setOpenHyperlinkInNewTab(false)
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
|
-
return
|
|
2493
|
-
}
|
|
2494
|
-
|
|
2495
|
-
this.pageBuilderStateStore.setElementContainsHyperlink(false)
|
|
2496
|
-
this.pageBuilderStateStore.setHyperlinkInput('')
|
|
2497
|
-
this.pageBuilderStateStore.setHyperlinkError(null)
|
|
2498
|
-
this.pageBuilderStateStore.setHyperlinkMessage(null)
|
|
2499
|
-
this.pageBuilderStateStore.setHyberlinkEnable(false)
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
/**
|
|
2503
|
-
* Handles all hyperlink-related actions for the selected element.
|
|
2504
|
-
* @param {boolean} [hyperlinkEnable] - Whether to enable or disable the hyperlink.
|
|
2505
|
-
* @param {string | null} [urlInput] - The URL for the hyperlink.
|
|
2506
|
-
* @param {boolean} [openHyperlinkInNewTab] - Whether the link should open in a new tab.
|
|
2507
|
-
*/
|
|
2508
|
-
public handleHyperlink(
|
|
2509
|
-
hyperlinkEnable?: boolean,
|
|
2510
|
-
urlInput?: string | null,
|
|
2511
|
-
openHyperlinkInNewTab?: boolean,
|
|
2512
|
-
): void {
|
|
2513
|
-
this.pageBuilderStateStore.setHyperlinkAbility(true)
|
|
2514
|
-
|
|
2515
|
-
if (!this.getElement.value) return
|
|
2516
|
-
|
|
2517
|
-
// Check if element is a proper DOM element and has closest method
|
|
2518
|
-
if (
|
|
2519
|
-
!(this.getElement.value instanceof HTMLElement) ||
|
|
2520
|
-
typeof this.getElement.value.closest !== 'function'
|
|
2521
|
-
)
|
|
2522
|
-
return
|
|
2523
|
-
|
|
2524
|
-
const parentHyperlink = this.getElement.value.closest('a')
|
|
2525
|
-
|
|
2526
|
-
// handle case where parent element already has an a href tag
|
|
2527
|
-
// when clicking directly on a hyperlink
|
|
2528
|
-
if (parentHyperlink !== null) {
|
|
2529
|
-
this.pageBuilderStateStore.setHyperlinkAbility(false)
|
|
2530
|
-
}
|
|
2531
|
-
const elementTag = this.getElement.value?.tagName.toUpperCase()
|
|
2532
|
-
|
|
2533
|
-
if (
|
|
2534
|
-
elementTag !== 'P' &&
|
|
2535
|
-
elementTag !== 'H1' &&
|
|
2536
|
-
elementTag !== 'H2' &&
|
|
2537
|
-
elementTag !== 'H3' &&
|
|
2538
|
-
elementTag !== 'H4' &&
|
|
2539
|
-
elementTag !== 'H5' &&
|
|
2540
|
-
elementTag !== 'H6'
|
|
2541
|
-
) {
|
|
2542
|
-
this.pageBuilderStateStore.setHyperlinkAbility(false)
|
|
2543
|
-
}
|
|
2544
|
-
|
|
2545
|
-
if (hyperlinkEnable === undefined) {
|
|
2546
|
-
this.checkForHyperlink()
|
|
2547
|
-
return
|
|
2548
|
-
}
|
|
2549
|
-
|
|
2550
|
-
this.addHyperlinkToElement(hyperlinkEnable, urlInput || null, openHyperlinkInNewTab || false)
|
|
2551
|
-
}
|
|
2552
|
-
|
|
2553
|
-
public async addTheme(components: string): Promise<void> {
|
|
2554
|
-
if (components) {
|
|
2555
|
-
this.validateMountingHTML(components)
|
|
2556
|
-
await this.mountComponentsToDOM(components)
|
|
2557
|
-
}
|
|
2558
|
-
await this.handleAutoSave()
|
|
2559
|
-
}
|
|
2560
|
-
|
|
2561
|
-
/**
|
|
2562
|
-
* Adds a new component to the page builder.
|
|
2563
|
-
* @param {ComponentObject} componentObject - The component to add.
|
|
2564
|
-
* @returns {Promise<void>}
|
|
2565
|
-
*/
|
|
2566
|
-
public async addComponent(componentObject: ComponentObject): Promise<void> {
|
|
2567
|
-
try {
|
|
2568
|
-
const clonedComponent = this.cloneCompObjForDOMInsertion({
|
|
2569
|
-
html_code: componentObject.html_code,
|
|
2570
|
-
id: componentObject.id,
|
|
2571
|
-
title: componentObject.title,
|
|
2572
|
-
})
|
|
2573
|
-
|
|
2574
|
-
this.pageBuilderStateStore.setPushComponents({
|
|
2575
|
-
component: clonedComponent,
|
|
2576
|
-
componentArrayAddMethod: this.getComponentArrayAddMethod.value
|
|
2577
|
-
? this.getComponentArrayAddMethod.value
|
|
2578
|
-
: 'push',
|
|
2579
|
-
})
|
|
2580
|
-
|
|
2581
|
-
const pageBuilderWrapper = document.querySelector('#page-builder-wrapper')
|
|
2582
|
-
// scoll to top or bottom
|
|
2583
|
-
if (pageBuilderWrapper) {
|
|
2584
|
-
// push to bottom
|
|
2585
|
-
if (this.getComponentArrayAddMethod.value === 'push') {
|
|
2586
|
-
pageBuilderWrapper.scrollTo({
|
|
2587
|
-
top: pageBuilderWrapper.scrollHeight + 50,
|
|
2588
|
-
behavior: 'smooth',
|
|
2589
|
-
})
|
|
2590
|
-
}
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
|
-
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
2594
|
-
await nextTick()
|
|
2595
|
-
// Attach event listeners to all editable elements in the Builder
|
|
2596
|
-
await this.addListenersToEditableElements()
|
|
2597
|
-
|
|
2598
|
-
await this.handleAutoSave()
|
|
2599
|
-
} catch (error) {
|
|
2600
|
-
console.error('Error adding component:', error)
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
|
|
2604
|
-
/**
|
|
2605
|
-
* Adds a prefix to Tailwind CSS classes in a string.
|
|
2606
|
-
* @param {string} classList - The string of classes.
|
|
2607
|
-
* @param {string} [prefix='pbx-'] - The prefix to add.
|
|
2608
|
-
* @returns {string} The prefixed class string.
|
|
2609
|
-
* @private
|
|
2610
|
-
*/
|
|
2611
|
-
private addTailwindPrefixToClasses(classList: string, prefix = 'pbx-'): string {
|
|
2612
|
-
return classList
|
|
2613
|
-
.split(/\s+/)
|
|
2614
|
-
.map((cls) => {
|
|
2615
|
-
if (!cls || cls.startsWith(prefix)) return cls
|
|
2616
|
-
const parts = cls.split(':')
|
|
2617
|
-
const base = parts.pop()!
|
|
2618
|
-
if (base.startsWith(prefix)) return cls
|
|
2619
|
-
// Always prefix if not already prefixed
|
|
2620
|
-
return [...parts, prefix + base].join(':')
|
|
2621
|
-
})
|
|
2622
|
-
.join(' ')
|
|
2623
|
-
}
|
|
2624
|
-
|
|
2625
|
-
/**
|
|
2626
|
-
* Converts a style object to a CSS string.
|
|
2627
|
-
* @param {string | Record<string, string> | null | undefined} styleObj - The style object.
|
|
2628
|
-
* @returns {string} The CSS style string.
|
|
2629
|
-
* @private
|
|
2630
|
-
*/
|
|
2631
|
-
private convertStyleObjectToString(
|
|
2632
|
-
styleObj: string | Record<string, string> | null | undefined,
|
|
2633
|
-
): string {
|
|
2634
|
-
if (!styleObj) return ''
|
|
2635
|
-
if (typeof styleObj === 'string') return styleObj
|
|
2636
|
-
|
|
2637
|
-
return Object.entries(styleObj)
|
|
2638
|
-
.map(([key, value]) => {
|
|
2639
|
-
const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
2640
|
-
return `${kebabKey}: ${value};`
|
|
2641
|
-
})
|
|
2642
|
-
.join(' ')
|
|
2643
|
-
}
|
|
2644
|
-
|
|
2645
|
-
/**
|
|
2646
|
-
* Parses a string of HTML and extracts builder components and global page settings.
|
|
2647
|
-
* - 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).
|
|
2648
|
-
* - **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.
|
|
2649
|
-
* - 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.
|
|
2650
|
-
*
|
|
2651
|
-
* Why only HTML?
|
|
2652
|
-
* - It enforces a single source of truth for builder state (HTML).
|
|
2653
|
-
* - It prevents misuse (e.g., passing JSON to a DOM parser, which is always a bug).
|
|
2654
|
-
* - It makes your documentation and support much simpler.
|
|
2655
|
-
*
|
|
2656
|
-
* @param htmlString - The HTML string to parse (must contain `<section>...</section>` elements, not JSON).
|
|
2657
|
-
* @returns An object with `components` (array of builder components) and `pageSettings` (global styles for the page).
|
|
2658
|
-
*/
|
|
2659
|
-
public parsePageBuilderHTML(htmlString: string): {
|
|
2660
|
-
components: ComponentObject[]
|
|
2661
|
-
pageSettings: PageSettings
|
|
2662
|
-
} {
|
|
2663
|
-
const parser = new DOMParser()
|
|
2664
|
-
const doc = parser.parseFromString(htmlString, 'text/html')
|
|
2665
|
-
|
|
2666
|
-
// Prefix all classes in the document
|
|
2667
|
-
doc.querySelectorAll('[class]').forEach((element) => {
|
|
2668
|
-
const currentClasses = element.getAttribute('class') || ''
|
|
2669
|
-
const prefixedClasses = this.addTailwindPrefixToClasses(currentClasses)
|
|
2670
|
-
element.setAttribute('class', prefixedClasses)
|
|
2671
|
-
})
|
|
2672
|
-
|
|
2673
|
-
const pagebuilderDiv = doc.querySelector('#pagebuilder')
|
|
2674
|
-
let pageSettings: PageSettings = {
|
|
2675
|
-
classes: '',
|
|
2676
|
-
style: {},
|
|
2677
|
-
}
|
|
2678
|
-
|
|
2679
|
-
if (pagebuilderDiv) {
|
|
2680
|
-
const rawStyle = pagebuilderDiv.getAttribute('style') || ''
|
|
2681
|
-
pageSettings = {
|
|
2682
|
-
classes: pagebuilderDiv.className || '',
|
|
2683
|
-
style: this.parseStyleString(rawStyle),
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
|
-
|
|
2687
|
-
// Always assign sectionNodes before use
|
|
2688
|
-
let sectionNodes: NodeListOf<HTMLElement> = doc.querySelectorAll('section')
|
|
2689
|
-
if (pagebuilderDiv) {
|
|
2690
|
-
sectionNodes = pagebuilderDiv.querySelectorAll('section')
|
|
2691
|
-
}
|
|
2692
|
-
|
|
2693
|
-
// Only use top-level (non-nested) sections as components
|
|
2694
|
-
const topLevelSections = Array.from(sectionNodes).filter(
|
|
2695
|
-
(section) =>
|
|
2696
|
-
!section.parentElement || section.parentElement.tagName.toLowerCase() !== 'section',
|
|
2697
|
-
)
|
|
2698
|
-
|
|
2699
|
-
let components: ComponentObject[] = []
|
|
2700
|
-
|
|
2701
|
-
if (topLevelSections.length > 0) {
|
|
2702
|
-
components = topLevelSections.map((section) => ({
|
|
2703
|
-
id: null,
|
|
2704
|
-
html_code: section.outerHTML.trim(),
|
|
2705
|
-
title: section.getAttribute('data-component-title') || 'Untitled Component',
|
|
2706
|
-
}))
|
|
2707
|
-
}
|
|
2708
|
-
if (topLevelSections.length === 0) {
|
|
2709
|
-
// No <section> found: treat each first-level child as a component, wrapped in a section
|
|
2710
|
-
const parent = pagebuilderDiv || doc.body
|
|
2711
|
-
const children = Array.from(parent.children)
|
|
2712
|
-
if (children.length > 0) {
|
|
2713
|
-
components = children.map((child) => {
|
|
2714
|
-
// Wrap in a section with data-componentid and data-component-title
|
|
2715
|
-
const section = doc.createElement('section')
|
|
2716
|
-
section.setAttribute('data-component-title', 'Untitled Component')
|
|
2717
|
-
// Optionally: generate a uuid for data-componentid if needed
|
|
2718
|
-
// section.setAttribute('data-componentid', uuidv4())
|
|
2719
|
-
section.innerHTML = child.outerHTML.trim()
|
|
2720
|
-
return {
|
|
2721
|
-
id: null,
|
|
2722
|
-
html_code: section.outerHTML.trim(),
|
|
2723
|
-
title: 'Untitled Component',
|
|
2724
|
-
}
|
|
2725
|
-
})
|
|
2726
|
-
}
|
|
2727
|
-
if (children.length === 0) {
|
|
2728
|
-
// No children: wrap the entire content in a <section> as a single component
|
|
2729
|
-
const section = doc.createElement('section')
|
|
2730
|
-
section.setAttribute('data-component-title', 'Untitled Component')
|
|
2731
|
-
section.innerHTML = parent.innerHTML.trim()
|
|
2732
|
-
components = [
|
|
2733
|
-
{
|
|
2734
|
-
id: null,
|
|
2735
|
-
html_code: section.outerHTML.trim(),
|
|
2736
|
-
title: 'Untitled Component',
|
|
2737
|
-
},
|
|
2738
|
-
]
|
|
2739
|
-
}
|
|
2740
|
-
}
|
|
2741
|
-
|
|
2742
|
-
return {
|
|
2743
|
-
components,
|
|
2744
|
-
pageSettings,
|
|
2745
|
-
}
|
|
2746
|
-
}
|
|
2747
|
-
|
|
2748
|
-
/**
|
|
2749
|
-
* Applies modified components by mounting them to the DOM and attaching listeners.
|
|
2750
|
-
* @param htmlString - The HTML string to apply
|
|
2751
|
-
* @returns {Promise<string | null>} - Returns error message if failed, otherwise null
|
|
2752
|
-
*/
|
|
2753
|
-
public async applyModifiedHTML(htmlString: string): Promise<string | null> {
|
|
2754
|
-
if (!htmlString || (typeof htmlString === 'string' && htmlString.length === 0)) {
|
|
2755
|
-
return 'No HTML content was provided. Please ensure a valid HTML string is passed.'
|
|
2756
|
-
}
|
|
2757
|
-
|
|
2758
|
-
// Check if the htmlString contains any <section> tags
|
|
2759
|
-
if (/<section[\s>]/i.test(htmlString)) {
|
|
2760
|
-
return 'Error: The <section> tag cannot be used as it is already included inside this component.'
|
|
2761
|
-
}
|
|
2762
|
-
|
|
2763
|
-
const tempDiv = document.createElement('div')
|
|
2764
|
-
tempDiv.innerHTML = htmlString.trim()
|
|
2765
|
-
|
|
2766
|
-
const parsedElement = tempDiv.firstElementChild as HTMLElement | null
|
|
2767
|
-
|
|
2768
|
-
if (!parsedElement) {
|
|
2769
|
-
return 'Could not parse element from HTML string.'
|
|
2770
|
-
}
|
|
2771
|
-
|
|
2772
|
-
// Replace the actual DOM element
|
|
2773
|
-
const oldElement = this.pageBuilderStateStore.getElement
|
|
2774
|
-
|
|
2775
|
-
if (oldElement && oldElement.parentElement) {
|
|
2776
|
-
oldElement.replaceWith(parsedElement)
|
|
2777
|
-
|
|
2778
|
-
// Update the element in the store (now referencing the new one)
|
|
2779
|
-
this.pageBuilderStateStore.setElement(parsedElement)
|
|
2780
|
-
}
|
|
2781
|
-
|
|
2782
|
-
await this.addListenersToEditableElements()
|
|
2783
|
-
await nextTick()
|
|
2784
|
-
return null
|
|
2785
|
-
}
|
|
2786
|
-
|
|
2787
|
-
private validateMountingHTML(
|
|
2788
|
-
htmlString: string,
|
|
2789
|
-
options?: { logError?: boolean },
|
|
2790
|
-
): string | null {
|
|
2791
|
-
// Trim HTML string
|
|
2792
|
-
const trimmedData = htmlString.trim()
|
|
2793
|
-
const openingSectionMatches = htmlString.match(/<section\b[^>]*>/gi) || []
|
|
2794
|
-
const closingSectionMatches = htmlString.match(/<\/section>/gi) || []
|
|
2795
|
-
|
|
2796
|
-
if (!htmlString || htmlString.trim().length === 0) {
|
|
2797
|
-
const error = 'No HTML content was provided. Please ensure a valid HTML string is passed.'
|
|
2798
|
-
if (options && options.logError) {
|
|
2799
|
-
console.error(error)
|
|
2800
|
-
// Behavior
|
|
2801
|
-
return error
|
|
2802
|
-
}
|
|
2803
|
-
// default behavior
|
|
2804
|
-
return error
|
|
2805
|
-
}
|
|
2806
|
-
|
|
2807
|
-
if (openingSectionMatches.length !== closingSectionMatches.length) {
|
|
2808
|
-
const error =
|
|
2809
|
-
'Uneven <section> tags detected in the provided HTML. Each component must be wrapped in its own properly paired <section>...</section>. ' +
|
|
2810
|
-
'Ensure that all <section> tags have a matching closing </section> tag.'
|
|
2811
|
-
|
|
2812
|
-
if (options && options.logError) {
|
|
2813
|
-
console.error(error)
|
|
2814
|
-
return error
|
|
2815
|
-
}
|
|
2816
|
-
|
|
2817
|
-
return error
|
|
2818
|
-
}
|
|
2819
|
-
|
|
2820
|
-
const tempDiv = document.createElement('div')
|
|
2821
|
-
tempDiv.innerHTML = trimmedData
|
|
2822
|
-
const nestedSection = tempDiv.querySelector('section section')
|
|
2823
|
-
if (nestedSection) {
|
|
2824
|
-
const error =
|
|
2825
|
-
'Nested <section> tags are not allowed. Please ensure that no <section> is placed inside another <section>.'
|
|
2826
|
-
if (options && options.logError) {
|
|
2827
|
-
console.error(error)
|
|
2828
|
-
return error
|
|
2829
|
-
}
|
|
2830
|
-
return error
|
|
2831
|
-
}
|
|
2832
|
-
|
|
2833
|
-
// Return error since JSON data has been passed to mount HTML to DOM
|
|
2834
|
-
if (trimmedData.startsWith('[') || trimmedData.startsWith('{')) {
|
|
2835
|
-
const error =
|
|
2836
|
-
'Brackets [] or curly braces {} are not valid HTML. They are used for data formats like JSON.'
|
|
2837
|
-
if (options && options.logError) {
|
|
2838
|
-
console.error(error)
|
|
2839
|
-
return error
|
|
2840
|
-
}
|
|
2841
|
-
|
|
2842
|
-
return error
|
|
2843
|
-
}
|
|
2844
|
-
|
|
2845
|
-
return null
|
|
2846
|
-
}
|
|
2847
|
-
|
|
2848
|
-
/**
|
|
2849
|
-
* Applies modified components by mounting them to the DOM and attaching listeners.
|
|
2850
|
-
* @param htmlString - The HTML string to apply
|
|
2851
|
-
* @returns {Promise<string | null>} - Returns error message if failed, otherwise null
|
|
2852
|
-
*/
|
|
2853
|
-
public async applyModifiedComponents(htmlString: string): Promise<string | null> {
|
|
2854
|
-
// Trim HTML string
|
|
2855
|
-
const trimmedData = htmlString.trim()
|
|
2856
|
-
|
|
2857
|
-
const openingSectionMatches = htmlString.match(/<section\b[^>]*>/gi) || []
|
|
2858
|
-
|
|
2859
|
-
if (openingSectionMatches.length === 0) {
|
|
2860
|
-
const error = 'No <section> tags found. Each component must be wrapped in a <section> tag.'
|
|
2861
|
-
if (error) {
|
|
2862
|
-
return error
|
|
2863
|
-
}
|
|
2864
|
-
}
|
|
2865
|
-
|
|
2866
|
-
const validationError = this.validateMountingHTML(trimmedData)
|
|
2867
|
-
if (validationError) return validationError
|
|
2868
|
-
|
|
2869
|
-
// also fixed to use `trimmedData`
|
|
2870
|
-
await this.mountComponentsToDOM(trimmedData)
|
|
2871
|
-
await this.addListenersToEditableElements()
|
|
2872
|
-
await nextTick()
|
|
2873
|
-
return null
|
|
2874
|
-
}
|
|
2875
|
-
|
|
2876
|
-
/**
|
|
2877
|
-
* Mounts builder components to the DOM from an HTML string.
|
|
2878
|
-
*
|
|
2879
|
-
* Input format detection:
|
|
2880
|
-
* - If the input starts with `[` or `{`: treated as JSON (array or object).
|
|
2881
|
-
* - If the input starts with `<`: treated as HTML.
|
|
2882
|
-
*
|
|
2883
|
-
* This function should be used when:
|
|
2884
|
-
* - Restoring the builder from a published HTML snapshot.
|
|
2885
|
-
* - Importing a static HTML export.
|
|
2886
|
-
* - Loading the builder from previously published or saved HTML (e.g., from `getSavedPageHtml()`).
|
|
2887
|
-
*
|
|
2888
|
-
* Typical use cases include restoring a published state, importing templates, or previewing published content.
|
|
2889
|
-
*/
|
|
2890
|
-
|
|
2891
|
-
private async mountComponentsToDOM(
|
|
2892
|
-
htmlString: string,
|
|
2893
|
-
usePassedPageSettings?: boolean,
|
|
2894
|
-
pageSettingsFromHistory?: PageSettings,
|
|
2895
|
-
): Promise<void> {
|
|
2896
|
-
// Trim HTML string
|
|
2897
|
-
const trimmedData = htmlString.trim()
|
|
2898
|
-
|
|
2899
|
-
const validationError = this.validateMountingHTML(trimmedData, { logError: true })
|
|
2900
|
-
if (validationError) return
|
|
2901
|
-
|
|
2902
|
-
// HTML string
|
|
2903
|
-
try {
|
|
2904
|
-
const parser = new DOMParser()
|
|
2905
|
-
const doc = parser.parseFromString(htmlString, 'text/html')
|
|
2906
|
-
|
|
2907
|
-
const importedPageBuilder = doc.querySelector('#pagebuilder') as HTMLElement | null
|
|
2908
|
-
const livePageBuilder = document.querySelector('#pagebuilder') as HTMLElement | null
|
|
2909
|
-
|
|
2910
|
-
// Initialize configPageSettings to null
|
|
2911
|
-
let configPageSettings = null
|
|
2912
|
-
|
|
2913
|
-
// Use stored page settings if the flag is true
|
|
2914
|
-
if (usePassedPageSettings) {
|
|
2915
|
-
configPageSettings = this.pageBuilderStateStore.getPageBuilderConfig?.pageSettings || null
|
|
2916
|
-
}
|
|
2917
|
-
|
|
2918
|
-
// Use imported page builder settings if available and pageSettings is still null
|
|
2919
|
-
if (!pageSettingsFromHistory && !configPageSettings && importedPageBuilder) {
|
|
2920
|
-
configPageSettings = {
|
|
2921
|
-
classes: importedPageBuilder.className || '',
|
|
2922
|
-
style: importedPageBuilder.getAttribute('style') || '',
|
|
2923
|
-
}
|
|
2924
|
-
}
|
|
2925
|
-
|
|
2926
|
-
// Fallback to stored page settings if pageSettings is still null
|
|
2927
|
-
if (!configPageSettings) {
|
|
2928
|
-
configPageSettings = this.pageBuilderStateStore.getPageBuilderConfig?.pageSettings || null
|
|
2929
|
-
}
|
|
2930
|
-
|
|
2931
|
-
// Apply the page settings to the live page builder
|
|
2932
|
-
if (!pageSettingsFromHistory && configPageSettings && livePageBuilder) {
|
|
2933
|
-
// Remove existing class and style attributes
|
|
2934
|
-
livePageBuilder.removeAttribute('class')
|
|
2935
|
-
livePageBuilder.removeAttribute('style')
|
|
2936
|
-
|
|
2937
|
-
// Apply new classes and styles
|
|
2938
|
-
livePageBuilder.className = configPageSettings.classes || ''
|
|
2939
|
-
livePageBuilder.setAttribute(
|
|
2940
|
-
'style',
|
|
2941
|
-
this.convertStyleObjectToString(configPageSettings.style),
|
|
2942
|
-
)
|
|
2943
|
-
}
|
|
2944
|
-
|
|
2945
|
-
// Apply the page settings to the live page builder
|
|
2946
|
-
if (pageSettingsFromHistory && livePageBuilder) {
|
|
2947
|
-
// Remove existing class and style attributes
|
|
2948
|
-
livePageBuilder.removeAttribute('class')
|
|
2949
|
-
livePageBuilder.removeAttribute('style')
|
|
2950
|
-
|
|
2951
|
-
// Apply new classes and styles
|
|
2952
|
-
livePageBuilder.className = pageSettingsFromHistory.classes || ''
|
|
2953
|
-
livePageBuilder.setAttribute(
|
|
2954
|
-
'style',
|
|
2955
|
-
this.convertStyleObjectToString(pageSettingsFromHistory.style),
|
|
2956
|
-
)
|
|
2957
|
-
}
|
|
2958
|
-
|
|
2959
|
-
// Select all <section> elements
|
|
2960
|
-
const sectionElements = doc.querySelectorAll('section')
|
|
2961
|
-
|
|
2962
|
-
const extractedSections: ComponentObject[] = []
|
|
2963
|
-
sectionElements.forEach((section) => {
|
|
2964
|
-
// Prefix all classes inside section
|
|
2965
|
-
section.querySelectorAll('[class]').forEach((el) => {
|
|
2966
|
-
el.setAttribute(
|
|
2967
|
-
'class',
|
|
2968
|
-
this.addTailwindPrefixToClasses(el.getAttribute('class') || '', 'pbx-'),
|
|
2969
|
-
)
|
|
2970
|
-
})
|
|
2971
|
-
|
|
2972
|
-
const htmlElement = section as HTMLElement
|
|
2973
|
-
|
|
2974
|
-
// Ensure data-componentid exists
|
|
2975
|
-
if (!htmlElement.hasAttribute('data-componentid')) {
|
|
2976
|
-
htmlElement.setAttribute('data-componentid', uuidv4())
|
|
2977
|
-
}
|
|
2978
|
-
const componentId = htmlElement.getAttribute('data-componentid')!
|
|
2979
|
-
|
|
2980
|
-
// Ensure data-component-title exists
|
|
2981
|
-
const title = htmlElement.getAttribute('data-component-title') || 'Untitled Component'
|
|
2982
|
-
|
|
2983
|
-
htmlElement.setAttribute('data-component-title', title)
|
|
2984
|
-
|
|
2985
|
-
extractedSections.push({
|
|
2986
|
-
html_code: htmlElement.outerHTML,
|
|
2987
|
-
id: componentId,
|
|
2988
|
-
title: title,
|
|
2989
|
-
})
|
|
2990
|
-
})
|
|
2991
|
-
|
|
2992
|
-
this.pageBuilderStateStore.setComponents(extractedSections)
|
|
2993
|
-
|
|
2994
|
-
// Clear selections and re-bind events
|
|
2995
|
-
await this.clearHtmlSelection()
|
|
2996
|
-
await nextTick()
|
|
2997
|
-
await this.addListenersToEditableElements()
|
|
2998
|
-
} catch (error) {
|
|
2999
|
-
console.error('Error parsing HTML components:', error)
|
|
3000
|
-
this.deleteAllComponentsFromDOM()
|
|
3001
|
-
// Clear selections and re-bind events
|
|
3002
|
-
await this.clearHtmlSelection()
|
|
3003
|
-
await nextTick()
|
|
3004
|
-
await this.addListenersToEditableElements()
|
|
3005
|
-
}
|
|
3006
|
-
}
|
|
3007
|
-
private updateLocalStorageItemName(): void {
|
|
3008
|
-
const formtype =
|
|
3009
|
-
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
3010
|
-
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
3011
|
-
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType
|
|
3012
|
-
|
|
3013
|
-
const formname =
|
|
3014
|
-
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
3015
|
-
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
|
|
3016
|
-
this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formName
|
|
3017
|
-
|
|
3018
|
-
const resourceData =
|
|
3019
|
-
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
3020
|
-
this.pageBuilderStateStore.getPageBuilderConfig.resourceData
|
|
3021
|
-
|
|
3022
|
-
// Logic for create resource
|
|
3023
|
-
if (formtype === 'create') {
|
|
3024
|
-
if (formname && formname.length > 0) {
|
|
3025
|
-
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
3026
|
-
`page-builder-create-resource-${this.sanitizeForLocalStorage(formname)}`,
|
|
3027
|
-
)
|
|
3028
|
-
return
|
|
3029
|
-
}
|
|
3030
|
-
|
|
3031
|
-
this.pageBuilderStateStore.setLocalStorageItemName(`page-builder-create-resource`)
|
|
3032
|
-
return
|
|
3033
|
-
}
|
|
3034
|
-
|
|
3035
|
-
// Logic for create
|
|
3036
|
-
// Logic for update and with resource form name
|
|
3037
|
-
if (formtype === 'update') {
|
|
3038
|
-
if (typeof formname === 'string' && formname.length > 0) {
|
|
3039
|
-
//
|
|
3040
|
-
//
|
|
3041
|
-
if (resourceData && resourceData != null && !resourceData.title) {
|
|
3042
|
-
// Check if id is missing, null, undefined, or an empty string (after trimming)
|
|
3043
|
-
if (!resourceData.id || typeof resourceData.id === 'string') {
|
|
3044
|
-
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
3045
|
-
`page-builder-update-resource-${this.sanitizeForLocalStorage(formname)}`,
|
|
3046
|
-
)
|
|
3047
|
-
return
|
|
3048
|
-
}
|
|
3049
|
-
}
|
|
3050
|
-
|
|
3051
|
-
// Runs when resourceData has title but no ID
|
|
3052
|
-
if (resourceData && resourceData != null) {
|
|
3053
|
-
if (
|
|
3054
|
-
resourceData.title &&
|
|
3055
|
-
typeof resourceData.title === 'string' &&
|
|
3056
|
-
resourceData.title.length > 0
|
|
3057
|
-
) {
|
|
3058
|
-
if (!resourceData.id || typeof resourceData.id === 'string') {
|
|
3059
|
-
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
3060
|
-
`page-builder-update-resource-${this.sanitizeForLocalStorage(formname)}-${this.sanitizeForLocalStorage(resourceData.title)}`,
|
|
3061
|
-
)
|
|
3062
|
-
return
|
|
3063
|
-
}
|
|
3064
|
-
}
|
|
3065
|
-
}
|
|
3066
|
-
|
|
3067
|
-
// Runs when resourceData has ID but no title
|
|
3068
|
-
if (resourceData && resourceData != null) {
|
|
3069
|
-
if (!resourceData.title && typeof resourceData.title !== 'string') {
|
|
3070
|
-
if (resourceData.id || typeof resourceData.id === 'number') {
|
|
3071
|
-
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
3072
|
-
`page-builder-update-resource-${this.sanitizeForLocalStorage(formname)}-${this.sanitizeForLocalStorage(String(resourceData.id))}`,
|
|
3073
|
-
)
|
|
3074
|
-
return
|
|
3075
|
-
}
|
|
3076
|
-
}
|
|
3077
|
-
}
|
|
3078
|
-
|
|
3079
|
-
// Runs when resourceData has both title and ID
|
|
3080
|
-
if (resourceData && resourceData != null) {
|
|
3081
|
-
if (
|
|
3082
|
-
resourceData.title &&
|
|
3083
|
-
typeof resourceData.title === 'string' &&
|
|
3084
|
-
resourceData.title.length > 0
|
|
3085
|
-
) {
|
|
3086
|
-
if (resourceData.id || typeof resourceData.id === 'number') {
|
|
3087
|
-
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
3088
|
-
`page-builder-update-resource-${this.sanitizeForLocalStorage(formname)}-${this.sanitizeForLocalStorage(resourceData.title)}-${this.sanitizeForLocalStorage(String(resourceData.id))}`,
|
|
3089
|
-
)
|
|
3090
|
-
return
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3093
|
-
}
|
|
3094
|
-
}
|
|
3095
|
-
|
|
3096
|
-
// Logic for update without without formname
|
|
3097
|
-
if (!formname || (typeof formname === 'string' && formname.length === 0)) {
|
|
3098
|
-
//
|
|
3099
|
-
//
|
|
3100
|
-
if (resourceData && resourceData != null && !resourceData.title) {
|
|
3101
|
-
// Check if id is missing, null, undefined, or an empty string (after trimming)
|
|
3102
|
-
if (!resourceData.id || typeof resourceData.id === 'string') {
|
|
3103
|
-
this.pageBuilderStateStore.setLocalStorageItemName(`page-builder-update-resource`)
|
|
3104
|
-
return
|
|
3105
|
-
}
|
|
3106
|
-
}
|
|
3107
|
-
|
|
3108
|
-
// Runs when resourceData has title but no ID
|
|
3109
|
-
if (resourceData && resourceData != null) {
|
|
3110
|
-
if (
|
|
3111
|
-
resourceData.title &&
|
|
3112
|
-
typeof resourceData.title === 'string' &&
|
|
3113
|
-
resourceData.title.length > 0
|
|
3114
|
-
) {
|
|
3115
|
-
if (!resourceData.id || typeof resourceData.id === 'string') {
|
|
3116
|
-
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
3117
|
-
`page-builder-update-resource-${this.sanitizeForLocalStorage(resourceData.title)}`,
|
|
3118
|
-
)
|
|
3119
|
-
return
|
|
3120
|
-
}
|
|
3121
|
-
}
|
|
3122
|
-
}
|
|
3123
|
-
|
|
3124
|
-
// Runs when resourceData has ID but no title
|
|
3125
|
-
if (resourceData && resourceData != null) {
|
|
3126
|
-
if (!resourceData.title && typeof resourceData.title !== 'string') {
|
|
3127
|
-
if (resourceData.id || typeof resourceData.id === 'number') {
|
|
3128
|
-
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
3129
|
-
`page-builder-update-resource-${this.sanitizeForLocalStorage(String(resourceData.id))}`,
|
|
3130
|
-
)
|
|
3131
|
-
return
|
|
3132
|
-
}
|
|
3133
|
-
}
|
|
3134
|
-
}
|
|
3135
|
-
|
|
3136
|
-
// Runs when resourceData has both title and ID
|
|
3137
|
-
if (resourceData && resourceData != null) {
|
|
3138
|
-
if (
|
|
3139
|
-
resourceData.title &&
|
|
3140
|
-
typeof resourceData.title === 'string' &&
|
|
3141
|
-
resourceData.title.length > 0
|
|
3142
|
-
) {
|
|
3143
|
-
if (resourceData.id || typeof resourceData.id === 'number') {
|
|
3144
|
-
this.pageBuilderStateStore.setLocalStorageItemName(
|
|
3145
|
-
`page-builder-update-resource-${this.sanitizeForLocalStorage(resourceData.title)}-${this.sanitizeForLocalStorage(String(resourceData.id))}`,
|
|
3146
|
-
)
|
|
3147
|
-
return
|
|
3148
|
-
}
|
|
3149
|
-
}
|
|
3150
|
-
}
|
|
3151
|
-
}
|
|
3152
|
-
}
|
|
3153
|
-
}
|
|
3154
|
-
|
|
3155
|
-
/**
|
|
3156
|
-
* Initializes the styles for the currently selected element.
|
|
3157
|
-
*/
|
|
3158
|
-
public async initializeElementStyles(): Promise<void> {
|
|
3159
|
-
// Wait for Vue to finish DOM updates before attaching event listeners.
|
|
3160
|
-
// This ensures elements exist in the DOM.
|
|
3161
|
-
await nextTick()
|
|
3162
|
-
this.setBasePrimaryImageFromSelectedElement()
|
|
3163
|
-
this.handleHyperlink(undefined, null, false)
|
|
3164
|
-
this.handleOpacity(undefined)
|
|
3165
|
-
this.handleBackgroundOpacity(undefined)
|
|
3166
|
-
this.handleBackgroundColor(undefined)
|
|
3167
|
-
this.handleTextColor(undefined)
|
|
3168
|
-
this.handleBorderStyle(undefined)
|
|
3169
|
-
this.handleBorderWidth(undefined)
|
|
3170
|
-
this.handleBorderColor(undefined)
|
|
3171
|
-
this.handleBorderRadiusGlobal(undefined)
|
|
3172
|
-
this.handleBorderRadiusTopLeft(undefined)
|
|
3173
|
-
this.handleBorderRadiusTopRight(undefined)
|
|
3174
|
-
this.handleBorderRadiusBottomleft(undefined)
|
|
3175
|
-
this.handleBorderRadiusBottomRight(undefined)
|
|
3176
|
-
this.handleFontSizeBase(undefined)
|
|
3177
|
-
this.handleFontSizeDesktop(undefined)
|
|
3178
|
-
this.handleFontSizeTablet(undefined)
|
|
3179
|
-
this.handleFontSizeMobile(undefined)
|
|
3180
|
-
this.handleFontWeight(undefined)
|
|
3181
|
-
this.handleFontFamily(undefined)
|
|
3182
|
-
this.handleFontStyle(undefined)
|
|
3183
|
-
this.handleVerticalPadding(undefined)
|
|
3184
|
-
this.handleHorizontalPadding(undefined)
|
|
3185
|
-
this.handleVerticalMargin(undefined)
|
|
3186
|
-
this.handleHorizontalMargin(undefined)
|
|
3187
|
-
|
|
3188
|
-
await this.syncCurrentClasses()
|
|
3189
|
-
await this.syncCurrentStyles()
|
|
3190
|
-
}
|
|
3191
|
-
}
|