@conduction/nextcloud-vue 0.1.0-beta.1 → 0.1.0-beta.11

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 (208) hide show
  1. package/README.md +226 -0
  2. package/css/index.css +5 -0
  3. package/dist/nextcloud-vue.cjs.js +79416 -7715
  4. package/dist/nextcloud-vue.cjs.js.map +1 -1
  5. package/dist/nextcloud-vue.css +3583 -504
  6. package/dist/nextcloud-vue.esm.js +79343 -7692
  7. package/dist/nextcloud-vue.esm.js.map +1 -1
  8. package/l10n/en.json +164 -0
  9. package/l10n/nl.json +164 -0
  10. package/package.json +104 -63
  11. package/src/components/CnActionsBar/CnActionsBar.vue +254 -0
  12. package/src/components/CnActionsBar/index.js +1 -0
  13. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +570 -0
  14. package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -0
  15. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -0
  16. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +422 -0
  17. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -0
  18. package/src/components/CnAdvancedFormDialog/index.js +1 -0
  19. package/src/components/CnCard/CnCard.vue +415 -0
  20. package/src/components/CnCard/index.js +1 -0
  21. package/src/components/CnCardGrid/CnCardGrid.vue +156 -152
  22. package/src/components/CnCardGrid/index.js +1 -1
  23. package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -132
  24. package/src/components/CnCellRenderer/index.js +1 -1
  25. package/src/components/CnChartWidget/CnChartWidget.vue +346 -0
  26. package/src/components/CnChartWidget/index.js +1 -0
  27. package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -77
  28. package/src/components/CnConfigurationCard/index.js +1 -1
  29. package/src/components/CnContextMenu/CnContextMenu.vue +142 -0
  30. package/src/components/CnContextMenu/index.js +1 -0
  31. package/src/components/CnCopyDialog/CnCopyDialog.vue +266 -0
  32. package/src/components/CnCopyDialog/index.js +1 -0
  33. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +229 -0
  34. package/src/components/CnDashboardGrid/index.js +1 -0
  35. package/src/components/CnDashboardPage/CnDashboardPage.vue +397 -0
  36. package/src/components/CnDashboardPage/index.js +1 -0
  37. package/src/components/CnDataTable/CnDataTable.vue +362 -354
  38. package/src/components/CnDataTable/index.js +1 -1
  39. package/src/components/CnDeleteDialog/CnDeleteDialog.vue +177 -0
  40. package/src/components/CnDeleteDialog/index.js +1 -0
  41. package/src/components/CnDetailCard/CnDetailCard.vue +225 -0
  42. package/src/components/CnDetailCard/index.js +1 -0
  43. package/src/components/CnDetailGrid/CnDetailGrid.vue +256 -0
  44. package/src/components/CnDetailGrid/index.js +1 -0
  45. package/src/components/CnDetailPage/CnDetailPage.vue +432 -0
  46. package/src/components/CnDetailPage/index.js +1 -0
  47. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +234 -223
  48. package/src/components/CnFacetSidebar/index.js +1 -1
  49. package/src/components/CnFilterBar/CnFilterBar.vue +153 -152
  50. package/src/components/CnFilterBar/index.js +1 -1
  51. package/src/components/CnFormDialog/CnFormDialog.vue +1047 -0
  52. package/src/components/CnFormDialog/index.js +1 -0
  53. package/src/components/CnIcon/CnIcon.vue +89 -0
  54. package/src/components/CnIcon/index.js +1 -0
  55. package/src/components/CnIndexPage/CnIndexPage.vue +980 -682
  56. package/src/components/CnIndexPage/index.js +1 -1
  57. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +536 -0
  58. package/src/components/CnIndexSidebar/index.js +1 -0
  59. package/src/components/CnInfoWidget/CnInfoWidget.vue +219 -0
  60. package/src/components/CnInfoWidget/index.js +1 -0
  61. package/src/components/CnItemCard/CnItemCard.vue +134 -0
  62. package/src/components/CnItemCard/index.js +1 -0
  63. package/src/components/CnJsonViewer/CnJsonViewer.vue +312 -0
  64. package/src/components/CnJsonViewer/index.js +1 -0
  65. package/src/components/CnKpiGrid/CnKpiGrid.vue +93 -89
  66. package/src/components/CnKpiGrid/index.js +1 -1
  67. package/src/components/CnMassActionBar/CnMassActionBar.vue +161 -160
  68. package/src/components/CnMassActionBar/index.js +1 -1
  69. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +327 -320
  70. package/src/components/CnMassCopyDialog/index.js +1 -1
  71. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +245 -238
  72. package/src/components/CnMassDeleteDialog/index.js +1 -1
  73. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +191 -190
  74. package/src/components/CnMassExportDialog/index.js +1 -1
  75. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +494 -491
  76. package/src/components/CnMassImportDialog/index.js +1 -1
  77. package/src/components/CnNoteCard/CnNoteCard.vue +149 -0
  78. package/src/components/CnNoteCard/index.js +1 -0
  79. package/src/components/CnNotesCard/CnNotesCard.vue +416 -0
  80. package/src/components/CnNotesCard/index.js +1 -0
  81. package/src/components/CnObjectCard/CnObjectCard.vue +294 -292
  82. package/src/components/CnObjectCard/index.js +1 -1
  83. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +854 -0
  84. package/src/components/CnObjectDataWidget/index.js +1 -0
  85. package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +289 -0
  86. package/src/components/CnObjectMetadataWidget/index.js +1 -0
  87. package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +369 -0
  88. package/src/components/CnObjectSidebar/CnFilesTab.vue +287 -0
  89. package/src/components/CnObjectSidebar/CnNotesTab.vue +250 -0
  90. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +255 -0
  91. package/src/components/CnObjectSidebar/CnTagsTab.vue +259 -0
  92. package/src/components/CnObjectSidebar/CnTasksTab.vue +483 -0
  93. package/src/components/CnObjectSidebar/index.js +6 -0
  94. package/src/components/CnPageHeader/CnPageHeader.vue +61 -0
  95. package/src/components/CnPageHeader/index.js +1 -0
  96. package/src/components/CnPagination/CnPagination.vue +253 -252
  97. package/src/components/CnPagination/index.js +1 -1
  98. package/src/components/CnProgressBar/CnProgressBar.vue +262 -0
  99. package/src/components/CnProgressBar/index.js +1 -0
  100. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +793 -0
  101. package/src/components/CnRegisterMapping/index.js +1 -0
  102. package/src/components/CnRowActions/CnRowActions.vue +95 -73
  103. package/src/components/CnRowActions/index.js +1 -1
  104. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -0
  105. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +788 -0
  106. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -0
  107. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -0
  108. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -0
  109. package/src/components/CnSchemaFormDialog/index.js +1 -0
  110. package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -92
  111. package/src/components/CnSettingsCard/index.js +1 -1
  112. package/src/components/CnSettingsSection/CnSettingsSection.vue +267 -266
  113. package/src/components/CnSettingsSection/index.js +1 -1
  114. package/src/components/CnStatsBlock/CnStatsBlock.vue +437 -366
  115. package/src/components/CnStatsBlock/index.js +1 -1
  116. package/src/components/CnStatsPanel/CnStatsPanel.vue +321 -0
  117. package/src/components/CnStatsPanel/index.js +1 -0
  118. package/src/components/CnStatusBadge/CnStatusBadge.vue +90 -77
  119. package/src/components/CnStatusBadge/index.js +1 -1
  120. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +545 -0
  121. package/src/components/CnTabbedFormDialog/index.js +1 -0
  122. package/src/components/CnTableWidget/CnTableWidget.vue +333 -0
  123. package/src/components/CnTableWidget/index.js +1 -0
  124. package/src/components/CnTasksCard/CnTasksCard.vue +374 -0
  125. package/src/components/CnTasksCard/index.js +1 -0
  126. package/src/components/CnTileWidget/CnTileWidget.vue +159 -0
  127. package/src/components/CnTileWidget/index.js +1 -0
  128. package/src/components/CnTimelineStages/CnTimelineStages.vue +294 -0
  129. package/src/components/CnTimelineStages/index.js +1 -0
  130. package/src/components/CnUserActionMenu/CnUserActionMenu.vue +436 -0
  131. package/src/components/CnUserActionMenu/index.js +1 -0
  132. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +313 -312
  133. package/src/components/CnVersionInfoCard/index.js +1 -1
  134. package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -0
  135. package/src/components/CnWidgetRenderer/index.js +1 -0
  136. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +248 -0
  137. package/src/components/CnWidgetWrapper/index.js +1 -0
  138. package/src/components/index.js +57 -25
  139. package/src/composables/index.js +5 -3
  140. package/src/composables/useContextMenu.js +126 -0
  141. package/src/composables/useDashboardView.js +286 -0
  142. package/src/composables/useDetailView.js +290 -132
  143. package/src/composables/useListView.js +364 -153
  144. package/src/composables/useSubResource.js +142 -142
  145. package/src/constants/metadata.js +30 -0
  146. package/src/css/CnSchemaFormDialog.css +546 -0
  147. package/src/css/__sample_nextcloud_tokens.css +110 -0
  148. package/src/css/actions-bar.css +54 -0
  149. package/src/css/badge.css +83 -51
  150. package/src/css/card.css +129 -128
  151. package/src/css/context-menu.css +20 -0
  152. package/src/css/dashboard.css +70 -0
  153. package/src/css/detail-page.css +235 -0
  154. package/src/css/detail.css +68 -68
  155. package/src/css/index-page.css +44 -0
  156. package/src/css/index-sidebar.css +193 -0
  157. package/src/css/index.css +17 -8
  158. package/src/css/layout.css +90 -90
  159. package/src/css/page-header.css +35 -0
  160. package/src/css/pagination.css +72 -72
  161. package/src/css/table.css +142 -143
  162. package/src/css/timeline-stages.css +220 -0
  163. package/src/css/utilities.css +46 -46
  164. package/src/index.js +95 -50
  165. package/src/l10n/index.js +12 -0
  166. package/src/mixins/gridLayout.js +118 -0
  167. package/src/store/createCrudStore.d.ts +350 -0
  168. package/src/store/createCrudStore.js +413 -0
  169. package/src/store/createSubResourcePlugin.js +125 -135
  170. package/src/store/index.js +4 -3
  171. package/src/store/pluginMerge.js +55 -0
  172. package/src/store/plugins/auditTrails.js +357 -17
  173. package/src/store/plugins/files.js +250 -186
  174. package/src/store/plugins/index.js +8 -4
  175. package/src/store/plugins/lifecycle.js +180 -180
  176. package/src/store/plugins/logs.d.ts +22 -0
  177. package/src/store/plugins/logs.js +172 -0
  178. package/src/store/plugins/registerMapping.js +195 -0
  179. package/src/store/plugins/relations.js +68 -68
  180. package/src/store/plugins/search.js +385 -0
  181. package/src/store/plugins/selection.js +104 -0
  182. package/src/store/useObjectStore.js +793 -625
  183. package/src/types/auditTrail.d.ts +32 -32
  184. package/src/types/file.d.ts +23 -23
  185. package/src/types/index.d.ts +67 -35
  186. package/src/types/notification.d.ts +36 -36
  187. package/src/types/object.d.ts +40 -40
  188. package/src/types/organisation.d.ts +41 -41
  189. package/src/types/register.d.ts +25 -25
  190. package/src/types/schema.d.ts +39 -39
  191. package/src/types/shared.d.ts +79 -79
  192. package/src/types/source.d.ts +14 -14
  193. package/src/types/task.d.ts +31 -31
  194. package/src/utils/errors.js +96 -96
  195. package/src/utils/getTheme.js +9 -0
  196. package/src/utils/headers.js +80 -44
  197. package/src/utils/id.js +13 -0
  198. package/src/utils/index.js +4 -3
  199. package/src/utils/schema.js +423 -287
  200. package/src/utils/widgetVisibility.js +162 -0
  201. package/src/components/CnDetailViewLayout/CnDetailViewLayout.vue +0 -88
  202. package/src/components/CnDetailViewLayout/index.js +0 -1
  203. package/src/components/CnEmptyState/CnEmptyState.vue +0 -78
  204. package/src/components/CnEmptyState/index.js +0 -1
  205. package/src/components/CnListViewLayout/CnListViewLayout.vue +0 -80
  206. package/src/components/CnListViewLayout/index.js +0 -1
  207. package/src/components/CnViewModeToggle/CnViewModeToggle.vue +0 -77
  208. package/src/components/CnViewModeToggle/index.js +0 -1
