@dcodegroup-au/page-builder 0.0.2 → 0.0.4
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 +1 -1
- package/dist/page-builder.es.js +2443 -2336
- package/dist/page-builder.umd.js +39 -39
- package/example/src/App.vue +12 -6
- package/package.json +1 -1
- package/src/assets/css/style.css +1 -1
- package/src/components/PageBuilder.vue +63 -68
- package/src/components/PageRender.vue +1 -1
- package/src/components/SlideEdit.vue +32 -9
- package/src/components/builders/VLinks.vue +3 -21
- package/src/components/builders/VSliders.vue +37 -16
- package/src/components/common/ActionMenu.vue +18 -1
- package/src/components/common/FileUpload.vue +16 -6
- package/src/components/common/InputWrapper.vue +33 -15
- package/src/components/common/QuillEditor.vue +0 -2
- package/src/components/helpers/pageBuilderFactory.js +8 -7
- package/src/components/presenters/components/VSliderPresenter.vue +4 -4
- package/src/components/presenters/modules/HeroHeader.vue +1 -1
- package/tailwind.config.js +9 -0
- package/example/src/components/HelloWorld.vue +0 -43
package/example/src/App.vue
CHANGED
|
@@ -248,12 +248,18 @@ const sites = [
|
|
|
248
248
|
</script>
|
|
249
249
|
|
|
250
250
|
<template>
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
251
|
+
<div>
|
|
252
|
+
<div style="margin: 40px 0">
|
|
253
|
+
<h1 style="margin-bottom: 20px; font-size: 50px;">Page Render</h1>
|
|
254
|
+
<PageRender :page="page"/>
|
|
255
|
+
</div>
|
|
256
|
+
<div style="margin: 40px 0">
|
|
257
|
+
<h1 style="margin-bottom: 20px; font-size: 50px;">Slider Edit</h1>
|
|
258
|
+
<SlideEdit :slide="slide" :sites="sites"/>
|
|
259
|
+
</div>
|
|
260
|
+
<div style="margin: 40px 0">
|
|
261
|
+
<h1 style="margin-bottom: 20px; font-size: 50px;">Page Builder</h1>
|
|
262
|
+
<PageBuilder v-model="page"/>
|
|
256
263
|
</div>
|
|
257
|
-
<SlideEdit :slide="slide" :sites="sites"/>
|
|
258
264
|
</div>
|
|
259
265
|
</template>
|
package/package.json
CHANGED
package/src/assets/css/style.css
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[multiple],
|
|
13
13
|
textarea,
|
|
14
14
|
select {
|
|
15
|
-
@apply text-base block w-full rounded-lg border border-gray-300 !border-double placeholder:text-gray-400 focus:border-sky-300 focus:ring focus:ring-sky-200 focus:ring-opacity-50 py-2 px-3 font-normal;
|
|
15
|
+
@apply !text-base block !w-full !rounded-lg !border !border-gray-300 !border-double placeholder:text-gray-400 focus:border-sky-300 focus:ring focus:ring-sky-200 focus:ring-opacity-50 !py-2 !px-3 !font-normal;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
.q-editor {
|
|
@@ -1,63 +1,78 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
<div class="flex
|
|
4
|
-
<div
|
|
5
|
-
<div
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
<div>
|
|
3
|
+
<div class="flex gap-4 px-6 overflow-auto">
|
|
4
|
+
<div class="flex w-[356px] flex-col gap-2">
|
|
5
|
+
<div v-for="(parent, sectionIndex) in modelValue.sections" class="border-b border-gray-200 pb-2">
|
|
6
|
+
<div
|
|
7
|
+
@click="openStates[sectionIndex] = !openStates[sectionIndex]"
|
|
8
|
+
class="flex cursor-pointer items-center justify-between px-2 py-4"
|
|
9
|
+
>
|
|
9
10
|
<span class="text-lg font-semibold text-brand-800">
|
|
10
11
|
{{ parent.title }}
|
|
11
12
|
</span>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
<div class="relative flex items-center gap-3">
|
|
14
|
+
<div class="cursor-pointer">
|
|
15
|
+
<ChevronRight class="h-5 w-5" v-if="!openStates[sectionIndex]" />
|
|
16
|
+
<ChevronDown class="h-5 w-5" v-else />
|
|
17
|
+
</div>
|
|
16
18
|
</div>
|
|
17
19
|
</div>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
<div class="flex flex-col gap-2" v-if="openStates[sectionIndex]">
|
|
21
|
+
<template v-for="(component, index) in parent.components">
|
|
22
|
+
<div
|
|
23
|
+
@click="selectComponent(sectionIndex, component, index)"
|
|
24
|
+
class="flex cursor-pointer items-center justify-between rounded-lg py-1.5 pl-6 pr-2 hover:bg-gray-100"
|
|
25
|
+
:class="{'bg-gray-200 hover:bg-gray-200': selected?.sectionIndex === sectionIndex && selected?.componentIndex === index}"
|
|
26
|
+
>
|
|
27
|
+
<div class="flex flex-col">
|
|
28
|
+
<div class="text-xs text-gray-600">Sub-module</div>
|
|
29
|
+
<div class="text-sm font-medium text-gray-900">
|
|
30
|
+
{{ component.name }}
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="flex items-center justify-between gap-3 px-[10px]">
|
|
34
|
+
<ChevronRight class="h-5 w-5" />
|
|
29
35
|
</div>
|
|
30
36
|
</div>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
</div>
|
|
34
|
-
</div>
|
|
35
|
-
</template>
|
|
37
|
+
</template>
|
|
38
|
+
</div>
|
|
36
39
|
</div>
|
|
37
40
|
</div>
|
|
41
|
+
<div class="flex h-full flex-1 flex-col rounded-xl bg-gray-50 px-6 py-5 mb-20">
|
|
42
|
+
<Instructions v-if="!selected" />
|
|
43
|
+
<component
|
|
44
|
+
:is="currentComponent"
|
|
45
|
+
:key="selected?.sectionIndex + selected?.componentIndex"
|
|
46
|
+
:data="selected"
|
|
47
|
+
:sites="sites"
|
|
48
|
+
@update="update"
|
|
49
|
+
></component>
|
|
50
|
+
</div>
|
|
38
51
|
</div>
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
<slot>
|
|
53
|
+
<div class="flex justify-between space-x-xsSpace pt-xsSpace gap-4 fixed bottom-0 w-full bg-gray-200 py-2 px-6">
|
|
54
|
+
<a @click="close"
|
|
55
|
+
class="w-[200px] py-[9px] bg-white rounded-full border border-gray-300 shadow-xs text-md font-semibold hover:bg-gray-100 text-gray-700 text-center cursor-pointer">
|
|
56
|
+
Cancel
|
|
57
|
+
</a>
|
|
58
|
+
<a @click.prevent="save"
|
|
59
|
+
class="w-full py-[9px] rounded-full shadow-xs text-md font-semibold text-white bg-brand-600 hover:bg-brand-700 border border-brand-600 text-center cursor-pointer"
|
|
60
|
+
>Save changes</a>
|
|
61
|
+
</div>
|
|
62
|
+
</slot>
|
|
49
63
|
</div>
|
|
50
64
|
</template>
|
|
51
65
|
<script setup>
|
|
52
|
-
import { ref,
|
|
66
|
+
import { ref, markRaw, computed } from "vue";
|
|
53
67
|
import ChevronRight from "@/assets/img/icons/chevron-right.svg";
|
|
54
68
|
import ChevronDown from "@/assets/img/icons/chevron-down.svg";
|
|
55
69
|
import Instructions from "@/components/builders/Instructions.vue";
|
|
56
70
|
import VSliders from "@/components/builders/VSliders.vue";
|
|
57
71
|
import VLinks from "@/components/builders/VLinks.vue";
|
|
58
72
|
|
|
73
|
+
const emit = defineEmits(["save", "close"]);
|
|
59
74
|
const props = defineProps({
|
|
60
|
-
|
|
75
|
+
modelValue: {
|
|
61
76
|
required: true,
|
|
62
77
|
type: Object,
|
|
63
78
|
},
|
|
@@ -79,9 +94,8 @@ const props = defineProps({
|
|
|
79
94
|
});
|
|
80
95
|
|
|
81
96
|
const openStates = ref(JSON.parse(window.localStorage.getItem("pageBuilderOpenStates")));
|
|
82
|
-
const sections = ref(props.
|
|
97
|
+
const sections = ref(props.modelValue?.sections ?? []);
|
|
83
98
|
let selected = ref(null);
|
|
84
|
-
const contentUnchanged = ref(true);
|
|
85
99
|
const componentMaps = ref({
|
|
86
100
|
sliders: markRaw(VSliders),
|
|
87
101
|
links: markRaw(VLinks),
|
|
@@ -99,18 +113,20 @@ if (!openStates.value) {
|
|
|
99
113
|
|
|
100
114
|
const selectComponent = (sectionIndex, component, index) => {
|
|
101
115
|
selected.value = {
|
|
102
|
-
page: props.
|
|
116
|
+
page: props.modelValue,
|
|
103
117
|
component: component,
|
|
104
118
|
componentIndex: index,
|
|
105
119
|
sectionIndex: sectionIndex,
|
|
106
120
|
};
|
|
107
|
-
console.log(selected, currentComponent);
|
|
108
121
|
};
|
|
109
122
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
123
|
+
const close = () => {
|
|
124
|
+
emit("close", props.modelValue);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const save = () => {
|
|
128
|
+
emit("save", props.modelValue);
|
|
129
|
+
};
|
|
114
130
|
|
|
115
131
|
const currentComponent = computed(() => {
|
|
116
132
|
if (!selected.value?.component?.type) {
|
|
@@ -119,25 +135,4 @@ const currentComponent = computed(() => {
|
|
|
119
135
|
|
|
120
136
|
return componentMaps.value[selected.value?.component?.type];
|
|
121
137
|
});
|
|
122
|
-
|
|
123
|
-
const update = (data) => {
|
|
124
|
-
// content.value[selected.value.sectionIndex].value.submodules[selected.value.itemIndex] = data;
|
|
125
|
-
|
|
126
|
-
// axios
|
|
127
|
-
// .post("api.admin.pages.update-content", {
|
|
128
|
-
// attributes: content.value,
|
|
129
|
-
// })
|
|
130
|
-
// .then(() => {
|
|
131
|
-
// contentUnchanged.value = true;
|
|
132
|
-
// })
|
|
133
|
-
// .catch((error) => {
|
|
134
|
-
// console.error(error);
|
|
135
|
-
// });
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
watch(
|
|
139
|
-
() => sections.value,
|
|
140
|
-
() => (contentUnchanged.value = false),
|
|
141
|
-
{ deep: true },
|
|
142
|
-
);
|
|
143
138
|
</script>
|
|
@@ -106,15 +106,17 @@
|
|
|
106
106
|
</card>
|
|
107
107
|
</div>
|
|
108
108
|
</div>
|
|
109
|
-
<
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
109
|
+
<slot>
|
|
110
|
+
<div class="flex justify-between space-x-xsSpace pt-xsSpace gap-4 sticky bottom-0 w-full bg-gray-200 py-2 px-6">
|
|
111
|
+
<a @click="close"
|
|
112
|
+
class="w-[200px] py-[9px] bg-white rounded-full border border-gray-300 shadow-xs text-md font-semibold hover:bg-gray-100 text-gray-700 text-center cursor-pointer">
|
|
113
|
+
Cancel
|
|
114
|
+
</a>
|
|
115
|
+
<a @click.prevent="save"
|
|
116
|
+
class="w-full py-[9px] rounded-full shadow-xs text-md font-semibold text-white bg-brand-600 hover:bg-brand-700 border border-brand-600 text-center cursor-pointer"
|
|
117
|
+
>Save changes</a>
|
|
118
|
+
</div>
|
|
119
|
+
</slot>
|
|
118
120
|
</div>
|
|
119
121
|
</template>
|
|
120
122
|
<script setup>
|
|
@@ -125,6 +127,7 @@ import LinkedTo from "@/components/common/LinkedTo.vue";
|
|
|
125
127
|
import InputWrapper from "@/components/common/InputWrapper.vue";
|
|
126
128
|
import VToggle from "@/components/common/VToggle.vue";
|
|
127
129
|
import FileUpload from "@/components/common/FileUpload.vue";
|
|
130
|
+
import axios from "axios";
|
|
128
131
|
|
|
129
132
|
const emit = defineEmits(["update"]);
|
|
130
133
|
|
|
@@ -133,6 +136,14 @@ const props = defineProps({
|
|
|
133
136
|
type: Object,
|
|
134
137
|
required: true,
|
|
135
138
|
},
|
|
139
|
+
saveEndpoint: {
|
|
140
|
+
type: String,
|
|
141
|
+
required: false,
|
|
142
|
+
},
|
|
143
|
+
cancelEndpoint: {
|
|
144
|
+
type: String,
|
|
145
|
+
required: false,
|
|
146
|
+
},
|
|
136
147
|
sites: {
|
|
137
148
|
type: Object,
|
|
138
149
|
required: true,
|
|
@@ -150,10 +161,22 @@ const form = ref({
|
|
|
150
161
|
|
|
151
162
|
const save = () => {
|
|
152
163
|
emit("update", form.value);
|
|
164
|
+
|
|
165
|
+
if (props.saveEndpoint) {
|
|
166
|
+
axios.post(props.saveEndpoint, form.value).then(() => {
|
|
167
|
+
if (props.cancelEndpoint) {
|
|
168
|
+
window.location.href = props.cancelEndpoint;
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
}
|
|
153
172
|
};
|
|
154
173
|
|
|
155
174
|
const close = () => {
|
|
156
175
|
emit("close", form.value);
|
|
176
|
+
|
|
177
|
+
if (props.cancelEndpoint) {
|
|
178
|
+
window.location.href = props.cancelEndpoint;
|
|
179
|
+
}
|
|
157
180
|
};
|
|
158
181
|
|
|
159
182
|
const descriptionWordCount = computed(() => {
|
|
@@ -33,18 +33,7 @@
|
|
|
33
33
|
Link #{{index + 1}}
|
|
34
34
|
</div>
|
|
35
35
|
<div class="relative flex items-end">
|
|
36
|
-
<
|
|
37
|
-
<button
|
|
38
|
-
class="flex min-w-[15rem] items-center gap-2 p-2.5 hover:rounded-md hover:bg-gray-100"
|
|
39
|
-
type="button"
|
|
40
|
-
@click="handleDeleteItem(index)"
|
|
41
|
-
>
|
|
42
|
-
<TrashIcon class="h-5 w-5 cursor-pointer stroke-gray-500"/>
|
|
43
|
-
<span class="text-sm font-medium text-gray-700">
|
|
44
|
-
Delete Link
|
|
45
|
-
</span>
|
|
46
|
-
</button>
|
|
47
|
-
</action-menu>
|
|
36
|
+
<ActionMenu @removeItem="handleDeleteItem(index)"/>
|
|
48
37
|
</div>
|
|
49
38
|
</div>
|
|
50
39
|
<div class="flex flex-col gap-6">
|
|
@@ -83,11 +72,9 @@
|
|
|
83
72
|
<script setup>
|
|
84
73
|
import {ref, nextTick} from "vue";
|
|
85
74
|
import PlusIcon from "@/assets/img/icons/plus.svg";
|
|
86
|
-
import TrashIcon from "@/assets/img/icons/trash-01.svg";
|
|
87
75
|
import {defaultProps} from "@/components/helpers/defaultProps.js";
|
|
88
|
-
import {
|
|
76
|
+
import {createLink} from "@/components/helpers/pageBuilderFactory";
|
|
89
77
|
import ActionMenu from "@/components/common/ActionMenu.vue";
|
|
90
|
-
import VToggle from "@/components/common/VToggle.vue";
|
|
91
78
|
import InputWrapper from "@/components/common/InputWrapper.vue";
|
|
92
79
|
import LinkedTo from "@/components/common/LinkedTo.vue";
|
|
93
80
|
import VModal from "@/components/common/VModal.vue";
|
|
@@ -107,12 +94,7 @@ function addItem() {
|
|
|
107
94
|
if (componentData.value.data.length >= componentData.value.max_items) {
|
|
108
95
|
return;
|
|
109
96
|
}
|
|
110
|
-
componentData.value.data.push(
|
|
111
|
-
title: "",
|
|
112
|
-
type: "site-content",
|
|
113
|
-
url: "",
|
|
114
|
-
openInNewTab: "",
|
|
115
|
-
});
|
|
97
|
+
componentData.value.data.push(createLink());
|
|
116
98
|
|
|
117
99
|
nextTick(() => {
|
|
118
100
|
if (lastItemRef.value) {
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="flex justify-between pb-2">
|
|
3
3
|
<p class="text-lg font-semibold text-gray-900">
|
|
4
|
-
{{
|
|
4
|
+
{{ dataRef.name }}
|
|
5
5
|
</p>
|
|
6
6
|
</div>
|
|
7
7
|
<div class="flex flex-col gap-4">
|
|
8
8
|
<div class="text-gray-600 border-b border-gray-200 pb-4 text-sm">
|
|
9
|
-
{{
|
|
9
|
+
{{ dataRef.supportive_text }}
|
|
10
10
|
</div>
|
|
11
11
|
<div class="flex flex-col gap-3">
|
|
12
12
|
<div class="flex justify-between">
|
|
13
13
|
<div class="flex flex-col gap-1">
|
|
14
14
|
<div class="font-semibold text-gray-900">Sliders</div>
|
|
15
|
-
<div class="text-sm text-gray-600">This slider can contain up to {{
|
|
15
|
+
<div class="text-sm text-gray-600">This slider can contain up to {{ dataRef.max_items }} slides</div>
|
|
16
16
|
</div>
|
|
17
17
|
<div>
|
|
18
18
|
<button
|
|
19
|
-
:disabled="dataRef.data.length >=
|
|
19
|
+
:disabled="dataRef.data.length >= dataRef.max_items"
|
|
20
20
|
@click="addItem"
|
|
21
21
|
type="button"
|
|
22
22
|
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"
|
|
23
|
-
:class="{ 'border-gray-100 bg-gray-100 !text-gray-400 hover:bg-gray-100': dataRef.data.length >=
|
|
23
|
+
:class="{ 'border-gray-100 bg-gray-100 !text-gray-400 hover:bg-gray-100': dataRef.data.length >= dataRef.max_items }"
|
|
24
24
|
>
|
|
25
25
|
<PlusIcon class="h-5 w-5"></PlusIcon>
|
|
26
26
|
<span>Add</span>
|
|
@@ -28,9 +28,11 @@
|
|
|
28
28
|
</div>
|
|
29
29
|
</div>
|
|
30
30
|
<div class="flex flex-col gap-3">
|
|
31
|
-
<div
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
<div v-for="(item, index) in dataRef.data"
|
|
32
|
+
class="flex items-center gap-4 px-2 py-1 hover:bg-gray-100 rounded-lg"
|
|
33
|
+
:class="{'bg-gray-200 hover:bg-gray-200': openSlideStates[index]}">
|
|
34
|
+
<div class="flex flex-1 cursor-pointer items-center justify-between relative" @click="toggleSlide(index)">
|
|
35
|
+
<div class="flex flex-1 flex-col">
|
|
34
36
|
<div class="text-xs text-gray-600">
|
|
35
37
|
Slider #{{ index + 1 }}
|
|
36
38
|
</div>
|
|
@@ -38,7 +40,7 @@
|
|
|
38
40
|
{{ item.title }}
|
|
39
41
|
</div>
|
|
40
42
|
</div>
|
|
41
|
-
<ActionMenu @removeItem="handleDeleteItem(index)" />
|
|
43
|
+
<ActionMenu @removeItem="handleDeleteItem(index)" :enable-edit="true" @editItem="edit(item, index)"/>
|
|
42
44
|
</div>
|
|
43
45
|
</div>
|
|
44
46
|
</div>
|
|
@@ -62,6 +64,26 @@ const props = defineProps({
|
|
|
62
64
|
const dataRef = ref(props.data.component);
|
|
63
65
|
const modalRef = ref(null);
|
|
64
66
|
|
|
67
|
+
const openSlideStates = ref(JSON.parse(window.localStorage.getItem("openSlideStates")));
|
|
68
|
+
if (!openSlideStates.value) {
|
|
69
|
+
openSlideStates.value = {};
|
|
70
|
+
|
|
71
|
+
dataRef.value.data.forEach((item, index) => {
|
|
72
|
+
openSlideStates.value[index] = false;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
console.log(openSlideStates)
|
|
76
|
+
window.localStorage.setItem("openSlideStates", JSON.stringify(openSlideStates.value));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const toggleSlide = (index) => {
|
|
80
|
+
Object.keys(openSlideStates.value).forEach((key) => {
|
|
81
|
+
openSlideStates.value[key] = false;
|
|
82
|
+
});
|
|
83
|
+
openSlideStates.value[index] = !openSlideStates.value[index];
|
|
84
|
+
window.localStorage.setItem("openSlideStates", JSON.stringify(openSlideStates.value));
|
|
85
|
+
};
|
|
86
|
+
|
|
65
87
|
const addItem = () => {
|
|
66
88
|
dataRef.value.data.push(createSlide());
|
|
67
89
|
emit("update", dataRef.value);
|
|
@@ -76,12 +98,11 @@ const deleteCallback = (index) => {
|
|
|
76
98
|
emit("update", dataRef.value);
|
|
77
99
|
};
|
|
78
100
|
|
|
79
|
-
const edit = (index) => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// });
|
|
101
|
+
const edit = (item, index) => {
|
|
102
|
+
if (item.hasOwnProperty('edit_url')) {
|
|
103
|
+
window.location.href = item.edit_url;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
window.location.href = `/admin/pages/${props?.data?.page?.id}/sections/${props?.data?.sectionIndex}/components/${props?.data?.componentIndex}/slides/${index}`;
|
|
86
107
|
};
|
|
87
108
|
</script>
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
<div class="relative z-50" v-click-outside="click">
|
|
3
3
|
<DotsHorizontal @click="open = !open" class="w-5 cursor-pointer text-gray-600" />
|
|
4
4
|
<div class="absolute -left-60 top-6 w-[240px] rounded-lg border border-gray-200 bg-white" v-if="open">
|
|
5
|
-
<a class="flex items-center gap-2 p-2.5 cursor-pointer h-[48px] w-full" @click="
|
|
5
|
+
<a v-if="enableEdit" class="flex items-center gap-2 p-2.5 cursor-pointer h-[48px] w-full hover:bg-gray-100" @click="edit">
|
|
6
|
+
<Pencil class="w-5" />
|
|
7
|
+
Edit
|
|
8
|
+
</a>
|
|
9
|
+
<a class="flex items-center gap-2 p-2.5 cursor-pointer h-[48px] w-full hover:bg-gray-100" :class="{'hover:rounded-lg': !enableEdit}" @click="remove">
|
|
6
10
|
<TrashIcon class="w-5" />
|
|
7
11
|
Delete
|
|
8
12
|
</a>
|
|
@@ -23,10 +27,18 @@ export default {
|
|
|
23
27
|
<script setup>
|
|
24
28
|
import DotsHorizontal from "@/assets/img/icons/dots-horizontal.svg";
|
|
25
29
|
import TrashIcon from "@/assets/img/icons/trash-01.svg";
|
|
30
|
+
import Pencil from "@/assets/img/icons/pencil-01.svg";
|
|
26
31
|
import { ref } from "vue";
|
|
27
32
|
const open = ref(false);
|
|
28
33
|
const emit = defineEmits(['removeItem']);
|
|
29
34
|
|
|
35
|
+
const props = defineProps({
|
|
36
|
+
enableEdit: {
|
|
37
|
+
type: Boolean,
|
|
38
|
+
default: false,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
30
42
|
const click = () => {
|
|
31
43
|
if (!open.value) {
|
|
32
44
|
return;
|
|
@@ -38,4 +50,9 @@ const remove = () => {
|
|
|
38
50
|
emit('removeItem');
|
|
39
51
|
open.value = false;
|
|
40
52
|
}
|
|
53
|
+
|
|
54
|
+
const edit = () => {
|
|
55
|
+
emit('editItem');
|
|
56
|
+
open.value = false;
|
|
57
|
+
}
|
|
41
58
|
</script>
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="file-upload">
|
|
3
3
|
<input type="hidden" :name="name" :value="valueJson"/>
|
|
4
|
-
<div v-if="
|
|
4
|
+
<div v-if="modelValue" class="preview">
|
|
5
|
+
@todo handle upload here
|
|
6
|
+
<span class="file-upload-preview">
|
|
7
|
+
<img
|
|
8
|
+
class="img rounded-lg"
|
|
9
|
+
:src="modelValue"
|
|
10
|
+
title="Image"
|
|
11
|
+
/>
|
|
12
|
+
</span>
|
|
13
|
+
</div>
|
|
14
|
+
<div v-else-if="file && file.length" class="preview">
|
|
5
15
|
<span class="file-upload-preview">
|
|
6
16
|
<img
|
|
7
17
|
class="img"
|
|
@@ -17,7 +27,7 @@
|
|
|
17
27
|
<i class="fal fa-times" @click="deleteFile(file)"></i>
|
|
18
28
|
</a>
|
|
19
29
|
</div>
|
|
20
|
-
<div class="relative">
|
|
30
|
+
<div class="relative" v-show="!modelValue">
|
|
21
31
|
<div class="dropzone border border-dashed rounded-lg z-10 h-[200px] w-full cursor-pointer relative"
|
|
22
32
|
ref="dropzone">
|
|
23
33
|
</div>
|
|
@@ -47,7 +57,7 @@ Dropzone.autoDiscover = false;
|
|
|
47
57
|
const props = defineProps({
|
|
48
58
|
name: {
|
|
49
59
|
type: String,
|
|
50
|
-
required:
|
|
60
|
+
required: false,
|
|
51
61
|
},
|
|
52
62
|
modelValue: {
|
|
53
63
|
type: [String, Object],
|
|
@@ -55,11 +65,11 @@ const props = defineProps({
|
|
|
55
65
|
},
|
|
56
66
|
submitEndpoint: {
|
|
57
67
|
type: String,
|
|
58
|
-
required:
|
|
68
|
+
required: false,
|
|
59
69
|
},
|
|
60
70
|
deleteEndpoint: {
|
|
61
71
|
type: String,
|
|
62
|
-
required:
|
|
72
|
+
required: false,
|
|
63
73
|
},
|
|
64
74
|
formData: {
|
|
65
75
|
type: Object,
|
|
@@ -67,7 +77,7 @@ const props = defineProps({
|
|
|
67
77
|
},
|
|
68
78
|
csrf: {
|
|
69
79
|
type: String,
|
|
70
|
-
required:
|
|
80
|
+
required: false,
|
|
71
81
|
},
|
|
72
82
|
});
|
|
73
83
|
const emit = defineEmits(["update:modelValue"]);
|
|
@@ -17,25 +17,43 @@
|
|
|
17
17
|
</span>
|
|
18
18
|
<slot />
|
|
19
19
|
<span class="absolute right-0 -bottom-6 text-sm text-gray-600 font-normal" v-if="limit">
|
|
20
|
-
{{value
|
|
20
|
+
{{ value?.length }}/{{ limit }} characters
|
|
21
21
|
</span>
|
|
22
22
|
<span class="absolute left-0 -bottom-6 text-sm text-gray-600 font-normal" v-if="showCount">
|
|
23
|
-
Word count: {{value}}
|
|
23
|
+
Word count: {{ value }}
|
|
24
24
|
</span>
|
|
25
25
|
</label>
|
|
26
26
|
</template>
|
|
27
27
|
|
|
28
|
-
<script setup
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
28
|
+
<script setup>
|
|
29
|
+
const props = defineProps({
|
|
30
|
+
field: {
|
|
31
|
+
type: String,
|
|
32
|
+
required: true,
|
|
33
|
+
},
|
|
34
|
+
labelText: {
|
|
35
|
+
type: String,
|
|
36
|
+
default: "",
|
|
37
|
+
},
|
|
38
|
+
value: {
|
|
39
|
+
type: [String, Number],
|
|
40
|
+
default: "",
|
|
41
|
+
},
|
|
42
|
+
limit: {
|
|
43
|
+
type: Number,
|
|
44
|
+
default: null,
|
|
45
|
+
},
|
|
46
|
+
showCount: {
|
|
47
|
+
type: Boolean,
|
|
48
|
+
default: false,
|
|
49
|
+
},
|
|
50
|
+
darkTheme: {
|
|
51
|
+
type: Boolean,
|
|
52
|
+
default: false,
|
|
53
|
+
},
|
|
54
|
+
isRequired: {
|
|
55
|
+
type: Boolean,
|
|
56
|
+
default: false,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
41
59
|
</script>
|
|
@@ -45,10 +45,8 @@ export default {
|
|
|
45
45
|
onMounted(() => {
|
|
46
46
|
quillInstance = new Quill(editorContainer.value, props.options);
|
|
47
47
|
|
|
48
|
-
// Set initial content
|
|
49
48
|
quillInstance.root.innerHTML = props.modelValue;
|
|
50
49
|
|
|
51
|
-
// Listen for text changes
|
|
52
50
|
quillInstance.on("text-change", () => {
|
|
53
51
|
emit("update:modelValue", quillInstance.root.innerHTML);
|
|
54
52
|
});
|