@conduction/nextcloud-vue 2.138.0 → 2.140.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 (115) hide show
  1. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +9 -2
  2. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  4. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  5. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +17 -1
  6. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  7. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +2 -2
  8. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  9. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  10. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +52 -1
  11. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  12. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +23 -1
  13. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  14. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
  15. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +106 -0
  16. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
  17. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +30 -7
  18. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
  19. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  20. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +59 -3
  21. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  22. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +44 -34
  23. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  24. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  25. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +28 -1
  26. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  27. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  28. package/dist/esm/components/CnFilesBrowser/filesBrowser.js +7 -1
  29. package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
  30. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +5 -0
  31. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  32. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +129 -6
  33. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -2
  35. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +2 -0
  37. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  38. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +13 -2
  39. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  40. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  41. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +28 -3
  42. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  43. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  44. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  45. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +1 -1
  46. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  47. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  48. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +1 -1
  49. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +10 -0
  50. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -1
  51. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -1
  52. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +1 -1
  53. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +12 -1
  54. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
  55. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +1 -1
  56. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
  57. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue.js +1 -1
  58. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js +23 -1
  59. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js.map +1 -1
  60. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js +43 -13
  61. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js.map +1 -1
  62. package/dist/esm/composables/indexSources.js +48 -6
  63. package/dist/esm/composables/indexSources.js.map +1 -1
  64. package/dist/esm/composables/useTaskInboxStore.js +4 -3
  65. package/dist/esm/composables/useTaskInboxStore.js.map +1 -1
  66. package/dist/esm/l10n/en.json.js +4 -1
  67. package/dist/esm/l10n/en.json.js.map +1 -1
  68. package/dist/esm/l10n/nl.json.js +7 -1
  69. package/dist/esm/l10n/nl.json.js.map +1 -1
  70. package/dist/esm/library-style.js +1 -1
  71. package/dist/esm/nextcloud-vue.css +604 -407
  72. package/dist/esm/utils/builtInFormatters.js +23 -1
  73. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  74. package/dist/esm/utils/fillTemplate.js +7 -1
  75. package/dist/esm/utils/fillTemplate.js.map +1 -1
  76. package/dist/esm/utils/myTeam.js +24 -4
  77. package/dist/esm/utils/myTeam.js.map +1 -1
  78. package/dist/esm/utils/resolveFilterTokens.js +5 -4
  79. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  80. package/dist/esm/utils/transitionInputs.js +43 -2
  81. package/dist/esm/utils/transitionInputs.js.map +1 -1
  82. package/dist/nextcloud-vue.cjs.js +932 -236
  83. package/dist/nextcloud-vue.cjs.js.map +2 -2
  84. package/dist/nextcloud-vue.css +604 -407
  85. package/l10n/en.json +4 -1
  86. package/l10n/nl.json +7 -1
  87. package/package.json +1 -1
  88. package/src/components/CnAppRoot/CnAppRoot.vue +9 -2
  89. package/src/components/CnCellRenderer/CnCellRenderer.vue +19 -3
  90. package/src/components/CnChartWidget/CnChartWidget.md +1 -0
  91. package/src/components/CnChartWidget/CnChartWidget.vue +76 -1
  92. package/src/components/CnConversationThread/CnConversationWidget.vue +126 -1
  93. package/src/components/CnFactsWidget/CnFactsWidget.vue +78 -9
  94. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +39 -1
  95. package/src/components/CnFilesBrowser/filesBrowser.js +7 -1
  96. package/src/components/CnFilesBrowser/filesBrowserColumns.js +5 -0
  97. package/src/components/CnIndexPage/CnIndexPage.vue +131 -8
  98. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +10 -2
  99. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +28 -3
  100. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +1 -1
  101. package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +10 -0
  102. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +68 -2
  103. package/src/components/CnTransitionInputFields/CnTransitionInputFields.vue +43 -1
  104. package/src/composables/indexSources.js +48 -6
  105. package/src/composables/useTaskInboxStore.js +4 -3
  106. package/src/css/dialog.css +18 -0
  107. package/src/css/look-board-detail.css +24 -0
  108. package/src/css/look-board-index.css +21 -0
  109. package/src/css/look-board.css +31 -2
  110. package/src/css/table.css +13 -0
  111. package/src/utils/builtInFormatters.js +22 -0
  112. package/src/utils/fillTemplate.js +7 -1
  113. package/src/utils/myTeam.js +24 -4
  114. package/src/utils/resolveFilterTokens.js +5 -4
  115. package/src/utils/transitionInputs.js +42 -1
@@ -3602,7 +3602,10 @@ var translations$s = {
3602
3602
  "Search a person or group": "Search a person or group",
3603
3603
  "Now: by {order}.": "Now: by {order}.",
3604
3604
  "You save the order along with a view.": "You save the order along with a view.",
3605
- me: "me"
3605
+ me: "me",
3606
+ "Open profile": "Open profile",
3607
+ "Link a contact from your address book.": "Link a contact from your address book.",
3608
+ "The value could not be shown.": "The value could not be shown."
3606
3609
  };
3607
3610
  var plurals$1 = {
3608
3611
  "{count} member": [
@@ -6612,7 +6615,13 @@ var translations$c = {
6612
6615
  "Search a person or group": "Zoek een persoon of groep",
6613
6616
  "Now: by {order}.": "Nu: op {order}.",
6614
6617
  "You save the order along with a view.": "De sortering bewaart u mee in een weergave.",
6615
- me: "ik"
6618
+ me: "ik",
6619
+ "Open profile": "Profiel openen",
6620
+ "Link a contact from your address book.": "Koppel een contact uit je adresboek.",
6621
+ "The value could not be shown.": "De waarde kon niet worden getoond.",
6622
+ "Save as draft": "Opslaan als concept",
6623
+ "Draft saved at {time}": "Concept opgeslagen om {time}",
6624
+ "The draft could not be saved": "Het concept kon niet worden opgeslagen"
6616
6625
  };
6617
6626
  var plurals = {
6618
6627
  "{count} member": [
@@ -7268,7 +7277,7 @@ const _hoisted_3$74 = {
7268
7277
  class: "cn-ai-history-list__items",
7269
7278
  "data-testid": "cn-ai-history-list-items"
7270
7279
  };
7271
- const _hoisted_4$6l = {
7280
+ const _hoisted_4$6m = {
7272
7281
  key: 0,
7273
7282
  class: "cn-ai-history-list__edit",
7274
7283
  "data-testid": "cn-ai-history-list-edit"
@@ -7338,7 +7347,7 @@ function render$8G(_ctx, _cache, $props, $setup, $data, $options) {
7338
7347
  ]])
7339
7348
  }, [
7340
7349
  ($data.editingUuid === conv.uuid)
7341
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_4$6l, [
7350
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_4$6m, [
7342
7351
  vue$1.createVNode(_component_NcTextField, {
7343
7352
  modelValue: $data.editTitle,
7344
7353
  label: $options.cnTranslate('Name'),
@@ -10021,7 +10030,7 @@ const _hoisted_2$7U = {
10021
10030
  "data-testid": "cn-ai-input-chips"
10022
10031
  };
10023
10032
  const _hoisted_3$73 = { class: "cn-ai-input__chip-name" };
10024
- const _hoisted_4$6k = ["aria-label", "disabled", "data-testid", "onClick"];
10033
+ const _hoisted_4$6l = ["aria-label", "disabled", "data-testid", "onClick"];
10025
10034
  const _hoisted_5$5C = { class: "cn-ai-input__row" };
10026
10035
  const _hoisted_6$5b = ["aria-label", "title", "aria-pressed", "aria-disabled", "disabled", "data-engine"];
10027
10036
  const _hoisted_7$4J = ["aria-label", "title", "aria-pressed", "disabled"];
@@ -10091,7 +10100,7 @@ function render$8F(_ctx, _cache, $props, $setup, $data, $options) {
10091
10100
  onClick: $event => ($options.removeAttachment(index))
10092
10101
  }, [
10093
10102
  vue$1.createVNode(_component_Close, { size: 14 })
10094
- ], 8 /* PROPS */, _hoisted_4$6k)
10103
+ ], 8 /* PROPS */, _hoisted_4$6l)
10095
10104
  ]))
10096
10105
  }), 128 /* KEYED_FRAGMENT */))
10097
10106
  ]))
@@ -10444,7 +10453,7 @@ const _hoisted_3$72 = {
10444
10453
  key: 0,
10445
10454
  class: "cn-ai-message-list__system-text"
10446
10455
  };
10447
- const _hoisted_4$6j = {
10456
+ const _hoisted_4$6k = {
10448
10457
  key: 1,
10449
10458
  class: "cn-ai-message-list__bubble cn-ai-message-list__bubble--user"
10450
10459
  };
@@ -10490,12 +10499,12 @@ const _hoisted_19$1q = {
10490
10499
  class: "cn-ai-message-list__bubble cn-ai-message-list__bubble--assistant",
10491
10500
  "aria-live": "polite"
10492
10501
  };
10493
- const _hoisted_20$1f = {
10502
+ const _hoisted_20$1g = {
10494
10503
  key: 2,
10495
10504
  class: "cn-ai-message-list__item cn-ai-message-list__item--assistant",
10496
10505
  "data-testid": "cn-ai-thinking"
10497
10506
  };
10498
- const _hoisted_21$1d = ["aria-label"];
10507
+ const _hoisted_21$1e = ["aria-label"];
10499
10508
  const _hoisted_22$1a = { class: "cn-ai-message-list__thinking-text" };
10500
10509
 
10501
10510
  function render$8E(_ctx, _cache, $props, $setup, $data, $options) {
@@ -10516,7 +10525,7 @@ function render$8E(_ctx, _cache, $props, $setup, $data, $options) {
10516
10525
  (message.role === 'system')
10517
10526
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_3$72, vue$1.toDisplayString(message.content), 1 /* TEXT */))
10518
10527
  : (message.role === 'user')
10519
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_4$6j, [
10528
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_4$6k, [
10520
10529
  vue$1.createElementVNode("span", _hoisted_5$5B, vue$1.toDisplayString(message.content), 1 /* TEXT */),
10521
10530
  ($options.hasAttachments(message))
10522
10531
  ? (vue$1.openBlock(), vue$1.createElementBlock("ul", _hoisted_6$5a, [
@@ -10628,7 +10637,7 @@ function render$8E(_ctx, _cache, $props, $setup, $data, $options) {
10628
10637
  ]))
10629
10638
  : vue$1.createCommentVNode("v-if", true),
10630
10639
  ($options.isThinking)
10631
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_20$1f, [
10640
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_20$1g, [
10632
10641
  vue$1.createElementVNode("div", {
10633
10642
  class: "cn-ai-message-list__bubble cn-ai-message-list__bubble--assistant cn-ai-message-list__bubble--thinking",
10634
10643
  "aria-label": $options.cnTranslate('AI is thinking'),
@@ -10644,7 +10653,7 @@ function render$8E(_ctx, _cache, $props, $setup, $data, $options) {
10644
10653
  vue$1.createElementVNode("span", { class: "cn-ai-message-list__thinking-dot" }),
10645
10654
  vue$1.createElementVNode("span", { class: "cn-ai-message-list__thinking-dot" })
10646
10655
  ], -1 /* CACHED */))
10647
- ], 8 /* PROPS */, _hoisted_21$1d)
10656
+ ], 8 /* PROPS */, _hoisted_21$1e)
10648
10657
  ]))
10649
10658
  : vue$1.createCommentVNode("v-if", true)
10650
10659
  ]))
@@ -11723,7 +11732,7 @@ const _hoisted_2$7S = {
11723
11732
  class: "cn-ai-chat-window__titlebar"
11724
11733
  };
11725
11734
  const _hoisted_3$71 = ["title"];
11726
- const _hoisted_4$6i = { class: "cn-ai-chat-window__identity-name" };
11735
+ const _hoisted_4$6j = { class: "cn-ai-chat-window__identity-name" };
11727
11736
  const _hoisted_5$5A = { class: "cn-ai-chat-window__controls" };
11728
11737
  const _hoisted_6$59 = {
11729
11738
  key: 1,
@@ -11769,8 +11778,8 @@ const _hoisted_19$1p = {
11769
11778
  role: "note",
11770
11779
  "data-testid": "cn-ai-panel-mark"
11771
11780
  };
11772
- const _hoisted_20$1e = ["src", "alt"];
11773
- const _hoisted_21$1c = { class: "cn-ai-chat-window__mark-label" };
11781
+ const _hoisted_20$1f = ["src", "alt"];
11782
+ const _hoisted_21$1d = { class: "cn-ai-chat-window__mark-label" };
11774
11783
 
11775
11784
  function render$8D(_ctx, _cache, $props, $setup, $data, $options) {
11776
11785
  const _component_CnDashboardIcon = vue$1.resolveComponent("CnDashboardIcon");
@@ -11818,7 +11827,7 @@ function render$8D(_ctx, _cache, $props, $setup, $data, $options) {
11818
11827
  size: 16,
11819
11828
  class: "cn-ai-chat-window__identity-icon"
11820
11829
  }, null, 8 /* PROPS */, ["name"]),
11821
- vue$1.createElementVNode("span", _hoisted_4$6i, vue$1.toDisplayString($options.agentLabel), 1 /* TEXT */)
11830
+ vue$1.createElementVNode("span", _hoisted_4$6j, vue$1.toDisplayString($options.agentLabel), 1 /* TEXT */)
11822
11831
  ], 8 /* PROPS */, _hoisted_3$71),
11823
11832
  vue$1.createElementVNode("div", _hoisted_5$5A, [
11824
11833
  vue$1.createVNode(_component_NcActions, {
@@ -12083,9 +12092,9 @@ function render$8D(_ctx, _cache, $props, $setup, $data, $options) {
12083
12092
  class: "cn-ai-chat-window__mark-logo",
12084
12093
  src: $data.assistantMark.logo.url,
12085
12094
  alt: $data.assistantMark.logo.alt
12086
- }, null, 8 /* PROPS */, _hoisted_20$1e))
12095
+ }, null, 8 /* PROPS */, _hoisted_20$1f))
12087
12096
  : vue$1.createCommentVNode("v-if", true),
12088
- vue$1.createElementVNode("span", _hoisted_21$1c, vue$1.toDisplayString($data.assistantMark.label), 1 /* TEXT */)
12097
+ vue$1.createElementVNode("span", _hoisted_21$1d, vue$1.toDisplayString($data.assistantMark.label), 1 /* TEXT */)
12089
12098
  ]))
12090
12099
  : vue$1.createCommentVNode("v-if", true)
12091
12100
  ], 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_1$8c))
@@ -14619,12 +14628,32 @@ let _source = null;
14619
14628
  /** The read in flight, shared by every caller. */
14620
14629
  let _pending = null;
14621
14630
 
14631
+ /**
14632
+ * The team the app answered, as kept: one group id, a list of two or more,
14633
+ * or `''` for none. Blank entries are dropped and a list of one reads as that
14634
+ * one id.
14635
+ *
14636
+ * @spec openspec/changes/my-team-source-may-answer-several-teams/specs/schema-utilities/spec.md#requirement-myteam-may-resolve-to-several-teams
14637
+ * @param {unknown} team What the app's loader answered.
14638
+ * @return {string|string[]} The kept team.
14639
+ */
14640
+ function normalizeTeam(team) {
14641
+ if (Array.isArray(team)) {
14642
+ const ids = team.filter((id) => typeof id === 'string').map((id) => id.trim()).filter((id) => id !== '');
14643
+ if (ids.length === 0) {
14644
+ return ''
14645
+ }
14646
+ return ids.length === 1 ? ids[0] : ids
14647
+ }
14648
+ return typeof team === 'string' ? team.trim() : ''
14649
+ }
14650
+
14622
14651
  /**
14623
14652
  * Name where the current user's primary team is read: a function answering
14624
14653
  * the team's Nextcloud group id, or null/'' for a user in no team.
14625
14654
  *
14626
14655
  * @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
14627
- * @param {(() => Promise<string|null>)|null} loader The app's loader, or null to unregister.
14656
+ * @param {(() => Promise<string|string[]|null>)|null} loader The app's loader (a group id, or the ids of several teams), or null to unregister.
14628
14657
  * @return {void}
14629
14658
  */
