@conduction/nextcloud-vue 2.61.0 → 2.63.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 (186) hide show
  1. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js +10 -0
  2. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js.map +1 -0
  3. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js +189 -0
  4. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js.map +1 -0
  5. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js +72 -0
  6. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js.map +1 -0
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +394 -27
  8. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  10. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +99 -9
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +39 -18
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  14. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +79 -2
  15. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  16. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +38 -25
  17. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  18. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +21 -1
  19. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -1
  20. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +4 -1
  21. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -1
  22. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  23. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +53 -37
  24. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +30 -6
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js +10 -0
  30. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js.map +1 -0
  31. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +293 -0
  32. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -0
  33. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +189 -0
  34. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -0
  35. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +256 -23
  36. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  37. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +119 -51
  38. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  39. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  40. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
  41. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  42. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js +3 -1
  43. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js.map +1 -1
  44. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js +1 -1
  45. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js.map +1 -1
  46. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +234 -9
  47. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +96 -57
  49. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  50. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +51 -2
  51. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  52. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -1
  53. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  54. package/dist/esm/components/CnIndexPage/selfModeActions.js +3 -3
  55. package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
  56. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -1
  57. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -1
  58. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +36 -1
  59. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  60. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -1
  61. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  62. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +53 -0
  63. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  64. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +22 -5
  65. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  66. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
  67. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
  68. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js +121 -12
  69. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js.map +1 -1
  70. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js +3 -1
  71. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js.map +1 -1
  72. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +326 -1
  73. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  74. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +112 -26
  75. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  76. package/dist/esm/components/CnStorePage/CnStorePage.vue2.js.map +1 -1
  77. package/dist/esm/components/CnStorePage/CnStorePage.vue3.js.map +1 -1
  78. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +10 -0
  79. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js.map +1 -0
  80. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +337 -0
  81. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -0
  82. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +102 -0
  83. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -0
  84. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +34 -0
  85. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -0
  86. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +131 -8
  87. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  88. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js +40 -38
  89. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  90. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +8 -6
  91. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  92. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  93. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  94. package/dist/esm/components/CnWikiPage/CnWikiPage.vue2.js.map +1 -1
  95. package/dist/esm/components/CnWikiPage/CnWikiPage.vue3.js.map +1 -1
  96. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
  97. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
  98. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
  99. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
  100. package/dist/esm/composables/useDependencyCheck.js +65 -0
  101. package/dist/esm/composables/useDependencyCheck.js.map +1 -0
  102. package/dist/esm/composables/useWalkthrough.js +218 -6
  103. package/dist/esm/composables/useWalkthrough.js.map +1 -1
  104. package/dist/esm/index.js +2 -0
  105. package/dist/esm/index.js.map +1 -1
  106. package/dist/esm/l10n/en.json.js +48 -0
  107. package/dist/esm/l10n/en.json.js.map +1 -1
  108. package/dist/esm/l10n/nl.json.js +48 -0
  109. package/dist/esm/l10n/nl.json.js.map +1 -1
  110. package/dist/esm/nextcloud-vue.css +557 -9
  111. package/dist/esm/store/useObjectStore.js +8 -0
  112. package/dist/esm/store/useObjectStore.js.map +1 -1
  113. package/dist/esm/utils/columnFilters.js +311 -0
  114. package/dist/esm/utils/columnFilters.js.map +1 -0
  115. package/dist/esm/utils/groupAutocomplete.js +123 -0
  116. package/dist/esm/utils/groupAutocomplete.js.map +1 -0
  117. package/dist/esm/utils/libraryWidgetKeys.js +2 -0
  118. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  119. package/dist/esm/utils/pickerOptions.js +198 -0
  120. package/dist/esm/utils/pickerOptions.js.map +1 -0
  121. package/dist/esm/utils/schema.js +83 -7
  122. package/dist/esm/utils/schema.js.map +1 -1
  123. package/dist/esm/utils/timelineEvents.js +174 -0
  124. package/dist/esm/utils/timelineEvents.js.map +1 -0
  125. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  126. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  127. package/dist/esm/utils/walkthroughSignals.js +88 -0
  128. package/dist/esm/utils/walkthroughSignals.js.map +1 -0
  129. package/dist/nextcloud-vue.cjs.js +9779 -5236
  130. package/dist/nextcloud-vue.cjs.js.map +2 -2
  131. package/dist/nextcloud-vue.css +557 -9
  132. package/l10n/en.json +48 -0
  133. package/l10n/nl.json +48 -0
  134. package/package.json +3 -2
  135. package/scripts/manifest-schema-hash.js +41 -0
  136. package/scripts/record-manifest-schema-hash.js +52 -0
  137. package/src/components/CnAdminActionCard/CnAdminActionCard.vue +234 -0
  138. package/src/components/CnAdminActionCard/index.js +4 -0
  139. package/src/components/CnAppNav/CnAppNav.vue +394 -27
  140. package/src/components/CnAppRoot/CnAppRoot.vue +129 -15
  141. package/src/components/CnBannerWidget/CnBannerWidget.vue +113 -2
  142. package/src/components/CnBrandStripe/CnBrandStripe.md +16 -0
  143. package/src/components/CnBrandStripe/CnBrandStripe.vue +58 -5
  144. package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
  145. package/src/components/CnDashboardPage/CnDashboardPage.vue +32 -8
  146. package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
  147. package/src/components/CnDataTable/CnDataTable.md +4 -1
  148. package/src/components/CnDataTable/CnDataTable.vue +302 -21
  149. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
  150. package/src/components/CnFieldHelper/CnFieldHelper.vue +4 -2
  151. package/src/components/CnFormDialog/CnFormDialog.vue +290 -31
  152. package/src/components/CnIndexPage/CnIndexPage.vue +55 -2
  153. package/src/components/CnIndexPage/selfModeActions.js +3 -3
  154. package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +35 -2
  155. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +39 -1
  156. package/src/components/CnObjectSidebar/CnFilesTab.vue +65 -0
  157. package/src/components/CnReportsPage/CnReportsPage.vue +7 -0
  158. package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
  159. package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
  160. package/src/components/CnStorePage/CnStorePage.vue +7 -0
  161. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +477 -0
  162. package/src/components/CnTimelineWidget/dashboardRegistration.js +32 -0
  163. package/src/components/CnTimelineWidget/index.js +4 -0
  164. package/src/components/CnWalkthrough/CnWalkthrough.vue +143 -10
  165. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  166. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  167. package/src/components/CnWikiPage/CnWikiPage.vue +7 -0
  168. package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
  169. package/src/components/index.js +2 -0
  170. package/src/composables/useDependencyCheck.js +61 -0
  171. package/src/composables/useWalkthrough.js +217 -5
  172. package/src/css/dashboard.css +49 -0
  173. package/src/css/detail-page.css +19 -0
  174. package/src/css/table.css +82 -2
  175. package/src/index.js +2 -0
  176. package/src/public/index.js +5 -0
  177. package/src/schemas/app-manifest-v2.schema.json +23 -1
  178. package/src/store/useObjectStore.js +8 -0
  179. package/src/utils/columnFilters.js +324 -0
  180. package/src/utils/groupAutocomplete.js +119 -0
  181. package/src/utils/libraryWidgetKeys.js +2 -0
  182. package/src/utils/pickerOptions.js +194 -0
  183. package/src/utils/schema.js +83 -7
  184. package/src/utils/timelineEvents.js +171 -0
  185. package/src/utils/validateManifestV2.compiled.js +2 -2
  186. package/src/utils/walkthroughSignals.js +94 -0
