@conduction/nextcloud-vue 2.91.0 → 2.93.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 (149) hide show
  1. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +57 -4
  2. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +44 -17
  4. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  5. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  6. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +44 -2
  7. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  8. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +140 -59
  9. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  10. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  11. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +20 -6
  12. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  13. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +3 -2
  14. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  15. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +10 -39
  16. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  17. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  18. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +5 -3
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  20. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  21. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  22. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +6 -4
  23. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  24. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  25. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +46 -0
  26. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
  27. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue3.js +6 -2
  28. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue3.js.map +1 -1
  29. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
  30. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +82 -9
  31. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  32. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +104 -90
  33. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  34. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +9 -0
  35. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js.map +1 -0
  36. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +355 -0
  37. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -0
  38. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +462 -0
  39. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -0
  40. package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
  41. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +39 -10
  42. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  43. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +418 -399
  44. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  45. package/dist/esm/components/CnIcon/semanticIcons.js +2 -2
  46. package/dist/esm/components/CnIcon/semanticIcons.js.map +1 -1
  47. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js +1 -1
  48. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js +156 -3
  49. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -1
  50. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js +168 -52
  51. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -1
  52. package/dist/esm/components/CnModuleCard/CnModuleCard.vue.js +9 -0
  53. package/dist/esm/components/CnModuleCard/CnModuleCard.vue.js.map +1 -0
  54. package/dist/esm/components/CnModuleCard/CnModuleCard.vue2.js +83 -0
  55. package/dist/esm/components/CnModuleCard/CnModuleCard.vue2.js.map +1 -0
  56. package/dist/esm/components/CnModuleCard/CnModuleCard.vue3.js +119 -0
  57. package/dist/esm/components/CnModuleCard/CnModuleCard.vue3.js.map +1 -0
  58. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
  59. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +22 -1
  60. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
  61. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +20 -14
  62. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
  63. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue.js +1 -1
  64. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js +2 -0
  65. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js.map +1 -1
  66. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js +15 -5
  67. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js.map +1 -1
  68. package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue.js +1 -1
  69. package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue2.js +8 -30
  70. package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue2.js.map +1 -1
  71. package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue3.js.map +1 -1
  72. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  73. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +33 -2
  74. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  75. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  76. package/dist/esm/components/CnTextWidget/CnTextWidget.vue.js +1 -1
  77. package/dist/esm/components/CnTextWidget/CnTextWidget.vue2.js +63 -4
  78. package/dist/esm/components/CnTextWidget/CnTextWidget.vue2.js.map +1 -1
  79. package/dist/esm/components/CnTextWidget/CnTextWidget.vue3.js +19 -2
  80. package/dist/esm/components/CnTextWidget/CnTextWidget.vue3.js.map +1 -1
  81. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +9 -0
  82. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js.map +1 -0
  83. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +164 -0
  84. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -0
  85. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +93 -0
  86. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -0
  87. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
  88. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +12 -0
  89. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  90. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  91. package/dist/esm/l10n/en.json.js +45 -1
  92. package/dist/esm/l10n/en.json.js.map +1 -1
  93. package/dist/esm/l10n/nl.json.js +44 -1
  94. package/dist/esm/l10n/nl.json.js.map +1 -1
  95. package/dist/esm/library-style.js +1 -1
  96. package/dist/esm/nextcloud-vue.css +1182 -328
  97. package/dist/esm/utils/boardDate.js +35 -1
  98. package/dist/esm/utils/boardDate.js.map +1 -1
  99. package/dist/esm/utils/featureCatalog.js +135 -0
  100. package/dist/esm/utils/featureCatalog.js.map +1 -0
  101. package/dist/esm/utils/fillTemplate.js +84 -4
  102. package/dist/esm/utils/fillTemplate.js.map +1 -1
  103. package/dist/esm/utils/headerMeta.js +62 -12
  104. package/dist/esm/utils/headerMeta.js.map +1 -1
  105. package/dist/esm/utils/roadmapIssues.js +86 -0
  106. package/dist/esm/utils/roadmapIssues.js.map +1 -0
  107. package/dist/esm/utils/userDisplayName.js +47 -0
  108. package/dist/esm/utils/userDisplayName.js.map +1 -0
  109. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  110. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  111. package/dist/nextcloud-vue.cjs.js +15618 -13097
  112. package/dist/nextcloud-vue.cjs.js.map +2 -2
  113. package/dist/nextcloud-vue.css +1182 -328
  114. package/l10n/en.json +45 -1
  115. package/l10n/nl.json +44 -1
  116. package/package.json +1 -1
  117. package/src/components/CnAppRoot/CnAppRoot.md +2 -0
  118. package/src/components/CnAppRoot/CnAppRoot.vue +66 -5
  119. package/src/components/CnChoiceCards/CnChoiceCards.md +27 -0
  120. package/src/components/CnChoiceCards/CnChoiceCards.vue +168 -3
  121. package/src/components/CnDashboardPage/CnDashboardPage.vue +45 -6
  122. package/src/components/CnDataTable/CnDataTable.vue +10 -39
  123. package/src/components/CnDetailPage/CnDetailPage.vue +5 -3
  124. package/src/components/CnFactsWidget/CnFactsWidget.vue +6 -4
  125. package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +51 -1
  126. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +98 -13
  127. package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +1053 -0
  128. package/src/components/CnFormDialog/CnFormDialog.md +1 -0
  129. package/src/components/CnFormDialog/CnFormDialog.vue +619 -562
  130. package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +338 -5
  131. package/src/components/CnModuleCard/CnModuleCard.vue +258 -0
  132. package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +23 -2
  133. package/src/components/CnNotificationPreferences/CnNotificationPreferences.vue +5 -0
  134. package/src/components/CnRoadmapTab/CnRoadmapTab.vue +6 -28
  135. package/src/components/CnTabsWidget/CnTabsWidget.vue +33 -2
  136. package/src/components/CnTextWidget/CnTextWidget.vue +89 -4
  137. package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +305 -0
  138. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +12 -0
  139. package/src/css/dialog.css +47 -0
  140. package/src/css/look-board-detail.css +8 -0
  141. package/src/css/look-board.css +33 -0
  142. package/src/schemas/app-manifest-v2.schema.json +67 -4
  143. package/src/utils/boardDate.js +34 -0
  144. package/src/utils/featureCatalog.js +132 -0
  145. package/src/utils/fillTemplate.js +83 -3
  146. package/src/utils/headerMeta.js +62 -12
  147. package/src/utils/roadmapIssues.js +82 -0
  148. package/src/utils/userDisplayName.js +54 -0
  149. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -404,6 +404,7 @@ import CnColumnFilterPopover from './CnColumnFilterPopover.vue'
