@conduction/nextcloud-vue 2.84.0 → 2.85.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 (166) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +131 -3
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +54 -18
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  6. package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
  7. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +232 -4
  8. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  9. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +95 -51
  10. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  11. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
  12. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +114 -0
  13. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  14. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +2 -2
  15. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  16. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  17. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +45 -0
  18. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  19. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +22 -12
  20. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  21. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +23 -4
  22. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  24. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  25. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +3 -1
  26. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  28. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  29. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +18 -5
  30. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  31. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  32. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +548 -77
  33. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +191 -82
  35. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +11 -2
  37. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  38. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  39. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +95 -0
  40. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  41. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +24 -2
  42. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  43. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  44. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +70 -2
  45. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  46. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  47. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
  48. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +67 -5
  49. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
  50. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +181 -124
  51. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
  52. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
  53. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +5 -1
  54. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
  55. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
  56. package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
  57. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +134 -10
  58. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -2
  60. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  62. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +6 -3
  63. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  64. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +1 -1
  66. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +140 -7
  67. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -1
  68. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -1
  69. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
  70. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +37 -9
  71. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
  72. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +6 -5
  73. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
  74. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +1 -1
  75. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +2 -1
  76. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
  77. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
  78. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js +53 -1
  79. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js.map +1 -1
  80. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js +3 -2
  81. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js.map +1 -1
  82. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  83. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +135 -0
  84. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  85. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  86. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
  87. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +19 -0
  88. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  89. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +37 -19
  90. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  91. package/dist/esm/composables/useListView.js +21 -0
  92. package/dist/esm/composables/useListView.js.map +1 -1
  93. package/dist/esm/index.js +1 -0
  94. package/dist/esm/index.js.map +1 -1
  95. package/dist/esm/l10n/en.json.js +23 -1
  96. package/dist/esm/l10n/en.json.js.map +1 -1
  97. package/dist/esm/l10n/nl.json.js +41 -1
  98. package/dist/esm/l10n/nl.json.js.map +1 -1
  99. package/dist/esm/library-style.js +1 -1
  100. package/dist/esm/nextcloud-vue.css +742 -397
  101. package/dist/esm/store/useObjectStore.js +16 -2
  102. package/dist/esm/store/useObjectStore.js.map +1 -1
  103. package/dist/esm/utils/boardColumns.js +8 -1
  104. package/dist/esm/utils/boardColumns.js.map +1 -1
  105. package/dist/esm/utils/boardReferenceColumns.js +278 -0
  106. package/dist/esm/utils/boardReferenceColumns.js.map +1 -0
  107. package/dist/esm/utils/buildManifest.js +4 -1
  108. package/dist/esm/utils/buildManifest.js.map +1 -1
  109. package/dist/esm/utils/gridStack.js +40 -1
  110. package/dist/esm/utils/gridStack.js.map +1 -1
  111. package/dist/esm/utils/humaniseKey.js +41 -0
  112. package/dist/esm/utils/humaniseKey.js.map +1 -0
  113. package/dist/esm/utils/notificationRuleLabel.js +6 -23
  114. package/dist/esm/utils/notificationRuleLabel.js.map +1 -1
  115. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  116. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  117. package/dist/esm/utils/widgetDispatch.js +8 -2
  118. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  119. package/dist/nextcloud-vue.cjs.js +15075 -12590
  120. package/dist/nextcloud-vue.cjs.js.map +2 -2
  121. package/dist/nextcloud-vue.css +742 -397
  122. package/l10n/en.json +23 -1
  123. package/l10n/nl.json +41 -1
  124. package/package.json +1 -1
  125. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +160 -8
  126. package/src/components/CnBoardView/CnBoardView.vue +262 -6
  127. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +116 -2
  128. package/src/components/CnDashboardPage/CnDashboardPage.vue +57 -2
  129. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  130. package/src/components/CnDetailPage/CnDetailPage.vue +23 -4
  131. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +3 -1
  132. package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -5
  133. package/src/components/CnIndexPage/CnIndexPage.vue +586 -25
  134. package/src/components/CnIndexPage/useSelfFetchList.js +10 -1
  135. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +123 -3
  136. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +70 -2
  137. package/src/components/CnRegisterMapping/CnRegisterMapping.md +5 -0
  138. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +231 -96
  139. package/src/components/CnSettingsPage/CnSettingsPage.vue +5 -1
  140. package/src/components/CnStatWidget/CnStatWidget.vue +136 -11
  141. package/src/components/CnTabsWidget/CnTabsWidget.vue +6 -3
  142. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +140 -7
  143. package/src/components/CnVersionInfoCard/CnVersionInfoCard.md +2 -1
  144. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +42 -13
  145. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +2 -1
  146. package/src/components/CnWidgetCardGrid/CnWidgetCardGrid.vue +55 -2
  147. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +135 -0
  148. package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +10 -0
  149. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +44 -1
  150. package/src/composables/useListView.js +21 -0
  151. package/src/css/board.css +65 -4
  152. package/src/css/dialog.css +4 -2
  153. package/src/css/index-page.css +49 -0
  154. package/src/css/kpi-card.css +13 -0
  155. package/src/css/look-board.css +96 -7
  156. package/src/index.js +2 -0
  157. package/src/schemas/app-manifest-v2.schema.json +115 -5
  158. package/src/store/useObjectStore.js +16 -2
  159. package/src/utils/boardColumns.js +8 -1
  160. package/src/utils/boardReferenceColumns.js +274 -0
  161. package/src/utils/buildManifest.js +4 -1
  162. package/src/utils/gridStack.js +39 -0
  163. package/src/utils/humaniseKey.js +38 -0
  164. package/src/utils/notificationRuleLabel.js +5 -23
  165. package/src/utils/validateManifestV2.compiled.js +3 -3
  166. package/src/utils/widgetDispatch.js +8 -2