@@ -12,7 +12,9 @@ import { TENANT_CONTEXT_KEY } from '../../composables/useTenantContext.js';
12
12
  import { useObjectStore } from '../../store/useObjectStore.js';
13
13
  import { extendsFormDeclarations, prefillDeclarations, definitionQueryParams, propertiesFromDefinitions, prefillValues, splitDynamicFormData, DYNAMIC_KEY_PREFIX } from '../../utils/dynamicProperties.js';
14
14
  import { shouldShow } from '../../utils/fieldCondition.js';
15
+ import { resolveNextcloudGroup, searchNextcloudGroups } from '../../utils/groupAutocomplete.js';
15
16
  import { objectDisplayName } from '../../utils/objectName.js';
17
+ import { resolveDefaultToken, languageOptions, timezoneOptions } from '../../utils/pickerOptions.js';
16
18
  import { resolveFilterTokens } from '../../utils/resolveFilterTokens.js';
17
19
  import { fieldsFromSchema, isTenantProperty } from '../../utils/schema.js';
18
20
  import { resolveNextcloudUser, searchNextcloudUsers } from '../../utils/userAutocomplete.js';
@@ -543,6 +545,13 @@ var script = {
543
545
  * `dynamicToken` guards the definitions fetch.
544
546
  */
545
547
  prefillToken: '',
548
+ /**
549
+ * The inline create in progress, or null: `{ field, schema,
550
+ * register, slug, initialData, resolve }`. Set when "Create" is
551
+ * chosen on a select-or-create reference; renders a nested form
552
+ * for the referenced schema.
553
+ */
554
+ nestedCreate: null,
546
555
  }