404
404
  import { useClickDragGuard } from '../../composables/useClickDragGuard.js'
405
405
  import { normalizeLook } from '../../composables/useLook.js'
406
406
  import { clearedColumnFilterParams, columnFilterDef, columnFilterParams, columnFilterState, isColumnFilterActive, isColumnSortable } from '../../utils/columnFilters.js'
407
+ import { fillTemplate, joinTemplateParts } from '../../utils/fillTemplate.js'
407
408
  import { lensUnavailableText, readLensReports } from '../../utils/lensAvailability.js'
408
409
  import { followLinkClick, openRowTarget, resolveHref } from '../../utils/linkNavigation.js'
409
410
  import { nextSortState } from '../../utils/multiColumnSort.js'
@@ -420,40 +421,6 @@ import { CnUnreadMarker } from '../CnUnreadMarker/index.js'
420
421
  // consuming app does not pull in the library's global css/index.css.
421
422
  import '../../css/table.css'
422
423
 
423
- /**
424
- * Fill a `secondary` template such as `"{identifier} · {requester}"`.
425
- *
426
- * A field without a value takes its separator with it: the text between two
427
- * fields belongs to the field after it, so an empty requester leaves
428
- * "2026-0002", not "2026-0002 ·", and an empty first field does not leave a
429
- * leading separator either. Text before the first field and after the last
430
- * one is kept as written. A template whose fields all have values fills
431
- * exactly as before.
432
- *
433
- * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-empty-field-takes-its-separator-with-it
434
- * @param {string} template The template, with `{field}` placeholders.
435
- * @param {(key: string) => string} valueOf The text of a field ('' when empty).
436
- * @return {string} The filled template.
437
- */
438
- function fillSecondaryTemplate(template, valueOf) {
439
- // split() with a capture group alternates literal, field, literal, …
440
- const parts = template.split(/\{([^}]+)\}/)
441
- const lead = parts[0]
442
- const tail = parts[parts.length - 1]
443
- let body = ''
444
- for (let i = 1; i < parts.length; i += 2) {
445
- const value = valueOf(parts[i].trim())
446
- if (value === '') {
447
- continue
448
- }
449
- // The literal before this field: the separator, unless nothing has
450
- // been written yet (then there is nothing to separate from).
451
- const separator = i === 1 ? '' : parts[i - 1]
452
- body += (body === '' ? '' : separator) + value
453
- }
454
- return lead + body + tail
455
- }
456
-
457
424
  /**
458
425
  * CnDataTable — Generic sortable data table for list views.
459
426
  *
@@ -561,8 +528,8 @@ export default {
561
528
  *
562
529
  * `secondary` draws a second, muted line under the cell's value: a field
563
530
  * key (`"number"`), a template with `{field}` placeholders
564
- * (`"{number} · {requester}"`, dotted paths allowed), or a function of
565
- * the row. Empty fields leave their placeholder blank; a line that
531
+ * (`"{number} · {requester}"`, dotted paths allowed; `{startAt|weekday}`
532
+ * formats through `fillTemplate`), or a function of the row. Empty fields leave their placeholder blank; a line that
566
533
  * resolves to nothing is not drawn.
567
534
  *
568
535
  * @type {Array<{key: string, label: string, description: string, sortable: boolean, width: string, class: string, cellClass: string, secondary: (string|((row: object) => string))}|string>}
@@ -1769,8 +1736,10 @@ export default {
1769
1736
  /**
1770
1737
  * The secondary line of a cell, from `col.secondary`: a function is
1771
1738
  * called with the row; a string with `{field}` placeholders is a
1772
- * template filled from the row (dotted paths allowed); any other
1773
- * string is a field key. Returns '' when there is nothing to draw.
1739
+ * template filled from the row (dotted paths allowed), where a
1740
+ * `{field|format}` token is formatted by `fillTemplate` ("{startAt|weekday}"
1741
+ * reads "wo 7 okt"); any other string is a field key. Returns '' when
1742
+ * there is nothing to draw.
1774
1743
  *
1775
1744
  * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-table-column-carries-a-secondary-line
1776
1745
  * @param {object} row The row.
@@ -1787,7 +1756,9 @@ export default {
1787
1756
  return ''
1788
1757
  }
1789
1758
  if (spec.includes('{')) {
1790
- const filled = fillSecondaryTemplate(spec, (key) => this.secondaryFieldText(row, key))
1759
+ const filled = joinTemplateParts(spec, (key) => (key.includes('|')
1760
+ ? fillTemplate(`{${key}}`, row)
1761
+ : this.secondaryFieldText(row, key)))
1791
1762
  // A template whose every field is empty leaves only separators:
1792
1763
  // nothing worth a line.
1793
1764
  return /[\p{L}\p{N}]/u.test(filled) ? filled.trim() : ''
@@ -2631,8 +2631,10 @@ export default {
2631
2631
  /**
2632
2632
  * The meta line on row 2 of the board look's header (manifest
2633
2633
  * `config.headerMeta`): a field template such as `"via {channel}"`.
2634
- * Every `{path}` is read from the record; a line with an empty value
2635
- * is dropped. Only the board look draws it.
2634
+ * Every `{path}` is read from the record (an enum field as its label);
2635
+ * `{path|format}` formats it (`weekday`, `longdate`, `time`, `user`,
2636
+ * `currency:EUR`, ...). An empty token drops itself and its separator;
2637
+ * a line with no value at all is dropped. Only the board look draws it.
2636
2638
  *
2637
2639
  * @spec openspec/changes/screens-detail-page-parity/specs/detail-page-board-look/spec.md#requirement-the-detail-header-is-two-rows-on-the-ground
2638
2640
  */
