@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,63 @@
1
+ <template>
2
+ <div class="rounded-xl text-card-foreground shadow-lg border-0 bg-white dark:bg-slate-800 backdrop-blur-sm hover:shadow-xl transition-all duration-300 transform hover:scale-105">
3
+ <div class="p-6">
4
+ <div class="flex items-start justify-between">
5
+ <div class="flex-1">
6
+ <div class="flex items-center space-x-2 mb-2">
7
+ <div :class="`p-2 rounded-lg bg-${stat.color}-50 dark:bg-${stat.color}-900/20`">
8
+ <component
9
+ :is="getIcon(stat.icon)"
10
+ :class="`w-5 h-5 text-${stat.color}-600`"
11
+ />
12
+ </div>
13
+ <p class="text-sm font-medium text-slate-600 dark:text-slate-400">
14
+ {{ stat.title }}
15
+ </p>
16
+ </div>
17
+ <p class="text-3xl font-bold text-slate-800 dark:text-slate-100 mb-1">
18
+ {{ stat.value }}
19
+ </p>
20
+ <p class="text-xs text-slate-500 dark:text-slate-400 mb-3">
21
+ {{ stat.description }}
22
+ </p>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import {
31
+ FileText,
32
+ Users,
33
+ Image,
34
+ Eye,
35
+ ArrowUpRight,
36
+ ArrowDownRight
37
+ } from 'lucide-vue-next';
38
+
39
+ interface StatItem {
40
+ id: string;
41
+ title: string;
42
+ value: number | string;
43
+ description: string;
44
+ icon: string;
45
+ color: string;
46
+ }
47
+
48
+ interface Props {
49
+ stat: StatItem;
50
+ }
51
+
52
+ defineProps<Props>();
53
+
54
+ const getIcon = (iconName: string) => {
55
+ const icons = {
56
+ 'file-text': FileText,
57
+ 'users': Users,
58
+ 'image': Image,
59
+ 'eye': Eye
60
+ };
61
+ return icons[iconName as keyof typeof icons] || FileText;
62
+ };
63
+ </script>
@@ -0,0 +1,28 @@
1
+ <template>
2
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
3
+ <StatCard
4
+ v-for="stat in stats"
5
+ :key="stat.id"
6
+ :stat="stat"
7
+ />
8
+ </div>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import StatCard from './StatCard.vue';
13
+
14
+ interface StatItem {
15
+ id: string;
16
+ title: string;
17
+ value: number | string;
18
+ description: string;
19
+ icon: string;
20
+ color: string;
21
+ }
22
+
23
+ interface Props {
24
+ stats: StatItem[];
25
+ }
26
+
27
+ defineProps<Props>();
28
+ </script>
@@ -0,0 +1,104 @@
1
+ <template>
2
+ <div ref="editorContainer" :style="{ width, height }"></div>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ import { ref, onMounted, onBeforeUnmount, watch } from "vue";
7
+ import { useMonaco } from "../../composables/useMonaco";
8
+
9
+ const props = withDefaults(
10
+ defineProps<{
11
+ language?: string;
12
+ theme?: string;
13
+ width?: string;
14
+ height?: string;
15
+ options?: any;
16
+ }>(),
17
+ {
18
+ language: "javascript",
19
+ theme: "vs-dark",
20
+ width: "100%",
21
+ height: "100%",
22
+ options: () => ({
23
+ minimap: { enabled: false },
24
+ }),
25
+ }
26
+ );
27
+
28
+ const modelValue = defineModel<string>({
29
+ required: true,
30
+ });
31
+
32
+ const editorContainer = ref<HTMLElement | null>(null);
33
+ let editor: any = null;
34
+ let monaco: any = null;
35
+
36
+ // Initialize editor
37
+ onMounted(async () => {
38
+ if (!editorContainer.value) return;
39
+
40
+ try {
41
+ // Завантажуємо Monaco Editor через composable
42
+ monaco = await useMonaco();
43
+
44
+ // Ensure the value is properly formatted with line breaks
45
+ const formattedValue = modelValue.value?.replace(/\\n/g, "\n") || "";
46
+
47
+ editor = monaco.editor.create(editorContainer.value, {
48
+ value: formattedValue,
49
+ language: props.language,
50
+ theme: props.theme,
51
+ automaticLayout: true,
52
+ ...props.options,
53
+ });
54
+
55
+ // Handle value changes from editor
56
+ editor.onDidChangeModelContent(() => {
57
+ const value = editor?.getValue() || "";
58
+ modelValue.value = value;
59
+ });
60
+ } catch (error) {
61
+ console.error('Failed to load Monaco Editor:', error);
62
+ }
63
+ });
64
+
65
+ // Handle prop changes
66
+ watch(
67
+ () => modelValue.value,
68
+ (newValue) => {
69
+ if (editor) {
70
+ const formattedValue = newValue?.replace(/\\n/g, "\n") || "";
71
+ if (formattedValue !== editor.getValue()) {
72
+ editor.setValue(formattedValue);
73
+ }
74
+ }
75
+ },
76
+ { immediate: true }
77
+ );
78
+
79
+ watch(
80
+ () => props.language,
81
+ (newValue) => {
82
+ if (editor && newValue && monaco) {
83
+ monaco.editor.setModelLanguage(editor.getModel()!, newValue);
84
+ }
85
+ }
86
+ );
87
+
88
+ watch(
89
+ () => props.theme,
90
+ (newValue) => {
91
+ if (editor && newValue && monaco) {
92
+ monaco.editor.setTheme(newValue);
93
+ }
94
+ }
95
+ );
96
+
97
+ // Cleanup
98
+ onBeforeUnmount(() => {
99
+ if (editor) {
100
+ editor.dispose();
101
+ editor = null;
102
+ }
103
+ });
104
+ </script>
@@ -0,0 +1,40 @@
1
+ <template>
2
+ <VsForm v-model:form="form" :schema="scheme" v-model:values="model" />
3
+ </template>
4
+
5
+ <script setup>
6
+ import VsForm from '@opengis/form'
7
+ import { ref, provide } from "vue";
8
+ import { useI18n } from "vue-i18n";
9
+
10
+ const { t } = useI18n();
11
+
12
+ const model = ref({});
13
+ const form = ref({});
14
+
15
+ const scheme = [
16
+ {
17
+ key: "title",
18
+ label: t("common.title"),
19
+ type: "text",
20
+ },
21
+ {
22
+ key: "description",
23
+ label: t("common.description"),
24
+ type: "text",
25
+ },
26
+ {
27
+ key: "keywords",
28
+ label: t("common.keywords"),
29
+ type: "text",
30
+ },
31
+ {
32
+ key: "meta",
33
+ label: t("common.meta"),
34
+ type: "key-value",
35
+ ignore: ["title", "description", "keywords"],
36
+ },
37
+ ];
38
+ </script>
39
+
40
+ <style lang="scss" scoped></style>
@@ -0,0 +1,150 @@
1
+ <template>
2
+ <VsForm :schema="scheme" v-model="model" @update:model-value="handleUpdate" v-if="model" />
3
+
4
+ </template>
5
+
6
+ <script setup>
7
+ import { ref, provide, onMounted, watch, nextTick, computed } from "vue";
8
+ import { useI18n } from "vue-i18n";
9
+ const { t } = useI18n();
10
+ const model = defineModel({ default: () => [] });
11
+ const form = ref({});
12
+ const isSubmitting = ref(false);
13
+ import VsForm from "@opengis/form";
14
+ import { notify } from '@opengis/core';
15
+
16
+
17
+ provide("metaParentValue", model);
18
+
19
+ // Функція для перетворення тегів з об'єктів в ID
20
+ const normalizeTags = (tags) => {
21
+ if (!Array.isArray(tags)) return [];
22
+ return tags.map(tag => {
23
+ if (typeof tag === "object" && tag !== null && tag.id) {
24
+ return tag.id;
25
+ }
26
+ return tag;
27
+ });
28
+ };
29
+
30
+ // Функція для відновлення повних об'єктів тегів з ID
31
+ const restoreTagObjects = async (tagIds) => {
32
+ if (!Array.isArray(tagIds) || tagIds.length === 0) return [];
33
+
34
+ try {
35
+ const response = await fetch('/api/tags');
36
+ const data = await response.json();
37
+ const allTags = data?.rows || [];
38
+
39
+ return tagIds.map(id => {
40
+ const tag = allTags.find(t => t.tag_id === id);
41
+ return tag ? { id: tag.tag_id, text: tag.value, color: tag.color } : { id, text: '', color: '#ababab' };
42
+ });
43
+ } catch (error) {
44
+ console.error('Error fetching tags:', error);
45
+ return tagIds.map(id => ({ id, text: '', color: '#ababab' }));
46
+ }
47
+ };
48
+
49
+ onMounted(async () => {
50
+ // Чекаємо кілька тиків, щоб форма точно встигла ініціалізуватися
51
+ await nextTick();
52
+ await nextTick();
53
+
54
+ // Додаткова затримка для гарантії ініціалізації форми
55
+ setTimeout(() => {
56
+ if (Array.isArray(model.value?.tag_list)) {
57
+ const normalized = normalizeTags(model.value.tag_list);
58
+ if (JSON.stringify(normalized) !== JSON.stringify(model.value.tag_list)) {
59
+ model.value.tag_list = normalized;
60
+ }
61
+ }
62
+ }, 100);
63
+ });
64
+
65
+ // Відстежуємо зміни в tag_list і нормалізуємо їх
66
+ watch(() => model.value.tag_list, (newTags) => {
67
+ if (Array.isArray(newTags) && !isSubmitting.value) {
68
+ const normalized = normalizeTags(newTags);
69
+ if (JSON.stringify(normalized) !== JSON.stringify(newTags)) {
70
+ model.value.tag_list = normalized;
71
+ }
72
+ }
73
+ }, { deep: true });
74
+
75
+ // Додатковий watcher для відстеження змін в моделі
76
+ watch(() => model.value, (newModel) => {
77
+ if (newModel && Array.isArray(newModel.tag_list) && !isSubmitting.value) {
78
+ const normalized = normalizeTags(newModel.tag_list);
79
+ if (JSON.stringify(normalized) !== JSON.stringify(newModel.tag_list)) {
80
+ newModel.tag_list = normalized;
81
+ }
82
+ }
83
+ }, { deep: true, immediate: true });
84
+
85
+ // Watcher для примусового оновлення форми при зміні тегів
86
+ watch(() => model.value?.tag_list, (newTags) => {
87
+ if (Array.isArray(newTags) && form.value) {
88
+ // Примусово оновлюємо форму
89
+ nextTick(() => {
90
+ if (form.value && form.value.setValue) {
91
+ form.value.setValue('tag_list', newTags);
92
+ }
93
+ });
94
+ }
95
+ }, { deep: true });
96
+
97
+ const handleUpdate = async (value) => {
98
+ // Нормалізуємо теги перед валідацією
99
+ if (value.tag_list) {
100
+ value.tag_list = normalizeTags(value.tag_list);
101
+ model.value.tag_list = value.tag_list;
102
+ }
103
+
104
+ let result = await form.value.validate();
105
+ if (result) {
106
+ notify({
107
+ type: "warning",
108
+ title: t("common.actions.warning"),
109
+ message: t("builder.editFieldFailed"),
110
+ });
111
+ return;
112
+ }
113
+ };
114
+
115
+ // Функція для відновлення об'єктів тегів перед відправкою
116
+ const restoreTagsBeforeSubmit = async () => {
117
+ isSubmitting.value = true;
118
+ if (Array.isArray(model.value?.tag_list)) {
119
+ console.log('Before restore:', model.value.tag_list);
120
+ const restoredTags = await restoreTagObjects(model.value.tag_list);
121
+ console.log('After restore:', restoredTags);
122
+ model.value.tag_list = restoredTags;
123
+ }
124
+ // Даємо час для відправки, потім знову включаємо нормалізацію
125
+ setTimeout(() => {
126
+ isSubmitting.value = false;
127
+ }, 2000);
128
+ };
129
+
130
+ // Експортуємо функцію для використання в батьківському компоненті
131
+ defineExpose({
132
+ restoreTagsBeforeSubmit
133
+ });
134
+
135
+ // Використовуємо computed для схеми, щоб вона завжди мала актуальні значення
136
+ const scheme = computed(() => [
137
+ {
138
+ key: "tag_list",
139
+ type: "select",
140
+ multiple: true,
141
+ searchable: false,
142
+ placeholder: t("builder.tags"),
143
+ data: 'tag_id',
144
+ value: model.value?.tag_list || []
145
+ },
146
+ ]);
147
+ </script>
148
+
149
+ <style lang="scss" scoped></style>
150
+
@@ -0,0 +1,84 @@
1
+ <template>
2
+ <div
3
+ class="p-6 border-t border-slate-200 dark:border-slate-700 bg-slate-50/30 dark:bg-slate-700/30"
4
+ >
5
+ <button
6
+ class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 shadow-sm h-9 px-4 w-full border-2 border-dashed border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 hover:bg-slate-50 dark:hover:bg-slate-600 hover:border-blue-400 dark:hover:border-blue-500 text-slate-600 dark:text-slate-300 hover:text-blue-600 dark:hover:text-blue-400 py-3 transition-all duration-200"
7
+ @click="isOpen = true"
8
+ >
9
+ <Plus class="w-4 h-4 mr-2" />
10
+ {{ $t('common.actions.add') }}
11
+ </button>
12
+ <VsModal teleport="#modal" :title="$t('common.actions.add')" :visible="isOpen" @close="isOpen = false">
13
+
14
+ <VsForm v-model:form="form" :schema="colModel" v-model="formValues" />
15
+ <template #footer>
16
+ <div class="flex justify-end p-[20px] gap-[10px] border-t w-full">
17
+ <button
18
+ 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"
19
+ @click="isOpen = false"
20
+ >
21
+ {{ $t("common.actions.cancel") }}
22
+ </button>
23
+ <button
24
+ @click="createItem"
25
+ 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"
26
+ >
27
+ {{ $t("common.actions.save") }}
28
+ </button>
29
+ </div>
30
+ </template>
31
+ </VsModal>
32
+ </div>
33
+ </template>
34
+
35
+ <script setup>
36
+ import { Plus } from "lucide-vue-next";
37
+ import { ref } from "vue";
38
+ import VsForm from "@opengis/form";
39
+ import { notify } from '@opengis/core';
40
+ import { VsModal } from "@opengis/core";
41
+ import { useI18n } from "vue-i18n";
42
+ const { t } = useI18n();
43
+
44
+ defineProps({
45
+ colModel: {
46
+ type: Array,
47
+ required: true,
48
+ },
49
+ });
50
+
51
+ const data = defineModel({
52
+ type: Array,
53
+ required: true,
54
+ });
55
+
56
+ const isOpen = ref(false);
57
+ const formValues = ref({});
58
+ const form = ref({});
59
+
60
+ const createItem = async () => {
61
+ try {
62
+ let result = await form.value.validate();
63
+ if (result) {
64
+ notify({
65
+ type: "warning",
66
+ title: t("common.actions.warning"),
67
+ message: t("builder.editFieldFailed"),
68
+ });
69
+ return;
70
+ }
71
+
72
+ data.value = [
73
+ ...(data.value || []),
74
+ { ...formValues.value, id: Math.random().toString(36).substring(2, 15) },
75
+ ];
76
+ isOpen.value = false;
77
+ formValues.value = {};
78
+ } catch (error) {
79
+ console.log(error);
80
+ }
81
+ };
82
+ </script>
83
+
84
+ <style scoped></style>
@@ -0,0 +1,106 @@
1
+ <template>
2
+ <div class="flex items-center justify-end gap-2">
3
+ <button
4
+ class="inline-flex items-center justify-center whitespace-nowrap font-medium focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border rounded-md text-xs h-8 w-8 p-0 border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 hover:bg-blue-50 dark:hover:bg-slate-600 hover:border-blue-300 dark:hover:border-slate-500 hover:text-blue-600 dark:hover:text-slate-200 transition-all duration-200 shadow-sm"
5
+ @click="isOpen = true"
6
+ >
7
+ <Pencil class="w-4 h-4" />
8
+ </button>
9
+ <button
10
+ @click="openConfirm"
11
+ class="inline-flex items-center justify-center whitespace-nowrap font-medium focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border rounded-md text-xs h-8 w-8 p-0 border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 hover:bg-red-50 dark:hover:bg-slate-600 hover:border-red-300 dark:hover:border-slate-500 hover:text-red-600 dark:hover:text-slate-200 transition-all duration-200 shadow-sm"
12
+ >
13
+ <Trash2 class="w-4 h-4" />
14
+ </button>
15
+ <VsModal teleport="#modal" :title="$t('builder.editField')" :visible="isOpen" @close="isOpen = false">
16
+ <VsForm v-model:form="form" :schema="colModel" v-model="formValues" />
17
+ <template #footer>
18
+ <div class="flex justify-end p-[20px] gap-[10px] border-t w-full">
19
+ <button
20
+ 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"
21
+ @click="isOpen = false"
22
+ >
23
+ {{ $t("common.actions.cancel") }}
24
+ </button>
25
+ <button
26
+ @click="updateItem"
27
+ 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"
28
+ >
29
+ {{ $t("common.actions.save") }}
30
+ </button>
31
+ </div>
32
+ </template>
33
+ </VsModal>
34
+ </div>
35
+ </template>
36
+
37
+ <script setup>
38
+ import { Pencil, Trash2 } from "lucide-vue-next";
39
+ import { ref, watch } from "vue";
40
+ import { confirm, notify } from '@opengis/core';
41
+ import { useI18n } from "vue-i18n";
42
+ import VsForm from "@opengis/form";
43
+ import { VsModal } from "@opengis/core";
44
+
45
+ //import VsForm from '../../../../../form/src/index'
46
+
47
+
48
+ const { t } = useI18n();
49
+
50
+ const props = defineProps({
51
+ item: {
52
+ type: Object,
53
+ required: true,
54
+ },
55
+ colModel: {
56
+ type: Array,
57
+ required: true,
58
+ },
59
+ });
60
+
61
+ const data = defineModel({
62
+ type: Array,
63
+ required: true,
64
+ });
65
+
66
+ const isOpen = ref(false);
67
+ const formValues = ref({});
68
+ const form = ref({});
69
+
70
+ const openConfirm = () => {
71
+ confirm({
72
+ title: t("builder.deleteTitle"),
73
+ message: t("builder.deleteField"),
74
+ type: 'error',
75
+ onConfirm: () => {
76
+ deleteItem();
77
+ },
78
+ })
79
+ };
80
+
81
+ const deleteItem = () => {
82
+ data.value = data.value.filter((item) => item.id !== props.item.id);
83
+ };
84
+
85
+ const updateItem = async () => {
86
+ let result = await form.value.validate();
87
+ if (result) {
88
+ notify({
89
+ type: "warning",
90
+ title: t("common.actions.warning"),
91
+ message: t("builder.editFieldFailed"),
92
+ });
93
+ return;
94
+ }
95
+ data.value = data.value.map((item) =>
96
+ item.id === props.item.id ? formValues.value : item
97
+ );
98
+ isOpen.value = false;
99
+ };
100
+
101
+ watch(isOpen, (newVal) => {
102
+ if (newVal) {
103
+ formValues.value = props.item;
104
+ }
105
+ });
106
+ </script>
@@ -0,0 +1,23 @@
1
+ import VsFormSlug from "./vs-form-slug.vue";
2
+ import VsFormMonacoEditor from "./MonacoEditor.vue";
3
+ import VsFormReference from "./vs-form-reference-list.vue";
4
+ import VsFormRelation from "./vs-form-relation.vue";
5
+ import VsFormInteger from "./vs-form-integer.vue";
6
+ import VsFormCustomDatatable from "./vs-form-custom-datatable.vue";
7
+ import VsFormRichtextMd from "./vs-form-marcdown-md.vue";
8
+ import VsFormKeyValue from "./vs-form-key-value.vue";
9
+ import VsFormRelationLink from "./vs-form-reletion-link.vue";
10
+ import VsFormMediaselect from "./vs-form-media-select.vue";
11
+
12
+ export default {
13
+ VsFormSlug,
14
+ VsFormMonacoEditor,
15
+ VsFormReference,
16
+ VsFormRelation,
17
+ VsFormInteger,
18
+ VsFormCustomDatatable,
19
+ VsFormRichtextMd,
20
+ VsFormKeyValue,
21
+ VsFormRelationLink,
22
+ VsFormMediaselect,
23
+ };
@@ -0,0 +1,92 @@
1
+ <template>
2
+ <div>
3
+ <button
4
+ type="button"
5
+ class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sky-500"
6
+ @click="isOpen = !isOpen"
7
+ >
8
+ {{ $t("builder.create") }}
9
+ </button>
10
+ <VsDrawer v-model:visible="isOpen" @close="isOpen = false" :title="title" :size="'50vw'">
11
+ <VsForm
12
+ v-model:form="form"
13
+ v-if="schema"
14
+ :schema="schema"
15
+ v-model="formValue"
16
+ class="-mr-[10px] ml-[10px] mt-[10px]"
17
+ />
18
+ <template #footer>
19
+ <div
20
+ class="flex items-center justify-between w-full p-4 border-t border-gray-200 dark:border-gray-700"
21
+ >
22
+ <button
23
+ class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600"
24
+ >
25
+ {{ $t("builder.cancel") }}
26
+ </button>
27
+ <button
28
+ @click="handleCreate"
29
+ class="px-4 py-2 bg-sky-600 text-white rounded-md text-sm font-medium hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sky-500"
30
+ >
31
+ {{ $t("builder.create") }}
32
+ </button>
33
+ </div>
34
+ </template>
35
+ </VsDrawer>
36
+ </div>
37
+ </template>
38
+
39
+ <script setup>
40
+ import { ref, watch } from "vue";
41
+ import VsForm from "@opengis/form";
42
+ import { VsDrawer } from "@opengis/core";
43
+ import { notify } from '@opengis/core';
44
+ import { useI18n } from "vue-i18n";
45
+
46
+ const { t } = useI18n();
47
+
48
+ const props = defineProps({
49
+ title: {
50
+ type: String,
51
+ default: "",
52
+ },
53
+
54
+ schema: {
55
+ type: Object,
56
+ default: () => ({}),
57
+ },
58
+ });
59
+
60
+ const model = defineModel({
61
+ type: Object,
62
+ default: () => ({}),
63
+ });
64
+
65
+ const isOpen = ref(false);
66
+ const form = ref({});
67
+ const formValue = ref({});
68
+
69
+ const handleCreate = async () => {
70
+ try {
71
+ let result = await form.value.validate();
72
+ if (result) {
73
+ notify({
74
+ type: "warning",
75
+ title: t("common.actions.warning"),
76
+ message: t("builder.editFieldFailed"),
77
+ });
78
+ return;
79
+ }
80
+ model.value = { ...formValue.value };
81
+ isOpen.value = false;
82
+ } catch (error) {
83
+ console.log(error);
84
+ }
85
+ };
86
+
87
+ watch(isOpen, (newVal) => {
88
+ if (newVal) {
89
+ formValue.value = { ...model.value };
90
+ }
91
+ });
92
+ </script>