inicontent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/.github/pull_request.yml +18 -0
  2. package/CONTRIBUTING.md +67 -0
  3. package/LICENSE +21 -0
  4. package/README.md +142 -0
  5. package/app/app.vue +106 -0
  6. package/app/assets/main.css +974 -0
  7. package/app/components/Api/EndpointCard.vue +169 -0
  8. package/app/components/Api/SchemaDocsTable.vue +208 -0
  9. package/app/components/Asset/Card.vue +1363 -0
  10. package/app/components/Asset/Grid.vue +295 -0
  11. package/app/components/Asset/Icon.vue +38 -0
  12. package/app/components/Asset/Preview.vue +486 -0
  13. package/app/components/Asset/Scanner.vue +700 -0
  14. package/app/components/Asset/Thumb.vue +708 -0
  15. package/app/components/Auth/Reset.vue +99 -0
  16. package/app/components/Auth/index.vue +346 -0
  17. package/app/components/ChatInterface.vue +2326 -0
  18. package/app/components/ChatPagePreview.vue +34 -0
  19. package/app/components/Column/Array.vue +29 -0
  20. package/app/components/Column/Asset.vue +21 -0
  21. package/app/components/Column/Boolean.vue +12 -0
  22. package/app/components/Column/Color.vue +9 -0
  23. package/app/components/Column/Date.vue +57 -0
  24. package/app/components/Column/Edit.vue +215 -0
  25. package/app/components/Column/Email.vue +9 -0
  26. package/app/components/Column/Html.vue +27 -0
  27. package/app/components/Column/Id.vue +20 -0
  28. package/app/components/Column/Locale.vue +32 -0
  29. package/app/components/Column/Object.vue +18 -0
  30. package/app/components/Column/Password.vue +7 -0
  31. package/app/components/Column/Role.vue +15 -0
  32. package/app/components/Column/S.vue +14 -0
  33. package/app/components/Column/Table/Single.vue +49 -0
  34. package/app/components/Column/Table/index.vue +42 -0
  35. package/app/components/Column/Tags.vue +31 -0
  36. package/app/components/Column/Text.vue +9 -0
  37. package/app/components/Column/Url.vue +9 -0
  38. package/app/components/Column/index.vue +41 -0
  39. package/app/components/Dashboard/Editor.vue +221 -0
  40. package/app/components/Dashboard/Grid.vue +270 -0
  41. package/app/components/Dashboard/View.vue +131 -0
  42. package/app/components/Dashboard/Widget/BarChart.vue +73 -0
  43. package/app/components/Dashboard/Widget/Counter.vue +51 -0
  44. package/app/components/Dashboard/Widget/LineChart.vue +76 -0
  45. package/app/components/Dashboard/Widget/PieChart.vue +75 -0
  46. package/app/components/Dashboard/Widget/Table.vue +94 -0
  47. package/app/components/Dashboard/WidgetEditor.vue +274 -0
  48. package/app/components/Data/Array.vue +102 -0
  49. package/app/components/Data/Html.vue +9 -0
  50. package/app/components/Data/Icon.vue +9 -0
  51. package/app/components/Data/Object.vue +10 -0
  52. package/app/components/Data/S.vue +16 -0
  53. package/app/components/Data/Table.vue +53 -0
  54. package/app/components/Data/Value.vue +34 -0
  55. package/app/components/Data/index.vue +14 -0
  56. package/app/components/Field/Array.vue +324 -0
  57. package/app/components/Field/Asset/Actions.vue +97 -0
  58. package/app/components/Field/Asset/index.vue +639 -0
  59. package/app/components/Field/Boolean.vue +26 -0
  60. package/app/components/Field/Checkbox.vue +48 -0
  61. package/app/components/Field/Color.vue +27 -0
  62. package/app/components/Field/Date.vue +30 -0
  63. package/app/components/Field/Email.vue +51 -0
  64. package/app/components/Field/EmailTemplate.vue +420 -0
  65. package/app/components/Field/Html/ColorPicker.vue +127 -0
  66. package/app/components/Field/Html/Editor.vue +544 -0
  67. package/app/components/Field/Html/index.vue +25 -0
  68. package/app/components/Field/Icon.vue +114 -0
  69. package/app/components/Field/JSON.vue +113 -0
  70. package/app/components/Field/Mention.vue +36 -0
  71. package/app/components/Field/Number.vue +27 -0
  72. package/app/components/Field/Object.vue +92 -0
  73. package/app/components/Field/Password.vue +55 -0
  74. package/app/components/Field/Radio.vue +47 -0
  75. package/app/components/Field/S.vue +44 -0
  76. package/app/components/Field/Select.vue +49 -0
  77. package/app/components/Field/Table.vue +348 -0
  78. package/app/components/Field/Tags.vue +113 -0
  79. package/app/components/Field/Text.vue +31 -0
  80. package/app/components/Field/Textarea.vue +33 -0
  81. package/app/components/Field/Url.vue +40 -0
  82. package/app/components/Field/Wrapper.vue +128 -0
  83. package/app/components/Field/index.vue +68 -0
  84. package/app/components/FloatingChatbot.vue +73 -0
  85. package/app/components/Form/Card.vue +151 -0
  86. package/app/components/Form/Drawer.vue +195 -0
  87. package/app/components/Form/index.vue +621 -0
  88. package/app/components/Header.vue +328 -0
  89. package/app/components/Offline/ConflictModal.vue +200 -0
  90. package/app/components/Offline/SyncStatus.vue +249 -0
  91. package/app/components/PlatformFeatureLanding.vue +65 -0
  92. package/app/components/Table/Backups.vue +638 -0
  93. package/app/components/Table/Flows.vue +814 -0
  94. package/app/components/Table/Grid.vue +296 -0
  95. package/app/components/Table/Icon.vue +36 -0
  96. package/app/components/Table/Logs.vue +181 -0
  97. package/app/components/Table/Schedules.vue +722 -0
  98. package/app/components/Table/Search/Button.vue +256 -0
  99. package/app/components/Table/Search/Items.vue +286 -0
  100. package/app/components/Table/Search/index.vue +87 -0
  101. package/app/components/Table/Settings/Schema.vue +801 -0
  102. package/app/components/Table/Settings/index.vue +631 -0
  103. package/app/components/Table/TranslateDrawer.vue +526 -0
  104. package/app/components/Table/Views/Kanban.vue +289 -0
  105. package/app/components/Table/Views/Table.vue +1273 -0
  106. package/app/components/Table/index.vue +890 -0
  107. package/app/composables/Translation/define.ts +41 -0
  108. package/app/composables/Translation/fetch.ts +107 -0
  109. package/app/composables/Translation/languages.ts +1 -0
  110. package/app/composables/Translation/loadCoreTranslations.ts +38 -0
  111. package/app/composables/Translation/translate.ts +250 -0
  112. package/app/composables/databaseCookies.ts +79 -0
  113. package/app/composables/fieldValidator.ts +72 -0
  114. package/app/composables/fieldsList.ts +301 -0
  115. package/app/composables/formatDatabase.ts +101 -0
  116. package/app/composables/index.ts +444 -0
  117. package/app/composables/openDrawer.ts +118 -0
  118. package/app/composables/optimizeFile.ts +287 -0
  119. package/app/composables/renderLabel.ts +88 -0
  120. package/app/composables/schemaClipboard.ts +95 -0
  121. package/app/composables/search.ts +353 -0
  122. package/app/composables/setThemeConfig.ts +48 -0
  123. package/app/composables/translationValue.ts +169 -0
  124. package/app/composables/useAssetPreview.ts +84 -0
  125. package/app/composables/useAssetUploader.ts +56 -0
  126. package/app/composables/useDashboardData.ts +310 -0
  127. package/app/composables/useDocumentThumbnail.ts +351 -0
  128. package/app/composables/useEcharts.ts +33 -0
  129. package/app/composables/useLocalDatabaseConfig.ts +51 -0
  130. package/app/composables/useOcr.ts +300 -0
  131. package/app/composables/useOfflineFetch.ts +194 -0
  132. package/app/composables/useOfflineItem.ts +107 -0
  133. package/app/composables/useOfflineQueue.ts +302 -0
  134. package/app/composables/useOfflineSync.ts +235 -0
  135. package/app/composables/usePasskeyAuth.ts +156 -0
  136. package/app/composables/usePdfCompressor.ts +180 -0
  137. package/app/composables/usePlatformRedirect.ts +72 -0
  138. package/app/composables/useRealtimeSync.ts +233 -0
  139. package/app/composables/useReferencedItems.ts +180 -0
  140. package/app/composables/useScanner.ts +664 -0
  141. package/app/composables/useSubscription.ts +650 -0
  142. package/app/composables/useThumbnailCache.ts +136 -0
  143. package/app/composables/useVideoCompressor.ts +194 -0
  144. package/app/error.vue +112 -0
  145. package/app/layouts/api.vue +188 -0
  146. package/app/layouts/dashboard.vue +91 -0
  147. package/app/layouts/default.vue +228 -0
  148. package/app/layouts/table.vue +471 -0
  149. package/app/locales/ar.ts +996 -0
  150. package/app/locales/en.ts +655 -0
  151. package/app/locales/es.ts +996 -0
  152. package/app/locales/fr.ts +997 -0
  153. package/app/middleware/dashboard.ts +33 -0
  154. package/app/middleware/database.ts +83 -0
  155. package/app/middleware/global.ts +1 -0
  156. package/app/middleware/sid-ingest.global.ts +29 -0
  157. package/app/middleware/table.ts +54 -0
  158. package/app/middleware/user.ts +26 -0
  159. package/app/pages/[[database]]/admin/api/auth/index.vue +306 -0
  160. package/app/pages/[[database]]/admin/api/index.vue +18 -0
  161. package/app/pages/[[database]]/admin/api/tables/[table]/index.vue +497 -0
  162. package/app/pages/[[database]]/admin/api/tables/index.vue +87 -0
  163. package/app/pages/[[database]]/admin/billing/index.vue +16 -0
  164. package/app/pages/[[database]]/admin/dashboards/[id]/edit.vue +66 -0
  165. package/app/pages/[[database]]/admin/dashboards/[id]/index.vue +117 -0
  166. package/app/pages/[[database]]/admin/dashboards/index.vue +20 -0
  167. package/app/pages/[[database]]/admin/index.vue +28 -0
  168. package/app/pages/[[database]]/admin/settings.vue +526 -0
  169. package/app/pages/[[database]]/admin/tables/[table]/[id]/edit.vue +143 -0
  170. package/app/pages/[[database]]/admin/tables/[table]/[id]/index.vue +146 -0
  171. package/app/pages/[[database]]/admin/tables/[table]/flows.vue +10 -0
  172. package/app/pages/[[database]]/admin/tables/[table]/index.vue +10 -0
  173. package/app/pages/[[database]]/admin/tables/[table]/new.vue +12 -0
  174. package/app/pages/[[database]]/admin/tables/[table]/schedules.vue +10 -0
  175. package/app/pages/[[database]]/admin/tables/[table]/settings.vue +30 -0
  176. package/app/pages/[[database]]/admin/tables/assets/[...path].vue +24 -0
  177. package/app/pages/[[database]]/admin/tables/assets/flows.vue +10 -0
  178. package/app/pages/[[database]]/admin/tables/backups/index.vue +10 -0
  179. package/app/pages/[[database]]/admin/tables/blocks/index.vue +16 -0
  180. package/app/pages/[[database]]/admin/tables/index.vue +20 -0
  181. package/app/pages/[[database]]/admin/tables/pages/index.vue +16 -0
  182. package/app/pages/[[database]]/admin/tables/templates/index.vue +16 -0
  183. package/app/pages/[[database]]/api/index.vue +120 -0
  184. package/app/pages/[[database]]/auth/index.vue +12 -0
  185. package/app/pages/[[database]]/auth/reset.vue +10 -0
  186. package/app/pages/[[database]]/index.vue +10 -0
  187. package/app/plugins/offline-warmup.client.ts +53 -0
  188. package/app/plugins/template-globals.ts +9 -0
  189. package/app/spa-loading-template.html +93 -0
  190. package/biome.json +53 -0
  191. package/index.d.ts +380 -0
  192. package/modules/naiveui.ts +41 -0
  193. package/nuxt.config.ts +269 -0
  194. package/package.json +56 -0
  195. package/pnpm-workspace.yaml +5 -0
  196. package/public/apple-touch-icon.png +0 -0
  197. package/public/favicon-16x16.png +0 -0
  198. package/public/favicon-32x32.png +0 -0
  199. package/public/favicon.ico +0 -0
  200. package/public/pwa-192x192.png +0 -0
  201. package/public/pwa-512x512.png +0 -0
  202. package/public/pwa-maskable-192x192.png +0 -0
  203. package/public/pwa-maskable-512x512.png +0 -0
  204. package/public/vendor/opencv.js +48 -0
  205. package/tsconfig.json +9 -0
