@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.
Files changed (183) hide show
  1. package/package.json +2 -2
  2. package/src/App.css +52 -0
  3. package/src/App.vue +62 -0
  4. package/src/assets/image.png +0 -0
  5. package/src/assets/main.css +3 -0
  6. package/src/assets/tailwind-3.4.17.js +113 -0
  7. package/src/components/LanguageSwitcher.vue +73 -0
  8. package/src/components/SettingsCard.vue +40 -0
  9. package/src/components/builder/CreateForm.vue +128 -0
  10. package/src/components/builder/formTypeSchema.js +145 -0
  11. package/src/components/builder/tabs/index.ts +9 -0
  12. package/src/components/builder/tabs/vs-builder-edit.vue +133 -0
  13. package/src/components/builder/tabs/vs-builder-monaco.vue +29 -0
  14. package/src/components/builder/tabs/vs-builder-preview.vue +39 -0
  15. package/src/components/builder/vs-builder-datatable-controls.vue +138 -0
  16. package/src/components/builder/vs-builder-datatable-form.vue +80 -0
  17. package/src/components/builder/vs-builder-datatable.vue +191 -0
  18. package/src/components/builder/vs-builder-list-item.vue +110 -0
  19. package/src/components/collections/CollectionsBreadcrumb.vue +52 -0
  20. package/src/components/collections/CollectionsGrid.vue +176 -0
  21. package/src/components/collections/ContentBlock.vue +75 -0
  22. package/src/components/collections/formWrapper.vue +156 -0
  23. package/src/components/dashboard/ContentItem.vue +82 -0
  24. package/src/components/dashboard/DashboardHeader.vue +33 -0
  25. package/src/components/dashboard/QuickActions.vue +84 -0
  26. package/src/components/dashboard/RecentContent.vue +54 -0
  27. package/src/components/dashboard/StatCard.vue +63 -0
  28. package/src/components/dashboard/StatsGrid.vue +28 -0
  29. package/src/components/form-components/MonacoEditor.vue +104 -0
  30. package/src/components/form-components/VsFormMeta.vue +40 -0
  31. package/src/components/form-components/VsFormTags.vue +150 -0
  32. package/src/components/form-components/custom-datatable/vs-form-custom-datatable-add.vue +84 -0
  33. package/src/components/form-components/custom-datatable/vs-form-custom-datatable-controls.vue +106 -0
  34. package/src/components/form-components/index.js +23 -0
  35. package/src/components/form-components/reference/vs-form-reference-add.vue +92 -0
  36. package/src/components/form-components/reference/vs-form-reference-controls.vue +101 -0
  37. package/src/components/form-components/reference-list/referenceOptionList.js +78 -0
  38. package/src/components/form-components/reference-list/vs-form-reference-add.vue +145 -0
  39. package/src/components/form-components/reference-list/vs-form-reference-choce.vue +39 -0
  40. package/src/components/form-components/reference-list/vs-form-reference-controls.vue +110 -0
  41. package/src/components/form-components/reference-skeleton/about-skeleton.vue +37 -0
  42. package/src/components/form-components/reference-skeleton/banner-skeleton.vue +29 -0
  43. package/src/components/form-components/reference-skeleton/body-skeleton.vue +56 -0
  44. package/src/components/form-components/reference-skeleton/cards-skeleton.vue +47 -0
  45. package/src/components/form-components/reference-skeleton/documents-skeleton.vue +64 -0
  46. package/src/components/form-components/reference-skeleton/faq-skeleton.vue +64 -0
  47. package/src/components/form-components/reference-skeleton/form-skeleton.vue +41 -0
  48. package/src/components/form-components/reference-skeleton/index.js +36 -0
  49. package/src/components/form-components/reference-skeleton/infoLine-skeleton.vue +37 -0
  50. package/src/components/form-components/reference-skeleton/news-skeleton.vue +54 -0
  51. package/src/components/form-components/reference-skeleton/slider-skeleton.vue +41 -0
  52. package/src/components/form-components/reference-skeleton/tabs-skeleton.vue +40 -0
  53. package/src/components/form-components/reference-skeleton/team-skeleton.vue +103 -0
  54. package/src/components/form-components/reference-skeleton/usefulLinks-skeleton.vue +52 -0
  55. package/src/components/form-components/reference-skeleton/video-skeleton.vue +36 -0
  56. package/src/components/form-components/testReferenceTypes.js +773 -0
  57. package/src/components/form-components/vs-form-color-picker.vue +29 -0
  58. package/src/components/form-components/vs-form-custom-datatable.vue +214 -0
  59. package/src/components/form-components/vs-form-integer.vue +86 -0
  60. package/src/components/form-components/vs-form-key-value.vue +201 -0
  61. package/src/components/form-components/vs-form-marcdown-md.vue +3 -0
  62. package/src/components/form-components/vs-form-media-select.vue +780 -0
  63. package/src/components/form-components/vs-form-reference-list.vue +97 -0
  64. package/src/components/form-components/vs-form-reference.vue +59 -0
  65. package/src/components/form-components/vs-form-relation.vue +30 -0
  66. package/src/components/form-components/vs-form-reletion-link.vue +34 -0
  67. package/src/components/form-components/vs-form-select-collection.vue +0 -0
  68. package/src/components/form-components/vs-form-slug.vue +72 -0
  69. package/src/components/form-components/vs-form-tiptap.vue +7 -0
  70. package/src/components/form-components/vs-richtext-md.vue +3 -0
  71. package/src/components/icons/BellIcon.vue +17 -0
  72. package/src/components/icons/GlobeIcon.vue +18 -0
  73. package/src/components/icons/KeyIcon.vue +20 -0
  74. package/src/components/icons/PaletteIcon.vue +22 -0
  75. package/src/components/icons/SettingsIcon.vue +19 -0
  76. package/src/components/icons/ShieldIcon.vue +18 -0
  77. package/src/components/icons/UsersIcon.vue +19 -0
  78. package/src/components/icons/icon-chevron-right.vue +16 -0
  79. package/src/components/icons/icon-drag.vue +20 -0
  80. package/src/components/icons/icon-file-text.vue +21 -0
  81. package/src/components/icons/icon-folder.vue +18 -0
  82. package/src/components/icons/icon-grid.vue +17 -0
  83. package/src/components/icons/icon-group.vue +19 -0
  84. package/src/components/icons/icon-home.vue +16 -0
  85. package/src/components/icons/icon-image.vue +18 -0
  86. package/src/components/icons/icon-list.vue +20 -0
  87. package/src/components/icons/icon-more.vue +17 -0
  88. package/src/components/icons/icon-plus.vue +17 -0
  89. package/src/components/icons-types/icon-array.vue +22 -0
  90. package/src/components/icons-types/icon-boolean.vue +18 -0
  91. package/src/components/icons-types/icon-datalist.vue +22 -0
  92. package/src/components/icons-types/icon-date.vue +20 -0
  93. package/src/components/icons-types/icon-datetime.vue +20 -0
  94. package/src/components/icons-types/icon-file.vue +21 -0
  95. package/src/components/icons-types/icon-gallery.vue +18 -0
  96. package/src/components/icons-types/icon-image.vue +19 -0
  97. package/src/components/icons-types/icon-integer.vue +20 -0
  98. package/src/components/icons-types/icon-merkdown.vue +18 -0
  99. package/src/components/icons-types/icon-multiselect.vue +22 -0
  100. package/src/components/icons-types/icon-number.vue +20 -0
  101. package/src/components/icons-types/icon-radio.vue +22 -0
  102. package/src/components/icons-types/icon-reference-list.vue +22 -0
  103. package/src/components/icons-types/icon-reference.vue +20 -0
  104. package/src/components/icons-types/icon-relation.vue +22 -0
  105. package/src/components/icons-types/icon-richtext.vue +18 -0
  106. package/src/components/icons-types/icon-select.vue +22 -0
  107. package/src/components/icons-types/icon-slug.vue +19 -0
  108. package/src/components/icons-types/icon-text.vue +19 -0
  109. package/src/components/icons-types/index.js +43 -0
  110. package/src/components/layout/Layout.vue +67 -0
  111. package/src/components/layout/Sidebar.vue +128 -0
  112. package/src/components/media/FileUploadProgress.vue +29 -0
  113. package/src/components/media/MediaBreadcrumb.vue +42 -0
  114. package/src/components/media/MediaCreateFolder.vue +59 -0
  115. package/src/components/media/MediaFileInfo.vue +148 -0
  116. package/src/components/media/MediaGrid.vue +148 -0
  117. package/src/components/media/MediaList.vue +148 -0
  118. package/src/components/media/MediaViewControls.vue +38 -0
  119. package/src/components/media/TypeTag.vue +23 -0
  120. package/src/components/menu/AddNewItemInTree.vue +75 -0
  121. package/src/components/menu/MenuBody.vue +149 -0
  122. package/src/components/menu/MenuItem.vue +73 -0
  123. package/src/components/menu/MenuList.vue +101 -0
  124. package/src/components/referencec/index.ts +7 -0
  125. package/src/components/referencec/vs-reference-faq.vue +61 -0
  126. package/src/components/referencec/vs-reference-user-card.vue +40 -0
  127. package/src/components/settings/NotificationSettings.vue +32 -0
  128. package/src/components/settings/SettingsTable.vue +50 -0
  129. package/src/components/settings/SettingsTitle.vue +33 -0
  130. package/src/components/settings/SettingsToggleItem.vue +25 -0
  131. package/src/components/sidebar/DropdownMenu.vue +34 -0
  132. package/src/components/sidebar/SettingsSidebar.vue +121 -0
  133. package/src/components/sidebar/SidebarFooter.vue +52 -0
  134. package/src/components/sidebar/SidebarHeader.vue +57 -0
  135. package/src/components/sidebar/SidebarMenu.vue +78 -0
  136. package/src/components/ui/EmptyData.vue +76 -0
  137. package/src/components/ui/UniversalTable.vue +310 -0
  138. package/src/components/ui/UniversalTableFilters.vue +0 -0
  139. package/src/components/ui/UniversalTablePagination.vue +118 -0
  140. package/src/components/ui/VsPreview.vue +75 -0
  141. package/src/composables/useCollectionView.ts +21 -0
  142. package/src/composables/useDebounce.ts +26 -0
  143. package/src/composables/useMonaco.ts +28 -0
  144. package/src/composables/useTheme.ts +40 -0
  145. package/src/content/test-slug/metadata.json +1 -0
  146. package/src/i18n.ts +75 -0
  147. package/src/index.css +3 -0
  148. package/src/locales/en.json +778 -0
  149. package/src/locales/uk.json +797 -0
  150. package/src/main.ts +41 -0
  151. package/src/pages/Dashboard.vue +168 -0
  152. package/src/pages/EmailPage.vue +183 -0
  153. package/src/pages/FeedbackPage.vue +232 -0
  154. package/src/pages/MediaPage.vue +372 -0
  155. package/src/pages/TagsPage.vue +207 -0
  156. package/src/pages/builder/BuilderPage.vue +195 -0
  157. package/src/pages/builder/EditCollectionPage.vue +163 -0
  158. package/src/pages/collections/ArticlesPage.vue +385 -0
  159. package/src/pages/collections/CollectionsPage.vue +146 -0
  160. package/src/pages/collections/SingletonsPage.vue +119 -0
  161. package/src/pages/collections/contentForm.vue +484 -0
  162. package/src/pages/collections/schema/seo.ts +27 -0
  163. package/src/pages/menu/MenuAddPage.vue +123 -0
  164. package/src/pages/menu/MenuItemPage.vue +183 -0
  165. package/src/pages/menu/MenuPage.vue +133 -0
  166. package/src/pages/settings/ApiKeys.vue +75 -0
  167. package/src/pages/settings/Appearance.vue +80 -0
  168. package/src/pages/settings/Logs.vue +260 -0
  169. package/src/pages/settings/PermissionsPage.vue +237 -0
  170. package/src/pages/settings/Settings.vue +186 -0
  171. package/src/pages/settings/Users.vue +109 -0
  172. package/src/pages/settings/general.vue +154 -0
  173. package/src/pages/settings/generalScheme.js +132 -0
  174. package/src/pages/users/AddUser.vue +106 -0
  175. package/src/pages/users/UsersPage.vue +98 -0
  176. package/src/props/builder.ts +67 -0
  177. package/src/props/content.ts +56 -0
  178. package/src/props/media.ts +63 -0
  179. package/src/router/index.ts +181 -0
  180. package/src/types/fastify-auth.d.ts +4 -0
  181. package/src/utils/getField.js +270 -0
  182. package/src/utils/translit.js +19 -0
  183. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,75 @@
