@myissue/vue-website-page-builder 3.3.97 → 3.3.99
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 +46 -2
- package/dist/style.css +1 -1
- package/dist/vue-website-page-builder.js +13118 -13051
- package/dist/vue-website-page-builder.umd.cjs +84 -45
- package/package.json +4 -4
- package/src/Components/Homepage/Footer.vue +1 -1
- package/src/Components/PageBuilder/EditorMenu/RightSidebarEditor.vue +75 -6
- package/src/Components/PageBuilder/UndoRedo/UndoRedo.vue +27 -24
- package/src/PageBuilder/PageBuilder.vue +2 -2
- package/src/css/style.css +62 -32
- package/src/services/PageBuilderService.ts +180 -73
- package/src/tests/PageBuilderTest.vue +1 -1
- package/src/tests/componentsArray.test.json +12 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myissue/vue-website-page-builder",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.99",
|
|
4
4
|
"description": "Vue 3 page builder component with drag & drop functionality.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/vue-website-page-builder.umd.cjs",
|
|
@@ -52,12 +52,12 @@
|
|
|
52
52
|
"license": "MIT",
|
|
53
53
|
"repository": {
|
|
54
54
|
"type": "git",
|
|
55
|
-
"url": "
|
|
55
|
+
"url": "git+ssh://git@github.com/myissue-org/vue-website-page-builder.git"
|
|
56
56
|
},
|
|
57
57
|
"bugs": {
|
|
58
|
-
"url": "https://github.com/
|
|
58
|
+
"url": "https://github.com/myissue-org/vue-website-page-builder/issues"
|
|
59
59
|
},
|
|
60
|
-
"homepage": "https://mybuilder.dev",
|
|
60
|
+
"homepage": "https://www.mybuilder.dev",
|
|
61
61
|
"engines": {
|
|
62
62
|
"node": ">=18.0.0",
|
|
63
63
|
"npm": ">=8.0.0"
|
|
@@ -18,7 +18,7 @@ const version = __APP_VERSION__
|
|
|
18
18
|
</p>
|
|
19
19
|
<p class="pbx-myPrimaryParagraph pbx-mt-3">
|
|
20
20
|
<a
|
|
21
|
-
href="https://github.com/
|
|
21
|
+
href="https://github.com/myissue-org/vue-website-page-builder"
|
|
22
22
|
target="_blank"
|
|
23
23
|
class="pbx-myPrimaryLink pbx-text-myPrimaryDarkGrayColor"
|
|
24
24
|
>
|
|
@@ -73,13 +73,68 @@ function onScroll() {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
// generate HTML
|
|
77
76
|
const generateHTML = function (filename, HTML) {
|
|
77
|
+
// Extract existing styles from the page
|
|
78
|
+
const existingStyles = Array.from(document.querySelectorAll('style, link[rel="stylesheet"]'))
|
|
79
|
+
.map((style) => {
|
|
80
|
+
if (style.tagName === 'STYLE') {
|
|
81
|
+
return style.outerHTML // Inline styles
|
|
82
|
+
} else if (style.tagName === 'LINK') {
|
|
83
|
+
return `<link rel="stylesheet" href="${style.href}">` // External stylesheets
|
|
84
|
+
}
|
|
85
|
+
return ''
|
|
86
|
+
})
|
|
87
|
+
.join('\n')
|
|
88
|
+
|
|
89
|
+
// Add your custom CSS
|
|
90
|
+
const customCSS = `
|
|
91
|
+
<style>
|
|
92
|
+
#pagebuilder blockquote,
|
|
93
|
+
#pagebuilder dl,
|
|
94
|
+
#pagebuilder dd,
|
|
95
|
+
#pagebuilder pre,
|
|
96
|
+
#pagebuilder hr,
|
|
97
|
+
#pagebuilder figure,
|
|
98
|
+
#pagebuilder p,
|
|
99
|
+
#pagebuilder h1,
|
|
100
|
+
#pagebuilder h2,
|
|
101
|
+
#pagebuilder h3,
|
|
102
|
+
#pagebuilder h4,
|
|
103
|
+
#pagebuilder h5,
|
|
104
|
+
#pagebuilder h6,
|
|
105
|
+
#pagebuilder ul,
|
|
106
|
+
#pagebuilder ol,
|
|
107
|
+
#pagebuilder li {
|
|
108
|
+
margin: 0;
|
|
109
|
+
padding: 0; /* Often useful for ul/ol too */
|
|
110
|
+
}
|
|
111
|
+
</style>
|
|
112
|
+
`
|
|
113
|
+
|
|
114
|
+
// Combine existing styles and custom CSS
|
|
115
|
+
const css = `${existingStyles}\n${customCSS}`
|
|
116
|
+
|
|
117
|
+
// Generate the full HTML
|
|
118
|
+
const fullHTML = `
|
|
119
|
+
<!DOCTYPE html>
|
|
120
|
+
<html lang="en">
|
|
121
|
+
<head>
|
|
122
|
+
<meta charset="UTF-8">
|
|
123
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
124
|
+
<title>Downloaded HTML</title>
|
|
125
|
+
${css}
|
|
126
|
+
</head>
|
|
127
|
+
<body>
|
|
128
|
+
<div id="pagebuilder" class="pbx-font-sans pbx-text-black">
|
|
129
|
+
${HTML}
|
|
130
|
+
</div>
|
|
131
|
+
</body>
|
|
132
|
+
</html>
|
|
133
|
+
`
|
|
134
|
+
|
|
135
|
+
// Create and trigger the download
|
|
78
136
|
const element = document.createElement('a')
|
|
79
|
-
element.setAttribute(
|
|
80
|
-
'href',
|
|
81
|
-
'data:text/html;charset=utf-8,' + encodeURIComponent(fullHTMLContent(HTML)),
|
|
82
|
-
)
|
|
137
|
+
element.setAttribute('href', 'data:text/html;charset=utf-8,' + encodeURIComponent(fullHTML))
|
|
83
138
|
element.setAttribute('download', filename)
|
|
84
139
|
|
|
85
140
|
element.style.display = 'none'
|
|
@@ -96,7 +151,21 @@ const handleDownloadHTML = function () {
|
|
|
96
151
|
return
|
|
97
152
|
}
|
|
98
153
|
|
|
99
|
-
|
|
154
|
+
// Extract clean HTML
|
|
155
|
+
let html = extractCleanHTMLFromPageBuilder(pagebuilder)
|
|
156
|
+
|
|
157
|
+
// Create a temporary DOM element to manipulate the HTML
|
|
158
|
+
const tempDiv = document.createElement('div')
|
|
159
|
+
tempDiv.innerHTML = html
|
|
160
|
+
|
|
161
|
+
// Remove 'hovered' and 'selected' attributes
|
|
162
|
+
tempDiv.querySelectorAll('[hovered], [selected]').forEach((el) => {
|
|
163
|
+
el.removeAttribute('hovered')
|
|
164
|
+
el.removeAttribute('selected')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// Get the cleaned HTML back
|
|
168
|
+
html = tempDiv.innerHTML
|
|
100
169
|
|
|
101
170
|
generateHTML('downloaded_html.html', html)
|
|
102
171
|
}
|
|
@@ -51,18 +51,20 @@ const handleRedo = async function () {
|
|
|
51
51
|
class="pbx-flex-1 pbx-flex pbx-justify-center pbx-items-center pbx-py-2 pbx-w-full gap-1"
|
|
52
52
|
>
|
|
53
53
|
<!-- Undo Start -->
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
54
|
+
|
|
55
|
+
<div
|
|
56
|
+
@click="handleUndo"
|
|
57
|
+
type="button"
|
|
58
|
+
class="pbx-h-10 pbx-w-10 pbx-rounded-full pbx-flex pbx-items-center pbx-border-none pbx-justify-center pbx-bg-gray-50 pbx-aspect-square pbx-text-black hover:pbx-text-white"
|
|
59
|
+
:class="[
|
|
60
|
+
canUndo
|
|
61
|
+
? 'pbx-cursor-pointer hover:pbx-bg-myPrimaryLinkColor focus-visible:pbx-ring-0'
|
|
62
|
+
: 'pbx-cursor-not-allowed pbx-bg-opacity-20 hover:pbx-bg-gray-200',
|
|
63
|
+
]"
|
|
64
|
+
>
|
|
65
|
+
<span class="material-symbols-outlined"> undo </span>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
66
68
|
<!-- Undo End -->
|
|
67
69
|
<div
|
|
68
70
|
class="pbx-text-xs pbx-text-gray-600 pbx-mx-2 pbx-py-3 pbx-px-2 pbx-border-solid pbx-border pbx-border-gray-200 pbx-rounded-full"
|
|
@@ -70,18 +72,19 @@ const handleRedo = async function () {
|
|
|
70
72
|
{{ historyIndex + 1 }}/{{ historyLength }}
|
|
71
73
|
</div>
|
|
72
74
|
<!-- Redo Start -->
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
</
|
|
84
|
-
</
|
|
75
|
+
|
|
76
|
+
<div
|
|
77
|
+
@click="handleRedo"
|
|
78
|
+
class="pbx-h-10 pbx-w-10 pbx-rounded-full pbx-flex pbx-items-center pbx-border-none pbx-justify-center pbx-bg-gray-50 pbx-aspect-square pbx-text-black hover:pbx-text-white"
|
|
79
|
+
:class="[
|
|
80
|
+
canRedo
|
|
81
|
+
? 'pbx-cursor-pointer hover:pbx-bg-myPrimaryLinkColor focus-visible:pbx-ring-0'
|
|
82
|
+
: 'pbx-cursor-not-allowed pbx-bg-opacity-20 hover:pbx-bg-gray-200',
|
|
83
|
+
]"
|
|
84
|
+
>
|
|
85
|
+
<span class="material-symbols-outlined"> redo </span>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
85
88
|
<!-- Redo End -->
|
|
86
89
|
</div>
|
|
87
90
|
</template>
|
|
@@ -879,11 +879,11 @@ onMounted(async () => {
|
|
|
879
879
|
|
|
880
880
|
<div id="pagebuilder" class="pbx-text-black pbx-font-sans">
|
|
881
881
|
<template v-for="component in getComponents" :key="component.id">
|
|
882
|
-
<
|
|
882
|
+
<div
|
|
883
883
|
v-if="component.html_code"
|
|
884
884
|
v-html="component.html_code"
|
|
885
885
|
@mouseup="handleSelectComponent(component)"
|
|
886
|
-
></
|
|
886
|
+
></div>
|
|
887
887
|
</template>
|
|
888
888
|
</div>
|
|
889
889
|
</main>
|
package/src/css/style.css
CHANGED
|
@@ -314,11 +314,8 @@
|
|
|
314
314
|
@apply pbx-my-0 pbx-block pbx-text-sm pbx-font-normal pbx-text-myPrimaryDarkGrayColor pbx-text-left pbx-mb-2;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
-
.pbx-myPrimaryFormFocus {
|
|
318
|
-
@apply focus:pbx-ring-2 focus:pbx-ring-myPrimaryLinkColor focus:pbx-ring-offset-2 focus:pbx-border focus:pbx-border-gray-200;
|
|
319
|
-
}
|
|
320
317
|
.pbx-myPrimaryInput {
|
|
321
|
-
@apply pbx-block pbx-pr-8 pbx-text-left pbx-bg-white pbx-w-auto sm:pbx-text-sm pbx-font-normal pbx-text-myPrimaryDarkGrayColor placeholder:pbx-font-normal placeholder:pbx-accent-gray-300 focus:pbx-bg-white pbx-rounded-md pbx-py-3 pbx-px-3 pbx-border pbx-border-gray-300 pbx-shadow-sm focus:pbx-outline-none pbx-
|
|
318
|
+
@apply pbx-block pbx-pr-8 pbx-text-left pbx-bg-white pbx-w-auto sm:pbx-text-sm pbx-font-normal pbx-text-myPrimaryDarkGrayColor placeholder:pbx-font-normal placeholder:pbx-accent-gray-300 focus:pbx-bg-white pbx-rounded-md pbx-py-3 pbx-px-3 pbx-border pbx-border-gray-300 pbx-shadow-sm focus:pbx-outline-none pbx-h-full pbx-border-solid pbx-font-sans;
|
|
322
319
|
}
|
|
323
320
|
.pbx-myPrimaryInputNoBorder {
|
|
324
321
|
@apply pbx-myPrimaryInput placeholder:pbx-accent-gray-300 focus:pbx-bg-none pbx-rounded-md pbx-py-3 pbx-px-3 pbx-border-none focus:pbx-outline-none focus:pbx-ring-0 focus:pbx-ring-offset-0 focus:pbx-border-none pbx-shadow-none;
|
|
@@ -358,20 +355,6 @@
|
|
|
358
355
|
}
|
|
359
356
|
}
|
|
360
357
|
|
|
361
|
-
#page-builder-editor ol,
|
|
362
|
-
#page-builder-editor-editable-area ol,
|
|
363
|
-
#page-builder-editor ul,
|
|
364
|
-
#page-builder-editor-editable-area ul {
|
|
365
|
-
list-style: disc !important;
|
|
366
|
-
padding: 0 0 0 1rem;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
#page-builder-editor li,
|
|
370
|
-
#page-builder-editor-editable-area li {
|
|
371
|
-
line-height: 1.2;
|
|
372
|
-
margin-left: 1em; /* Adjust this value as needed */
|
|
373
|
-
}
|
|
374
|
-
|
|
375
358
|
#pbxEditToolbar {
|
|
376
359
|
opacity: 0;
|
|
377
360
|
pointer-events: none;
|
|
@@ -471,36 +454,83 @@
|
|
|
471
454
|
font-weight: 500;
|
|
472
455
|
}
|
|
473
456
|
|
|
474
|
-
#page-builder-editor ol,
|
|
475
|
-
#pagebuilder ol,
|
|
476
|
-
#page-builder-editor ul,
|
|
477
|
-
#pagebuilder ul {
|
|
478
|
-
list-style: disc !important;
|
|
479
|
-
padding: 0rem;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
457
|
.pbx-reorder-highlight {
|
|
483
458
|
animation: pbx-reorder-flash 0.4s ease-in-out;
|
|
484
459
|
}
|
|
485
460
|
|
|
486
461
|
@keyframes pbx-reorder-flash {
|
|
487
462
|
0% {
|
|
488
|
-
box-shadow: 0 0 0 0px rgba(66, 153, 225, 0.7);
|
|
489
463
|
transform: scale(1);
|
|
490
464
|
opacity: 1;
|
|
491
465
|
}
|
|
492
466
|
50% {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
opacity: 0.8;
|
|
467
|
+
transform: scale(0.8);
|
|
468
|
+
opacity: 0.6;
|
|
496
469
|
}
|
|
497
470
|
100% {
|
|
498
|
-
box-shadow: 0 0 0 0px rgba(66, 153, 225, 0);
|
|
499
471
|
transform: scale(1);
|
|
500
472
|
opacity: 1;
|
|
501
473
|
}
|
|
502
474
|
}
|
|
503
475
|
|
|
504
476
|
.pbx-sibling-highlight {
|
|
505
|
-
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.pbx-myPrimaryInput,
|
|
480
|
+
.pbx-myPrimarySelect {
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.pbx-myPrimaryInput:focus,
|
|
484
|
+
.pbx-myPrimarySelect:focus {
|
|
485
|
+
--tw-ring-inset: inset;
|
|
486
|
+
--tw-ring-opacity: 1;
|
|
487
|
+
/* Use the hex color with opacity variable */
|
|
488
|
+
--tw-ring-color: rgba(22, 163, 74, var(--tw-ring-opacity, 1));
|
|
489
|
+
|
|
490
|
+
--tw-ring-shadow: 0 0 0 4px var(--tw-ring-color);
|
|
491
|
+
|
|
492
|
+
--tw-ring-offset-width: 2px;
|
|
493
|
+
--tw-ring-offset-color: white;
|
|
494
|
+
--tw-ring-offset-shadow: 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
|
495
|
+
|
|
496
|
+
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow);
|
|
497
|
+
border: 1px #dee6f0 solid;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
#pagebuilder blockquote,
|
|
501
|
+
#pagebuilder dl,
|
|
502
|
+
#pagebuilder dd,
|
|
503
|
+
#pagebuilder pre,
|
|
504
|
+
#pagebuilder hr,
|
|
505
|
+
#pagebuilder figure,
|
|
506
|
+
#pagebuilder p,
|
|
507
|
+
#pagebuilder h1,
|
|
508
|
+
#pagebuilder h2,
|
|
509
|
+
#pagebuilder h3,
|
|
510
|
+
#pagebuilder h4,
|
|
511
|
+
#pagebuilder h5,
|
|
512
|
+
#pagebuilder h6,
|
|
513
|
+
#pagebuilder ul,
|
|
514
|
+
#pagebuilder ol,
|
|
515
|
+
#pagebuilder li {
|
|
516
|
+
margin: 0;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
#page-builder-editor ol,
|
|
520
|
+
#pagebuilder ol,
|
|
521
|
+
#page-builder-editor ul,
|
|
522
|
+
#pagebuilder ul {
|
|
523
|
+
list-style: disc !important;
|
|
524
|
+
padding: 1rem 0 0 1rem;
|
|
525
|
+
margin-left: 1em;
|
|
526
|
+
line-height: 1.2;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
#page-builder-editor ol,
|
|
530
|
+
#page-builder-editor-editable-area ol,
|
|
531
|
+
#page-builder-editor ul,
|
|
532
|
+
#page-builder-editor-editable-area ul {
|
|
533
|
+
list-style: disc !important;
|
|
534
|
+
padding: 1rem 0 0 1rem;
|
|
535
|
+
line-height: 1.2;
|
|
506
536
|
}
|
|
@@ -1043,9 +1043,11 @@ export class PageBuilderService {
|
|
|
1043
1043
|
/**
|
|
1044
1044
|
* Manually saves the current page builder content to local storage.
|
|
1045
1045
|
*/
|
|
1046
|
-
public handleManualSave = async () => {
|
|
1046
|
+
public handleManualSave = async (doNoClearHTML?: boolean) => {
|
|
1047
1047
|
this.pageBuilderStateStore.setIsSaving(true)
|
|
1048
|
-
|
|
1048
|
+
if (!doNoClearHTML) {
|
|
1049
|
+
this.clearHtmlSelection()
|
|
1050
|
+
}
|
|
1049
1051
|
this.startEditing()
|
|
1050
1052
|
this.saveDomComponentsToLocalStorage()
|
|
1051
1053
|
await delay(300)
|
|
@@ -1665,10 +1667,12 @@ export class PageBuilderService {
|
|
|
1665
1667
|
await nextTick()
|
|
1666
1668
|
|
|
1667
1669
|
// Scroll to the moved component
|
|
1668
|
-
const pageBuilderWrapper = document.querySelector(
|
|
1670
|
+
const pageBuilderWrapper = document.querySelector(
|
|
1671
|
+
'#page-builder-wrapper',
|
|
1672
|
+
) as HTMLElement | null
|
|
1669
1673
|
const movedComponentElement = pageBuilderWrapper?.querySelector(
|
|
1670
1674
|
`section[data-componentid="${componentToMove.id}"]`,
|
|
1671
|
-
)
|
|
1675
|
+
) as HTMLElement
|
|
1672
1676
|
|
|
1673
1677
|
if (movedComponentElement) {
|
|
1674
1678
|
// Apply highlight to the moved element
|
|
@@ -1685,19 +1689,22 @@ export class PageBuilderService {
|
|
|
1685
1689
|
nextSibling.classList.add('pbx-sibling-highlight')
|
|
1686
1690
|
}
|
|
1687
1691
|
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1692
|
+
if (pageBuilderWrapper) {
|
|
1693
|
+
// Scroll to the moved component
|
|
1694
|
+
const topPos = movedComponentElement.offsetTop - pageBuilderWrapper.offsetTop
|
|
1695
|
+
pageBuilderWrapper.scrollTop = topPos - pageBuilderWrapper.clientHeight / 2
|
|
1696
|
+
|
|
1697
|
+
// Remove highlights after a delay
|
|
1698
|
+
setTimeout(() => {
|
|
1699
|
+
movedComponentElement.classList.remove('pbx-reorder-highlight')
|
|
1700
|
+
if (prevSibling && prevSibling.tagName === 'SECTION') {
|
|
1701
|
+
prevSibling.classList.remove('pbx-sibling-highlight')
|
|
1702
|
+
}
|
|
1703
|
+
if (nextSibling && nextSibling.tagName === 'SECTION') {
|
|
1704
|
+
nextSibling.classList.remove('pbx-sibling-highlight')
|
|
1705
|
+
}
|
|
1706
|
+
}, 200)
|
|
1707
|
+
}
|
|
1701
1708
|
}
|
|
1702
1709
|
}
|
|
1703
1710
|
|
|
@@ -1920,6 +1927,9 @@ export class PageBuilderService {
|
|
|
1920
1927
|
pagebuilder.querySelectorAll('section[data-componentid]').forEach((section) => {
|
|
1921
1928
|
const sanitizedSection = this.cloneAndRemoveSelectionAttributes(section as HTMLElement)
|
|
1922
1929
|
|
|
1930
|
+
// Remove the data-componentid attribute
|
|
1931
|
+
sanitizedSection.removeAttribute('data-componentid')
|
|
1932
|
+
|
|
1923
1933
|
componentsToSave.push({
|
|
1924
1934
|
html_code: sanitizedSection.outerHTML,
|
|
1925
1935
|
title: sanitizedSection.getAttribute('data-component-title') || 'Untitled Component',
|
|
@@ -1938,7 +1948,29 @@ export class PageBuilderService {
|
|
|
1938
1948
|
}
|
|
1939
1949
|
|
|
1940
1950
|
const baseKey = this.getHistoryBaseKey()
|
|
1951
|
+
|
|
1941
1952
|
if (baseKey) {
|
|
1953
|
+
const currentDataRaw = localStorage.getItem(baseKey)
|
|
1954
|
+
if (currentDataRaw) {
|
|
1955
|
+
const currentData = JSON.parse(currentDataRaw)
|
|
1956
|
+
|
|
1957
|
+
// Compare components
|
|
1958
|
+
const currentComponents = currentData.components || []
|
|
1959
|
+
const newComponents = dataToSave.components || []
|
|
1960
|
+
|
|
1961
|
+
const hasChanges = newComponents.some((newComponent, index) => {
|
|
1962
|
+
const currentComponent = currentComponents[index]
|
|
1963
|
+
return (
|
|
1964
|
+
!currentComponent || // New component added
|
|
1965
|
+
currentComponent.html_code !== newComponent.html_code // Component HTML changed
|
|
1966
|
+
)
|
|
1967
|
+
})
|
|
1968
|
+
|
|
1969
|
+
if (!hasChanges) {
|
|
1970
|
+
return
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1942
1974
|
localStorage.setItem(baseKey, JSON.stringify(dataToSave))
|
|
1943
1975
|
let history = LocalStorageManager.getHistory(baseKey)
|
|
1944
1976
|
|
|
@@ -2588,6 +2620,134 @@ export class PageBuilderService {
|
|
|
2588
2620
|
}
|
|
2589
2621
|
}
|
|
2590
2622
|
|
|
2623
|
+
/**
|
|
2624
|
+
* Applies modified components by mounting them to the DOM and attaching listeners.
|
|
2625
|
+
* @param htmlString - The HTML string to apply
|
|
2626
|
+
* @returns {Promise<string | null>} - Returns error message if failed, otherwise null
|
|
2627
|
+
*/
|
|
2628
|
+
public async applyModifiedHTML(htmlString: string): Promise<string | null> {
|
|
2629
|
+
if (!htmlString || (typeof htmlString === 'string' && htmlString.length === 0)) {
|
|
2630
|
+
return 'No HTML content was provided. Please ensure a valid HTML string is passed.'
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
// Check if the htmlString contains any <section> tags
|
|
2634
|
+
if (/<section[\s>]/i.test(htmlString)) {
|
|
2635
|
+
return 'Error: The <section> tag cannot be used as it is already included inside this component.'
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
const tempDiv = document.createElement('div')
|
|
2639
|
+
tempDiv.innerHTML = htmlString.trim()
|
|
2640
|
+
|
|
2641
|
+
const parsedElement = tempDiv.firstElementChild as HTMLElement | null
|
|
2642
|
+
|
|
2643
|
+
if (!parsedElement) {
|
|
2644
|
+
return 'Could not parse element from HTML string.'
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
// Replace the actual DOM element
|
|
2648
|
+
const oldElement = this.pageBuilderStateStore.getElement
|
|
2649
|
+
|
|
2650
|
+
if (oldElement && oldElement.parentElement) {
|
|
2651
|
+
oldElement.replaceWith(parsedElement)
|
|
2652
|
+
|
|
2653
|
+
// Update the element in the store (now referencing the new one)
|
|
2654
|
+
this.pageBuilderStateStore.setElement(parsedElement)
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
await this.addListenersToEditableElements()
|
|
2658
|
+
await nextTick()
|
|
2659
|
+
return null
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
private validateMountingHTML(
|
|
2663
|
+
htmlString: string,
|
|
2664
|
+
options?: { logError?: boolean },
|
|
2665
|
+
): string | null {
|
|
2666
|
+
// Trim HTML string
|
|
2667
|
+
const trimmedData = htmlString.trim()
|
|
2668
|
+
const openingSectionMatches = htmlString.match(/<section\b[^>]*>/gi) || []
|
|
2669
|
+
const closingSectionMatches = htmlString.match(/<\/section>/gi) || []
|
|
2670
|
+
|
|
2671
|
+
if (!htmlString || htmlString.trim().length === 0) {
|
|
2672
|
+
const error = 'No HTML content was provided. Please ensure a valid HTML string is passed.'
|
|
2673
|
+
if (options && options.logError) {
|
|
2674
|
+
console.error(error)
|
|
2675
|
+
// Behavior
|
|
2676
|
+
return error
|
|
2677
|
+
}
|
|
2678
|
+
// default behavior
|
|
2679
|
+
return error
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
if (openingSectionMatches.length !== closingSectionMatches.length) {
|
|
2683
|
+
const error =
|
|
2684
|
+
'Uneven <section> tags detected in the provided HTML. Each component must be wrapped in its own properly paired <section>...</section>. ' +
|
|
2685
|
+
'Ensure that all <section> tags have a matching closing </section> tag.'
|
|
2686
|
+
|
|
2687
|
+
if (options && options.logError) {
|
|
2688
|
+
console.error(error)
|
|
2689
|
+
return error
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
return error
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
const tempDiv = document.createElement('div')
|
|
2696
|
+
tempDiv.innerHTML = trimmedData
|
|
2697
|
+
const nestedSection = tempDiv.querySelector('section section')
|
|
2698
|
+
if (nestedSection) {
|
|
2699
|
+
const error =
|
|
2700
|
+
'Nested <section> tags are not allowed. Please ensure that no <section> is placed inside another <section>.'
|
|
2701
|
+
if (options && options.logError) {
|
|
2702
|
+
console.error(error)
|
|
2703
|
+
return error
|
|
2704
|
+
}
|
|
2705
|
+
return error
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
// Return error since JSON data has been passed to mount HTML to DOM
|
|
2709
|
+
if (trimmedData.startsWith('[') || trimmedData.startsWith('{')) {
|
|
2710
|
+
const error =
|
|
2711
|
+
'Brackets [] or curly braces {} are not valid HTML. They are used for data formats like JSON.'
|
|
2712
|
+
if (options && options.logError) {
|
|
2713
|
+
console.error(error)
|
|
2714
|
+
return error
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
return error
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
return null
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
/**
|
|
2724
|
+
* Applies modified components by mounting them to the DOM and attaching listeners.
|
|
2725
|
+
* @param htmlString - The HTML string to apply
|
|
2726
|
+
* @returns {Promise<string | null>} - Returns error message if failed, otherwise null
|
|
2727
|
+
*/
|
|
2728
|
+
public async applyModifiedComponents(htmlString: string): Promise<string | null> {
|
|
2729
|
+
// Trim HTML string
|
|
2730
|
+
const trimmedData = htmlString.trim()
|
|
2731
|
+
|
|
2732
|
+
const openingSectionMatches = htmlString.match(/<section\b[^>]*>/gi) || []
|
|
2733
|
+
|
|
2734
|
+
if (openingSectionMatches.length === 0) {
|
|
2735
|
+
const error = 'No <section> tags found. Each component must be wrapped in a <section> tag.'
|
|
2736
|
+
if (error) {
|
|
2737
|
+
return error
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
const validationError = this.validateMountingHTML(trimmedData)
|
|
2742
|
+
if (validationError) return validationError
|
|
2743
|
+
|
|
2744
|
+
// also fixed to use `trimmedData`
|
|
2745
|
+
await this.mountComponentsToDOM(trimmedData)
|
|
2746
|
+
await this.addListenersToEditableElements()
|
|
2747
|
+
await nextTick()
|
|
2748
|
+
return null
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2591
2751
|
/**
|
|
2592
2752
|
* Mounts builder components to the DOM from an HTML string.
|
|
2593
2753
|
*
|
|
@@ -2606,13 +2766,11 @@ export class PageBuilderService {
|
|
|
2606
2766
|
htmlString: string,
|
|
2607
2767
|
usePassedPageSettings?: boolean,
|
|
2608
2768
|
): Promise<void> {
|
|
2769
|
+
// Trim HTML string
|
|
2609
2770
|
const trimmedData = htmlString.trim()
|
|
2610
2771
|
|
|
2611
|
-
|
|
2612
|
-
if (
|
|
2613
|
-
console.error('Error: JSON data passed to mountComponentsToDOM for the Page Builder Package.')
|
|
2614
|
-
return
|
|
2615
|
-
}
|
|
2772
|
+
const validationError = this.validateMountingHTML(trimmedData, { logError: true })
|
|
2773
|
+
if (validationError) return
|
|
2616
2774
|
|
|
2617
2775
|
// HTML string
|
|
2618
2776
|
try {
|
|
@@ -2851,57 +3009,6 @@ export class PageBuilderService {
|
|
|
2851
3009
|
}
|
|
2852
3010
|
}
|
|
2853
3011
|
|
|
2854
|
-
/**
|
|
2855
|
-
* Applies modified components by mounting them to the DOM and attaching listeners.
|
|
2856
|
-
* @param htmlString - The HTML string to apply
|
|
2857
|
-
* @returns {Promise<string | null>} - Returns error message if failed, otherwise null
|
|
2858
|
-
*/
|
|
2859
|
-
public async applyModifiedHTML(htmlString: string): Promise<string | null> {
|
|
2860
|
-
if (!htmlString || (typeof htmlString === 'string' && htmlString.length === 0)) {
|
|
2861
|
-
return 'No HTML content was provided. Please ensure a valid HTML string is passed.'
|
|
2862
|
-
}
|
|
2863
|
-
|
|
2864
|
-
const tempDiv = document.createElement('div')
|
|
2865
|
-
tempDiv.innerHTML = htmlString.trim()
|
|
2866
|
-
|
|
2867
|
-
const parsedElement = tempDiv.firstElementChild as HTMLElement | null
|
|
2868
|
-
|
|
2869
|
-
if (!parsedElement) {
|
|
2870
|
-
return 'Could not parse element from HTML string.'
|
|
2871
|
-
}
|
|
2872
|
-
|
|
2873
|
-
// Replace the actual DOM element
|
|
2874
|
-
const oldElement = this.pageBuilderStateStore.getElement
|
|
2875
|
-
|
|
2876
|
-
if (oldElement && oldElement.parentElement) {
|
|
2877
|
-
oldElement.replaceWith(parsedElement)
|
|
2878
|
-
|
|
2879
|
-
// Update the element in the store (now referencing the new one)
|
|
2880
|
-
this.pageBuilderStateStore.setElement(parsedElement)
|
|
2881
|
-
}
|
|
2882
|
-
|
|
2883
|
-
await this.addListenersToEditableElements()
|
|
2884
|
-
await nextTick()
|
|
2885
|
-
return null
|
|
2886
|
-
}
|
|
2887
|
-
|
|
2888
|
-
/**
|
|
2889
|
-
* Applies modified components by mounting them to the DOM and attaching listeners.
|
|
2890
|
-
* @param htmlString - The HTML string to apply
|
|
2891
|
-
* @returns {Promise<string | null>} - Returns error message if failed, otherwise null
|
|
2892
|
-
*/
|
|
2893
|
-
public async applyModifiedComponents(htmlString: string): Promise<string | null> {
|
|
2894
|
-
if (!htmlString || (typeof htmlString === 'string' && htmlString.length === 0)) {
|
|
2895
|
-
return 'No HTML content was provided. Please ensure a valid HTML string is passed.'
|
|
2896
|
-
}
|
|
2897
|
-
|
|
2898
|
-
await this.mountComponentsToDOM(htmlString)
|
|
2899
|
-
|
|
2900
|
-
await this.addListenersToEditableElements()
|
|
2901
|
-
await nextTick()
|
|
2902
|
-
return null
|
|
2903
|
-
}
|
|
2904
|
-
|
|
2905
3012
|
/**
|
|
2906
3013
|
* Initializes the styles for the currently selected element.
|
|
2907
3014
|
*/
|
|
@@ -3,7 +3,7 @@ import FullWidthElement from '../Components/Layouts/FullWidthElement.vue'
|
|
|
3
3
|
import PageBuilder from '../PageBuilder/PageBuilder.vue'
|
|
4
4
|
import DemoMediaLibraryComponentTest from '../tests/TestComponents/DemoMediaLibraryComponentTest.vue'
|
|
5
5
|
import DemoBuilderComponentsTest from '../tests/TestComponents/DemoBuilderComponentsTest.vue'
|
|
6
|
-
import {
|
|
6
|
+
import { computed, watch } from 'vue'
|
|
7
7
|
import componentsArray from '../tests/componentsArray.test.json'
|
|
8
8
|
import { getPageBuilder } from '../composables/builderInstance'
|
|
9
9
|
import { useTranslations } from '../composables/useTranslations'
|