@myissue/vue-website-page-builder 3.0.1
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/LICENSE +21 -0
- package/README.md +470 -0
- package/dist/android-chrome-192x192.png +0 -0
- package/dist/android-chrome-512x512.png +0 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/browserconfig.xml +9 -0
- package/dist/components.json +36 -0
- package/dist/favicon-16x16.png +0 -0
- package/dist/favicon-32x32.png +0 -0
- package/dist/favicon.ico +0 -0
- package/dist/home/features.jpg +0 -0
- package/dist/home/page-builder-example.jpg +0 -0
- package/dist/logo/logo.svg +49 -0
- package/dist/mstile-310x150.png +0 -0
- package/dist/mstile-310x310.png +0 -0
- package/dist/page-builder/2-images-text-top.png +0 -0
- package/dist/page-builder/2-images.png +0 -0
- package/dist/page-builder/3-images.png +0 -0
- package/dist/page-builder/6-images.png +0 -0
- package/dist/page-builder/image.png +0 -0
- package/dist/page-builder/placeholder.jpg +0 -0
- package/dist/page-builder/two-vertical-images.png +0 -0
- package/dist/placeholder_image.jpg +0 -0
- package/dist/robots.txt +2 -0
- package/dist/vue-website-page-builder.css +1 -0
- package/dist/vue-website-page-builder.js +24794 -0
- package/dist/vue-website-page-builder.umd.cjs +195 -0
- package/package.json +99 -0
- package/src/App.vue +122 -0
- package/src/Components/Homepage/Footer.vue +42 -0
- package/src/Components/Homepage/HomeSection.vue +540 -0
- package/src/Components/Homepage/Navbar.vue +30 -0
- package/src/Components/Layouts/FullWidthElement.vue +34 -0
- package/src/Components/Loaders/FullScreenSpinner.vue +13 -0
- package/src/Components/Loaders/SmallUniversalSpinner.vue +26 -0
- package/src/Components/MediaLibrary/SidebarUnsplash.vue +40 -0
- package/src/Components/MediaLibrary/Unsplash.vue +306 -0
- package/src/Components/Modals/DynamicModal.vue +476 -0
- package/src/Components/Modals/MediaLibraryModal.vue +418 -0
- package/src/Components/Modals/Modal.vue +102 -0
- package/src/Components/Modals/PageBuilderModal.vue +233 -0
- package/src/Components/Modals/PageBuilderPreviewModal.vue +123 -0
- package/src/Components/PageBuilder/DropdownsPlusToggles/OptionsDropdown.vue +202 -0
- package/src/Components/PageBuilder/DropdownsPlusToggles/SaveDesign.vue +7 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/BackgroundColorEditor.vue +91 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/BorderRadius.vue +163 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/Borders.vue +164 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/ClassEditor.vue +80 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/ComponentTopMenu.vue +93 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/DeleteElement.vue +42 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/EditGetElement.vue +338 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/ElementEditor.vue +58 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/ImageEditor.vue +82 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/LinkEditor.vue +301 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/ManageBackgroundOpacity.vue +109 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/ManageOpacity.vue +107 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/OpacityEditor.vue +16 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/PaddingPlusMargin.vue +134 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/TextColorEditor.vue +91 -0
- package/src/Components/PageBuilder/EditorMenu/Editables/Typography.vue +199 -0
- package/src/Components/PageBuilder/EditorMenu/EditorAccordion.vue +42 -0
- package/src/Components/PageBuilder/EditorMenu/RightSidebarEditor.vue +113 -0
- package/src/Components/PageBuilder/Settings/AdvancedPageBuilderSettings.vue +229 -0
- package/src/Components/PageBuilder/Settings/PageBuilderSettings.vue +101 -0
- package/src/Components/PageBuilder/Slidebars/SlideOverRight.vue +91 -0
- package/src/Components/PageBuilder/Slidebars/SlideOverRightParent.vue +91 -0
- package/src/Components/Search/SearchComponents.vue +259 -0
- package/src/Components/TipTap/TipTap.vue +32 -0
- package/src/Components/TipTap/TipTapInput.vue +325 -0
- package/src/PageBuilder/PageBuilder.vue +347 -0
- package/src/PageBuilder/Preview.vue +24 -0
- package/src/composables/PageBuilder.ts +1483 -0
- package/src/composables/delay.ts +5 -0
- package/src/composables/extract-text-content-html.ts +34 -0
- package/src/composables/isObject.ts +6 -0
- package/src/composables/usePromise.ts +10 -0
- package/src/composables/vueFetch.ts +278 -0
- package/src/css/app.css +614 -0
- package/src/index.ts +16 -0
- package/src/main.ts +11 -0
- package/src/stores/media-library.ts +34 -0
- package/src/stores/page-builder-state.ts +461 -0
- package/src/stores/unsplash.ts +107 -0
- package/src/stores/user.ts +30 -0
- package/src/types/global.d.ts +49 -0
- package/src/utils/builder/compiledCSS.ts +2205 -0
- package/src/utils/builder/html-doc-declaration-with-components.ts +7 -0
- package/src/utils/builder/html-elements/componentHelpers.ts +101 -0
- package/src/utils/builder/tailwaind-colors.ts +503 -0
- package/src/utils/builder/tailwind-border-radius.ts +67 -0
- package/src/utils/builder/tailwind-border-style-width-color.ts +272 -0
- package/src/utils/builder/tailwind-font-sizes.ts +76 -0
- package/src/utils/builder/tailwind-font-styles.ts +24 -0
- package/src/utils/builder/tailwind-opacities.ts +45 -0
- package/src/utils/builder/tailwind-padding-margin.ts +159 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { Editor, useEditor, EditorContent } from '@tiptap/vue-3'
|
|
3
|
+
import StarterKit from '@tiptap/starter-kit'
|
|
4
|
+
import { computed, onBeforeMount, onMounted, ref, watch } from 'vue'
|
|
5
|
+
import PageBuilder from '@/composables/PageBuilder.ts'
|
|
6
|
+
import Link from '@tiptap/extension-link'
|
|
7
|
+
import DynamicModal from '@/Components/Modals/DynamicModal.vue'
|
|
8
|
+
import { usePageBuilderStateStore } from '@/stores/page-builder-state'
|
|
9
|
+
import { useMediaLibraryStore } from '@/stores/media-library'
|
|
10
|
+
|
|
11
|
+
const mediaLibraryStore = useMediaLibraryStore()
|
|
12
|
+
const pageBuilderStateStore = usePageBuilderStateStore()
|
|
13
|
+
const showModalUrl = ref(false)
|
|
14
|
+
|
|
15
|
+
// use dynamic model
|
|
16
|
+
const typeModal = ref('')
|
|
17
|
+
const gridColumnModal = ref(Number(1))
|
|
18
|
+
const titleModal = ref('')
|
|
19
|
+
const descriptionModal = ref('')
|
|
20
|
+
const firstButtonModal = ref('')
|
|
21
|
+
const secondButtonModal = ref(null)
|
|
22
|
+
const thirdButtonModal = ref(null)
|
|
23
|
+
// set dynamic modal handle functions
|
|
24
|
+
const firstModalButtonFunction = ref(null)
|
|
25
|
+
const secondModalButtonFunction = ref(null)
|
|
26
|
+
const thirdModalButtonFunction = ref(null)
|
|
27
|
+
|
|
28
|
+
const pageBuilder = new PageBuilder(pageBuilderStateStore, mediaLibraryStore)
|
|
29
|
+
|
|
30
|
+
const getElement = computed(() => {
|
|
31
|
+
return pageBuilderStateStore.getElement
|
|
32
|
+
})
|
|
33
|
+
const textContentVueModel = ref('')
|
|
34
|
+
|
|
35
|
+
const textContent = computed(() => {
|
|
36
|
+
if (editor.value) {
|
|
37
|
+
return editor.value.getHTML()
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const getElementtextContentLength = ref(0)
|
|
42
|
+
|
|
43
|
+
watch(getElement, (newVal) => {
|
|
44
|
+
const tempContainer = document.createElement('div')
|
|
45
|
+
|
|
46
|
+
if (newVal) {
|
|
47
|
+
tempContainer.innerHTML = newVal
|
|
48
|
+
const textContent = tempContainer.textContent
|
|
49
|
+
getElementtextContentLength.value = textContent.length
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Check if any of the child elements have the tagName "IMG"
|
|
54
|
+
|
|
55
|
+
const editor = useEditor({
|
|
56
|
+
content: '',
|
|
57
|
+
extensions: [
|
|
58
|
+
StarterKit,
|
|
59
|
+
Link.configure({
|
|
60
|
+
openOnClick: false,
|
|
61
|
+
}),
|
|
62
|
+
],
|
|
63
|
+
editorProps: {
|
|
64
|
+
attributes: {
|
|
65
|
+
class: 'prose-sm sm:prose-sm lg:prose-sm focus:outline-none',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
// watch for changes in textContent and update store and textContentVueModel
|
|
71
|
+
watch(textContent, (newValue) => {
|
|
72
|
+
if (!pageBuilder.selectedElementIsValidText()) return
|
|
73
|
+
|
|
74
|
+
if (getElement.value) {
|
|
75
|
+
pageBuilderStateStore.setTextAreaVueModel(newValue)
|
|
76
|
+
|
|
77
|
+
if (typeof newValue === 'string' && newValue !== textContentVueModel.value) {
|
|
78
|
+
pageBuilder.handleTextInput(newValue)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// pageBuilder.selectedElementIsValidText(newValue);
|
|
84
|
+
|
|
85
|
+
const TipTapSetContent = function () {
|
|
86
|
+
if (!pageBuilder.selectedElementIsValidText()) return
|
|
87
|
+
|
|
88
|
+
if (editor.value) {
|
|
89
|
+
editor.value.commands.setContent(getElement.value.innerHTML)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
watch(getElement, () => {
|
|
94
|
+
TipTapSetContent()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Manage URL
|
|
98
|
+
const urlEnteret = ref('')
|
|
99
|
+
const newUpdatedExistingURL = ref('')
|
|
100
|
+
const urlError = ref(null)
|
|
101
|
+
|
|
102
|
+
watch(urlEnteret, (newVal) => {
|
|
103
|
+
newUpdatedExistingURL.value = newVal
|
|
104
|
+
})
|
|
105
|
+
const handleURL = function () {
|
|
106
|
+
urlEnteret.value = editor.value.getAttributes('link').href
|
|
107
|
+
|
|
108
|
+
showModalUrl.value = true
|
|
109
|
+
typeModal.value = 'success'
|
|
110
|
+
gridColumnModal.value = 2
|
|
111
|
+
titleModal.value = 'Enter URL'
|
|
112
|
+
descriptionModal.value = null
|
|
113
|
+
firstButtonModal.value = 'Close'
|
|
114
|
+
secondButtonModal.value = urlEnteret.value ? 'Remove url' : null
|
|
115
|
+
thirdButtonModal.value = 'Save'
|
|
116
|
+
|
|
117
|
+
// handle click
|
|
118
|
+
firstModalButtonFunction.value = function () {
|
|
119
|
+
showModalUrl.value = false
|
|
120
|
+
urlError.value = null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// handle click
|
|
124
|
+
secondModalButtonFunction.value = function () {
|
|
125
|
+
editor.value.chain().focus().extendMarkRange('link').unsetLink().run()
|
|
126
|
+
showModalUrl.value = false
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// handle click
|
|
130
|
+
thirdModalButtonFunction.value = function () {
|
|
131
|
+
const isNotValidated = validateURL()
|
|
132
|
+
if (isNotValidated) {
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
if (!isNotValidated) {
|
|
136
|
+
setEnteretURL()
|
|
137
|
+
}
|
|
138
|
+
showModalUrl.value = false
|
|
139
|
+
}
|
|
140
|
+
// end modal
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//
|
|
144
|
+
//
|
|
145
|
+
const validateURL = function () {
|
|
146
|
+
// initial value
|
|
147
|
+
urlError.value = null
|
|
148
|
+
|
|
149
|
+
// url validation
|
|
150
|
+
const urlRegex = /^https?:\/\//
|
|
151
|
+
const isValidURL = ref(true)
|
|
152
|
+
isValidURL.value = urlRegex.test(newUpdatedExistingURL.value)
|
|
153
|
+
|
|
154
|
+
// cancelled
|
|
155
|
+
if (isValidURL.value === false) {
|
|
156
|
+
urlError.value =
|
|
157
|
+
"The provided URL is invalid. Please ensure that it begins with 'https://' for proper formatting and security."
|
|
158
|
+
return true
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
const setEnteretURL = function () {
|
|
164
|
+
// update link
|
|
165
|
+
editor.value
|
|
166
|
+
.chain()
|
|
167
|
+
.focus()
|
|
168
|
+
.extendMarkRange('link')
|
|
169
|
+
.setLink({ href: newUpdatedExistingURL.value })
|
|
170
|
+
.run()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
onBeforeMount(() => {
|
|
174
|
+
editor.value?.destroy()
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
onMounted(() => {
|
|
178
|
+
TipTapSetContent()
|
|
179
|
+
})
|
|
180
|
+
</script>
|
|
181
|
+
<template>
|
|
182
|
+
<DynamicModal
|
|
183
|
+
:show="showModalUrl"
|
|
184
|
+
:type="typeModal"
|
|
185
|
+
:gridColumnAmount="gridColumnModal"
|
|
186
|
+
:title="titleModal"
|
|
187
|
+
:description="descriptionModal"
|
|
188
|
+
:firstButtonText="firstButtonModal"
|
|
189
|
+
:secondButtonText="secondButtonModal"
|
|
190
|
+
:thirdButtonText="thirdButtonModal"
|
|
191
|
+
@firstModalButtonFunction="firstModalButtonFunction"
|
|
192
|
+
@secondModalButtonFunction="secondModalButtonFunction"
|
|
193
|
+
@thirdModalButtonFunction="thirdModalButtonFunction"
|
|
194
|
+
>
|
|
195
|
+
<header></header>
|
|
196
|
+
<main>
|
|
197
|
+
<div class="myInputGroup">
|
|
198
|
+
<label class="myPrimaryInputLabel" for="roles"><span>Enter URL</span></label
|
|
199
|
+
><input v-model="urlEnteret" class="myPrimaryInput mt-1" type="url" placeholder="url" />
|
|
200
|
+
<div v-if="urlError" class="min-h-[2.5rem] flex items-center justify-start">
|
|
201
|
+
<p class="myPrimaryInputError mt-2 mb-0 py-0 self-start">
|
|
202
|
+
{{ urlError }}
|
|
203
|
+
</p>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</main>
|
|
207
|
+
</DynamicModal>
|
|
208
|
+
|
|
209
|
+
<div class="blockease-linear duration-200 block ease-linear">
|
|
210
|
+
<div v-if="pageBuilder.selectedElementIsValidText() && editor">
|
|
211
|
+
<div class="relative rounded-lg">
|
|
212
|
+
<div
|
|
213
|
+
class="flex justify-between myPrimaryGap items-center divide-x divide-gray-200 py-4 px-4 overflow-x-auto border-b border-gray-20"
|
|
214
|
+
>
|
|
215
|
+
<div class="flex items-center 0 divide-x divide-gray-200">
|
|
216
|
+
<div class="px-2 flex items-center justify-start gap-2">
|
|
217
|
+
<button
|
|
218
|
+
@click="editor.chain().focus().setHardBreak().run()"
|
|
219
|
+
type="button"
|
|
220
|
+
class="text-[12.5px] gap-2 text-nowrap pl-2 pr-3 w-full h-10 cursor-pointer rounded-full flex items-center border-none justify-center bg-gray-50 hover:bg-myPrimaryLinkColor hover:text-white focus-visible:ring-0"
|
|
221
|
+
>
|
|
222
|
+
<span class="material-symbols-outlined"> keyboard_return </span>
|
|
223
|
+
<span>Line break</span>
|
|
224
|
+
</button>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<div class="px-2 flex items-center justify-start gap-2">
|
|
228
|
+
<button
|
|
229
|
+
@click="editor.chain().focus().toggleBold().run()"
|
|
230
|
+
type="button"
|
|
231
|
+
class="text-[12.5px] gap-2 text-nowrap pl-2 pr-3 w-full h-10 cursor-pointer rounded-full flex items-center border-none justify-center bg-gray-50 hover:bg-myPrimaryLinkColor hover:text-white focus-visible:ring-0"
|
|
232
|
+
:class="{
|
|
233
|
+
'bg-myPrimaryLinkColor text-white': editor.isActive('bold'),
|
|
234
|
+
}"
|
|
235
|
+
>
|
|
236
|
+
<span class="material-symbols-outlined"> format_bold </span>
|
|
237
|
+
<span>Bold</span>
|
|
238
|
+
</button>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<div class="px-2 flex items-center justify-start gap-2">
|
|
242
|
+
<button
|
|
243
|
+
@click="handleURL"
|
|
244
|
+
type="button"
|
|
245
|
+
class="text-[12.5px] gap-2 text-nowrap pl-2 pr-3 w-full h-10 cursor-pointer rounded-full flex items-center border-none justify-center bg-gray-50 hover:bg-myPrimaryLinkColor hover:text-white focus-visible:ring-0"
|
|
246
|
+
:class="{
|
|
247
|
+
'bg-myPrimaryLinkColor text-white': editor.isActive('link'),
|
|
248
|
+
}"
|
|
249
|
+
>
|
|
250
|
+
<span class="material-symbols-outlined"> link </span>
|
|
251
|
+
<span>Link</span>
|
|
252
|
+
</button>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<div class="px-2 flex items-center justify-start gap-2">
|
|
256
|
+
<button
|
|
257
|
+
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
|
|
258
|
+
type="button"
|
|
259
|
+
class="text-[12.5px] gap-2 text-nowrap pl-2 pr-3 w-full h-10 cursor-pointer rounded-full flex items-center border-none justify-center bg-gray-50 hover:bg-myPrimaryLinkColor hover:text-white focus-visible:ring-0"
|
|
260
|
+
:class="{
|
|
261
|
+
'bg-myPrimaryLinkColor text-white': editor.isActive('heading', {
|
|
262
|
+
level: 2,
|
|
263
|
+
}),
|
|
264
|
+
}"
|
|
265
|
+
>
|
|
266
|
+
<span class="material-symbols-outlined"> titlecase </span>
|
|
267
|
+
<span>Header 2</span>
|
|
268
|
+
</button>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div class="px-2 flex items-center justify-start gap-2">
|
|
272
|
+
<button
|
|
273
|
+
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
|
|
274
|
+
type="button"
|
|
275
|
+
class="text-[12.5px] gap-2 text-nowrap pl-2 pr-3 w-full h-10 cursor-pointer rounded-full flex items-center border-none justify-center bg-gray-50 hover:bg-myPrimaryLinkColor hover:text-white focus-visible:ring-0"
|
|
276
|
+
:class="{
|
|
277
|
+
'bg-myPrimaryLinkColor text-white': editor.isActive('heading', {
|
|
278
|
+
level: 3,
|
|
279
|
+
}),
|
|
280
|
+
}"
|
|
281
|
+
>
|
|
282
|
+
<span class="material-symbols-outlined"> titlecase </span>
|
|
283
|
+
<span>Header 3</span>
|
|
284
|
+
</button>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<div class="px-2 flex items-center justify-start gap-2">
|
|
288
|
+
<button
|
|
289
|
+
@click="editor.chain().focus().toggleBulletList().run()"
|
|
290
|
+
type="button"
|
|
291
|
+
class="text-[12.5px] gap-2 text-nowrap pl-2 pr-3 w-full h-10 cursor-pointer rounded-full flex items-center border-none justify-center bg-gray-50 hover:bg-myPrimaryLinkColor hover:text-white focus-visible:ring-0"
|
|
292
|
+
:class="{
|
|
293
|
+
'bg-myPrimaryLinkColor text-white': editor.isActive('bulletList'),
|
|
294
|
+
}"
|
|
295
|
+
>
|
|
296
|
+
<span class="material-symbols-outlined"> list </span>
|
|
297
|
+
<span>List</span>
|
|
298
|
+
</button>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
<div>
|
|
302
|
+
<div>
|
|
303
|
+
<div class="px-2 flex items-center justify-start gap-2">
|
|
304
|
+
<button
|
|
305
|
+
@click="pageBuilderStateStore.setShowModalTipTap(false)"
|
|
306
|
+
type="button"
|
|
307
|
+
class="text-[12.5px] gap-2 text-nowrap pl-2 pr-3 w-full h-10 cursor-pointer rounded-full flex items-center border-none justify-center bg-gray-50 hover:bg-myPrimaryLinkColor hover:text-white focus-visible:ring-0"
|
|
308
|
+
>
|
|
309
|
+
<span class="material-symbols-outlined"> Save </span>
|
|
310
|
+
<span>Save</span>
|
|
311
|
+
</button>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
<editor-content
|
|
318
|
+
id="page-builder-editor"
|
|
319
|
+
:editor="editor"
|
|
320
|
+
class="px-4 pt-6 pb-12 bg-white rounded-lg lg:min-h-[20rem] lg:max-h-[30rem] md:min-h-[20rem] md:max-h-[20rem] min-h-[20rem] max-h-[20rem] overflow-y-auto"
|
|
321
|
+
/>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
</template>
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { onMounted, computed, ref, watch, provide } from 'vue'
|
|
3
|
+
import { createPinia } from 'pinia'
|
|
4
|
+
import PageBuilder from '@/composables/PageBuilder.ts'
|
|
5
|
+
import PageBuilderPreviewModal from '@/Components/Modals/PageBuilderPreviewModal.vue'
|
|
6
|
+
import Preview from '@/PageBuilder/Preview.vue'
|
|
7
|
+
import ComponentTopMenu from '@/Components/PageBuilder/EditorMenu/Editables/ComponentTopMenu.vue'
|
|
8
|
+
import EditGetElement from '@/Components/PageBuilder/EditorMenu/Editables/EditGetElement.vue'
|
|
9
|
+
import SearchComponents from '@/Components/Search/SearchComponents.vue'
|
|
10
|
+
import OptionsDropdown from '@/Components/PageBuilder/DropdownsPlusToggles/OptionsDropdown.vue'
|
|
11
|
+
import RightSidebarEditor from '@/Components/PageBuilder/EditorMenu/RightSidebarEditor.vue'
|
|
12
|
+
import { usePageBuilderStateStore } from '@/stores/page-builder-state'
|
|
13
|
+
import { useMediaLibraryStore } from '@/stores/media-library'
|
|
14
|
+
|
|
15
|
+
// Create internal Pinia instance for this component if none exists
|
|
16
|
+
let internalPinia = null
|
|
17
|
+
let mediaLibraryStore = null
|
|
18
|
+
let pageBuilderStateStore = null
|
|
19
|
+
let pageBuilder = null
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Try to use existing Pinia first
|
|
23
|
+
mediaLibraryStore = useMediaLibraryStore()
|
|
24
|
+
pageBuilderStateStore = usePageBuilderStateStore()
|
|
25
|
+
} catch (error) {
|
|
26
|
+
// If no Pinia exists, create our own internal instance
|
|
27
|
+
internalPinia = createPinia()
|
|
28
|
+
mediaLibraryStore = useMediaLibraryStore(internalPinia)
|
|
29
|
+
pageBuilderStateStore = usePageBuilderStateStore(internalPinia)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Initialize PageBuilder with stores
|
|
33
|
+
pageBuilder = new PageBuilder(pageBuilderStateStore, mediaLibraryStore)
|
|
34
|
+
|
|
35
|
+
// Provide the stores for child components if using internal Pinia
|
|
36
|
+
if (internalPinia) {
|
|
37
|
+
provide('pageBuilderStateStore', pageBuilderStateStore)
|
|
38
|
+
provide('mediaLibraryStore', mediaLibraryStore)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const getMenuRight = computed(() => {
|
|
42
|
+
return pageBuilderStateStore.getMenuRight
|
|
43
|
+
})
|
|
44
|
+
const previewCurrentDesign = function () {
|
|
45
|
+
pageBuilder.previewCurrentDesign()
|
|
46
|
+
}
|
|
47
|
+
const openPageBuilderPreviewModal = ref(false)
|
|
48
|
+
const firstPageBuilderPreviewModalButton = ref(null)
|
|
49
|
+
|
|
50
|
+
const handlePageBuilderPreview = function () {
|
|
51
|
+
previewCurrentDesign()
|
|
52
|
+
|
|
53
|
+
openPageBuilderPreviewModal.value = true
|
|
54
|
+
// handle click
|
|
55
|
+
firstPageBuilderPreviewModalButton.value = function () {
|
|
56
|
+
openPageBuilderPreviewModal.value = false
|
|
57
|
+
}
|
|
58
|
+
// end modal
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const showModalAddComponent = ref(false)
|
|
62
|
+
const titleModalAddComponent = ref('')
|
|
63
|
+
const firstButtonTextSearchComponents = ref('')
|
|
64
|
+
const firstModalButtonSearchComponentsFunction = ref(null)
|
|
65
|
+
|
|
66
|
+
const handleAddComponent = function () {
|
|
67
|
+
pageBuilderStateStore.setComponent(null)
|
|
68
|
+
|
|
69
|
+
//
|
|
70
|
+
titleModalAddComponent.value = 'Add Components to Page'
|
|
71
|
+
firstButtonTextSearchComponents.value = 'Close'
|
|
72
|
+
showModalAddComponent.value = true
|
|
73
|
+
|
|
74
|
+
firstModalButtonSearchComponentsFunction.value = function () {
|
|
75
|
+
// handle show modal for unique content
|
|
76
|
+
showModalAddComponent.value = false
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// end modal
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const getComponents = computed(() => {
|
|
83
|
+
return pageBuilderStateStore.getComponents
|
|
84
|
+
})
|
|
85
|
+
const getComponent = computed(() => {
|
|
86
|
+
return pageBuilderStateStore.getComponent
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const getElement = computed(() => {
|
|
90
|
+
return pageBuilderStateStore.getElement
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const getElementAttributes = computed(() => {
|
|
94
|
+
if (!getElement.value || !(getElement.value instanceof HTMLElement)) {
|
|
95
|
+
return new Object()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Extract the attributes you want to watch
|
|
99
|
+
const attributesToWatch = {
|
|
100
|
+
src: getElement.value.getAttribute('src'),
|
|
101
|
+
href: getElement.value.getAttribute('href'),
|
|
102
|
+
style: getElement.value.getAttribute('style'),
|
|
103
|
+
class: getElement.value.getAttribute('class'),
|
|
104
|
+
dataImage: getElement.value.getAttribute('data-image'),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return attributesToWatch
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
watch(getElementAttributes, (newAttributes, oldAttributes) => {
|
|
111
|
+
// Check if any of the specified attributes have changed
|
|
112
|
+
if (
|
|
113
|
+
newAttributes?.src !== oldAttributes?.src ||
|
|
114
|
+
newAttributes?.href !== oldAttributes?.href ||
|
|
115
|
+
newAttributes?.style !== oldAttributes?.style ||
|
|
116
|
+
newAttributes?.class !== oldAttributes?.class ||
|
|
117
|
+
newAttributes?.dataImage !== oldAttributes?.dataImage
|
|
118
|
+
) {
|
|
119
|
+
// Trigger your method when any of the specified attributes change
|
|
120
|
+
pageBuilder.handlePageBuilderMethods()
|
|
121
|
+
pageBuilder.setEventListenersForElements()
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const handleSelectComponent = function (componentObject) {
|
|
126
|
+
pageBuilderStateStore.setComponent(componentObject)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const draggableZone = ref(null)
|
|
130
|
+
|
|
131
|
+
onMounted(async () => {
|
|
132
|
+
pageBuilder.setEventListenersForElements()
|
|
133
|
+
})
|
|
134
|
+
</script>
|
|
135
|
+
|
|
136
|
+
<template>
|
|
137
|
+
<div>
|
|
138
|
+
<SearchComponents
|
|
139
|
+
v-if="showModalAddComponent"
|
|
140
|
+
:show="showModalAddComponent"
|
|
141
|
+
:firstButtonText="firstButtonTextSearchComponents"
|
|
142
|
+
:title="titleModalAddComponent"
|
|
143
|
+
@firstModalButtonSearchComponentsFunction="firstModalButtonSearchComponentsFunction"
|
|
144
|
+
></SearchComponents>
|
|
145
|
+
<PageBuilderPreviewModal
|
|
146
|
+
:show="openPageBuilderPreviewModal"
|
|
147
|
+
@firstPageBuilderPreviewModalButton="firstPageBuilderPreviewModalButton"
|
|
148
|
+
>
|
|
149
|
+
<Preview></Preview>
|
|
150
|
+
</PageBuilderPreviewModal>
|
|
151
|
+
|
|
152
|
+
<div class="w-full inset-x-0 h-[90vh] z-10 bg-white overflow-x-scroll lg:pt-2 pt-2">
|
|
153
|
+
<div class="relative h-full flex">
|
|
154
|
+
<div
|
|
155
|
+
@click.self="pageBuilderStateStore.setComponent(null)"
|
|
156
|
+
class="min-w-[3.5rem] pt-6 pb-2 my-2 mx-2 bg-myPrimaryLightGrayColor rounded-full shadow"
|
|
157
|
+
>
|
|
158
|
+
<div class="mx-2 flex flex-col myPrimaryGap">
|
|
159
|
+
<div class="flex gap-2 items-center justify-center">
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
@click="
|
|
163
|
+
() => {
|
|
164
|
+
pageBuilderStateStore.setComponentArrayAddMethod('unshift')
|
|
165
|
+
handleAddComponent()
|
|
166
|
+
}
|
|
167
|
+
"
|
|
168
|
+
class="h-10 w-10 cursor-pointer rounded-full flex items-center border-none justify-center bg-gray-50 aspect-square hover:bg-myPrimaryLinkColor hover:text-white focus-visible:ring-0"
|
|
169
|
+
>
|
|
170
|
+
<span class="myMediumIcon material-symbols-outlined"> add </span>
|
|
171
|
+
</button>
|
|
172
|
+
</div>
|
|
173
|
+
<div @click.self="pageBuilderStateStore.setComponent(null)">
|
|
174
|
+
<ComponentTopMenu v-if="getElement"></ComponentTopMenu>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
<main class="flex flex-col h-full grow rounded-2xl duration-300 shadow-2xl">
|
|
179
|
+
<div
|
|
180
|
+
class="flex items-center justify-between primary-gap rounded-t-2xl bg-myPrimaryLightGrayColor"
|
|
181
|
+
>
|
|
182
|
+
<div
|
|
183
|
+
@click.self="pageBuilderStateStore.setComponent(null)"
|
|
184
|
+
class="w-4/12 flex justify-start items-center py-2 pl-2 h-full"
|
|
185
|
+
>
|
|
186
|
+
<div class="flex gap-2">
|
|
187
|
+
<span class="w-2 h-2 rounded-full bg-red-400"></span>
|
|
188
|
+
<span class="w-2 h-2 rounded-full bg-yellow-400"></span>
|
|
189
|
+
<span class="w-2 h-2 rounded-full bg-green-400"></span>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<div
|
|
194
|
+
@click.self="pageBuilderStateStore.setComponent(null)"
|
|
195
|
+
class="w-4/12 flex justify-center py-2"
|
|
196
|
+
>
|
|
197
|
+
<OptionsDropdown @previewCurrentDesign="previewCurrentDesign"></OptionsDropdown>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<div
|
|
201
|
+
@click.self="pageBuilderStateStore.setComponent(null)"
|
|
202
|
+
class="w-4/12 flex justify-end py-2 pr-2"
|
|
203
|
+
>
|
|
204
|
+
<div class="flex items-center justify-center gap-4">
|
|
205
|
+
<button
|
|
206
|
+
type="button"
|
|
207
|
+
@click="
|
|
208
|
+
() => {
|
|
209
|
+
pageBuilderStateStore.setComponentArrayAddMethod('unshift')
|
|
210
|
+
handleAddComponent()
|
|
211
|
+
}
|
|
212
|
+
"
|
|
213
|
+
>
|
|
214
|
+
<div class="flex items-center justify-center gap-2">
|
|
215
|
+
<span class="lg:block hidden"> Add new Component - v.1.0.6 </span>
|
|
216
|
+
|
|
217
|
+
<span
|
|
218
|
+
class="h-10 w-10 cursor-pointer rounded-full flex items-center border-none justify-center bg-gray-50 aspect-square hover:bg-myPrimaryLinkColor hover:text-white focus-visible:ring-0"
|
|
219
|
+
>
|
|
220
|
+
<span class="myMediumIcon material-symbols-outlined"> add </span>
|
|
221
|
+
</span>
|
|
222
|
+
</div>
|
|
223
|
+
</button>
|
|
224
|
+
<button
|
|
225
|
+
type="button"
|
|
226
|
+
@click="
|
|
227
|
+
() => {
|
|
228
|
+
pageBuilderStateStore.setMenuRight(false)
|
|
229
|
+
pageBuilderStateStore.setElement(null)
|
|
230
|
+
pageBuilderStateStore.setComponent(null)
|
|
231
|
+
handlePageBuilderPreview()
|
|
232
|
+
}
|
|
233
|
+
"
|
|
234
|
+
class="h-10 w-10 cursor-pointer rounded-full flex items-center border-none justify-center bg-gray-50 aspect-square hover:bg-myPrimaryLinkColor hover:text-white focus-visible:ring-0"
|
|
235
|
+
>
|
|
236
|
+
<span class="material-symbols-outlined"> visibility </span>
|
|
237
|
+
</button>
|
|
238
|
+
|
|
239
|
+
<button
|
|
240
|
+
type="button"
|
|
241
|
+
v-if="getMenuRight === false"
|
|
242
|
+
@click="pageBuilderStateStore.setMenuRight(true)"
|
|
243
|
+
class="h-10 w-10 cursor-pointer rounded-full flex items-center border-none justify-center bg-gray-50 aspect-square hover:bg-myPrimaryLinkColor hover:text-white focus-visible:ring-0"
|
|
244
|
+
>
|
|
245
|
+
<span class="material-symbols-outlined"> gesture </span>
|
|
246
|
+
</button>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
<EditGetElement></EditGetElement>
|
|
252
|
+
<div
|
|
253
|
+
@click="pageBuilderStateStore.setComponent(null)"
|
|
254
|
+
id="contains-pagebuilder"
|
|
255
|
+
class="pl-4 pr-4 pb-4 overflow-y-auto h-screen pt-1"
|
|
256
|
+
>
|
|
257
|
+
<div id="pagebuilder">
|
|
258
|
+
<div ref="draggableZone">
|
|
259
|
+
<!-- Added Components to DOM # start -->
|
|
260
|
+
<div
|
|
261
|
+
v-for="component in Array.isArray(getComponents) && getComponents"
|
|
262
|
+
:key="component"
|
|
263
|
+
id="page-builder-editor-editable-area"
|
|
264
|
+
class="bg-white grow"
|
|
265
|
+
>
|
|
266
|
+
<div @mouseup="handleSelectComponent(component)" class="relative group">
|
|
267
|
+
<div v-html="component.html_code"></div>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
<!-- Added Components to DOM # end -->
|
|
272
|
+
|
|
273
|
+
<!-- Add Component # start -->
|
|
274
|
+
<div
|
|
275
|
+
class="rounded-lg border-2 border-dashed border-gray-300 p-12 text-center hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 my-12 mx-8"
|
|
276
|
+
>
|
|
277
|
+
<h3 class="mt-2 text-sm font-medium text-gray-900">Add Components</h3>
|
|
278
|
+
<p class="mt-1 text-sm text-gray-500">
|
|
279
|
+
Get started by adding components using the drag & drop Page Builder.
|
|
280
|
+
</p>
|
|
281
|
+
<div class="mt-6 flex items-center gap-2 justify-center">
|
|
282
|
+
<button
|
|
283
|
+
@click="
|
|
284
|
+
() => {
|
|
285
|
+
pageBuilderStateStore.setComponentArrayAddMethod('push')
|
|
286
|
+
handleAddComponent()
|
|
287
|
+
}
|
|
288
|
+
"
|
|
289
|
+
type="button"
|
|
290
|
+
class="myPrimaryButton flex items-center gap-2 justify-center"
|
|
291
|
+
>
|
|
292
|
+
<span class="myMediumIcon material-symbols-outlined"> add </span>
|
|
293
|
+
Add component
|
|
294
|
+
</button>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
<!-- Add Component # end -->
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
<!-- Add Component # end -->
|
|
301
|
+
</main>
|
|
302
|
+
|
|
303
|
+
<aside
|
|
304
|
+
aria-label="Menu"
|
|
305
|
+
:class="{ 'w-0': !getMenuRight, 'w-80 ml-4': getMenuRight }"
|
|
306
|
+
class="h-full duration-300 z-20 flex-shrink-0 overflow-hidden shadow-2xl rounded-l-2xl bg-white"
|
|
307
|
+
>
|
|
308
|
+
<RightSidebarEditor @closeEditor="pageBuilderStateStore.setMenuRight(false)">
|
|
309
|
+
</RightSidebarEditor>
|
|
310
|
+
</aside>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
</template>
|
|
315
|
+
|
|
316
|
+
<style>
|
|
317
|
+
#pagebuilder a {
|
|
318
|
+
cursor: default;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
#pagebuilder [element] {
|
|
322
|
+
outline: rgba(255, 255, 255, 0) dashed 3px !important;
|
|
323
|
+
outline-offset: -3px !important;
|
|
324
|
+
}
|
|
325
|
+
#pagebuilder [hovered] {
|
|
326
|
+
outline: rgb(0, 140, 14, 1) dashed 3px !important;
|
|
327
|
+
outline-offset: -3px !important;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
#pagebuilder [selected] {
|
|
331
|
+
position: relative;
|
|
332
|
+
|
|
333
|
+
outline: rgb(185, 16, 16) dashed 3px !important;
|
|
334
|
+
outline-offset: -3px !important;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* sortable */
|
|
338
|
+
|
|
339
|
+
.sortable-ghost {
|
|
340
|
+
display: flex;
|
|
341
|
+
justify-content: center;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.sortable-ghost > * {
|
|
345
|
+
width: 100%;
|
|
346
|
+
}
|
|
347
|
+
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="w-full inset-x-0 h-[90vh] z-10 bg-white overflow-x-scroll lg:pt-2 pt-2"
|
|
4
|
+
>
|
|
5
|
+
<div id="page-builder-editor">
|
|
6
|
+
<div class="" v-html="htmlPage"></div>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup>
|
|
12
|
+
import { ref, watch } from "vue";
|
|
13
|
+
|
|
14
|
+
// router
|
|
15
|
+
// const router = useRouter();
|
|
16
|
+
// preview content
|
|
17
|
+
const htmlPage = ref("");
|
|
18
|
+
// get preview item from local storage
|
|
19
|
+
htmlPage.value = localStorage.getItem("preview");
|
|
20
|
+
// parse
|
|
21
|
+
htmlPage.value = JSON.parse(htmlPage.value);
|
|
22
|
+
// join
|
|
23
|
+
htmlPage.value = htmlPage.value.join("");
|
|
24
|
+
</script>
|