14630
14659
  function registerMyTeamSource(loader) {
@@ -14638,7 +14667,7 @@ function registerMyTeamSource(loader) {
14638
14667
  * when no source is registered, or when the read fails.
14639
14668
  *
14640
14669
  * @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
14641
- * @return {Promise<string>} The team's group id, or ''.
14670
+ * @return {Promise<string|string[]>} The team's group id (or ids), or ''.
14642
14671
  */
14643
14672
  function loadMyTeam() {
14644
14673
  if (_state.team !== null) {
@@ -14653,7 +14682,7 @@ function loadMyTeam() {
14653
14682
  }
14654
14683
  _pending = Promise.resolve()
14655
14684
  .then(() => _source())
14656
- .then((team) => (typeof team === 'string' ? team.trim() : ''))
14685
+ .then((team) => normalizeTeam(team))
14657
14686
  .catch(() => '')
14658
14687
  .then((team) => {
14659
14688
  _state.team = team;
@@ -14668,7 +14697,7 @@ function loadMyTeam() {
14668
14697
  * it is being read (and starts the read on first use).
14669
14698
  *
14670
14699
  * @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
14671
- * @return {string|null} The team, '' or null.
14700
+ * @return {string|string[]|null} The team (or teams), '' or null.
14672
14701
  */
14673
14702
  function peekMyTeam() {
14674
14703
  if (_state.team === null) {
@@ -15353,15 +15382,16 @@ const MY_GROUPS_TOKEN = '@myGroups';
15353
15382
  const MY_TEAM_TOKEN = '@myTeam';
15354
15383
 
15355
15384
  /**
15356
- * Resolve `@myTeam`: the team's group id; `@myTeam` while it is being read (the
15385
+ * Resolve `@myTeam`: the team's group id, or the ids of several teams when the
15386
+ * app answers a list (an IN filter); `@myTeam` while it is being read (the
15357
15387
  * caller waits); `@myTeam?` for a user in no team (the caller drops the key).
15358
15388
  *
15359
15389
  * @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
15360
- * @param {{myTeam?: string}} [ctx] Optional context; `ctx.myTeam` wins.
15361
- * @return {string} The group id or the token.
15390
+ * @param {{myTeam?: (string|string[])}} [ctx] Optional context; `ctx.myTeam` wins.
15391
+ * @return {string|string[]} The group id(s) or the token.
15362
15392
  */
15363
15393
  function resolveMyTeam(ctx) {
15364
- const team = ctx && typeof ctx.myTeam === 'string' ? ctx.myTeam : peekMyTeam();
15394
+ const team = ctx && (typeof ctx.myTeam === 'string' || Array.isArray(ctx.myTeam)) ? ctx.myTeam : peekMyTeam();
15365
15395
  if (team === null) {
15366
15396
  return MY_TEAM_TOKEN
15367
15397
  }
@@ -24266,7 +24296,7 @@ function formatterFor(vf) {
24266
24296
  var script$8z = {
24267
24297
  name: 'CnChartWidget',
24268
24298
 
24269
- components: { CnEndpointNotice: __sfc_main$8u },
24299
+ components: { CnEndpointNotice: __sfc_main$8u, NcNoteCard: vue.NcNoteCard },
24270
24300
 
24271
24301
  inject: {
24272
24302
  /**
@@ -24328,6 +24358,24 @@ var script$8z = {
24328
24358
  default: null,
24329
24359
  },
24330
24360
 
24361
+ /**
24362
+ * A toned note card under the chart whose words carry values from the
24363
+ * page record: `{ lead?, text?, values?, labels?, tone? }`. `lead`
24364
+ * (bold) and `text` go through the host's translate, then each
24365
+ * `{name}` is filled from `values[name]`, a literal or a token such as
24366
+ * `@object.recommendedIntervention`. `labels[name]` turns a key into
24367
+ * words: `"rows"`, `"columns"` or `"values"` read the matrix's
24368
+ * cellSource label maps, an object is its own map. `tone` is info
24369
+ * (default), success, warning or error. Hidden while a value is
24370
+ * missing, so a half-filled sentence never shows.
24371
+ *
24372
+ * @spec openspec/changes/chart-notice/specs/chart-notice/spec.md#requirement-a-chart-can-carry-a-notice-with-record-values
24373
+ */
24374
+ notice: {
24375
+ type: Object,
24376
+ default: null,
24377
+ },
24378
+
24331
24379
  /**
24332
24380
  * A line under the chart ("Klik een balk om de zaken te zien"),
24333
24381
  * through the host's translate: under the rows of a `bar-list` or
@@ -25462,6 +25510,38 @@ var script$8z = {
25462
25510
  }
25463
25511
  },
25464
25512
 
25513
+ /**
25514
+ * The notice's bold lead, text and note-card type, values filled in;
25515
+ * null when there is none or a value is missing.
25516
+ *
25517
+ * @return {{lead: string, text: string, type: string}|null} The notice.
25518
+ * @spec openspec/changes/chart-notice/specs/chart-notice/spec.md#requirement-a-chart-can-carry-a-notice-with-record-values
25519
+ */
25520
+ noticeParts() {
25521
+ const def = this.notice;
25522
+ if (!def || typeof def !== 'object' || (!def.lead && !def.text)) {
25523
+ return null
25524
+ }
25525
+ const declared = def.values && typeof def.values === 'object' ? def.values : {};
25526
+ const resolved = resolveFilterTokens(declared, this.chartTokenCtx());
25527
+ const source = this.cellSource && typeof this.cellSource === 'object' ? this.cellSource : {};
25528
+ const maps = { rows: source.rowLabels, columns: source.columnLabels, values: source.valueLabels };
25529
+ const words = {};
25530
+ for (const name of Object.keys(declared)) {
25531
+ const raw = resolved[name];
25532
+ if (raw === null || raw === undefined || raw === '' || (typeof raw === 'string' && raw.charAt(0) === '@')) {
25533
+ return null
25534
+ }
25535
+ const ref = def.labels && def.labels[name];
25536
+ const map = typeof ref === 'string' ? maps[ref] : ref;
25537
+ const label = map && typeof map === 'object' && typeof map[String(raw)] === 'string' ? map[String(raw)] : null;
25538
+ words[name] = label ? this.hostTranslate(label) : String(raw);
25539
+ }
25540
+ const fill = (key) => (key ? this.hostTranslate(key).replace(/\{(\w+)\}/g, (match, name) => (name in words ? words[name] : match)) : '');
25541
+ const type = ['info', 'success', 'warning', 'error'].includes(def.tone) ? def.tone : 'info';
25542
+ return { lead: fill(def.lead), text: fill(def.text), type }
25543
+ },
25544
+
25465
25545
  /**
25466
25546
  * The matrix cell of the record (`highlight: { row, column }`, tokens
25467
25547
  * such as `@object.behaviour` resolved), as row and column indexes. A
@@ -26671,7 +26751,7 @@ const _hoisted_3$6$ = {
26671
26751
  class: "cn-chart-widget__empty",
26672
26752
  "data-testid": "cn-chart-widget-empty"
26673
26753
  };
26674
- const _hoisted_4$6h = {
26754
+ const _hoisted_4$6i = {
26675
26755
  key: 5,
26676
26756
  class: "cn-chart-widget__html",
26677
26757
  "data-testid": "cn-chart-widget-bar-list"
@@ -26720,9 +26800,12 @@ const _hoisted_19$1o = {
26720
26800
  class: "cn-chart-widget__footnote",
26721
26801
  "data-testid": "cn-chart-widget-footnote"
26722
26802
  };
26803
+ const _hoisted_20$1e = { class: "cn-chart-widget__notice-text" };
26804
+ const _hoisted_21$1c = { key: 0 };
26723
26805
 
26724
26806
  function render$8y(_ctx, _cache, $props, $setup, $data, $options) {
26725
26807
  const _component_CnEndpointNotice = vue$1.resolveComponent("CnEndpointNotice");
26808
+ const _component_NcNoteCard = vue$1.resolveComponent("NcNoteCard");
26726
26809
 
26727
26810
  return (vue$1.openBlock(), vue$1.createElementBlock("div", {
26728
26811
  class: vue$1.normalizeClass(["cn-chart-widget", { 'cn-chart-widget--fit': $options.fitToContainer }])
@@ -26771,7 +26854,7 @@ function render$8y(_ctx, _cache, $props, $setup, $data, $options) {
26771
26854
  : ($options.showEmptyState)
26772
26855
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_3$6$, vue$1.toDisplayString($options.hostTranslate($props.emptyLabel)), 1 /* TEXT */))
26773
26856
  : ($props.type === 'bar-list')
26774
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_4$6h, [
26857
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_4$6i, [
26775
26858
  vue$1.createElementVNode("ul", _hoisted_5$5z, [
26776
26859
  (vue$1.openBlock(true), vue$1.createElementBlock(vue$1.Fragment, null, vue$1.renderList($options.barListRows, (row) => {
26777
26860
  return (vue$1.openBlock(), vue$1.createElementBlock("li", {
@@ -26866,12 +26949,31 @@ function render$8y(_ctx, _cache, $props, $setup, $data, $options) {
26866
26949
  ])),
26867
26950
  ($props.footnote && $data.chartComponent && !$options.showEmptyState && $props.type !== 'bar-list' && $props.type !== 'matrix')
26868
26951
  ? (vue$1.openBlock(), vue$1.createElementBlock("p", _hoisted_19$1o, vue$1.toDisplayString($options.hostTranslate($props.footnote)), 1 /* TEXT */))
26952
+ : vue$1.createCommentVNode("v-if", true),
26953
+ ($options.noticeParts)
26954
+ ? (vue$1.openBlock(), vue$1.createBlock(_component_NcNoteCard, {
26955
+ key: 10,
26956
+ type: $options.noticeParts.type,
26957
+ "data-tone": $options.noticeParts.type,
26958
+ class: "cn-chart-widget__notice",
26959
+ "data-testid": "cn-chart-widget-notice"
26960
+ }, {
26961
+ default: vue$1.withCtx(() => [
26962
+ vue$1.createElementVNode("p", _hoisted_20$1e, [
26963
+ ($options.noticeParts.lead)
26964
+ ? (vue$1.openBlock(), vue$1.createElementBlock("strong", _hoisted_21$1c, vue$1.toDisplayString($options.noticeParts.lead), 1 /* TEXT */))
26965
+ : vue$1.createCommentVNode("v-if", true),
26966
+ vue$1.createTextVNode(vue$1.toDisplayString($options.noticeParts.lead && $options.noticeParts.text ? ' ' : '') + vue$1.toDisplayString($options.noticeParts.text), 1 /* TEXT */)
26967
+ ])
26968
+ ]),
26969
+ _: 1 /* STABLE */
26970
+ }, 8 /* PROPS */, ["type", "data-tone"]))
26869
26971
  : vue$1.createCommentVNode("v-if", true)
26870
26972
  ], 2 /* CLASS */))
26871
26973
  }
26872
26974
 
26873
26975
  script$8z.render = render$8y;
26874
- script$8z.__scopeId = "data-v-8e52ae5c";
26976
+ script$8z.__scopeId = "data-v-4e61b5f0";
26875
26977
  const __sfc_main$8t = script$8z;
26876
26978
 
26877
26979
  /**
@@ -27795,7 +27897,7 @@ var script$8v = {
27795
27897
  const _hoisted_1$84 = { class: "cn-chart-widget-form" };
27796
27898
  const _hoisted_2$7N = { class: "cn-chart-widget-form__section" };
27797
27899
  const _hoisted_3$6_ = { class: "cn-chart-widget-form__row2" };
27798
- const _hoisted_4$6g = { class: "cn-chart-widget-form__section" };
27900
+ const _hoisted_4$6h = { class: "cn-chart-widget-form__section" };
27799
27901
  const _hoisted_5$5y = { class: "cn-chart-widget-form__row2" };
27800
27902
  const _hoisted_6$57 = {
27801
27903
  key: 0,
@@ -27844,7 +27946,7 @@ function render$8u(_ctx, _cache, $props, $setup, $data, $options) {
27844
27946
  _: 1 /* STABLE */
27845
27947
  }, 8 /* PROPS */, ["modelValue", "options", "inputLabel"])
27846
27948
  ]),
27847
- vue$1.createElementVNode("h4", _hoisted_4$6g, vue$1.toDisplayString($options.t('nextcloud-vue', 'Data source')), 1 /* TEXT */),
27949
+ vue$1.createElementVNode("h4", _hoisted_4$6h, vue$1.toDisplayString($options.t('nextcloud-vue', 'Data source')), 1 /* TEXT */),
27848
27950
  vue$1.createElementVNode("div", _hoisted_5$5y, [
27849
27951
  vue$1.createVNode(_component_CnRegisterSchemaSelect, {
27850
27952
  register: $data.source.register,
@@ -29277,7 +29379,7 @@ const _hoisted_3$6Z = {
29277
29379
  key: 0,
29278
29380
  class: "cn-quick-filter-bar__count"
29279
29381
  };
29280
- const _hoisted_4$6f = {
29382
+ const _hoisted_4$6g = {
29281
29383
  class: "cn-quick-filter-bar__tab cn-quick-filter-bar__more",
29282
29384
  "data-measure": "more"
29283
29385
  };
@@ -29352,7 +29454,7 @@ function render$8r(_ctx, _cache, $props, $setup, $data, $options) {
29352
29454
  : vue$1.createCommentVNode("v-if", true)
29353
29455
  ], 2 /* CLASS */))
29354
29456
  }), 128 /* KEYED_FRAGMENT */)),
29355
- vue$1.createElementVNode("span", _hoisted_4$6f, [
29457
+ vue$1.createElementVNode("span", _hoisted_4$6g, [
29356
29458
  ($options.overflowLabel)
29357
29459
  ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_5$5x, vue$1.toDisplayString($options.overflowLabel), 1 /* TEXT */))
29358
29460
  : vue$1.createCommentVNode("v-if", true),
@@ -29907,7 +30009,7 @@ const _hoisted_3$6Y = {
29907
30009
  key: 0,
29908
30010
  class: "cn-conversation-thread__heading"
29909
30011
  };
29910
- const _hoisted_4$6e = {
30012
+ const _hoisted_4$6f = {
29911
30013
  key: 0,
29912
30014
  class: "cn-conversation-thread__subtitle",
29913
30015
  "data-testid": "cn-conversation-subtitle"
@@ -29973,15 +30075,15 @@ const _hoisted_22$19 = {
29973
30075
  class: "cn-conversation-thread__message-actions"
29974
30076
  };
29975
30077
  const _hoisted_23$_ = { class: "cn-conversation-thread__reply-head" };
29976
- const _hoisted_24$T = ["for"];
29977
- const _hoisted_25$M = {
30078
+ const _hoisted_24$U = ["for"];
30079
+ const _hoisted_25$N = {
29978
30080
  key: 0,
29979
30081
  class: "cn-conversation-thread__reply-hint",
29980
30082
  "data-testid": "cn-conversation-reply-hint"
29981
30083
  };
29982
- const _hoisted_26$G = ["id", "placeholder", "disabled", "aria-describedby"];
30084
+ const _hoisted_26$H = ["id", "placeholder", "disabled", "aria-describedby"];
29983
30085
  const _hoisted_27$y = ["id"];
29984
- const _hoisted_28$v = { class: "cn-conversation-thread__reply-foot" };
30086
+ const _hoisted_28$w = { class: "cn-conversation-thread__reply-foot" };
29985
30087
  const _hoisted_29$u = {
29986
30088
  key: 0,
29987
30089
  class: "cn-conversation-thread__reply-option",
@@ -30015,7 +30117,7 @@ function render$8q(_ctx, _cache, $props, $setup, $data, $options) {
30015
30117
  _: 1 /* STABLE */
30016
30118
  }, 8 /* PROPS */, ["id"])),
30017
30119
  ($props.subtitle)
30018
- ? (vue$1.openBlock(), vue$1.createElementBlock("p", _hoisted_4$6e, vue$1.toDisplayString($props.subtitle), 1 /* TEXT */))
30120
+ ? (vue$1.openBlock(), vue$1.createElementBlock("p", _hoisted_4$6f, vue$1.toDisplayString($props.subtitle), 1 /* TEXT */))
30019
30121
  : vue$1.createCommentVNode("v-if", true)
30020
30122
  ]))
30021
30123
  : vue$1.createCommentVNode("v-if", true),
@@ -30109,9 +30211,9 @@ function render$8q(_ctx, _cache, $props, $setup, $data, $options) {
30109
30211
  vue$1.createElementVNode("label", {
30110
30212
  for: $data.inputId,
30111
30213
  class: "cn-conversation-thread__reply-label"
30112
- }, vue$1.toDisplayString($props.replyLabel), 9 /* TEXT, PROPS */, _hoisted_24$T),
30214
+ }, vue$1.toDisplayString($props.replyLabel), 9 /* TEXT, PROPS */, _hoisted_24$U),
30113
30215
  ($props.replyHint)
30114
- ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_25$M, vue$1.toDisplayString($props.replyHint), 1 /* TEXT */))
30216
+ ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_25$N, vue$1.toDisplayString($props.replyHint), 1 /* TEXT */))
30115
30217
  : vue$1.createCommentVNode("v-if", true)
30116
30218
  ]),
30117
30219
  vue$1.withDirectives(vue$1.createElementVNode("textarea", {
@@ -30127,7 +30229,7 @@ function render$8q(_ctx, _cache, $props, $setup, $data, $options) {
30127
30229
  _cache[1] || (_cache[1] = vue$1.withKeys(vue$1.withModifiers((...args) => ($options.onSend && $options.onSend(...args)), ["ctrl","prevent"]), ["enter"])),
30128
30230
  _cache[2] || (_cache[2] = vue$1.withKeys(vue$1.withModifiers((...args) => ($options.onSend && $options.onSend(...args)), ["meta","prevent"]), ["enter"]))
30129
30231
  ]
30130
- }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_26$G), [
30232
+ }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_26$H), [
30131
30233
  [vue$1.vModelText, $data.draft]
30132
30234
  ]),
30133
30235
  ($data.errorMessage)
@@ -30139,7 +30241,7 @@ function render$8q(_ctx, _cache, $props, $setup, $data, $options) {
30139
30241
  "data-testid": "cn-conversation-error"
30140
30242
  }, vue$1.toDisplayString($data.errorMessage), 9 /* TEXT, PROPS */, _hoisted_27$y))
30141
30243
  : vue$1.createCommentVNode("v-if", true),
30142
- vue$1.createElementVNode("div", _hoisted_28$v, [
30244
+ vue$1.createElementVNode("div", _hoisted_28$w, [
30143
30245
  ($options.optionShown)
30144
30246
  ? (vue$1.openBlock(), vue$1.createElementBlock("label", _hoisted_29$u, [
30145
30247
  vue$1.withDirectives(vue$1.createElementVNode("input", {
@@ -30528,6 +30630,10 @@ var script$8q = {
30528
30630
  acting: '',
30529
30631
  /** The threads `statusBadge` reads. */
30530
30632
  badgeRows: [],
30633
+ /** What the last draft save said: when it was saved, or that it failed. */
30634
+ draftStatus: '',
30635
+ /** Whether a draft is being saved. */
30636
+ savingDraft: false,
30531
30637
  }
30532
30638
  },
30533
30639
 
@@ -30665,6 +30771,21 @@ var script$8q = {
30665
30771
  * @return {string} The URL, or '' when the content names none.
30666
30772
  * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread
30667
30773
  */
30774
+ /**
30775
+ * The reply draft's url (`content.draft.url`, `@objectId` filled in),
30776
+ * or '' without one.
30777
+ *
30778
+ * @return {string}
30779
+ * @spec openspec/changes/conversation-reply-draft/specs/conversation-reply-draft/spec.md#requirement-a-reply-can-be-saved-as-a-draft-and-comes-back
30780
+ */
30781
+ draftUrl() {
30782
+ const url = this.content.draft && this.content.draft.url;
30783
+ if (typeof url !== 'string' || url === '') {
30784
+ return ''
30785
+ }
30786
+ return url.replace(/@objectId/g, encodeURIComponent(String(this.objectId ?? '')))
30787
+ },
30788
+
30668
30789
  endpoint() {
30669
30790
  const url = this.content.endpoint;
30670
30791
  if (typeof url !== 'string' || url === '') {
@@ -30690,19 +30811,101 @@ var script$8q = {
30690
30811
  handler() {
30691
30812
  this.loadSource();
30692
30813
  this.loadBadge();
30814
+ this.loadDraft();
30693
30815
  },
30694
30816
  },
30695
30817
  },
30696
30818
 
30697
30819
  mounted() {
30698
30820
  eventBus.subscribe('cn:page:refresh', this.onPageRefresh);
30821
+ eventBus.subscribe('cn:conversation:reply-sent', this.onReplySentElsewhere);
30699
30822
  },
30700
30823
 
30701
30824
  beforeUnmount() {
30702
30825
  eventBus.unsubscribe('cn:page:refresh', this.onPageRefresh);
30826
+ eventBus.unsubscribe('cn:conversation:reply-sent', this.onReplySentElsewhere);
30703
30827
  },
30704
30828
 
30705
30829
  methods: {
30830
+ t: l10n.translate,
30831
+
30832
+ /**
30833
+ * Read the saved reply draft into the reply box.
30834
+ *
30835
+ * @return {Promise<void>}
30836
+ * @spec openspec/changes/conversation-reply-draft/specs/conversation-reply-draft/spec.md#requirement-a-reply-can-be-saved-as-a-draft-and-comes-back
30837
+ */
30838
+ async loadDraft() {
30839
+ if (!this.draftUrl) {
30840
+ return
30841
+ }
30842
+ try {
30843
+ const { default: axios } = await import('@nextcloud/axios');
30844
+ const { generateUrl } = await import('@nextcloud/router');
30845
+ const { data } = await axios.get(generateUrl(this.draftUrl));
30846
+ const text = data && typeof data.text === 'string' ? data.text : '';
30847
+ if (text !== '' && this.$refs.thread) {
30848
+ this.$refs.thread.setDraft(text);
30849
+ }
30850
+ } catch {
30851
+ // No saved draft to read leaves the box empty.
30852
+ }
30853
+ },
30854
+
30855
+ /**
30856
+ * Store the reply box as the draft and say when it was saved.
30857
+ *
30858
+ * @param {string} text The reply box's text.
30859
+ * @return {Promise<void>}
30860
+ */
30861
+ async saveDraft(text) {
30862
+ this.savingDraft = true;
30863
+ try {
30864
+ await this.storeDraft(String(text ?? ''));
30865
+ this.draftStatus = l10n.translate('nextcloud-vue', 'Draft saved at {time}', { time: formatTimeOfDay(new Date()) });
30866
+ } catch {
30867
+ this.draftStatus = l10n.translate('nextcloud-vue', 'The draft could not be saved');
30868
+ } finally {
30869
+ this.savingDraft = false;
30870
+ }
30871
+ },
30872
+
30873
+ /**
30874
+ * A reply on this record went out through a dialog or another surface
30875
+ * (`cn:conversation:reply-sent` with `{ objectId }`): the saved draft
30876
+ * and the reply box are cleared.
30877
+ *
30878
+ * @param {{objectId?: string}} payload The record the reply was on.
30879
+ * @return {Promise<void>}
30880
+ * @spec openspec/changes/conversation-reply-draft/specs/conversation-reply-draft/spec.md#requirement-a-reply-can-be-saved-as-a-draft-and-comes-back
30881
+ */
30882
+ async onReplySentElsewhere(payload) {
30883
+ if (!this.draftUrl || String(payload?.objectId ?? '') !== String(this.objectId ?? '')) {
30884
+ return
30885
+ }
30886
+ if (this.$refs.thread) {
30887
+ this.$refs.thread.setDraft('');
30888
+ }
30889
+ this.draftStatus = '';
30890
+ try {
30891
+ await this.storeDraft('');
30892
+ } catch {
30893
+ // The next save overwrites a draft that could not be cleared.
30894
+ }
30895
+ },
30896
+
30897
+ /**
30898
+ * PUT the draft text.
30899
+ *
30900
+ * @param {string} text The text; '' clears the draft.
30901
+ * @return {Promise<void>}
30902
+ */
30903
+ async storeDraft(text) {
30904
+ const { default: axios } = await import('@nextcloud/axios');
30905
+ const { generateUrl } = await import('@nextcloud/router');
30906
+ await axios.put(generateUrl(this.draftUrl), { text });
30907
+ },
30908
+
30706
30909
  /**
30707
30910
  * Read the messages again when the page refreshes (a dialog sent one).
30708
30911
  *
@@ -31041,6 +31244,9 @@ var script$8q = {
31041
31244
  if (typeof text !== 'string' || text === '') {
31042
31245
  return
31043
31246
  }
31247
+ if (this.draftUrl) {
31248
+ this.storeDraft('');
31249
+ }
31044
31250
  this.sentHere.push({
31045
31251
  id: `sent-${this.sentHere.length}`,
31046
31252
  time: new Date().toISOString(),
@@ -31053,11 +31259,17 @@ var script$8q = {
31053
31259
 
31054
31260
  const _hoisted_1$80 = { class: "cn-case-widget-card" };
31055
31261
  const _hoisted_2$7K = {
31262
+ key: 0,
31263
+ class: "cn-conversation-widget__draft-status",
31264
+ role: "status",
31265
+ "data-testid": "cn-conversation-draft-status"
31266
+ };
31267
+ const _hoisted_3$6X = {
31056
31268
  key: 0,
31057
31269
  class: "cn-conversation-widget__badge",
31058
31270
  "data-testid": "cn-conversation-status-badge"
31059
31271
  };
31060
- const _hoisted_3$6X = {
31272
+ const _hoisted_4$6e = {
31061
31273
  key: 0,
31062
31274
  class: "cn-conversation-widget__error",
31063
31275
  role: "alert",
@@ -31067,12 +31279,13 @@ const _hoisted_3$6X = {
31067
31279
  function render$8p(_ctx, _cache, $props, $setup, $data, $options) {
31068
31280
  const _component_CnTemplateSelect = vue$1.resolveComponent("CnTemplateSelect");
31069
31281
  const _component_NcCheckboxRadioSwitch = vue$1.resolveComponent("NcCheckboxRadioSwitch");
31070
- const _component_CnIcon = vue$1.resolveComponent("CnIcon");
31071
31282
  const _component_NcButton = vue$1.resolveComponent("NcButton");
31283
+ const _component_CnIcon = vue$1.resolveComponent("CnIcon");
31072
31284
  const _component_CnConversationThread = vue$1.resolveComponent("CnConversationThread");
31073
31285
 
31074
31286
  return (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_1$80, [
31075
31287
  vue$1.createVNode(_component_CnConversationThread, {
31288
+ ref: "thread",
31076
31289
  messages: $options.messages,
31077
31290
  title: $options.tr($props.content.title || ''),
31078
31291
  subtitle: $options.tr($props.content.subtitle || ''),
@@ -31109,10 +31322,10 @@ function render$8p(_ctx, _cache, $props, $setup, $data, $options) {
31109
31322
  key: "0"
31110
31323
  }
31111
31324
  : undefined,
31112
- ($options.replyOptions.length > 0)
31325
+ ($options.replyOptions.length > 0 || $options.draftUrl)
31113
31326
  ? {
31114
31327
  name: "reply-actions",
31115
- fn: vue$1.withCtx(() => [
31328
+ fn: vue$1.withCtx(({ draft }) => [
31116
31329
  (vue$1.openBlock(true), vue$1.createElementBlock(vue$1.Fragment, null, vue$1.renderList($options.replyOptions, (option) => {
31117
31330
  return (vue$1.openBlock(), vue$1.createBlock(_component_NcCheckboxRadioSwitch, {
31118
31331
  key: option.key,
@@ -31125,7 +31338,23 @@ function render$8p(_ctx, _cache, $props, $setup, $data, $options) {
31125
31338
  ]),
31126
31339
  _: 2 /* DYNAMIC */
31127
31340
  }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["modelValue", "onUpdate:modelValue", "data-testid"]))
31128
- }), 128 /* KEYED_FRAGMENT */))
31341
+ }), 128 /* KEYED_FRAGMENT */)),
31342
+ ($options.draftUrl && $data.draftStatus)
31343
+ ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_2$7K, vue$1.toDisplayString($data.draftStatus), 1 /* TEXT */))
31344
+ : vue$1.createCommentVNode("v-if", true),
31345
+ ($options.draftUrl)
31346
+ ? (vue$1.openBlock(), vue$1.createBlock(_component_NcButton, {
31347
+ key: 1,
31348
+ "data-testid": "cn-conversation-save-draft",
31349
+ disabled: $data.savingDraft,
31350
+ onClick: $event => ($options.saveDraft(draft))
31351
+ }, {
31352
+ default: vue$1.withCtx(() => [
31353
+ vue$1.createTextVNode(vue$1.toDisplayString($options.t('nextcloud-vue', 'Save as draft')), 1 /* TEXT */)
31354
+ ]),
31355
+ _: 1 /* STABLE */
31356
+ }, 8 /* PROPS */, ["disabled", "onClick"]))
31357
+ : vue$1.createCommentVNode("v-if", true)
31129
31358
  ]),
