@conduction/nextcloud-vue 2.111.0 → 2.113.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 (153) hide show
  1. package/dist/esm/components/CnActionButtons/CnActionButtons.vue.js +1 -1
  2. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js +4 -6
  3. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
  5. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  6. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +3 -2
  7. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  8. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +4 -2
  9. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  10. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
  11. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +3 -2
  12. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
  13. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +2 -1
  14. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
  15. package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
  16. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +54 -5
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +16 -2
  19. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  20. package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
  21. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +10 -0
  22. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  23. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +2 -1
  24. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  25. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  26. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +22 -2
  27. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  28. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +17 -3
  29. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  30. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  31. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  32. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  33. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  34. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  35. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  36. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  37. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +18 -3
  38. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  39. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
  40. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  41. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +52 -13
  42. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  43. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +31 -3
  44. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  45. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  46. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +48 -11
  47. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  48. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +160 -97
  49. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
  51. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  52. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +2 -2
  53. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  54. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +222 -15
  55. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  56. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +24 -7
  57. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  58. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js +1 -1
  59. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js +21 -1
  60. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js.map +1 -1
  61. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js.map +1 -1
  62. package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
  63. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +36 -2
  64. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  65. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +44 -5
  66. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  67. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +1 -0
  68. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js.map +1 -1
  69. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +22 -2
  70. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -1
  71. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js +36 -16
  72. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -1
  73. package/dist/esm/components/CnReportsPage/CnReportsPage.vue.js +1 -1
  74. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +29 -12
  75. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
  76. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +3 -1
  77. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
  78. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js +1 -1
  79. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js.map +1 -1
  80. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js.map +1 -1
  81. package/dist/esm/composables/useRefLabels.js +26 -6
  82. package/dist/esm/composables/useRefLabels.js.map +1 -1
  83. package/dist/esm/integrations/registry.js +4 -0
  84. package/dist/esm/integrations/registry.js.map +1 -1
  85. package/dist/esm/l10n/en.json.js +3 -1
  86. package/dist/esm/l10n/en.json.js.map +1 -1
  87. package/dist/esm/l10n/nl.json.js +3 -1
  88. package/dist/esm/l10n/nl.json.js.map +1 -1
  89. package/dist/esm/library-style.js +1 -1
  90. package/dist/esm/nextcloud-vue.css +680 -449
  91. package/dist/esm/store/plugins/liveUpdates.js +3 -1
  92. package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
  93. package/dist/esm/store/plugins/logs.js +2 -1
  94. package/dist/esm/store/plugins/logs.js.map +1 -1
  95. package/dist/esm/utils/actionLink.js +104 -1
  96. package/dist/esm/utils/actionLink.js.map +1 -1
  97. package/dist/esm/utils/detailActionModel.js +28 -1
  98. package/dist/esm/utils/detailActionModel.js.map +1 -1
  99. package/dist/esm/utils/fillTemplate.js +13 -5
  100. package/dist/esm/utils/fillTemplate.js.map +1 -1
  101. package/dist/esm/utils/headerFieldChips.js +21 -2
  102. package/dist/esm/utils/headerFieldChips.js.map +1 -1
  103. package/dist/esm/utils/mapList.js +26 -11
  104. package/dist/esm/utils/mapList.js.map +1 -1
  105. package/dist/esm/utils/savedViewMenu.js +12 -5
  106. package/dist/esm/utils/savedViewMenu.js.map +1 -1
  107. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  108. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  109. package/dist/nextcloud-vue.cjs.js +2800 -1933
  110. package/dist/nextcloud-vue.cjs.js.map +2 -2
  111. package/dist/nextcloud-vue.css +680 -449
  112. package/l10n/en.json +3 -1
  113. package/l10n/nl.json +3 -1
  114. package/package.json +1 -1
  115. package/src/components/CnActionButtons/CnActionButtons.vue +4 -6
  116. package/src/components/CnActionDialog/CnActionDialog.vue +32 -9
  117. package/src/components/CnAssignDialog/CnAssignDialog.vue +4 -2
  118. package/src/components/CnBoardView/CnBoardView.vue +58 -5
  119. package/src/components/CnCardGrid/CnCardGrid.md +1 -0
  120. package/src/components/CnCardGrid/CnCardGrid.vue +11 -0
  121. package/src/components/CnCellRenderer/CnCellRenderer.vue +27 -3
  122. package/src/components/CnChartWidget/CnChartWidget.vue +6 -0
  123. package/src/components/CnChoiceCards/CnChoiceCards.vue +0 -3
  124. package/src/components/CnDashboardPage/CnDashboardPage.vue +20 -5
  125. package/src/components/CnDetailPage/CnDetailPage.vue +62 -14
  126. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +100 -5
  127. package/src/components/CnFormDialog/CnFormDialog.vue +1 -1
  128. package/src/components/CnIndexPage/CnIndexPage.vue +233 -18
  129. package/src/components/CnNcDashboardList/CnNcDashboardList.vue +21 -1
  130. package/src/components/CnObjectCard/CnObjectCard.md +1 -0
  131. package/src/components/CnObjectCard/CnObjectCard.vue +51 -5
  132. package/src/components/CnPartyPicker/CnPartyPicker.vue +62 -16
  133. package/src/components/CnReportsPage/CnReportsPage.vue +31 -12
  134. package/src/components/CnSearchSelect/CnSearchSelect.vue +8 -0
  135. package/src/composables/useRefLabels.js +25 -5
  136. package/src/css/badge.css +15 -13
  137. package/src/css/board.css +0 -1
  138. package/src/css/dialog.css +53 -3
  139. package/src/css/index-page.css +26 -1
  140. package/src/css/look-board-detail.css +36 -15
  141. package/src/css/look-board-index.css +67 -15
  142. package/src/css/look-board.css +32 -18
  143. package/src/integrations/registry.js +4 -0
  144. package/src/schemas/app-manifest-v2.schema.json +61 -13
  145. package/src/store/plugins/liveUpdates.js +3 -1
  146. package/src/store/plugins/logs.js +2 -1
  147. package/src/utils/actionLink.js +103 -0
  148. package/src/utils/detailActionModel.js +27 -0
  149. package/src/utils/fillTemplate.js +13 -5
  150. package/src/utils/headerFieldChips.js +21 -2
  151. package/src/utils/mapList.js +26 -11
  152. package/src/utils/savedViewMenu.js +12 -5
  153. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -27,8 +27,8 @@ import { createRefLabelResolver } from '../../composables/useRefLabels.js';