547
556
  },
548
557
 
@@ -813,6 +822,7 @@ var script = {
813
822
  .filter((field) => shouldShow(field, this.formData))
814
823
  .map((field) => this.applySemanticResolution(field))
815
824
  .map((field) => this.degradeUnresolvableReference(field))
825
+ .map((field) => this.applyCodePicker(field))
816
826
  },
817
827
 
818
828
  /**
@@ -1327,11 +1337,16 @@ var script = {
1327
1337
  // Create mode: initialize with field defaults
1328
1338
  const data = {};
1329
1339
  for (const field of this.resolvedFields) {
1340
+ const tokenDefault = field.defaultToken ? resolveDefaultToken(field.defaultToken, field) : null;
1330
1341
  if (field.default !== null && field.default !== undefined) {
1331
1342
  data[field.key] = field.default;
1343
+ } else if (tokenDefault !== null) {
1344
+ // `x-default: current-language` / `current-timezone`:
1345
+ // a NEW object starts with the user's own value.
1346
+ data[field.key] = tokenDefault;
1332
1347
  } else if (field.widget === 'checkbox') {
1333
1348
  data[field.key] = false;
1334
- } else if (field.widget === 'tags' || field.widget === 'multiselect' || field.widget === 'user-multiselect') {
1349
+ } else if (field.widget === 'tags' || field.widget === 'multiselect' || field.widget === 'user-multiselect' || field.widget === 'group-multiselect') {
1335
1350
  data[field.key] = [];
1336
1351
  } else if (field.widget === 'code') {
1337
1352
  data[field.key] = '';
@@ -1398,10 +1413,35 @@ var script = {
1398
1413
  this.resolveUserLabel(uid);
1399
1414
  }
1400
1415
  }
1416
+ } else if (this.isGroupField(field) || this.isGroupArrayField(field)) {
1417
+ const value = this.formData[field.key];
1418
+ for (const gid of (Array.isArray(value) ? value : [value])) {
1419
+ if (gid) {
1420
+ this.resolveGroupLabel(gid);
1421
+ }
1422
+ }
1401
1423
  }
1402
1424
  }
1403
1425
  },
1404
1426
 
1427
+ /**
1428
+ * Resolve a stored group id to its display name and cache it in
1429
+ * `referenceLabels`. No-op once cached; falls back to the gid.
1430
+ *
1431
+ * @spec openspec/changes/form-pickers-from-schema/specs/schema-utilities/spec.md
1432
+ * @param {string} gid The stored group id.
1433
+ * @return {Promise<void>}
1434
+ */
1435
+ async resolveGroupLabel(gid) {
1436
+ if (!gid || this.referenceLabels[gid]) {
1437
+ return
1438
+ }
1439
+ const option = await resolveNextcloudGroup(gid);
1440
+ if (option && option.id) {
1441
+ this.referenceLabels = { ...this.referenceLabels, [option.id]: option.label || String(option.id) };
1442
+ }
1443
+ },
1444
+
1405
1445
  /**
1406
1446
  * Resolve a single user UID to its display name (edit mode) and cache it
1407
1447
  * in `referenceLabels` so the select shows the name instead of the UID.
@@ -1898,6 +1938,67 @@ var script = {
1898
1938
  return !!(field && field.userPicker && field.userPicker.multiple)
1899
1939
  },
1900
1940
 
1941
+ /**
1942
+ * Whether a field is a single Nextcloud group (`format: 'nc-group'` or
1943
+ * `referenceType: 'nextcloud-group'`). Stores the group id.
1944
+ *
1945
+ * @spec openspec/changes/form-pickers-from-schema/specs/schema-utilities/spec.md
1946
+ * @param {object} field The field definition
1947
+ * @return {boolean}
1948
+ */
1949
+ isGroupField(field) {
1950
+ return !!(field && field.groupPicker && !field.groupPicker.multiple)
1951
+ },
1952
+
1953
+ /**
1954
+ * Whether a field is an array of Nextcloud groups.
1955
+ *
1956
+ * @spec openspec/changes/form-pickers-from-schema/specs/schema-utilities/spec.md
1957
+ * @param {object} field The field definition
1958
+ * @return {boolean}
1959
+ */
1960
+ isGroupArrayField(field) {
1961
+ return !!(field && field.groupPicker && field.groupPicker.multiple)
1962
+ },
1963
+
1964
+ /**
1965
+ * Whether a single-value field stores an id that maps to a label
1966
+ * fetched elsewhere: an object reference, a user or a group.
1967
+ *
1968
+ * @param {object} field The field definition
1969
+ * @return {boolean}
1970
+ */
1971
+ isIdPickerField(field) {
1972
+ return this.isReferenceField(field) || this.isUserField(field) || this.isGroupField(field)
1973
+ },
1974
+
1975
+ /**
1976
+ * The multi-value sibling of `isIdPickerField`.
1977
+ *
1978
+ * @param {object} field The field definition
1979
+ * @return {boolean}
1980
+ */
1981
+ isIdPickerArrayField(field) {
1982
+ return this.isReferenceArrayField(field) || this.isUserArrayField(field) || this.isGroupArrayField(field)
1983
+ },
1984
+
1985
+ /**
1986
+ * Turn a language or time zone field into a searchable select over
1987
+ * a fixed code list: labels in the user's language, the code stored.
1988
+ * Other fields pass through unchanged.
1989
+ *
1990
+ * @spec openspec/changes/form-pickers-from-schema/specs/schema-utilities/spec.md
1991
+ * @param {object} field A resolved field descriptor.
1992
+ * @return {object} The field, as a `select` when it is a code picker.
1993
+ */
1994
+ applyCodePicker(field) {
1995
+ if (!field || (field.widget !== 'language' && field.widget !== 'timezone')) {
1996
+ return field
1997
+ }
1998
+ const list = field.widget === 'language' ? languageOptions() : timezoneOptions();
1999
+ return { ...field, widget: 'select', codePicker: field.widget, enum: list.codes, enumLabels: list.labels }
2000
+ },
2001
+
1901
2002
  /**
1902
2003
  * Check if a field has an async enum (function instead of static array).
1903
2004
  * Object references (`$ref`) and Nextcloud user references are treated
@@ -1907,7 +2008,7 @@ var script = {
1907
2008
  * @return {boolean}
1908
2009
  */