31130
31359
  key: "1"
31131
31360
  }
@@ -31135,7 +31364,7 @@ function render$8p(_ctx, _cache, $props, $setup, $data, $options) {
31135
31364
  name: "header-actions",
31136
31365
  fn: vue$1.withCtx(() => [
31137
31366
  ($options.statusBadgeText)
31138
- ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_2$7K, vue$1.toDisplayString($options.statusBadgeText), 1 /* TEXT */))
31367
+ ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_3$6X, vue$1.toDisplayString($options.statusBadgeText), 1 /* TEXT */))
31139
31368
  : vue$1.createCommentVNode("v-if", true),
31140
31369
  ($options.headerAction)
31141
31370
  ? (vue$1.openBlock(), vue$1.createBlock(_component_NcButton, {
@@ -31168,13 +31397,13 @@ function render$8p(_ctx, _cache, $props, $setup, $data, $options) {
31168
31397
  : undefined
31169
31398
  ]), 1032 /* PROPS, DYNAMIC_SLOTS */, ["messages", "title", "subtitle", "showDirection", "allowReply", "replyLabel", "placeholder", "sendLabel", "replyHint", "replyOption", "usLabel", "endpoint", "loading", "emptyText", "channelFilter", "channels", "payload", "onSend", "onSent", "onMessageAction"]),
31170
31399
  ($data.actionError)
31171
- ? (vue$1.openBlock(), vue$1.createElementBlock("p", _hoisted_3$6X, vue$1.toDisplayString($data.actionError), 1 /* TEXT */))
31400
+ ? (vue$1.openBlock(), vue$1.createElementBlock("p", _hoisted_4$6e, vue$1.toDisplayString($data.actionError), 1 /* TEXT */))
31172
31401
  : vue$1.createCommentVNode("v-if", true)
31173
31402
  ]))
31174
31403
  }
31175
31404
 
31176
31405
  script$8q.render = render$8p;
31177
- script$8q.__scopeId = "data-v-c7d9637a";
31406
+ script$8q.__scopeId = "data-v-63984d97";
31178
31407
  const __sfc_main$8k = script$8q;
31179
31408
 
31180
31409
  /**
@@ -41464,7 +41693,7 @@ function formatLongDate(value, now = new Date(), withYear = false) {
41464
41693
  * Format one token value.
41465
41694
  *
41466
41695
  * @param {unknown} value The raw value.
41467
- * @param {string} format `date`, `datetime`, `weekday`, `longdate`, `time`, `user`, `currency`, `relative`, `relativetime`, `ago`, `week`, `days`, `lower` or '' (as is).
41696
+ * @param {string} format `date`, `datetime`, `weekday`, `longdate`, `time`, `user`, `currency`, `relative`, `relativetime`, `ago`, `week`, `days`, `lower`, `mailbox` or '' (as is).
41468
41697
  * @param {Date} now The reference moment.
41469
41698
  * @param {string} [arg] The format's argument (`currency:EUR`, or a row path holding the code).
41470
41699
  * @param {object|null} [object] The row, for a currency read from a field.
@@ -41525,6 +41754,12 @@ function formatValue(value, format, now, arg = '', object = null, config = null,
41525
41754
  if (format === 'ago') {
41526
41755
  return formatAgo(value, now)
41527
41756
  }
41757
+ if (format === 'mailbox') {
41758
+ // An address up to and with its @ ("gemeente@" from gemeente@zuiddrecht.nl).
41759
+ const text = String(value);
41760
+ const at = text.indexOf('@');
41761
+ return at > 0 ? text.slice(0, at + 1) : text
41762
+ }
41528
41763
  if (format === 'lower') {
41529
41764
  return String(value).toLocaleLowerCase()
41530
41765
  }
@@ -45411,6 +45646,22 @@ var script$85 = {
45411
45646
  * @return {string}
45412
45647
  * @spec openspec/changes/cell-labels-and-draft-indicator/specs/cell-labels-and-draft-indicator/spec.md#requirement-a-built-in-cell-widget-shows-an-enum-value-by-its-label
45413
45648
  */
45649
+ /**
45650
+ * What a `label` or `badge` cell shows: its label, worded by
45651
+ * `widgetProps.textTemplate` when given (translated, filled from the
45652
+ * row, `{value}` the label; DqPostvak "Lijkt op 2026-0079").
45653
+ *
45654
+ * @return {string}
45655
+ * @spec openspec/changes/cell-text-template/specs/cell-text-template/spec.md
45656
+ */
45657
+ shownLabel() {
45658
+ const template = this.widgetProps && this.widgetProps.textTemplate;
45659
+ if (typeof template !== 'string' || template === '') {
45660
+ return this.labelledValue
45661
+ }
45662
+ return fillTemplate(this.cnTranslate(template), { ...(this.row || {}), value: this.labelledValue })
45663
+ },
45664
+
45414
45665
  labelledValue() {
45415
45666
  if (!this.hasFormatter && this.isEnum && this.hasValue && typeof this.value !== 'object') {
45416
45667
  return this.enumLabel
@@ -46014,18 +46265,18 @@ const _hoisted_22$17 = {
46014
46265
  "aria-hidden": "true"
46015
46266
  };
46016
46267
  const _hoisted_23$Y = { class: "cn-cell-renderer__avatar-text" };
46017
- const _hoisted_24$S = ["title"];
46018
- const _hoisted_25$L = {
46268
+ const _hoisted_24$T = ["title"];
46269
+ const _hoisted_25$M = {
46019
46270
  key: 0,
46020
46271
  class: "cn-cell-renderer__avatar-secondary",
46021
46272
  "data-testid": "cn-cell-secondary"
46022
46273
  };
46023
- const _hoisted_26$F = {
46274
+ const _hoisted_26$G = {
46024
46275
  key: 1,
46025
46276
  class: "cn-cell-renderer__dash"
46026
46277
  };
46027
46278
  const _hoisted_27$x = ["datetime", "title"];
46028
- const _hoisted_28$u = {
46279
+ const _hoisted_28$v = {
46029
46280
  key: 1,
46030
46281
  class: "cn-cell-renderer__dash"
46031
46282
  };
@@ -46188,7 +46439,7 @@ function render$84(_ctx, _cache, $props, $setup, $data, $options) {
46188
46439
  ? (vue$1.openBlock(), vue$1.createElementBlock("span", {
46189
46440
  key: 0,
46190
46441
  title: $options.rawTitle
46191
- }, vue$1.toDisplayString($options.labelledValue), 9 /* TEXT, PROPS */, _hoisted_7$4v))
46442
+ }, vue$1.toDisplayString($options.shownLabel), 9 /* TEXT, PROPS */, _hoisted_7$4v))
46192
46443
  : (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_8$43, "—"))
46193
46444
  ], 64 /* STABLE_FRAGMENT */))
46194
46445
  : ($props.widget === 'badge')
@@ -46196,7 +46447,7 @@ function render$84(_ctx, _cache, $props, $setup, $data, $options) {
46196
46447
  ($options.hasValue)
46197
46448
  ? (vue$1.openBlock(), vue$1.createBlock(_component_CnStatusBadge, {
46198
46449
  key: 0,
46199
- label: $options.labelledValue,
46450
+ label: $options.shownLabel,
46200
46451
  variant: $options.badgeVariant,
46201
46452
  colorMap: $options.badgeColorMap,
46202
46453
  colorKey: $options.badgeColorKey,
@@ -46342,13 +46593,13 @@ function render$84(_ctx, _cache, $props, $setup, $data, $options) {
46342
46593
  vue$1.createElementVNode("span", {
46343
46594
  class: "cn-cell-renderer__avatar-name",
46344
46595
  title: $options.avatarName || $options.avatarUserId
46345
- }, vue$1.toDisplayString($options.avatarName || $options.avatarUserId), 9 /* TEXT, PROPS */, _hoisted_24$S),
46596
+ }, vue$1.toDisplayString($options.avatarName || $options.avatarUserId), 9 /* TEXT, PROPS */, _hoisted_24$T),
46346
46597
  ($props.secondary)
46347
- ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_25$L, vue$1.toDisplayString($props.secondary), 1 /* TEXT */))
46598
+ ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_25$M, vue$1.toDisplayString($props.secondary), 1 /* TEXT */))
46348
46599
  : vue$1.createCommentVNode("v-if", true)
46349
46600
  ])
46350
46601
  ]))
46351
- : (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_26$F, "—"))
46602
+ : (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_26$G, "—"))
46352
46603
  ], 64 /* STABLE_FRAGMENT */))
46353
46604
  : ($props.widget === 'age')
46354
46605
  ? (vue$1.openBlock(), vue$1.createElementBlock(vue$1.Fragment, { key: 11 }, [
@@ -46359,7 +46610,7 @@ function render$84(_ctx, _cache, $props, $setup, $data, $options) {
46359
46610
  datetime: $options.dateTimestamp.toISOString(),
46360
46611
  title: $options.absoluteDateLabel
46361
46612
  }, vue$1.toDisplayString($options.ageLabel), 11 /* TEXT, CLASS, PROPS */, _hoisted_27$x))
46362
- : (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_28$u, "—"))
46613
+ : (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_28$v, "—"))
46363
46614
  ], 64 /* STABLE_FRAGMENT */))
46364
46615
  : ($props.widget === 'date')
46365
46616
  ? (vue$1.openBlock(), vue$1.createElementBlock(vue$1.Fragment, { key: 12 }, [
@@ -46606,7 +46857,7 @@ function render$84(_ctx, _cache, $props, $setup, $data, $options) {
46606
46857
  }
46607
46858
 
46608
46859
  script$85.render = render$84;
46609
- script$85.__scopeId = "data-v-5e132788";
46860
+ script$85.__scopeId = "data-v-2a3bc230";
46610
46861
  const __sfc_main$7$ = script$85;
46611
46862
 
46612
46863
  /**
@@ -49113,19 +49364,19 @@ const _hoisted_23$X = {
49113
49364
  key: 0,
49114
49365
  class: "hidden-visually"
49115
49366
  };
49116
- const _hoisted_24$R = {
49367
+ const _hoisted_24$S = {
49117
49368
  key: 0,
49118
49369
  class: "cn-table-empty",
49119
49370
  "data-testid": "cn-object-list-empty"
49120
49371
  };
49121
- const _hoisted_25$K = ["colspan"];
49122
- const _hoisted_26$E = {
49372
+ const _hoisted_25$L = ["colspan"];
49373
+ const _hoisted_26$F = {
49123
49374
  key: 0,
49124
49375
  class: "cn-table-empty__lens-reason",
49125
49376
  "data-testid": "cn-data-table-lens-reason"
49126
49377
  };
49127
49378
  const _hoisted_27$w = ["draggable", "data-testid-row-id", "onDragstart", "onDragover", "onDragleave", "onDrop", "onClick", "onAuxclick", "onContextmenu"];
49128
- const _hoisted_28$t = ["href", "aria-label", "onClick"];
49379
+ const _hoisted_28$u = ["href", "aria-label", "onClick"];
49129
49380
  const _hoisted_29$s = {
49130
49381
  key: 3,
49131
49382
  class: "cn-table-row-indicators",
@@ -49384,16 +49635,16 @@ function render$81(_ctx, _cache, $props, $setup, $data, $options) {
49384
49635
  ])),
49385
49636
  vue$1.createElementVNode("tbody", null, [
49386
49637
  ($options.effectiveRows.length === 0)
49387
- ? (vue$1.openBlock(), vue$1.createElementBlock("tr", _hoisted_24$R, [
49638
+ ? (vue$1.openBlock(), vue$1.createElementBlock("tr", _hoisted_24$S, [
49388
49639
  vue$1.createElementVNode("td", { colspan: $options.totalColumns }, [
49389
49640
  vue$1.renderSlot(_ctx.$slots, "empty", {}, () => [
49390
49641
  ($options.lensReasonText)
49391
- ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_26$E, vue$1.toDisplayString($options.lensReasonText), 1 /* TEXT */))
49642
+ ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_26$F, vue$1.toDisplayString($options.lensReasonText), 1 /* TEXT */))
49392
49643
  : (vue$1.openBlock(), vue$1.createElementBlock(vue$1.Fragment, { key: 1 }, [
49393
49644
  vue$1.createTextVNode(vue$1.toDisplayString($options.translateLabel($props.emptyText)), 1 /* TEXT */)
49394
49645
  ], 64 /* STABLE_FRAGMENT */))
49395
49646
  ])
49396
- ], 8 /* PROPS */, _hoisted_25$K)
49647
+ ], 8 /* PROPS */, _hoisted_25$L)
49397
49648
  ]))
49398
49649
  : (vue$1.openBlock(true), vue$1.createElementBlock(vue$1.Fragment, { key: 1 }, vue$1.renderList($options.effectiveRows, (row, rowIndex) => {
49399
49650
  return (vue$1.openBlock(), vue$1.createElementBlock(vue$1.Fragment, {
@@ -49465,7 +49716,7 @@ function render$81(_ctx, _cache, $props, $setup, $data, $options) {
49465
49716
  "data-testid": "cn-row-link",
49466
49717
  onMousemove: _cache[4] || (_cache[4] = (...args) => ($options.linkTitleAtPointer && $options.linkTitleAtPointer(...args))),
49467
49718
  onClick: $event => ($options.onRowLinkClick(row, $event))
49468
- }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_28$t))
49719
+ }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_28$u))
49469
49720
  : vue$1.createCommentVNode("v-if", true),
49470
49721
  (colIndex === $options.titleColumnIndex && $options.isUnreadRow(row))
49471
49722
  ? (vue$1.openBlock(), vue$1.createBlock(_component_CnUnreadMarker, { key: 1 }))
@@ -85225,17 +85476,17 @@ const _hoisted_22$15 = {
85225
85476
  role: "alert"
85226
85477
  };
85227
85478
  const _hoisted_23$W = ["placeholder", "aria-label"];
85228
- const _hoisted_24$Q = {
85479
+ const _hoisted_24$R = {
85229
85480
  key: 3,
85230
85481
  class: "cn-icon-browser-panel__grid"
85231
85482
  };
85232
- const _hoisted_25$J = ["title", "aria-label", "onClick"];
85233
- const _hoisted_26$D = ["src", "alt"];
85483
+ const _hoisted_25$K = ["title", "aria-label", "onClick"];
85484
+ const _hoisted_26$E = ["src", "alt"];
85234
85485
  const _hoisted_27$v = {
85235
85486
  key: 0,
85236
85487
  class: "cn-icon-browser-panel__cell-label"
85237
85488
  };
85238
- const _hoisted_28$s = {
85489
+ const _hoisted_28$t = {
85239
85490
  key: 4,
85240
85491
  class: "cn-icon-browser-panel__hint"
85241
85492
  };
@@ -85425,7 +85676,7 @@ function render$7S(_ctx, _cache, $props, $setup, $data, $options) {
85425
85676
  ])
85426
85677
  : vue$1.createCommentVNode("v-if", true),
85427
85678
  ($options.customVisibleIcons.length > 0)
85428
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$Q, [
85679
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$R, [
85429
85680
  (vue$1.openBlock(true), vue$1.createElementBlock(vue$1.Fragment, null, vue$1.renderList($options.customVisibleIcons, (icon) => {
85430
85681
  return (vue$1.openBlock(), vue$1.createElementBlock("button", {
85431
85682
  key: icon.id || icon.url,
@@ -85440,16 +85691,16 @@ function render$7S(_ctx, _cache, $props, $setup, $data, $options) {
85440
85691
  src: icon.url,
85441
85692
  alt: icon.label,
85442
85693
  loading: "lazy"
85443
- }, null, 8 /* PROPS */, _hoisted_26$D),
85694
+ }, null, 8 /* PROPS */, _hoisted_26$E),
85444
85695
  ($props.showLabels)
85445
85696
  ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_27$v, vue$1.toDisplayString(icon.label), 1 /* TEXT */))
85446
85697
  : vue$1.createCommentVNode("v-if", true)
85447
- ], 10 /* CLASS, PROPS */, _hoisted_25$J))
85698
+ ], 10 /* CLASS, PROPS */, _hoisted_25$K))
85448
85699
  }), 128 /* KEYED_FRAGMENT */))
85449
85700
  ]))
85450
85701
  : vue$1.createCommentVNode("v-if", true),
85451
85702
  ($options.customTruncated)
85452
- ? (vue$1.openBlock(), vue$1.createElementBlock("p", _hoisted_28$s, vue$1.toDisplayString($options.t('nextcloud-vue', 'Showing {shown} of {total} — refine your search to narrow results.', { shown: $options.customVisibleIcons.length, total: $options.customMatches.length })), 1 /* TEXT */))
85703
+ ? (vue$1.openBlock(), vue$1.createElementBlock("p", _hoisted_28$t, vue$1.toDisplayString($options.t('nextcloud-vue', 'Showing {shown} of {total} — refine your search to narrow results.', { shown: $options.customVisibleIcons.length, total: $options.customMatches.length })), 1 /* TEXT */))
85453
85704
  : vue$1.createCommentVNode("v-if", true)
85454
85705
  ], 64 /* STABLE_FRAGMENT */))
85455
85706
  : vue$1.createCommentVNode("v-if", true)
@@ -96376,14 +96627,14 @@ const _hoisted_22$13 = {
96376
96627
  class: "cn-activity-tab__filter"
96377
96628
  };
96378
96629
  const _hoisted_23$V = { class: "cn-activity-tab__filter-label" };
96379
- const _hoisted_24$P = { value: "" };
96380
- const _hoisted_25$I = { value: "internal" };
96381
- const _hoisted_26$C = { value: "public" };
96630
+ const _hoisted_24$Q = { value: "" };
96631
+ const _hoisted_25$J = { value: "internal" };
96632
+ const _hoisted_26$D = { value: "public" };
96382
96633
  const _hoisted_27$u = {
96383
96634
  key: 2,
96384
96635
  class: "cn-activity-tab__reads"
96385
96636
  };
96386
- const _hoisted_28$r = ["checked"];
96637
+ const _hoisted_28$s = ["checked"];
96387
96638
  const _hoisted_29$q = ["aria-label"];
96388
96639
  const _hoisted_30$o = ["onClick"];
96389
96640
  const _hoisted_31$m = {
@@ -96582,9 +96833,9 @@ function render$7H(_ctx, _cache, $props, $setup, $data, $options) {
96582
96833
  "data-testid": "cn-activity-visibility-select",
96583
96834
  onChange: _cache[6] || (_cache[6] = (...args) => ($options.resetAndFetch && $options.resetAndFetch(...args)))
96584
96835
  }, [
96585
- vue$1.createElementVNode("option", _hoisted_24$P, vue$1.toDisplayString($data.allVisibilityLabel), 1 /* TEXT */),
96586
- vue$1.createElementVNode("option", _hoisted_25$I, vue$1.toDisplayString($data.internalLabel), 1 /* TEXT */),
96587
- vue$1.createElementVNode("option", _hoisted_26$C, vue$1.toDisplayString($data.publicLabel), 1 /* TEXT */)
96836
+ vue$1.createElementVNode("option", _hoisted_24$Q, vue$1.toDisplayString($data.allVisibilityLabel), 1 /* TEXT */),
96837
+ vue$1.createElementVNode("option", _hoisted_25$J, vue$1.toDisplayString($data.internalLabel), 1 /* TEXT */),
96838
+ vue$1.createElementVNode("option", _hoisted_26$D, vue$1.toDisplayString($data.publicLabel), 1 /* TEXT */)
96588
96839
  ], 544 /* NEED_HYDRATION, NEED_PATCH */), [
96589
96840
  [vue$1.vModelSelect, $data.selectedVisibility]
96590
96841
  ])
@@ -96598,7 +96849,7 @@ function render$7H(_ctx, _cache, $props, $setup, $data, $options) {
96598
96849
  checked: $data.showReads,
96599
96850
  "data-testid": "cn-activity-reads",
96600
96851
  onChange: _cache[7] || (_cache[7] = $event => ($options.setShowReads($event.target.checked)))
96601
- }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_28$r),
96852
+ }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_28$s),
96602
96853
  vue$1.createTextVNode(" " + vue$1.toDisplayString($data.showReadsLabel), 1 /* TEXT */)
96603
96854
  ]))
96604
96855
  : vue$1.createCommentVNode("v-if", true),
@@ -100306,11 +100557,11 @@ const _hoisted_23$U = {
100306
100557
  key: 1,
100307
100558
  class: "cn-bookmarks-card__title"
100308
100559
  };
100309
- const _hoisted_24$O = {
100560
+ const _hoisted_24$P = {
100310
100561
  key: 3,
100311
100562
  class: "cn-bookmarks-card__view-all"
100312
100563
  };
100313
- const _hoisted_25$H = ["href"];
100564
+ const _hoisted_25$I = ["href"];
100314
100565
 
100315
100566
  function render$7y(_ctx, _cache, $props, $setup, $data, $options) {
100316
100567
  const _component_NcLoadingIcon = vue$1.resolveComponent("NcLoadingIcon");
@@ -100441,12 +100692,12 @@ function render$7y(_ctx, _cache, $props, $setup, $data, $options) {
100441
100692
  }), 128 /* KEYED_FRAGMENT */))