@@ -26,6 +26,15 @@ function resolveInitialQuickFilterIndex(quickFilters) {
26
26
  * @param {Array<object>} filterMaps Already route-resolved filter maps.
27
27
  * @return {object} The merged filter (scalar when a key has one value, array when many).
28
28
  */
29
+ /**
30
+ * Rows one board fetch asks for. A board shows every matching row at once,
31
+ * so it loads far more than a table page (OpenRegister serves up to 1000);
32
+ * past this the board offers "Load more".
33
+ *
34
+ * @type {number}
35
+ */
36
+ export const BOARD_PAGE_SIZE = 500
37
+
29
38
  /**
30
39
  * The sort a quick filter sets while it is active (`sort: { key, order }`, or a
31
40
  * list of those for a multi-column order), as sort keys, or null when it sets
@@ -236,7 +245,7 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
236
245
  defaultSortKeys: initialSortKeys,
237
246
  defaultActiveFilters: initialActiveFilters,
238
247
  defaultSearchTerm: initialSearchTerm,
239
- defaultPageSize: (props.pagination && props.pagination.limit) || undefined,
248
+ defaultPageSize: props.viewMode === 'board' ? BOARD_PAGE_SIZE : ((props.pagination && props.pagination.limit) || undefined),
240
249
  defaultVisibleColumns: configuredColumnKeys.length ? configuredColumnKeys : null,
241
250
  // `pages[].config.extend` → OpenRegister's repeated `_extend[]`. A
242
251
  // getter so a reactive change re-scopes the next fetch, matching how
@@ -339,16 +339,30 @@
339
339
  {{ stringifyCell(v) }}
340
340
  </span>
341
341
  </div>
342
- <!-- Single plain OBJECT → key: value definition list. -->
342
+ <!-- Single plain OBJECT → key: value definition list; a
343
+ nested plain object (e.g. a per-language map) gets
344
+ its own list one level down. -->
343
345
  <dl
344
346
  v-else-if="fieldValueKind(field) === 'object'"
345
347
  class="cn-object-data-widget__deflist">
346
348
  <template v-for="(pair, pi) in objectEntries(rawOf(field))" :key="pi">
347
349
  <dt>
348
- {{ pair[0] }}
350
+ {{ nestedLabel(field, [pair[0]]) }}
349
351
  </dt>
350
352
  <dd>
351
- {{ stringifyCell(pair[1]) }}
353
+ <dl
354
+ v-if="isPlainObject(pair[1])"
355
+ class="cn-object-data-widget__deflist cn-object-data-widget__deflist--nested">
356
+ <template v-for="(sub, si) in objectEntries(pair[1])" :key="si">
357
+ <dt>
358
+ {{ nestedLabel(field, [pair[0], sub[0]]) }}
359
+ </dt>
360
+ <dd>
361
+ <span class="cn-object-data-widget__deflist-text">{{ stringifyNested(sub[1]) }}</span>
362
+ </dd>
363
+ </template>
364
+ </dl>
365
+ <span v-else class="cn-object-data-widget__deflist-text">{{ stringifyNested(pair[1]) }}</span>
352
366
  </dd>
353
367
  </template>
354
368
  </dl>
@@ -413,6 +427,7 @@ import CnFormDialog from '../CnFormDialog/CnFormDialog.vue'
413
427
  import { normalizeLook } from '../../composables/useLook.js'
414
428
  import { useObjectStore } from '../../store/index.js'
415
429
  import { groupDisplayName, loadGroupDisplayName, searchNextcloudGroups } from '../../utils/groupAutocomplete.js'
430
+ import { humaniseKey } from '../../utils/humaniseKey.js'
416
431
  import { PANEL_ACTION_SINK } from '../../utils/panelActions.js'
417
432
  import { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
418
433
  import { fieldsFromSchema, formatValue } from '../../utils/schema.js'
@@ -423,6 +438,9 @@ import { CnIcon } from '../CnIcon/index.js'
423
438
  import { CnObjectMetadataModal } from '../CnObjectMetadataModal/index.js'
424
439
  import { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'
425
440
 
441
+ // Widgets whose value the inline text fallback would corrupt.
442
+ const STRUCTURED_WIDGETS = ['json', 'code', 'file', 'sub-objects', 'child-records']
443
+
426
444
  /**
427
445
  * CnObjectDataWidget — Schema-driven editable data grid widget.
428
446
  *
@@ -1363,6 +1381,59 @@ export default {
1363
1381
  return (raw && typeof raw === 'object') ? Object.entries(raw) : []
1364
1382
  },
1365
1383
 
1384
+ /**
1385
+ * Whether a value is a plain object (not an array, not null).
1386
+ *
1387
+ * @param {unknown} v The value.
1388
+ * @return {boolean} True for a plain object.
1389
+ */
1390
+ isPlainObject(v) {
1391
+ return v !== null && typeof v === 'object' && !Array.isArray(v)
1392
+ },
1393
+
1394
+ /**
1395
+ * Label for a key inside an object value: the sub-schema's `title`
1396
+ * (translated) when the schema describes it, else the key, made
1397
+ * readable when it is identifier-shaped (`shortDescription` → "Short
1398
+ * description"; `nl` stays `nl`).
1399
+ *
1400
+ * @param {object} field Resolved field definition.
1401
+ * @param {string[]} path Keys from the field's value down to this entry.
1402
+ * @return {string} The label.
1403
+ * @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-nested-object-reads-as-a-list-per-key
1404
+ */
1405
+ nestedLabel(field, path) {
1406
+ let prop = ((this.schema && this.schema.properties) || {})[field.key]
1407
+ for (const key of path) {
1408
+ if (!this.isPlainObject(prop)) {
1409
+ prop = null
1410
+ break
1411
+ }
1412
+ prop = (prop.properties && prop.properties[key])
1413
+ || (this.isPlainObject(prop.additionalProperties) ? prop.additionalProperties : null)
1414
+ }
1415
+ if (prop && typeof prop.title === 'string' && prop.title) {
1416
+ return this.cnTranslate(prop.title)
1417
+ }
1418
+ const key = String(path[path.length - 1])
1419
+ return /[A-Z_-]/.test(key) ? humaniseKey(key) : key
1420
+ },
1421
+
1422
+ /**
1423
+ * Stringify a value inside a definition list: a list of scalars reads
1424
+ * as a comma-separated line, anything else as `stringifyCell`.
1425
+ *
1426
+ * @param {unknown} v The value.
1427
+ * @return {string} The display string.
1428
+ * @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-nested-object-reads-as-a-list-per-key
1429
+ */
1430
+ stringifyNested(v) {
1431
+ if (Array.isArray(v) && v.length && v.every((item) => item === null || typeof item !== 'object')) {
1432
+ return v.map((item) => this.stringifyCell(item)).join(', ')
1433
+ }
1434
+ return this.stringifyCell(v)
1435
+ },
1436
+
1366
1437
  /**
1367
1438
  * Stringify a scalar cell value; a nested object/array collapses to
1368
1439
  * compact JSON (never "[object Object]").
@@ -1690,15 +1761,53 @@ export default {
1690
1761
  }))
1691
1762
  },
1692
1763
 
1764
+ /**
1765
+ * Whether a field's value cannot be edited inline: a structured widget
1766
+ * (json, code, file, sub-objects, child-records), or a plain-object /
1767
+ * list-of-objects value that is not a relation. Scalar widgets without
1768
+ * a dedicated editor (duration, language, …) keep the text fallback.
1769
+ *
1770
+ * @param {object} field Resolved field definition.
1771
+ * @return {boolean} True when only the form dialog can edit it.
1772
+ * @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-structured-value-is-never-edited-inline
1773
+ */
1774
+ hasNoInlineEditor(field) {
1775
+ if (STRUCTURED_WIDGETS.includes(field.widget)) {
1776
+ return true
1777
+ }
1778
+ const prop = ((this.schema && this.schema.properties) || {})[field.key]
1779
+ if (this.isRelationField(prop)) {
1780
+ return false
1781
+ }
1782
+ // A list of objects by schema, also while empty (it resolves to `tags`).
1783
+ if (prop && prop.type === 'array' && prop.items && prop.items.type === 'object') {
1784
+ return true
1785
+ }
1786
+ // Classify the raw value itself: fieldValueKind reports a formatter
1787
+ // field as 'scalar' whatever it holds.
1788
+ const raw = this.rawOf(field)
1789
+ if (Array.isArray(raw)) {
1790
+ return raw.some((v) => this.isPlainObject(v))
1791
+ }
1792
+ return this.isPlainObject(raw)
1793
+ },
1794
+
1693
1795
  /**
1694
1796
  * Check if a field is editable.
1695
1797
  *
1696
1798
  * @param {object} field - Resolved field definition from resolvedFields
1799
+ * @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-structured-value-is-never-edited-inline
1697
1800
  */
1698
1801
  isEditable(field) {
1699
1802
  if (!this.editable) {
1700
1803
  return false
1701
1804
  }
1805
+ // No inline editor exists for structured values: the text fallback
1806
+ // would commit "[object Object]". Those edit through the form dialog,
1807
+ // unless the consumer supplies its own `field-<key>` editor slot.
1808
+ if (!this.$slots['field-' + field.key] && this.hasNoInlineEditor(field)) {
1809
+ return false
1810
+ }
1702
1811
  // Per-field override takes priority
1703
1812
  const override = this.resolvedOverrides[field.key]
1704
1813
  if (override && typeof override.editable === 'boolean') {
@@ -2439,6 +2548,17 @@ export default {
2439
2548
  overflow-wrap: anywhere;
2440
2549
  }
2441
2550
 
2551
+ /* A long description keeps its own paragraphs. Set on an inline span with no
2552
+ surrounding template whitespace, so only the value's own line breaks show. */
2553
+ .cn-object-data-widget__deflist-text {
2554
+ white-space: pre-wrap;
2555
+ }
2556
+
2557
+ .cn-object-data-widget__deflist--nested {
2558
+ padding-inline-start: 8px;
2559
+ border-inline-start: 2px solid var(--color-border);
2560
+ }
2561
+
2442
2562
  /* Responsive: collapse to single column on narrow widths */
2443
2563
  @media (max-width: 600px) {
2444
2564
  .cn-object-data-widget__grid {
@@ -141,9 +141,11 @@
141
141
  import { subscribe, unsubscribe } from '@nextcloud/event-bus'
142
142
  import { translate as t } from '@nextcloud/l10n'
143
143
  import { NcLoadingIcon } from '@nextcloud/vue'
144
+ import { inject, ref } from 'vue'
144
145
  import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
145
146
  import CnIcon from '../CnIcon/CnIcon.vue'
146
147
  import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
148
+ import { useEndpointSource } from '../../composables/useEndpointSource.js'
147
149
  import { actionLink, dispatchAction, interpolateActionTarget } from '../../utils/actionsDispatcher.js'
148
150
  import { isBadgeVariant } from '../../utils/badgeVariants.js'
149
151
  import { ageInDays } from '../../utils/boardDate.js'
@@ -180,7 +182,8 @@ function unwrap(value) {
180
182
  * objects filtered on a reference to this record. The source keys are those of
181
183
  * `object-list` (`register`, `schema`, `filter` with the `@objectId` /
182
184
  * `@object.<field>` / `@workspace.<key>` tokens, `sort`, `limit`, `extend`,
183
- * `rowRoute`, `pageControls`); `avatar` puts a circle with the initials of a name
185
+ * `rowRoute`, `pageControls`), or `endpointSource` (an app endpoint whose
186
+ * payload, after `responsePath`, is the array of rows, as on the object table); `avatar` puts a circle with the initials of a name
184
187
  * template before the title; what differs is the drawing: `presentation` is `rows`, `feed`
185
188
  * or `inline`, and each line is a template (`"{identifier} · {caseTypeName}"`,
186
189
  * `"Termijn {deadline|date}"`).
@@ -213,7 +216,9 @@ export default {
213
216
  * The list config: `{ register, schema, filter?, sort?, limit?, extend?,
214
217
  * presentation?, title?, meta?, text?, label?, pill?, trailing?, icon?,
215
218
  * rowRoute?, rowIdField?, headerLink?, headerNote?, emptyText?, errorText?,
216
- * valueLabels? }`. `valueLabels` maps a field's stored values to the labels
219
+ * valueLabels?, endpointSource? }`. `endpointSource` (`{ url, method?,
220
+ * params?, responsePath? }`) reads the rows from an app endpoint instead
221
+ * of `register` / `schema`. `valueLabels` maps a field's stored values to the labels
217
222
  * the templates show.
218
223
  *
219
224
  * @type {object}
@@ -238,6 +243,28 @@ export default {
238
243
 
239
244
  emits: ['header'],
240
245
 
246
+ setup(props) {
247
+ const objectCtxRaw = inject('cnObjectContext', null)
248
+ const workspaceRaw = inject('cnWorkspaceContext', null)
249
+ const appConfigRaw = inject('cnAppConfig', ref({}))
250
+ const { data, loading, error } = useEndpointSource(
251
+ () => props.content?.endpointSource || null,
252
+ {
253
+ ctx: () => {
254
+ const injected = unwrap(objectCtxRaw) || {}
255
+ return {
256
+ ...injected,
257
+ objectId: props.objectId ?? injected.objectId ?? props.objectData?.id ?? null,
258
+ object: props.objectData || injected.object || null,
259
+ workspace: unwrap(workspaceRaw) || {},
260
+ config: unwrapAppConfig(appConfigRaw) || {},
261
+ }
262
+ },
263
+ },
264
+ )
265
+ return { epData: data, epLoading: loading, epError: error }
266
+ },
267
+
241
268
  data() {
242
269
  return {
243
270
  rows: [],
@@ -294,6 +321,22 @@ export default {
294
321
  return { config: unwrapAppConfig(this.cnAppConfig) }
295
322
  },
296
323
 
324
+ /**
325
+ * Whether the rows come from `endpointSource` rather than OpenRegister.
326
+ *
327
+ * @return {boolean}
328
+ * @spec openspec/changes/endpoint-source-card-grid-record-list/specs/dashboard-page/spec.md#requirement-a-record-list-and-a-card-grid-can-read-an-endpoint
329
+ */
330
+ endpointActive() {
331
+ const source = this.content?.endpointSource
332
+ return Boolean(source && typeof source === 'object' && source.url)
333
+ },
334
+
335
+ /** @return {string} The endpoint read's state, to copy into the list when it changes. */
336
+ endpointKey() {
337
+ return this.endpointActive ? JSON.stringify([this.epData, this.epLoading, this.epError]) : ''
338
+ },
339
+
297
340
  /** @return {number} The fetch cap. */
298
341
  limit() {
299
342
  const value = Number(this.content?.limit)
@@ -398,6 +441,10 @@ export default {
398
441
  requestKey() {
399
442
  this.fetchRows()
400
443
  },
444
+
445
+ endpointKey() {
446
+ this.fetchRows()
447
+ },
401
448
  },
402
449
 
403
450
  mounted() {
@@ -854,6 +901,23 @@ export default {
854
901
  return { rows, total: typeof head.total === 'number' ? head.total : rows.length }
855
902
  },
856
903
 
904
+ /**
905
+ * Copy the endpoint read into the list: its payload must be an array of
906
+ * rows (anything else is an empty list), and a failed read is an error.
907
+ *
908
+ * @spec openspec/changes/endpoint-source-card-grid-record-list/specs/dashboard-page/spec.md#requirement-a-record-list-and-a-card-grid-can-read-an-endpoint
909
+ */
910
+ takeEndpointRows() {
911
+ this.rows = Array.isArray(this.epData) ? this.epData : []
912
+ this.total = this.rows.length
913
+ this.loading = this.epLoading === true
914
+ this.error = Boolean(this.epError)
915
+ this.lenses = {}
916
+ if (!this.loading) {
917
+ this.emitHeader()
918
+ }
919
+ },
920
+
857
921
  /**
858
922
  * Fetch the list. Sends nothing while a required token is unresolved
859
923
  * or the source is incomplete.
@@ -862,6 +926,10 @@ export default {
862
926
  */
863
927
  async fetchRows() {
864
928
  const c = this.content || {}
929
+ if (this.endpointActive) {
930
+ this.takeEndpointRows()
931
+ return
932
+ }
865
933
  if (!c.register || !c.schema || this.waiting) {
866
934
  this.rows = []
867
935
  this.total = 0
@@ -111,6 +111,10 @@ export default {
111
111
  </script>
112
112
  ```
113
113
 
114
+ ## Several groups
115
+
116
+ With more than one group each group starts closed: its heading is a button that shows the group name and how many of its types are configured ("2/8 configured"), and opens the register select and type list on a click or Enter. A page with several registers is a short card until the administrator opens the group they need.
117
+
114
118
  ## Additional props
115
119
 
116
120
  | Prop | Type | Default | Description |
@@ -122,5 +126,6 @@ export default {
122
126
  | `reimporting` | Boolean | `false` | Disables the reimport button and shows a spinner while reimport is in progress |
123
127
  | `saveButtonText` | String | `'Save configuration'` | Label for the save button |
124
128
  | `reimportButtonText` | String | `'Re-import configuration'` | Label for the reimport button |
129
+ | `collapseGroups` | Boolean | `null` | Draw each group as a heading button that opens and closes it, with its configured count shown while closed; the groups start closed. `null` does this when there is more than one group, `true` or `false` forces it |
125
130
  | `autoMatch` | Boolean | `true` | Automatically match schema titles to type slugs when a register is selected |
126
131
  | `labels` | Object | *(see below)* | UI label overrides for all user-visible strings (register, schema, notConfigured, noSchemas, selectRegister, selectSchema, allConfigured, partiallyConfigured) |