1909
2010
  isAsyncEnum(field) {
1910
- return typeof field.enum === 'function' || this.isReferenceField(field) || this.isUserField(field)
2011
+ return typeof field.enum === 'function' || this.isIdPickerField(field)
1911
2012
  },
1912
2013
 
1913
2014
  /**
@@ -1919,7 +2020,7 @@ var script = {
1919
2020
  * @return {boolean}
1920
2021
  */
1921
2022
  isAsyncItemsEnum(field) {
1922
- return !!(field.items && typeof field.items.enum === 'function') || this.isReferenceArrayField(field) || this.isUserArrayField(field)
2023
+ return !!(field.items && typeof field.items.enum === 'function') || this.isIdPickerArrayField(field)
1923
2024
  },
1924
2025
 
1925
2026
  /**
@@ -1986,12 +2087,119 @@ var script = {
1986
2087
  * @return {void}
1987
2088
  */
1988
2089
  onReferenceSelected(field, value) {
2090
+ if (Array.isArray(value)) {
2091
+ this.updateField(field.key, value);
2092
+ return
2093
+ }
1989
2094
  this.updateField(field.key, value || null);
1990
2095
  if (value) {
1991
2096
  this.applyTemplateFill(field, value);
1992
2097
  }
1993
2098
  },
1994
2099
 