100442
100693
  ])),
100443
100694
  ($data.bookmarks.length > $data.COMPACT_LIMIT)
100444
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$O, [
100695
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$P, [
100445
100696
  vue$1.createElementVNode("a", {
100446
100697
  href: $props.bookmarksAppUrl,
100447
100698
  target: "_blank",
100448
100699
  rel: "noopener noreferrer"
100449
- }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_25$H)
100700
+ }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_25$I)
100450
100701
  ]))
100451
100702
  : vue$1.createCommentVNode("v-if", true)
100452
100703
  ], 64 /* STABLE_FRAGMENT */))
@@ -102402,11 +102653,11 @@ const _hoisted_23$T = {
102402
102653
  class: "cn-calendar-tab__date cn-calendar-tab__date--past",
102403
102654
  "aria-hidden": "true"
102404
102655
  };
102405
- const _hoisted_24$N = { class: "cn-calendar-tab__date-month" };
102406
- const _hoisted_25$G = { class: "cn-calendar-tab__date-day" };
102407
- const _hoisted_26$B = { class: "cn-calendar-tab__main" };
102656
+ const _hoisted_24$O = { class: "cn-calendar-tab__date-month" };
102657
+ const _hoisted_25$H = { class: "cn-calendar-tab__date-day" };
102658
+ const _hoisted_26$C = { class: "cn-calendar-tab__main" };
102408
102659
  const _hoisted_27$t = { class: "cn-calendar-tab__title-row" };
102409
- const _hoisted_28$q = { class: "cn-calendar-tab__title" };
102660
+ const _hoisted_28$r = { class: "cn-calendar-tab__title" };
102410
102661
  const _hoisted_29$p = { class: "cn-calendar-tab__when" };
102411
102662
  const _hoisted_30$n = {
102412
102663
  key: 0,
@@ -102584,12 +102835,12 @@ function render$7t(_ctx, _cache, $props, $setup, $data, $options) {
102584
102835
  class: "cn-calendar-tab__event cn-calendar-tab__event--past"
102585
102836
  }, [
102586
102837
  vue$1.createElementVNode("div", _hoisted_23$T, [
102587
- vue$1.createElementVNode("span", _hoisted_24$N, vue$1.toDisplayString($options.monthOf(ev)), 1 /* TEXT */),
102588
- vue$1.createElementVNode("span", _hoisted_25$G, vue$1.toDisplayString($options.dayOf(ev)), 1 /* TEXT */)
102838
+ vue$1.createElementVNode("span", _hoisted_24$O, vue$1.toDisplayString($options.monthOf(ev)), 1 /* TEXT */),
102839
+ vue$1.createElementVNode("span", _hoisted_25$H, vue$1.toDisplayString($options.dayOf(ev)), 1 /* TEXT */)
102589
102840
  ]),
102590
- vue$1.createElementVNode("div", _hoisted_26$B, [
102841
+ vue$1.createElementVNode("div", _hoisted_26$C, [
102591
102842
  vue$1.createElementVNode("div", _hoisted_27$t, [
102592
- vue$1.createElementVNode("span", _hoisted_28$q, vue$1.toDisplayString(ev.summary || $props.untitledLabel), 1 /* TEXT */),
102843
+ vue$1.createElementVNode("span", _hoisted_28$r, vue$1.toDisplayString(ev.summary || $props.untitledLabel), 1 /* TEXT */),
102593
102844
  ($options.statusLabel(ev))
102594
102845
  ? (vue$1.openBlock(), vue$1.createBlock(_component_CnStatusBadge, {
102595
102846
  key: 0,
@@ -103103,11 +103354,11 @@ const _hoisted_23$S = {
103103
103354
  key: 0,
103104
103355
  class: "cn-collectives-card__row-collective"
103105
103356
  };
103106
- const _hoisted_24$M = {
103357
+ const _hoisted_24$N = {
103107
103358
  key: 3,
103108
103359
  class: "cn-collectives-card__view-all"
103109
103360
  };
103110
- const _hoisted_25$F = ["href"];
103361
+ const _hoisted_25$G = ["href"];
103111
103362
 
103112
103363
  function render$7s(_ctx, _cache, $props, $setup, $data, $options) {
103113
103364
  const _component_NcLoadingIcon = vue$1.resolveComponent("NcLoadingIcon");
@@ -103235,12 +103486,12 @@ function render$7s(_ctx, _cache, $props, $setup, $data, $options) {
103235
103486
  }), 128 /* KEYED_FRAGMENT */))
103236
103487
  ])),
103237
103488
  ($data.pages.length > $data.COMPACT_LIMIT)
103238
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$M, [
103489
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$N, [
103239
103490
  vue$1.createElementVNode("a", {
103240
103491
  href: $props.collectivesAppUrl,
103241
103492
  target: "_blank",
103242
103493
  rel: "noopener"
103243
- }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_25$F)
103494
+ }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_25$G)
103244
103495
  ]))
103245
103496
  : vue$1.createCommentVNode("v-if", true)
103246
103497
  ], 64 /* STABLE_FRAGMENT */))
@@ -115729,13 +115980,19 @@ function indexRowData(data, { path = '', key = 'fileId' } = {}) {
115729
115980
  * `visibleIf.extension` lists file extensions (no case, a leading dot is
115730
115981
  * allowed); `visibleIf.mime` lists mime types, where one ending in `/` or
115731
115982
  * `/*` names the whole family. Every condition named must hold. An action
115732
- * without `visibleIf` is offered on every file, as before.
115983
+ * without `visibleIf` is offered on every file, as before. An action with
115984
+ * `when` is offered only while `when` is `true`.
115733
115985
  *
115734
115986
  * @param {object} action The declared action.
115735
115987
  * @param {object} node The file (`basename`, `mime`).
115736
115988
  * @return {boolean} True when the action belongs on this file.
115737
115989
  */
115738
115990
  function hostActionApplies(action, node) {
115991
+ // `when`: the action applies only while it is true (a case type flag).
115992
+ // @spec openspec/changes/files-review-row-when/specs/files-review-row-when/spec.md#requirement-a-files-browser-entry-can-apply-only-when-a-condition-holds
115993
+ if (action && typeof action === 'object' && 'when' in action && action.when !== true) {
115994
+ return false
115995
+ }
115739
115996
  const condition = action && action.visibleIf;
115740
115997
  if (!condition || typeof condition !== 'object') {
115741
115998
  return true
@@ -115845,6 +116102,11 @@ function normaliseFileColumns(columns) {
115845
116102
  if (!entry || typeof entry !== 'object' || typeof entry.key !== 'string' || entry.key === '') {
115846
116103
  return
115847
116104
  }
116105
+ // `when`: the column applies only while it is true (a case type flag).
116106
+ // @spec openspec/changes/files-review-row-when/specs/files-review-row-when/spec.md#requirement-a-files-browser-entry-can-apply-only-when-a-condition-holds
116107
+ if ('when' in entry && entry.when !== true) {
116108
+ return
116109
+ }
115848
116110
  if (isBuiltInFileColumn(entry.key) && entry.source === undefined) {
115849
116111
  out.push({ ...BUILT_IN_FILE_COLUMNS[entry.key], ...entry, builtIn: true });
115850
116112
  return
@@ -118450,7 +118712,7 @@ var script$71 = {
118450
118712
  ? { path: this.currentPath }
118451
118713
  : { fileId: node.fileid, fileName: node.basename, path: node.path };
118452
118714
  if (type === 'open-modal') {
118453
- wrapped = { ...action, props: { ...(action.props || {}), ...context } };
118715
+ wrapped = { ...action, props: { ...(action.props || {}), ...context, ...this.rowPropsOf(action, node) } };
118454
118716
  } else if (type === 'handler') {
118455
118717
  wrapped = { ...action, args: [...(action.args || []), node ?? this.folder] };
118456
118718
  }
@@ -118461,6 +118723,32 @@ var script$71 = {
118461
118723
  dispatchAction$1(wrapped, { router: this.$router || null });
118462
118724
  },
118463
118725
 
118726
+ /**
118727
+ * The clicked file's row-data fields an action asks for as modal props
118728
+ * (`rowProps: { prop: field }`), or none.
118729
+ *
118730
+ * @param {object} action The declared action.
118731
+ * @param {object|null} node The file, or null for a New-menu entry.
118732
+ * @return {object} The props.
118733
+ * @spec openspec/changes/files-review-row-when/specs/files-review-row-when/spec.md#requirement-a-modal-action-can-carry-fields-of-the-clicked-files-row-data
118734
+ */
118735
+ rowPropsOf(action, node) {
118736
+ const wanted = action && action.rowProps;
118737
+ if (node === null || !wanted || typeof wanted !== 'object') {
118738
+ return {}
118739
+ }
118740
+ const byId = this.resolvedRowData && typeof this.resolvedRowData === 'object' ? this.resolvedRowData : {};
118741
+ const entry = byId[node.fileid] ?? byId[String(node.fileid)] ?? {};
118742
+ const out = {};
118743
+ for (const [prop, field] of Object.entries(wanted)) {
118744
+ const value = readPath$8(entry, String(field));
118745
+ if (value !== undefined && value !== null && value !== '') {
118746
+ out[prop] = value;
118747
+ }
118748
+ }
118749
+ return out
118750
+ },
118751
+
118464
118752
  /**
118465
118753
  * A click on a linked row opens the file where it lives.
118466
118754
  *
@@ -118560,18 +118848,18 @@ const _hoisted_23$R = {
118560
118848
  class: "cn-files-browser__table",
118561
118849
  "data-testid": "cn-files-browser-table"
118562
118850
  };
118563
- const _hoisted_24$L = {
118851
+ const _hoisted_24$M = {
118564
118852
  key: 0,
118565
118853
  class: "cn-files-browser__col-select",
118566
118854
  scope: "col"
118567
118855
  };
118568
- const _hoisted_25$E = ["checked", ".indeterminate", "disabled", "aria-label"];
118569
- const _hoisted_26$A = {
118856
+ const _hoisted_25$F = ["checked", ".indeterminate", "disabled", "aria-label"];
118857
+ const _hoisted_26$B = {
118570
118858
  class: "cn-files-browser__col-icon",
118571
118859
  scope: "col"
118572
118860
  };
118573
118861
  const _hoisted_27$s = { class: "hidden-visually" };
118574
- const _hoisted_28$p = ["aria-sort", "data-testid"];
118862
+ const _hoisted_28$q = ["aria-sort", "data-testid"];
118575
118863
  const _hoisted_29$o = ["onClick"];
118576
118864
  const _hoisted_30$m = {
118577
118865
  class: "cn-files-browser__col-actions",
@@ -119076,7 +119364,7 @@ function render$70(_ctx, _cache, $props, $setup, $data, $options) {
119076
119364
  vue$1.createElementVNode("thead", null, [
119077
119365
  vue$1.createElementVNode("tr", null, [
119078
119366
  ($options.bulkEnabled)
119079
- ? (vue$1.openBlock(), vue$1.createElementBlock("th", _hoisted_24$L, [
119367
+ ? (vue$1.openBlock(), vue$1.createElementBlock("th", _hoisted_24$M, [
119080
119368
  vue$1.createElementVNode("input", {
119081
119369
  type: "checkbox",
119082
119370
  class: "cn-files-browser__select",
@@ -119086,10 +119374,10 @@ function render$70(_ctx, _cache, $props, $setup, $data, $options) {
119086
119374
  "aria-label": $options.t('nextcloud-vue', 'Select all files'),
119087
119375
  "data-testid": "cn-files-browser-select-all",
119088
119376
  onChange: _cache[1] || (_cache[1] = $event => ($options.toggleAll($event.target.checked)))
119089
- }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_25$E)
119377
+ }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_25$F)
119090
119378
  ]))
119091
119379
  : vue$1.createCommentVNode("v-if", true),
119092
- vue$1.createElementVNode("th", _hoisted_26$A, [
119380
+ vue$1.createElementVNode("th", _hoisted_26$B, [
119093
119381
  vue$1.createElementVNode("span", _hoisted_27$s, vue$1.toDisplayString($options.t('nextcloud-vue', 'Type')), 1 /* TEXT */)
119094
119382
  ]),
119095
119383
  (vue$1.openBlock(true), vue$1.createElementBlock(vue$1.Fragment, null, vue$1.renderList($options.renderedColumns, (column) => {
@@ -119118,7 +119406,7 @@ function render$70(_ctx, _cache, $props, $setup, $data, $options) {
119118
119406
  : (vue$1.openBlock(), vue$1.createElementBlock(vue$1.Fragment, { key: 1 }, [
119119
119407
  vue$1.createTextVNode(vue$1.toDisplayString($options.columnLabel(column)), 1 /* TEXT */)
119120
119408
  ], 64 /* STABLE_FRAGMENT */))
119121
- ], 10 /* CLASS, PROPS */, _hoisted_28$p))
119409
+ ], 10 /* CLASS, PROPS */, _hoisted_28$q))
119122
119410
  }), 128 /* KEYED_FRAGMENT */)),
119123
119411
  vue$1.createElementVNode("th", _hoisted_30$m, [
119124
119412
  vue$1.createElementVNode("span", _hoisted_31$k, vue$1.toDisplayString($options.t('nextcloud-vue', 'Actions')), 1 /* TEXT */)
@@ -119711,7 +119999,7 @@ function render$70(_ctx, _cache, $props, $setup, $data, $options) {
119711
119999
  }
119712
120000
 
119713
120001
  script$71.render = render$70;
119714
- script$71.__scopeId = "data-v-7ce558e7";
120002
+ script$71.__scopeId = "data-v-3a771a01";
119715
120003
  const __sfc_main$6X = script$71;
119716
120004
 
119717
120005
  /**
@@ -124442,11 +124730,11 @@ const _hoisted_23$Q = {
124442
124730
  key: 0,
124443
124731
  class: "cn-maps-card__coords"
124444
124732
  };
124445
- const _hoisted_24$K = {
124733
+ const _hoisted_24$L = {
124446
124734
  key: 3,
124447
124735
  class: "cn-maps-card__view-all"
124448
124736
  };
124449
- const _hoisted_25$D = ["href"];
124737
+ const _hoisted_25$E = ["href"];
124450
124738
 
124451
124739
  function render$6T(_ctx, _cache, $props, $setup, $data, $options) {
124452
124740
  const _component_NcLoadingIcon = vue$1.resolveComponent("NcLoadingIcon");
@@ -124561,12 +124849,12 @@ function render$6T(_ctx, _cache, $props, $setup, $data, $options) {
124561
124849
  }), 128 /* KEYED_FRAGMENT */))
124562
124850
  ])),
124563
124851
  ($data.points.length > $data.COMPACT_LIMIT)
124564
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$K, [
124852
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$L, [
124565
124853
  vue$1.createElementVNode("a", {
124566
124854
  href: $props.mapsAppUrl,
124567
124855
  target: "_blank",
124568
124856
  rel: "noopener"
124569
- }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_25$D)
124857
+ }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_25$E)
124570
124858
  ]))
124571
124859
  : vue$1.createCommentVNode("v-if", true)
124572
124860
  ], 64 /* STABLE_FRAGMENT */))
@@ -131187,7 +131475,7 @@ const _hoisted_23$P = {
131187
131475
  key: 3,
131188
131476
  class: "cn-photos-card__view-all"
131189
131477
  };
131190
- const _hoisted_24$J = ["href"];
131478
+ const _hoisted_24$K = ["href"];
131191
131479
 
131192
131480
  function render$6F(_ctx, _cache, $props, $setup, $data, $options) {
131193
131481
  const _component_NcLoadingIcon = vue$1.resolveComponent("NcLoadingIcon");
@@ -131320,7 +131608,7 @@ function render$6F(_ctx, _cache, $props, $setup, $data, $options) {
131320
131608
  href: $props.photosAppUrl,
131321
131609
  target: "_blank",
131322
131610
  rel: "noopener noreferrer"
131323
- }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_24$J)
131611
+ }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_24$K)
131324
131612
  ]))
131325
131613
  : vue$1.createCommentVNode("v-if", true)
131326
131614
  ], 64 /* STABLE_FRAGMENT */))
@@ -140627,7 +140915,8 @@ const FLOW_TASKS_URL$1 = '/apps/openregister/api/flow-tasks';
140627
140915
  // creating app named it, so it is the one filter that lets a Tasks page say
140628
140916
  // "the reminders" without the engine learning what a reminder is. It widens
140629
140917
  // nothing: it narrows within the inbox the session already decides.
140630
- const ALLOWED_PARAMS = ['scope', 'state', 'isTerminal', 'priority', 'kind', 'overdue', 'dueAfter', 'dueBefore', 'objectUuid', 'sort', 'limit', 'offset'];
140918
+ // `openFirst` asks open tasks before finished ones, a sort the engine applies.
140919
+ const ALLOWED_PARAMS = ['scope', 'state', 'isTerminal', 'priority', 'kind', 'overdue', 'dueAfter', 'dueBefore', 'objectUuid', 'sort', 'openFirst', 'limit', 'offset'];
140631
140920
 
140632
140921
  /**
140633
140922
  * The flat body `POST /api/flow-tasks` takes for a task on a record.
@@ -140764,7 +141053,7 @@ const useTaskInboxStore = pinia.defineStore('cnTaskInbox', {
140764
141053
  if (value === undefined || value === null || value === '') {
140765
141054
  continue
140766
141055
  }
140767
- params[key] = (key === 'overdue' || key === 'isTerminal') ? String(value) : value;
141056
+ params[key] = (key === 'overdue' || key === 'isTerminal' || key === 'openFirst') ? String(value) : value;
140768
141057
  }
140769
141058
  const response = await axios$1.get(router.generateUrl(FLOW_TASKS_URL$1), { params });
140770
141059
  const results = response.data?.results || [];
@@ -140944,7 +141233,7 @@ const useTaskInboxStore = pinia.defineStore('cnTaskInbox', {
140944
141233
  }
140945
141234
  // The endpoint reads `overdue` as a boolean-ish string; a quick
140946
141235
  // filter authors it as `true` for legibility.
140947
- params[key] = (key === 'overdue' || key === 'isTerminal') ? String(value) : value;
141236
+ params[key] = (key === 'overdue' || key === 'isTerminal' || key === 'openFirst') ? String(value) : value;
140948
141237
  }
140949
141238
 
140950
141239
  this.loading = true;
@@ -141650,12 +141939,15 @@ function tasksSource() {
141650
141939
  *
141651
141940
  * @param {string} key The column key.
141652
141941
  * @param {string} order `asc` or `desc`.
141653
- * @return {{sort: string}|null} The parameter, or null for a column the inbox cannot sort by.
141942
+ * Open tasks come before finished ones (`openFirst`), each in that order.
141943
+ *
141944
+ * @return {{sort: string, openFirst: boolean}|null} The parameters, or null for a column the inbox cannot sort by.
141654
141945
  * @spec openspec/changes/tasks-source-sorts-on-server/specs/index-sources/spec.md#requirement-a-named-source-can-sort-on-the-server
141946
+ * @spec openspec/changes/tasks-source-open-first/specs/index-sources/spec.md#requirement-a-sorted-task-list-puts-open-tasks-first
141655
141947
  */
141656
141948
  sortParam: (key, order) => {
141657
141949
  const field = TASK_SORT_FIELDS[key];
141658
- return field ? { sort: (order === 'desc' ? '-' : '') + field } : null
141950
+ return field ? { sort: (order === 'desc' ? '-' : '') + field, openFirst: true } : null
141659
141951
  },
141660
141952
 
141661
141953
  // The scope tabs. Each tab's filter is merged over the page's