@@ -2991,7 +2993,7 @@ export default {
2991
2993
  * @spec openspec/changes/screens-detail-page-parity/specs/detail-page-board-look/spec.md#requirement-the-detail-header-is-two-rows-on-the-ground
2992
2994
  */
2993
2995
  headerMetaText() {
2994
- return resolveHeaderMeta(this.headerMeta ? this.effectiveTranslate(this.headerMeta) : '', this.resolvedObject)
2996
+ return resolveHeaderMeta(this.headerMeta ? this.effectiveTranslate(this.headerMeta) : '', this.resolvedObject, { schema: this.currentSchema, translate: this.effectiveTranslate })
2995
2997
  },
2996
2998
 
2997
2999
  /**
@@ -257,7 +257,7 @@ export default {
257
257
  /**
258
258
  * The widget configuration.
259
259
  *
260
- * - `fields` — the entries, in order. Each is `{ path, label?, as?, format?, map?, itemPath?, parts?, separator?, route?, variant?, variantMap?, colorMap?, icon?, copy?, reveal?, text?, title?, subtitle?, initialsPath?, tone? }`.
260
+ * - `fields` — the entries, in order. Each is `{ path, label?, as?, from?, format?, map?, itemPath?, parts?, separator?, route?, variant?, variantMap?, colorMap?, icon?, copy?, reveal?, text?, title?, subtitle?, initialsPath?, tone? }`.
261
261
  * `as` is `row` (default when `label` or `icon` is set), `title`, `line`, `text` (a line in the normal ink), `lead` (an icon tile or initials beside `title` and `subtitle`) or `pill`.
262
262
  * A row's `format` may also be `email`, `phone` or `url` (a link) or `masked` (the last four characters; `reveal: true` adds Show). `copy: true` adds a copy button; `icon` replaces the label.
263
263
  * A part may be `{ text }`, a constant; a `line` or `text` entry without `path` or `parts` shows its `text`.
@@ -265,7 +265,7 @@ export default {
265
265
  * - `action` — an action drawn as a button under the facts; `headerAction` — one drawn in the card header (reported through the `header` event).
266
266
  * `format` is `text` (default), `date` (board short date), `date-long` (always with the year), `list` (an array joined by ", ", each item read at `itemPath`) or `currency` (a money amount in the reader's language, "€ 1.234,50": the code in `currency`, else the record's `currencyField`, else the app's reporting currency, else EUR; `decimals` defaults to 2).
267
267
  * `map` turns a raw value into a label (translated). `parts` (on a `line`) lists paths, or `{ path, label?, format? }`, joined by `separator` (default " · "); empty parts are dropped.
268
- * - `source` — `{ register, schema, id }` or `{ url }`; omitted = the current object.
268
+ * - `source` — `{ register, schema, id }` or `{ url }`; omitted = the current object. An entry with `from: "object"` reads the current object even then (a client card that also shows the ticket's channel).
269
269
  * - `link` — `{ label, route? | href? }` at the foot.
270
270
  * - `hideEmpty` — drop an entry whose value is empty (default true).
271
271
  * - `emptyText` — shown when no entry has a value.
@@ -370,8 +370,8 @@ export default {
370
370
  * @return {Array<object>}
371
371
  */
372
372
  blocks() {
373
- const record = this.record
374
- if (!record) {
373
+ const sourceRecord = this.record
374
+ if (!sourceRecord) {
375
375
  return []
376
376
  }
377
377
  const fields = Array.isArray(this.content?.fields) ? this.content.fields : []
@@ -381,6 +381,8 @@ export default {
381
381
  if (!field || typeof field !== 'object') {
382
382
  return
383
383
  }
384
+ // `from: "object"` reads the page's own object while the card shows a `source`.
385
+ const record = field.from === 'object' && this.objectData ? this.objectData : sourceRecord
384
386
  const kind = this.kindOf(field)
385
387
  const key = String(field.key || field.path || `${kind}-${index}`).replace(/[^\w-]/g, '-')
386
388
  const value = this.entryValue(record, field, kind)
@@ -29,7 +29,11 @@
29
29
  :llmSkillsUrl="resolvedLlmSkillsUrl"
30
30
  :suggestUrl="resolvedSuggestUrl"
31
31
  :documentationUrl="resolvedDocumentationUrl"
32
- :capabilityComparison="resolvedCapabilityComparison" />
32
+ :capabilityComparison="resolvedCapabilityComparison"
33
+ :categories="categories || {}"
34
+ :board="board || {}"
35
+ :title="title"
36
+ :appVersion="resolvedAppVersion" />
33
37
  </template>
34
38
 
35
39
  <script>
@@ -188,6 +192,47 @@ export default {
188
192
  default: null,
189
193
  },
190
194
 
195
+ /**
196
+ * Feature category key to caption, for the board look's groups.
197
+ * Manifest `config.categories`.
198
+ *
199
+ * @type {object|null}
200
+ */
201
+ categories: {
202
+ type: Object,
203
+ default: null,
204
+ },
205
+
206
+ /**
207
+ * Board look options (`layout`, `tabs`, `filter`, `statusLabels`,
208
+ * `next`, `issueLabels`, `subtitle`, `notice`, `documentationButton`,
209
+ * `suggestButton`). Manifest `config.board`; see CnFeaturesBoard.
210
+ *
211
+ * @type {object|null}
212
+ */
213
+ board: {
214
+ type: Object,
215
+ default: null,
216
+ },
217
+
218
+ /**
219
+ * Page title, lifted from the manifest page's `title`. Shown in the
220
+ * board look.
221
+ */
222
+ title: {
223
+ type: String,
224
+ default: '',
225
+ },
226
+
227
+ /**
228
+ * The app version for the board look's subtitle. Manifest config >
229
+ * initialState (`features_roadmap_app_version`) > ''.
230
+ */
231
+ appVersion: {
232
+ type: String,
233
+ default: '',
234
+ },
235
+
191
236
  /**
192
237
  * Override for the appId used to namespace loadState lookups.
193
238
  * Tests pass an explicit value; production reads it from the
@@ -283,6 +328,11 @@ export default {
283
328
  || readInitialState(this.effectiveAppId, 'features_roadmap_suggest_url', '')
284
329
  },
285
330
 
331
+ resolvedAppVersion() {
332
+ return this.appVersion
333
+ || readInitialState(this.effectiveAppId, 'features_roadmap_app_version', '')
334
+ },
335
+
286
336
  resolvedDocumentationUrl() {
287
337
  return this.documentationUrl
288
338
  || readInitialState(this.effectiveAppId, 'features_roadmap_documentation_url', '')
@@ -7,6 +7,19 @@
7
7
  </template>
8
8
  </NcEmptyContent>
9
9
  </div>
10
+ <CnFeaturesBoard
11
+ v-else-if="isBoardLook"
12
+ :features="features"
13
+ :categories="categories"
14
+ :repo="repo"
15
+ :title="title"
16
+ :appVersion="appVersion"
17
+ :documentationUrl="resolvedDocumentationUrl"
18
+ :suggestUrl="resolvedSuggestUrl"
19
+ :openbuiltUrl="resolvedOpenbuiltUrl"
20
+ :llmSkillsUrl="resolvedLlmSkillsUrl"
21
+ v-bind="boardOptions"
22
+ @support="openSupportDialog" />
10
23
  <template v-else>
11
24
  <header class="cn-features-and-roadmap-view__header">
12
25
  <h2 class="cn-features-and-roadmap-view__title">
@@ -57,19 +70,18 @@
57
70
  :comparison="capabilityComparison" />
58
71
  <CnRoadmapTab v-else :repo="repo" />
59
72
  </main>
60
-
61
- <CnSupportDialog
62
- v-if="showSupportDialog"
63
- :appName="resolvedAppName"
64
- :appSlug="resolvedAppSlug"
65
- :appStoreUrl="resolvedAppStoreUrl"
66
- :featureRequestUrl="resolvedFeatureRequestUrl"
67
- :donateUrl="donateUrl"
68
- :supportUrl="supportUrl"
69
- :founderName="founderName"
70
- :founderTitle="founderTitle"
71
- @close="showSupportDialog = false" />
72
73
  </template>
74
+ <CnSupportDialog
75
+ v-if="showSupportDialog && !disabled"
76
+ :appName="resolvedAppName"
77
+ :appSlug="resolvedAppSlug"
78
+ :appStoreUrl="resolvedAppStoreUrl"
79
+ :featureRequestUrl="resolvedFeatureRequestUrl"
80
+ :donateUrl="donateUrl"
81
+ :supportUrl="supportUrl"
82
+ :founderName="founderName"
83
+ :founderTitle="founderTitle"
84
+ @close="showSupportDialog = false" />
73
85
  </div>
74
86
  </template>
75
87
 
@@ -111,9 +123,11 @@ import RoadVariant from 'vue-material-design-icons/RoadVariant.vue'
111
123
  import TableSearch from 'vue-material-design-icons/TableSearch.vue'
112
124
  import CnCapabilityTable from '../CnCapabilityTable/CnCapabilityTable.vue'
113
125
  import CnFeaturesAndRoadmapSidebar from '../CnFeaturesAndRoadmapSidebar/CnFeaturesAndRoadmapSidebar.vue'
126
+ import CnFeaturesBoard from '../CnFeaturesBoard/CnFeaturesBoard.vue'
114
127
  import CnFeaturesTab from '../CnFeaturesTab/CnFeaturesTab.vue'
115
128
  import CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'
116
129
  import CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'
130
+ import { normalizeLook } from '../../composables/useLook.js'
117
131
  import { warnCapabilityComparisonDeprecated } from '../../utils/capabilityComparison.js'
118
132
  import { buildFeatureRequestUrl, DEFAULT_FORGE } from '../../utils/forge.js'
119
133
 
@@ -137,6 +151,7 @@ export default {
137
151
  RoadVariant,
138
152
  TableSearch,
139
153
  CnCapabilityTable,
154
+ CnFeaturesBoard,
140
155
  CnFeaturesTab,
141
156
  CnRoadmapTab,
142
157
  CnSupportDialog,
@@ -158,6 +173,8 @@ export default {
158
173
  * unmount. Same holder CnIndexPage uses.
159
174
  */
160
175
  cnIndexSidebarConfig: { default: () => ({ value: null }) },
176
+ /** The app's look, provided by CnAppRoot (`nextcloud` or `board`). */
177
+ cnLook: { default: 'nextcloud' },
161
178
  },
162
179
 
163
180
  props: {
@@ -192,6 +209,46 @@ export default {
192
209
  default: () => [],
193
210
  },
194
211
 
212
+ /**
213
+ * Feature category key to caption, in the order the board look shows
214
+ * the groups. A feature names its group with `category`.
215
+ *
216
+ * @type {object}
217
+ */
218
+ categories: {
219
+ type: Object,
220
+ default: () => ({}),
221
+ },
222
+
223
+ /**
224
+ * Board look options, passed to CnFeaturesBoard: `layout` (`cards` or
225
+ * `rows`), `tabs`, `filter` (`chips` or `segmented`), `statusLabels`,
226
+ * `next`, `issueLabels`, `subtitle`, `notice`, `documentationButton`
227
+ * and `suggestButton`. Ignored in the default look.
228
+ *
229
+ * @type {object}
230
+ */
231
+ board: {
232
+ type: Object,
233
+ default: () => ({}),
234
+ },
235
+
236
+ /**
237
+ * Page title in the board look. Empty reads "Features and roadmap".
238
+ */
239
+ title: {
240
+ type: String,
241
+ default: '',
242
+ },
243
+
244
+ /**
245
+ * The app version, for `{appVersion}` in the board look's subtitle.
246
+ */
247
+ appVersion: {
248
+ type: String,
249
+ default: '',
250
+ },
251
+
195
252
  /**
196
253
  * Admin opt-out flag — when true the entire view collapses to a
197
254
  * single "disabled by admin" empty state.
@@ -387,6 +444,32 @@ export default {
387
444
  },
388
445
 
389
446
  computed: {
447
+ /**
448
+ * Whether the page is drawn in the board look.
449
+ *
450
+ * @return {boolean}
451
+ */
452
+ isBoardLook() {
453
+ return normalizeLook(this.cnLook) === 'board'
454
+ },
455
+
456
+ /**
457
+ * The board options CnFeaturesBoard declares, so an unknown key in the
458
+ * manifest never lands on its root element as an attribute.
459
+ *
460
+ * @return {object}
461
+ */
462
+ boardOptions() {
463
+ const keys = ['layout', 'tabs', 'filter', 'statusLabels', 'next', 'issueLabels', 'subtitle', 'notice', 'documentationButton', 'suggestButton']
464
+ const options = {}
465
+ for (const key of keys) {
466
+ if (this.board && this.board[key] !== undefined && this.board[key] !== null) {
467
+ options[key] = this.board[key]
468
+ }
469
+ }
470
+ return options
471
+ },
472
+
390
473
  /**
391
474
  * The stops the header toggle cycles through. Without a capability
392
475
  * comparison this is the two-stop list every consumer has today, and
@@ -580,7 +663,9 @@ export default {
580
663
  if (!this.cnHostsIndexSidebar || !this.cnIndexSidebarConfig) {
581
664
  return
582
665
  }
583
- if (this.disabled) {
666
+ // The board look has no right-hand column: its links sit in the
667
+ // board's Actions menu.
668
+ if (this.disabled || this.isBoardLook) {
584
669
  this.cnIndexSidebarConfig.value = null
585
670
  return
586
671
  }