2100
+ /**
2101
+ * The value CnResourceSelect binds for a select-or-create reference:
2102
+ * an array of ids for a multi-value reference, else the id string.
2103
+ *
2104
+ * @param {object} field The reference field descriptor.
2105
+ * @return {string|string[]} The bound value.
2106
+ */
2107
+ referenceModelValue(field) {
2108
+ const value = this.formData[field.key];
2109
+ if (field.reference && field.reference.multiple) {
2110
+ return Array.isArray(value) ? value.map((v) => String(v)) : []
2111
+ }
2112
+ return value !== null && value !== undefined ? String(value) : ''
2113
+ },
2114
+
2115
+ /**
2116
+ * "Create" on a select-or-create reference: open the referenced
2117
+ * schema's own form, prefilled with the typed term, on top of this one.
2118
+ *
2119
+ * Resolves with the created object once that form is saved, or with
2120
+ * null when it is closed without saving. CnResourceSelect then selects
2121
+ * the new object. When the referenced schema cannot be loaded, the
2122
+ * term alone is saved as before, so the field never dead-ends.
2123
+ *
2124
+ * @spec openspec/changes/form-pickers-from-schema/specs/schema-utilities/spec.md
2125
+ * @param {object} field The reference field descriptor.
2126
+ * @param {string} term The typed search term.
2127
+ * @return {Promise<object|null>} The created object, or null.
2128
+ */
2129
+ async openNestedCreate(field, term) {
2130
+ const store = this.getObjectStore();
2131
+ const register = this.referenceRegister(field);
2132
+ if (!store || !register || !field.reference) {
2133
+ return null
2134
+ }
2135
+ const schemaRef = String(field.reference.schema);
2136
+ const slug = store.createObjectTypeSlug(register, schemaRef);
2137
+ if (!store.objectTypeRegistry[slug]) {
2138
+ store.registerObjectType(slug, schemaRef, register);
2139
+ }
2140
+ const labelField = this.referenceLabelField(field);
2141
+ let schema = null;
2142
+ try {
2143
+ schema = await store.fetchSchema(slug);
2144
+ } catch {
2145
+ schema = null;
2146
+ }
2147
+ if (!schema || !schema.properties) {
2148
+ return store.saveObject(slug, { [labelField]: term })
2149
+ }
2150
+ const initialData = {};
2151
+ if (term && schema.properties[labelField]) {
2152
+ initialData[labelField] = term;
2153
+ }
2154
+ return new Promise((resolve) => {
2155
+ this.nestedCreate = { field, schema, register, slug, initialData, resolve };
2156
+ })
2157
+ },
2158
+
2159
+ /**
2160
+ * The nested create form was confirmed: save the object, hand it to
2161
+ * the waiting picker and close the nested form. A refused save keeps
2162
+ * the nested form open with the server's message.
2163
+ *
2164
+ * @spec openspec/changes/form-pickers-from-schema/specs/schema-utilities/spec.md
2165
+ * @param {object} formData The nested form's values.
2166
+ * @return {Promise<void>}
2167
+ */
2168
+ async onNestedCreateConfirm(formData) {
2169
+ const pending = this.nestedCreate;
2170
+ if (!pending) {
2171
+ return
2172
+ }
2173
+ const store = this.getObjectStore();
2174
+ const created = store ? await store.saveObject(pending.slug, formData) : null;
2175
+ if (!created) {
2176
+ const error = store && store.errors ? store.errors[pending.slug] : null;
2177
+ const message = (error && error.message)
2178
+ || translate('nextcloud-vue', 'The item could not be created.');
2179
+ const dialog = this.$refs.nestedCreateDialog;
2180
+ if (dialog && typeof dialog.setValidationErrors === 'function') {
2181
+ dialog.setValidationErrors({}, message);
2182
+ }
2183
+ return
2184
+ }
2185
+ this.nestedCreate = null;
2186
+ pending.resolve(created);
2187
+ },
2188
+
2189
+ /**
2190
+ * The nested create form was closed without saving: leave the field
2191
+ * as it was.
2192
+ *
2193
+ * @return {void}
2194
+ */
2195
+ onNestedCreateClose() {
2196
+ const pending = this.nestedCreate;
2197
+ this.nestedCreate = null;
2198
+ if (pending) {
2199
+ pending.resolve(null);
2200
+ }
2201
+ },
2202
+
1995
2203
  /**
1996
2204
  * Copy template values off a selected/created reference object into the
1997
2205
  * form, per the field's `fillFrom` map (`{ formKey: sourceKey }`). When
@@ -2438,9 +2646,14 @@ var script = {
2438
2646
  }
2439
2647
  this.asyncState = newState;
2440
2648
 
2441
- // Trigger initial load for each async field
2649
+ // Trigger initial load for each async field. A select-or-create
2650
+ // reference loads its own options (CnResourceSelect), so it is
2651
+ // skipped here rather than fetched twice.
2442
2652
  this.$nextTick(() => {
2443
2653
  for (const field of fields) {
2654
+ if (field.allowCreate && field.reference) {
2655
+ continue
2656
+ }
2444
2657
  if (this.isAsyncEnum(field) || this.isAsyncItemsEnum(field)) {
2445
2658
  this.loadAsyncOptions(field, '');
2446
2659
  }
@@ -2481,6 +2694,18 @@ var script = {
2481
2694
  if (Object.keys(labels).length > 0) {
2482
2695
  this.referenceLabels = { ...this.referenceLabels, ...labels };
2483
2696
  }
2697
+ } else if (this.isGroupField(field) || this.isGroupArrayField(field)) {
2698
+ // Nextcloud group: search groups, store the gid, show the name.
2699
+ results = await searchNextcloudGroups(query);
2700
+ const labels = {};
2701
+ for (const opt of results) {
2702
+ if (opt && opt.id) {
2703
+ labels[opt.id] = opt.label || String(opt.id);
2704
+ }
2705
+ }
2706
+ if (Object.keys(labels).length > 0) {
2707
+ this.referenceLabels = { ...this.referenceLabels, ...labels };
2708
+ }
2484
2709
  } else {
2485
2710
  const enumFn = typeof field.enum === 'function' ? field.enum : field.items.enum;
2486
2711
  results = await enumFn(query);
@@ -2539,7 +2764,7 @@ var script = {
2539
2764
  * @return {object|null}
2540
2765
  */