@@ -142039,7 +142331,7 @@ function fillRowTemplate(template, row) {
142039
142331
  * and its count (DqZoekresultaten).
142040
142332
  *
142041
142333
  * `sourceConfig.kinds` lists what is searched:
142042
- * `{ id, label, register, schema, titleField?, subtitleField?, subtitleTemplate?, statusField?, statusResolve?, linkedField?, linkedTemplate?, href?, limit? }`
142334
+ * `{ id, label, register, schema, titleField?, subtitleField?, subtitleTemplate?, statusField?, statusResolve?, linkedField?, linkedTemplate?, linkedUser?, href?, limit? }`
142043
142335
  * for objects (OpenRegister's `_search`, so the reader sees what they may
142044
142336
  * read), or `{ id, label, files: true, limit? }` for the reader's files
142045
142337
  * (Nextcloud's unified search). The term is `sourceConfig.term`, else the
@@ -142055,6 +142347,8 @@ function fillRowTemplate(template, row) {
142055
142347
  * column, drawn when a kind declares one; `sourceConfig.linkedLabel` names it.
142056
142348
  * `subtitleTemplate` ("Case {identifier}", translated) fills the line under a
142057
142349
  * result, else `subtitleField`.
142350
+ * `linkedUser: true` reads that value as a user id and shows the user's
142351
+ * display name. `sourceConfig.columnWidths` sets a width per column key.
142058
142352
  * The page's title and count line can name the term as `{term}`, and the
142059
142353
  * count is the active kind's total. Kind, link and status are filterable.
142060
142354
  *
@@ -142064,6 +142358,23 @@ function fillRowTemplate(template, row) {
142064
142358
  * @return {object} The source adapter.
142065
142359
  * @spec openspec/changes/index-search-source/specs/index-page/spec.md#requirement-an-index-page-can-search-several-schemas-and-files
142066
142360
  */
142361
+ /**
142362
+ * Columns with the widths a page names per column key
142363
+ * (`sourceConfig.columnWidths`, `{ title: '250px' }`); a column it does not
142364
+ * name keeps none.
142365
+ *
142366
+ * @param {Array<object>} columns The columns.
142367
+ * @param {object|undefined} widths The widths by key.
142368
+ * @return {Array<object>} The columns.
142369
+ * @spec openspec/changes/search-linked-user-and-widths/specs/search-linked-user-and-widths/spec.md
142370
+ */
142371
+ function withColumnWidths(columns, widths) {
142372
+ if (!widths || typeof widths !== 'object') {
142373
+ return columns
142374
+ }
142375
+ return columns.map((col) => (typeof widths[col.key] === 'string' && widths[col.key] !== '' ? { ...col, width: widths[col.key] } : col))
142376
+ }
142377
+
142067
142378
  function searchSource(config = {}, context = {}) {
142068
142379
  const translate = typeof context.translate === 'function' ? context.translate : (text) => text;
142069
142380
  const kinds = (Array.isArray(config && config.kinds) ? config.kinds : [])
@@ -142159,12 +142470,20 @@ function searchSource(config = {}, context = {}) {
142159
142470
  return kind.subtitleField ? String(readPath$8(object, kind.subtitleField) ?? '') : ''
142160
142471
  };
142161
142472
 
142473
+ // `linkedUser`: the Linked cell holds a user id; name the user instead.
142474
+ const linkedUserNames = async (kind, objects) => {
142475
+ const ids = [...new Set(objects.map((object) => linkedOf(kind, object)).filter((id) => id !== ''))];
142476
+ const options = await Promise.all(ids.map((id) => resolveNextcloudUser(id).catch(() => null)));
142477
+ return new Map(ids.map((id, index) => [id, (options[index] && options[index].label) || id]))
142478
+ };
142479
+
142162
142480
  const searchObjects = async (axios, kind, term) => {
142163
142481
  const url = router.generateUrl('/apps/openregister/api/objects/{register}/{schema}', { register: kind.register, schema: kind.schema });
142164
142482
  const res = await axios.get(url, { params: { _search: term, _limit: kind.limit || 20 } });
142165
142483
  const body = (res && res.data) || {};
142166
142484
  const results = Array.isArray(body.results) ? body.results : (Array.isArray(body) ? body : []);
142167
142485
  const labels = kind.statusResolve && kind.statusField ? await statusLabels(kind) : null;
142486
+ const userNames = kind.linkedUser === true ? await linkedUserNames(kind, results) : null;
142168
142487
  return {
142169
142488
  total: typeof body.total === 'number' ? body.total : results.length,
142170
142489
  rows: results.map((object) => {
@@ -142181,7 +142500,7 @@ function searchSource(config = {}, context = {}) {
142181
142500
  title: String(readPath$8(object, kind.titleField || 'title') ?? object.name ?? id ?? ''),
142182
142501
  subtitle: subtitleOf(kind, object),
142183
142502
  status,
142184
- linked: linkedOf(kind, object),
142503
+ linked: userNames ? (userNames.get(linkedOf(kind, object)) || linkedOf(kind, object)) : linkedOf(kind, object),
142185
142504
  href: kind.href ? router.generateUrl(fillRowTemplate(kind.href, { ...object, id })) : '',
142186
142505
  object,
142187
142506
  }
@@ -142289,7 +142608,7 @@ function searchSource(config = {}, context = {}) {
142289
142608
  */
142290
142609
  loading: () => state.loading,
142291
142610
 
142292
- columns: [
142611
+ columns: withColumnWidths([
142293
142612
  { key: 'title', label: l10n.translate('nextcloud-vue', 'Result'), secondary: 'subtitle', sortable: true },
142294
142613
  // `kindAsText`: what one row is, as plain text ("Zaak", "Lantaarnpaal",
142295
142614
  // DqZoekresultaten), instead of the plural kind as a pill.
@@ -142304,7 +142623,7 @@ function searchSource(config = {}, context = {}) {
142304
142623
  widgetProps: { colorMap: (config && config.statusColorMap) || {} },
142305
142624
  sortable: true,
142306
142625
  },
142307
- ],
142626
+ ], config && config.columnWidths),
142308
142627
 
142309
142628
  /**
142310
142629
  * Every column header carries a filter button (DqZoekresultaten); the
@@ -142554,6 +142873,15 @@ function usersSource() {
142554
142873
  /** A filter button on every head (DqAfdelingen); the members read are narrowed here. */
142555
142874
  headerFilters: true,
142556
142875
 
142876
+ /**
142877
+ * A member's row menu (DqAfdelingen): their Nextcloud profile, and an
142878
+ * e-mail when they have an address. Both are links.
142879
+ */
142880
+ rowActions: [
142881
+ { id: 'open-profile', label: l10n.translate('nextcloud-vue', 'Open profile'), icon: AccountOutline, href: (row) => router.generateUrl(`/u/${encodeURIComponent(row.id)}`) },
142882
+ { id: 'send-email', label: l10n.translate('nextcloud-vue', 'Send email'), icon: EmailOutline, href: (row) => `mailto:${row.email}`, visible: (row) => Boolean(row && row.email) },
142883
+ ],
142884
+
142557
142885
  showAdd: false,
142558
142886
  }
142559
142887
  }
@@ -154093,14 +154421,14 @@ const _hoisted_23$N = {
154093
154421
  key: 2,
154094
154422
  class: "cn-form-dialog__option-group"
154095
154423
  };
154096
- const _hoisted_24$I = { class: "cn-form-dialog__option" };
154097
- const _hoisted_25$C = {
154424
+ const _hoisted_24$J = { class: "cn-form-dialog__option" };
154425
+ const _hoisted_25$D = {
154098
154426
  key: 10,
154099
154427
  class: "cn-form-dialog__select-wrapper"
154100
154428
  };
154101
- const _hoisted_26$z = { class: "cn-form-dialog__option" };
154429
+ const _hoisted_26$A = { class: "cn-form-dialog__option" };
154102
154430
  const _hoisted_27$r = { class: "cn-form-dialog__option" };
154103
- const _hoisted_28$o = {
154431
+ const _hoisted_28$p = {
154104
154432
  key: 11,
154105
154433
  class: "cn-form-dialog__select-wrapper"
154106
154434
  };
@@ -154810,7 +155138,7 @@ function render$6c(_ctx, _cache, $props, $setup, $data, $options) {
154810
155138
  ? {
154811
155139
  name: "selected-option",
154812
155140
  fn: vue$1.withCtx((optionProps) => [
154813
- vue$1.createElementVNode("span", _hoisted_24$I, [
155141
+ vue$1.createElementVNode("span", _hoisted_24$J, [
154814
155142
  ($options.optionSwatch(field, optionProps))
154815
155143
  ? (vue$1.openBlock(), vue$1.createElementBlock("span", {
154816
155144
  key: 0,
@@ -154844,7 +155172,7 @@ function render$6c(_ctx, _cache, $props, $setup, $data, $options) {
154844
155172
  }, null, 8 /* PROPS */, ["id", "text", "more", "help", "label", "error"])
154845
155173
  ]))
154846
155174
  : (field.widget === 'multiselect' || field.widget === 'user-multiselect' || field.widget === 'group-multiselect')
154847
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_25$C, [
155175
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_25$D, [
154848
155176
  vue$1.withDirectives((vue$1.openBlock(), vue$1.createBlock(_component_NcSelect, {
154849
155177
  inputId: 'cn-form-' + field.key,
154850
155178
  inputLabel: $options.fieldLabelText(field),
@@ -154871,7 +155199,7 @@ function render$6c(_ctx, _cache, $props, $setup, $data, $options) {
154871
155199
  : {
154872
155200
  name: "option",
154873
155201
  fn: vue$1.withCtx((optionProps) => [
154874
- vue$1.createElementVNode("span", _hoisted_26$z, vue$1.toDisplayString($options.optionText(optionProps)), 1 /* TEXT */)
155202
+ vue$1.createElementVNode("span", _hoisted_26$A, vue$1.toDisplayString($options.optionText(optionProps)), 1 /* TEXT */)
154875
155203
  ]),
154876
155204
  key: "1"
154877
155205
  },
@@ -154903,7 +155231,7 @@ function render$6c(_ctx, _cache, $props, $setup, $data, $options) {
154903
155231
  }, null, 8 /* PROPS */, ["id", "text", "more", "help", "label", "error"])
154904
155232
  ]))
154905
155233
  : (field.widget === 'tags')
154906
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_28$o, [
155234
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_28$p, [
154907
155235
  vue$1.withDirectives((vue$1.openBlock(), vue$1.createBlock(_component_NcSelect, {
154908
155236
  inputId: 'cn-form-' + field.key,
154909
155237
  inputLabel: $options.fieldLabelText(field),
@@ -157062,7 +157390,7 @@ var script$6b = {
157062
157390
  // `secondary` (a second, muted line under the cell) rides
157063
157391
  // along too: the table draws it, and dropping it here took the
157064
157392
  // list rows' second line away (zuiddrecht-pixel-gaps-3).
157065
- for (const k of ['format', 'widget', 'widgetProps', 'formatter', 'align', 'width', 'type', 'enum', 'enumLabels', 'sortable', 'secondary', 'colorMap', 'grow']) {
157393
+ for (const k of ['format', 'widget', 'widgetProps', 'formatter', 'align', 'width', 'type', 'enum', 'enumLabels', 'sortable', 'secondary', 'colorMap', 'grow', 'cellClass']) {
157066
157394
  if (c[k] !== undefined) {
157067
157395
  out[k] = c[k];
157068
157396
  }
@@ -159612,7 +159940,7 @@ function render$6a(_ctx, _cache, $props, $setup, $data, $options) {
159612
159940
  }
159613
159941
 
159614
159942
  script$6b.render = render$6a;
159615
- script$6b.__scopeId = "data-v-445e213c";
159943
+ script$6b.__scopeId = "data-v-3a0165c8";
159616
159944
  const __sfc_main$65 = script$6b;
159617
159945
 
159618
159946
  const DEFAULT_CONTENT$s = Object.freeze({
@@ -164981,21 +165309,21 @@ const _hoisted_20$T = {
164981
165309
  const _hoisted_21$R = { class: "cn-related-objects-widget__list" };
164982
165310
  const _hoisted_22$Q = ["href"];
164983
165311
  const _hoisted_23$M = { class: "cn-related-objects-widget__label" };
164984
- const _hoisted_24$H = {
165312
+ const _hoisted_24$I = {
164985
165313
  key: 0,
164986
165314
  class: "cn-related-objects-widget__meta"
164987
165315
  };
164988
- const _hoisted_25$B = {
165316
+ const _hoisted_25$C = {
164989
165317
  key: 1,
164990
165318
  class: "cn-related-objects-widget__row cn-related-objects-widget__row--static",
164991
165319
  "data-testid": "cn-related-static-row"
164992
165320
  };
164993
- const _hoisted_26$y = { class: "cn-related-objects-widget__label" };
165321
+ const _hoisted_26$z = { class: "cn-related-objects-widget__label" };
164994
165322
  const _hoisted_27$q = {
164995
165323
  key: 0,
164996
165324
  class: "cn-related-objects-widget__meta"
164997
165325
  };
164998
- const _hoisted_28$n = ["onClick", "onKeydown"];
165326
+ const _hoisted_28$o = ["onClick", "onKeydown"];
164999
165327
  const _hoisted_29$m = { class: "cn-related-objects-widget__label" };
165000
165328
  const _hoisted_30$k = {
165001
165329
  key: 0,
@@ -165251,17 +165579,17 @@ function render$64(_ctx, _cache, $props, $setup, $data, $options) {
165251
165579
  }, null, 8 /* PROPS */, ["name"]),
165252
165580
  vue$1.createElementVNode("span", _hoisted_23$M, vue$1.toDisplayString(item.label), 1 /* TEXT */),
165253
165581
  (item.meta)
165254
- ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_24$H, vue$1.toDisplayString(item.meta), 1 /* TEXT */))
165582
+ ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_24$I, vue$1.toDisplayString(item.meta), 1 /* TEXT */))
165255
165583
  : vue$1.createCommentVNode("v-if", true)
165256
165584
  ], 8 /* PROPS */, _hoisted_22$Q))
165257
165585
  : ($options.isStaticRow($options.activeGroup, item))
165258
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_25$B, [
165586
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_25$C, [
165259
165587
  vue$1.createVNode(_component_CnIcon, {
165260
165588
  name: $options.activeGroup.icon,
165261
165589
  size: 20,
165262
165590
  class: "cn-related-objects-widget__icon"
165263
165591
  }, null, 8 /* PROPS */, ["name"]),
165264
- vue$1.createElementVNode("span", _hoisted_26$y, vue$1.toDisplayString(item.label), 1 /* TEXT */),
165592
+ vue$1.createElementVNode("span", _hoisted_26$z, vue$1.toDisplayString(item.label), 1 /* TEXT */),
165265
165593
  (item.meta)
165266
165594
  ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_27$q, vue$1.toDisplayString(item.meta), 1 /* TEXT */))
165267
165595
  : vue$1.createCommentVNode("v-if", true)
@@ -165286,7 +165614,7 @@ function render$64(_ctx, _cache, $props, $setup, $data, $options) {
165286
165614
  (item.detail && $options.isExpanded($options.activeGroup.key, item))
165287
165615
  ? (vue$1.openBlock(), vue$1.createElementBlock("p", _hoisted_31$i, vue$1.toDisplayString(item.detail), 1 /* TEXT */))
165288
165616
  : vue$1.createCommentVNode("v-if", true)
165289
- ], 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_28$n)),
165617
+ ], 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_28$o)),
165290
165618
  (item.children && item.children.length)
165291
165619
  ? (vue$1.openBlock(), vue$1.createElementBlock("ul", _hoisted_32$h, [
165292
165620
  (vue$1.openBlock(true), vue$1.createElementBlock(vue$1.Fragment, null, vue$1.renderList(item.children, (child) => {
@@ -166469,9 +166797,9 @@ const _hoisted_20$S = {
166469
166797
  const _hoisted_21$Q = { class: "cn-retention__rows" };
166470
166798
  const _hoisted_22$P = { class: "cn-retention__row" };
166471
166799
  const _hoisted_23$L = { class: "cn-retention__row-text" };
166472
- const _hoisted_24$G = { class: "cn-retention__row-title" };
166473
- const _hoisted_25$A = { class: "cn-retention__muted" };
166474
- const _hoisted_26$x = {
166800
+ const _hoisted_24$H = { class: "cn-retention__row-title" };
166801
+ const _hoisted_25$B = { class: "cn-retention__muted" };
166802
+ const _hoisted_26$y = {
166475
166803
  key: 7,
166476
166804
  class: "cn-retention__outcome",
166477
166805
  "data-testid": "cn-retention-outcome"
@@ -166655,8 +166983,8 @@ function render$61(_ctx, _cache, $props, $setup, $data, $options) {
166655
166983
  vue$1.createElementVNode("ul", _hoisted_21$Q, [
166656
166984
  vue$1.createElementVNode("li", _hoisted_22$P, [
166657
166985
  vue$1.createElementVNode("span", _hoisted_23$L, [
166658
- vue$1.createElementVNode("span", _hoisted_24$G, vue$1.toDisplayString($options.transfers ? $options.t('nextcloud-vue', 'Goes to the e-Depot') : $options.t('nextcloud-vue', 'Not applicable')), 1 /* TEXT */),
166659
- vue$1.createElementVNode("span", _hoisted_25$A, vue$1.toDisplayString($options.transfers ? $options.t('nextcloud-vue', 'After the retention period the record is transferred') : $options.t('nextcloud-vue', 'Only records appraised to keep go to the e-Depot')), 1 /* TEXT */)
166986
+ vue$1.createElementVNode("span", _hoisted_24$H, vue$1.toDisplayString($options.transfers ? $options.t('nextcloud-vue', 'Goes to the e-Depot') : $options.t('nextcloud-vue', 'Not applicable')), 1 /* TEXT */),
166987
+ vue$1.createElementVNode("span", _hoisted_25$B, vue$1.toDisplayString($options.transfers ? $options.t('nextcloud-vue', 'After the retention period the record is transferred') : $options.t('nextcloud-vue', 'Only records appraised to keep go to the e-Depot')), 1 /* TEXT */)
166660
166988
  ]),
166661
166989
  vue$1.createVNode(_component_CnStatusBadge, {
166662
166990
  label: $options.transfers ? $options.t('nextcloud-vue', 'Transfer') : $options.t('nextcloud-vue', 'No transfer'),
@@ -166667,7 +166995,7 @@ function render$61(_ctx, _cache, $props, $setup, $data, $options) {
166667
166995
  ]))
166668
166996
  : vue$1.createCommentVNode("v-if", true),
166669
166997
  ($options.outcome)
166670
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_26$x, [
166998
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_26$y, [
166671
166999
  (vue$1.openBlock(), vue$1.createBlock(vue$1.resolveDynamicComponent(_ctx.innerHeadingTag), { class: "cn-inner-heading" }, {
166672
167000
  default: vue$1.withCtx(() => [
166673
167001
  vue$1.createTextVNode(vue$1.toDisplayString($options.t('nextcloud-vue', 'Outcome')), 1 /* TEXT */)
@@ -168401,7 +168729,7 @@ var script$5_ = {
168401
168729
  * segments to the card (`header` event, `count`), for a chip after the
168402
168730
  * title or a `{count}` in it.
168403
168731
  *
168404
- * @type {{inset?: boolean, headerCount?: boolean, source?: {register?: string, schema?: string, groupBy?: string, filter?: object}, segments?: Array<{key?: string, label: string, value: number}>, order?: Array<string>, labels?: {[key: string]: string}, emptyText?: string}}
168732
+ * @type {{inset?: boolean, compact?: boolean, headerCount?: boolean, source?: {register?: string, schema?: string, groupBy?: string, filter?: object}, segments?: Array<{key?: string, label: string, value: number}>, order?: Array<string>, labels?: {[key: string]: string}, emptyText?: string}}
168405
168733
  */
168406
168734
  content: {
168407
168735
  type: Object,
@@ -168459,6 +168787,17 @@ var script$5_ = {
168459
168787
  return Boolean(this.content && this.content.inset === true)
168460
168788
  },
168461
168789
 
168790
+ /**
168791
+ * Whether the widget is the compact summary box (`content.compact`):
168792
+ * a 15px heading, one thin bar and the legend on one line.
168793
+ *
168794
+ * @spec openspec/changes/stacked-bar-compact/specs/stacked-bar-compact/spec.md#requirement-a-stacked-bar-can-be-drawn-as-the-compact-summary-box
168795
+ * @return {boolean}
168796
+ */
168797
+ compact() {
168798
+ return Boolean(this.content && this.content.compact === true)
168799
+ },
168800
+
168462
168801
  /**
168463
168802
  * The translate function in use.
168464
168803
  *
@@ -168815,7 +169154,7 @@ function render$5Z(_ctx, _cache, $props, $setup, $data, $options) {
168815
169154
  const _component_CnWidgetEmptyState = vue$1.resolveComponent("CnWidgetEmptyState");
168816
169155
 
168817
169156
  return (vue$1.openBlock(), vue$1.createElementBlock("div", {
168818
- class: vue$1.normalizeClass(["cn-stacked-bar", { 'cn-stacked-bar--inset': $options.inset }]),
169157
+ class: vue$1.normalizeClass(["cn-stacked-bar", { 'cn-stacked-bar--inset': $options.inset, 'cn-stacked-bar--compact': $options.compact }]),
168819
169158
  "data-testid": "cn-stacked-bar"
168820
169159
  }, [
168821
169160
  ($options.isLoading)
@@ -168875,7 +169214,7 @@ function render$5Z(_ctx, _cache, $props, $setup, $data, $options) {
168875
169214
  }
168876
169215
 
168877
169216
  script$5_.render = render$5Z;
168878
- script$5_.__scopeId = "data-v-455eb41f";
169217
+ script$5_.__scopeId = "data-v-7bd1aead";
168879
169218
  const __sfc_main$5U = script$5_;
168880
169219
 
168881
169220
  /**
@@ -169453,6 +169792,41 @@ function daysAfter(days, now) {
169453
169792
  return date
169454
169793
  }
169455
169794
 
169795
+ /**
169796
+ * Today plus that many days, as a date input's value.
169797
+ *
169798
+ * @param {unknown} days Whole days, 1 or more.
169799
+ * @param {Date} [now] What counts as today.
169800
+ * @return {string} `yyyy-mm-dd`, '' without a positive whole number.
169801
+ * @spec openspec/changes/days-input-as-date/specs/transition-inputs/spec.md
169802
+ */
169803
+ function dayAfterIso(days, now = new Date()) {
169804
+ const date = daysAfter(days, now);
169805
+ if (date === null) {
169806
+ return ''
169807
+ }
169808
+ const pad = (number) => String(number).padStart(2, '0');
169809
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
169810
+ }
169811
+
169812
+ /**
169813
+ * The whole days from today to a date input's value, at least 1.
169814
+ *
169815
+ * @param {unknown} iso A `yyyy-mm-dd` date.
169816
+ * @param {Date} [now] What counts as today.
169817
+ * @return {string} The days, '' without a date.
169818
+ * @spec openspec/changes/days-input-as-date/specs/transition-inputs/spec.md
169819
+ */
169820
+ function daysUntilIso(iso, now = new Date()) {
169821
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(iso ?? '').trim());
169822
+ if (!match) {
169823
+ return ''
169824
+ }
169825
+ const target = Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
169826
+ const today = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate());
169827
+ return String(Math.max(1, Math.round((target - today) / 86400000)))
169828
+ }
169829
+
169456
169830
  /**
169457
169831
  * A date as a reader sees it: day, short month and year.
169458
169832
  *
@@ -169520,6 +169894,9 @@ function fillInputTemplate(template, values, now = new Date()) {
169520
169894
  * in that other input.
169521
169895
  * - `preview: { label, template, note, testid }`: a line under the input,
169522
169896
  * filled by `fillInputTemplate` and shown only once it resolves.
169897
+ * - `dateField: { label, note, testid }`: on a days input, an editable date
169898
+ * under it, today plus the days; a chosen date sets the days to the whole
169899
+ * days from today, at least 1. `dateValue` carries the date.
169523
169900
  * - `default`: the starting value.
169524
169901
  * - Presentation: `labelOutside`, `id`, `note`, `wide`, `testid`, `rows`,
169525
169902
  * `placeholder`.
@@ -169532,7 +169909,7 @@ function fillInputTemplate(template, values, now = new Date()) {
169532
169909
  * @param {string} [options.register] Register the reference pickers fall back to.
169533
169910
  * @param {object} [options.context] What `optionsSource` reads.
169534
169911
  * @param {object} [options.values] The values so far.
169535
- * @param {Date} [options.now] What counts as today, for previews.
169912
+ * @param {Date} [options.now] What counts as today, for previews and `dateField`.
169536
169913
  * @return {Array<object>} The descriptors.
169537
169914
  */
169538
169915
  function resolveInputFields(declared, { schema = null, register = '', context = {}, values = {}, now } = {}) {
@@ -169575,6 +169952,9 @@ function resolveInputFields(declared, { schema = null, register = '', context =
169575
169952
  field.previewText = preview && typeof preview === 'object'
169576
169953
  ? fillInputTemplate(preview.template, values, now)
169577
169954
  : '';
169955
+ field.dateValue = field.input.dateField && typeof field.input.dateField === 'object'
169956
+ ? dayAfterIso(values[field.key], now)
169957
+ : '';
169578
169958
  }
169579
169959
 
169580
169960
  return fields
@@ -170011,6 +170391,28 @@ var script$5Y = {
170011
170391
  return this.markOptional && !field.required ? `${field.label} (${l10n.translate('nextcloud-vue', 'optional')})` : field.label
170012
170392
  },
170013
170393
 
170394
+ /**
170395
+ * The label of a days input's date field (`dateField`), with the
170396
+ * optional marker when asked for: the date is never required.
170397
+ *
170398
+ * @param {object} field The descriptor.
170399
+ * @return {string}
170400
+ */
170401
+ dateFieldLabel(field) {
170402
+ const label = String(field.input.dateField.label || '');
170403
+ return this.markOptional ? `${label} (${l10n.translate('nextcloud-vue', 'optional')})` : label
170404
+ },
170405
+
170406
+ /**
170407
+ * The days a chosen date stands for.
170408
+ *
170409
+ * @param {string} iso The date input's value.
170410
+ * @return {string} The days, '' without a date.
170411
+ */
170412
+ daysUntilIso(iso) {
170413
+ return daysUntilIso(iso)
170414
+ },
170415
+
170014
170416
  /**
170015
170417
  * Label of a sub-field, with the required marker.
170016
170418
  *
@@ -170192,15 +170594,21 @@ const _hoisted_15$1p = {
170192
170594
  };
170193
170595
  const _hoisted_16$1e = ["data-testid"];
170194
170596
  const _hoisted_17$16 = ["data-testid"];
170195
- const _hoisted_18$Z = { class: "cn-transition-input-fields__label" };
170196
- const _hoisted_19$W = ["data-testid"];
170197
- const _hoisted_20$R = {
170597
+ const _hoisted_18$Z = ["for"];
170598
+ const _hoisted_19$W = {
170599
+ key: 0,
170600
+ class: "cn-transition-input-fields__muted"
170601
+ };
170602
+ const _hoisted_20$R = ["data-testid"];
170603
+ const _hoisted_21$P = { class: "cn-transition-input-fields__label" };
170604
+ const _hoisted_22$O = ["data-testid"];
170605
+ const _hoisted_23$K = {
170198
170606
  key: 0,
170199
170607
  class: "cn-transition-input-fields__muted"
170200
170608
  };
170201
- const _hoisted_21$P = { class: "cn-transition-input-fields__notice-text" };
170202
- const _hoisted_22$O = { key: 0 };
170203
- const _hoisted_23$K = ["data-testid"];
170609
+ const _hoisted_24$G = { class: "cn-transition-input-fields__notice-text" };
170610
+ const _hoisted_25$A = { key: 0 };
170611
+ const _hoisted_26$x = ["data-testid"];
170204
170612
 
170205
170613
  function render$5X(_ctx, _cache, $props, $setup, $data, $options) {
170206
170614
  const _component_CnTemplateSelect = vue$1.resolveComponent("CnTemplateSelect");
@@ -170418,20 +170826,44 @@ function render$5X(_ctx, _cache, $props, $setup, $data, $options) {
170418
170826
  }, vue$1.toDisplayString($options.refusalFor(field)), 9 /* TEXT, PROPS */, _hoisted_16$1e))
170419
170827
  : vue$1.createCommentVNode("v-if", true)
170420
170828
  ], 10 /* CLASS, PROPS */, _hoisted_8$2S),
170829
+ (field.input.dateField)
170830
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", {
170831
+ key: `${field.key}-date`,
170832
+ class: "cn-transition-input-fields__field cn-transition-input-fields__wide",
170833
+ "data-testid": field.input.dateField.testid || `cn-transition-input-date-${field.key}`
170834
+ }, [
170835
+ vue$1.createElementVNode("label", {
170836
+ class: "cn-transition-input-fields__label",
170837
+ for: `${$options.inputId(field)}-date`
170838
+ }, vue$1.toDisplayString($options.dateFieldLabel(field)), 9 /* TEXT, PROPS */, _hoisted_18$Z),
170839
+ vue$1.createVNode(_component_NcTextField, {
170840
+ id: `${$options.inputId(field)}-date`,
170841
+ modelValue: field.dateValue,
170842
+ type: "date",
170843
+ label: $options.dateFieldLabel(field),
170844
+ labelOutside: true,
170845
+ disabled: $props.disabled,
170846
+ "onUpdate:modelValue": $event => ($options.setValue(field.key, $options.daysUntilIso($event)))
170847
+ }, null, 8 /* PROPS */, ["id", "modelValue", "label", "disabled", "onUpdate:modelValue"]),
170848
+ (field.input.dateField.note)
170849
+ ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_19$W, vue$1.toDisplayString(field.input.dateField.note), 1 /* TEXT */))
170850
+ : vue$1.createCommentVNode("v-if", true)
170851
+ ], 8 /* PROPS */, _hoisted_17$16))
170852
+ : vue$1.createCommentVNode("v-if", true),
170421
170853
  (field.previewText)
170422
170854
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", {
170423
170855
  key: `${field.key}-preview`,
170424
170856
  class: "cn-transition-input-fields__preview cn-transition-input-fields__wide",
170425
170857
  "data-testid": field.input.preview.testid || `cn-transition-input-preview-${field.key}`
170426
170858
  }, [
170427
- vue$1.createElementVNode("span", _hoisted_18$Z, vue$1.toDisplayString(field.input.preview.label), 1 /* TEXT */),
170859
+ vue$1.createElementVNode("span", _hoisted_21$P, vue$1.toDisplayString(field.input.preview.label), 1 /* TEXT */),
170428
170860
  vue$1.createElementVNode("span", {
170429
170861
  "data-testid": `${field.input.preview.testid || `cn-transition-input-preview-${field.key}`}-value`
170430
- }, vue$1.toDisplayString(field.previewText), 9 /* TEXT, PROPS */, _hoisted_19$W),
170862
+ }, vue$1.toDisplayString(field.previewText), 9 /* TEXT, PROPS */, _hoisted_22$O),
170431
170863
  (field.input.preview.note)
170432
- ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_20$R, vue$1.toDisplayString(field.input.preview.note), 1 /* TEXT */))
170864
+ ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_23$K, vue$1.toDisplayString(field.input.preview.note), 1 /* TEXT */))
170433
170865
  : vue$1.createCommentVNode("v-if", true)
170434
- ], 8 /* PROPS */, _hoisted_17$16))
170866
+ ], 8 /* PROPS */, _hoisted_20$R))
170435
170867
  : vue$1.createCommentVNode("v-if", true)
170436
170868
  ], 64 /* STABLE_FRAGMENT */))
170437
170869
  }), 128 /* KEYED_FRAGMENT */)),
@@ -170447,9 +170879,9 @@ function render$5X(_ctx, _cache, $props, $setup, $data, $options) {
170447
170879
  size: 20,
170448
170880
  class: "cn-transition-input-fields__notice-icon"
170449
170881
  }, null, 8 /* PROPS */, ["name"]),
170450
- vue$1.createElementVNode("span", _hoisted_21$P, [
170882
+ vue$1.createElementVNode("span", _hoisted_24$G, [
170451
170883
  ($props.notice.heading)
170452
- ? (vue$1.openBlock(), vue$1.createElementBlock("strong", _hoisted_22$O, vue$1.toDisplayString($props.notice.heading), 1 /* TEXT */))
170884
+ ? (vue$1.openBlock(), vue$1.createElementBlock("strong", _hoisted_25$A, vue$1.toDisplayString($props.notice.heading), 1 /* TEXT */))
170453
170885
  : vue$1.createCommentVNode("v-if", true),
170454
170886
  vue$1.createElementVNode("span", null, vue$1.toDisplayString($props.notice.text), 1 /* TEXT */)
170455
170887
  ])
@@ -170460,13 +170892,13 @@ function render$5X(_ctx, _cache, $props, $setup, $data, $options) {
170460
170892
  key: `unoffered-${key}`,
170461
170893
  class: "cn-transition-input__field-error",
170462
170894
  "data-testid": `cn-transition-input-error-${key}`
170463
- }, vue$1.toDisplayString($options.t('nextcloud-vue', 'This action does not accept the field "{field}".', { field: key })), 9 /* TEXT, PROPS */, _hoisted_23$K))
170895
+ }, vue$1.toDisplayString($options.t('nextcloud-vue', 'This action does not accept the field "{field}".', { field: key })), 9 /* TEXT, PROPS */, _hoisted_26$x))
170464
170896
  }), 128 /* KEYED_FRAGMENT */))
170465
170897
  ], 2 /* CLASS */))
170466
170898
  }
170467
170899
 
170468
170900
  script$5Y.render = render$5X;
170469
- script$5Y.__scopeId = "data-v-3e4484aa";
170901
+ script$5Y.__scopeId = "data-v-731d1a6c";
170470
170902
  const __sfc_main$5S = script$5Y;
170471
170903
 
170472
170904
  /**
@@ -180250,6 +180682,7 @@ var script$5J = {
180250
180682
  * - `hidden` (boolean) — Hide this property
180251
180683
  * - `editable` (boolean) — Override editability (default: based on schema readOnly)
180252
180684
  * - `label` (string) — Override the display label
180685
+ * - `emptyValue` (string) — Words shown, translated, for an empty or false value instead of a dash
180253
180686
  * - `widget` (string) — Override the widget type for editing
180254
180687
  * - `formatter` (string) — Display the value through the formatter
180255
180688
  * registered under this id on CnAppRoot (`cnFormatters`), called as
@@ -180269,7 +180702,8 @@ var script$5J = {
180269
180702
  },
180270
180703
 
180271
180704
  /**
180272
- * Number of grid columns.
180705
+ * Number of grid columns. The board look fits 200px columns instead,
180706
+ * except for one column, which it keeps.
180273
180707
  */
180274
180708
  columns: {
180275
180709
  type: Number,
@@ -180630,7 +181064,8 @@ var script$5J = {
180630
181064
  // still showing the stored code — `submitted` under a Dutch
180631
181065
  // label, beside a list that already read "Ingediend".
180632
181066
  const enumLabel = this.enumDisplayLabel(field, raw);
180633
- values[field.key] = enumLabel !== null ? enumLabel : formatValue$1(raw, prop || {});
181067
+ const empty = this.emptyValueFor(field, raw);
181068
+ values[field.key] = empty !== null ? empty : (enumLabel !== null ? enumLabel : formatValue$1(raw, prop || {}));
180634
181069
  }
180635
181070
  return values
180636
181071
  },
@@ -180646,8 +181081,12 @@ var script$5J = {
180646
181081
  * CSS grid template for the container.
180647
181082
  */
180648
181083
  gridStyle() {
180649
- // The board look: as many 200px columns as the card has room for.
181084
+ // The board look: as many 200px columns as the card has room for,
181085
+ // unless the config asks for one column (a stacked list of pairs).
180650
181086
  if (normalizeLook(this.cnLook) === 'board') {
181087
+ if (this.columns === 1) {
181088
+ return { 'grid-template-columns': 'minmax(0, 1fr)' }
181089
+ }
180651
181090
  return {
180652
181091
  'grid-template-columns': 'repeat(auto-fit, minmax(200px, 1fr))',
180653
181092
  }
@@ -181372,6 +181811,24 @@ var script$5J = {
181372
181811
  return this.isPlainObject(raw)
181373
181812
  },
181374
181813
 
181814
+ /**
181815
+ * The words an override gives an empty or false value
181816
+ * (`overrides[key].emptyValue`, translated), or null to format it as usual.
181817
+ *
181818
+ * @param {object} field - Resolved field definition from resolvedFields
181819
+ * @param {*} raw - The field's value
181820
+ * @return {string|null}
181821
+ * @spec openspec/changes/data-widget-empty-value-one-column/specs/data-widget/spec.md
181822
+ */
181823
+ emptyValueFor(field, raw) {
181824
+ const override = this.resolvedOverrides[field.key];
181825
+ const words = override && typeof override.emptyValue === 'string' ? override.emptyValue : '';
181826
+ if (words === '' || !(raw === null || raw === undefined || raw === '' || raw === false)) {
181827
+ return null
181828
+ }
181829
+ return this.cnTranslate(words)
181830
+ },
181831
+
181375
181832
  /**
181376
181833
  * Check if a field is editable.
181377
181834
  *
@@ -182496,7 +182953,7 @@ function render$5I(_ctx, _cache, $props, $setup, $data, $options) {
182496
182953
  }
182497
182954
 
182498
182955
  script$5J.render = render$5I;
182499
- script$5J.__scopeId = "data-v-1042b83e";
182956
+ script$5J.__scopeId = "data-v-702c7c98";
182500
182957
  const __sfc_main$5D = script$5J;
182501
182958
 
182502
182959
  /**
@@ -190899,7 +191356,7 @@ const _hoisted_24$F = { class: "cn-timeline-manage__heading" };
190899
191356
  const _hoisted_25$z = { class: "cn-timeline-manage__title" };
190900
191357
  const _hoisted_26$w = { class: "cn-timeline-manage__subtitle" };
190901
191358
  const _hoisted_27$o = { class: "cn-timeline-manage__composer-title" };
190902
- const _hoisted_28$m = { class: "cn-timeline-manage__composer-hint" };
191359
+ const _hoisted_28$n = { class: "cn-timeline-manage__composer-hint" };
190903
191360
  const _hoisted_29$l = ["for"];
190904
191361
  const _hoisted_30$j = ["id", "placeholder"];
190905
191362
  const _hoisted_31$h = { class: "cn-timeline-manage__composer-foot" };
@@ -191155,7 +191612,7 @@ function render$5r(_ctx, _cache, $props, $setup, $data, $options) {
191155
191612
  (!$options.composerOnly)
191156
191613
  ? (vue$1.openBlock(), vue$1.createElementBlock(vue$1.Fragment, { key: 0 }, [
191157
191614
  vue$1.createElementVNode("h3", _hoisted_27$o, vue$1.toDisplayString($options.t('nextcloud-vue', 'Add note')), 1 /* TEXT */),
191158
- vue$1.createElementVNode("p", _hoisted_28$m, vue$1.toDisplayString($options.t('nextcloud-vue', 'Goes on the timeline above, internal unless you make it visible')), 1 /* TEXT */)
191615
+ vue$1.createElementVNode("p", _hoisted_28$n, vue$1.toDisplayString($options.t('nextcloud-vue', 'Goes on the timeline above, internal unless you make it visible')), 1 /* TEXT */)
191159
191616
  ], 64 /* STABLE_FRAGMENT */))
191160
191617
  : vue$1.createCommentVNode("v-if", true),
191161
191618
  ($data.textBlocks.length)
@@ -216294,7 +216751,7 @@ const _hoisted_24$B = { value: "icon-only" };
216294
216751
  const _hoisted_25$w = { class: "cn-links-form__option" };
216295
216752
  const _hoisted_26$u = ["value"];
216296
216753
  const _hoisted_27$n = { value: "small" };
216297
- const _hoisted_28$l = { value: "medium" };
216754
+ const _hoisted_28$m = { value: "medium" };
216298
216755
  const _hoisted_29$k = { value: "large" };
216299
216756
  const _hoisted_30$i = { class: "cn-links-form__option cn-links-form__option--checkbox" };
216300
216757
  const _hoisted_31$g = ["checked"];
@@ -216452,7 +216909,7 @@ function render$4I(_ctx, _cache, $props, $setup, $data, $options) {
216452
216909
  onChange: _cache[3] || (_cache[3] = $event => ($options.updateOption('iconSize', $event.target.value)))
216453
216910
  }, [
216454
216911
  vue$1.createElementVNode("option", _hoisted_27$n, vue$1.toDisplayString(_ctx.t('nextcloud-vue', 'Small (24 px)')), 1 /* TEXT */),
216455
- vue$1.createElementVNode("option", _hoisted_28$l, vue$1.toDisplayString(_ctx.t('nextcloud-vue', 'Medium (40 px)')), 1 /* TEXT */),
216912
+ vue$1.createElementVNode("option", _hoisted_28$m, vue$1.toDisplayString(_ctx.t('nextcloud-vue', 'Medium (40 px)')), 1 /* TEXT */),
216456
216913
  vue$1.createElementVNode("option", _hoisted_29$k, vue$1.toDisplayString(_ctx.t('nextcloud-vue', 'Large (64 px)')), 1 /* TEXT */)
216457
216914
  ], 40 /* PROPS, NEED_HYDRATION */, _hoisted_26$u)
216458
216915
  ]),
@@ -222326,7 +222783,7 @@ const _hoisted_25$v = {
222326
222783
  };
222327
222784
  const _hoisted_26$t = { class: "cn-calendar-widget__agenda-header" };
222328
222785
  const _hoisted_27$m = { class: "cn-calendar-widget__agenda-time" };
222329
- const _hoisted_28$k = { class: "cn-calendar-widget__agenda-title" };
222786
+ const _hoisted_28$l = { class: "cn-calendar-widget__agenda-title" };
222330
222787
  const _hoisted_29$j = {
222331
222788
  key: 0,
222332
222789
  class: "cn-calendar-widget__agenda-cal"
@@ -222444,7 +222901,7 @@ function render$4u(_ctx, _cache, $props, $setup, $data, $options) {
222444
222901
  style: vue$1.normalizeStyle($options.eventStyle(event))
222445
222902
  }, [
222446
222903
  vue$1.createElementVNode("span", _hoisted_27$m, vue$1.toDisplayString($options.formatTime(event)), 1 /* TEXT */),
222447
- vue$1.createElementVNode("span", _hoisted_28$k, vue$1.toDisplayString(event.title), 1 /* TEXT */),
222904
+ vue$1.createElementVNode("span", _hoisted_28$l, vue$1.toDisplayString(event.title), 1 /* TEXT */),
222448
222905
  (event.calendarName)
222449
222906
  ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_29$j, vue$1.toDisplayString(event.calendarName), 1 /* TEXT */))
222450
222907
  : vue$1.createCommentVNode("v-if", true)
@@ -223531,7 +223988,7 @@ const _hoisted_26$s = {
223531
223988
  class: "cn-spend-analytics-widget__panel"
223532
223989
  };
223533
223990
  const _hoisted_27$l = { class: "cn-spend-analytics-widget__panel-title" };
223534
- const _hoisted_28$j = {
223991
+ const _hoisted_28$k = {
223535
223992
  key: 0,
223536
223993
  class: "cn-spend-analytics-widget__inline-empty"
223537
223994
  };
@@ -223647,7 +224104,7 @@ function render$4s(_ctx, _cache, $props, $setup, $data, $options) {
223647
224104
  ? (vue$1.openBlock(), vue$1.createElementBlock("section", _hoisted_26$s, [
223648
224105
  vue$1.createElementVNode("h3", _hoisted_27$l, vue$1.toDisplayString($options.t('nextcloud-vue', 'Top vendors')), 1 /* TEXT */),
223649
224106
  ($options.vendorMissing)
223650
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_28$j, vue$1.toDisplayString($options.t('nextcloud-vue', 'No vendor data — procest is not installed')), 1 /* TEXT */))
224107
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_28$k, vue$1.toDisplayString($options.t('nextcloud-vue', 'No vendor data — procest is not installed')), 1 /* TEXT */))
223651
224108
  : ($options.vendorEmpty)
223652
224109
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_29$i, vue$1.toDisplayString($options.t('nextcloud-vue', 'No vendor commitments for the selected period')), 1 /* TEXT */))
223653
224110
  : (vue$1.openBlock(), vue$1.createElementBlock("ul", _hoisted_30$h, [
@@ -229896,7 +230353,7 @@ const _hoisted_24$x = { class: "cn-tasks-widget__others-title" };
229896
230353
  const _hoisted_25$t = { class: "cn-tasks-widget__others-list" };
229897
230354
  const _hoisted_26$r = ["data-testid"];
229898
230355
  const _hoisted_27$k = { class: "cn-tasks-widget__body" };
229899
- const _hoisted_28$i = {
230356
+ const _hoisted_28$j = {
229900
230357
  key: 1,
229901
230358
  class: "cn-tasks-widget__name"
229902
230359
  };
@@ -230220,7 +230677,7 @@ function render$4g(_ctx, _cache, $props, $setup, $data, $options) {
230220
230677
  ]),
230221
230678
  _: 2 /* DYNAMIC */
230222
230679
  }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["to"]))
230223
- : (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_28$i, vue$1.toDisplayString($options.titleOf(task)), 1 /* TEXT */)),
230680
+ : (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_28$j, vue$1.toDisplayString($options.titleOf(task)), 1 /* TEXT */)),
230224
230681
  vue$1.createElementVNode("span", _hoisted_29$h, vue$1.toDisplayString($options.dueLabel(task) || $options.tr('No due date')), 1 /* TEXT */)
