@opengis/cms 0.0.21 → 0.0.22
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/package.json +2 -2
- package/src/App.css +52 -0
- package/src/App.vue +62 -0
- package/src/assets/image.png +0 -0
- package/src/assets/main.css +3 -0
- package/src/assets/tailwind-3.4.17.js +113 -0
- package/src/components/LanguageSwitcher.vue +73 -0
- package/src/components/SettingsCard.vue +40 -0
- package/src/components/builder/CreateForm.vue +128 -0
- package/src/components/builder/formTypeSchema.js +145 -0
- package/src/components/builder/tabs/index.ts +9 -0
- package/src/components/builder/tabs/vs-builder-edit.vue +133 -0
- package/src/components/builder/tabs/vs-builder-monaco.vue +29 -0
- package/src/components/builder/tabs/vs-builder-preview.vue +39 -0
- package/src/components/builder/vs-builder-datatable-controls.vue +138 -0
- package/src/components/builder/vs-builder-datatable-form.vue +80 -0
- package/src/components/builder/vs-builder-datatable.vue +191 -0
- package/src/components/builder/vs-builder-list-item.vue +110 -0
- package/src/components/collections/CollectionsBreadcrumb.vue +52 -0
- package/src/components/collections/CollectionsGrid.vue +176 -0
- package/src/components/collections/ContentBlock.vue +75 -0
- package/src/components/collections/formWrapper.vue +156 -0
- package/src/components/dashboard/ContentItem.vue +82 -0
- package/src/components/dashboard/DashboardHeader.vue +33 -0
- package/src/components/dashboard/QuickActions.vue +84 -0
- package/src/components/dashboard/RecentContent.vue +54 -0
- package/src/components/dashboard/StatCard.vue +63 -0
- package/src/components/dashboard/StatsGrid.vue +28 -0
- package/src/components/form-components/MonacoEditor.vue +104 -0
- package/src/components/form-components/VsFormMeta.vue +40 -0
- package/src/components/form-components/VsFormTags.vue +150 -0
- package/src/components/form-components/custom-datatable/vs-form-custom-datatable-add.vue +84 -0
- package/src/components/form-components/custom-datatable/vs-form-custom-datatable-controls.vue +106 -0
- package/src/components/form-components/index.js +23 -0
- package/src/components/form-components/reference/vs-form-reference-add.vue +92 -0
- package/src/components/form-components/reference/vs-form-reference-controls.vue +101 -0
- package/src/components/form-components/reference-list/referenceOptionList.js +78 -0
- package/src/components/form-components/reference-list/vs-form-reference-add.vue +145 -0
- package/src/components/form-components/reference-list/vs-form-reference-choce.vue +39 -0
- package/src/components/form-components/reference-list/vs-form-reference-controls.vue +110 -0
- package/src/components/form-components/reference-skeleton/about-skeleton.vue +37 -0
- package/src/components/form-components/reference-skeleton/banner-skeleton.vue +29 -0
- package/src/components/form-components/reference-skeleton/body-skeleton.vue +56 -0
- package/src/components/form-components/reference-skeleton/cards-skeleton.vue +47 -0
- package/src/components/form-components/reference-skeleton/documents-skeleton.vue +64 -0
- package/src/components/form-components/reference-skeleton/faq-skeleton.vue +64 -0
- package/src/components/form-components/reference-skeleton/form-skeleton.vue +41 -0
- package/src/components/form-components/reference-skeleton/index.js +36 -0
- package/src/components/form-components/reference-skeleton/infoLine-skeleton.vue +37 -0
- package/src/components/form-components/reference-skeleton/news-skeleton.vue +54 -0
- package/src/components/form-components/reference-skeleton/slider-skeleton.vue +41 -0
- package/src/components/form-components/reference-skeleton/tabs-skeleton.vue +40 -0
- package/src/components/form-components/reference-skeleton/team-skeleton.vue +103 -0
- package/src/components/form-components/reference-skeleton/usefulLinks-skeleton.vue +52 -0
- package/src/components/form-components/reference-skeleton/video-skeleton.vue +36 -0
- package/src/components/form-components/testReferenceTypes.js +773 -0
- package/src/components/form-components/vs-form-color-picker.vue +29 -0
- package/src/components/form-components/vs-form-custom-datatable.vue +214 -0
- package/src/components/form-components/vs-form-integer.vue +86 -0
- package/src/components/form-components/vs-form-key-value.vue +201 -0
- package/src/components/form-components/vs-form-marcdown-md.vue +3 -0
- package/src/components/form-components/vs-form-media-select.vue +780 -0
- package/src/components/form-components/vs-form-reference-list.vue +97 -0
- package/src/components/form-components/vs-form-reference.vue +59 -0
- package/src/components/form-components/vs-form-relation.vue +30 -0
- package/src/components/form-components/vs-form-reletion-link.vue +34 -0
- package/src/components/form-components/vs-form-select-collection.vue +0 -0
- package/src/components/form-components/vs-form-slug.vue +72 -0
- package/src/components/form-components/vs-form-tiptap.vue +7 -0
- package/src/components/form-components/vs-richtext-md.vue +3 -0
- package/src/components/icons/BellIcon.vue +17 -0
- package/src/components/icons/GlobeIcon.vue +18 -0
- package/src/components/icons/KeyIcon.vue +20 -0
- package/src/components/icons/PaletteIcon.vue +22 -0
- package/src/components/icons/SettingsIcon.vue +19 -0
- package/src/components/icons/ShieldIcon.vue +18 -0
- package/src/components/icons/UsersIcon.vue +19 -0
- package/src/components/icons/icon-chevron-right.vue +16 -0
- package/src/components/icons/icon-drag.vue +20 -0
- package/src/components/icons/icon-file-text.vue +21 -0
- package/src/components/icons/icon-folder.vue +18 -0
- package/src/components/icons/icon-grid.vue +17 -0
- package/src/components/icons/icon-group.vue +19 -0
- package/src/components/icons/icon-home.vue +16 -0
- package/src/components/icons/icon-image.vue +18 -0
- package/src/components/icons/icon-list.vue +20 -0
- package/src/components/icons/icon-more.vue +17 -0
- package/src/components/icons/icon-plus.vue +17 -0
- package/src/components/icons-types/icon-array.vue +22 -0
- package/src/components/icons-types/icon-boolean.vue +18 -0
- package/src/components/icons-types/icon-datalist.vue +22 -0
- package/src/components/icons-types/icon-date.vue +20 -0
- package/src/components/icons-types/icon-datetime.vue +20 -0
- package/src/components/icons-types/icon-file.vue +21 -0
- package/src/components/icons-types/icon-gallery.vue +18 -0
- package/src/components/icons-types/icon-image.vue +19 -0
- package/src/components/icons-types/icon-integer.vue +20 -0
- package/src/components/icons-types/icon-merkdown.vue +18 -0
- package/src/components/icons-types/icon-multiselect.vue +22 -0
- package/src/components/icons-types/icon-number.vue +20 -0
- package/src/components/icons-types/icon-radio.vue +22 -0
- package/src/components/icons-types/icon-reference-list.vue +22 -0
- package/src/components/icons-types/icon-reference.vue +20 -0
- package/src/components/icons-types/icon-relation.vue +22 -0
- package/src/components/icons-types/icon-richtext.vue +18 -0
- package/src/components/icons-types/icon-select.vue +22 -0
- package/src/components/icons-types/icon-slug.vue +19 -0
- package/src/components/icons-types/icon-text.vue +19 -0
- package/src/components/icons-types/index.js +43 -0
- package/src/components/layout/Layout.vue +67 -0
- package/src/components/layout/Sidebar.vue +128 -0
- package/src/components/media/FileUploadProgress.vue +29 -0
- package/src/components/media/MediaBreadcrumb.vue +42 -0
- package/src/components/media/MediaCreateFolder.vue +59 -0
- package/src/components/media/MediaFileInfo.vue +148 -0
- package/src/components/media/MediaGrid.vue +148 -0
- package/src/components/media/MediaList.vue +148 -0
- package/src/components/media/MediaViewControls.vue +38 -0
- package/src/components/media/TypeTag.vue +23 -0
- package/src/components/menu/AddNewItemInTree.vue +75 -0
- package/src/components/menu/MenuBody.vue +149 -0
- package/src/components/menu/MenuItem.vue +73 -0
- package/src/components/menu/MenuList.vue +101 -0
- package/src/components/referencec/index.ts +7 -0
- package/src/components/referencec/vs-reference-faq.vue +61 -0
- package/src/components/referencec/vs-reference-user-card.vue +40 -0
- package/src/components/settings/NotificationSettings.vue +32 -0
- package/src/components/settings/SettingsTable.vue +50 -0
- package/src/components/settings/SettingsTitle.vue +33 -0
- package/src/components/settings/SettingsToggleItem.vue +25 -0
- package/src/components/sidebar/DropdownMenu.vue +34 -0
- package/src/components/sidebar/SettingsSidebar.vue +121 -0
- package/src/components/sidebar/SidebarFooter.vue +52 -0
- package/src/components/sidebar/SidebarHeader.vue +57 -0
- package/src/components/sidebar/SidebarMenu.vue +78 -0
- package/src/components/ui/EmptyData.vue +76 -0
- package/src/components/ui/UniversalTable.vue +310 -0
- package/src/components/ui/UniversalTableFilters.vue +0 -0
- package/src/components/ui/UniversalTablePagination.vue +118 -0
- package/src/components/ui/VsPreview.vue +75 -0
- package/src/composables/useCollectionView.ts +21 -0
- package/src/composables/useDebounce.ts +26 -0
- package/src/composables/useMonaco.ts +28 -0
- package/src/composables/useTheme.ts +40 -0
- package/src/content/test-slug/metadata.json +1 -0
- package/src/i18n.ts +75 -0
- package/src/index.css +3 -0
- package/src/locales/en.json +778 -0
- package/src/locales/uk.json +797 -0
- package/src/main.ts +41 -0
- package/src/pages/Dashboard.vue +168 -0
- package/src/pages/EmailPage.vue +183 -0
- package/src/pages/FeedbackPage.vue +232 -0
- package/src/pages/MediaPage.vue +372 -0
- package/src/pages/TagsPage.vue +207 -0
- package/src/pages/builder/BuilderPage.vue +195 -0
- package/src/pages/builder/EditCollectionPage.vue +163 -0
- package/src/pages/collections/ArticlesPage.vue +385 -0
- package/src/pages/collections/CollectionsPage.vue +146 -0
- package/src/pages/collections/SingletonsPage.vue +119 -0
- package/src/pages/collections/contentForm.vue +484 -0
- package/src/pages/collections/schema/seo.ts +27 -0
- package/src/pages/menu/MenuAddPage.vue +123 -0
- package/src/pages/menu/MenuItemPage.vue +183 -0
- package/src/pages/menu/MenuPage.vue +133 -0
- package/src/pages/settings/ApiKeys.vue +75 -0
- package/src/pages/settings/Appearance.vue +80 -0
- package/src/pages/settings/Logs.vue +260 -0
- package/src/pages/settings/PermissionsPage.vue +237 -0
- package/src/pages/settings/Settings.vue +186 -0
- package/src/pages/settings/Users.vue +109 -0
- package/src/pages/settings/general.vue +154 -0
- package/src/pages/settings/generalScheme.js +132 -0
- package/src/pages/users/AddUser.vue +106 -0
- package/src/pages/users/UsersPage.vue +98 -0
- package/src/props/builder.ts +67 -0
- package/src/props/content.ts +56 -0
- package/src/props/media.ts +63 -0
- package/src/router/index.ts +181 -0
- package/src/types/fastify-auth.d.ts +4 -0
- package/src/utils/getField.js +270 -0
- package/src/utils/translit.js +19 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex items-center justify-end col-span-3 gap-2">
|
|
3
|
+
<button
|
|
4
|
+
v-if="!['status']?.includes(data.name)"
|
|
5
|
+
@click="dialog = true"
|
|
6
|
+
class="inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:text-accent-foreground h-8 w-8 rounded-full hover:bg-gray-100"
|
|
7
|
+
>
|
|
8
|
+
<Edit class="w-4 h-4" /></button
|
|
9
|
+
><button
|
|
10
|
+
v-if="!data.required"
|
|
11
|
+
@click="openConfirm"
|
|
12
|
+
class="inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 h-8 w-8 rounded-full hover:bg-red-100 text-red-600 hover:text-red-700"
|
|
13
|
+
>
|
|
14
|
+
<Trash class="w-4 h-4" />
|
|
15
|
+
</button>
|
|
16
|
+
<button v-else class="w-8 h-4"></button>
|
|
17
|
+
<VsModal
|
|
18
|
+
teleport="#modal"
|
|
19
|
+
:visible="dialog"
|
|
20
|
+
:title="$t('builder.editField')"
|
|
21
|
+
@close="dialog = false"
|
|
22
|
+
>
|
|
23
|
+
<VsForm v-model="formValue" :schema="schema" v-model:form="form" />
|
|
24
|
+
<template #footer>
|
|
25
|
+
<div class="flex justify-end p-[20px] gap-[10px] border-t w-full">
|
|
26
|
+
<button
|
|
27
|
+
class="inline-flex items-center px-3 py-2 text-sm text-black duration-300 border border-gray-200 rounded-lg gap-x-2 whitespace-nowrap hover:bg-gray-100"
|
|
28
|
+
@click="dialog = false"
|
|
29
|
+
>
|
|
30
|
+
{{ $t("common.actions.cancel") }}
|
|
31
|
+
</button>
|
|
32
|
+
<button
|
|
33
|
+
class="py-2 px-3 inline-flex items-center gap-x-2 text-sm whitespace-nowrap text-white bg-blue-500 rounded-lg !border-gray-200 hover:bg-blue-700 duration-300"
|
|
34
|
+
@click="editColumn"
|
|
35
|
+
>
|
|
36
|
+
{{ $t("common.actions.save") }}
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
40
|
+
</VsModal>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<script setup>
|
|
45
|
+
import { Edit, Trash } from "lucide-vue-next";
|
|
46
|
+
import { formTypeSchema } from "./formTypeSchema.js";
|
|
47
|
+
import { ref, onMounted, computed } from "vue";
|
|
48
|
+
import { confirm, notify } from '@opengis/core';
|
|
49
|
+
import { useI18n } from "vue-i18n";
|
|
50
|
+
import VsForm from "@opengis/form";
|
|
51
|
+
import { VsModal } from "@opengis/core";
|
|
52
|
+
const { t } = useI18n();
|
|
53
|
+
// import VsForm from '../../../../form/src/index'
|
|
54
|
+
|
|
55
|
+
const props = defineProps({
|
|
56
|
+
index: {
|
|
57
|
+
type: Number,
|
|
58
|
+
required: true,
|
|
59
|
+
},
|
|
60
|
+
data: {
|
|
61
|
+
type: Object,
|
|
62
|
+
required: true,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const columns = defineModel("columns", {
|
|
67
|
+
type: Array,
|
|
68
|
+
default: () => [],
|
|
69
|
+
});
|
|
70
|
+
const dialog = ref(false);
|
|
71
|
+
const form = ref({});
|
|
72
|
+
const formValue = ref({ ...(props.data || {}) });
|
|
73
|
+
|
|
74
|
+
const schema = ref({});
|
|
75
|
+
|
|
76
|
+
onMounted(() => {
|
|
77
|
+
if (props.data.required) {
|
|
78
|
+
|
|
79
|
+
schema.value = {
|
|
80
|
+
...formTypeSchema(0, t),
|
|
81
|
+
name: {
|
|
82
|
+
...formTypeSchema(0, t).name,
|
|
83
|
+
disabled: true,
|
|
84
|
+
},
|
|
85
|
+
label: {
|
|
86
|
+
...formTypeSchema(0, t).label,
|
|
87
|
+
disabled: true,
|
|
88
|
+
},
|
|
89
|
+
type: {
|
|
90
|
+
...formTypeSchema(0, t).type,
|
|
91
|
+
disabled: true,
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
schema.value = formTypeSchema(0, t);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const openConfirm = () => {
|
|
100
|
+
confirm({
|
|
101
|
+
title: t("builder.deleteTitle"),
|
|
102
|
+
message: t("builder.deleteField"),
|
|
103
|
+
type: 'error',
|
|
104
|
+
onConfirm: () => {
|
|
105
|
+
deleteColumn();
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const deleteColumn = () => {
|
|
111
|
+
columns.value = columns.value.filter((_, index) => index !== props.index);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const editColumn = async () => {
|
|
115
|
+
let result = await form.value.validate();
|
|
116
|
+
|
|
117
|
+
if (result) {
|
|
118
|
+
notify({
|
|
119
|
+
type: "warning",
|
|
120
|
+
title: t("common.actions.warning"),
|
|
121
|
+
message: t("builder.editFieldFailed"),
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
columns.value[props.index] = formValue.value;
|
|
127
|
+
|
|
128
|
+
dialog.value = false;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.log(error);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
<style lang="scss" scoped></style>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<button
|
|
4
|
+
@click="dialog = true"
|
|
5
|
+
class="inline-flex items-center justify-center h-10 gap-2 px-4 py-2 text-sm font-medium transition-colors border rounded-md hover hover:bg-gray-200 whitespace-nowrap ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring 0"
|
|
6
|
+
>
|
|
7
|
+
<Plus class="w-4 h-4 mr-2" />
|
|
8
|
+
{{ $t("common.actions.addField") }}
|
|
9
|
+
</button>
|
|
10
|
+
<VsModal teleport="#modal" :title="$t('builder.createField')" size="small" v-model:visible="dialog" @close="dialog = false">
|
|
11
|
+
<VForm v-model="formValue" :schema="scheme" v-model:form="form" />
|
|
12
|
+
<template #footer>
|
|
13
|
+
<div class="flex justify-end p-[20px] gap-[10px] border-t w-full">
|
|
14
|
+
<button
|
|
15
|
+
class="inline-flex items-center px-3 py-2 text-sm text-black duration-300 border border-gray-200 rounded-lg gap-x-2 whitespace-nowrap hover:bg-gray-100"
|
|
16
|
+
@click="dialog = false"
|
|
17
|
+
>
|
|
18
|
+
{{ $t("common.actions.cancel") }}
|
|
19
|
+
</button>
|
|
20
|
+
<button
|
|
21
|
+
@click="createField"
|
|
22
|
+
class="py-2 px-3 inline-flex items-center gap-x-2 text-sm whitespace-nowrap text-white bg-blue-500 rounded-lg !border-gray-200 hover:bg-blue-700 duration-300"
|
|
23
|
+
>
|
|
24
|
+
{{ $t("common.actions.save") }}
|
|
25
|
+
</button>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
</VsModal>
|
|
29
|
+
</div>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script setup>
|
|
33
|
+
import { ref, computed, watch } from "vue";
|
|
34
|
+
import { Plus } from "lucide-vue-next";
|
|
35
|
+
import { formTypeSchema } from "./formTypeSchema";
|
|
36
|
+
import { VForm,inputs } from "@opengis/form";
|
|
37
|
+
import { VsModal, notify } from "@opengis/core";
|
|
38
|
+
import { useI18n } from "vue-i18n";
|
|
39
|
+
const { t } = useI18n();
|
|
40
|
+
// import { VForm,inputs } from '../../../../form/src/index'
|
|
41
|
+
|
|
42
|
+
inputs['vs-input-datalist'] = inputs.VsInputDatatable;
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const columns = defineModel({
|
|
46
|
+
type: Array,
|
|
47
|
+
default: () => [],
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const dialog = ref(false);
|
|
51
|
+
const form = ref({});
|
|
52
|
+
const formValue = ref({});
|
|
53
|
+
|
|
54
|
+
const scheme = computed(() => {
|
|
55
|
+
const i = formValue.value;
|
|
56
|
+
return formTypeSchema(0, t);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const createField = async () => {
|
|
60
|
+
let result = await form.value.validate();
|
|
61
|
+
if (result) {
|
|
62
|
+
notify({
|
|
63
|
+
type: "warning",
|
|
64
|
+
title: t("common.actions.warning"),
|
|
65
|
+
message: t("builder.createFieldFailed"),
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
columns.value = [...columns.value, formValue.value];
|
|
72
|
+
formValue.value = {};
|
|
73
|
+
dialog.value = false;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.log(error);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<style lang="scss" scoped></style>
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex items-center justify-between mb-6">
|
|
3
|
+
<div>
|
|
4
|
+
<h2 class="text-xl font-semibold text-gray-900">{{ $t("builder.collectionStructure") }}</h2>
|
|
5
|
+
<p class="mt-1 text-sm text-gray-600">
|
|
6
|
+
{{ $t("builder.defineFields") }}
|
|
7
|
+
</p>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="flex items-center gap-2">
|
|
10
|
+
<VSBuilderDatatableForm v-model="data.columns" />
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<div
|
|
14
|
+
class="overflow-hidden bg-white border border-gray-200 shadow-sm rounded-xl"
|
|
15
|
+
>
|
|
16
|
+
<div
|
|
17
|
+
class="grid grid-cols-12 gap-4 px-6 py-4 border-b border-gray-200 bg-gray-50"
|
|
18
|
+
>
|
|
19
|
+
<div class="col-span-5 font-medium text-gray-700">{{ $t("builder.name") }}</div>
|
|
20
|
+
<div class="col-span-3 font-medium text-gray-700">{{ $t("builder.type") }}</div>
|
|
21
|
+
<div class="col-span-2 font-medium text-gray-700">{{ $t("builder.localization") }}</div>
|
|
22
|
+
<div class="col-span-2 font-medium text-right text-gray-700">{{ $t("builder.actions") }}</div>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="divide-y divide-gray-100">
|
|
25
|
+
<template v-if="data?.columns?.length">
|
|
26
|
+
<draggable
|
|
27
|
+
v-model="data.columns"
|
|
28
|
+
item-key="name"
|
|
29
|
+
handle=".drag-handle"
|
|
30
|
+
:animation="200"
|
|
31
|
+
>
|
|
32
|
+
<template #item="{ element: column, index }">
|
|
33
|
+
<div
|
|
34
|
+
class="grid items-center grid-cols-12 gap-4 px-6 py-4 transition-colors hover:bg-gray-50/50"
|
|
35
|
+
>
|
|
36
|
+
<div class="flex items-center col-span-5 gap-3">
|
|
37
|
+
<span class="drag-handle cursor-move mr-2 text-gray-400"
|
|
38
|
+
>☰</span
|
|
39
|
+
>
|
|
40
|
+
<div
|
|
41
|
+
class="flex items-center justify-center p-1 rounded"
|
|
42
|
+
:class="`bg-${getColorType(column.type?.toLowerCase())}-100`"
|
|
43
|
+
>
|
|
44
|
+
<component :is="getIconType(column.type?.toLowerCase())" class="w-4 h-4" />
|
|
45
|
+
</div>
|
|
46
|
+
<div>
|
|
47
|
+
<p class="font-medium text-gray-900">{{ column.label }}</p>
|
|
48
|
+
<p class="text-xs text-gray-500">{{ column.name }}</p>
|
|
49
|
+
</div>
|
|
50
|
+
<span
|
|
51
|
+
v-if="column.required"
|
|
52
|
+
class="inline-flex items-center px-2 py-1 text-xs font-medium text-blue-700 rounded-full bg-blue-50 ring-1 ring-inset ring-blue-700/10"
|
|
53
|
+
>{{ $t("builder.required") }}</span
|
|
54
|
+
>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="col-span-3">
|
|
57
|
+
<span
|
|
58
|
+
class="inline-flex items-center px-2 py-1 text-sm font-medium text-gray-600 rounded-md bg-gray-50 ring-1 ring-inset ring-gray-500/10"
|
|
59
|
+
>{{ column.type }}</span
|
|
60
|
+
>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="col-span-1">
|
|
63
|
+
<svg
|
|
64
|
+
v-if="column.localization"
|
|
65
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
66
|
+
width="24"
|
|
67
|
+
height="24"
|
|
68
|
+
viewBox="0 0 24 24"
|
|
69
|
+
fill="none"
|
|
70
|
+
stroke="currentColor"
|
|
71
|
+
stroke-width="2"
|
|
72
|
+
stroke-linecap="round"
|
|
73
|
+
stroke-linejoin="round"
|
|
74
|
+
class="lucide lucide-circle-check w-4 h-4 text-emerald-600"
|
|
75
|
+
>
|
|
76
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
77
|
+
<path d="m9 12 2 2 4-4"></path>
|
|
78
|
+
</svg>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<VSBuilderDatatableControls
|
|
82
|
+
v-model:columns="data.columns"
|
|
83
|
+
:data="data.columns[index]"
|
|
84
|
+
:index
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
</template>
|
|
88
|
+
</draggable>
|
|
89
|
+
</template>
|
|
90
|
+
<template v-else>
|
|
91
|
+
<div
|
|
92
|
+
class="grid items-center grid-cols-12 gap-4 px-6 py-4 transition-colors hover:bg-gray-50/50"
|
|
93
|
+
>
|
|
94
|
+
<div class="col-span-5">
|
|
95
|
+
<p class="text-gray-500">{{ $t("builder.noColumnsFound") }}</p>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</template>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</template>
|
|
102
|
+
|
|
103
|
+
<script setup>
|
|
104
|
+
import VSBuilderDatatableForm from "./vs-builder-datatable-form.vue";
|
|
105
|
+
import VSBuilderDatatableControls from "./vs-builder-datatable-controls.vue";
|
|
106
|
+
import draggable from "vuedraggable";
|
|
107
|
+
import { List, CheckSquare, Calendar, Clock, Hash, Type, File, ListTree, Link, Image as LucideImage, LayoutTemplate } from "lucide-vue-next";
|
|
108
|
+
|
|
109
|
+
const data = defineModel();
|
|
110
|
+
|
|
111
|
+
const getColorType = (type) => {
|
|
112
|
+
if (type === "array") {
|
|
113
|
+
return "purple";
|
|
114
|
+
}
|
|
115
|
+
if (type === "boolean") {
|
|
116
|
+
return "red";
|
|
117
|
+
}
|
|
118
|
+
if (type === "date") {
|
|
119
|
+
return "red";
|
|
120
|
+
}
|
|
121
|
+
if (type === "datetime") {
|
|
122
|
+
return "purple";
|
|
123
|
+
}
|
|
124
|
+
if (type === "number") {
|
|
125
|
+
return "green";
|
|
126
|
+
}
|
|
127
|
+
if (type === "text") {
|
|
128
|
+
return "blue";
|
|
129
|
+
}
|
|
130
|
+
if (type === "file") {
|
|
131
|
+
return "yellow";
|
|
132
|
+
}
|
|
133
|
+
if (type === "select") {
|
|
134
|
+
return "yellow";
|
|
135
|
+
}
|
|
136
|
+
if (type === "slug") {
|
|
137
|
+
return "green";
|
|
138
|
+
}
|
|
139
|
+
if (type === "image") {
|
|
140
|
+
return "blue";
|
|
141
|
+
}
|
|
142
|
+
return "gray";
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const getIconType = (type) => {
|
|
146
|
+
switch (type) {
|
|
147
|
+
case "text":
|
|
148
|
+
return Type;
|
|
149
|
+
case "number":
|
|
150
|
+
return Hash;
|
|
151
|
+
case "integer":
|
|
152
|
+
return Hash;
|
|
153
|
+
case "date":
|
|
154
|
+
return Calendar;
|
|
155
|
+
case "datetime":
|
|
156
|
+
return Clock;
|
|
157
|
+
case "file":
|
|
158
|
+
return File;
|
|
159
|
+
case "select":
|
|
160
|
+
return ListTree;
|
|
161
|
+
case "multiselect":
|
|
162
|
+
return List;
|
|
163
|
+
case "mediaselect":
|
|
164
|
+
return Image;
|
|
165
|
+
case "radio":
|
|
166
|
+
return CheckSquare;
|
|
167
|
+
case "boolean":
|
|
168
|
+
return CheckSquare;
|
|
169
|
+
case "datalist":
|
|
170
|
+
return ListTree;
|
|
171
|
+
case "image":
|
|
172
|
+
return LucideImage;
|
|
173
|
+
case "slug":
|
|
174
|
+
return Link;
|
|
175
|
+
case "richtext":
|
|
176
|
+
return Type;
|
|
177
|
+
case "markdown":
|
|
178
|
+
return Type;
|
|
179
|
+
case "reference":
|
|
180
|
+
return LayoutTemplate;
|
|
181
|
+
case "relation":
|
|
182
|
+
return Link;
|
|
183
|
+
case "FileList":
|
|
184
|
+
return File;
|
|
185
|
+
default:
|
|
186
|
+
return Type;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
</script>
|
|
190
|
+
|
|
191
|
+
<style lang="scss" scoped></style>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="p-4 transition-shadow border border-gray-200 rounded-lg bg-gray-50 dark:bg-gray-700 dark:border-gray-600 hover:shadow-md"
|
|
4
|
+
>
|
|
5
|
+
<div class="flex items-start justify-between">
|
|
6
|
+
<div class="flex items-center">
|
|
7
|
+
<Database class="w-5 h-5 mr-2 text-sky-500" />
|
|
8
|
+
<h3
|
|
9
|
+
class="text-lg font-medium text-gray-900 cursor-pointer dark:text-white hover:underline"
|
|
10
|
+
@click="handleGoToContent(collection)"
|
|
11
|
+
>
|
|
12
|
+
{{ collection.title }}
|
|
13
|
+
</h3>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="flex space-x-2">
|
|
16
|
+
<button
|
|
17
|
+
@click="handleEditCollection(collection)"
|
|
18
|
+
class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
|
|
19
|
+
>
|
|
20
|
+
<Edit class="w-4 h-4" />
|
|
21
|
+
</button>
|
|
22
|
+
<button
|
|
23
|
+
@click="openConfirm"
|
|
24
|
+
class="text-gray-400 hover:text-red-500 dark:hover:text-red-400"
|
|
25
|
+
>
|
|
26
|
+
<Trash2 class="w-4 h-4" />
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
31
|
+
{{ collection.description }}
|
|
32
|
+
</p>
|
|
33
|
+
<div class="mt-4">
|
|
34
|
+
<h4
|
|
35
|
+
class="text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
|
|
36
|
+
>
|
|
37
|
+
{{ $t("collections.form.fields") }}
|
|
38
|
+
</h4>
|
|
39
|
+
<div class="flex flex-wrap gap-2 mt-2">
|
|
40
|
+
<span
|
|
41
|
+
v-for="(field, index) in collection.fields"
|
|
42
|
+
:key="index"
|
|
43
|
+
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-600 dark:text-gray-300"
|
|
44
|
+
>
|
|
45
|
+
{{ field.label }}
|
|
46
|
+
</span>
|
|
47
|
+
<button class="px-2 py-1 text-xs text-white bg-blue-500 rounded-lg">
|
|
48
|
+
{{ collection?.type || "single" }}
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<script setup lang="ts">
|
|
56
|
+
import { Database, Edit, Trash2 } from "lucide-vue-next";
|
|
57
|
+
import { useRouter } from "vue-router";
|
|
58
|
+
import { confirm } from '@opengis/core';
|
|
59
|
+
import { useI18n } from "vue-i18n";
|
|
60
|
+
const { t } = useI18n();
|
|
61
|
+
|
|
62
|
+
interface Field {
|
|
63
|
+
name: string;
|
|
64
|
+
type: "text" | "textarea" | "number" | "date" | "select" | "boolean";
|
|
65
|
+
label: string;
|
|
66
|
+
required: boolean;
|
|
67
|
+
options?: string[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface Collection {
|
|
71
|
+
id: string;
|
|
72
|
+
name: string;
|
|
73
|
+
title: string;
|
|
74
|
+
slug: string;
|
|
75
|
+
description: string;
|
|
76
|
+
fields: Field[];
|
|
77
|
+
type: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const router = useRouter();
|
|
81
|
+
const props = defineProps<{
|
|
82
|
+
collection: any;
|
|
83
|
+
handleDeleteCollection: (collection: any) => void;
|
|
84
|
+
}>();
|
|
85
|
+
|
|
86
|
+
const openConfirm = () => {
|
|
87
|
+
confirm({
|
|
88
|
+
title: t("builder.deleteTitle"),
|
|
89
|
+
message: t("builder.deleteField"),
|
|
90
|
+
type: 'error',
|
|
91
|
+
onConfirm: () => {
|
|
92
|
+
props.handleDeleteCollection(props.collection.id);
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleEditCollection = (collection: Collection) => {
|
|
98
|
+
router.push(`/settings/collections/edit/${collection.id || collection.name}`);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleGoToContent = (collection: Collection) => {
|
|
102
|
+
if (collection.type === "single") {
|
|
103
|
+
router.push(`/collections/single/${collection.name}`);
|
|
104
|
+
} else {
|
|
105
|
+
router.push(`/collections/${collection.name}`);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<style scoped></style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex items-center space-x-1 mb-4">
|
|
3
|
+
<button
|
|
4
|
+
class="flex gap-x-1 items-center px-2 py-1 rounded text-sm font-medium transition-all duration-200 text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/20 hover:bg-blue-100 dark:hover:bg-blue-900/30"
|
|
5
|
+
@click="$emit('navigate', 'collections')"
|
|
6
|
+
>
|
|
7
|
+
<Layers class="w-3 h-3" />
|
|
8
|
+
{{ $t("navigation.collections") }}
|
|
9
|
+
</button>
|
|
10
|
+
<ChevronRight
|
|
11
|
+
v-if="items.length > 0"
|
|
12
|
+
class="w-3 h-3 text-slate-400 dark:text-slate-500"
|
|
13
|
+
/>
|
|
14
|
+
<template v-for="(item, index) in items" :key="index">
|
|
15
|
+
<button
|
|
16
|
+
v-if="index < items.length - 1"
|
|
17
|
+
class="flex gap-x-1 items-center px-2 py-1 rounded text-sm font-medium transition-all duration-200 text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-900/20 hover:bg-blue-100 dark:hover:bg-blue-900/30"
|
|
18
|
+
@click="$emit('navigate', item.route)"
|
|
19
|
+
>
|
|
20
|
+
{{ item.label }}
|
|
21
|
+
</button>
|
|
22
|
+
<span
|
|
23
|
+
v-else
|
|
24
|
+
class="flex gap-x-1 items-center px-2 py-1 rounded text-sm font-medium text-slate-700 dark:text-slate-300"
|
|
25
|
+
>
|
|
26
|
+
{{ item.label }}
|
|
27
|
+
</span>
|
|
28
|
+
<ChevronRight
|
|
29
|
+
v-if="index < items.length - 1"
|
|
30
|
+
class="w-3 h-3 text-slate-400 dark:text-slate-500"
|
|
31
|
+
/>
|
|
32
|
+
</template>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script setup lang="ts">
|
|
37
|
+
import { ChevronRight, Layers } from "lucide-vue-next";
|
|
38
|
+
|
|
39
|
+
interface BreadcrumbItem {
|
|
40
|
+
label: string;
|
|
41
|
+
route: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
defineProps<{
|
|
45
|
+
items: BreadcrumbItem[];
|
|
46
|
+
}>();
|
|
47
|
+
|
|
48
|
+
defineEmits<{
|
|
49
|
+
(e: "navigate", route: string): void;
|
|
50
|
+
}>();
|
|
51
|
+
</script>
|
|
52
|
+
|