2541
2766
  getEffectiveSelectedOption(field) {
2542
- if (this.isReferenceField(field) || this.isUserField(field)) {
2767
+ if (this.isIdPickerField(field)) {
2543
2768
  // Reference / user fields store the UUID/UID — resolve it to a
2544
2769
  // display option `{ id, label }` (label from the resolved-labels
2545
2770
  // cache, falling back to the id until it loads).
@@ -2566,7 +2791,7 @@ var script = {
2566
2791
  * @param {object|null} option The selected option
2567
2792
  */
2568
2793
  onEffectiveSelectChange(field, option) {
2569
- if (this.isReferenceField(field) || this.isUserField(field)) {
2794
+ if (this.isIdPickerField(field)) {
2570
2795
  // Reference / user fields store the chosen id (UUID / UID),
2571
2796
  // not the full option. Cache its label so the selection displays.
2572
2797
  if (option && option.id) {
@@ -2575,7 +2800,7 @@ var script = {
2575
2800
  this.updateField(field.key, option ? option.id : null);
2576
2801
  // Reference options are label-only ({id,label}) — pass the id so
2577
2802
  // template pre-fill fetches the full object.
2578
- if (option && option.id) {
2803
+ if (option && option.id && this.isReferenceField(field)) {
2579
2804
  this.applyTemplateFill(field, String(option.id));
2580
2805
  }
2581
2806
  } else if (this.isAsyncEnum(field)) {
@@ -2607,7 +2832,7 @@ var script = {
2607
2832
  * @return {Array}
2608
2833
  */
2609
2834
  getEffectiveSelectedArrayOptions(field) {
2610
- if (this.isReferenceArrayField(field) || this.isUserArrayField(field)) {
2835
+ if (this.isIdPickerArrayField(field)) {
2611
2836
  // Reference / user arrays store an array of ids (UUIDs / UIDs) —
2612
2837
  // resolve each to a display option `{ id, label }`.
2613
2838
  const uuids = this.formData[field.key];
@@ -2630,7 +2855,7 @@ var script = {
2630
2855
  * @param {Array} options The selected options
2631
2856
  */
2632
2857
  onEffectiveMultiSelectChange(field, options) {
2633
- if (this.isReferenceArrayField(field) || this.isUserArrayField(field)) {
2858
+ if (this.isIdPickerArrayField(field)) {
2634
2859
  // Reference / user arrays store an array of ids (UUIDs / UIDs).
2635
2860
  // Cache labels so the chips still display the human names.
2636
2861
  const list = options || [];