@dcodegroup-au/page-builder 0.1.8 → 0.2.0
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/dist/page-builder.css +2 -2
- package/dist/page-builder.es.js +13000 -12845
- package/dist/page-builder.umd.js +51 -51
- package/example/src/App.vue +50 -1
- package/example/src/assets/ExampleComponent.vue +17 -0
- package/example/src/main.js +5 -1
- package/package.json +1 -1
- package/src/components/ItemEdit.vue +7 -3
- package/src/components/PageBuilder.vue +2 -0
- package/src/components/PageRender.vue +2 -0
- package/src/components/builders/VHeader.vue +4 -2
- package/src/components/builders/VLogos.vue +121 -0
- package/src/components/common/FileUpload.vue +6 -2
- package/src/components/presenters/modules/LogoCloud.vue +49 -0
- package/src/components/builders/PageBuilderLogos.vue +0 -29
package/example/src/App.vue
CHANGED
|
@@ -121,16 +121,29 @@ const page = {
|
|
|
121
121
|
{
|
|
122
122
|
title: "Logo cloud",
|
|
123
123
|
type: "logo",
|
|
124
|
+
display: "horizontal",
|
|
124
125
|
components: [
|
|
125
126
|
{
|
|
126
127
|
name: "Section header",
|
|
127
128
|
type: "header",
|
|
128
|
-
|
|
129
|
+
supporting_text: "ELAA is proudly supporting",
|
|
129
130
|
},
|
|
130
131
|
{
|
|
131
132
|
id: 2,
|
|
132
133
|
name: "Logos",
|
|
133
134
|
type: "logos",
|
|
135
|
+
max_items: 7,
|
|
136
|
+
height: 'medium',
|
|
137
|
+
data: [
|
|
138
|
+
{
|
|
139
|
+
url: "https://childroadsafety.org.au/",
|
|
140
|
+
logo: "https://beta-frontend.elaa.org.au/img/logos/starting_out_safely.png",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
url: "https://carseatssavelives.com.au/",
|
|
144
|
+
logo: "https://beta-frontend.elaa.org.au/img/logos/car_seats_save_lives.jpg",
|
|
145
|
+
}
|
|
146
|
+
]
|
|
134
147
|
}
|
|
135
148
|
]
|
|
136
149
|
},
|
|
@@ -213,6 +226,42 @@ const page = {
|
|
|
213
226
|
}
|
|
214
227
|
]
|
|
215
228
|
},
|
|
229
|
+
{
|
|
230
|
+
title: "Logo cloud",
|
|
231
|
+
type: "logo",
|
|
232
|
+
display: "vertical",
|
|
233
|
+
components: [
|
|
234
|
+
{
|
|
235
|
+
name: "Section header",
|
|
236
|
+
type: "header",
|
|
237
|
+
supporting_text: "ELAA’s preferred partners offer special benefits to ELAA members and support ELAA’s activities",
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: 2,
|
|
241
|
+
name: "Logos",
|
|
242
|
+
type: "logos",
|
|
243
|
+
max_items: 7,
|
|
244
|
+
height: 'small',
|
|
245
|
+
data: [
|
|
246
|
+
{
|
|
247
|
+
url: "/",
|
|
248
|
+
logo: "https://beta-frontend.elaa.org.au/img/logos/anzuk.png",
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
url: "/",
|
|
252
|
+
logo: "https://beta-frontend.elaa.org.au/img/logos/bunnings.png",
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
url: "/",
|
|
256
|
+
logo: "https://beta-frontend.elaa.org.au/img/logos/hesta.png",
|
|
257
|
+
},{
|
|
258
|
+
url: "/",
|
|
259
|
+
logo: "",
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
]
|
|
264
|
+
},
|
|
216
265
|
{
|
|
217
266
|
title: "Collection grid",
|
|
218
267
|
type: "collection_grid",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="text-lg pb-6 font-semibold text-gray-900 border-b border-gray-200">Instruction</div>
|
|
3
|
+
<div class="flex flex-col gap-3 py-6 text-gray-600">
|
|
4
|
+
<div class="font-semibold text-gray-900">Modules</div>
|
|
5
|
+
<p>Modules are fixed and for internal reference only, and admin users cannot make the following changes:</p>
|
|
6
|
+
<ul class="list-inside list-disc pl-3">
|
|
7
|
+
<li>Edit the title of any module (e.g., "Hero header").</li>
|
|
8
|
+
<li>Change the order of the modules.</li>
|
|
9
|
+
<li>Add new module.</li>
|
|
10
|
+
</ul>
|
|
11
|
+
<p>For any changes at the module level, please contact the development team.</p>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="flex flex-col gap-3 py-6 text-gray-600">
|
|
14
|
+
<div class="font-semibold text-gray-900">Edit sub-modules or edit nested items</div>
|
|
15
|
+
<p>Click any sub-module to edit its settings and any nested items within it.</p>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
package/example/src/main.js
CHANGED
|
@@ -2,9 +2,13 @@ import { createApp } from 'vue'
|
|
|
2
2
|
|
|
3
3
|
import App from './App.vue'
|
|
4
4
|
import { PageBuilder, ItemEdit, PageRender } from '../../dist/page-builder.es.js'
|
|
5
|
-
import '../../dist/page-builder.css'
|
|
5
|
+
import '../../dist/page-builder.css';
|
|
6
|
+
|
|
7
|
+
// This is how to override upload component
|
|
8
|
+
// import VFileUpload from "./assets/ExampleComponent.vue";
|
|
6
9
|
|
|
7
10
|
const app = createApp(App)
|
|
11
|
+
// app.provide("VFileUpload", VFileUpload)
|
|
8
12
|
app.component('ItemEdit', ItemEdit)
|
|
9
13
|
app.component('PageBuilder', PageBuilder)
|
|
10
14
|
app.component('PageRender', PageRender)
|
package/package.json
CHANGED
|
@@ -99,8 +99,9 @@
|
|
|
99
99
|
<VToggle v-model="form.public" name="public" label="Public" />
|
|
100
100
|
</card>
|
|
101
101
|
<card title="Featured Image" supporting_text="Recommended dimension: 1200*1060px">
|
|
102
|
-
<
|
|
102
|
+
<VFileUpload
|
|
103
103
|
name="image"
|
|
104
|
+
height="h-[200px]"
|
|
104
105
|
v-model="form.featured_image"
|
|
105
106
|
/>
|
|
106
107
|
</card>
|
|
@@ -120,13 +121,16 @@
|
|
|
120
121
|
</div>
|
|
121
122
|
</template>
|
|
122
123
|
<script setup>
|
|
123
|
-
import { ref, computed } from "vue";
|
|
124
|
+
import { ref, computed, inject } from "vue";
|
|
124
125
|
import QuillEditor from "@/components/common/QuillEditor.vue";
|
|
125
126
|
import Card from "@/components/common/Card.vue";
|
|
126
127
|
import LinkedTo from "@/components/common/LinkedTo.vue";
|
|
127
128
|
import InputWrapper from "@/components/common/InputWrapper.vue";
|
|
128
129
|
import VToggle from "@/components/common/VToggle.vue";
|
|
129
|
-
import
|
|
130
|
+
import DefaultFileUpload from "@/components/common/FileUpload.vue";
|
|
131
|
+
|
|
132
|
+
// Inject the FileUpload component or fallback to the default one
|
|
133
|
+
const VFileUpload = inject("VFileUpload", DefaultFileUpload);
|
|
130
134
|
import axios from "axios";
|
|
131
135
|
|
|
132
136
|
const emit = defineEmits(["update"]);
|
|
@@ -70,6 +70,7 @@ import Instructions from "@/components/builders/Instructions.vue";
|
|
|
70
70
|
import VItems from "@/components/builders/VItems.vue";
|
|
71
71
|
import VLinks from "@/components/builders/VLinks.vue";
|
|
72
72
|
import VHeader from "@/components/builders/VHeader.vue";
|
|
73
|
+
import VLogos from "@/components/builders/VLogos.vue";
|
|
73
74
|
|
|
74
75
|
const emit = defineEmits(["save", "close"]);
|
|
75
76
|
const props = defineProps({
|
|
@@ -94,6 +95,7 @@ const componentMaps = ref({
|
|
|
94
95
|
links: markRaw(VLinks),
|
|
95
96
|
header: markRaw(VHeader),
|
|
96
97
|
link_grid: markRaw(VLinks),
|
|
98
|
+
logos: markRaw(VLogos),
|
|
97
99
|
});
|
|
98
100
|
|
|
99
101
|
if (!openStates.value) {
|
|
@@ -14,6 +14,7 @@ import {ref, markRaw} from "vue";
|
|
|
14
14
|
import HeroHeader from "@/components/presenters/modules/HeroHeader.vue";
|
|
15
15
|
import QuickLinks from "@/components/presenters/modules/QuickLinks.vue";
|
|
16
16
|
import VTabs from "@/components/presenters/modules/VTabs.vue";
|
|
17
|
+
import LogoCloud from "@/components/presenters/modules/LogoCloud.vue";
|
|
17
18
|
|
|
18
19
|
const props = defineProps({
|
|
19
20
|
page: {
|
|
@@ -26,6 +27,7 @@ const componentMaps = ref({
|
|
|
26
27
|
header: markRaw(HeroHeader),
|
|
27
28
|
quick_links: markRaw(QuickLinks),
|
|
28
29
|
tabs: markRaw(VTabs),
|
|
30
|
+
logo: markRaw(LogoCloud),
|
|
29
31
|
});
|
|
30
32
|
|
|
31
33
|
const currentComponent = (section) => {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
{{ dataRef.name }}
|
|
5
5
|
</p>
|
|
6
6
|
<input-wrapper
|
|
7
|
+
v-if="dataRef.hasOwnProperty('title')"
|
|
7
8
|
is-vertical
|
|
8
9
|
field="title"
|
|
9
10
|
label-text="Title *"
|
|
@@ -22,18 +23,19 @@
|
|
|
22
23
|
</input-wrapper>
|
|
23
24
|
<input-wrapper
|
|
24
25
|
is-vertical
|
|
26
|
+
v-if="dataRef.hasOwnProperty('supporting_text')"
|
|
25
27
|
field="supporting_text"
|
|
26
28
|
label-text="Supporting Text *"
|
|
27
29
|
class="w-full mb-4"
|
|
28
30
|
:value="dataRef.supporting_text"
|
|
29
|
-
:limit="100"
|
|
31
|
+
:limit="dataRef.supporting_text_max_length ?? 100"
|
|
30
32
|
>
|
|
31
33
|
<input
|
|
32
34
|
v-model="dataRef.supporting_text"
|
|
33
35
|
name="supporting_text"
|
|
34
36
|
type="text"
|
|
35
37
|
placeholder="Supporting Text"
|
|
36
|
-
:maxlength="100"
|
|
38
|
+
:maxlength="dataRef.supporting_text_max_length ?? 100"
|
|
37
39
|
class="border-1 border-solid border-gray-300 rounded-lg bg-white w-full"
|
|
38
40
|
/>
|
|
39
41
|
</input-wrapper>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex justify-between pb-2">
|
|
3
|
+
<div class="flex justify-between w-full py-1">
|
|
4
|
+
<div>
|
|
5
|
+
<p class="text-lg font-semibold text-gray-900">
|
|
6
|
+
{{ componentData.name }}
|
|
7
|
+
</p>
|
|
8
|
+
<p class="text-sm text-gray-600 mt-1">
|
|
9
|
+
This section can contain up to {{ componentData.max_items }} logos
|
|
10
|
+
</p>
|
|
11
|
+
</div>
|
|
12
|
+
<div>
|
|
13
|
+
<a
|
|
14
|
+
@click="addItem"
|
|
15
|
+
class="text-sm cursor-pointer flex items-center justify-center gap-1 rounded-[99px] border border-brand-600 bg-brand-500 px-3.5 py-2 font-semibold text-white hover:bg-brand-600"
|
|
16
|
+
:class="{ 'border-gray-100 bg-gray-100 !text-gray-400 hover:bg-gray-100': componentData.data?.length >= componentData.max_items }"
|
|
17
|
+
>
|
|
18
|
+
<PlusIcon class="h-5 w-5"></PlusIcon>
|
|
19
|
+
<span>Add</span>
|
|
20
|
+
</a>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="flex flex-col gap-3">
|
|
25
|
+
<div
|
|
26
|
+
v-for="(item, index) in componentData.data"
|
|
27
|
+
class="flex flex-col gap-4 rounded-xl px-6 py-4 bg-gray-200"
|
|
28
|
+
:key="index"
|
|
29
|
+
:ref="index === componentData.data.length - 1 ? (el) => (lastItemRef = el) : null"
|
|
30
|
+
>
|
|
31
|
+
<div class="flex items-center justify-between">
|
|
32
|
+
<div class="text-lg font-semibold text-gray-900">
|
|
33
|
+
Logo #{{index + 1}}
|
|
34
|
+
</div>
|
|
35
|
+
<div class="relative flex items-end">
|
|
36
|
+
<ActionMenu @removeItem="handleDeleteItem(index)"/>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="flex flex-col gap-6">
|
|
40
|
+
<div class="flex flex-col gap-1.5">
|
|
41
|
+
<VFileUpload
|
|
42
|
+
name="image"
|
|
43
|
+
background="bg-white"
|
|
44
|
+
v-model="item.logo"
|
|
45
|
+
/>
|
|
46
|
+
<input-wrapper
|
|
47
|
+
is-vertical
|
|
48
|
+
field="url"
|
|
49
|
+
label-text="Url *"
|
|
50
|
+
class="w-full my-4"
|
|
51
|
+
:value="item.url"
|
|
52
|
+
>
|
|
53
|
+
<input
|
|
54
|
+
v-model="item.url"
|
|
55
|
+
name="url"
|
|
56
|
+
type="text"
|
|
57
|
+
placeholder="Enter your url"
|
|
58
|
+
class="border-1 border-solid border-gray-300 rounded-lg bg-white w-full"
|
|
59
|
+
/>
|
|
60
|
+
</input-wrapper>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<VModal ref="modalRef" entity="logo" :callback="deleteCallback"></VModal>
|
|
66
|
+
</template>
|
|
67
|
+
<script setup>
|
|
68
|
+
import {ref, nextTick, inject} from "vue";
|
|
69
|
+
import PlusIcon from "@/assets/img/icons/plus.svg";
|
|
70
|
+
import {defaultProps} from "@/components/helpers/defaultProps";
|
|
71
|
+
import ActionMenu from "@/components/common/ActionMenu.vue";
|
|
72
|
+
import InputWrapper from "@/components/common/InputWrapper.vue";
|
|
73
|
+
import VModal from "@/components/common/VModal.vue";
|
|
74
|
+
import DefaultFileUpload from "@/components/common/FileUpload.vue";
|
|
75
|
+
|
|
76
|
+
// Inject the FileUpload component or fallback to the default one
|
|
77
|
+
const VFileUpload = inject("VFileUpload", DefaultFileUpload);
|
|
78
|
+
|
|
79
|
+
const emit = defineEmits(["update"]);
|
|
80
|
+
|
|
81
|
+
const props = defineProps({
|
|
82
|
+
...defaultProps,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const componentData = ref(props.data.component);
|
|
86
|
+
const modalRef = ref(null);
|
|
87
|
+
const lastItemRef = ref(null);
|
|
88
|
+
const deleteItemIndex = ref(null);
|
|
89
|
+
|
|
90
|
+
function addItem() {
|
|
91
|
+
if (!componentData.value.hasOwnProperty('data')) {
|
|
92
|
+
componentData.value.data = [];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (componentData.value.data?.length >= componentData.value.max_items) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
componentData.value.data?.push({
|
|
99
|
+
url: null,
|
|
100
|
+
logo: null,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
nextTick(() => {
|
|
104
|
+
if (lastItemRef.value) {
|
|
105
|
+
lastItemRef.value.scrollIntoView({behavior: "smooth"});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
emit("update", false);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const handleDeleteItem = (index) => {
|
|
113
|
+
deleteItemIndex.value = index;
|
|
114
|
+
modalRef?.value?.open(index);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const deleteCallback = (index) => {
|
|
118
|
+
componentData.value.data?.splice(index, 1);
|
|
119
|
+
emit("update", false);
|
|
120
|
+
};
|
|
121
|
+
</script>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
@todo handle upload here
|
|
6
6
|
<span class="file-upload-preview">
|
|
7
7
|
<img
|
|
8
|
-
class="img rounded-lg"
|
|
8
|
+
class="img rounded-lg h-[200px] object-contain"
|
|
9
9
|
:src="modelValue"
|
|
10
10
|
title="Image"
|
|
11
11
|
/>
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<i class="fal fa-times" @click="deleteFile(file)"></i>
|
|
28
28
|
</a>
|
|
29
29
|
</div>
|
|
30
|
-
<div class="relative" v-show="!modelValue">
|
|
30
|
+
<div :class="'relative rounded-lg ' + background" v-show="!modelValue">
|
|
31
31
|
<div class="dropzone border border-dashed rounded-lg z-10 h-[200px] w-full cursor-pointer relative"
|
|
32
32
|
ref="dropzone">
|
|
33
33
|
</div>
|
|
@@ -79,6 +79,10 @@ const props = defineProps({
|
|
|
79
79
|
type: String,
|
|
80
80
|
required: false,
|
|
81
81
|
},
|
|
82
|
+
background: {
|
|
83
|
+
type: String,
|
|
84
|
+
default: "",
|
|
85
|
+
}
|
|
82
86
|
});
|
|
83
87
|
const emit = defineEmits(["update:modelValue"]);
|
|
84
88
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full relative" :class="{'bg-gray-50': !isHorizontal}">
|
|
3
|
+
<div class="1xl:max-w-[1824px] mx-auto relative flex justify-center items-center"
|
|
4
|
+
:class="{'py-8': isHorizontal, 'flex-col py-6': !isHorizontal}">
|
|
5
|
+
<div v-for="(component, index) in section.components">
|
|
6
|
+
<p v-if="component?.type === 'header' && component?.supporting_text"
|
|
7
|
+
class="mr-[48px] text-base text-gray-600 font-medium"
|
|
8
|
+
:class="{'mb-6': !isHorizontal}"
|
|
9
|
+
>
|
|
10
|
+
{{ component?.supporting_text }}
|
|
11
|
+
</p>
|
|
12
|
+
<div v-if="component?.type === 'logos'" class="flex flex-wrap justify-center" :class="{'gap-6': isHorizontal, 'gap-12': !isHorizontal}">
|
|
13
|
+
<template v-for="logo in component.data">
|
|
14
|
+
<a :href="logo?.url" title="Brand">
|
|
15
|
+
<img v-if="logo?.logo" :src="logo?.logo" :class="getLogoHeight(component?.height)" alt="Brand Logo"/>
|
|
16
|
+
<span v-else class="text-sm">No photo</span>
|
|
17
|
+
</a>
|
|
18
|
+
</template>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup>
|
|
26
|
+
import {ref} from "vue";
|
|
27
|
+
|
|
28
|
+
const props = defineProps({
|
|
29
|
+
section: {
|
|
30
|
+
required: true,
|
|
31
|
+
type: Object,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const section = ref(props.section);
|
|
36
|
+
const isHorizontal = section.value.display === 'horizontal';
|
|
37
|
+
|
|
38
|
+
const getLogoHeight = (size) => {
|
|
39
|
+
if (size === 'small') {
|
|
40
|
+
return 'h-[32px]';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (size === 'medium') {
|
|
44
|
+
return 'h-[40px]';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return 'h-[32px]';
|
|
48
|
+
};
|
|
49
|
+
</script>
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="flex flex-col gap-4">
|
|
3
|
-
<div class="text-lg font-semibold text-gray-900">
|
|
4
|
-
{{ props.data.item.name }}
|
|
5
|
-
</div>
|
|
6
|
-
<hr class="bg-gray-200" />
|
|
7
|
-
<page-builder-base-module-form
|
|
8
|
-
:form="props.data.item.form_component"
|
|
9
|
-
:page="props.data.page"
|
|
10
|
-
:data="dataRef"
|
|
11
|
-
:attribute="props.data.pageAttribute"
|
|
12
|
-
:module-index="props.data.subModuleIndex"
|
|
13
|
-
:back-url="route('admin.pages.edit', { page: props.data.page.id })"
|
|
14
|
-
:sites="sites"
|
|
15
|
-
></page-builder-base-module-form>
|
|
16
|
-
</div>
|
|
17
|
-
</template>
|
|
18
|
-
<script setup>
|
|
19
|
-
import { defaultProps } from "@/js/vue/components/admin/pages/common/defaultProps";
|
|
20
|
-
|
|
21
|
-
const emit = defineEmits(["update"]);
|
|
22
|
-
const route = inject("route");
|
|
23
|
-
|
|
24
|
-
const props = defineProps({
|
|
25
|
-
...defaultProps,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const dataRef = ref(props.data.item);
|
|
29
|
-
</script>
|