230225
230682
  ]),
230226
230683
  vue$1.createElementVNode("span", _hoisted_30$g, [
@@ -236400,7 +236857,7 @@ var script$48 = {
236400
236857
  * `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`), `pill` or `meter`.
236401
236858
  * `countFrom` (`{ register, schema, filter? }`, `@objectId` / `@object.<field>` in the filter) shows how many records that list holds ("Lopende zaken 43"); the row is left out while the count cannot be read.
236402
236859
  * A `meter` shows the amount at `path` against the limit at `limitPath` (or a constant `limit`) as "12 of 50" with a bar; past the limit the bar turns red and the text says "over the limit"; without a positive limit it shows the amount and no bar.
236403
- * 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.
236860
+ * A row's `format` may also be `email`, `phone` or `url` (a link) or `masked` (the last four characters; `reveal: true` adds Show, which unmasks the record's value; `reveal: { url, path? }` makes Show ask the server for the value at `url` (tokens resolved), read at `path` (else the entry's path), so a server that answers the mask can check and record the reveal). `copy: true` adds a copy button; `icon` replaces the label.
236404
236861
  * A part may be `{ text }`, a constant; a `line` or `text` entry without `path` or `parts` shows its `text`.
236405
236862
  * `resolve: { register, schema, labelField? }` on an entry or a part turns a value that is a reference (an id, or a list of ids) into the label of the referenced object (`labelField`, else `title`, then `name`): the case type or status of a task shows its name, not its uuid. The ids are fetched once per register and schema through the object store and shared by every facts widget on the page; the entry stays empty until its label is known, and when no label can be read. A resolved value works as a pill too: `map` is applied to the resolved label, and `variantMap` / `colorMap` look the tone up by the value of `resolve.toneField` on the referenced object when that is set (a status named by its `name`, coloured by its `state`), then by the resolved label, then by the stored value.
236406
236863
  * `route` (`{ name, params?, query? }`, `@source.<path>` in a param) on a `title` or a `row` links its value to that route; a param that does not fill leaves it plain.
@@ -236454,6 +236911,9 @@ var script$48 = {
236454
236911
  error: false,
236455
236912
  /** Keys of the masked rows the reader chose to show. */
236456
236913
  revealed: {},
236914
+ revealedValues: {},
236915
+ revealErrors: {},
236916
+ revealing: '',
236457
236917
  /** Key of the row whose value was just copied. */
236458
236918
  copied: '',
236459
236919
  /** Labels of the referenced objects read so far, by `refKey`. */
@@ -237301,7 +237761,12 @@ var script$48 = {
237301
237761
  extras.external = true;
237302
237762
  } else if (field.format === 'masked') {
237303
237763
  extras.masked = value.length > 4 ? '*'.repeat(Math.min(value.length - 4, 5)) + value.slice(-4) : value;
237304
- extras.reveal = field.reveal === true && value.length > 4;
237764
+ const server = field.reveal && typeof field.reveal === 'object' && typeof field.reveal.url === 'string' && field.reveal.url !== '';
237765
+ extras.reveal = (field.reveal === true || server) && value.length > 4;
237766
+ if (server) {
237767
+ extras.revealUrl = field.reveal.url;
237768
+ extras.revealPath = typeof field.reveal.path === 'string' && field.reveal.path !== '' ? field.reveal.path : (field.path || '');
237769
+ }
237305
237770
  }
237306
237771
  return extras
237307
237772
  },
@@ -237313,7 +237778,14 @@ var script$48 = {
237313
237778
  * @param {object} row The row.
237314
237779
  * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-the-facts-card-draws-contact-details-identity-and-a-lead
237315
237780
  */
237316
- reveal(row) {
237781
+ async reveal(row) {
237782
+ if (row.revealUrl) {
237783
+ const value = await this.fetchRevealed(row);
237784
+ if (value === null) {
237785
+ return
237786
+ }
237787
+ this.revealedValues = { ...this.revealedValues, [row.key]: value };
237788
+ }
237317
237789
  this.revealed = { ...this.revealed, [row.key]: true };
237318
237790
  const detail = { path: row.path };
237319
237791
  /**
@@ -237324,6 +237796,47 @@ var script$48 = {
237324
237796
  eventBus.emit(FACTS_REVEAL_CHANNEL, { ...detail, objectId: this.tokenCtx.objectId, register: this.tokenCtx.register || null, schema: this.tokenCtx.schema || null });
237325
237797
  },
237326
237798
 
237799
+ /**
237800
+ * The text a row shows: its mask until revealed, then the value the
237801
+ * server answered (a server reveal) or the value the record holds.
237802
+ *
237803
+ * @param {object} row The row.
237804
+ * @return {string}
237805
+ * @spec openspec/changes/facts-reveal-from-server/specs/facts-widget/spec.md#requirement-a-masked-fact-can-be-revealed-by-the-server
237806
+ */
237807
+ shownValue(row) {
237808
+ if (!row.masked || !this.revealed[row.key]) {
237809
+ return row.masked || row.value
237810
+ }
237811
+ return Object.hasOwn(this.revealedValues, row.key) ? this.revealedValues[row.key] : row.value
237812
+ },
237813
+
237814
+ /**
237815
+ * Ask the server for a masked value (`reveal.url`, tokens resolved,
237816
+ * read at `reveal.path`), never from a cache: each show is a request
237817
+ * the server can check and record.
237818
+ *
237819
+ * @param {object} row The row.
237820
+ * @return {Promise<string|null>} The value, or null when it could not be read.
237821
+ * @spec openspec/changes/facts-reveal-from-server/specs/facts-widget/spec.md#requirement-a-masked-fact-can-be-revealed-by-the-server
237822
+ */
237823
+ async fetchRevealed(row) {
237824
+ this.revealing = row.key;
237825
+ this.revealErrors = { ...this.revealErrors, [row.key]: '' };
237826
+ try {
237827
+ const payload = await fetchEndpointSource({ url: row.revealUrl, responsePath: row.revealPath }, this.tokenCtx, { force: true });
237828
+ if (payload === null || payload === undefined || payload === '' || typeof payload === 'object') {
237829
+ throw new Error('no value')
237830
+ }
237831
+ return String(payload)
237832
+ } catch {
237833
+ this.revealErrors = { ...this.revealErrors, [row.key]: l10n.translate('nextcloud-vue', 'The value could not be shown.') };
237834
+ return null
237835
+ } finally {
237836
+ this.revealing = '';
237837
+ }
237838
+ },
237839
+
237327
237840
  /**
237328
237841
  * Copy a row's value to the clipboard.
237329
237842
  *
@@ -237826,26 +238339,27 @@ const _hoisted_7$2l = ["data-testid"];
237826
238339
  const _hoisted_8$27 = { class: "cn-facts-widget__value" };
237827
238340
  const _hoisted_9$1Y = ["href", "target", "rel"];
237828
238341
  const _hoisted_10$1L = { key: 2 };
237829
- const _hoisted_11$1y = ["aria-label", "data-testid", "onClick"];
237830
- const _hoisted_12$1l = ["aria-label", "title", "data-testid", "onClick"];
237831
- const _hoisted_13$19 = ["data-testid"];
238342
+ const _hoisted_11$1y = ["aria-label", "data-testid", "disabled", "onClick"];
238343
+ const _hoisted_12$1l = ["data-testid"];
238344
+ const _hoisted_13$19 = ["aria-label", "title", "data-testid", "onClick"];
237832
238345
  const _hoisted_14$16 = ["data-testid"];
237833
238346
  const _hoisted_15$12 = ["data-testid"];
237834
- const _hoisted_16$X = { class: "cn-facts-widget__lead-text" };
237835
- const _hoisted_17$S = { class: "cn-facts-widget__lead-title" };
237836
- const _hoisted_18$K = {
238347
+ const _hoisted_16$X = ["data-testid"];
238348
+ const _hoisted_17$S = { class: "cn-facts-widget__lead-text" };
238349
+ const _hoisted_18$K = { class: "cn-facts-widget__lead-title" };
238350
+ const _hoisted_19$I = {
237837
238351
  key: 0,
237838
238352
  class: "cn-facts-widget__lead-subtitle"
237839
238353
  };
237840
- const _hoisted_19$I = ["data-testid"];
237841
- const _hoisted_20$D = { class: "cn-facts-widget__meter-head" };
237842
- const _hoisted_21$C = { class: "cn-facts-widget__meter-label" };
237843
- const _hoisted_22$C = { class: "cn-facts-widget__meter-value" };
237844
- const _hoisted_23$z = ["aria-label", "aria-valuenow", "aria-valuemax", "aria-valuetext", "data-testid"];
237845
- const _hoisted_24$w = ["data-testid"];
237846
- const _hoisted_25$s = ["aria-label", "aria-valuenow", "data-testid"];
237847
- const _hoisted_26$q = { class: "cn-facts-widget__line" };
237848
- const _hoisted_27$j = { key: 7 };
238354
+ const _hoisted_20$D = ["data-testid"];
238355
+ const _hoisted_21$C = { class: "cn-facts-widget__meter-head" };
238356
+ const _hoisted_22$C = { class: "cn-facts-widget__meter-label" };
238357
+ const _hoisted_23$z = { class: "cn-facts-widget__meter-value" };
238358
+ const _hoisted_24$w = ["aria-label", "aria-valuenow", "aria-valuemax", "aria-valuetext", "data-testid"];
238359
+ const _hoisted_25$s = ["data-testid"];
238360
+ const _hoisted_26$q = ["aria-label", "aria-valuenow", "data-testid"];
238361
+ const _hoisted_27$j = { class: "cn-facts-widget__line" };
238362
+ const _hoisted_28$i = { key: 7 };
237849
238363
 
237850
238364
  function render$47(_ctx, _cache, $props, $setup, $data, $options) {
237851
238365
  const _component_NcLoadingIcon = vue$1.resolveComponent("NcLoadingIcon");
@@ -237941,11 +238455,11 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
237941
238455
  class: "cn-facts-widget__value-link"
237942
238456
  }, {
237943
238457
  default: vue$1.withCtx(() => [
237944
- vue$1.createTextVNode(vue$1.toDisplayString(row.masked && !$data.revealed[row.key] ? row.masked : row.value), 1 /* TEXT */)
238458
+ vue$1.createTextVNode(vue$1.toDisplayString($options.shownValue(row)), 1 /* TEXT */)
237945
238459
  ]),
237946
238460
  _: 2 /* DYNAMIC */
237947
238461
  }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["to"]))