@@ -0,0 +1,788 @@
1
+ <template>
2
+ <CnTabbedFormDialog
3
+ ref="dialog"
4
+ :tabs="dialogTabs"
5
+ :item="item"
6
+ :dialog-title="dialogTitle"
7
+ entity-name="Schema"
8
+ :size="size"
9
+ :disable-save="!schemaItem.title"
10
+ :success-text="resolvedSuccessText"
11
+ :cancel-label="cancelLabel"
12
+ :close-label="closeLabel"
13
+ :confirm-label="confirmLabel"
14
+ @confirm="handleConfirm"
15
+ @close="$emit('close')">
16
+ <!-- Metadata Display -->
17
+ <template #above-tabs="{ loading: dialogLoading }">
18
+ <div class="cn-schema-form__detail-grid">
19
+ <div v-if="schemaItem.id"
20
+ class="cn-schema-form__detail-item cn-schema-form__id-card">
21
+ <div class="cn-schema-form__id-card-header">
22
+ <span class="cn-schema-form__detail-label">ID / UUID:</span>
23
+ <NcButton class="cn-schema-form__copy-button"
24
+ @click="copyToClipboard(schemaItem.uuid || schemaItem.id)">
25
+ <template #icon>
26
+ <Check v-if="isCopied" :size="20" />
27
+ <ContentCopy v-else :size="20" />
28
+ </template>
29
+ {{ isCopied ? 'Copied' : 'Copy' }}
30
+ </NcButton>
31
+ </div>
32
+ <span class="cn-schema-form__detail-value">{{ schemaItem.id }}</span>
33
+ <span v-if="schemaItem.uuid && schemaItem.uuid !== schemaItem.id"
34
+ class="cn-schema-form__detail-value cn-schema-form__uuid-value">{{ schemaItem.uuid }}</span>
35
+ </div>
36
+ <div class="cn-schema-form__detail-item cn-schema-form__title-with-badge">
37
+ <NcTextField :disabled="dialogLoading"
38
+ label="Title *"
39
+ :value.sync="schemaItem.title" />
40
+ <span v-if="schemaItem.allOf && schemaItem.allOf.length > 0"
41
+ class="cn-schema-form__statusPill cn-schema-form__statusPill--success">
42
+ allOf
43
+ </span>
44
+ <span v-if="schemaItem.oneOf && schemaItem.oneOf.length > 0"
45
+ class="cn-schema-form__statusPill cn-schema-form__statusPill--info">
46
+ oneOf
47
+ </span>
48
+ <span v-if="schemaItem.anyOf && schemaItem.anyOf.length > 0"
49
+ class="cn-schema-form__statusPill cn-schema-form__statusPill--info">
50
+ anyOf
51
+ </span>
52
+ </div>
53
+ <div v-if="schemaItem.created" class="cn-schema-form__detail-item">
54
+ <span class="cn-schema-form__detail-label">Created:</span>
55
+ <span class="cn-schema-form__detail-value">{{ new Date(schemaItem.created).toLocaleString() }}</span>
56
+ </div>
57
+ <div v-if="schemaItem.updated" class="cn-schema-form__detail-item">
58
+ <span class="cn-schema-form__detail-label">Updated:</span>
59
+ <span class="cn-schema-form__detail-value">{{ new Date(schemaItem.updated).toLocaleString() }}</span>
60
+ </div>
61
+ <div class="cn-schema-form__detail-item">
62
+ <span class="cn-schema-form__detail-label">Version:</span>
63
+ <span class="cn-schema-form__detail-value">{{ schemaItem.version || 'Not set' }}</span>
64
+ </div>
65
+ <div class="cn-schema-form__detail-item">
66
+ <span class="cn-schema-form__detail-label">Owner:</span>
67
+ <span class="cn-schema-form__detail-value">{{ schemaItem.owner || 'Not set' }}</span>
68
+ </div>
69
+ </div>
70
+ </template>
71
+
72
+ <!-- Properties Tab -->
73
+ <template #tab-properties="{ loading: dialogLoading }">
74
+ <CnSchemaPropertiesTab
75
+ :schema-item="schemaItem"
76
+ :loading="dialogLoading"
77
+ :selected-property="selectedProperty"
78
+ :properties-modified="propertiesModified"
79
+ :original-properties="originalProperties"
80
+ :type-options-for-select="typeOptionsForSelect"
81
+ :available-schemas="availableSchemas"
82
+ :available-registers="availableRegisters"
83
+ :available-tags-options="availableTagsOptions"
84
+ :user-groups="userGroups"
85
+ :sorted-user-groups="sortedUserGroups"
86
+ :loading-groups="loadingGroups"
87
+ @add-property="addProperty"
88
+ @update:selected-property="selectedProperty = $event"
89
+ @update:property-key="updatePropertyKey($event.oldKey, $event.newKey)"
90
+ @copy-property="copyProperty"
91
+ @delete-property="deleteProperty" />
92
+ </template>
93
+
94
+ <!-- Configuration Tab -->
95
+ <template #tab-configuration="{ loading: dialogLoading }">
96
+ <CnSchemaConfigurationTab
97
+ :schema-item="schemaItem"
98
+ :loading="dialogLoading"
99
+ :available-schemas="availableSchemas"
100
+ :property-options="propertyOptions"
101
+ :all-of-schema-names="allOfSchemaNames" />
102
+ </template>
103
+
104
+ <!-- Security Tab -->
105
+ <template #tab-security>
106
+ <CnSchemaSecurityTab
107
+ :schema-item="schemaItem"
108
+ :user-groups="userGroups"
109
+ :sorted-user-groups="sortedUserGroups"
110
+ :loading-groups="loadingGroups"
111
+ :has-any-permissions="hasAnyPermissions"
112
+ :is-restrictive-schema="isRestrictiveSchema" />
113
+ </template>
114
+
115
+ <!-- Optional Action Buttons (edit mode only) -->
116
+ <template #actions-right="{ loading: dialogLoading, isCreateMode, result: dialogResult }">
117
+ <template v-if="!isCreateMode && dialogResult === null">
118
+ <NcButton
119
+ v-if="showExtendSchema"
120
+ :disabled="dialogLoading"
121
+ @click="$emit('extend-schema')">
122
+ <template #icon>
123
+ <CallSplit :size="20" />
124
+ </template>
125
+ {{ extendSchemaLabel }}
126
+ </NcButton>
127
+ <NcButton
128
+ v-if="showAnalyzeProperties"
129
+ :disabled="dialogLoading"
130
+ @click="$emit('analyze-properties')">
131
+ <template #icon>
132
+ <DatabaseSearch :size="20" />
133
+ </template>
134
+ {{ analyzePropertiesLabel }}
135
+ </NcButton>
136
+ <NcButton
137
+ v-if="showValidateObjects"
138
+ :disabled="dialogLoading"
139
+ @click="$emit('validate-objects')">
140
+ <template #icon>
141
+ <CheckCircle :size="20" />
142
+ </template>
143
+ {{ validateObjectsLabel }}
144
+ </NcButton>
145
+ <NcButton
146
+ v-if="showDeleteObjects"
147
+ v-tooltip="objectCount > 0 ? deleteObjectsTooltip : noDeleteObjectsTooltip"
148
+ :disabled="dialogLoading || objectCount === 0"
149
+ @click="$emit('delete-objects')">
150
+ <template #icon>
151
+ <DeleteSweep :size="20" />
152
+ </template>
153
+ {{ deleteObjectsLabel }}
154
+ </NcButton>
155
+ <NcButton
156
+ v-if="showPublishObjects"
157
+ v-tooltip="objectCount > 0 ? publishObjectsTooltip : noPublishObjectsTooltip"
158
+ :disabled="dialogLoading || objectCount === 0"
159
+ @click="$emit('publish-objects')">
160
+ <template #icon>
161
+ <Upload :size="20" />
162
+ </template>
163
+ {{ publishObjectsLabel }}
164
+ </NcButton>
165
+ <NcButton
166
+ v-if="showDelete"
167
+ v-tooltip="objectCount > 0 ? cannotDeleteTooltip : ''"
168
+ :disabled="dialogLoading || objectCount > 0"
169
+ type="error"
170
+ @click="$emit('delete-schema')">
171
+ <template #icon>
172
+ <TrashCanOutline :size="20" />
173
+ </template>
174
+ {{ deleteLabel }}
175
+ </NcButton>
176
+ </template>
177
+ </template>
178
+ </CnTabbedFormDialog>
179
+ </template>
180
+
181
+ <script>
182
+ import { translate as t } from '@nextcloud/l10n'
183
+ import {
184
+ NcButton,
185
+ NcTextField,
186
+ } from '@nextcloud/vue'
187
+
188
+ import CnTabbedFormDialog from '../CnTabbedFormDialog/CnTabbedFormDialog.vue'
189
+ import CnSchemaPropertiesTab from './CnSchemaPropertiesTab.vue'
190
+ import CnSchemaConfigurationTab from './CnSchemaConfigurationTab.vue'
191
+ import CnSchemaSecurityTab from './CnSchemaSecurityTab.vue'
192
+
193
+ import ContentCopy from 'vue-material-design-icons/ContentCopy.vue'
194
+ import Check from 'vue-material-design-icons/Check.vue'
195
+ import TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'
196
+ import CallSplit from 'vue-material-design-icons/CallSplit.vue'
197
+ import DatabaseSearch from 'vue-material-design-icons/DatabaseSearch.vue'
198
+ import CheckCircle from 'vue-material-design-icons/CheckCircle.vue'
199
+ import DeleteSweep from 'vue-material-design-icons/DeleteSweep.vue'
200
+ import Upload from 'vue-material-design-icons/Upload.vue'
201
+
202
+ /**
203
+ * CnSchemaFormDialog — Generic JSON Schema editor dialog.
204
+ *
205
+ * Provides a full-featured form for creating and editing JSON Schemas with
206
+ * properties table, configuration, and security (RBAC) tabs. Uses CnTabbedFormDialog.
207
+ *
208
+ * The dialog does NOT perform saves — it emits a `confirm` event with the schema data.
209
+ * The parent performs the actual API call and calls `setResult()` via a ref.
210
+ *
211
+ * @event confirm Emitted when the user confirms. Payload: cleaned schema data object.
212
+ * @event close Emitted when the dialog should be closed.
213
+ * @event extend-schema Emitted when the Extend Schema button is clicked.
214
+ * @event analyze-properties Emitted when the Analyze Properties button is clicked.
215
+ * @event validate-objects Emitted when the Validate Objects button is clicked.
216
+ * @event delete-objects Emitted when the Delete Objects button is clicked.
217
+ * @event publish-objects Emitted when the Publish Objects button is clicked.
218
+ * @event delete-schema Emitted when the Delete button is clicked.
219
+ */
220
+ export default {
221
+ name: 'CnSchemaFormDialog',
222
+ components: {
223
+ NcTextField,
224
+ NcButton,
225
+ CnTabbedFormDialog,
226
+ CnSchemaPropertiesTab,
227
+ CnSchemaConfigurationTab,
228
+ CnSchemaSecurityTab,
229
+ // Icons
230
+ ContentCopy,
231
+ Check,
232
+ TrashCanOutline,
233
+ CallSplit,
234
+ DatabaseSearch,
235
+ CheckCircle,
236
+ DeleteSweep,
237
+ Upload,
238
+ },
239
+ props: {
240
+ /** Existing schema item for edit mode. Pass null for create mode. */
241
+ item: { type: Object, default: null },
242
+ /** Dialog title. Defaults to "Create Schema" or "Edit Schema". */
243
+ dialogTitle: { type: String, default: '' },
244
+ /** NcDialog size */
245
+ size: { type: String, default: 'large' },
246
+ /** Available schemas for references and composition. Array of { id, title, description, reference } */
247
+ availableSchemas: { type: Array, default: () => [] },
248
+ /** Available registers. Array of { id, label } */
249
+ availableRegisters: { type: Array, default: () => [] },
250
+ /** User groups for RBAC. Array of { id, displayname } */
251
+ userGroups: { type: Array, default: () => [] },
252
+ /** Available tags for file property configuration */
253
+ availableTags: { type: Array, default: () => [] },
254
+ /** Whether user groups are still loading */
255
+ loadingGroups: { type: Boolean, default: false },
256
+ /** Number of objects attached to this schema (used for action button disable logic) */
257
+ objectCount: { type: Number, default: 0 },
258
+ // Optional action button visibility
259
+ /** Show "Extend Schema" button */
260
+ showExtendSchema: { type: Boolean, default: false },
261
+ /** Show "Analyze Properties" button */
262
+ showAnalyzeProperties: { type: Boolean, default: false },
263
+ /** Show "Validate Objects" button */
264
+ showValidateObjects: { type: Boolean, default: false },
265
+ /** Show "Delete Objects" button */
266
+ showDeleteObjects: { type: Boolean, default: false },
267
+ /** Show "Publish Objects" button */
268
+ showPublishObjects: { type: Boolean, default: false },
269
+ /** Show "Delete" button */
270
+ showDelete: { type: Boolean, default: false },
271
+ // Labels (pre-translated strings with English defaults)
272
+ cancelLabel: { type: String, default: () => t('nextcloud-vue', 'Cancel') },
273
+ closeLabel: { type: String, default: () => t('nextcloud-vue', 'Close') },
274
+ /** Confirm button label. Defaults to "Create" or "Save". */
275
+ confirmLabel: { type: String, default: '' },
276
+ /** Success message. Defaults to "Schema saved successfully." */
277
+ successText: { type: String, default: '' },
278
+ extendSchemaLabel: { type: String, default: () => t('nextcloud-vue', 'Extend schema') },
279
+ analyzePropertiesLabel: { type: String, default: () => t('nextcloud-vue', 'Analyze properties') },
280
+ validateObjectsLabel: { type: String, default: () => t('nextcloud-vue', 'Validate objects') },
281
+ deleteObjectsLabel: { type: String, default: () => t('nextcloud-vue', 'Delete objects') },
282
+ publishObjectsLabel: { type: String, default: () => t('nextcloud-vue', 'Publish objects') },
283
+ deleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete') },
284
+ deleteObjectsTooltip: { type: String, default: () => t('nextcloud-vue', 'Delete all objects in this schema') },
285
+ publishObjectsTooltip: { type: String, default: () => t('nextcloud-vue', 'Publish all objects in this schema') },
286
+ /** Tooltip for the Delete Objects button when no objects exist */
287
+ noDeleteObjectsTooltip: { type: String, default: () => t('nextcloud-vue', 'No objects to delete') },
288
+ /** Tooltip for the Publish Objects button when no objects exist */
289
+ noPublishObjectsTooltip: { type: String, default: () => t('nextcloud-vue', 'No objects to publish') },
290
+ cannotDeleteTooltip: { type: String, default: () => t('nextcloud-vue', 'Cannot delete: objects are still attached') },
291
+ },
292
+ data() {
293
+ return {
294
+ isCopied: false,
295
+ selectedProperty: null,
296
+ propertiesModified: false,
297
+ originalProperties: null,
298
+ schemaItem: {
299
+ title: '',
300
+ version: '0.0.0',
301
+ description: '',
302
+ summary: '',
303
+ slug: '',
304
+ properties: {},
305
+ configuration: {
306
+ objectNameField: '',
307
+ objectDescriptionField: '',
308
+ objectImageField: '',
309
+ objectSummaryField: '',
310
+ allowFiles: false,
311
+ allowedTags: [],
312
+ autoPublish: false,
313
+ },
314
+ authorization: {},
315
+ hardValidation: false,
316
+ immutable: false,
317
+ searchable: true,
318
+ maxDepth: 0,
319
+ },
320
+ }
321
+ },
322
+ computed: {
323
+ /**
324
+ * Tab definitions for CnTabbedFormDialog.
325
+ *
326
+ * @return {Array} Tab configuration
327
+ */
328
+ dialogTabs() {
329
+ return [
330
+ { id: 'properties', title: 'Properties' },
331
+ { id: 'configuration', title: 'Configuration' },
332
+ { id: 'security', title: 'Security' },
333
+ ]
334
+ },
335
+ sortedUserGroups() {
336
+ return this.userGroups
337
+ .filter(group => group.id !== 'admin' && group.id !== 'public' && group.id !== 'authenticated')
338
+ .sort((a, b) => {
339
+ const nameA = a.displayname || a.id
340
+ const nameB = b.displayname || b.id
341
+ return nameA.localeCompare(nameB)
342
+ })
343
+ },
344
+ hasAnyPermissions() {
345
+ const auth = this.schemaItem.authorization || {}
346
+ return Object.keys(auth).some(action =>
347
+ Array.isArray(auth[action]) && auth[action].length > 0,
348
+ )
349
+ },
350
+ isRestrictiveSchema() {
351
+ const auth = this.schemaItem.authorization || {}
352
+ const actions = ['create', 'read', 'update', 'delete']
353
+ return actions.some(action =>
354
+ Array.isArray(auth[action]) && auth[action].length > 0
355
+ && !auth[action].includes('public'),
356
+ )
357
+ },
358
+ typeOptionsForSelect() {
359
+ return [
360
+ { id: 'string', label: 'String' },
361
+ { id: 'number', label: 'Number' },
362
+ { id: 'integer', label: 'Integer' },
363
+ { id: 'boolean', label: 'Boolean' },
364
+ { id: 'array', label: 'Array' },
365
+ { id: 'object', label: 'Object' },
366
+ { id: 'dictionary', label: 'Dictionary' },
367
+ { id: 'file', label: 'File' },
368
+ { id: 'oneOf', label: 'One Of' },
369
+ ]
370
+ },
371
+ propertyOptions() {
372
+ const properties = this.schemaItem.properties || {}
373
+ return ['', ...Object.keys(properties)]
374
+ },
375
+ availableTagsOptions() {
376
+ return this.availableTags.map(tag => ({
377
+ id: tag,
378
+ label: tag,
379
+ }))
380
+ },
381
+ /**
382
+ * Resolved success text for backwards compatibility (includes trailing period).
383
+ *
384
+ * @return {string}
385
+ */
386
+ resolvedSuccessText() {
387
+ if (this.successText) return this.successText
388
+ return 'Schema saved successfully.'
389
+ },
390
+ allOfSchemaNames() {
391
+ if (!this.schemaItem.allOf || !Array.isArray(this.schemaItem.allOf) || this.schemaItem.allOf.length === 0) {
392
+ return []
393
+ }
394
+
395
+ return this.schemaItem.allOf
396
+ .map(ref => {
397
+ const schemaId = typeof ref === 'object' ? ref.id : ref
398
+ const schema = this.availableSchemas.find(s => s.id === schemaId)
399
+ return schema ? (schema.title || `Schema ${schema.id}`) : schemaId
400
+ })
401
+ .filter(name => name)
402
+ },
403
+ },
404
+ watch: {
405
+ item: {
406
+ immediate: true,
407
+ handler() {
408
+ this.initializeSchemaItem()
409
+ },
410
+ },
411
+ 'schemaItem.properties': {
412
+ handler(newProperties) {
413
+ if (newProperties) {
414
+ Object.keys(newProperties).forEach(key => {
415
+ const property = newProperties[key]
416
+ if (property) {
417
+ // Initialize nested objects if they don't exist
418
+ if (property.type === 'array' && !property.items) {
419
+ this.$set(this.schemaItem.properties[key], 'items', { type: 'string' })
420
+ }
421
+ if (property.type === 'object' && !property.objectConfiguration) {
422
+ this.$set(this.schemaItem.properties[key], 'objectConfiguration', { handling: 'nested-object' })
423
+ }
424
+ if (property.type === 'array' && property.items && property.items.type === 'object' && !property.items.objectConfiguration) {
425
+ this.$set(this.schemaItem.properties[key].items, 'objectConfiguration', { handling: 'nested-object' })
426
+ }
427
+
428
+ // Convert property type from object to string
429
+ if (property.type && typeof property.type === 'object' && property.type.id) {
430
+ this.$set(this.schemaItem.properties[key], 'type', property.type.id)
431
+ }
432
+
433
+ // Convert property format from object to string
434
+ if (property.format && typeof property.format === 'object' && property.format.id) {
435
+ this.$set(this.schemaItem.properties[key], 'format', property.format.id)
436
+ }
437
+
438
+ // Convert array item type from object to string
439
+ if (property.items && property.items.type && typeof property.items.type === 'object' && property.items.type.id) {
440
+ this.$set(this.schemaItem.properties[key].items, 'type', property.items.type.id)
441
+ }
442
+
443
+ // Convert object handling from object to string
444
+ if (property.objectConfiguration && property.objectConfiguration.handling
445
+ && typeof property.objectConfiguration.handling === 'object' && property.objectConfiguration.handling.id) {
446
+ this.$set(this.schemaItem.properties[key].objectConfiguration, 'handling', property.objectConfiguration.handling.id)
447
+ }
448
+
449
+ // Convert register from object to ID
450
+ if (property.objectConfiguration && property.objectConfiguration.register
451
+ && typeof property.objectConfiguration.register === 'object' && property.objectConfiguration.register.id) {
452
+ this.$set(this.schemaItem.properties[key].objectConfiguration, 'register', property.objectConfiguration.register.id)
453
+ }
454
+
455
+ // Convert array item object handling from object to string
456
+ if (property.items && property.items.objectConfiguration && property.items.objectConfiguration.handling
457
+ && typeof property.items.objectConfiguration.handling === 'object' && property.items.objectConfiguration.handling.id) {
458
+ this.$set(this.schemaItem.properties[key].items.objectConfiguration, 'handling', property.items.objectConfiguration.handling.id)
459
+ }
460
+
461
+ // Convert array item register from object to ID
462
+ if (property.items && property.items.objectConfiguration && property.items.objectConfiguration.register
463
+ && typeof property.items.objectConfiguration.register === 'object' && property.items.objectConfiguration.register.id) {
464
+ this.$set(this.schemaItem.properties[key].items.objectConfiguration, 'register', property.items.objectConfiguration.register.id)
465
+ }
466
+
467
+ // Ensure $ref is always a string
468
+ this.ensureRefIsString(this.schemaItem.properties, key)
469
+
470
+ // Ensure inversedBy is always a string for regular properties
471
+ if (property.inversedBy && typeof property.inversedBy === 'object' && property.inversedBy.id) {
472
+ this.$set(this.schemaItem.properties[key], 'inversedBy', property.inversedBy.id)
473
+ }
474
+
475
+ // Ensure inversedBy is always a string for array items
476
+ if (property.items && property.items.inversedBy && typeof property.items.inversedBy === 'object' && property.items.inversedBy.id) {
477
+ this.$set(this.schemaItem.properties[key].items, 'inversedBy', property.items.inversedBy.id)
478
+ }
479
+ }
480
+ })
481
+ }
482
+ this.checkPropertiesModified()
483
+ },
484
+ deep: true,
485
+ },
486
+ },
487
+ methods: {
488
+ findSchemaBySlug(schemaSlug) {
489
+ if (!schemaSlug) return undefined
490
+ return this.availableSchemas.find(schema =>
491
+ (schema.slug && schema.slug.toLowerCase() === schemaSlug.toLowerCase())
492
+ || schema.id === schemaSlug
493
+ || schema.title === schemaSlug,
494
+ )
495
+ },
496
+
497
+ ensureRefIsString(obj, key) {
498
+ if (!obj || !key) return
499
+
500
+ if (obj[key] && typeof obj[key].$ref === 'object' && obj[key].$ref !== null) {
501
+ if (obj[key].$ref.id) {
502
+ obj[key].$ref = obj[key].$ref.id
503
+ } else {
504
+ obj[key].$ref = ''
505
+ }
506
+ }
507
+
508
+ if (obj[key] && obj[key].items && typeof obj[key].items.$ref === 'object' && obj[key].items.$ref !== null) {
509
+ if (obj[key].items.$ref.id) {
510
+ obj[key].items.$ref = obj[key].items.$ref.id
511
+ } else {
512
+ obj[key].items.$ref = ''
513
+ }
514
+ }
515
+ },
516
+
517
+ initializeSchemaItem() {
518
+ // Reset CnTabbedFormDialog state if available (not yet mounted on first call)
519
+ if (this.$refs.dialog) {
520
+ this.$refs.dialog.resetDialog()
521
+ }
522
+
523
+ if (this.item && this.item.id) {
524
+ this.schemaItem = {
525
+ ...this.schemaItem,
526
+ ...JSON.parse(JSON.stringify(this.item)),
527
+ }
528
+
529
+ // Ensure configuration object exists
530
+ if (!this.schemaItem.configuration) {
531
+ this.schemaItem.configuration = {
532
+ objectNameField: '',
533
+ objectDescriptionField: '',
534
+ objectImageField: '',
535
+ objectSummaryField: '',
536
+ allowFiles: false,
537
+ allowedTags: [],
538
+ }
539
+ } else {
540
+ if (!this.schemaItem.configuration.objectNameField) {
541
+ this.schemaItem.configuration.objectNameField = ''
542
+ }
543
+ if (!this.schemaItem.configuration.objectDescriptionField) {
544
+ this.schemaItem.configuration.objectDescriptionField = ''
545
+ }
546
+ if (!this.schemaItem.configuration.objectImageField) {
547
+ this.schemaItem.configuration.objectImageField = ''
548
+ }
549
+ if (!this.schemaItem.configuration.objectSummaryField) {
550
+ this.schemaItem.configuration.objectSummaryField = ''
551
+ }
552
+ if (this.schemaItem.configuration.allowFiles === undefined) {
553
+ this.schemaItem.configuration.allowFiles = false
554
+ }
555
+ if (!this.schemaItem.configuration.allowedTags) {
556
+ this.schemaItem.configuration.allowedTags = []
557
+ }
558
+ if (this.schemaItem.configuration.autoPublish === undefined) {
559
+ this.schemaItem.configuration.autoPublish = false
560
+ }
561
+ }
562
+
563
+ // Ensure authorization object exists
564
+ if (!this.schemaItem.authorization) {
565
+ this.schemaItem.authorization = {}
566
+ }
567
+
568
+ // Ensure existing properties have facetable set to false by default
569
+ Object.keys(this.schemaItem.properties || {}).forEach(key => {
570
+ if (this.schemaItem.properties[key].facetable === undefined) {
571
+ this.$set(this.schemaItem.properties[key], 'facetable', false)
572
+ }
573
+
574
+ if (this.schemaItem.properties[key].enum && Array.isArray(this.schemaItem.properties[key].enum)) {
575
+ this.$set(this.schemaItem.properties[key], 'enum', [...this.schemaItem.properties[key].enum])
576
+ }
577
+
578
+ const property = this.schemaItem.properties[key]
579
+ if (property.type === 'array' && property.items && property.items.type === 'object' && !property.items.objectConfiguration) {
580
+ this.$set(this.schemaItem.properties[key].items, 'objectConfiguration', { handling: 'nested-object' })
581
+ }
582
+ })
583
+
584
+ // Ensure all $ref values are strings and migrate old structure
585
+ Object.keys(this.schemaItem.properties || {}).forEach(key => {
586
+ this.ensureRefIsString(this.schemaItem.properties, key)
587
+ this.migratePropertyToNewStructure(key)
588
+ })
589
+
590
+ this.originalProperties = JSON.parse(JSON.stringify(this.schemaItem.properties || {}))
591
+ } else {
592
+ this.schemaItem.configuration = {
593
+ objectNameField: '',
594
+ objectDescriptionField: '',
595
+ objectImageField: '',
596
+ objectSummaryField: '',
597
+ allowFiles: false,
598
+ allowedTags: [],
599
+ autoPublish: false,
600
+ }
601
+ this.originalProperties = {}
602
+ }
603
+ this.propertiesModified = false
604
+ },
605
+
606
+ checkPropertiesModified() {
607
+ if (!this.originalProperties) return false
608
+
609
+ const currentProperties = JSON.stringify(this.schemaItem.properties || {})
610
+ const originalProperties = JSON.stringify(this.originalProperties)
611
+
612
+ this.propertiesModified = currentProperties !== originalProperties
613
+ },
614
+
615
+ async copyToClipboard(text) {
616
+ try {
617
+ await navigator.clipboard.writeText(text)
618
+ this.isCopied = true
619
+ setTimeout(() => { this.isCopied = false }, 2000)
620
+ } catch (err) {
621
+ console.error('Failed to copy text:', err)
622
+ }
623
+ },
624
+
625
+ addProperty() {
626
+ let newPropertyName = 'new'
627
+ let counter = 1
628
+
629
+ while (this.schemaItem.properties[newPropertyName]) {
630
+ counter++
631
+ newPropertyName = `new_${counter}`
632
+ }
633
+
634
+ this.$set(this.schemaItem.properties, newPropertyName, {
635
+ type: 'string',
636
+ format: '',
637
+ title: newPropertyName,
638
+ description: '',
639
+ facetable: false,
640
+ })
641
+
642
+ this.checkPropertiesModified()
643
+ this.selectedProperty = newPropertyName
644
+ },
645
+
646
+ updatePropertyKey(oldKey, newKey) {
647
+ if (!newKey || newKey === oldKey) return
648
+ if (this.schemaItem.properties[newKey] && newKey !== oldKey) return
649
+
650
+ const propertyData = { ...this.schemaItem.properties[oldKey] }
651
+
652
+ this.$set(this.schemaItem.properties, newKey, propertyData)
653
+ this.$delete(this.schemaItem.properties, oldKey)
654
+
655
+ this.selectedProperty = newKey
656
+ this.checkPropertiesModified()
657
+ },
658
+
659
+ deleteProperty(key) {
660
+ this.$delete(this.schemaItem.properties, key)
661
+
662
+ if (this.selectedProperty === key) {
663
+ this.selectedProperty = null
664
+ }
665
+
666
+ this.checkPropertiesModified()
667
+ },
668
+
669
+ copyProperty(key) {
670
+ if (this.schemaItem.properties[key]) {
671
+ const originalProperty = JSON.parse(JSON.stringify(this.schemaItem.properties[key]))
672
+
673
+ let newPropertyName = `${key}_copy`
674
+ let counter = 1
675
+
676
+ while (this.schemaItem.properties[newPropertyName]) {
677
+ counter++
678
+ newPropertyName = `${key}_copy_${counter}`
679
+ }
680
+
681
+ const originalTitle = originalProperty.title || key
682
+ this.$set(this.schemaItem.properties, newPropertyName, {
683
+ ...originalProperty,
684
+ title: `${originalTitle} (copy)`,
685
+ })
686
+
687
+ this.checkPropertiesModified()
688
+ this.selectedProperty = newPropertyName
689
+ }
690
+ },
691
+
692
+ /**
693
+ * Handle confirm from CnTabbedFormDialog.
694
+ * Cleans schema data and emits confirm with the cleaned payload.
695
+ */
696
+ handleConfirm() {
697
+ const cleanedSchemaItem = JSON.parse(JSON.stringify(this.schemaItem))
698
+ Object.keys(cleanedSchemaItem.properties || {}).forEach(key => {
699
+ this.ensureRefIsString(cleanedSchemaItem.properties, key)
700
+
701
+ if (cleanedSchemaItem.properties[key].register
702
+ && cleanedSchemaItem.properties[key].objectConfiguration
703
+ && cleanedSchemaItem.properties[key].objectConfiguration.register) {
704
+ delete cleanedSchemaItem.properties[key].register
705
+ }
706
+
707
+ if (cleanedSchemaItem.properties[key].items
708
+ && cleanedSchemaItem.properties[key].items.register
709
+ && cleanedSchemaItem.properties[key].items.objectConfiguration
710
+ && cleanedSchemaItem.properties[key].items.objectConfiguration.register) {
711
+ delete cleanedSchemaItem.properties[key].items.register
712
+ }
713
+ })
714
+
715
+ this.$emit('confirm', cleanedSchemaItem)
716
+ },
717
+
718
+ /**
719
+ * Set the result of the save operation. Call this from the parent
720
+ * after the API call completes.
721
+ *
722
+ * @param {{ success?: boolean, error?: string }} resultData - result data
723
+ * @public
724
+ */
725
+ setResult(resultData) {
726
+ this.$refs.dialog.setResult(resultData)
727
+ if (resultData.success) {
728
+ this.originalProperties = JSON.parse(JSON.stringify(this.schemaItem.properties || {}))
729
+ this.propertiesModified = false
730
+ }
731
+ },
732
+
733
+ migratePropertyToNewStructure(key) {
734
+ if (!this.schemaItem.properties[key]) return
735
+
736
+ const property = this.schemaItem.properties[key]
737
+
738
+ if (property.$ref && property.register && !property.objectConfiguration?.register) {
739
+ if (!property.objectConfiguration) {
740
+ this.$set(this.schemaItem.properties[key], 'objectConfiguration', { handling: 'related-object' })
741
+ }
742
+
743
+ const registerId = typeof property.register === 'object' && property.register.id
744
+ ? property.register.id
745
+ : property.register
746
+
747
+ this.$set(this.schemaItem.properties[key].objectConfiguration, 'register', registerId)
748
+
749
+ if (property.$ref) {
750
+ let schemaSlug = property.$ref
751
+ if (schemaSlug.includes('/')) {
752
+ schemaSlug = schemaSlug.substring(schemaSlug.lastIndexOf('/') + 1)
753
+ }
754
+
755
+ const referencedSchema = this.findSchemaBySlug(schemaSlug)
756
+ if (referencedSchema) {
757
+ this.$set(this.schemaItem.properties[key].objectConfiguration, 'schema', referencedSchema.id)
758
+ }
759
+ }
760
+ }
761
+
762
+ if (property.items && property.items.$ref && property.items.register && !property.items.objectConfiguration?.register) {
763
+ if (!property.items.objectConfiguration) {
764
+ this.$set(this.schemaItem.properties[key].items, 'objectConfiguration', { handling: 'related-object' })
765
+ }
766
+
767
+ const registerId = typeof property.items.register === 'object' && property.items.register.id
768
+ ? property.items.register.id
769
+ : property.items.register
770
+
771
+ this.$set(this.schemaItem.properties[key].items.objectConfiguration, 'register', registerId)
772
+
773
+ if (property.items.$ref) {
774
+ let schemaSlug = property.items.$ref
775
+ if (schemaSlug.includes('/')) {
776
+ schemaSlug = schemaSlug.substring(schemaSlug.lastIndexOf('/') + 1)
777
+ }
778
+
779
+ const referencedSchema = this.findSchemaBySlug(schemaSlug)
780
+ if (referencedSchema) {
781
+ this.$set(this.schemaItem.properties[key].items.objectConfiguration, 'schema', referencedSchema.id)
782
+ }
783
+ }
784
+ }
785
+ },
786
+ },
787
+ }
788
+ </script>