27
27
  import { useSavedViewsApi } from '../../composables/useSavedViewsApi.js';
28
28
  import { METADATA_COLUMNS } from '../../constants/metadata.js';
29
29
  import { useObjectStore } from '../../store/useObjectStore.js';
30
- import { routeHref, followItemActionLink, resolveItemActionLink } from '../../utils/actionLink.js';
31
- import { resolveRegisteredHandler, buildOnSuccessRoute } from '../../utils/actionsDispatcher.js';
30
+ import { routeHref, headerActionAsLink, followItemActionLink, resolveItemActionLink } from '../../utils/actionLink.js';
31
+ import { resolveRegisteredHandler, isExternalActionTarget, buildOnSuccessRoute } from '../../utils/actionsDispatcher.js';
32
32
  import { columnFieldsOf, refIdOf, buildValueStages, buildReferenceStages, columnKeyOfRow, resolveDropValue, fullTargetOf, referenceTargetOf, fetchReferenceObjects } from '../../utils/boardReferenceColumns.js';
33
33
  import { isColumnSortable } from '../../utils/columnFilters.js';
34
34
  import { reportBindingProblems } from '../../utils/diagnostics.js';
@@ -443,6 +443,8 @@ var script = {
443
443
 
444
444
  inject: {
445
445
  cnCustomComponents: { default: () => ({}) },
446
+ /** The app's named formatters, provided by CnAppRoot, for the map list's pills and template tokens. */
447
+ cnFormatters: { default: () => ({}) },
446
448
  /** The permissions the user holds, provided by CnAppRoot; for gated page tabs. */
447
449
  cnPermissions: { default: () => [] },
448
450
  /** The app config an ancestor provides, for `@config.<key>` in quick-filter labels. */
@@ -2587,11 +2589,16 @@ var script = {
2587
2589
  * (unless the handler is the `'none'` keyword) emits
2588
2590
  * `@header-action({ action: id, id })`.
2589
2591
  *
2592
+ * An entry with `href` instead renders as a real link and is never
2593
+ * dispatched or emitted: an absolute `http(s)`, `mailto:`, `tel:` or
2594
+ * `//host/...` URL is used as written, an instance path (leading `/`) goes through
2595
+ * `generateUrl()`. `target: '_blank'` opens it in a new tab.
2596
+ *
2590
2597
  * Reserved ids (those used by built-ins on the bar — `refresh`,
2591
2598
  * `import`, `export`, `copy`, `delete`) are dropped from the
2592
2599
  * merged list with a `console.warn`.
2593
2600
  *
2594
- * @type {Array<{ id: string, label: string, icon?: string, handler?: string|(() => void), route?: string, disabled?: boolean }>}
2601
+ * @type {Array<{ id: string, label: string, icon?: string, handler?: string|(() => void), route?: string, href?: string, target?: ('_blank'|'_self'), disabled?: boolean }>}
2595
2602
  */
2596
2603
  headerActions: {
2597
2604
  type: Array,
@@ -2801,6 +2808,8 @@ var script = {
2801
2808
  boardPrevPageSize: this.viewMode === 'board' ? ((this.pagination && this.pagination.limit) || 20) : null,
2802
2809
  /** Resolved labels of reference columns: `{ [columnKey]: { [id]: label|null } }`. */
2803
2810
  refLabels: {},
2811
+ /** Colour and icon names of the records badge columns point at: `{ [columnKey]: { [id]: { color, icon } } }`. */
2812
+ refLooks: {},
2804
2813
  /** Count per tab-strip index, for the entries that asked for one; null = none. */
2805
2814
  tabCounts: null,
2806
2815
  // The counts of the `filterStrip` pills, by pill key ('all', 'v0', ...).
@@ -3042,7 +3051,7 @@ var script = {
3042
3051
  /**
3043
3052
  * The header actions that open a dialog: `type: "open-form"` (the
3044
3053
  * schema-driven create form, as the navigation's primary action opens
3045
- * it) or `type: "open-modal"`, without a `handler` of their own.
3054
+ * it) or `type: "open-modal"`, without a `handler` or `href` of their own.
3046
3055
  * Reserved ids are left out, as in `mergedHeaderActions`.
3047
3056
  *
3048
3057
  * @spec openspec/changes/index-header-open-form/specs/index-header/spec.md#requirement-an-index-header-action-can-open-the-create-form
@@ -3054,9 +3063,18 @@ var script = {
3054
3063
  && typeof entry.id === 'string'
3055
3064
  && !reserved.has(entry.id)
3056
3065
  && (entry.handler === undefined || entry.handler === null || entry.handler === '')
3066
+ && !entry.href
3057
3067
  && (entry.type === 'open-form' || entry.type === 'open-modal'))
3058
3068
  },
3059
3069
 
3070
+ /**
3071
+ * The header actions without reserved ids, each through
3072
+ * `resolveHeaderHandler` (a manifest `href` entry becomes a link with
3073
+ * `_dispatchSuppress`, so the page neither runs nor emits it).
3074
+ *
3075
+ * @spec openspec/changes/header-action-href/specs/header-action-href/spec.md#requirement-a-header-action-with-href-is-a-link-that-runs-and-emits-nothing
3076
+ * @return {Array<object>}
3077
+ */
3060
3078
  mergedHeaderActions() {
3061
3079
  const reserved = new Set(['refresh', 'import', 'export', 'copy', 'delete']);
3062
3080
  const merged = [];
@@ -4715,6 +4733,12 @@ var script = {
4715
4733
  */
4716
4734
  renderedColumns() {
4717
4735
  let cols = this.tableColumns;
4736
+ if (this.refLookSpecs.length > 0) {
4737
+ const lookKeys = new Set(this.refLookSpecs.map((spec) => spec.key));
4738
+ cols = cols.map((col) => (col && typeof col === 'object' && lookKeys.has(col.key)
4739
+ ? { ...col, widgetProps: { ...(col.widgetProps || {}), looks: this.refLooks[col.key] || {} } }
4740
+ : col));
4741
+ }
4718
4742
  if (this.refLabelSpecs.length > 0) {
4719
4743
  const byKey = new Map(this.refLabelSpecs.map((s) => [s.key, s]));
4720
4744
  cols = cols.map((col) => {
@@ -4785,6 +4809,108 @@ var script = {
4785
4809
  *
4786
4810
  * @return {Object<string, string[]>} Distinct ids per column key.
4787
4811
  */
4812
+ /**
4813
+ * The pills that take their tone and icon from the record they point at
4814
+ * (`resolve: { colorField?, iconField?, register?, schema? }`): badge
4815
+ * columns and the list view's leading and trailing cells
4816
+ * (`widgetProps.resolve`), the board card's pill (`board.card.resolve`)
4817
+ * and the record card's pills (`cardLayout` headPill, footerPill, chips).
4818
+ * Register and schema default to the field's reference in the schema.
4819
+ *
4820
+ * @return {Array<{key: string, register: string, schema: string, colorField: string, iconField: string}>} The specs.
4821
+ * @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-badge-cell-takes-its-tone-and-icon-from-the-referenced-record
4822
+ */
4823
+ refLookSpecs() {
4824
+ const list = this.listConfig || {};
4825
+ const listCells = [list.leading, ...(Array.isArray(list.trailing) ? list.trailing : [list.trailing])];
4826
+ const badgeSources = [...this.tableColumns, ...listCells]
4827
+ .filter((c) => c && typeof c === 'object' && c.widget === 'badge' && c.widgetProps)
4828
+ .map((c) => ({ key: c.key, resolve: c.widgetProps.resolve }));
4829
+ // The board card's pill (`board.card.pill` with `board.card.resolve`)
4830
+ // and the record card's pills (`cardLayout` headPill, footerPill,
4831
+ // chips, each with its own `resolve`).
4832
+ const card = this.board && this.board.card && typeof this.board.card === 'object' ? this.board.card : {};
4833
+ const layout = this.cardLayout && typeof this.cardLayout === 'object' ? this.cardLayout : {};
4834
+ const layoutPills = [layout.headPill, layout.footerPill, ...(Array.isArray(layout.chips) ? layout.chips : [])]
4835
+ .filter((def) => def && typeof def === 'object')
4836
+ .map((def) => ({ key: def.field, resolve: def.resolve }));
4837
+ // The map list's pills (`mapConfig.list.pills`, each with its own `resolve`).
4838
+ const mapListPills = (this.mapListEnabled && Array.isArray(this.mapConfig.list.pills) ? this.mapConfig.list.pills : [])
4839
+ .filter((def) => def && typeof def === 'object')
4840
+ .map((def) => ({ key: def.path, resolve: def.resolve }));
4841
+ const seen = new Set();
4842
+ const cols = [...badgeSources, { key: card.pill, resolve: card.resolve }, ...layoutPills, ...mapListPills].filter((c) => {
4843
+ const wanted = typeof c.key === 'string' && c.key !== '' && c.resolve && typeof c.resolve === 'object'
4844
+ && (c.resolve.colorField || c.resolve.iconField) && !seen.has(c.key);
4845
+ if (wanted) {
4846
+ seen.add(c.key);
4847
+ }
4848
+ return wanted
4849
+ });
4850
+ if (cols.length === 0) {
4851
+ return []
4852
+ }
4853
+ const fields = this.effectiveSchema ? fieldsFromSchema(this.effectiveSchema, { includeReadOnly: true }) : [];
4854
+ const specs = [];
4855
+ for (const col of cols) {
4856
+ const resolve = col.resolve;
4857
+ const field = fields.find((f) => f.key === col.key);
4858
+ const ref = (field && field.reference) || {};
4859
+ const register = resolve.register || ref.register || (typeof this.register === 'string' ? this.register : '');
4860
+ const schema = resolve.schema || (ref.schema !== undefined && ref.schema !== null ? String(ref.schema) : '');
4861
+ if (!register || !schema) {
4862
+ continue
4863
+ }
4864
+ specs.push({ key: col.key, register, schema, colorField: resolve.colorField || '', iconField: resolve.iconField || '' });
4865
+ }
4866
+ return specs
4867
+ },
4868
+
4869
+ /**
4870
+ * `listConfig` with the referenced records' looks on its badge cells,
4871
+ * as `renderedColumns` does for the table.
4872
+ *
4873
+ * @return {object} The list view's config.
4874
+ * @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-badge-cell-takes-its-tone-and-icon-from-the-referenced-record
4875
+ */
4876
+ renderedListConfig() {
4877
+ const list = this.listConfig;
4878
+ if (!list || this.refLookSpecs.length === 0) {
4879
+ return list
4880
+ }
4881
+ const lookKeys = new Set(this.refLookSpecs.map((spec) => spec.key));
4882
+ const withLooks = (cell) => (cell && typeof cell === 'object' && lookKeys.has(cell.key)
4883
+ ? { ...cell, widgetProps: { ...(cell.widgetProps || {}), looks: this.refLooks[cell.key] || {} } }
4884
+ : cell);
4885
+ return {
4886
+ ...list,
4887
+ ...(list.leading ? { leading: withLooks(list.leading) } : {}),
4888
+ ...(list.trailing ? { trailing: Array.isArray(list.trailing) ? list.trailing.map(withLooks) : withLooks(list.trailing) } : {}),
4889
+ }
4890
+ },
4891
+
4892
+ /**
4893
+ * The ids each `refLookSpecs` column shows on screen.
4894
+ *
4895
+ * @return {{[key: string]: Array<string>}} Sorted ids by column key.
4896
+ */
4897
+ refLookIds() {
4898
+ const out = {};
4899
+ for (const spec of this.refLookSpecs) {
4900
+ const ids = new Set();
4901
+ for (const row of this.displayObjects) {
4902
+ const v = row ? row[spec.key] : undefined
4903
+ ;(Array.isArray(v) ? v : [v]).forEach((id) => {
4904
+ if (id !== null && id !== undefined && id !== '' && typeof id !== 'object') {
4905
+ ids.add(String(id));
4906
+ }
4907
+ });
4908
+ }
4909
+ out[spec.key] = [...ids].sort();
4910
+ }
4911
+ return out
4912
+ },
4913
+
4788
4914
  refLabelIds() {
4789
4915
  const out = {};
4790
4916
  const facets = this.storeFacets || this.resolvedSidebar.facets || {};
@@ -5052,13 +5178,15 @@ var script = {
5052
5178
  const translated = {
5053
5179
  ...list,
5054
5180
  title: this.cnTranslate(list.title),
5055
- sub: list.sub ? this.cnTranslate(list.sub) : '',
5181
+ sub: Array.isArray(list.sub) ? list.sub.map((s) => this.cnTranslate(s)) : (list.sub ? this.cnTranslate(list.sub) : ''),
5056
5182
  due: list.due ? this.cnTranslate(list.due) : '',
5057
5183
  };
5058
5184
  return mapListItems(this.displayObjects, translated, {
5059
5185
  rowKey: this.rowKey,
5060
5186
  located: (row) => Boolean(this.resolveRowGeometry(row)),
5061
5187
  translate: (text) => this.cnTranslate(text),
5188
+ formatters: this.cnFormatters,
5189
+ looks: this.refLooks,
5062
5190
  })
5063
5191
  },
5064
5192
 
@@ -5985,11 +6113,13 @@ var script = {
5985
6113
  *
5986
6114
  * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-index-page-can-take-the-board-header
5987
6115
  * @spec openspec/changes/screens-index-header-buttons-parity/specs/index-list-board-look/spec.md#requirement-the-add-header-button-carries-a-plus
5988
- * @return {Array<{key: string, label: string, action: string, variant: string, icon: string, format: string}>}
6116
+ * @spec openspec/changes/header-action-href/specs/header-action-href/spec.md#requirement-a-header-action-with-href-is-a-link-that-runs-and-emits-nothing
6117
+ * @return {Array<{key: string, label: string, action: string, variant: string, icon: string, format: string, link: ({href: string, target: (string|undefined)}|null)}>}
5989
6118
  */
5990
6119
  resolvedHeaderButtons() {
5991
6120
  const headerButtons = this.presented('headerButtons');
5992
6121
  const declared = Array.isArray(headerButtons) ? headerButtons : [];
6122
+ const builtInActions = new Set(['add', 'export', 'import', 'refresh', 'save-view', 'actions-menu']);
5993
6123
  return declared
5994
6124
  .filter((button) => button && typeof button === 'object' && typeof button.action === 'string' && button.action !== '')
5995
6125
  .map((button, index) => {
@@ -6016,6 +6146,13 @@ var script = {
6016
6146
  if (label === '' && button.action === 'save-view') {
6017
6147
  label = translate('nextcloud-vue', 'Save view');
6018
6148
  }
6149
+ const run = typeof button.run === 'string' ? button.run : '';
6150
+ // A button that runs a manifest `href` header action is that link;
6151
+ // a `type: "navigate"` or function-handler entry keeps dispatching.
6152
+ const actionId = button.action === 'export' ? run : (builtInActions.has(button.action) ? '' : button.action);
6153
+ const linked = actionId
6154
+ ? this.mergedHeaderActions.find((entry) => entry && entry.id === actionId && entry.href && entry._dispatchSuppress && (!entry.type || entry.type === 'handler'))
6155
+ : null;
6019
6156
  return {
6020
6157
  key: String(button.id || button.action || index),
6021
6158
  label,
@@ -6023,7 +6160,8 @@ var script = {
6023
6160
  variant: button.variant === 'primary' ? 'primary' : 'secondary',
6024
6161
  icon,
6025
6162
  format: button.format === 'excel' ? 'excel' : 'csv',
6026
- run: typeof button.run === 'string' ? button.run : '',
6163
+ run,
6164
+ link: linked ? { href: linked.href, target: linked.linkTarget || undefined } : null,
6027
6165
  items: Array.isArray(button.items)
6028
6166
  ? button.items.filter((item) => (typeof item === 'string' && item !== '')
6029
6167
  || (item && typeof item === 'object' && typeof item.heading === 'string' && item.heading !== ''))
@@ -6682,6 +6820,15 @@ var script = {
6682
6820
  },
6683
6821
  },
6684
6822
 
6823
+ // Read the colour and icon of the records badge columns point at.
6824
+ refLookIds: {
6825
+ immediate: true,
6826
+ deep: true,
6827
+ handler() {
6828
+ this.loadRefLooks();
6829
+ },
6830
+ },
6831
+
6685
6832
  // The Active line names a referenced value, not its id.
6686
6833
  activeFilterRefIds: {
6687
6834
  deep: true,
@@ -7133,6 +7280,45 @@ var script = {
7133
7280
  }));
7134
7281
  },
7135
7282
 
7283
+ /**
7284
+ * Read the colour and icon names `refLookSpecs` columns need, for the
7285
+ * rows on screen, with the same resolver as the labels. A failure
7286
+ * leaves the badge as declared.
7287
+ *
7288
+ * @return {Promise<void>}
7289
+ * @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-badge-cell-takes-its-tone-and-icon-from-the-referenced-record
7290
+ */
7291
+ async loadRefLooks() {
7292
+ if (this.refLookSpecs.length === 0) {
7293
+ return
7294
+ }
7295
+ if (!this._refLabelResolver) {
7296
+ this._refLabelResolver = createRefLabelResolver(() => {
7297
+ try {
7298
+ return (this.list && this.list.objectStore) || useObjectStore()
7299
+ } catch {
7300
+ return null
7301
+ }
7302
+ });
7303
+ }
7304
+ const read = (spec, ids, name) => (name
7305
+ ? this._refLabelResolver.resolve(spec.register, spec.schema, ids, name, { strict: true }).catch(() => ({}))
7306
+ : Promise.resolve({}));
7307
+ await Promise.all(this.refLookSpecs.map(async (spec) => {
7308
+ const known = this.refLooks[spec.key] || {};
7309
+ const ids = (this.refLookIds[spec.key] || []).filter((id) => !Object.hasOwn(known, id));
7310
+ if (ids.length === 0) {
7311
+ return
7312
+ }
7313
+ const [colors, icons] = await Promise.all([read(spec, ids, spec.colorField), read(spec, ids, spec.iconField)]);
7314
+ const found = {};
7315
+ ids.forEach((id) => {
7316
+ found[id] = { color: (colors && colors[id]) || null, icon: (icons && icons[id]) || null };
7317
+ });
7318
+ this.refLooks = { ...this.refLooks, [spec.key]: { ...(this.refLooks[spec.key] || {}), ...found } };
7319
+ }));
7320
+ },
7321
+
7136
7322
  /**
7137
7323
  * Whether a saved view shows its record count (`viewCounts`).
7138
7324
  *
@@ -7633,8 +7819,11 @@ var script = {
7633
7819
  * manifest-actions-dispatch — `navigate`, `emit`, `none`, or a
7634
7820
  * registry name (resolved through `resolveRegisteredHandler`).
7635
7821
  *
7822
+ * @spec openspec/changes/header-action-href/specs/header-action-href/spec.md#requirement-a-header-action-with-href-is-a-link-that-runs-and-emits-nothing
7636
7823
  * @param {object} entry Raw headerActions entry.
7637
- * @return {object} Possibly-mutated copy: function-typed
7824
+ * @return {object} Possibly-mutated copy: an `href` becomes a
7825
+ * resolved link with `linkTarget`, no handler (a function one
7826
+ * included) and `_dispatchSuppress: true`; function-typed
7638
7827
  * handlers untouched; `'emit'` and unknown registry names
7639
7828
  * strip the `handler` (emit-only fall-through); `'navigate'`
7640
7829
  * becomes a `$router.push` thunk (with the entry's literal
@@ -7646,14 +7835,23 @@ var script = {
7646
7835
  if (!entry) {
7647
7836
  return entry
7648
7837
  }
7838
+ // An `href` makes the entry a link, which only navigates: it
7839
+ // carries no handler, and the page emits nothing for it.
7840
+ if (typeof entry.href === 'string' && entry.href !== '') {
7841
+ const link = headerActionAsLink(entry);
7842
+ return link.href ? { ...link, linkTarget: link.target, _dispatchSuppress: true } : link
7843
+ }
7649
7844
  const handler = entry.handler;
7650
7845
  if (typeof handler === 'function') {
7651
7846
  return { ...entry }
7652
7847
  }
7653
7848
  // `type: "navigate"` with a `target` URL is a link: the bar and the
7654
- // header's Actions menu render it as a real anchor in a new tab.
7849
+ // header's Actions menu render it as a real anchor, an external URL
7850
+ // in a new tab.
7655
7851
  if (typeof handler !== 'string' && entry.type === 'navigate' && typeof entry.target === 'string' && entry.target !== '' && !entry.href) {
7656
- return { ...entry, href: entry.target }
7852
+ return isExternalActionTarget(entry.target)
7853
+ ? { ...entry, href: entry.target, linkTarget: '_blank' }
7854
+ : { ...entry, href: entry.target }
7657
7855
  }
7658
7856
  if (typeof handler !== 'string') {
7659
7857
  // No handler declared — emit-only fall-through.
@@ -7824,8 +8022,9 @@ var script = {
7824
8022
  *
7825
8023
  * @spec openspec/changes/screens-index-header-actions-menu/specs/index-header/spec.md#requirement-the-header-actions-menu-lists-what-its-items-name
7826
8024
  * @spec openspec/changes/screens-index-header-menu-headings/specs/index-header/spec.md#requirement-the-header-actions-menu-can-draw-group-headings-and-separators
8025
+ * @spec openspec/changes/header-action-href/specs/header-action-href/spec.md#requirement-a-header-action-with-href-is-a-link-that-runs-and-emits-nothing
7827
8026
  * @param {{items?: Array<(string|{heading: string})>|null, format?: string}} button The resolved header button.
7828
- * @return {Array<{key: string, label: string, kind?: ('heading'|'separator'), icon?: string, href?: string, disabled?: boolean, title?: string, iconComponent?: object, run?: Function}>}
8027
+ * @return {Array<{key: string, label: string, kind?: ('heading'|'separator'), icon?: string, href?: string, linkTarget?: string, disabled?: boolean, title?: string, iconComponent?: object, run?: () => void}>}
7829
8028
  */
7830
8029
  headerMenuEntries(button) {
7831
8030
  const fromAction = (entry) => ({
@@ -7833,6 +8032,7 @@ var script = {
7833
8032
  label: entry.label ? this.cnTranslate(entry.label) : '',
7834
8033
  icon: entry.icon && typeof entry.icon === 'string' && !entry.icon.startsWith('icon-') ? entry.icon : '',
7835
8034
  href: typeof entry.href === 'string' && entry.href !== '' ? entry.href : '',
8035
+ linkTarget: entry.linkTarget || '',
7836
8036
  // A `handler: "navigate"` entry whose route resolves carries `to`
7837
8037
  // (resolveHeaderHandler drops its handler): the menu draws it as
7838
8038
  // an in-app link, as the actions bar does, so it navigates.
@@ -7866,7 +8066,7 @@ var script = {
7866
8066
  }),
7867
8067
 
7868
8068
  documentation: () => (this.documentationUrl
7869
- ? { key: 'documentation', label: this.documentationLabel || translate('nextcloud-vue', 'Documentation'), icon: 'BookOpenVariantOutline', href: this.documentationUrl }
8069
+ ? { key: 'documentation', label: this.documentationLabel || translate('nextcloud-vue', 'Documentation'), icon: 'BookOpenVariantOutline', href: this.documentationUrl, linkTarget: '_blank' }
7870
8070
  : null),
7871
8071
  };
7872
8072
  const entries = [];
@@ -7962,13 +8162,18 @@ var script = {
7962
8162
 
7963
8163
  /**
7964
8164
  * A header button was clicked: run its built-in action, or dispatch
7965
- * it as the `headerActions` entry with that id.
8165
+ * it as the `headerActions` entry with that id. A button that is a
8166
+ * link is left to the browser.
7966
8167
  *
7967
8168
  * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-index-page-can-take-the-board-header
7968
- * @param {{action: string, format: string}} button The resolved button.
8169
+ * @spec openspec/changes/header-action-href/specs/header-action-href/spec.md#requirement-a-header-action-with-href-is-a-link-that-runs-and-emits-nothing
8170
+ * @param {{action: string, format: string, link?: object}} button The resolved button.
7969
8171
  * @return {void}
7970
8172
  */
7971
8173
  onHeaderButton(button) {
8174
+ if (button.link) {
8175
+ return
8176
+ }
7972
8177
  if (button.action === 'add') {
7973
8178
  this.onAddClick();
7974
8179
  } else if (button.action === 'export') {
@@ -10294,7 +10499,7 @@ var script = {
10294
10499
  return
10295
10500
  }
10296
10501
  const badge = this.savedViewMenuBadge(view);
10297
- menu.toggle({ id: String(view.id || view.slug), page: this.savedViewPage, name: view.name, ...(badge ? { badge } : {}) });
10502
+ menu.toggle({ id: String(view.id || view.slug), page: this.savedViewPage, name: view.name, ...(typeof view.icon === 'string' && view.icon !== '' ? { icon: view.icon } : {}), ...(badge ? { badge } : {}) });
10298
10503
  },
10299
10504
 
10300
10505
  /**
@@ -10829,7 +11034,9 @@ var script = {
10829
11034
  if (resultData && resultData.success && this._refLabelResolver) {
10830
11035
  this._refLabelResolver.invalidate();
10831
11036
  this.refLabels = {};
11037
+ this.refLooks = {};
10832
11038
  this.loadRefLabels();
11039
+ this.loadRefLooks();
10833
11040
  }
10834
11041
  },
10835
11042