237948
- : (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_10$1L, vue$1.toDisplayString(row.masked && !$data.revealed[row.key] ? row.masked : row.value), 1 /* TEXT */)),
238462
+ : (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_10$1L, vue$1.toDisplayString($options.shownValue(row)), 1 /* TEXT */)),
237949
238463
  (row.reveal && !$data.revealed[row.key])
237950
238464
  ? (vue$1.openBlock(), vue$1.createElementBlock("button", {
237951
238465
  key: 3,
@@ -237953,6 +238467,7 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
237953
238467
  class: "cn-facts-widget__inline-button",
237954
238468
  "aria-label": $options.revealLabel(row),
237955
238469
  "data-testid": `cn-fact-reveal-${row.key}`,
238470
+ disabled: $data.revealing === row.key,
237956
238471
  onClick: $event => ($options.reveal(row))
237957
238472
  }, [
237958
238473
  vue$1.createVNode(_component_EyeOutline, {
@@ -237962,9 +238477,17 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
237962
238477
  vue$1.createTextVNode(" " + vue$1.toDisplayString($options.showLabel), 1 /* TEXT */)
237963
238478
  ], 8 /* PROPS */, _hoisted_11$1y))
237964
238479
  : vue$1.createCommentVNode("v-if", true),
238480
+ ($data.revealErrors[row.key])
238481
+ ? (vue$1.openBlock(), vue$1.createElementBlock("span", {
238482
+ key: 4,
238483
+ class: "cn-facts-widget__reveal-error",
238484
+ role: "alert",
238485
+ "data-testid": `cn-fact-reveal-error-${row.key}`
238486
+ }, vue$1.toDisplayString($data.revealErrors[row.key]), 9 /* TEXT, PROPS */, _hoisted_12$1l))
238487
+ : vue$1.createCommentVNode("v-if", true),
237965
238488
  (row.copy)
237966
238489
  ? (vue$1.openBlock(), vue$1.createElementBlock("button", {
237967
- key: 4,
238490
+ key: 5,
237968
238491
  type: "button",
237969
238492
  class: "cn-facts-widget__icon-button",
237970
238493
  "aria-label": $options.copyLabel(row),
@@ -237983,7 +238506,7 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
237983
238506
  size: 16,
237984
238507
  "aria-hidden": "true"
237985
238508
  }))
237986
- ], 8 /* PROPS */, _hoisted_12$1l))
238509
+ ], 8 /* PROPS */, _hoisted_13$19))
237987
238510
  : vue$1.createCommentVNode("v-if", true)
237988
238511
  ])
237989
238512
  ], 8 /* PROPS */, _hoisted_7$2l))
@@ -238006,13 +238529,13 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
238006
238529
  key: 2,
238007
238530
  class: "cn-facts-widget__line",
238008
238531
  "data-testid": `cn-fact-${block.key}`
238009
- }, vue$1.toDisplayString(block.value), 9 /* TEXT, PROPS */, _hoisted_13$19))
238532
+ }, vue$1.toDisplayString(block.value), 9 /* TEXT, PROPS */, _hoisted_14$16))
238010
238533
  : (block.kind === 'text')
238011
238534
  ? (vue$1.openBlock(), vue$1.createElementBlock("span", {
238012
238535
  key: 3,
238013
238536
  class: "cn-facts-widget__text",
238014
238537
  "data-testid": `cn-fact-${block.key}`
238015
- }, vue$1.toDisplayString(block.value), 9 /* TEXT, PROPS */, _hoisted_14$16))
238538
+ }, vue$1.toDisplayString(block.value), 9 /* TEXT, PROPS */, _hoisted_15$12))
238016
238539
  : (block.kind === 'lead')
238017
238540
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", {
238018
238541
  key: 4,
@@ -238033,22 +238556,22 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
238033
238556
  vue$1.createTextVNode(vue$1.toDisplayString(block.initials), 1 /* TEXT */)
238034
238557
  ], 64 /* STABLE_FRAGMENT */))
238035
238558
  ], 2 /* CLASS */),
238036
- vue$1.createElementVNode("span", _hoisted_16$X, [
238037
- vue$1.createElementVNode("strong", _hoisted_17$S, vue$1.toDisplayString(block.value), 1 /* TEXT */),
238559
+ vue$1.createElementVNode("span", _hoisted_17$S, [
238560
+ vue$1.createElementVNode("strong", _hoisted_18$K, vue$1.toDisplayString(block.value), 1 /* TEXT */),
238038
238561
  (block.subtitle)
238039
- ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_18$K, vue$1.toDisplayString(block.subtitle), 1 /* TEXT */))
238562
+ ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_19$I, vue$1.toDisplayString(block.subtitle), 1 /* TEXT */))
238040
238563
  : vue$1.createCommentVNode("v-if", true)
238041
238564
  ])
238042
- ], 8 /* PROPS */, _hoisted_15$12))
238565
+ ], 8 /* PROPS */, _hoisted_16$X))
238043
238566
  : (block.kind === 'meter')
238044
238567
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", {
238045
238568
  key: 5,
238046
238569
  class: vue$1.normalizeClass(["cn-facts-widget__meter", { 'cn-facts-widget__meter--over': block.over }]),
238047
238570
  "data-testid": `cn-fact-${block.key}`
238048
238571
  }, [
238049
- vue$1.createElementVNode("div", _hoisted_20$D, [
238050
- vue$1.createElementVNode("span", _hoisted_21$C, vue$1.toDisplayString(block.label), 1 /* TEXT */),
238051
- vue$1.createElementVNode("span", _hoisted_22$C, vue$1.toDisplayString(block.value), 1 /* TEXT */)
238572
+ vue$1.createElementVNode("div", _hoisted_21$C, [
238573
+ vue$1.createElementVNode("span", _hoisted_22$C, vue$1.toDisplayString(block.label), 1 /* TEXT */),
238574
+ vue$1.createElementVNode("span", _hoisted_23$z, vue$1.toDisplayString(block.value), 1 /* TEXT */)
238052
238575
  ]),
238053
238576
  (block.limit !== null)
238054
238577
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", {
@@ -238066,9 +238589,9 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
238066
238589
  class: "cn-facts-widget__meter-bar",
238067
238590
  style: vue$1.normalizeStyle({ width: `${block.percent}%` })
238068
238591
  }, null, 4 /* STYLE */)
238069
- ], 8 /* PROPS */, _hoisted_23$z))
238592
+ ], 8 /* PROPS */, _hoisted_24$w))
238070
238593
  : vue$1.createCommentVNode("v-if", true)
238071
- ], 10 /* CLASS, PROPS */, _hoisted_19$I))
238594
+ ], 10 /* CLASS, PROPS */, _hoisted_20$D))
238072
238595
  : (block.kind === 'progress')
238073
238596
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", {
238074
238597
  key: 6,
@@ -238090,12 +238613,12 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
238090
238613
  class: "cn-facts-widget__meter-bar",
238091
238614
  style: vue$1.normalizeStyle({ width: `${block.percent}%` })
238092
238615
  }, null, 4 /* STYLE */)
238093
- ], 8 /* PROPS */, _hoisted_25$s))
238616
+ ], 8 /* PROPS */, _hoisted_26$q))
238094
238617
  : vue$1.createCommentVNode("v-if", true),
238095
- vue$1.createElementVNode("span", _hoisted_26$q, vue$1.toDisplayString(block.value), 1 /* TEXT */)
238096
- ], 8 /* PROPS */, _hoisted_24$w))
238618
+ vue$1.createElementVNode("span", _hoisted_27$j, vue$1.toDisplayString(block.value), 1 /* TEXT */)
238619
+ ], 8 /* PROPS */, _hoisted_25$s))
238097
238620
  : (block.kind === 'pill')
238098
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_27$j, [
238621
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_28$i, [
238099
238622
  vue$1.createVNode(_component_CnStatusBadge, {
238100
238623
  label: block.value,
238101
238624
  variant: block.variant,
@@ -238189,7 +238712,7 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
238189
238712
  }
238190
238713
 
238191
238714
  script$48.render = render$47;
238192
- script$48.__scopeId = "data-v-e3954500";
238715
+ script$48.__scopeId = "data-v-1b8f8e6f";
238193
238716
  const __sfc_main$48 = script$48;
238194
238717
 
238195
238718
  /**
@@ -250022,6 +250545,13 @@ registerDashboardWidget('choice-picker', {
250022
250545
  /** The bus channel every endpoint-bound widget on the page refetches on. */
250023
250546
  const PAGE_REFRESH_CHANNEL$3 = 'cn:page:refresh';
250024
250547
 
250548
+ /**
250549
+ * The document event a recorded note sends, so a row's leaf (humaniq's
250550
+ * hours) can record what it holds for the same object at the same moment.
250551
+ * `detail` is the row's leaf props plus `note`.
250552
+ */
250553
+ const NOTE_RECORDED_EVENT = 'cn-record-notes:recorded';
250554
+
250025
250555
  /**
250026
250556
  * The rows of a record list drawn as notes to record (`presentation:
250027
250557
  * "notes"`, DqDagAfsluiten "Nog open van vandaag"): per row its pill, title,
@@ -250256,6 +250786,9 @@ var script$3R = {
250256
250786
  try {
250257
250787
  const { default: axios } = await import('@nextcloud/axios');
250258
250788
  await axios.post(prefixUrl(url), { [this.action.bodyField || 'message']: text });
250789
+ if (this.leaf) {
250790
+ document.dispatchEvent(new CustomEvent(NOTE_RECORDED_EVENT, { detail: { ...this.leafProps(item.row), note: text } }));
250791
+ }
250259
250792
  const { [item.key]: done, ...rest } = this.drafts;
250260
250793
  this.drafts = rest;
250261
250794
  if (refresh) {
@@ -250487,7 +251020,7 @@ function render$3Q(_ctx, _cache, $props, $setup, $data, $options) {
250487
251020
  }
250488
251021
 
250489
251022
  script$3R.render = render$3Q;
250490
- script$3R.__scopeId = "data-v-6d7ff53b";
251023
+ script$3R.__scopeId = "data-v-56a223c4";
250491
251024
  const __sfc_main$3R = script$3R;
250492
251025
 
250493
251026
  const PAGE_REFRESH_CHANNEL$2 = 'cn:page:refresh';
@@ -320562,6 +321095,27 @@ function formatMasked(value, _row, _property, options) {
320562
321095
  return '*'.repeat(Math.min(text.length - visible, 5)) + (visible > 0 ? text.slice(-visible) : '')
320563
321096
  }
320564
321097
 
321098
+ /**
321099
+ * A stored value through a display map (`valueMap` registry key): the label
321100
+ * `options.map` gives the value, else the value as stored. "Besloten
321101
+ * Vennootschap" reads "BV" on DqOrganisaties.
321102
+ *
321103
+ * @param {unknown} value The stored value.
321104
+ * @param {object} [_row] The full row (unused).
321105
+ * @param {object} [_property] The schema property (unused).
321106
+ * @param {{map?: Object<string, string>}} [options] The column's `formatterOptions`.
321107
+ * @return {string} The label, or the value.
321108
+ * @spec openspec/changes/value-map-formatter/specs/index-page/spec.md#requirement-a-column-can-show-stored-values-through-a-display-map
321109
+ */
321110
+ function formatValueMap(value, _row, _property, options) {
321111
+ if (value === null || value === undefined || value === '') {
321112
+ return ''
321113
+ }
321114
+ const map = options && options.map && typeof options.map === 'object' ? options.map : {};
321115
+ const label = Object.hasOwn(map, String(value)) ? map[String(value)] : undefined;
321116
+ return typeof label === 'string' && label !== '' ? label : String(value)
321117
+ }
321118
+
320565
321119
  /**
320566
321120
  * Built-in formatter registry merged under any consumer-registered
320567
321121
  * `formatters` in `CnAppRoot`'s `cnFormatters` provide.
@@ -320608,6 +321162,7 @@ const BUILT_IN_FORMATTERS = {
320608
321162
  connectionSettingsLabel: formatConnectionSettingsLabel,
320609
321163
  masked: formatMasked,
320610
321164
  isoDuration: formatIsoDuration,
321165
+ valueMap: formatValueMap,
320611
321166
  };
320612
321167
 
320613
321168
  /**
@@ -344082,6 +344637,7 @@ var script$2d = {
344082
344637
  Pin,
344083
344638
  PinOutline,
344084
344639
  InformationOutline,
344640
+ Magnify,
344085
344641
  },
344086
344642
 
344087
344643
  inject: {
@@ -345170,6 +345726,7 @@ const _hoisted_29$a = { class: "cn-index-sidebar__section" };
345170
345726
  function render$2c(_ctx, _cache, $props, $setup, $data, $options) {
345171
345727
  const _component_CnIcon = vue$1.resolveComponent("CnIcon");
345172
345728
  const _component_FilterOutline = vue$1.resolveComponent("FilterOutline");
345729
+ const _component_Magnify = vue$1.resolveComponent("Magnify");
345173
345730
  const _component_NcTextField = vue$1.resolveComponent("NcTextField");
345174
345731
  const _component_NcSelect = vue$1.resolveComponent("NcSelect");
345175
345732
  const _component_ArrowUp = vue$1.resolveComponent("ArrowUp");
@@ -345226,9 +345783,19 @@ function render$2c(_ctx, _cache, $props, $setup, $data, $options) {
345226
345783
  vue$1.createVNode(_component_NcTextField, {
345227
345784
  modelValue: $props.searchValue || '',
345228
345785
  placeholder: $props.searchPlaceholder,
345229
- label: $props.searchLabel,
345786
+ label: $options.isBoardLook ? $props.searchPlaceholder : $props.searchLabel,
345230
345787
  "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => (_ctx.$emit('search', $event)))
345231
- }, null, 8 /* PROPS */, ["modelValue", "placeholder", "label"])
345788
+ }, vue$1.createSlots({ _: 2 /* DYNAMIC */ }, [
345789
+ ($options.isBoardLook)
345790
+ ? {
345791
+ name: "icon",
345792
+ fn: vue$1.withCtx(() => [
345793
+ vue$1.createVNode(_component_Magnify, { size: 18 })
345794
+ ]),
345795
+ key: "0"
345796
+ }
345797
+ : undefined
345798
+ ]), 1032 /* PROPS, DYNAMIC_SLOTS */, ["modelValue", "placeholder", "label"])
345232
345799
  ]),