@@ -0,0 +1,631 @@
1
+ <template>
2
+ <NGrid x-gap="12" cols="12" layout-shift-disabled>
3
+ <NGridItem :span="!$device.isMobile ? 10 : 12">
4
+ <NCard
5
+ :title="t('tableSettings')" style="background-color: transparent;"
6
+ :header-style="{ paddingRight: 0, paddingLeft: 0 }" content-style="padding: 0" :bordered="false" embedded>
7
+ <template #header-extra>
8
+ <NButtonGroup>
9
+ <NTooltip :delay="1500">
10
+ <template #trigger>
11
+ <NPopconfirm :show-icon="false" @positive-click="deleteTable">
12
+ <template #trigger>
13
+ <NButton :disabled="isUnDeletable" type="error" secondary round
14
+ :loading="Loading.deleteTable">
15
+ <template #icon>
16
+ <NIcon>
17
+ <Icon name="tabler:trash" />
18
+ </NIcon>
19
+ </template>
20
+ </NButton>
21
+ </template>
22
+ {{ t("theFollowingActionIsIrreversible") }}
23
+ </NPopconfirm>
24
+ </template>
25
+ {{ t("deleteTable") }}
26
+ </NTooltip>
27
+ <NButton round type="primary" secondary :loading="Loading.updateTable" @click="updateTable">
28
+ <template #icon>
29
+ <NIcon>
30
+ <Icon name="tabler:device-floppy" />
31
+ </NIcon>
32
+ </template>
33
+ <template v-if="!$device.isMobile" #default>
34
+ {{ t('save') }}
35
+ </template>
36
+ </NButton>
37
+ </NButtonGroup>
38
+ </template>
39
+ <NFlex vertical :size="24">
40
+ <NSpin :show="!!Loading.updateTable">
41
+ <NCard :title="t('generalSettings')" id="generalSettings" hoverable>
42
+ <NForm ref="settingsFormRef" :model="tableCopy">
43
+ <FieldS v-model="tableCopy" :schema="generalSettingsSchema.slice(0, 3)" />
44
+ <NFormItem path="defaultSearchableColumns" :label="t('defaultSearchableColumns')">
45
+ <NCascader multiple clearable filterable expand-trigger="hover" check-strategy="child"
46
+ :cascard="false" v-model:value="tableCopy.defaultSearchableColumns"
47
+ :options="searchInSelectOptions" />
48
+ </NFormItem>
49
+ <template v-if="tableCopy.displayAs === 'table'">
50
+ <NFormItem path="defaultTableColumns" :label="t('defaultTableColumns')">
51
+ <NCascader multiple clearable filterable expand-trigger="hover"
52
+ check-strategy="child" :cascard="false"
53
+ v-model:value="tableCopy.defaultTableColumns"
54
+ :options="searchInSelectOptions" />
55
+ </NFormItem>
56
+ <NFormItem path="localLabel" :label="t('tableItemLabel')">
57
+ <NDynamicTags v-model:value="tableCopy.localLabel" :onCreate="onAppendToLabel"
58
+ :render-tag="renderSingleLabel">
59
+ <template #input="{ submit, deactivate }">
60
+ <NSelect ref="singleLabelSelect" size="small" clearable filterable show tag
61
+ :options="searchInSelectOptions" @update:value="submit($event)"
62
+ @update:show="(value) => value === undefined ? '' : deactivate()" />
63
+ </template>
64
+ <template #trigger="{ activate, disabled }">
65
+ <NButton size="small" tertiary round
66
+ @click="activate(), focusSingleLabelSelect()" :disabled>
67
+ <template #icon>
68
+ <NIcon>
69
+ <Icon name="tabler:plus" />
70
+ </NIcon>
71
+ </template>
72
+ </NButton>
73
+ </template>
74
+ </NDynamicTags>
75
+ </NFormItem>
76
+ </template>
77
+ <template v-else>
78
+ <NFormItem path="groupBy" :label="t('groupBy')">
79
+ <NCascader clearable filterable expand-trigger="hover" check-strategy="child"
80
+ :cascard="false" v-model:value="tableCopy.groupBy"
81
+ :options="groupBySelectOptions" />
82
+ </NFormItem>
83
+ <FieldMention :field="labelField" v-model="tableCopy.label" />
84
+ </template>
85
+ <FieldS v-model="tableCopy.config" :schema="generalSettingsSchema.slice(3)" />
86
+ </NForm>
87
+ </NCard>
88
+ </NSpin>
89
+ <NSpin :show="!!Loading.updateTable">
90
+ <NCard :title="t('schemaSettings')" id="schemaSettings" hoverable>
91
+ <template #header-extra>
92
+ <NButtonGroup>
93
+ <NTooltip :delay="1500">
94
+ <template #trigger>
95
+ <NButton :type="reorderEnabled ? 'success' : 'default'" secondary round
96
+ @click="reorderEnabled = !reorderEnabled">
97
+ <template #icon>
98
+ <NIcon>
99
+ <Icon name="tabler:arrows-move" />
100
+ </NIcon>
101
+ </template>
102
+ </NButton>
103
+ </template>
104
+ {{ t('reorderFields') }}
105
+ </NTooltip>
106
+ <NDropdown :options="addFieldDropdownOptions" style="max-height: 200px" scrollable
107
+ @select="onAddFieldSelect">
108
+ <NButton secondary type="primary" round @click="pushToSchema('string')">
109
+ <template #icon>
110
+ <NIcon>
111
+ <Icon name="tabler:plus" />
112
+ </NIcon>
113
+ </template>
114
+ </NButton>
115
+ </NDropdown>
116
+ </NButtonGroup>
117
+ </template>
118
+ <NForm size="small">
119
+ <LazyTableSettingsSchema v-model="tableCopy.schema"
120
+ v-model:expanded-names="expandedNames" :reorder-enabled="reorderEnabled" />
121
+ </NForm>
122
+ </NCard>
123
+ </NSpin>
124
+ </NFlex>
125
+ </NCard>
126
+ </NGridItem>
127
+ <NGridItem v-if="!$device.isMobile" span="2">
128
+ <NAnchor affix listen-to="#container" :top="88" style="z-index: 1;" :bound="90">
129
+ <NAnchorLink :title="t('generalSettings')" href="#generalSettings" />
130
+ <NAnchorLink :title="t('schemaSettings')" href="#schemaSettings" />
131
+ </NAnchor>
132
+ </NGridItem>
133
+ </NGrid>
134
+ </template>
135
+
136
+ <script lang="ts" setup>
137
+ import { flattenSchema, isArrayOfObjects, isNumber } from "inibase/utils";
138
+ import type { FormInst } from "naive-ui";
139
+ import { Icon, NIcon, NTag } from "#components";
140
+
141
+ onMounted(() => {
142
+ document.onkeydown = (e) => {
143
+ if (
144
+ !(
145
+ (e.ctrlKey || e.metaKey) &&
146
+ (e.key.toLowerCase() === "s" || e.key === "س")
147
+ )
148
+ )
149
+ return;
150
+ e.preventDefault();
151
+ updateTable();
152
+ };
153
+ });
154
+
155
+ const expandedNames = ref<string[]>();
156
+ const reorderEnabled = ref(false);
157
+ function pushToSchema(type: string) {
158
+ const newField = {
159
+ id: `temp-${randomID()}`,
160
+ key: "",
161
+ required: false,
162
+ ...(handleSelectedSchemaType(type) as any),
163
+ };
164
+ if (tableCopy.value.schema?.[0]?.key === "id")
165
+ tableCopy.value.schema?.splice(-2, 0, newField);
166
+ else {
167
+ if (!tableCopy.value.schema) tableCopy.value.schema = [];
168
+ tableCopy.value.schema.push(newField);
169
+ }
170
+ expandedNames.value = [newField.id];
171
+ setTimeout(
172
+ () => document.getElementById(`element-${newField.id}`)?.scrollIntoView(),
173
+ 300,
174
+ );
175
+ }
176
+
177
+ // Show a "Paste from clipboard" entry on top of the add-field dropdown. The
178
+ // clipboard is the system clipboard, so it may contain fields copied from
179
+ // another table/browser tab.
180
+ const addFieldDropdownOptions = computed(() => [
181
+ {
182
+ label: () => t("pasteFields"),
183
+ key: "paste-from-clipboard",
184
+ icon: () => h(NIcon, () => h(Icon, { name: "tabler:clipboard" })),
185
+ },
186
+ { type: "divider", key: "divider-paste" },
187
+ ...fieldsList(),
188
+ ]);
189
+
190
+ function onAddFieldSelect(key: string) {
191
+ if (key === "paste-from-clipboard") {
192
+ void pasteFieldsFromClipboard();
193
+ return;
194
+ }
195
+ pushToSchema(key);
196
+ }
197
+
198
+ async function pasteFieldsFromClipboard() {
199
+ if (!tableCopy.value.schema) tableCopy.value.schema = [];
200
+ const schema = tableCopy.value.schema;
201
+ // Append before the trailing system fields, matching pushToSchema behavior.
202
+ const index =
203
+ schema[0]?.key === "id" ? Math.max(0, schema.length - 2) : schema.length;
204
+ const inserted = await pasteSchemaFields(schema, index);
205
+ if (!inserted.length) {
206
+ window.$message.warning(t("noFieldsInClipboard"));
207
+ return;
208
+ }
209
+ expandedNames.value = [inserted[0].id as string];
210
+ setTimeout(
211
+ () => document.getElementById(`element-${inserted[0].id}`)?.scrollIntoView(),
212
+ 300,
213
+ );
214
+ }
215
+
216
+ const config = useRuntimeConfig();
217
+ const Loading = useState<Record<string, boolean>>("Loading", () => ({}));
218
+ const route = useRoute();
219
+ const router = useRouter();
220
+ const database = useState<Database>("database");
221
+ const table = useState<Table>("table");
222
+ const settingsFormRef = ref<FormInst>();
223
+ const tableCopy = ref<
224
+ Table & {
225
+ localLabel?: { value: string; label: string }[];
226
+ }
227
+ >({
228
+ ...toRaw(table.value),
229
+ displayAs: table.value.displayAs || "table",
230
+ schema: table.value.schema || [],
231
+ });
232
+ const Language = useScopedCookie<LanguagesType>("language", database.value?.slug);
233
+ function sanitizeSchema(schema: Schema): Schema {
234
+ return schema
235
+ .filter((field) => field.type !== "custom") // Remove custom fields
236
+ .map((field) => {
237
+ // Remove temp IDs
238
+ if (field.id && String(field.id).startsWith("temp-")) delete field.id;
239
+
240
+ // Remove function/VNode properties
241
+ const {
242
+ render,
243
+ itemExtraActions,
244
+ itemExtraButtons,
245
+ extraActions,
246
+ extraButtons,
247
+ onDelete,
248
+ onCreate,
249
+ ...cleanField
250
+ } = field;
251
+
252
+ // Recursively clean children
253
+ if (isArrayOfObjects(cleanField.children)) {
254
+ return {
255
+ ...cleanField,
256
+ children: sanitizeSchema(cleanField.children),
257
+ };
258
+ }
259
+ return cleanField;
260
+ });
261
+ }
262
+
263
+ const sessionID = useScopedCookie<string>("sid", database.value?.slug);
264
+
265
+ async function updateTable() {
266
+ settingsFormRef.value?.validate(async (errors) => {
267
+ if (!errors) {
268
+ const bodyContent = (({ onRequest, onResponse, ...rest }) => ({
269
+ ...rest,
270
+ }))(tableCopy.value);
271
+ Loading.value.updateTable = true;
272
+
273
+ if (bodyContent.displayAs === "table") bodyContent.displayAs = "" as any;
274
+
275
+ if (bodyContent.localLabel)
276
+ bodyContent.label = bodyContent.localLabel
277
+ .map(({ value }: { value: string }) => value)
278
+ .join(" ");
279
+
280
+ if (bodyContent.schema)
281
+ bodyContent.schema = sanitizeSchema(bodyContent.schema);
282
+
283
+ const data = await $fetch<
284
+ apiResponse<Table & { localLabel?: { value: string; label: string }[] }>
285
+ >(
286
+ `${config.public.apiBase}inicontent/databases/${database.value.slug
287
+ }/${route.params.table ?? table.value.slug}`,
288
+ {
289
+ method: "PUT",
290
+ body: bodyContent,
291
+ params: {
292
+ locale: Language.value,
293
+ [`${database.value.slug}_sid`]: sessionID.value,
294
+ },
295
+ credentials: "include",
296
+ },
297
+ );
298
+ const tableIndex = database.value.tables?.findIndex(
299
+ ({ slug }) => slug === (route.params.table ?? table.value.slug),
300
+ );
301
+ if (
302
+ tableIndex !== undefined &&
303
+ tableIndex !== -1 &&
304
+ database.value.tables &&
305
+ data?.result
306
+ ) {
307
+ data.result.displayAs = data.result.displayAs || "table";
308
+ database.value.tables[tableIndex] = data.result;
309
+ table.value = data.result;
310
+ tableCopy.value = data.result;
311
+
312
+ if ((route.params.table ?? table.value.slug) !== data.result.slug)
313
+ router.replace({
314
+ params: { table: data.result.slug },
315
+ });
316
+ window.$message.success(data?.message ?? t("success"));
317
+ } else window.$message.error(data?.message ?? t("error"));
318
+
319
+ Loading.value.updateTable = false;
320
+ } else window.$message.error(t("inputsAreInvalid"));
321
+ });
322
+ }
323
+
324
+ const isUnDeletable = computed(() =>
325
+ ["users", "pages", "blocks", "templates"].includes(table.value?.slug),
326
+ );
327
+
328
+ async function deleteTable() {
329
+ Loading.value.deleteTable = true;
330
+ const data = await $fetch<apiResponse>(
331
+ `${config.public.apiBase}inicontent/databases/${database.value.slug
332
+ }/${route.params.table ?? table.value.slug}`,
333
+ {
334
+ method: "DELETE",
335
+ params: {
336
+ locale: Language.value,
337
+ [`${database.value.slug}_sid`]: sessionID.value,
338
+ },
339
+ credentials: "include",
340
+ },
341
+ );
342
+ if (data?.result) {
343
+ const tableIndex = database.value.tables?.findIndex(
344
+ ({ slug }) => slug === (route.params.table ?? table.value.slug),
345
+ );
346
+ if (tableIndex !== undefined && tableIndex !== -1)
347
+ database.value.tables = database.value.tables?.toSpliced(tableIndex, 1);
348
+ Loading.value.deleteTable = false;
349
+ window.$message.success(data?.message ?? t("success"));
350
+ setTimeout(
351
+ async () =>
352
+ await navigateTo(
353
+ `${route.params.database ? `/${database.value.slug}` : ""}/admin/tables`,
354
+ ),
355
+ 800,
356
+ );
357
+ } else window.$message.error(data?.message ?? t("error"));
358
+ Loading.value.deleteTable = false;
359
+ }
360
+
361
+ const flattenCopySchema = computed<Schema>(() =>
362
+ tableCopy.value.schema ? flattenSchema(tableCopy.value.schema as any) : [],
363
+ );
364
+ const singleLabelSelect = ref();
365
+ function focusSingleLabelSelect() {
366
+ setTimeout(() => singleLabelSelect.value?.focusInput(), 200);
367
+ }
368
+ watch(
369
+ () => tableCopy.value.displayAs,
370
+ (view) => {
371
+ tableCopy.value.localLabel =
372
+ view !== "kanban"
373
+ ? tableCopy.value.label
374
+ ?.split(/(@\w+)/g)
375
+ .filter((value: string) => value.trim() !== "")
376
+ .map((label: string) => {
377
+ if (label.startsWith("@"))
378
+ return {
379
+ label:
380
+ flattenCopySchema.value.find(
381
+ ({ id }) => String(id) === label.slice(1),
382
+ )?.key ?? "",
383
+ value: label,
384
+ };
385
+ return {
386
+ label,
387
+ value: label,
388
+ };
389
+ })
390
+ : undefined;
391
+ },
392
+ { immediate: true },
393
+ );
394
+ watch(
395
+ () => tableCopy.value?.localLabel,
396
+ (newValue) => {
397
+ if (newValue?.length) {
398
+ const localLabelWithNoEmptys = newValue.filter(
399
+ ({ label }) => label.trim() !== "",
400
+ );
401
+ if (localLabelWithNoEmptys.length !== newValue.length)
402
+ tableCopy.value.localLabel = localLabelWithNoEmptys;
403
+ }
404
+ },
405
+ );
406
+ function onAppendToLabel(label: string) {
407
+ if (isNumber(label) && !String(label).startsWith("@")) label = `@${label}`;
408
+ if (isNumber(label.slice(1)) && String(label).startsWith("@"))
409
+ return {
410
+ label:
411
+ flattenCopySchema.value.find(({ id }) => String(id) === label.slice(1))
412
+ ?.key ?? "undefined",
413
+ value: label,
414
+ };
415
+
416
+ return {
417
+ label: label,
418
+ value: label,
419
+ };
420
+ }
421
+ function renderSingleLabel(
422
+ labelObject: { label: string; value: string },
423
+ index: number,
424
+ ) {
425
+ return h(
426
+ NTag,
427
+ {
428
+ type:
429
+ labelObject.value.startsWith("@") &&
430
+ isNumber(labelObject.value.slice(1))
431
+ ? "primary"
432
+ : "default",
433
+ closable: true,
434
+ round: true,
435
+ bordered: false,
436
+ onClose: () => {
437
+ tableCopy.value.localLabel?.splice(index, 1);
438
+ },
439
+ },
440
+ {
441
+ default: () => labelObject.label,
442
+ },
443
+ );
444
+ }
445
+
446
+ const searchInSelectOptions = computed(() =>
447
+ generateSearchInOptions(table.value?.schema, undefined, true),
448
+ );
449
+
450
+ const groupBySelectOptions = computed(() =>
451
+ generateSearchInOptions(
452
+ table.value?.schema,
453
+ table.value?.schema
454
+ ?.filter(
455
+ ({ id, subType, options }) =>
456
+ id &&
457
+ (!subType ||
458
+ !["select", "radio"].includes(subType) ||
459
+ !options?.length),
460
+ )
461
+ .map(({ id }) => String(id)),
462
+ true,
463
+ ),
464
+ );
465
+
466
+ const generalSettingsSchema = reactive<Schema>([
467
+ {
468
+ key: "slug",
469
+ type: "string",
470
+ required: true,
471
+ inputProps: isUnDeletable.value
472
+ ? {
473
+ disabled: true,
474
+ }
475
+ : {},
476
+ width: 3,
477
+ },
478
+ {
479
+ key: "icon",
480
+ type: "string",
481
+ subType: "icon",
482
+ inputProps: isUnDeletable.value
483
+ ? {
484
+ disabled: true,
485
+ }
486
+ : {},
487
+ width: 3,
488
+ },
489
+ {
490
+ key: "displayAs",
491
+ type: "string",
492
+ subType: "select",
493
+ options: [
494
+ { label: t("table"), value: "table" },
495
+ { label: t("kanban"), value: "kanban" },
496
+ { label: t("list"), value: "list", disabled: true },
497
+ { label: t("cards"), value: "cards", disabled: true },
498
+ ],
499
+ inputProps: isUnDeletable.value
500
+ ? {
501
+ disabled: true,
502
+ }
503
+ : {},
504
+ width: 3,
505
+ },
506
+ {
507
+ key: "compression",
508
+ labelKey: "compression.label",
509
+ type: "boolean",
510
+ inputProps: isUnDeletable.value
511
+ ? {
512
+ disabled: true,
513
+ }
514
+ : {},
515
+ width: 6,
516
+ },
517
+ {
518
+ key: "cache",
519
+ type: "boolean",
520
+ inputProps: isUnDeletable.value
521
+ ? {
522
+ disabled: true,
523
+ }
524
+ : {},
525
+ width: 6,
526
+ },
527
+ {
528
+ key: "prepend",
529
+ type: "boolean",
530
+ description: "recentItemsAppearAtTheTop",
531
+ inputProps: isUnDeletable.value
532
+ ? {
533
+ disabled: true,
534
+ }
535
+ : {},
536
+ width: 6,
537
+ },
538
+ {
539
+ key: "decodeID",
540
+ type: "boolean",
541
+ description: "disableIdEncryption",
542
+ inputProps: isUnDeletable.value
543
+ ? {
544
+ disabled: true,
545
+ }
546
+ : {},
547
+ width: 6,
548
+ },
549
+ {
550
+ key: "log",
551
+ type: "boolean",
552
+ description: "enableActivityLog",
553
+ width: 6,
554
+ },
555
+ {
556
+ key: "realtime",
557
+ type: "boolean",
558
+ description: "enableRealtimeSync",
559
+ width: 6,
560
+ },
561
+ ]);
562
+
563
+ function generateMentionOptions(
564
+ schema?: Schema,
565
+ prefix?: string,
566
+ ): {
567
+ label: string;
568
+ value: string | number;
569
+ }[] {
570
+ let RETURN: {
571
+ label: string;
572
+ value: string | number;
573
+ }[] = [];
574
+
575
+ if (!schema) return RETURN;
576
+
577
+ for (const field of schema) {
578
+ if (!field.id || field.id.toString().startsWith("temp-")) continue;
579
+ if (
580
+ (Array.isArray(field.type) && field.type.includes("array")) ||
581
+ (field.type === "array" &&
582
+ field.children &&
583
+ Array.isArray(field.children) &&
584
+ isArrayOfObjects(field.children))
585
+ )
586
+ continue;
587
+ if (
588
+ field.children &&
589
+ Array.isArray(field.children) &&
590
+ isArrayOfObjects(field.children)
591
+ )
592
+ RETURN = [
593
+ ...RETURN,
594
+ ...generateMentionOptions(field.children, field.key),
595
+ ];
596
+ else
597
+ RETURN.push({
598
+ label: (prefix ? `${prefix}/` : "") + field.key,
599
+ value: field.id,
600
+ });
601
+ }
602
+ return RETURN;
603
+ }
604
+
605
+ const labelField: Field = {
606
+ key: "label",
607
+ type: "string",
608
+ subType: "mention",
609
+ options: generateMentionOptions(tableCopy.value?.schema),
610
+ inputProps: {
611
+ filter: (pattern: string, option: { value: string }) => {
612
+ if (!pattern) return true;
613
+ if (typeof option.value === "string") {
614
+ return option.value.startsWith(pattern);
615
+ }
616
+ return pattern === option.value;
617
+ },
618
+ type: "textarea",
619
+ autosize: {
620
+ minRows: 3,
621
+ },
622
+ },
623
+ };
624
+
625
+ useHead({
626
+ title: `${t(database.value.slug)} | ${t(table.value?.slug)} : ${t("settings")}`,
627
+ link: [
628
+ { rel: "icon", href: database.value?.icon?.publicURL ?? "/favicon.ico" },
629
+ ],
630
+ });
631
+ </script>