@myissue/vue-website-page-builder 3.3.56 → 3.3.58
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 +39 -2
- package/dist/home/page_builder_architecture.png +0 -0
- package/dist/vue-website-page-builder.css +1 -1
- package/dist/vue-website-page-builder.js +3651 -3453
- package/dist/vue-website-page-builder.umd.cjs +37 -37
- package/package.json +1 -1
- package/src/Components/PageBuilder/EditorMenu/Editables/BackgroundColorEditor.vue +22 -1
- package/src/Components/PageBuilder/EditorMenu/Editables/ClassEditor.vue +1 -3
- package/src/Components/PageBuilder/EditorMenu/Editables/TextColorEditor.vue +23 -1
- package/src/Components/PageBuilder/EditorMenu/RightSidebarEditor.vue +101 -20
- package/src/Components/PageBuilder/ToolbarOption/ToolbarOption.vue +1 -1
- package/src/DemoComponents/HomeSection.vue +9 -1
- package/src/PageBuilder/PageBuilder.vue +3 -3
- package/src/PageBuilder/Preview.vue +4 -6
- package/src/composables/PageBuilderService.ts +167 -147
- package/src/composables/extractCleanHTMLFromPageBuilder.ts +58 -0
- package/src/css/app.css +7 -0
- package/src/types/index.ts +8 -1
|
@@ -21,6 +21,7 @@ import type { ComputedRef } from 'vue'
|
|
|
21
21
|
import { v4 as uuidv4 } from 'uuid'
|
|
22
22
|
import { delay } from './delay'
|
|
23
23
|
import { isEmptyObject } from '../helpers/isEmptyObject'
|
|
24
|
+
import { extractCleanHTMLFromPageBuilder } from './extractCleanHTMLFromPageBuilder'
|
|
24
25
|
|
|
25
26
|
export class PageBuilderService {
|
|
26
27
|
// Class properties with types
|
|
@@ -245,79 +246,6 @@ export class PageBuilderService {
|
|
|
245
246
|
}
|
|
246
247
|
}
|
|
247
248
|
|
|
248
|
-
#handlePageBuilderNotPresent(passedDataComponents: BuilderResourceData) {
|
|
249
|
-
this.pendingMountData = passedDataComponents
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async #mountPassedComponentsToDOM(components?: BuilderResourceData): Promise<void> {
|
|
253
|
-
const config = this.pageBuilderStateStore.getPageBuilderConfig
|
|
254
|
-
const formType = config && config.updateOrCreate && config.updateOrCreate.formType
|
|
255
|
-
const localStorageData = this.loadStoredComponentsFromStorage()
|
|
256
|
-
|
|
257
|
-
let dataToPass: string
|
|
258
|
-
if (typeof components === 'string') {
|
|
259
|
-
dataToPass = components
|
|
260
|
-
} else if (components !== undefined) {
|
|
261
|
-
dataToPass = JSON.stringify(components)
|
|
262
|
-
} else {
|
|
263
|
-
dataToPass = ''
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
await this.#updateComponentsFromString(dataToPass)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
async tryMountPendingComponents() {
|
|
270
|
-
this.pageBuilderStateStore.setIsLoadingGlobal(true)
|
|
271
|
-
await delay(400)
|
|
272
|
-
|
|
273
|
-
// Always clear DOM and store before mounting new resource
|
|
274
|
-
this.deleteAllComponentsFromDOM()
|
|
275
|
-
const localStorageData = this.loadStoredComponentsFromStorage()
|
|
276
|
-
|
|
277
|
-
const config = this.pageBuilderStateStore.getPageBuilderConfig
|
|
278
|
-
const formType = config && config.updateOrCreate && config.updateOrCreate.formType
|
|
279
|
-
|
|
280
|
-
//
|
|
281
|
-
if (config) {
|
|
282
|
-
if (formType === 'update') {
|
|
283
|
-
//
|
|
284
|
-
if (localStorageData && typeof localStorageData === 'string') {
|
|
285
|
-
this.pageBuilderStateStore.setHasLocalDraftForUpdate(true)
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (this.pendingMountData) {
|
|
289
|
-
this.#completeBuilderInitialization(this.pendingMountData)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Pending data for mount is null at this stage
|
|
293
|
-
if (typeof localStorageData === 'string' && !this.pendingMountData) {
|
|
294
|
-
await this.#updateComponentsFromString(localStorageData)
|
|
295
|
-
this.#completeBuilderInitialization()
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (formType === 'create') {
|
|
300
|
-
// Pending data for mount is null at this stage
|
|
301
|
-
if (typeof localStorageData === 'string') {
|
|
302
|
-
await this.#updateComponentsFromString(localStorageData)
|
|
303
|
-
this.#completeBuilderInitialization()
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
313
|
-
await nextTick()
|
|
314
|
-
// Attach event listeners to all editable elements in the Builder
|
|
315
|
-
await this.#addListenersToEditableElements()
|
|
316
|
-
|
|
317
|
-
this.pageBuilderStateStore.setIsRestoring(false)
|
|
318
|
-
this.pageBuilderStateStore.setIsLoadingGlobal(false)
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
249
|
/**
|
|
322
250
|
* - Entry point for initializing the Page Builder.
|
|
323
251
|
* - Sets the builder as started in the state store.
|
|
@@ -351,11 +279,11 @@ export class PageBuilderService {
|
|
|
351
279
|
|
|
352
280
|
// Page Builder is not Present in the DOM but Components have been passed to the Builder
|
|
353
281
|
if (passedComponentsArray && !pagebuilder) {
|
|
354
|
-
this
|
|
282
|
+
this.pendingMountData = passedComponentsArray
|
|
355
283
|
}
|
|
356
284
|
// Page Builder is Present in the DOM & Components have been passed to the Builder
|
|
357
285
|
if (pagebuilder) {
|
|
358
|
-
this
|
|
286
|
+
this.completeBuilderInitialization(passedComponentsArray)
|
|
359
287
|
}
|
|
360
288
|
|
|
361
289
|
// result to end user
|
|
@@ -385,45 +313,90 @@ export class PageBuilderService {
|
|
|
385
313
|
}
|
|
386
314
|
}
|
|
387
315
|
|
|
388
|
-
async
|
|
316
|
+
async completeBuilderInitialization(passedComponentsArray?: BuilderResourceData): Promise<void> {
|
|
317
|
+
this.pageBuilderStateStore.setIsLoadingGlobal(true)
|
|
318
|
+
await delay(400)
|
|
319
|
+
|
|
320
|
+
// Always clear DOM and store before mounting new resource
|
|
321
|
+
this.deleteAllComponentsFromDOM()
|
|
322
|
+
|
|
323
|
+
const config = this.pageBuilderStateStore.getPageBuilderConfig
|
|
324
|
+
const formType = config && config.updateOrCreate && config.updateOrCreate.formType
|
|
325
|
+
|
|
389
326
|
const localStorageData = this.loadStoredComponentsFromStorage()
|
|
390
327
|
|
|
391
328
|
// Deselect any selected or hovered elements in the builder UI
|
|
392
329
|
await this.clearHtmlSelection()
|
|
393
330
|
|
|
394
|
-
if (passedComponentsArray) {
|
|
395
|
-
// Prefer components from local storage if available for this resource
|
|
396
|
-
if (!this.pendingMountData && localStorageData && typeof localStorageData === 'string') {
|
|
397
|
-
await this.#updateComponentsFromString(localStorageData)
|
|
398
|
-
} else {
|
|
399
|
-
// If no local storage is found, use the components array provided by the user
|
|
400
|
-
await this.#mountPassedComponentsToDOM(passedComponentsArray)
|
|
401
|
-
this.pendingMountData = null
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
331
|
//
|
|
406
332
|
//
|
|
407
333
|
//
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
334
|
+
if (formType === 'update' || formType === 'create') {
|
|
335
|
+
if (!this.pendingMountData) {
|
|
336
|
+
// FOCUS ON: passedComponentsArray
|
|
337
|
+
if (passedComponentsArray && !localStorageData) {
|
|
338
|
+
await this.#completeMountProcess(JSON.stringify(passedComponentsArray))
|
|
339
|
+
return
|
|
340
|
+
}
|
|
341
|
+
if (passedComponentsArray && localStorageData) {
|
|
342
|
+
this.pageBuilderStateStore.setHasLocalDraftForUpdate(true)
|
|
343
|
+
|
|
344
|
+
await this.#completeMountProcess(JSON.stringify(passedComponentsArray))
|
|
345
|
+
return
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (passedComponentsArray && localStorageData) {
|
|
349
|
+
this.pageBuilderStateStore.setHasLocalDraftForUpdate(true)
|
|
350
|
+
|
|
351
|
+
await this.#completeMountProcess(JSON.stringify(passedComponentsArray))
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (localStorageData && !passedComponentsArray) {
|
|
356
|
+
await this.#completeMountProcess(localStorageData)
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
if (!passedComponentsArray && !localStorageData) {
|
|
360
|
+
await this.#completeMountProcess(JSON.stringify([]))
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// FOCUS ON: pendingMountData
|
|
366
|
+
if (this.pendingMountData) {
|
|
367
|
+
if (localStorageData) {
|
|
368
|
+
this.pageBuilderStateStore.setHasLocalDraftForUpdate(true)
|
|
369
|
+
await this.#completeMountProcess(JSON.stringify(this.pendingMountData))
|
|
370
|
+
this.pendingMountData = null
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
if (!localStorageData && passedComponentsArray) {
|
|
374
|
+
await this.#completeMountProcess(JSON.stringify(this.pendingMountData))
|
|
375
|
+
this.pendingMountData = null
|
|
376
|
+
return
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (!passedComponentsArray && !localStorageData) {
|
|
380
|
+
await this.#completeMountProcess(JSON.stringify([]))
|
|
381
|
+
return
|
|
382
|
+
}
|
|
415
383
|
}
|
|
416
384
|
}
|
|
417
385
|
//
|
|
386
|
+
}
|
|
418
387
|
|
|
388
|
+
async #completeMountProcess(html: string) {
|
|
389
|
+
await this.#mountComponentsToDOM(html)
|
|
419
390
|
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
420
391
|
await nextTick()
|
|
421
392
|
// Attach event listeners to all editable elements in the Builder
|
|
422
393
|
await this.#addListenersToEditableElements()
|
|
423
|
-
// Show a global loading indicator while initializing
|
|
424
394
|
|
|
425
395
|
// Clean up any old localStorage items related to previous builder sessions
|
|
426
396
|
this.deleteOldPageBuilderLocalStorage()
|
|
397
|
+
|
|
398
|
+
this.pageBuilderStateStore.setIsRestoring(false)
|
|
399
|
+
this.pageBuilderStateStore.setIsLoadingGlobal(false)
|
|
427
400
|
}
|
|
428
401
|
|
|
429
402
|
#applyElementClassChanges(
|
|
@@ -476,6 +449,22 @@ export class PageBuilderService {
|
|
|
476
449
|
return currentCSS
|
|
477
450
|
}
|
|
478
451
|
|
|
452
|
+
async globalPageStyles() {
|
|
453
|
+
const pagebuilder = document.querySelector('#pagebuilder')
|
|
454
|
+
if (!pagebuilder) return
|
|
455
|
+
|
|
456
|
+
// Deselect any selected or hovered elements in the builder UI
|
|
457
|
+
await this.clearHtmlSelection()
|
|
458
|
+
//
|
|
459
|
+
// Set the element in the store
|
|
460
|
+
this.pageBuilderStateStore.setElement(pagebuilder as HTMLElement)
|
|
461
|
+
|
|
462
|
+
// Add the data attribute for styling
|
|
463
|
+
pagebuilder.setAttribute('data-global-selected', 'true')
|
|
464
|
+
|
|
465
|
+
await nextTick()
|
|
466
|
+
}
|
|
467
|
+
|
|
479
468
|
handleFontWeight(userSelectedFontWeight?: string): void {
|
|
480
469
|
this.#applyElementClassChanges(
|
|
481
470
|
userSelectedFontWeight,
|
|
@@ -860,8 +849,6 @@ export class PageBuilderService {
|
|
|
860
849
|
// Add prefix if missing
|
|
861
850
|
const prefixedClass = cleanedClass.startsWith('pbx-') ? cleanedClass : 'pbx-' + cleanedClass
|
|
862
851
|
|
|
863
|
-
console.log('Adding class:', prefixedClass)
|
|
864
|
-
|
|
865
852
|
this.getElement.value?.classList.add(prefixedClass)
|
|
866
853
|
|
|
867
854
|
this.pageBuilderStateStore.setElement(this.getElement.value)
|
|
@@ -1275,40 +1262,21 @@ export class PageBuilderService {
|
|
|
1275
1262
|
const pagebuilder = document.querySelector('#pagebuilder')
|
|
1276
1263
|
if (!pagebuilder) return
|
|
1277
1264
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
if (hoveredElement) {
|
|
1287
|
-
hoveredElement.removeAttribute('hovered')
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
// remove selected
|
|
1291
|
-
const selectedElement = section.querySelector('[selected]')
|
|
1292
|
-
if (selectedElement) {
|
|
1293
|
-
selectedElement.removeAttribute('selected')
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
// push outer html into the array
|
|
1297
|
-
addedHtmlComponents.value.push(section.outerHTML)
|
|
1298
|
-
})
|
|
1299
|
-
|
|
1300
|
-
// stringify added html components
|
|
1301
|
-
const stringifiedComponents = JSON.stringify(addedHtmlComponents.value)
|
|
1302
|
-
|
|
1303
|
-
// commit
|
|
1304
|
-
this.pageBuilderStateStore.setCurrentLayoutPreview(stringifiedComponents)
|
|
1265
|
+
if (pagebuilder) {
|
|
1266
|
+
// Get cleaned HTML from entire builder
|
|
1267
|
+
const cleanedHTML = extractCleanHTMLFromPageBuilder(
|
|
1268
|
+
pagebuilder as HTMLElement,
|
|
1269
|
+
this.pageBuilderStateStore.getPageBuilderConfig
|
|
1270
|
+
? this.pageBuilderStateStore.getPageBuilderConfig
|
|
1271
|
+
: undefined,
|
|
1272
|
+
)
|
|
1305
1273
|
|
|
1306
|
-
|
|
1307
|
-
|
|
1274
|
+
// Store as array with one string (as your preview expects an array)
|
|
1275
|
+
const previewData = JSON.stringify([cleanedHTML])
|
|
1308
1276
|
|
|
1309
|
-
|
|
1277
|
+
this.pageBuilderStateStore.setCurrentLayoutPreview(previewData)
|
|
1278
|
+
}
|
|
1310
1279
|
}
|
|
1311
|
-
|
|
1312
1280
|
// Helper function to sanitize title for localStorage key
|
|
1313
1281
|
private sanitizeForLocalStorage(input: string): string {
|
|
1314
1282
|
return input
|
|
@@ -1534,10 +1502,15 @@ export class PageBuilderService {
|
|
|
1534
1502
|
})
|
|
1535
1503
|
})
|
|
1536
1504
|
|
|
1537
|
-
|
|
1505
|
+
const pageSettings = {
|
|
1506
|
+
classes: pagebuilder.className || '',
|
|
1507
|
+
style: pagebuilder.getAttribute('style') || '',
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1538
1510
|
const dataToSave = {
|
|
1539
1511
|
components: componentsToSave,
|
|
1540
1512
|
pageBuilderContentSavedAt: new Date().toISOString(),
|
|
1513
|
+
pageSettings,
|
|
1541
1514
|
}
|
|
1542
1515
|
|
|
1543
1516
|
const keyForSavingFromDomToLocal = this.getLocalStorageItemName.value
|
|
@@ -1546,7 +1519,6 @@ export class PageBuilderService {
|
|
|
1546
1519
|
localStorage.setItem(keyForSavingFromDomToLocal, JSON.stringify(dataToSave))
|
|
1547
1520
|
}
|
|
1548
1521
|
}
|
|
1549
|
-
|
|
1550
1522
|
async removeCurrentComponentsFromLocalStorage() {
|
|
1551
1523
|
this.#updateLocalStorageItemName()
|
|
1552
1524
|
await nextTick()
|
|
@@ -1616,12 +1588,11 @@ export class PageBuilderService {
|
|
|
1616
1588
|
}
|
|
1617
1589
|
|
|
1618
1590
|
//
|
|
1619
|
-
async
|
|
1591
|
+
async resumeEditingFromDraft() {
|
|
1620
1592
|
this.#updateLocalStorageItemName()
|
|
1621
1593
|
const config = this.pageBuilderStateStore.getPageBuilderConfig
|
|
1622
1594
|
const formType = config && config.updateOrCreate && config.updateOrCreate.formType
|
|
1623
1595
|
|
|
1624
|
-
if (formType !== 'update') return
|
|
1625
1596
|
//
|
|
1626
1597
|
//
|
|
1627
1598
|
//
|
|
@@ -1634,7 +1605,7 @@ export class PageBuilderService {
|
|
|
1634
1605
|
if (typeof updateDraftFromLocalStorage === 'string') {
|
|
1635
1606
|
this.pageBuilderStateStore.setIsLoadingResumeEditing(true)
|
|
1636
1607
|
await delay(400)
|
|
1637
|
-
await this.#
|
|
1608
|
+
await this.#mountComponentsToDOM(updateDraftFromLocalStorage)
|
|
1638
1609
|
this.pageBuilderStateStore.setIsLoadingResumeEditing(false)
|
|
1639
1610
|
}
|
|
1640
1611
|
}
|
|
@@ -1658,7 +1629,7 @@ export class PageBuilderService {
|
|
|
1658
1629
|
|
|
1659
1630
|
// Restore the original content if available
|
|
1660
1631
|
if (Array.isArray(this.originalComponents)) {
|
|
1661
|
-
await this.#
|
|
1632
|
+
await this.#mountComponentsToDOM(JSON.stringify(this.originalComponents))
|
|
1662
1633
|
this.removeCurrentComponentsFromLocalStorage()
|
|
1663
1634
|
}
|
|
1664
1635
|
|
|
@@ -1973,6 +1944,20 @@ export class PageBuilderService {
|
|
|
1973
1944
|
.join(' ')
|
|
1974
1945
|
}
|
|
1975
1946
|
|
|
1947
|
+
#convertStyleObjectToString(
|
|
1948
|
+
styleObj: string | Record<string, string> | null | undefined,
|
|
1949
|
+
): string {
|
|
1950
|
+
if (!styleObj) return ''
|
|
1951
|
+
if (typeof styleObj === 'string') return styleObj
|
|
1952
|
+
|
|
1953
|
+
return Object.entries(styleObj)
|
|
1954
|
+
.map(([key, value]) => {
|
|
1955
|
+
const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
1956
|
+
return `${kebabKey}: ${value};`
|
|
1957
|
+
})
|
|
1958
|
+
.join(' ')
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1976
1961
|
/**
|
|
1977
1962
|
* Parse and set components from JSON or HTML data
|
|
1978
1963
|
*
|
|
@@ -1985,7 +1970,7 @@ export class PageBuilderService {
|
|
|
1985
1970
|
* @param data - JSON string (e.g., '[{"html_code":"...","id":"123","title":"..."}]')
|
|
1986
1971
|
* OR HTML string (e.g., '<section data-componentid="123">...</section>')
|
|
1987
1972
|
*/
|
|
1988
|
-
async #
|
|
1973
|
+
async #mountComponentsToDOM(htmlString: string): Promise<void> {
|
|
1989
1974
|
// Auto-detect if input is JSON or HTML
|
|
1990
1975
|
const trimmedData = htmlString.trim()
|
|
1991
1976
|
|
|
@@ -2003,11 +1988,27 @@ export class PageBuilderService {
|
|
|
2003
1988
|
await this.#parseJSONComponents(trimmedData)
|
|
2004
1989
|
}
|
|
2005
1990
|
|
|
1991
|
+
// Private method to parse JSON components and save pageBuilderContentSavedAt to localStorage
|
|
2006
1992
|
// Private method to parse JSON components and save pageBuilderContentSavedAt to localStorage
|
|
2007
1993
|
async #parseJSONComponents(jsonData: string): Promise<void> {
|
|
2008
1994
|
try {
|
|
2009
1995
|
const parsedData = JSON.parse(jsonData)
|
|
2010
1996
|
let componentsArray: ComponentObject[] = []
|
|
1997
|
+
|
|
1998
|
+
// Fallback to store-based config if not provided in imported JSON
|
|
1999
|
+
const pageSettings =
|
|
2000
|
+
this.pageBuilderStateStore.getPageBuilderConfig &&
|
|
2001
|
+
this.pageBuilderStateStore.getPageBuilderConfig.pageSettings
|
|
2002
|
+
|
|
2003
|
+
// Restore page-level settings like class and style
|
|
2004
|
+
if (pageSettings) {
|
|
2005
|
+
const pagebuilder = document.querySelector('#pagebuilder') as HTMLElement
|
|
2006
|
+
if (pagebuilder) {
|
|
2007
|
+
pagebuilder.className = pageSettings.classes || ''
|
|
2008
|
+
pagebuilder.setAttribute('style', this.#convertStyleObjectToString(pageSettings.style))
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2011
2012
|
// Support both old and new structure
|
|
2012
2013
|
if (Array.isArray(parsedData)) {
|
|
2013
2014
|
componentsArray = parsedData
|
|
@@ -2024,7 +2025,7 @@ export class PageBuilderService {
|
|
|
2024
2025
|
const section = doc.querySelector('section')
|
|
2025
2026
|
|
|
2026
2027
|
if (section) {
|
|
2027
|
-
//
|
|
2028
|
+
// Prefix Tailwind classes
|
|
2028
2029
|
section.querySelectorAll('[class]').forEach((el) => {
|
|
2029
2030
|
el.setAttribute(
|
|
2030
2031
|
'class',
|
|
@@ -2032,7 +2033,7 @@ export class PageBuilderService {
|
|
|
2032
2033
|
)
|
|
2033
2034
|
})
|
|
2034
2035
|
|
|
2035
|
-
// Ensure
|
|
2036
|
+
// Ensure IDs & titles
|
|
2036
2037
|
if (!section.hasAttribute('data-componentid')) {
|
|
2037
2038
|
const newId = uuidv4()
|
|
2038
2039
|
section.setAttribute('data-componentid', newId)
|
|
@@ -2041,24 +2042,20 @@ export class PageBuilderService {
|
|
|
2041
2042
|
component.id = section.getAttribute('data-componentid')!
|
|
2042
2043
|
}
|
|
2043
2044
|
|
|
2044
|
-
// Ensure data-component-title exists
|
|
2045
2045
|
const title = component.title || 'Untitled Component'
|
|
2046
2046
|
section.setAttribute('data-component-title', title)
|
|
2047
2047
|
component.title = title
|
|
2048
2048
|
|
|
2049
|
-
// Update html_code
|
|
2049
|
+
// Update html_code
|
|
2050
2050
|
component.html_code = section.outerHTML
|
|
2051
2051
|
}
|
|
2052
|
-
|
|
2053
2052
|
return component
|
|
2054
2053
|
})
|
|
2055
2054
|
}
|
|
2056
2055
|
|
|
2057
2056
|
this.pageBuilderStateStore.setComponents(savedCurrentDesign)
|
|
2058
2057
|
|
|
2059
|
-
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
2060
2058
|
await nextTick()
|
|
2061
|
-
// Attach event listeners to all editable elements in the Builder
|
|
2062
2059
|
await this.#addListenersToEditableElements()
|
|
2063
2060
|
} catch (error) {
|
|
2064
2061
|
console.error('Error parsing JSON components:', error)
|
|
@@ -2071,12 +2068,37 @@ export class PageBuilderService {
|
|
|
2071
2068
|
const parser = new DOMParser()
|
|
2072
2069
|
const doc = parser.parseFromString(htmlData, 'text/html')
|
|
2073
2070
|
|
|
2074
|
-
|
|
2071
|
+
const importedPageBuilder = doc.querySelector('#pagebuilder') as HTMLElement | null
|
|
2072
|
+
const livePageBuilder = document.querySelector('#pagebuilder') as HTMLElement | null
|
|
2073
|
+
|
|
2074
|
+
if (livePageBuilder) {
|
|
2075
|
+
if (importedPageBuilder) {
|
|
2076
|
+
livePageBuilder.className = importedPageBuilder.className || ''
|
|
2077
|
+
const style = importedPageBuilder.getAttribute('style')
|
|
2078
|
+
if (style !== null) {
|
|
2079
|
+
livePageBuilder.setAttribute('style', style)
|
|
2080
|
+
} else {
|
|
2081
|
+
livePageBuilder.removeAttribute('style')
|
|
2082
|
+
}
|
|
2083
|
+
} else {
|
|
2084
|
+
// Fallback: inject settings from store if not present in HTML
|
|
2085
|
+
const fallbackSettings = this.pageBuilderStateStore.getPageBuilderConfig?.pageSettings
|
|
2086
|
+
if (fallbackSettings) {
|
|
2087
|
+
livePageBuilder.className = fallbackSettings.classes || ''
|
|
2088
|
+
livePageBuilder.setAttribute(
|
|
2089
|
+
'style',
|
|
2090
|
+
this.#convertStyleObjectToString(fallbackSettings.style),
|
|
2091
|
+
)
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
// Select all <section> elements
|
|
2075
2097
|
const sectionElements = doc.querySelectorAll('section')
|
|
2076
2098
|
|
|
2077
2099
|
const extractedSections: ComponentObject[] = []
|
|
2078
2100
|
sectionElements.forEach((section) => {
|
|
2079
|
-
//
|
|
2101
|
+
// Prefix all classes inside section
|
|
2080
2102
|
section.querySelectorAll('[class]').forEach((el) => {
|
|
2081
2103
|
el.setAttribute(
|
|
2082
2104
|
'class',
|
|
@@ -2109,11 +2131,9 @@ export class PageBuilderService {
|
|
|
2109
2131
|
|
|
2110
2132
|
this.pageBuilderStateStore.setComponents(extractedSections)
|
|
2111
2133
|
|
|
2112
|
-
//
|
|
2134
|
+
// Clear selections and re-bind events
|
|
2113
2135
|
await this.clearHtmlSelection()
|
|
2114
|
-
// Wait for Vue to finish DOM updates before attaching event listeners. This ensure elements exist in the DOM.
|
|
2115
2136
|
await nextTick()
|
|
2116
|
-
// Attach event listeners to all editable elements in the Builder
|
|
2117
2137
|
await this.#addListenersToEditableElements()
|
|
2118
2138
|
} catch (error) {
|
|
2119
2139
|
console.error('Error parsing HTML components:', error)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { PageBuilderConfig } from '@/types'
|
|
2
|
+
|
|
3
|
+
export function extractCleanHTMLFromPageBuilder(
|
|
4
|
+
pagebuilder: HTMLElement | null,
|
|
5
|
+
config?: PageBuilderConfig,
|
|
6
|
+
): string {
|
|
7
|
+
if (!pagebuilder) {
|
|
8
|
+
console.warn('No valid pagebuilder element passed')
|
|
9
|
+
return ''
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const clone = pagebuilder.cloneNode(true) as HTMLElement
|
|
13
|
+
clone.removeAttribute('id')
|
|
14
|
+
|
|
15
|
+
// Remove custom attributes
|
|
16
|
+
const elementsWithAttrs = clone.querySelectorAll<HTMLElement>(
|
|
17
|
+
'[data-componentid], [data-component-title], #page-builder-editor-editable-area',
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
elementsWithAttrs.forEach((el) => {
|
|
21
|
+
el.removeAttribute('data-componentid')
|
|
22
|
+
el.removeAttribute('data-component-title')
|
|
23
|
+
if (el.id === 'page-builder-editor-editable-area') {
|
|
24
|
+
el.removeAttribute('id')
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if (config && config.pageSettings && typeof config.pageSettings.imageUrlPrefix === 'string') {
|
|
29
|
+
const imageUrlPrefix = config.pageSettings.imageUrlPrefix
|
|
30
|
+
const imgs = clone.querySelectorAll<HTMLImageElement>('img')
|
|
31
|
+
imgs.forEach((img) => {
|
|
32
|
+
const src = img.getAttribute('src') || ''
|
|
33
|
+
if (
|
|
34
|
+
!src.startsWith('http') &&
|
|
35
|
+
imageUrlPrefix && // extra safety
|
|
36
|
+
!src.startsWith(imageUrlPrefix)
|
|
37
|
+
) {
|
|
38
|
+
img.setAttribute('src', imageUrlPrefix + src.replace(/^\/+/, ''))
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Recursively remove all comment nodes
|
|
44
|
+
const removeComments = (node: Node): void => {
|
|
45
|
+
for (let i = node.childNodes.length - 1; i >= 0; i--) {
|
|
46
|
+
const child = node.childNodes[i]
|
|
47
|
+
if (child.nodeType === Node.COMMENT_NODE) {
|
|
48
|
+
node.removeChild(child)
|
|
49
|
+
} else if (child.nodeType === Node.ELEMENT_NODE) {
|
|
50
|
+
removeComments(child)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
removeComments(clone)
|
|
56
|
+
|
|
57
|
+
return clone.outerHTML
|
|
58
|
+
}
|
package/src/css/app.css
CHANGED
package/src/types/index.ts
CHANGED
|
@@ -201,6 +201,12 @@ export interface PageBuilderUser {
|
|
|
201
201
|
image: string
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
export interface PageSettings {
|
|
205
|
+
classes: string
|
|
206
|
+
style?: Record<string, string>
|
|
207
|
+
imageUrlPrefix?: string
|
|
208
|
+
}
|
|
209
|
+
|
|
204
210
|
// Page Builder Configuration interface
|
|
205
211
|
export interface PageBuilderConfig {
|
|
206
212
|
updateOrCreate: {
|
|
@@ -209,7 +215,7 @@ export interface PageBuilderConfig {
|
|
|
209
215
|
}
|
|
210
216
|
pageBuilderLogo?: { src: string } | null
|
|
211
217
|
resourceData?: { title: string; id?: number } | null
|
|
212
|
-
userForPageBuilder?:
|
|
218
|
+
userForPageBuilder?: PageBuilderUser
|
|
213
219
|
[key: string]: unknown
|
|
214
220
|
userSettings?: {
|
|
215
221
|
theme?: 'light' | 'dark' | 'auto'
|
|
@@ -221,6 +227,7 @@ export interface PageBuilderConfig {
|
|
|
221
227
|
brandColor?: string
|
|
222
228
|
[key: string]: unknown
|
|
223
229
|
} | null
|
|
230
|
+
pageSettings?: PageSettings
|
|
224
231
|
}
|
|
225
232
|
// Tailwind utility interfaces
|
|
226
233
|
export interface TailwindColors {
|