345233
345800
  ($props.sortOptions.length > 0)
345234
345801
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_5$1z, [
@@ -351852,6 +352419,21 @@ var script$20 = {
351852
352419
  default: '',
351853
352420
  },
351854
352421
 
352422
+ /**
352423
+ * Board look: the count line while a saved view is applied (manifest
352424
+ * `config.savedViewCountText`), a template as `countText` that may name
352425
+ * the view's conditions as `{view}`: its description, else its filters
352426
+ * in words (DqFilterInMenu "3 van 48 lopende zaken · termijn binnen 7
352427
+ * dagen, van mij · gesorteerd op termijn"). A view's own `countText`
352428
+ * override and a scope's come first.
352429
+ *
352430
+ * @spec openspec/changes/applied-view-lights-its-lens/specs/applied-view-lights-its-lens/spec.md
352431
+ */
352432
+ savedViewCountText: {
352433
+ type: String,
352434
+ default: '',
352435
+ },
352436
+
351855
352437
  /**
351856
352438
  * Board look: a count line per view (manifest `config.countTextByView`),
351857
352439
  * `{ table?, cards?, list?, board?, map? }`, each a template as
@@ -354192,6 +354774,8 @@ var script$20 = {
354192
354774
  savedViewCounts: null,
354193
354775
  // The id (or slug) of the saved view that is applied; its chip shows selected under the board look.
354194
354776
  appliedSavedViewId: '',
354777
+ /** Whether the reader picked a lens chip after the applied view (then their chip shows). */
354778
+ lensChosenByHand: false,
354195
354779
  // The sort option the applied view's `overrides.sortOption` selected, so leaving the view can undo it.
354196
354780
  viewSortOption: '',
354197
354781
  // The applied view's filters as one comparable key, so the address keeps naming the view until they change.
@@ -354265,6 +354849,8 @@ var script$20 = {
354265
354849
  savedViews: [],
354266
354850
  savedViewsLoading: false,
354267
354851
  showSaveViewDialog: false,
354852
+ /** Whether the reader changed the view since it was opened or saved. */
354853
+ viewChanged: false,
354268
354854
  viewPendingDelete: null,
354269
354855
  /** An `api-call` header action waiting on its confirmation, or null. */
354270
354856
  headerApiCallPending: null,
@@ -356031,13 +356617,53 @@ var script$20 = {
356031
356617
  * @spec openspec/changes/working-list-row-actions/specs/index-page/spec.md
356032
356618
  */
356033
356619
  /**
356034
- * The chip the strip lights: the active tab, else the default tab of
356035
- * tabs that arrived after setup (`quickFiltersFrom`), which is the
356036
- * one the fetch applies.
356620
+ * The lens chip an applied saved view narrows within: the non-default
356621
+ * tab whose every condition the view's own conditions (its filters
356622
+ * over its locked filters, tokens as written) hold, the most specific
356623
+ * first (DqFilterInMenu: "Termijn deze week" lit for the view of that
356624
+ * name). Null without an applied view or a fitting tab.
356625
+ *
356626
+ * @return {number|null} The tab index.
356627
+ * @spec openspec/changes/applied-view-lights-its-lens/specs/applied-view-lights-its-lens/spec.md
356628
+ */
356629
+ appliedViewLensIndex() {
356630
+ const view = this.appliedSavedView;
356631
+ const tabs = Array.isArray(this.tabStripEntries) ? this.tabStripEntries : [];
356632
+ if (!view || tabs.length === 0) {
356633
+ return null
356634
+ }
356635
+ const query = view.query && typeof view.query === 'object' ? view.query : {};
356636
+ const locked = query.overrides && typeof query.overrides.lockedFilters === 'object' ? query.overrides.lockedFilters : {};
356637
+ const conditions = { ...(locked || {}), ...(query.filters || view.filters || {}) };
356638
+ const same = (a, b) => JSON.stringify(Array.isArray(a) ? a.map(String) : String(a)) === JSON.stringify(Array.isArray(b) ? b.map(String) : String(b));
356639
+ let best = null;
356640
+ let bestSize = 0;
356641
+ tabs.forEach((tab, index) => {
356642
+ const filter = tab && tab.filter && typeof tab.filter === 'object' ? tab.filter : {};
356643
+ const keys = Object.keys(filter);
356644
+ if (tab.default === true || keys.length === 0 || keys.length <= bestSize) {
356645
+ return
356646
+ }
356647
+ if (keys.every((key) => Object.hasOwn(conditions, key) && same(conditions[key], filter[key]))) {
356648
+ best = index;
356649
+ bestSize = keys.length;
356650
+ }
356651
+ });
356652
+ return best
356653
+ },
356654
+
356655
+ /**
356656
+ * The chip the strip lights: the lens an applied saved view narrows
356657
+ * within (until the reader picks a chip), else the active tab, else
356658
+ * the default tab of tabs that arrived after setup
356659
+ * (`quickFiltersFrom`), which is the one the fetch applies.
356037
356660
  *
356038
356661
  * @return {number|null} The tab index, or null without tabs.
356039
356662
  */
356040
356663
  shownQuickFilterIndex() {
356664
+ if (!this.quickFilterMultiple && !this.lensChosenByHand && this.appliedViewLensIndex !== null) {
356665
+ return this.appliedViewLensIndex
356666
+ }
356041
356667
  if (this.activeQuickFilterIndex !== null && this.activeQuickFilterIndex !== undefined) {
356042
356668
  return this.activeQuickFilterIndex
356043
356669
  }
@@ -358261,12 +358887,20 @@ var script$20 = {
358261
358887
 
358262
358888
  /**
358263
358889
  * Whether the toolbar draws Save view: not when the page switches it
358264
- * off (`savedViewSave: false`) or its header carries one.
358890
+ * off (`savedViewSave: false`) or its header carries one, and in the
358891
+ * board look only once the view has changed.
358265
358892
  *
358893
+ * @spec openspec/changes/board-save-view-only-when-changed/specs/saved-views/spec.md#requirement-the-board-look-offers-save-view-once-the-view-has-changed
358266
358894
  * @return {boolean}
358267
358895
  */
358268
358896
  toolbarSavesView() {
358269
- return this.savedViewSave !== false && !this.headerSavesView
358897
+ if (this.savedViewSave === false || this.headerSavesView) {
358898
+ return false
358899
+ }
358900
+ // The board look offers it once the view differs from the one
358901
+ // opened: changed by the reader, or, with no saved view open,
358902
+ // filtered by the address.
358903
+ return !this.isBoardLook || this.viewChanged || (!this.appliedSavedViewId && this.activeFilterChips.length > 0)
358270
358904
  },
358271
358905
 
358272
358906
  /**
@@ -358427,6 +359061,11 @@ var script$20 = {
358427
359061
  * @return {string}
358428
359062
  */
358429
359063
  presentedCountText() {
359064
+ const viewLine = this.savedViewCountText;
359065
+ const view = this.appliedSavedView;
359066
+ if (view && typeof viewLine === 'string' && viewLine !== '' && this.activeScopePresentation.countText === undefined && !savedViewOverrides(view)?.countText) {
359067
+ return viewLine
359068
+ }
358430
359069
  if (this.activeScopePresentation.countText === undefined) {
358431
359070
  const byView = this.countTextByView;
358432
359071
  const own = byView && typeof byView === 'object' ? byView[this.currentViewMode] : undefined;
@@ -358437,6 +359076,23 @@ var script$20 = {
358437
359076
  return this.presented('countText')
358438
359077
  },
358439
359078
 
359079
+ /**
359080
+ * The applied saved view's conditions for the count line's `{view}`:
359081
+ * its description, else its filters in words, starting lower case.
359082
+ *
359083
+ * @return {string}
359084
+ * @spec openspec/changes/applied-view-lights-its-lens/specs/applied-view-lights-its-lens/spec.md
359085
+ */
359086
+ appliedViewConditionsText() {
359087
+ const view = this.appliedSavedView;
359088
+ if (!view) {
359089
+ return ''
359090
+ }
359091
+ const own = typeof view.description === 'string' ? view.description.trim() : '';
359092
+ const text = own !== '' ? own : String(this.viewFilterSummary(view) || '');
359093
+ return text.charAt(0).toLowerCase() + text.slice(1)
359094
+ },
359095
+
358440
359096
  headerDescriptionBase() {
358441
359097
  const total = this.effectivePagination?.total;
358442
359098
  // Board look: the count line is a template over the rows on this page
@@ -358460,6 +359116,7 @@ var script$20 = {
358460
359116
  .replace('{shown}', String(shown))
358461
359117
  .replace('{total}', String(total))
358462
359118
  .replace(/\{term\}/g, this.sourceTerm)
359119
+ .replace(/\{view\}/g, this.appliedViewConditionsText)
358463
359120
  }
358464
359121
  return this.countLine(this.countSubtitle, total, shown)
358465
359122
  }
@@ -358525,6 +359182,17 @@ var script$20 = {
358525
359182
  return !!this.resolvedSidebar.enabled && this.resolvedSidebar.show !== false
358526
359183
  },
358527
359184
 
359185
+ /**
359186
+ * Whether the page's side panel is open now; the board look then lays
359187
+ * the header out narrower (DqZakenSorteren).
359188
+ *
359189
+ * @spec openspec/changes/board-header-buttons-under-title-beside-side-panel/specs/board-header-buttons-under-title-beside-side-panel/spec.md#requirement-the-header-buttons-sit-under-the-title-while-the-side-panel-is-open
359190
+ * @return {boolean}
359191
+ */
359192
+ sidebarPanelOpen() {
359193
+ return Boolean(this.sidebarOpen && this.hasSidebar)
359194
+ },
359195
+
358528
359196
  /**
358529
359197
  * Whether the board toolbar draws the Filter button as a menu: the
358530
359198
  * board look, `filterMenu` on, something to filter by, and no filter
@@ -358567,7 +359235,9 @@ var script$20 = {
358567
359235
 
358568
359236
  /** Search props forwarded to the embedded CnIndexSidebar (defaults applied per CnIndexSidebar). */
358569
359237
  sidebarSearchProps() {
358570
- return (this.sidebar && this.sidebar.search) || {}
359238
+ // The page's own search hint, unless `sidebar.search` names one.
359239
+ const own = this.shownSearchPlaceholder ? { searchPlaceholder: this.shownSearchPlaceholder } : {};
359240
+ return { ...own, ...((this.sidebar && this.sidebar.search) || {}) }
358571
359241
  },
358572
359242
 
358573
359243
  /**
@@ -360368,6 +361038,7 @@ var script$20 = {
360368
361038
  * @return {void}
360369
361039
  */
360370
361040
  onSearchEvent(value) {
361041
+ this.viewChanged = true;
360371
361042
  if (this.namedSourceSearches) {
360372
361043
  this.namedSearchTerm = value || '';
360373
361044
  }
@@ -360387,9 +361058,11 @@ var script$20 = {
360387
361058
  * @return {void}
360388
361059
  */
360389
361060
  onQuickFilterChange(index) {
361061
+ this.viewChanged = true;
360390
361062
  // `activeQuickFilterIndex` is a setup-returned ref; the
360391
361063
  // Vue 2 ref-unwrap proxy makes plain assignment work.
360392
361064
  this.activeQuickFilterIndex = index;
361065
+ this.lensChosenByHand = true;
360393
361066
  this.$emit('quick-filter-change', index);
360394
361067
  },
360395
361068
 
@@ -360402,6 +361075,7 @@ var script$20 = {
360402
361075
  * @return {void}
360403
361076
  */
360404
361077
  onQuickFilterMultiChange(indices) {
361078
+ this.viewChanged = true;
360405
361079
  this.selectedQuickFilterIndices = Array.isArray(indices) ? indices : [];
360406
361080
  this.$emit('quick-filter-change', this.selectedQuickFilterIndices);
360407
361081
  },
@@ -360417,6 +361091,7 @@ var script$20 = {
360417
361091
  * @return {void}
360418
361092
  */
360419
361093
  onSidebarSortChange(keys, value) {
361094
+ this.viewChanged = true;
360420
361095
  if (value !== undefined && this.selectSortOption(value)) {
360421
361096
  return
360422
361097
  }
@@ -360496,6 +361171,7 @@ var script$20 = {
360496
361171
  * @return {void}
360497
361172
  */
360498
361173
  onSortEvent(payload) {
361174
+ this.viewChanged = true;
360499
361175
  // An order by keys (a column header, a field in the Sort section)
360500
361176
  // leaves an endpoint-ranked order.
360501
361177
  if (this.sortSource.value) {
@@ -360699,6 +361375,7 @@ var script$20 = {
360699
361375
  * @return {void}
360700
361376
  */
360701
361377
  onClearFilters() {
361378
+ this.viewChanged = true;
360702
361379
  // "Clear all" on the Active line also undoes the quick filters it
360703
361380
  // listed (a tab without an `activeLabel` is not a filter there).
360704
361381
  for (const chip of this.quickFilterActiveChips) {
@@ -360807,6 +361484,7 @@ var script$20 = {
360807
361484
  * @spec openspec/changes/screens-board-reference-columns/specs/board-view/spec.md#requirement-a-board-can-switch-the-field-its-columns-come-from
360808
361485
  */
360809
361486
  onBoardColumnFieldChange(field) {
361487
+ this.viewChanged = true;
360810
361488
  this.boardColumnFieldChoice = field;
360811
361489
  /**
360812
361490
  * @event board-column-field-change The board's column switch picked another field. Payload: the field name.
@@ -360822,6 +361500,7 @@ var script$20 = {
360822
361500
  * @return {void}
360823
361501
  */
360824
361502
  onBoardNarrowChange(value) {
361503
+ this.viewChanged = true;
360825
361504
  this.onFilterEvent({ key: this.boardNarrow.field, values: value === '' ? [] : [value] });
360826
361505
  },
360827
361506
 
@@ -360884,6 +361563,7 @@ var script$20 = {
360884
361563
  * @return {void}
360885
361564
  */
360886
361565
  onFilterEvent(payload) {
361566
+ this.viewChanged = true;
360887
361567
  if (this.isSelfFetchMode && typeof this.list.onFilterChange === 'function') {
360888
361568
  this.list.onFilterChange(payload.key, payload.values);
360889
361569
  this.persistViewStateToRoute(this.currentViewState());
@@ -360906,6 +361586,7 @@ var script$20 = {
360906
361586
  * @spec openspec/changes/screens-index-list-parity/specs/index-list-board-look/spec.md#requirement-the-toolbar-sits-on-the-ground-in-two-rows
360907
361587
  */
360908
361588
  onRemoveActiveFilter(chip) {
361589
+ this.viewChanged = true;
360909
361590
  if (chip && chip.searchTerm === true) {
360910
361591
  this.onSearchEvent('');
360911
361592
  return
@@ -361874,6 +362555,7 @@ var script$20 = {
361874
362555
  * @return {void}
361875
362556
  */
361876
362557
  onColumnsEvent(columns) {
362558
+ this.viewChanged = true;
361877
362559
  if (this.isSelfFetchMode && this.list.visibleColumns) {
361878
362560
  this.list.visibleColumns.value = columns;
361879
362561
  }
@@ -361921,6 +362603,7 @@ var script$20 = {
361921
362603
  * @return {void}
361922
362604
  */
361923
362605
  onColumnsReorder(columns) {
362606
+ this.viewChanged = true;
361924
362607
  this.onColumnsEvent(columns);
361925
362608
  },
361926
362609
 
@@ -361931,6 +362614,7 @@ var script$20 = {
361931
362614
  * @return {void}
361932
362615
  */
361933
362616
  onPinChange(count) {
362617
+ this.viewChanged = true;
361934
362618
  if (!this.personalColumns || Array.isArray(this.appliedViewColumns)) {
361935
362619
  return
361936
362620
  }
@@ -362825,6 +363509,7 @@ var script$20 = {
362825
363509
  */
362826
363510
  onApplySavedView(view, { keepSavedViewKey = false } = {}) {
362827
363511
  this.appliedSavedViewId = view && (view.id || view.slug) ? String(view.id || view.slug) : '';
363512
+ this.lensChosenByHand = false;
362828
363513
  const state = extractViewState(view);
362829
363514
  // A view's filters may carry tokens (`@me`, `@myTeam`): resolved here,
362830
363515
  // and an optional one that answers nothing (no team) is dropped. A
@@ -362833,6 +363518,10 @@ var script$20 = {
362833
363518
  loadMyTeam().then(() => this.onApplySavedView(view, { keepSavedViewKey }));
362834
363519
  return
362835
363520
  }
363521
+ // An opened view is unchanged until the reader changes it.
363522
+ this.$nextTick(() => {
363523
+ this.viewChanged = false;
363524
+ });
362836
363525
  const ctx = typeof this.selfFetchTokenCtx === 'function' ? this.selfFetchTokenCtx() : {};
362837
363526
  state.filters = dropOptionalUnresolved(resolveFilterTokens(state.filters, ctx));
362838
363527
  this.appliedViewFiltersKey = this.viewFiltersKey(state.filters);
@@ -362958,6 +363647,7 @@ var script$20 = {
362958
363647
  }
362959
363648
  this.savedViews = [...this.savedViews, view];
362960
363649
  this.showSaveViewDialog = false;
363650
+ this.viewChanged = false;
362961
363651
  this.sidebarActiveTab = 'filters-tab';
362962
363652
  this.sidebarOpen = true;
362963
363653
  this.toastSavedView('success', l10n.translate('nextcloud-vue', 'View "{name}" saved', { name }));
@@ -363977,7 +364667,7 @@ function render$1$(_ctx, _cache, $props, $setup, $data, $options) {
363977
364667
  const _component_CnIndexSidebar = vue$1.resolveComponent("CnIndexSidebar");
363978
364668
 
363979
364669
  return (vue$1.openBlock(), vue$1.createElementBlock("div", {
363980
- class: vue$1.normalizeClass(["cn-index-page", [$setup.lookClass, { 'cn-index-page--with-side-panel': $options.hasSidePanel, 'cn-index-page--quick-filters-toolbar': $options.effectiveQuickFilterPlacement === 'toolbar' }]]),
364670
+ class: vue$1.normalizeClass(["cn-index-page", [$setup.lookClass, { 'cn-index-page--with-side-panel': $options.hasSidePanel, 'cn-index-page--sidebar-open': $options.sidebarPanelOpen, 'cn-index-page--quick-filters-toolbar': $options.effectiveQuickFilterPlacement === 'toolbar' }]]),
363981
364671
  "data-testid": "cn-index-page",
363982
364672
  onKeydown: _cache[34] || (_cache[34] = (...args) => ($options.onListKeydown && $options.onListKeydown(...args)))
363983
364673
  }, [
@@ -364217,7 +364907,7 @@ function render$1$(_ctx, _cache, $props, $setup, $data, $options) {
364217
364907
  maxVisible: $props.quickFilterMaxVisible,
364218
364908
  fit: false,
364219
364909
  multiple: $props.quickFilterMultiple,
364220
- activeIndex: $setup.activeQuickFilterIndex,
364910
+ activeIndex: $options.shownQuickFilterIndex,
364221
364911
  selectedIndices: $setup.selectedQuickFilterIndices,
364222
364912
  counts: $options.effectiveTabCounts,
364223
364913
  "onUpdate:activeIndex": $options.onQuickFilterChange,
@@ -435602,7 +436292,13 @@ var script$n = {
435602
436292
  async _fetchMenuItemCount(item) {
435603
436293
  try {
435604
436294
  const { register, schema, filter } = item.count;
435605
- const resolved = resolveFilterTokens(filter && typeof filter === 'object' ? filter : {}, {});
436295
+ const raw = filter && typeof filter === 'object' ? filter : {};
436296
+ // `@myTeam` counts as the list filters: once the team is read,
436297
+ // every team the app answers, and without one no team at all.
436298
+ if (Object.values(raw).some((value) => value === MY_TEAM_TOKEN || (Array.isArray(value) && value.includes(MY_TEAM_TOKEN)))) {
436299
+ await loadMyTeam();
436300
+ }
436301
+ const resolved = dropOptionalUnresolved(resolveFilterTokens(raw, {}));
435606
436302
  const url = router.generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(register)}/${encodeURIComponent(schema)}`);
435607
436303
  const { data } = await axios$1.get(url, { params: { _limit: 1, ...resolved } });
435608
436304
  const total = data?.total;