1
+ <template>
2
+ <VsModal
3
+ teleport="#modal"
4
+ :visible="isLocalOpen"
5
+ :title="$t('menu.createMenu')"
6
+ template="default"
7
+ :closeClickBack="false"
8
+ @close="isLocalOpen = false"
9
+ >
10
+ <VsForm v-model:form="form" v-model="values" :schema="scheme" />
11
+ <template #footer>
12
+ <div class="flex justify-end p-[20px] gap-[10px] border-t w-full">
13
+ <button
14
+ 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"
15
+ @click="isLocalOpen = false; values = {}"
16
+ >
17
+ {{ $t("common.actions.cancel") }}
18
+ </button>
19
+ <button
20
+ @click="handleCreateCollection"
21
+ 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"
22
+ >
23
+ {{ $t("common.actions.save") }}
24
+ </button>
25
+ </div>
26
+ </template>
27
+ </VsModal>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import { ref } from 'vue';
32
+ import { useI18n } from 'vue-i18n';
33
+ import VsForm from "@opengis/form";
34
+ import { VsModal } from "@opengis/core";
35
+ import { notify } from '@opengis/core';
36
+ const { t } = useI18n();
37
+
38
+ const isLocalOpen = defineModel<boolean>('isOpen', { required: true });
39
+ const form = ref<any>({});
40
+
41
+ const emit = defineEmits([ 'addNewItem']);
42
+
43
+ const values = ref({});
44
+
45
+ const scheme = {
46
+ title: {
47
+ type: "text",
48
+ label: t("menu.form.name"),
49
+ required: true,
50
+ placeholder: t("menu.form.name"),
51
+ validators: ["required"],
52
+ },
53
+ value: {
54
+ type: "text",
55
+ label: t("menu.form.value"),
56
+ required: true,
57
+ placeholder: t("menu.form.value"),
58
+ validators: ["required"],
59
+ },
60
+ };
61
+
62
+ const handleCreateCollection = async () => {
63
+ let result = await form.value.validate();
64
+ if (result) {
65
+ notify({
66
+ type: "warning",
67
+ title: t("common.actions.warning"),
68
+ message: t("menu.createMenuFailed"),
69
+ });
70
+ return;
71
+ }
72
+ emit('addNewItem', values.value);
73
+ values.value = {}; // Очищуємо форму після створення
74
+ }
75
+ </script>
@@ -0,0 +1,149 @@
1
+ <template>
2
+ <VForm :schema="scheme" v-model="formValue" v-model:form="form" />
3
+ <button
4
+ @click="isOpen = true"
5
+ class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium w-min ml-auto my-6 h-9 px-4 py-2 bg-blue-600 text-white shadow-md transition-all duration-200 transform
6
+ hover:bg-blue-700 hover:shadow-lg hover:scale-105
7
+ focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring
8
+ disabled:pointer-events-none disabled:opacity-50"
9
+ >
10
+ <Plus class="w-4 h-4 mr-2" />
11
+ {{ $t("menu.createMenu") }}
12
+ </button>
13
+ <AddNewItemInTree
14
+ v-model:isOpen="isOpen"
15
+ v-model:form="addModalForm"
16
+ @addNewItem="addNewItem"
17
+ />
18
+ <MenuList
19
+ class="px-2"
20
+ :list="formValue.items"
21
+ @update:list="updateList"
22
+ @delete:item="deleteItem"
23
+ />
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ import MenuList from './MenuList.vue';
28
+ import { onBeforeMount, ref } from 'vue';
29
+ import AddNewItemInTree from './AddNewItemInTree.vue';
30
+ import MonacoEditor from '../form-components/MonacoEditor.vue';
31
+ import { Plus } from "lucide-vue-next";
32
+ import { useI18n } from 'vue-i18n';
33
+ type MenuItem = { title: string; value: string; id: string, children: MenuItem[] };
34
+ import { VForm, inputs } from '@opengis/form'
35
+ import { notify } from '@opengis/core';
36
+ inputs['vs-input-monaco-editor'] = MonacoEditor
37
+
38
+ const { t } = useI18n();
39
+
40
+ const isOpen = ref(false);
41
+ const addModalForm = ref<any>(null);
42
+ const formValue = defineModel<any>('modelValue');
43
+ const form = ref<any>({});
44
+ const addNewItem = async (item: MenuItem) => {
45
+ try {
46
+ let result = await form.value.validate();
47
+ if (result) {
48
+ notify({
49
+ type: "warning",
50
+ title: t("common.actions.warning"),
51
+ message: t("menu.createMenuFailed"),
52
+ });
53
+ return;
54
+ }
55
+ isOpen.value = false;
56
+
57
+ item.id = Math.random().toString(36).substr(2, 9);
58
+ item.children = [];
59
+ formValue.value.items.unshift(item);
60
+ } catch (error) {
61
+
62
+ console.log(error);
63
+ }
64
+ }
65
+
66
+ const scheme = ref<Record<string, any>>([
67
+ {
68
+ key: "name",
69
+ title: t("common.title"),
70
+ type: "text",
71
+ placeholder: t("common.title"),
72
+ required: true,
73
+ validators: ["required"],
74
+ },
75
+ {
76
+ key: "description",
77
+ title: t("common.description"),
78
+ type: "text",
79
+ placeholder: t("common.description"),
80
+ required: true,
81
+ validators: ["required"],
82
+ },
83
+ {
84
+ key: "locale",
85
+ title: t("menu.form.language"),
86
+ type: "select",
87
+ placeholder: t("menu.form.language"),
88
+ required: true,
89
+ options: [
90
+ {text: "Ukrainian", id: "uk"},
91
+ {text: "English", id: "en"},
92
+ ],
93
+ validators: ["required"],
94
+ },
95
+ ]);
96
+
97
+ const convertToArray = (obj: object): MenuItem[] => {
98
+ let dataArray: MenuItem[] = [];
99
+
100
+ for (const key in obj) {
101
+ if (typeof obj[key] === 'object') {
102
+ dataArray.push({
103
+ title: key,
104
+ value: '',
105
+ id: Math.random().toString(36).substr(2, 9),
106
+ children: convertToArray(obj[key])
107
+ });
108
+ } else {
109
+ dataArray.push({
110
+ title: key,
111
+ value: obj[key],
112
+ id: Math.random().toString(36).substr(2, 9),
113
+ children: []
114
+ });
115
+ }
116
+ }
117
+
118
+ return dataArray;
119
+ }
120
+
121
+ const updateList = (newVal: any) => {
122
+ formValue.value.items = newVal;
123
+ };
124
+
125
+ const deleteItem = (item: any) => {
126
+ removeItemRecursiveInPlace(formValue.value.items, item.id);
127
+ };
128
+
129
+ function removeItemRecursiveInPlace(list: any[], id: string): boolean {
130
+ for (let i = list.length - 1; i >= 0; i--) {
131
+ if (list[i].id === id) {
132
+ list.splice(i, 1);
133
+ return true;
134
+ } else if (list[i].children && list[i].children.length) {
135
+ if (removeItemRecursiveInPlace(list[i].children, id)) {
136
+ return true;
137
+ }
138
+ }
139
+ }
140
+
141
+ return false;
142
+ }
143
+
144
+ onBeforeMount(()=> {
145
+ if (!Array.isArray(formValue.value.items)) {
146
+ formValue.value.items = convertToArray(formValue.value.items)
147
+ }
148
+ })
149
+ </script>
@@ -0,0 +1,73 @@
1
+ <template>
2
+ <div :class="[{'space-y-1': typeof value!=='string'}, 'dark:bg-transparent']">
3
+ <div v-if="!isEditing" class="p-3 flex items-center gap-x-3 cursor-grab bg-gray-100 border border-gray-300 rounded-lg font-medium text-sm text-gray-800 dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-200">
4
+ {{ title }}
5
+ <span v-if="typeof value === 'string'" class="text-xs text-gray-500 dark:text-neutral-400 ml-2">{{ value }}</span>
6
+ <Pencil v-if="typeof value === 'string'" class="shrink-0 size-4 ml-auto text-gray-500 dark:text-neutral-500 cursor-pointer hover:text-blue-600 dark:hover:text-blue-400" @click.stop="startEdit" />
7
+ <Trash2 class="shrink-0 size-4 text-gray-500 dark:text-neutral-500 cursor-pointer hover:text-red-600 dark:hover:text-red-400" @click.stop="emit('delete')" />
8
+ <IconDrag class="shrink-0 size-4 cursor-pointer text-gray-500 dark:text-neutral-500" />
9
+ </div>
10
+ <div v-else class="p-3 flex items-center gap-x-2 bg-gray-100 border border-gray-300 rounded-lg dark:bg-neutral-900 dark:border-neutral-700">
11
+ <span class="text-sm font-medium text-gray-800 dark:text-neutral-200 shrink-0">{{ title }}:</span>
12
+ <input
13
+ v-model="editedValue"
14
+ @keyup.enter="saveEdit"
15
+ @keyup.esc="cancelEdit"
16
+ @blur="saveEdit"
17
+ class="flex-1 px-2 py-1 text-sm border border-gray-300 rounded dark:bg-neutral-800 dark:border-neutral-600 dark:text-neutral-200 focus:outline-none focus:ring-2 focus:ring-blue-500"
18
+ type="text"
19
+ placeholder="/link"
20
+ />
21
+ <button
22
+ @click.stop="saveEdit"
23
+ class="shrink-0 p-1 text-green-600 dark:text-green-400 hover:bg-green-50 dark:hover:bg-green-900/20 rounded"
24
+ title="Save"
25
+ >
26
+ <Check class="size-4" />
27
+ </button>
28
+ <button
29
+ @click.stop="cancelEdit"
30
+ class="shrink-0 p-1 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded"
31
+ title="Cancel"
32
+ >
33
+ <X class="size-4" />
34
+ </button>
35
+ </div>
36
+ </div>
37
+
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ import IconDrag from '../icons/icon-drag.vue';
42
+ import { Trash2, Pencil, Check, X } from "lucide-vue-next";
43
+ import { ref } from 'vue';
44
+
45
+ const props = defineProps<{
46
+ title: string;
47
+ value: string | Array<any>;
48
+ }>();
49
+
50
+ const emit = defineEmits(['delete', 'update:value']);
51
+
52
+ const isEditing = ref(false);
53
+ const editedValue = ref('');
54
+
55
+ const startEdit = () => {
56
+ if (typeof props.value === 'string') {
57
+ editedValue.value = props.value;
58
+ isEditing.value = true;
59
+ }
60
+ };
61
+
62
+ const saveEdit = () => {
63
+ if (typeof props.value === 'string') {
64
+ emit('update:value', editedValue.value);
65
+ isEditing.value = false;
66
+ }
67
+ };
68
+
69
+ const cancelEdit = () => {
70
+ isEditing.value = false;
71
+ editedValue.value = '';
72
+ };
73
+ </script>
@@ -0,0 +1,101 @@
1
+ <template>
2
+ <draggable
3
+ tag="ul"
4
+ :list="singleArray"
5
+ :group="{ name: 'nested', pull: true, put: true }"
6
+ item-key="id"
7
+ :class="['nested-sortable space-y-1', {'ps-5 dark:bg-transparent': inside}]"
8
+ @change="onChange"
9
+ @end="onChange"
10
+ >
11
+ <template #item="{element}">
12
+ <div class="space-y-1">
13
+ <MenuItem
14
+ :title="element.title"
15
+ :value="element.value"
16
+ @delete="deleteItem(element)"
17
+ @update:value="updateItemValue(element, $event)"
18
+ />
19
+ <MenuList
20
+ v-if="Array.isArray(element.children)"
21
+ :list="element.children"
22
+ :inside="true"
23
+ @update:list="updateChildrenList(element, $event)"
24
+ @delete:item="deleteItem"
25
+ />
26
+ </div>
27
+ </template>
28
+ </draggable>
29
+ </template>
30
+
31
+ <script setup lang="ts">
32
+ import draggable from 'vuedraggable';
33
+ import MenuItem from '../../components/menu/MenuItem.vue';
34
+ import { confirm } from '@opengis/core';
35
+ import { ref, watch, nextTick } from 'vue';
36
+ import { useI18n } from "vue-i18n";
37
+ const { t } = useI18n();
38
+
39
+ const props = defineProps({
40
+ list: {
41
+ type: Array,
42
+ default: () => []
43
+ },
44
+ inside: {
45
+ type: Boolean,
46
+ default: false
47
+ }
48
+ });
49
+ const emit = defineEmits(['update:list', 'delete:item']);
50
+
51
+ const singleArray = ref<any[]>([]);
52
+
53
+ watch(
54
+ () => props.list,
55
+ (val) => {
56
+ // Створюємо новий масив, щоб уникнути проблем з реактивністю
57
+ singleArray.value = val ? [...val] : [];
58
+ },
59
+ { immediate: true, deep: true }
60
+ );
61
+
62
+ const onChange = () => {
63
+ emit('update:list', singleArray.value);
64
+ };
65
+
66
+ const deleteItem = (item: any) => {
67
+ confirm({
68
+ title: t("builder.deleteTitle"),
69
+ message: t("builder.deleteObject"),
70
+ type: 'error',
71
+ onConfirm: () => {
72
+ emit('update:list', singleArray.value);
73
+ emit('delete:item', item);
74
+ },
75
+ })
76
+ };
77
+
78
+ const updateItemValue = (item: any, newValue: string) => {
79
+ // Оновлюємо значення безпосередньо в об'єкті
80
+ item.value = newValue;
81
+ // Використовуємо nextTick, щоб переконатися, що зміни застосовані
82
+ nextTick(() => {
83
+ // Створюємо нову копію масиву, щоб Vue відстежував зміни
84
+ singleArray.value = [...singleArray.value];
85
+ // Поширюємо оновлення вгору по дереву, зберігаючи всю структуру
86
+ emit('update:list', singleArray.value);
87
+ });
88
+ };
89
+
90
+ const updateChildrenList = (element: any, newChildren: any[]) => {
91
+ // Оновлюємо children безпосередньо в елементі
92
+ element.children = newChildren;
93
+ // Використовуємо nextTick, щоб переконатися, що зміни застосовані
94
+ nextTick(() => {
95
+ // Створюємо нову копію масиву, щоб Vue відстежував зміни
96
+ singleArray.value = [...singleArray.value];
97
+ // Поширюємо оновлення вгору по дереву
98
+ emit('update:list', singleArray.value);
99
+ });
100
+ };
101
+ </script>
@@ -0,0 +1,7 @@
1
+ import VsReferenceUserCard from "./vs-reference-user-card.vue";
2
+ import VsReferenceFaq from "./vs-reference-faq.vue";
3
+
4
+ export default {
5
+ "vs-reference-user-card": VsReferenceUserCard,
6
+ "vs-reference-faq": VsReferenceFaq,
7
+ };
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <div class="w-full border rounded p-[16px]">
3
+ <div class="flex items-center cursor-pointer" @click="isOpen = !isOpen">
4
+ <span
5
+ class="block w-[22px] mr-[10px] h-[22px] p-[3px] rounded-full border shrink-0 border-[#3F2B72] text-[#3F2B72] flex items-center justify-center"
6
+ >
7
+ <span
8
+ class="text-[10px] h-full w-full rounded-full flex items-center justify-center bg-[#3F2B72] text-white"
9
+ >
10
+ {{ index + 1 }}
11
+ </span>
12
+ </span>
13
+ <h3 class="text-[20px] font-medium">{{ title }}</h3>
14
+ <button class="ml-auto shrink-0" :class="{ 'rotate-180': isOpen }">
15
+ <svg
16
+ xmlns="http://www.w3.org/2000/svg"
17
+ width="24"
18
+ height="24"
19
+ viewBox="0 0 24 24"
20
+ fill="none"
21
+ stroke="currentColor"
22
+ stroke-width="2"
23
+ stroke-linecap="round"
24
+ stroke-linejoin="round"
25
+ class="icon icon-tabler icons-tabler-outline icon-tabler-chevron-down"
26
+ >
27
+ <path stroke="none" d="M0 0h24v24H0z" fill="none" />
28
+ <path d="M6 9l6 6l6 -6" />
29
+ </svg>
30
+ </button>
31
+ </div>
32
+ <transition name="fade">
33
+ <div v-if="isOpen" class="mt-[24px]">
34
+ <div v-html="description"></div>
35
+ </div>
36
+ </transition>
37
+ </div>
38
+ </template>
39
+
40
+ <script setup>
41
+ import { ref } from "vue";
42
+
43
+ const props = defineProps({
44
+ title: {
45
+ type: String,
46
+ required: true,
47
+ },
48
+ description: {
49
+ type: String,
50
+ required: true,
51
+ },
52
+ index: {
53
+ type: Number,
54
+ required: true,
55
+ },
56
+ });
57
+
58
+ const isOpen = ref(false);
59
+ </script>
60
+
61
+ <style lang="scss" scoped></style>
@@ -0,0 +1,40 @@
1
+ <template>
2
+ <div
3
+ class="aspect-video bg-gray-100 dark:bg-gray-700 flex items-center justify-center"
4
+ >
5
+ <img
6
+ :src="image || noDataImage"
7
+ alt="Team collaboration"
8
+ class="w-full h-full object-cover"
9
+ />
10
+ </div>
11
+ <div class="p-3">
12
+ <h4 class="text-sm font-medium text-gray-900 dark:text-white truncate">
13
+ {{ name }}
14
+ </h4>
15
+ <p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
16
+ {{ date }}
17
+ </p>
18
+ </div>
19
+ </template>
20
+
21
+ <script setup>
22
+ import noDataImage from "../../assets/image.png";
23
+
24
+ const props = defineProps({
25
+ image: {
26
+ type: String,
27
+ required: true,
28
+ },
29
+ name: {
30
+ type: String,
31
+ required: true,
32
+ },
33
+ date: {
34
+ type: String,
35
+ required: true,
36
+ },
37
+ });
38
+ </script>
39
+
40
+ <style lang="scss" scoped></style>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <template v-for="(item, idx) in modelValue" :key="item.key">
3
+ <div
4
+ class="flex min-h-[61px]"
5
+ :class="{
6
+ 'border-b border-slate-200': idx < modelValue.length - 1,
7
+ 'items-center justify-between gap-4': !item.options,
8
+ 'flex-col items-start': item.options && item.options.length > 0
9
+ }"
10
+ >
11
+ <div>
12
+ <label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-slate-700 dark:text-slate-300">
13
+ {{ item.label }}
14
+ </label>
15
+ <p class="text-sm text-slate-500 dark:text-slate-400">{{ item.description }}</p>
16
+ </div>
17
+ <VsSelect
18
+ v-if="item.options && item.options.length > 0"
19
+ :options="item.options"
20
+ v-model="item.value"
21
+ class="min-h-10 mt-1"
22
+ />
23
+ <SettingsToggleItem v-else v-model="item.checked" />
24
+ </div>
25
+ </template>
26
+ </template>
27
+
28
+ <script setup>
29
+ import SettingsToggleItem from "./SettingsToggleItem.vue";
30
+
31
+ const modelValue = defineModel("modelValue");
32
+ </script>
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <div class="rounded-xl text-card-foreground shadow-lg border-0 bg-white dark:bg-slate-800 backdrop-blur-sm">
3
+ <div class="flex flex-col space-y-1.5 p-6 border-b border-slate-200 dark:border-slate-700 bg-gradient-to-r from-slate-50 dark:from-slate-800 to-white dark:to-slate-800">
4
+ <h3 class="tracking-tight text-xl font-semibold text-slate-800 dark:text-slate-100 flex items-center">
5
+ <component
6
+ :is="icon"
7
+ class="lucide lucide-users w-5 h-5 mr-2"
8
+ :class="colorScheme[color]"
9
+ />
10
+ {{ title }}
11
+ </h3>
12
+ </div>
13
+ <div class="p-6 space-y-4">
14
+ <slot name="content" />
15
+ <NotificationSettings v-model="modelValue" />
16
+ </div>
17
+ </div>
18
+ </template>
19
+
20
+ <script setup>
21
+ import NotificationSettings from '@/components/settings/NotificationSettings.vue';
22
+
23
+ const props = defineProps({
24
+ icon: {
25
+ type: Object,
26
+ required: true,
27
+ },
28
+ title: {
29
+ type: String,
30
+ required: true,
31
+ },
32
+ color: {
33
+ type: String,
34
+ required: false,
35
+ default: 'green',
36
+ },
37
+ });
38
+
39
+ const colorScheme = {
40
+ green: 'text-green-600',
41
+ blue: 'text-blue-600',
42
+ purple: 'text-purple-600',
43
+ red: 'text-red-600',
44
+ yellow: 'text-yellow-600',
45
+ orange: 'text-orange-600',
46
+ pink: 'text-pink-600',
47
+ }
48
+
49
+ const modelValue = defineModel("modelValue");
50
+ </script>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <div class="mb-8">
3
+ <div class="flex items-center gap-2">
4
+ <h1 class="text-3xl font-bold text-slate-800 dark:text-slate-100 mb-2">
5
+ {{ $t(title) }}
6
+ </h1>
7
+ <a :href="`https://cms.opengis.info/${locale}/config/general-settings/`" target="_blank" :title="$t('guide.settings')">
8
+ <HelpCircle class="w-5 h-5" />
9
+ </a>
10
+ </div>
11
+ <p class="text-slate-600 dark:text-slate-300">
12
+ {{ $t(description) }}
13
+ </p>
14
+ </div>
15
+ </template>
16
+
17
+ <script setup>
18
+ import { HelpCircle } from "lucide-vue-next";
19
+ import { useI18n } from "vue-i18n";
20
+
21
+ const { locale } = useI18n();
22
+
23
+ const props = defineProps({
24
+ title: {
25
+ type: String,
26
+ required: true,
27
+ },
28
+ description: {
29
+ type: String,
30
+ required: true,
31
+ },
32
+ });
33
+ </script>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <button
3
+ type="button"
4
+ role="switch"
5
+ :aria-checked="modelValue ? 'true' : 'false'"
6
+ :data-state="modelValue ? 'checked' : 'unchecked'"
7
+ value="on"
8
+ @click="modelValue = !modelValue"
9
+ class="peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors
10
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background
11
+ disabled:cursor-not-allowed disabled:opacity-50
12
+ data-[state=checked]:bg-blue-600 data-[state=unchecked]:bg-slate-200
13
+ dark:data-[state=unchecked]:bg-slate-600"
14
+ >
15
+ <span
16
+ :data-state="modelValue ? 'checked' : 'unchecked'"
17
+ class="pointer-events-none block h-4 w-4 rounded-full bg-white shadow-lg ring-0 transition-transform shrink-0
18
+ data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-1"
19
+ ></span>
20
+ </button>
21
+ </template>
22
+
23
+ <script setup>
24
+ const modelValue = defineModel("modelValue");
25
+ </script>