@conduction/nextcloud-vue 2.139.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 (101) 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 +30 -2
  33. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  35. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  36. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +28 -3
  37. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  38. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  39. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +1 -1
  40. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +10 -0
  41. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -1
  42. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -1
  43. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +1 -1
  44. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +12 -1
  45. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
  46. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +1 -1
  47. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
  48. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue.js +1 -1
  49. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js +23 -1
  50. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js.map +1 -1
  51. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js +43 -13
  52. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js.map +1 -1
  53. package/dist/esm/composables/indexSources.js +5 -2
  54. package/dist/esm/composables/indexSources.js.map +1 -1
  55. package/dist/esm/composables/useTaskInboxStore.js +4 -3
  56. package/dist/esm/composables/useTaskInboxStore.js.map +1 -1
  57. package/dist/esm/l10n/en.json.js +2 -1
  58. package/dist/esm/l10n/en.json.js.map +1 -1
  59. package/dist/esm/l10n/nl.json.js +5 -1
  60. package/dist/esm/l10n/nl.json.js.map +1 -1
  61. package/dist/esm/library-style.js +1 -1
  62. package/dist/esm/nextcloud-vue.css +466 -371
  63. package/dist/esm/utils/builtInFormatters.js +23 -1
  64. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  65. package/dist/esm/utils/fillTemplate.js +7 -1
  66. package/dist/esm/utils/fillTemplate.js.map +1 -1
  67. package/dist/esm/utils/myTeam.js +24 -4
  68. package/dist/esm/utils/myTeam.js.map +1 -1
  69. package/dist/esm/utils/resolveFilterTokens.js +5 -4
  70. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  71. package/dist/esm/utils/transitionInputs.js +43 -2
  72. package/dist/esm/utils/transitionInputs.js.map +1 -1
  73. package/dist/nextcloud-vue.cjs.js +771 -222
  74. package/dist/nextcloud-vue.cjs.js.map +2 -2
  75. package/dist/nextcloud-vue.css +466 -371
  76. package/l10n/en.json +2 -1
  77. package/l10n/nl.json +5 -1
  78. package/package.json +1 -1
  79. package/src/components/CnAppRoot/CnAppRoot.vue +9 -2
  80. package/src/components/CnCellRenderer/CnCellRenderer.vue +19 -3
  81. package/src/components/CnChartWidget/CnChartWidget.md +1 -0
  82. package/src/components/CnChartWidget/CnChartWidget.vue +72 -1
  83. package/src/components/CnConversationThread/CnConversationWidget.vue +126 -1
  84. package/src/components/CnFactsWidget/CnFactsWidget.vue +72 -5
  85. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +28 -1
  86. package/src/components/CnFilesBrowser/filesBrowser.js +7 -1
  87. package/src/components/CnFilesBrowser/filesBrowserColumns.js +5 -0
  88. package/src/components/CnIndexPage/CnIndexPage.vue +30 -2
  89. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +28 -3
  90. package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +10 -0
  91. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +68 -2
  92. package/src/components/CnTransitionInputFields/CnTransitionInputFields.vue +43 -1
  93. package/src/composables/indexSources.js +5 -2
  94. package/src/composables/useTaskInboxStore.js +4 -3
  95. package/src/css/look-board.css +10 -2
  96. package/src/css/table.css +13 -0
  97. package/src/utils/builtInFormatters.js +22 -0
  98. package/src/utils/fillTemplate.js +7 -1
  99. package/src/utils/myTeam.js +24 -4
  100. package/src/utils/resolveFilterTokens.js +5 -4
  101. package/src/utils/transitionInputs.js +42 -1
@@ -3604,7 +3604,8 @@ var translations$s = {
3604
3604
  "You save the order along with a view.": "You save the order along with a view.",
3605
3605
  me: "me",
3606
3606
  "Open profile": "Open profile",
3607
- "Link a contact from your address book.": "Link a contact from your address book."
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."
3608
3609
  };
3609
3610
  var plurals$1 = {
3610
3611
  "{count} member": [
@@ -6616,7 +6617,11 @@ var translations$c = {
6616
6617
  "You save the order along with a view.": "De sortering bewaart u mee in een weergave.",
6617
6618
  me: "ik",
6618
6619
  "Open profile": "Profiel openen",
6619
- "Link a contact from your address book.": "Koppel een contact uit je adresboek."
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"
6620
6625
  };
6621
6626
  var plurals = {
6622
6627
  "{count} member": [
@@ -7272,7 +7277,7 @@ const _hoisted_3$74 = {
7272
7277
  class: "cn-ai-history-list__items",
7273
7278
  "data-testid": "cn-ai-history-list-items"
7274
7279
  };
7275
- const _hoisted_4$6l = {
7280
+ const _hoisted_4$6m = {
7276
7281
  key: 0,
7277
7282
  class: "cn-ai-history-list__edit",
7278
7283
  "data-testid": "cn-ai-history-list-edit"
@@ -7342,7 +7347,7 @@ function render$8G(_ctx, _cache, $props, $setup, $data, $options) {
7342
7347
  ]])
7343
7348
  }, [
7344
7349
  ($data.editingUuid === conv.uuid)
7345
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_4$6l, [
7350
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_4$6m, [
7346
7351
  vue$1.createVNode(_component_NcTextField, {
7347
7352
  modelValue: $data.editTitle,
7348
7353
  label: $options.cnTranslate('Name'),
@@ -10025,7 +10030,7 @@ const _hoisted_2$7U = {
10025
10030
  "data-testid": "cn-ai-input-chips"
10026
10031
  };
10027
10032
  const _hoisted_3$73 = { class: "cn-ai-input__chip-name" };
10028
- const _hoisted_4$6k = ["aria-label", "disabled", "data-testid", "onClick"];
10033
+ const _hoisted_4$6l = ["aria-label", "disabled", "data-testid", "onClick"];
10029
10034
  const _hoisted_5$5C = { class: "cn-ai-input__row" };
10030
10035
  const _hoisted_6$5b = ["aria-label", "title", "aria-pressed", "aria-disabled", "disabled", "data-engine"];
10031
10036
  const _hoisted_7$4J = ["aria-label", "title", "aria-pressed", "disabled"];
@@ -10095,7 +10100,7 @@ function render$8F(_ctx, _cache, $props, $setup, $data, $options) {
10095
10100
  onClick: $event => ($options.removeAttachment(index))
10096
10101
  }, [
10097
10102
  vue$1.createVNode(_component_Close, { size: 14 })
10098
- ], 8 /* PROPS */, _hoisted_4$6k)
10103
+ ], 8 /* PROPS */, _hoisted_4$6l)
10099
10104
  ]))
10100
10105
  }), 128 /* KEYED_FRAGMENT */))
10101
10106
  ]))
@@ -10448,7 +10453,7 @@ const _hoisted_3$72 = {
10448
10453
  key: 0,
10449
10454
  class: "cn-ai-message-list__system-text"
10450
10455
  };
10451
- const _hoisted_4$6j = {
10456
+ const _hoisted_4$6k = {
10452
10457
  key: 1,
10453
10458
  class: "cn-ai-message-list__bubble cn-ai-message-list__bubble--user"
10454
10459
  };
@@ -10494,12 +10499,12 @@ const _hoisted_19$1q = {
10494
10499
  class: "cn-ai-message-list__bubble cn-ai-message-list__bubble--assistant",
10495
10500
  "aria-live": "polite"
10496
10501
  };
10497
- const _hoisted_20$1f = {
10502
+ const _hoisted_20$1g = {
10498
10503
  key: 2,
10499
10504
  class: "cn-ai-message-list__item cn-ai-message-list__item--assistant",
10500
10505
  "data-testid": "cn-ai-thinking"
10501
10506
  };
10502
- const _hoisted_21$1d = ["aria-label"];
10507
+ const _hoisted_21$1e = ["aria-label"];
10503
10508
  const _hoisted_22$1a = { class: "cn-ai-message-list__thinking-text" };
10504
10509
 
10505
10510
  function render$8E(_ctx, _cache, $props, $setup, $data, $options) {
@@ -10520,7 +10525,7 @@ function render$8E(_ctx, _cache, $props, $setup, $data, $options) {
10520
10525
  (message.role === 'system')
10521
10526
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_3$72, vue$1.toDisplayString(message.content), 1 /* TEXT */))
10522
10527
  : (message.role === 'user')
10523
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_4$6j, [
10528
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_4$6k, [
10524
10529
  vue$1.createElementVNode("span", _hoisted_5$5B, vue$1.toDisplayString(message.content), 1 /* TEXT */),
10525
10530
  ($options.hasAttachments(message))
10526
10531
  ? (vue$1.openBlock(), vue$1.createElementBlock("ul", _hoisted_6$5a, [
@@ -10632,7 +10637,7 @@ function render$8E(_ctx, _cache, $props, $setup, $data, $options) {
10632
10637
  ]))
10633
10638
  : vue$1.createCommentVNode("v-if", true),
10634
10639
  ($options.isThinking)
10635
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_20$1f, [
10640
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_20$1g, [
10636
10641
  vue$1.createElementVNode("div", {
10637
10642
  class: "cn-ai-message-list__bubble cn-ai-message-list__bubble--assistant cn-ai-message-list__bubble--thinking",
10638
10643
  "aria-label": $options.cnTranslate('AI is thinking'),
@@ -10648,7 +10653,7 @@ function render$8E(_ctx, _cache, $props, $setup, $data, $options) {
10648
10653
  vue$1.createElementVNode("span", { class: "cn-ai-message-list__thinking-dot" }),
10649
10654
  vue$1.createElementVNode("span", { class: "cn-ai-message-list__thinking-dot" })
10650
10655
  ], -1 /* CACHED */))
10651
- ], 8 /* PROPS */, _hoisted_21$1d)
10656
+ ], 8 /* PROPS */, _hoisted_21$1e)
10652
10657
  ]))
10653
10658
  : vue$1.createCommentVNode("v-if", true)
10654
10659
  ]))
@@ -11727,7 +11732,7 @@ const _hoisted_2$7S = {
11727
11732
  class: "cn-ai-chat-window__titlebar"
11728
11733
  };
11729
11734
  const _hoisted_3$71 = ["title"];
11730
- const _hoisted_4$6i = { class: "cn-ai-chat-window__identity-name" };
11735
+ const _hoisted_4$6j = { class: "cn-ai-chat-window__identity-name" };
11731
11736
  const _hoisted_5$5A = { class: "cn-ai-chat-window__controls" };
11732
11737
  const _hoisted_6$59 = {
11733
11738
  key: 1,
@@ -11773,8 +11778,8 @@ const _hoisted_19$1p = {
11773
11778
  role: "note",
11774
11779
  "data-testid": "cn-ai-panel-mark"
11775
11780
  };
11776
- const _hoisted_20$1e = ["src", "alt"];
11777
- 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" };
11778
11783
 
11779
11784
  function render$8D(_ctx, _cache, $props, $setup, $data, $options) {
11780
11785
  const _component_CnDashboardIcon = vue$1.resolveComponent("CnDashboardIcon");
@@ -11822,7 +11827,7 @@ function render$8D(_ctx, _cache, $props, $setup, $data, $options) {
11822
11827
  size: 16,
11823
11828
  class: "cn-ai-chat-window__identity-icon"
11824
11829
  }, null, 8 /* PROPS */, ["name"]),
11825
- 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 */)
11826
11831
  ], 8 /* PROPS */, _hoisted_3$71),
11827
11832
  vue$1.createElementVNode("div", _hoisted_5$5A, [
11828
11833
  vue$1.createVNode(_component_NcActions, {
@@ -12087,9 +12092,9 @@ function render$8D(_ctx, _cache, $props, $setup, $data, $options) {
12087
12092
  class: "cn-ai-chat-window__mark-logo",
12088
12093
  src: $data.assistantMark.logo.url,
12089
12094
  alt: $data.assistantMark.logo.alt
12090
- }, null, 8 /* PROPS */, _hoisted_20$1e))
12095
+ }, null, 8 /* PROPS */, _hoisted_20$1f))
12091
12096
  : vue$1.createCommentVNode("v-if", true),
12092
- 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 */)
12093
12098
  ]))
12094
12099
  : vue$1.createCommentVNode("v-if", true)
12095
12100
  ], 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_1$8c))
@@ -14623,12 +14628,32 @@ let _source = null;
14623
14628
  /** The read in flight, shared by every caller. */
14624
14629
  let _pending = null;
14625
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
+
14626
14651
  /**
14627
14652
  * Name where the current user's primary team is read: a function answering
14628
14653
  * the team's Nextcloud group id, or null/'' for a user in no team.
14629
14654
  *
14630
14655
  * @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
14631
- * @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.
14632
14657
  * @return {void}
14633
14658
  */
14634
14659
  function registerMyTeamSource(loader) {
@@ -14642,7 +14667,7 @@ function registerMyTeamSource(loader) {
14642
14667
  * when no source is registered, or when the read fails.
14643
14668
  *
14644
14669
  * @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
14645
- * @return {Promise<string>} The team's group id, or ''.
14670
+ * @return {Promise<string|string[]>} The team's group id (or ids), or ''.
14646
14671
  */
14647
14672
  function loadMyTeam() {
14648
14673
  if (_state.team !== null) {
@@ -14657,7 +14682,7 @@ function loadMyTeam() {
14657
14682
  }
14658
14683
  _pending = Promise.resolve()
14659
14684
  .then(() => _source())
14660
- .then((team) => (typeof team === 'string' ? team.trim() : ''))
14685
+ .then((team) => normalizeTeam(team))
14661
14686
  .catch(() => '')
14662
14687
  .then((team) => {
14663
14688
  _state.team = team;
@@ -14672,7 +14697,7 @@ function loadMyTeam() {
14672
14697
  * it is being read (and starts the read on first use).
14673
14698
  *
14674
14699
  * @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
14675
- * @return {string|null} The team, '' or null.
14700
+ * @return {string|string[]|null} The team (or teams), '' or null.
14676
14701
  */
14677
14702
  function peekMyTeam() {
14678
14703
  if (_state.team === null) {
@@ -15357,15 +15382,16 @@ const MY_GROUPS_TOKEN = '@myGroups';
15357
15382
  const MY_TEAM_TOKEN = '@myTeam';
15358
15383
 
15359
15384
  /**
15360
- * 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
15361
15387
  * caller waits); `@myTeam?` for a user in no team (the caller drops the key).
15362
15388
  *
15363
15389
  * @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
15364
- * @param {{myTeam?: string}} [ctx] Optional context; `ctx.myTeam` wins.
15365
- * @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.
15366
15392
  */
15367
15393
  function resolveMyTeam(ctx) {
15368
- 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();
15369
15395
  if (team === null) {
15370
15396
  return MY_TEAM_TOKEN
15371
15397
  }
@@ -24270,7 +24296,7 @@ function formatterFor(vf) {
24270
24296
  var script$8z = {
24271
24297
  name: 'CnChartWidget',
24272
24298
 
24273
- components: { CnEndpointNotice: __sfc_main$8u },
24299
+ components: { CnEndpointNotice: __sfc_main$8u, NcNoteCard: vue.NcNoteCard },
24274
24300
 
24275
24301
  inject: {
24276
24302
  /**
@@ -24332,6 +24358,24 @@ var script$8z = {
24332
24358
  default: null,
24333
24359
  },
24334
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
+
24335
24379
  /**
24336
24380
  * A line under the chart ("Klik een balk om de zaken te zien"),
24337
24381
  * through the host's translate: under the rows of a `bar-list` or
@@ -25466,6 +25510,38 @@ var script$8z = {
25466
25510
  }
25467
25511
  },
25468
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
+
25469
25545
  /**
25470
25546
  * The matrix cell of the record (`highlight: { row, column }`, tokens
25471
25547
  * such as `@object.behaviour` resolved), as row and column indexes. A
@@ -26675,7 +26751,7 @@ const _hoisted_3$6$ = {
26675
26751
  class: "cn-chart-widget__empty",
26676
26752
  "data-testid": "cn-chart-widget-empty"
26677
26753
  };
26678
- const _hoisted_4$6h = {
26754
+ const _hoisted_4$6i = {
26679
26755
  key: 5,
26680
26756
  class: "cn-chart-widget__html",
26681
26757
  "data-testid": "cn-chart-widget-bar-list"
@@ -26724,9 +26800,12 @@ const _hoisted_19$1o = {
26724
26800
  class: "cn-chart-widget__footnote",
26725
26801
  "data-testid": "cn-chart-widget-footnote"
26726
26802
  };
26803
+ const _hoisted_20$1e = { class: "cn-chart-widget__notice-text" };
26804
+ const _hoisted_21$1c = { key: 0 };
26727
26805
 
26728
26806
  function render$8y(_ctx, _cache, $props, $setup, $data, $options) {
26729
26807
  const _component_CnEndpointNotice = vue$1.resolveComponent("CnEndpointNotice");
26808
+ const _component_NcNoteCard = vue$1.resolveComponent("NcNoteCard");
26730
26809
 
26731
26810
  return (vue$1.openBlock(), vue$1.createElementBlock("div", {
26732
26811
  class: vue$1.normalizeClass(["cn-chart-widget", { 'cn-chart-widget--fit': $options.fitToContainer }])
@@ -26775,7 +26854,7 @@ function render$8y(_ctx, _cache, $props, $setup, $data, $options) {
26775
26854
  : ($options.showEmptyState)
26776
26855
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_3$6$, vue$1.toDisplayString($options.hostTranslate($props.emptyLabel)), 1 /* TEXT */))
26777
26856
  : ($props.type === 'bar-list')
26778
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_4$6h, [
26857
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_4$6i, [
26779
26858
  vue$1.createElementVNode("ul", _hoisted_5$5z, [
26780
26859
  (vue$1.openBlock(true), vue$1.createElementBlock(vue$1.Fragment, null, vue$1.renderList($options.barListRows, (row) => {
26781
26860
  return (vue$1.openBlock(), vue$1.createElementBlock("li", {
@@ -26870,12 +26949,31 @@ function render$8y(_ctx, _cache, $props, $setup, $data, $options) {
26870
26949
  ])),
26871
26950
  ($props.footnote && $data.chartComponent && !$options.showEmptyState && $props.type !== 'bar-list' && $props.type !== 'matrix')
26872
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"]))
26873
26971
  : vue$1.createCommentVNode("v-if", true)
26874
26972
  ], 2 /* CLASS */))
26875
26973
  }
26876
26974
 
26877
26975
  script$8z.render = render$8y;
26878
- script$8z.__scopeId = "data-v-56aabaf0";
26976
+ script$8z.__scopeId = "data-v-4e61b5f0";
26879
26977
  const __sfc_main$8t = script$8z;
26880
26978
 
26881
26979
  /**
@@ -27799,7 +27897,7 @@ var script$8v = {
27799
27897
  const _hoisted_1$84 = { class: "cn-chart-widget-form" };
27800
27898
  const _hoisted_2$7N = { class: "cn-chart-widget-form__section" };
27801
27899
  const _hoisted_3$6_ = { class: "cn-chart-widget-form__row2" };
27802
- const _hoisted_4$6g = { class: "cn-chart-widget-form__section" };
27900
+ const _hoisted_4$6h = { class: "cn-chart-widget-form__section" };
27803
27901
  const _hoisted_5$5y = { class: "cn-chart-widget-form__row2" };
27804
27902
  const _hoisted_6$57 = {
27805
27903
  key: 0,
@@ -27848,7 +27946,7 @@ function render$8u(_ctx, _cache, $props, $setup, $data, $options) {
27848
27946
  _: 1 /* STABLE */
27849
27947
  }, 8 /* PROPS */, ["modelValue", "options", "inputLabel"])
27850
27948
  ]),
27851
- 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 */),
27852
27950
  vue$1.createElementVNode("div", _hoisted_5$5y, [
27853
27951
  vue$1.createVNode(_component_CnRegisterSchemaSelect, {
27854
27952
  register: $data.source.register,
@@ -29281,7 +29379,7 @@ const _hoisted_3$6Z = {
29281
29379
  key: 0,
29282
29380
  class: "cn-quick-filter-bar__count"
29283
29381
  };
29284
- const _hoisted_4$6f = {
29382
+ const _hoisted_4$6g = {
29285
29383
  class: "cn-quick-filter-bar__tab cn-quick-filter-bar__more",
29286
29384
  "data-measure": "more"
29287
29385
  };
@@ -29356,7 +29454,7 @@ function render$8r(_ctx, _cache, $props, $setup, $data, $options) {
29356
29454
  : vue$1.createCommentVNode("v-if", true)
29357
29455
  ], 2 /* CLASS */))
29358
29456
  }), 128 /* KEYED_FRAGMENT */)),
29359
- vue$1.createElementVNode("span", _hoisted_4$6f, [
29457
+ vue$1.createElementVNode("span", _hoisted_4$6g, [
29360
29458
  ($options.overflowLabel)
29361
29459
  ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_5$5x, vue$1.toDisplayString($options.overflowLabel), 1 /* TEXT */))
29362
29460
  : vue$1.createCommentVNode("v-if", true),
@@ -29911,7 +30009,7 @@ const _hoisted_3$6Y = {
29911
30009
  key: 0,
29912
30010
  class: "cn-conversation-thread__heading"
29913
30011
  };
29914
- const _hoisted_4$6e = {
30012
+ const _hoisted_4$6f = {
29915
30013
  key: 0,
29916
30014
  class: "cn-conversation-thread__subtitle",
29917
30015
  "data-testid": "cn-conversation-subtitle"
@@ -29977,15 +30075,15 @@ const _hoisted_22$19 = {
29977
30075
  class: "cn-conversation-thread__message-actions"
29978
30076
  };
29979
30077
  const _hoisted_23$_ = { class: "cn-conversation-thread__reply-head" };
29980
- const _hoisted_24$T = ["for"];
29981
- const _hoisted_25$M = {
30078
+ const _hoisted_24$U = ["for"];
30079
+ const _hoisted_25$N = {
29982
30080
  key: 0,
29983
30081
  class: "cn-conversation-thread__reply-hint",
29984
30082
  "data-testid": "cn-conversation-reply-hint"
29985
30083
  };
29986
- const _hoisted_26$G = ["id", "placeholder", "disabled", "aria-describedby"];
30084
+ const _hoisted_26$H = ["id", "placeholder", "disabled", "aria-describedby"];
29987
30085
  const _hoisted_27$y = ["id"];
29988
- const _hoisted_28$v = { class: "cn-conversation-thread__reply-foot" };
30086
+ const _hoisted_28$w = { class: "cn-conversation-thread__reply-foot" };
29989
30087
  const _hoisted_29$u = {
29990
30088
  key: 0,
29991
30089
  class: "cn-conversation-thread__reply-option",
@@ -30019,7 +30117,7 @@ function render$8q(_ctx, _cache, $props, $setup, $data, $options) {
30019
30117
  _: 1 /* STABLE */
30020
30118
  }, 8 /* PROPS */, ["id"])),
30021
30119
  ($props.subtitle)
30022
- ? (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 */))
30023
30121
  : vue$1.createCommentVNode("v-if", true)
30024
30122
  ]))
30025
30123
  : vue$1.createCommentVNode("v-if", true),
@@ -30113,9 +30211,9 @@ function render$8q(_ctx, _cache, $props, $setup, $data, $options) {
30113
30211
  vue$1.createElementVNode("label", {
30114
30212
  for: $data.inputId,
30115
30213
  class: "cn-conversation-thread__reply-label"
30116
- }, vue$1.toDisplayString($props.replyLabel), 9 /* TEXT, PROPS */, _hoisted_24$T),
30214
+ }, vue$1.toDisplayString($props.replyLabel), 9 /* TEXT, PROPS */, _hoisted_24$U),
30117
30215
  ($props.replyHint)
30118
- ? (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 */))
30119
30217
  : vue$1.createCommentVNode("v-if", true)
30120
30218
  ]),
30121
30219
  vue$1.withDirectives(vue$1.createElementVNode("textarea", {
@@ -30131,7 +30229,7 @@ function render$8q(_ctx, _cache, $props, $setup, $data, $options) {
30131
30229
  _cache[1] || (_cache[1] = vue$1.withKeys(vue$1.withModifiers((...args) => ($options.onSend && $options.onSend(...args)), ["ctrl","prevent"]), ["enter"])),
30132
30230
  _cache[2] || (_cache[2] = vue$1.withKeys(vue$1.withModifiers((...args) => ($options.onSend && $options.onSend(...args)), ["meta","prevent"]), ["enter"]))
30133
30231
  ]
30134
- }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_26$G), [
30232
+ }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_26$H), [
30135
30233
  [vue$1.vModelText, $data.draft]
30136
30234
  ]),
30137
30235
  ($data.errorMessage)
@@ -30143,7 +30241,7 @@ function render$8q(_ctx, _cache, $props, $setup, $data, $options) {
30143
30241
  "data-testid": "cn-conversation-error"
30144
30242
  }, vue$1.toDisplayString($data.errorMessage), 9 /* TEXT, PROPS */, _hoisted_27$y))
30145
30243
  : vue$1.createCommentVNode("v-if", true),
30146
- vue$1.createElementVNode("div", _hoisted_28$v, [
30244
+ vue$1.createElementVNode("div", _hoisted_28$w, [
30147
30245
  ($options.optionShown)
30148
30246
  ? (vue$1.openBlock(), vue$1.createElementBlock("label", _hoisted_29$u, [
30149
30247
  vue$1.withDirectives(vue$1.createElementVNode("input", {
@@ -30532,6 +30630,10 @@ var script$8q = {
30532
30630
  acting: '',
30533
30631
  /** The threads `statusBadge` reads. */
30534
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,
30535
30637
  }
30536
30638
  },
30537
30639
 
@@ -30669,6 +30771,21 @@ var script$8q = {
30669
30771
  * @return {string} The URL, or '' when the content names none.
30670
30772
  * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread
30671
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
+
30672
30789
  endpoint() {
30673
30790
  const url = this.content.endpoint;
30674
30791
  if (typeof url !== 'string' || url === '') {
@@ -30694,19 +30811,101 @@ var script$8q = {
30694
30811
  handler() {
30695
30812
  this.loadSource();
30696
30813
  this.loadBadge();
30814
+ this.loadDraft();
30697
30815
  },
30698
30816
  },
30699
30817
  },
30700
30818
 
30701
30819
  mounted() {
30702
30820
  eventBus.subscribe('cn:page:refresh', this.onPageRefresh);
30821
+ eventBus.subscribe('cn:conversation:reply-sent', this.onReplySentElsewhere);
30703
30822
  },
30704
30823
 
30705
30824
  beforeUnmount() {
30706
30825
  eventBus.unsubscribe('cn:page:refresh', this.onPageRefresh);
30826
+ eventBus.unsubscribe('cn:conversation:reply-sent', this.onReplySentElsewhere);
30707
30827
  },
30708
30828
 
30709
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
+
30710
30909
  /**
30711
30910
  * Read the messages again when the page refreshes (a dialog sent one).
30712
30911
  *
@@ -31045,6 +31244,9 @@ var script$8q = {
31045
31244
  if (typeof text !== 'string' || text === '') {
31046
31245
  return
31047
31246
  }
31247
+ if (this.draftUrl) {
31248
+ this.storeDraft('');
31249
+ }
31048
31250
  this.sentHere.push({
31049
31251
  id: `sent-${this.sentHere.length}`,
31050
31252
  time: new Date().toISOString(),
@@ -31057,11 +31259,17 @@ var script$8q = {
31057
31259
 
31058
31260
  const _hoisted_1$80 = { class: "cn-case-widget-card" };
31059
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 = {
31060
31268
  key: 0,
31061
31269
  class: "cn-conversation-widget__badge",
31062
31270
  "data-testid": "cn-conversation-status-badge"
31063
31271
  };
31064
- const _hoisted_3$6X = {
31272
+ const _hoisted_4$6e = {
31065
31273
  key: 0,
31066
31274
  class: "cn-conversation-widget__error",
31067
31275
  role: "alert",
@@ -31071,12 +31279,13 @@ const _hoisted_3$6X = {
31071
31279
  function render$8p(_ctx, _cache, $props, $setup, $data, $options) {
31072
31280
  const _component_CnTemplateSelect = vue$1.resolveComponent("CnTemplateSelect");
31073
31281
  const _component_NcCheckboxRadioSwitch = vue$1.resolveComponent("NcCheckboxRadioSwitch");
31074
- const _component_CnIcon = vue$1.resolveComponent("CnIcon");
31075
31282
  const _component_NcButton = vue$1.resolveComponent("NcButton");
31283
+ const _component_CnIcon = vue$1.resolveComponent("CnIcon");
31076
31284
  const _component_CnConversationThread = vue$1.resolveComponent("CnConversationThread");
31077
31285
 
31078
31286
  return (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_1$80, [
31079
31287
  vue$1.createVNode(_component_CnConversationThread, {
31288
+ ref: "thread",
31080
31289
  messages: $options.messages,
31081
31290
  title: $options.tr($props.content.title || ''),
31082
31291
  subtitle: $options.tr($props.content.subtitle || ''),
@@ -31113,10 +31322,10 @@ function render$8p(_ctx, _cache, $props, $setup, $data, $options) {
31113
31322
  key: "0"
31114
31323
  }
31115
31324
  : undefined,
31116
- ($options.replyOptions.length > 0)
31325
+ ($options.replyOptions.length > 0 || $options.draftUrl)
31117
31326
  ? {
31118
31327
  name: "reply-actions",
31119
- fn: vue$1.withCtx(() => [
31328
+ fn: vue$1.withCtx(({ draft }) => [
31120
31329
  (vue$1.openBlock(true), vue$1.createElementBlock(vue$1.Fragment, null, vue$1.renderList($options.replyOptions, (option) => {
31121
31330
  return (vue$1.openBlock(), vue$1.createBlock(_component_NcCheckboxRadioSwitch, {
31122
31331
  key: option.key,
@@ -31129,7 +31338,23 @@ function render$8p(_ctx, _cache, $props, $setup, $data, $options) {
31129
31338
  ]),
31130
31339
  _: 2 /* DYNAMIC */
31131
31340
  }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["modelValue", "onUpdate:modelValue", "data-testid"]))
31132
- }), 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)
31133
31358
  ]),
31134
31359
  key: "1"
31135
31360
  }
@@ -31139,7 +31364,7 @@ function render$8p(_ctx, _cache, $props, $setup, $data, $options) {
31139
31364
  name: "header-actions",
31140
31365
  fn: vue$1.withCtx(() => [
31141
31366
  ($options.statusBadgeText)
31142
- ? (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 */))
31143
31368
  : vue$1.createCommentVNode("v-if", true),
31144
31369
  ($options.headerAction)
31145
31370
  ? (vue$1.openBlock(), vue$1.createBlock(_component_NcButton, {
@@ -31172,13 +31397,13 @@ function render$8p(_ctx, _cache, $props, $setup, $data, $options) {
31172
31397
  : undefined
31173
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"]),
31174
31399
  ($data.actionError)
31175
- ? (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 */))
31176
31401
  : vue$1.createCommentVNode("v-if", true)
31177
31402
  ]))
31178
31403
  }
31179
31404
 
31180
31405
  script$8q.render = render$8p;
31181
- script$8q.__scopeId = "data-v-c7d9637a";
31406
+ script$8q.__scopeId = "data-v-63984d97";
31182
31407
  const __sfc_main$8k = script$8q;
31183
31408
 
31184
31409
  /**
@@ -41468,7 +41693,7 @@ function formatLongDate(value, now = new Date(), withYear = false) {
41468
41693
  * Format one token value.
41469
41694
  *
41470
41695
  * @param {unknown} value The raw value.
41471
- * @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).
41472
41697
  * @param {Date} now The reference moment.
41473
41698
  * @param {string} [arg] The format's argument (`currency:EUR`, or a row path holding the code).
41474
41699
  * @param {object|null} [object] The row, for a currency read from a field.
@@ -41529,6 +41754,12 @@ function formatValue(value, format, now, arg = '', object = null, config = null,
41529
41754
  if (format === 'ago') {
41530
41755
  return formatAgo(value, now)
41531
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
+ }
41532
41763
  if (format === 'lower') {
41533
41764
  return String(value).toLocaleLowerCase()
41534
41765
  }
@@ -45415,6 +45646,22 @@ var script$85 = {
45415
45646
  * @return {string}
45416
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
45417
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
+
45418
45665
  labelledValue() {
45419
45666
  if (!this.hasFormatter && this.isEnum && this.hasValue && typeof this.value !== 'object') {
45420
45667
  return this.enumLabel
@@ -46018,18 +46265,18 @@ const _hoisted_22$17 = {
46018
46265
  "aria-hidden": "true"
46019
46266
  };
46020
46267
  const _hoisted_23$Y = { class: "cn-cell-renderer__avatar-text" };
46021
- const _hoisted_24$S = ["title"];
46022
- const _hoisted_25$L = {
46268
+ const _hoisted_24$T = ["title"];
46269
+ const _hoisted_25$M = {
46023
46270
  key: 0,
46024
46271
  class: "cn-cell-renderer__avatar-secondary",
46025
46272
  "data-testid": "cn-cell-secondary"
46026
46273
  };
46027
- const _hoisted_26$F = {
46274
+ const _hoisted_26$G = {
46028
46275
  key: 1,
46029
46276
  class: "cn-cell-renderer__dash"
46030
46277
  };
46031
46278
  const _hoisted_27$x = ["datetime", "title"];
46032
- const _hoisted_28$u = {
46279
+ const _hoisted_28$v = {
46033
46280
  key: 1,
46034
46281
  class: "cn-cell-renderer__dash"
46035
46282
  };
@@ -46192,7 +46439,7 @@ function render$84(_ctx, _cache, $props, $setup, $data, $options) {
46192
46439
  ? (vue$1.openBlock(), vue$1.createElementBlock("span", {
46193
46440
  key: 0,
46194
46441
  title: $options.rawTitle
46195
- }, vue$1.toDisplayString($options.labelledValue), 9 /* TEXT, PROPS */, _hoisted_7$4v))
46442
+ }, vue$1.toDisplayString($options.shownLabel), 9 /* TEXT, PROPS */, _hoisted_7$4v))
46196
46443
  : (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_8$43, "—"))
46197
46444
  ], 64 /* STABLE_FRAGMENT */))
46198
46445
  : ($props.widget === 'badge')
@@ -46200,7 +46447,7 @@ function render$84(_ctx, _cache, $props, $setup, $data, $options) {
46200
46447
  ($options.hasValue)
46201
46448
  ? (vue$1.openBlock(), vue$1.createBlock(_component_CnStatusBadge, {
46202
46449
  key: 0,
46203
- label: $options.labelledValue,
46450
+ label: $options.shownLabel,
46204
46451
  variant: $options.badgeVariant,
46205
46452
  colorMap: $options.badgeColorMap,
46206
46453
  colorKey: $options.badgeColorKey,
@@ -46346,13 +46593,13 @@ function render$84(_ctx, _cache, $props, $setup, $data, $options) {
46346
46593
  vue$1.createElementVNode("span", {
46347
46594
  class: "cn-cell-renderer__avatar-name",
46348
46595
  title: $options.avatarName || $options.avatarUserId
46349
- }, 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),
46350
46597
  ($props.secondary)
46351
- ? (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 */))
46352
46599
  : vue$1.createCommentVNode("v-if", true)
46353
46600
  ])
46354
46601
  ]))
46355
- : (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_26$F, "—"))
46602
+ : (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_26$G, "—"))
46356
46603
  ], 64 /* STABLE_FRAGMENT */))
46357
46604
  : ($props.widget === 'age')
46358
46605
  ? (vue$1.openBlock(), vue$1.createElementBlock(vue$1.Fragment, { key: 11 }, [
@@ -46363,7 +46610,7 @@ function render$84(_ctx, _cache, $props, $setup, $data, $options) {
46363
46610
  datetime: $options.dateTimestamp.toISOString(),
46364
46611
  title: $options.absoluteDateLabel
46365
46612
  }, vue$1.toDisplayString($options.ageLabel), 11 /* TEXT, CLASS, PROPS */, _hoisted_27$x))
46366
- : (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_28$u, "—"))
46613
+ : (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_28$v, "—"))
46367
46614
  ], 64 /* STABLE_FRAGMENT */))
46368
46615
  : ($props.widget === 'date')
46369
46616
  ? (vue$1.openBlock(), vue$1.createElementBlock(vue$1.Fragment, { key: 12 }, [
@@ -46610,7 +46857,7 @@ function render$84(_ctx, _cache, $props, $setup, $data, $options) {
46610
46857
  }
46611
46858
 
46612
46859
  script$85.render = render$84;
46613
- script$85.__scopeId = "data-v-5e132788";
46860
+ script$85.__scopeId = "data-v-2a3bc230";
46614
46861
  const __sfc_main$7$ = script$85;
46615
46862
 
46616
46863
  /**
@@ -49117,19 +49364,19 @@ const _hoisted_23$X = {
49117
49364
  key: 0,
49118
49365
  class: "hidden-visually"
49119
49366
  };
49120
- const _hoisted_24$R = {
49367
+ const _hoisted_24$S = {
49121
49368
  key: 0,
49122
49369
  class: "cn-table-empty",
49123
49370
  "data-testid": "cn-object-list-empty"
49124
49371
  };
49125
- const _hoisted_25$K = ["colspan"];
49126
- const _hoisted_26$E = {
49372
+ const _hoisted_25$L = ["colspan"];
49373
+ const _hoisted_26$F = {
49127
49374
  key: 0,
49128
49375
  class: "cn-table-empty__lens-reason",
49129
49376
  "data-testid": "cn-data-table-lens-reason"
49130
49377
  };
49131
49378
  const _hoisted_27$w = ["draggable", "data-testid-row-id", "onDragstart", "onDragover", "onDragleave", "onDrop", "onClick", "onAuxclick", "onContextmenu"];
49132
- const _hoisted_28$t = ["href", "aria-label", "onClick"];
49379
+ const _hoisted_28$u = ["href", "aria-label", "onClick"];
49133
49380
  const _hoisted_29$s = {
49134
49381
  key: 3,
49135
49382
  class: "cn-table-row-indicators",
@@ -49388,16 +49635,16 @@ function render$81(_ctx, _cache, $props, $setup, $data, $options) {
49388
49635
  ])),
49389
49636
  vue$1.createElementVNode("tbody", null, [
49390
49637
  ($options.effectiveRows.length === 0)
49391
- ? (vue$1.openBlock(), vue$1.createElementBlock("tr", _hoisted_24$R, [
49638
+ ? (vue$1.openBlock(), vue$1.createElementBlock("tr", _hoisted_24$S, [
49392
49639
  vue$1.createElementVNode("td", { colspan: $options.totalColumns }, [
49393
49640
  vue$1.renderSlot(_ctx.$slots, "empty", {}, () => [
49394
49641
  ($options.lensReasonText)
49395
- ? (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 */))
49396
49643
  : (vue$1.openBlock(), vue$1.createElementBlock(vue$1.Fragment, { key: 1 }, [
49397
49644
  vue$1.createTextVNode(vue$1.toDisplayString($options.translateLabel($props.emptyText)), 1 /* TEXT */)
49398
49645
  ], 64 /* STABLE_FRAGMENT */))
49399
49646
  ])
49400
- ], 8 /* PROPS */, _hoisted_25$K)
49647
+ ], 8 /* PROPS */, _hoisted_25$L)
49401
49648
  ]))
49402
49649
  : (vue$1.openBlock(true), vue$1.createElementBlock(vue$1.Fragment, { key: 1 }, vue$1.renderList($options.effectiveRows, (row, rowIndex) => {
49403
49650
  return (vue$1.openBlock(), vue$1.createElementBlock(vue$1.Fragment, {
@@ -49469,7 +49716,7 @@ function render$81(_ctx, _cache, $props, $setup, $data, $options) {
49469
49716
  "data-testid": "cn-row-link",
49470
49717
  onMousemove: _cache[4] || (_cache[4] = (...args) => ($options.linkTitleAtPointer && $options.linkTitleAtPointer(...args))),
49471
49718
  onClick: $event => ($options.onRowLinkClick(row, $event))
49472
- }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_28$t))
49719
+ }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_28$u))
49473
49720
  : vue$1.createCommentVNode("v-if", true),
49474
49721
  (colIndex === $options.titleColumnIndex && $options.isUnreadRow(row))
49475
49722
  ? (vue$1.openBlock(), vue$1.createBlock(_component_CnUnreadMarker, { key: 1 }))
@@ -85229,17 +85476,17 @@ const _hoisted_22$15 = {
85229
85476
  role: "alert"
85230
85477
  };
85231
85478
  const _hoisted_23$W = ["placeholder", "aria-label"];
85232
- const _hoisted_24$Q = {
85479
+ const _hoisted_24$R = {
85233
85480
  key: 3,
85234
85481
  class: "cn-icon-browser-panel__grid"
85235
85482
  };
85236
- const _hoisted_25$J = ["title", "aria-label", "onClick"];
85237
- const _hoisted_26$D = ["src", "alt"];
85483
+ const _hoisted_25$K = ["title", "aria-label", "onClick"];
85484
+ const _hoisted_26$E = ["src", "alt"];
85238
85485
  const _hoisted_27$v = {
85239
85486
  key: 0,
85240
85487
  class: "cn-icon-browser-panel__cell-label"
85241
85488
  };
85242
- const _hoisted_28$s = {
85489
+ const _hoisted_28$t = {
85243
85490
  key: 4,
85244
85491
  class: "cn-icon-browser-panel__hint"
85245
85492
  };
@@ -85429,7 +85676,7 @@ function render$7S(_ctx, _cache, $props, $setup, $data, $options) {
85429
85676
  ])
85430
85677
  : vue$1.createCommentVNode("v-if", true),
85431
85678
  ($options.customVisibleIcons.length > 0)
85432
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$Q, [
85679
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$R, [
85433
85680
  (vue$1.openBlock(true), vue$1.createElementBlock(vue$1.Fragment, null, vue$1.renderList($options.customVisibleIcons, (icon) => {
85434
85681
  return (vue$1.openBlock(), vue$1.createElementBlock("button", {
85435
85682
  key: icon.id || icon.url,
@@ -85444,16 +85691,16 @@ function render$7S(_ctx, _cache, $props, $setup, $data, $options) {
85444
85691
  src: icon.url,
85445
85692
  alt: icon.label,
85446
85693
  loading: "lazy"
85447
- }, null, 8 /* PROPS */, _hoisted_26$D),
85694
+ }, null, 8 /* PROPS */, _hoisted_26$E),
85448
85695
  ($props.showLabels)
85449
85696
  ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_27$v, vue$1.toDisplayString(icon.label), 1 /* TEXT */))
85450
85697
  : vue$1.createCommentVNode("v-if", true)
85451
- ], 10 /* CLASS, PROPS */, _hoisted_25$J))
85698
+ ], 10 /* CLASS, PROPS */, _hoisted_25$K))
85452
85699
  }), 128 /* KEYED_FRAGMENT */))
85453
85700
  ]))
85454
85701
  : vue$1.createCommentVNode("v-if", true),
85455
85702
  ($options.customTruncated)
85456
- ? (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 */))
85457
85704
  : vue$1.createCommentVNode("v-if", true)
85458
85705
  ], 64 /* STABLE_FRAGMENT */))
85459
85706
  : vue$1.createCommentVNode("v-if", true)
@@ -96380,14 +96627,14 @@ const _hoisted_22$13 = {
96380
96627
  class: "cn-activity-tab__filter"
96381
96628
  };
96382
96629
  const _hoisted_23$V = { class: "cn-activity-tab__filter-label" };
96383
- const _hoisted_24$P = { value: "" };
96384
- const _hoisted_25$I = { value: "internal" };
96385
- 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" };
96386
96633
  const _hoisted_27$u = {
96387
96634
  key: 2,
96388
96635
  class: "cn-activity-tab__reads"
96389
96636
  };
96390
- const _hoisted_28$r = ["checked"];
96637
+ const _hoisted_28$s = ["checked"];
96391
96638
  const _hoisted_29$q = ["aria-label"];
96392
96639
  const _hoisted_30$o = ["onClick"];
96393
96640
  const _hoisted_31$m = {
@@ -96586,9 +96833,9 @@ function render$7H(_ctx, _cache, $props, $setup, $data, $options) {
96586
96833
  "data-testid": "cn-activity-visibility-select",
96587
96834
  onChange: _cache[6] || (_cache[6] = (...args) => ($options.resetAndFetch && $options.resetAndFetch(...args)))
96588
96835
  }, [
96589
- vue$1.createElementVNode("option", _hoisted_24$P, vue$1.toDisplayString($data.allVisibilityLabel), 1 /* TEXT */),
96590
- vue$1.createElementVNode("option", _hoisted_25$I, vue$1.toDisplayString($data.internalLabel), 1 /* TEXT */),
96591
- 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 */)
96592
96839
  ], 544 /* NEED_HYDRATION, NEED_PATCH */), [
96593
96840
  [vue$1.vModelSelect, $data.selectedVisibility]
96594
96841
  ])
@@ -96602,7 +96849,7 @@ function render$7H(_ctx, _cache, $props, $setup, $data, $options) {
96602
96849
  checked: $data.showReads,
96603
96850
  "data-testid": "cn-activity-reads",
96604
96851
  onChange: _cache[7] || (_cache[7] = $event => ($options.setShowReads($event.target.checked)))
96605
- }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_28$r),
96852
+ }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_28$s),
96606
96853
  vue$1.createTextVNode(" " + vue$1.toDisplayString($data.showReadsLabel), 1 /* TEXT */)
96607
96854
  ]))
96608
96855
  : vue$1.createCommentVNode("v-if", true),
@@ -100310,11 +100557,11 @@ const _hoisted_23$U = {
100310
100557
  key: 1,
100311
100558
  class: "cn-bookmarks-card__title"
100312
100559
  };
100313
- const _hoisted_24$O = {
100560
+ const _hoisted_24$P = {
100314
100561
  key: 3,
100315
100562
  class: "cn-bookmarks-card__view-all"
100316
100563
  };
100317
- const _hoisted_25$H = ["href"];
100564
+ const _hoisted_25$I = ["href"];
100318
100565
 
100319
100566
  function render$7y(_ctx, _cache, $props, $setup, $data, $options) {
100320
100567
  const _component_NcLoadingIcon = vue$1.resolveComponent("NcLoadingIcon");
@@ -100445,12 +100692,12 @@ function render$7y(_ctx, _cache, $props, $setup, $data, $options) {
100445
100692
  }), 128 /* KEYED_FRAGMENT */))
100446
100693
  ])),
100447
100694
  ($data.bookmarks.length > $data.COMPACT_LIMIT)
100448
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$O, [
100695
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$P, [
100449
100696
  vue$1.createElementVNode("a", {
100450
100697
  href: $props.bookmarksAppUrl,
100451
100698
  target: "_blank",
100452
100699
  rel: "noopener noreferrer"
100453
- }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_25$H)
100700
+ }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_25$I)
100454
100701
  ]))
100455
100702
  : vue$1.createCommentVNode("v-if", true)
100456
100703
  ], 64 /* STABLE_FRAGMENT */))
@@ -102406,11 +102653,11 @@ const _hoisted_23$T = {
102406
102653
  class: "cn-calendar-tab__date cn-calendar-tab__date--past",
102407
102654
  "aria-hidden": "true"
102408
102655
  };
102409
- const _hoisted_24$N = { class: "cn-calendar-tab__date-month" };
102410
- const _hoisted_25$G = { class: "cn-calendar-tab__date-day" };
102411
- 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" };
102412
102659
  const _hoisted_27$t = { class: "cn-calendar-tab__title-row" };
102413
- const _hoisted_28$q = { class: "cn-calendar-tab__title" };
102660
+ const _hoisted_28$r = { class: "cn-calendar-tab__title" };
102414
102661
  const _hoisted_29$p = { class: "cn-calendar-tab__when" };
102415
102662
  const _hoisted_30$n = {
102416
102663
  key: 0,
@@ -102588,12 +102835,12 @@ function render$7t(_ctx, _cache, $props, $setup, $data, $options) {
102588
102835
  class: "cn-calendar-tab__event cn-calendar-tab__event--past"
102589
102836
  }, [
102590
102837
  vue$1.createElementVNode("div", _hoisted_23$T, [
102591
- vue$1.createElementVNode("span", _hoisted_24$N, vue$1.toDisplayString($options.monthOf(ev)), 1 /* TEXT */),
102592
- 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 */)
102593
102840
  ]),
102594
- vue$1.createElementVNode("div", _hoisted_26$B, [
102841
+ vue$1.createElementVNode("div", _hoisted_26$C, [
102595
102842
  vue$1.createElementVNode("div", _hoisted_27$t, [
102596
- 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 */),
102597
102844
  ($options.statusLabel(ev))
102598
102845
  ? (vue$1.openBlock(), vue$1.createBlock(_component_CnStatusBadge, {
102599
102846
  key: 0,
@@ -103107,11 +103354,11 @@ const _hoisted_23$S = {
103107
103354
  key: 0,
103108
103355
  class: "cn-collectives-card__row-collective"
103109
103356
  };
103110
- const _hoisted_24$M = {
103357
+ const _hoisted_24$N = {
103111
103358
  key: 3,
103112
103359
  class: "cn-collectives-card__view-all"
103113
103360
  };
103114
- const _hoisted_25$F = ["href"];
103361
+ const _hoisted_25$G = ["href"];
103115
103362
 
103116
103363
  function render$7s(_ctx, _cache, $props, $setup, $data, $options) {
103117
103364
  const _component_NcLoadingIcon = vue$1.resolveComponent("NcLoadingIcon");
@@ -103239,12 +103486,12 @@ function render$7s(_ctx, _cache, $props, $setup, $data, $options) {
103239
103486
  }), 128 /* KEYED_FRAGMENT */))
103240
103487
  ])),
103241
103488
  ($data.pages.length > $data.COMPACT_LIMIT)
103242
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$M, [
103489
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$N, [
103243
103490
  vue$1.createElementVNode("a", {
103244
103491
  href: $props.collectivesAppUrl,
103245
103492
  target: "_blank",
103246
103493
  rel: "noopener"
103247
- }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_25$F)
103494
+ }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_25$G)
103248
103495
  ]))
103249
103496
  : vue$1.createCommentVNode("v-if", true)
103250
103497
  ], 64 /* STABLE_FRAGMENT */))
@@ -115733,13 +115980,19 @@ function indexRowData(data, { path = '', key = 'fileId' } = {}) {
115733
115980
  * `visibleIf.extension` lists file extensions (no case, a leading dot is
115734
115981
  * allowed); `visibleIf.mime` lists mime types, where one ending in `/` or
115735
115982
  * `/*` names the whole family. Every condition named must hold. An action
115736
- * 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`.
115737
115985
  *
115738
115986
  * @param {object} action The declared action.
115739
115987
  * @param {object} node The file (`basename`, `mime`).
115740
115988
  * @return {boolean} True when the action belongs on this file.
115741
115989
  */
115742
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
+ }
115743
115996
  const condition = action && action.visibleIf;
115744
115997
  if (!condition || typeof condition !== 'object') {
115745
115998
  return true
@@ -115849,6 +116102,11 @@ function normaliseFileColumns(columns) {
115849
116102
  if (!entry || typeof entry !== 'object' || typeof entry.key !== 'string' || entry.key === '') {
115850
116103
  return
115851
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
+ }
115852
116110
  if (isBuiltInFileColumn(entry.key) && entry.source === undefined) {
115853
116111
  out.push({ ...BUILT_IN_FILE_COLUMNS[entry.key], ...entry, builtIn: true });
115854
116112
  return
@@ -118454,7 +118712,7 @@ var script$71 = {
118454
118712
  ? { path: this.currentPath }
118455
118713
  : { fileId: node.fileid, fileName: node.basename, path: node.path };
118456
118714
  if (type === 'open-modal') {
118457
- wrapped = { ...action, props: { ...(action.props || {}), ...context } };
118715
+ wrapped = { ...action, props: { ...(action.props || {}), ...context, ...this.rowPropsOf(action, node) } };
118458
118716
  } else if (type === 'handler') {
118459
118717
  wrapped = { ...action, args: [...(action.args || []), node ?? this.folder] };
118460
118718
  }
@@ -118465,6 +118723,32 @@ var script$71 = {
118465
118723
  dispatchAction$1(wrapped, { router: this.$router || null });
118466
118724
  },
118467
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
+
118468
118752
  /**
118469
118753
  * A click on a linked row opens the file where it lives.
118470
118754
  *
@@ -118564,18 +118848,18 @@ const _hoisted_23$R = {
118564
118848
  class: "cn-files-browser__table",
118565
118849
  "data-testid": "cn-files-browser-table"
118566
118850
  };
118567
- const _hoisted_24$L = {
118851
+ const _hoisted_24$M = {
118568
118852
  key: 0,
118569
118853
  class: "cn-files-browser__col-select",
118570
118854
  scope: "col"
118571
118855
  };
118572
- const _hoisted_25$E = ["checked", ".indeterminate", "disabled", "aria-label"];
118573
- const _hoisted_26$A = {
118856
+ const _hoisted_25$F = ["checked", ".indeterminate", "disabled", "aria-label"];
118857
+ const _hoisted_26$B = {
118574
118858
  class: "cn-files-browser__col-icon",
118575
118859
  scope: "col"
118576
118860
  };
118577
118861
  const _hoisted_27$s = { class: "hidden-visually" };
118578
- const _hoisted_28$p = ["aria-sort", "data-testid"];
118862
+ const _hoisted_28$q = ["aria-sort", "data-testid"];
118579
118863
  const _hoisted_29$o = ["onClick"];
118580
118864
  const _hoisted_30$m = {
118581
118865
  class: "cn-files-browser__col-actions",
@@ -119080,7 +119364,7 @@ function render$70(_ctx, _cache, $props, $setup, $data, $options) {
119080
119364
  vue$1.createElementVNode("thead", null, [
119081
119365
  vue$1.createElementVNode("tr", null, [
119082
119366
  ($options.bulkEnabled)
119083
- ? (vue$1.openBlock(), vue$1.createElementBlock("th", _hoisted_24$L, [
119367
+ ? (vue$1.openBlock(), vue$1.createElementBlock("th", _hoisted_24$M, [
119084
119368
  vue$1.createElementVNode("input", {
119085
119369
  type: "checkbox",
119086
119370
  class: "cn-files-browser__select",
@@ -119090,10 +119374,10 @@ function render$70(_ctx, _cache, $props, $setup, $data, $options) {
119090
119374
  "aria-label": $options.t('nextcloud-vue', 'Select all files'),
119091
119375
  "data-testid": "cn-files-browser-select-all",
119092
119376
  onChange: _cache[1] || (_cache[1] = $event => ($options.toggleAll($event.target.checked)))
119093
- }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_25$E)
119377
+ }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_25$F)
119094
119378
  ]))
119095
119379
  : vue$1.createCommentVNode("v-if", true),
119096
- vue$1.createElementVNode("th", _hoisted_26$A, [
119380
+ vue$1.createElementVNode("th", _hoisted_26$B, [
119097
119381
  vue$1.createElementVNode("span", _hoisted_27$s, vue$1.toDisplayString($options.t('nextcloud-vue', 'Type')), 1 /* TEXT */)
119098
119382
  ]),
119099
119383
  (vue$1.openBlock(true), vue$1.createElementBlock(vue$1.Fragment, null, vue$1.renderList($options.renderedColumns, (column) => {
@@ -119122,7 +119406,7 @@ function render$70(_ctx, _cache, $props, $setup, $data, $options) {
119122
119406
  : (vue$1.openBlock(), vue$1.createElementBlock(vue$1.Fragment, { key: 1 }, [
119123
119407
  vue$1.createTextVNode(vue$1.toDisplayString($options.columnLabel(column)), 1 /* TEXT */)
119124
119408
  ], 64 /* STABLE_FRAGMENT */))
119125
- ], 10 /* CLASS, PROPS */, _hoisted_28$p))
119409
+ ], 10 /* CLASS, PROPS */, _hoisted_28$q))
119126
119410
  }), 128 /* KEYED_FRAGMENT */)),
119127
119411
  vue$1.createElementVNode("th", _hoisted_30$m, [
119128
119412
  vue$1.createElementVNode("span", _hoisted_31$k, vue$1.toDisplayString($options.t('nextcloud-vue', 'Actions')), 1 /* TEXT */)
@@ -119715,7 +119999,7 @@ function render$70(_ctx, _cache, $props, $setup, $data, $options) {
119715
119999
  }
119716
120000
 
119717
120001
  script$71.render = render$70;
119718
- script$71.__scopeId = "data-v-ad907164";
120002
+ script$71.__scopeId = "data-v-3a771a01";
119719
120003
  const __sfc_main$6X = script$71;
119720
120004
 
119721
120005
  /**
@@ -124446,11 +124730,11 @@ const _hoisted_23$Q = {
124446
124730
  key: 0,
124447
124731
  class: "cn-maps-card__coords"
124448
124732
  };
124449
- const _hoisted_24$K = {
124733
+ const _hoisted_24$L = {
124450
124734
  key: 3,
124451
124735
  class: "cn-maps-card__view-all"
124452
124736
  };
124453
- const _hoisted_25$D = ["href"];
124737
+ const _hoisted_25$E = ["href"];
124454
124738
 
124455
124739
  function render$6T(_ctx, _cache, $props, $setup, $data, $options) {
124456
124740
  const _component_NcLoadingIcon = vue$1.resolveComponent("NcLoadingIcon");
@@ -124565,12 +124849,12 @@ function render$6T(_ctx, _cache, $props, $setup, $data, $options) {
124565
124849
  }), 128 /* KEYED_FRAGMENT */))
124566
124850
  ])),
124567
124851
  ($data.points.length > $data.COMPACT_LIMIT)
124568
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$K, [
124852
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_24$L, [
124569
124853
  vue$1.createElementVNode("a", {
124570
124854
  href: $props.mapsAppUrl,
124571
124855
  target: "_blank",
124572
124856
  rel: "noopener"
124573
- }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_25$D)
124857
+ }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_25$E)
124574
124858
  ]))
124575
124859
  : vue$1.createCommentVNode("v-if", true)
124576
124860
  ], 64 /* STABLE_FRAGMENT */))
@@ -131191,7 +131475,7 @@ const _hoisted_23$P = {
131191
131475
  key: 3,
131192
131476
  class: "cn-photos-card__view-all"
131193
131477
  };
131194
- const _hoisted_24$J = ["href"];
131478
+ const _hoisted_24$K = ["href"];
131195
131479
 
131196
131480
  function render$6F(_ctx, _cache, $props, $setup, $data, $options) {
131197
131481
  const _component_NcLoadingIcon = vue$1.resolveComponent("NcLoadingIcon");
@@ -131324,7 +131608,7 @@ function render$6F(_ctx, _cache, $props, $setup, $data, $options) {
131324
131608
  href: $props.photosAppUrl,
131325
131609
  target: "_blank",
131326
131610
  rel: "noopener noreferrer"
131327
- }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_24$J)
131611
+ }, vue$1.toDisplayString($props.viewAllLabel), 9 /* TEXT, PROPS */, _hoisted_24$K)
131328
131612
  ]))
131329
131613
  : vue$1.createCommentVNode("v-if", true)
131330
131614
  ], 64 /* STABLE_FRAGMENT */))
@@ -140631,7 +140915,8 @@ const FLOW_TASKS_URL$1 = '/apps/openregister/api/flow-tasks';
140631
140915
  // creating app named it, so it is the one filter that lets a Tasks page say
140632
140916
  // "the reminders" without the engine learning what a reminder is. It widens
140633
140917
  // nothing: it narrows within the inbox the session already decides.
140634
- 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'];
140635
140920
 
140636
140921
  /**
140637
140922
  * The flat body `POST /api/flow-tasks` takes for a task on a record.
@@ -140768,7 +141053,7 @@ const useTaskInboxStore = pinia.defineStore('cnTaskInbox', {
140768
141053
  if (value === undefined || value === null || value === '') {
140769
141054
  continue
140770
141055
  }
140771
- params[key] = (key === 'overdue' || key === 'isTerminal') ? String(value) : value;
141056
+ params[key] = (key === 'overdue' || key === 'isTerminal' || key === 'openFirst') ? String(value) : value;
140772
141057
  }
140773
141058
  const response = await axios$1.get(router.generateUrl(FLOW_TASKS_URL$1), { params });
140774
141059
  const results = response.data?.results || [];
@@ -140948,7 +141233,7 @@ const useTaskInboxStore = pinia.defineStore('cnTaskInbox', {
140948
141233
  }
140949
141234
  // The endpoint reads `overdue` as a boolean-ish string; a quick
140950
141235
  // filter authors it as `true` for legibility.
140951
- params[key] = (key === 'overdue' || key === 'isTerminal') ? String(value) : value;
141236
+ params[key] = (key === 'overdue' || key === 'isTerminal' || key === 'openFirst') ? String(value) : value;
140952
141237
  }
140953
141238
 
140954
141239
  this.loading = true;
@@ -141654,12 +141939,15 @@ function tasksSource() {
141654
141939
  *
141655
141940
  * @param {string} key The column key.
141656
141941
  * @param {string} order `asc` or `desc`.
141657
- * @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.
141658
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
141659
141947
  */
141660
141948
  sortParam: (key, order) => {
141661
141949
  const field = TASK_SORT_FIELDS[key];
141662
- return field ? { sort: (order === 'desc' ? '-' : '') + field } : null
141950
+ return field ? { sort: (order === 'desc' ? '-' : '') + field, openFirst: true } : null
141663
141951
  },
141664
141952
 
141665
141953
  // The scope tabs. Each tab's filter is merged over the page's
@@ -154133,14 +154421,14 @@ const _hoisted_23$N = {
154133
154421
  key: 2,
154134
154422
  class: "cn-form-dialog__option-group"
154135
154423
  };
154136
- const _hoisted_24$I = { class: "cn-form-dialog__option" };
154137
- const _hoisted_25$C = {
154424
+ const _hoisted_24$J = { class: "cn-form-dialog__option" };
154425
+ const _hoisted_25$D = {
154138
154426
  key: 10,
154139
154427
  class: "cn-form-dialog__select-wrapper"
154140
154428
  };
154141
- const _hoisted_26$z = { class: "cn-form-dialog__option" };
154429
+ const _hoisted_26$A = { class: "cn-form-dialog__option" };
154142
154430
  const _hoisted_27$r = { class: "cn-form-dialog__option" };
154143
- const _hoisted_28$o = {
154431
+ const _hoisted_28$p = {
154144
154432
  key: 11,
154145
154433
  class: "cn-form-dialog__select-wrapper"
154146
154434
  };
@@ -154850,7 +155138,7 @@ function render$6c(_ctx, _cache, $props, $setup, $data, $options) {
154850
155138
  ? {
154851
155139
  name: "selected-option",
154852
155140
  fn: vue$1.withCtx((optionProps) => [
154853
- vue$1.createElementVNode("span", _hoisted_24$I, [
155141
+ vue$1.createElementVNode("span", _hoisted_24$J, [
154854
155142
  ($options.optionSwatch(field, optionProps))
154855
155143
  ? (vue$1.openBlock(), vue$1.createElementBlock("span", {
154856
155144
  key: 0,
@@ -154884,7 +155172,7 @@ function render$6c(_ctx, _cache, $props, $setup, $data, $options) {
154884
155172
  }, null, 8 /* PROPS */, ["id", "text", "more", "help", "label", "error"])
154885
155173
  ]))
154886
155174
  : (field.widget === 'multiselect' || field.widget === 'user-multiselect' || field.widget === 'group-multiselect')
154887
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_25$C, [
155175
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_25$D, [
154888
155176
  vue$1.withDirectives((vue$1.openBlock(), vue$1.createBlock(_component_NcSelect, {
154889
155177
  inputId: 'cn-form-' + field.key,
154890
155178
  inputLabel: $options.fieldLabelText(field),
@@ -154911,7 +155199,7 @@ function render$6c(_ctx, _cache, $props, $setup, $data, $options) {
154911
155199
  : {
154912
155200
  name: "option",
154913
155201
  fn: vue$1.withCtx((optionProps) => [
154914
- 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 */)
154915
155203
  ]),
154916
155204
  key: "1"
154917
155205
  },
@@ -154943,7 +155231,7 @@ function render$6c(_ctx, _cache, $props, $setup, $data, $options) {
154943
155231
  }, null, 8 /* PROPS */, ["id", "text", "more", "help", "label", "error"])
154944
155232
  ]))
154945
155233
  : (field.widget === 'tags')
154946
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_28$o, [
155234
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_28$p, [
154947
155235
  vue$1.withDirectives((vue$1.openBlock(), vue$1.createBlock(_component_NcSelect, {
154948
155236
  inputId: 'cn-form-' + field.key,
154949
155237
  inputLabel: $options.fieldLabelText(field),
@@ -165021,21 +165309,21 @@ const _hoisted_20$T = {
165021
165309
  const _hoisted_21$R = { class: "cn-related-objects-widget__list" };
165022
165310
  const _hoisted_22$Q = ["href"];
165023
165311
  const _hoisted_23$M = { class: "cn-related-objects-widget__label" };
165024
- const _hoisted_24$H = {
165312
+ const _hoisted_24$I = {
165025
165313
  key: 0,
165026
165314
  class: "cn-related-objects-widget__meta"
165027
165315
  };
165028
- const _hoisted_25$B = {
165316
+ const _hoisted_25$C = {
165029
165317
  key: 1,
165030
165318
  class: "cn-related-objects-widget__row cn-related-objects-widget__row--static",
165031
165319
  "data-testid": "cn-related-static-row"
165032
165320
  };
165033
- const _hoisted_26$y = { class: "cn-related-objects-widget__label" };
165321
+ const _hoisted_26$z = { class: "cn-related-objects-widget__label" };
165034
165322
  const _hoisted_27$q = {
165035
165323
  key: 0,
165036
165324
  class: "cn-related-objects-widget__meta"
165037
165325
  };
165038
- const _hoisted_28$n = ["onClick", "onKeydown"];
165326
+ const _hoisted_28$o = ["onClick", "onKeydown"];
165039
165327
  const _hoisted_29$m = { class: "cn-related-objects-widget__label" };
165040
165328
  const _hoisted_30$k = {
165041
165329
  key: 0,
@@ -165291,17 +165579,17 @@ function render$64(_ctx, _cache, $props, $setup, $data, $options) {
165291
165579
  }, null, 8 /* PROPS */, ["name"]),
165292
165580
  vue$1.createElementVNode("span", _hoisted_23$M, vue$1.toDisplayString(item.label), 1 /* TEXT */),
165293
165581
  (item.meta)
165294
- ? (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 */))
165295
165583
  : vue$1.createCommentVNode("v-if", true)
165296
165584
  ], 8 /* PROPS */, _hoisted_22$Q))
165297
165585
  : ($options.isStaticRow($options.activeGroup, item))
165298
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_25$B, [
165586
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_25$C, [
165299
165587
  vue$1.createVNode(_component_CnIcon, {
165300
165588
  name: $options.activeGroup.icon,
165301
165589
  size: 20,
165302
165590
  class: "cn-related-objects-widget__icon"
165303
165591
  }, null, 8 /* PROPS */, ["name"]),
165304
- 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 */),
165305
165593
  (item.meta)
165306
165594
  ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_27$q, vue$1.toDisplayString(item.meta), 1 /* TEXT */))
165307
165595
  : vue$1.createCommentVNode("v-if", true)
@@ -165326,7 +165614,7 @@ function render$64(_ctx, _cache, $props, $setup, $data, $options) {
165326
165614
  (item.detail && $options.isExpanded($options.activeGroup.key, item))
165327
165615
  ? (vue$1.openBlock(), vue$1.createElementBlock("p", _hoisted_31$i, vue$1.toDisplayString(item.detail), 1 /* TEXT */))
165328
165616
  : vue$1.createCommentVNode("v-if", true)
165329
- ], 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_28$n)),
165617
+ ], 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_28$o)),
165330
165618
  (item.children && item.children.length)
165331
165619
  ? (vue$1.openBlock(), vue$1.createElementBlock("ul", _hoisted_32$h, [
165332
165620
  (vue$1.openBlock(true), vue$1.createElementBlock(vue$1.Fragment, null, vue$1.renderList(item.children, (child) => {
@@ -166509,9 +166797,9 @@ const _hoisted_20$S = {
166509
166797
  const _hoisted_21$Q = { class: "cn-retention__rows" };
166510
166798
  const _hoisted_22$P = { class: "cn-retention__row" };
166511
166799
  const _hoisted_23$L = { class: "cn-retention__row-text" };
166512
- const _hoisted_24$G = { class: "cn-retention__row-title" };
166513
- const _hoisted_25$A = { class: "cn-retention__muted" };
166514
- 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 = {
166515
166803
  key: 7,
166516
166804
  class: "cn-retention__outcome",
166517
166805
  "data-testid": "cn-retention-outcome"
@@ -166695,8 +166983,8 @@ function render$61(_ctx, _cache, $props, $setup, $data, $options) {
166695
166983
  vue$1.createElementVNode("ul", _hoisted_21$Q, [
166696
166984
  vue$1.createElementVNode("li", _hoisted_22$P, [
166697
166985
  vue$1.createElementVNode("span", _hoisted_23$L, [
166698
- 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 */),
166699
- 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 */)
166700
166988
  ]),
166701
166989
  vue$1.createVNode(_component_CnStatusBadge, {
166702
166990
  label: $options.transfers ? $options.t('nextcloud-vue', 'Transfer') : $options.t('nextcloud-vue', 'No transfer'),
@@ -166707,7 +166995,7 @@ function render$61(_ctx, _cache, $props, $setup, $data, $options) {
166707
166995
  ]))
166708
166996
  : vue$1.createCommentVNode("v-if", true),
166709
166997
  ($options.outcome)
166710
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_26$x, [
166998
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_26$y, [
166711
166999
  (vue$1.openBlock(), vue$1.createBlock(vue$1.resolveDynamicComponent(_ctx.innerHeadingTag), { class: "cn-inner-heading" }, {
166712
167000
  default: vue$1.withCtx(() => [
166713
167001
  vue$1.createTextVNode(vue$1.toDisplayString($options.t('nextcloud-vue', 'Outcome')), 1 /* TEXT */)
@@ -168441,7 +168729,7 @@ var script$5_ = {
168441
168729
  * segments to the card (`header` event, `count`), for a chip after the
168442
168730
  * title or a `{count}` in it.
168443
168731
  *
168444
- * @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}}
168445
168733
  */
168446
168734
  content: {
168447
168735
  type: Object,
@@ -168499,6 +168787,17 @@ var script$5_ = {
168499
168787
  return Boolean(this.content && this.content.inset === true)
168500
168788
  },
168501
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
+
168502
168801
  /**
168503
168802
  * The translate function in use.
168504
168803
  *
@@ -168855,7 +169154,7 @@ function render$5Z(_ctx, _cache, $props, $setup, $data, $options) {
168855
169154
  const _component_CnWidgetEmptyState = vue$1.resolveComponent("CnWidgetEmptyState");
168856
169155
 
168857
169156
  return (vue$1.openBlock(), vue$1.createElementBlock("div", {
168858
- 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 }]),
168859
169158
  "data-testid": "cn-stacked-bar"
168860
169159
  }, [
168861
169160
  ($options.isLoading)
@@ -168915,7 +169214,7 @@ function render$5Z(_ctx, _cache, $props, $setup, $data, $options) {
168915
169214
  }
168916
169215
 
168917
169216
  script$5_.render = render$5Z;
168918
- script$5_.__scopeId = "data-v-455eb41f";
169217
+ script$5_.__scopeId = "data-v-7bd1aead";
168919
169218
  const __sfc_main$5U = script$5_;
168920
169219
 
168921
169220
  /**
@@ -169493,6 +169792,41 @@ function daysAfter(days, now) {
169493
169792
  return date
169494
169793
  }
169495
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
+
169496
169830
  /**
169497
169831
  * A date as a reader sees it: day, short month and year.
169498
169832
  *
@@ -169560,6 +169894,9 @@ function fillInputTemplate(template, values, now = new Date()) {
169560
169894
  * in that other input.
169561
169895
  * - `preview: { label, template, note, testid }`: a line under the input,
169562
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.
169563
169900
  * - `default`: the starting value.
169564
169901
  * - Presentation: `labelOutside`, `id`, `note`, `wide`, `testid`, `rows`,
169565
169902
  * `placeholder`.
@@ -169572,7 +169909,7 @@ function fillInputTemplate(template, values, now = new Date()) {
169572
169909
  * @param {string} [options.register] Register the reference pickers fall back to.
169573
169910
  * @param {object} [options.context] What `optionsSource` reads.
169574
169911
  * @param {object} [options.values] The values so far.
169575
- * @param {Date} [options.now] What counts as today, for previews.
169912
+ * @param {Date} [options.now] What counts as today, for previews and `dateField`.
169576
169913
  * @return {Array<object>} The descriptors.
169577
169914
  */
169578
169915
  function resolveInputFields(declared, { schema = null, register = '', context = {}, values = {}, now } = {}) {
@@ -169615,6 +169952,9 @@ function resolveInputFields(declared, { schema = null, register = '', context =
169615
169952
  field.previewText = preview && typeof preview === 'object'
169616
169953
  ? fillInputTemplate(preview.template, values, now)
169617
169954
  : '';
169955
+ field.dateValue = field.input.dateField && typeof field.input.dateField === 'object'
169956
+ ? dayAfterIso(values[field.key], now)
169957
+ : '';
169618
169958
  }
169619
169959
 
169620
169960
  return fields
@@ -170051,6 +170391,28 @@ var script$5Y = {
170051
170391
  return this.markOptional && !field.required ? `${field.label} (${l10n.translate('nextcloud-vue', 'optional')})` : field.label
170052
170392
  },
170053
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
+
170054
170416
  /**
170055
170417
  * Label of a sub-field, with the required marker.
170056
170418
  *
@@ -170232,15 +170594,21 @@ const _hoisted_15$1p = {
170232
170594
  };
170233
170595
  const _hoisted_16$1e = ["data-testid"];
170234
170596
  const _hoisted_17$16 = ["data-testid"];
170235
- const _hoisted_18$Z = { class: "cn-transition-input-fields__label" };
170236
- const _hoisted_19$W = ["data-testid"];
170237
- 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 = {
170238
170606
  key: 0,
170239
170607
  class: "cn-transition-input-fields__muted"
170240
170608
  };
170241
- const _hoisted_21$P = { class: "cn-transition-input-fields__notice-text" };
170242
- const _hoisted_22$O = { key: 0 };
170243
- 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"];
170244
170612
 
170245
170613
  function render$5X(_ctx, _cache, $props, $setup, $data, $options) {
170246
170614
  const _component_CnTemplateSelect = vue$1.resolveComponent("CnTemplateSelect");
@@ -170458,20 +170826,44 @@ function render$5X(_ctx, _cache, $props, $setup, $data, $options) {
170458
170826
  }, vue$1.toDisplayString($options.refusalFor(field)), 9 /* TEXT, PROPS */, _hoisted_16$1e))
170459
170827
  : vue$1.createCommentVNode("v-if", true)
170460
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),
170461
170853
  (field.previewText)
170462
170854
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", {
170463
170855
  key: `${field.key}-preview`,
170464
170856
  class: "cn-transition-input-fields__preview cn-transition-input-fields__wide",
170465
170857
  "data-testid": field.input.preview.testid || `cn-transition-input-preview-${field.key}`
170466
170858
  }, [
170467
- 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 */),
170468
170860
  vue$1.createElementVNode("span", {
170469
170861
  "data-testid": `${field.input.preview.testid || `cn-transition-input-preview-${field.key}`}-value`
170470
- }, vue$1.toDisplayString(field.previewText), 9 /* TEXT, PROPS */, _hoisted_19$W),
170862
+ }, vue$1.toDisplayString(field.previewText), 9 /* TEXT, PROPS */, _hoisted_22$O),
170471
170863
  (field.input.preview.note)
170472
- ? (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 */))
170473
170865
  : vue$1.createCommentVNode("v-if", true)
170474
- ], 8 /* PROPS */, _hoisted_17$16))
170866
+ ], 8 /* PROPS */, _hoisted_20$R))
170475
170867
  : vue$1.createCommentVNode("v-if", true)
170476
170868
  ], 64 /* STABLE_FRAGMENT */))
170477
170869
  }), 128 /* KEYED_FRAGMENT */)),
@@ -170487,9 +170879,9 @@ function render$5X(_ctx, _cache, $props, $setup, $data, $options) {
170487
170879
  size: 20,
170488
170880
  class: "cn-transition-input-fields__notice-icon"
170489
170881
  }, null, 8 /* PROPS */, ["name"]),
170490
- vue$1.createElementVNode("span", _hoisted_21$P, [
170882
+ vue$1.createElementVNode("span", _hoisted_24$G, [
170491
170883
  ($props.notice.heading)
170492
- ? (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 */))
170493
170885
  : vue$1.createCommentVNode("v-if", true),
170494
170886
  vue$1.createElementVNode("span", null, vue$1.toDisplayString($props.notice.text), 1 /* TEXT */)
170495
170887
  ])
@@ -170500,13 +170892,13 @@ function render$5X(_ctx, _cache, $props, $setup, $data, $options) {
170500
170892
  key: `unoffered-${key}`,
170501
170893
  class: "cn-transition-input__field-error",
170502
170894
  "data-testid": `cn-transition-input-error-${key}`
170503
- }, 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))
170504
170896
  }), 128 /* KEYED_FRAGMENT */))
170505
170897
  ], 2 /* CLASS */))
170506
170898
  }
170507
170899
 
170508
170900
  script$5Y.render = render$5X;
170509
- script$5Y.__scopeId = "data-v-3e4484aa";
170901
+ script$5Y.__scopeId = "data-v-731d1a6c";
170510
170902
  const __sfc_main$5S = script$5Y;
170511
170903
 
170512
170904
  /**
@@ -180290,6 +180682,7 @@ var script$5J = {
180290
180682
  * - `hidden` (boolean) — Hide this property
180291
180683
  * - `editable` (boolean) — Override editability (default: based on schema readOnly)
180292
180684
  * - `label` (string) — Override the display label
180685
+ * - `emptyValue` (string) — Words shown, translated, for an empty or false value instead of a dash
180293
180686
  * - `widget` (string) — Override the widget type for editing
180294
180687
  * - `formatter` (string) — Display the value through the formatter
180295
180688
  * registered under this id on CnAppRoot (`cnFormatters`), called as
@@ -180309,7 +180702,8 @@ var script$5J = {
180309
180702
  },
180310
180703
 
180311
180704
  /**
180312
- * Number of grid columns.
180705
+ * Number of grid columns. The board look fits 200px columns instead,
180706
+ * except for one column, which it keeps.
180313
180707
  */
180314
180708
  columns: {
180315
180709
  type: Number,
@@ -180670,7 +181064,8 @@ var script$5J = {
180670
181064
  // still showing the stored code — `submitted` under a Dutch
180671
181065
  // label, beside a list that already read "Ingediend".
180672
181066
  const enumLabel = this.enumDisplayLabel(field, raw);
180673
- 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 || {}));
180674
181069
  }
180675
181070
  return values
180676
181071
  },
@@ -180686,8 +181081,12 @@ var script$5J = {
180686
181081
  * CSS grid template for the container.
180687
181082
  */
180688
181083
  gridStyle() {
180689
- // 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).
180690
181086
  if (normalizeLook(this.cnLook) === 'board') {
181087
+ if (this.columns === 1) {
181088
+ return { 'grid-template-columns': 'minmax(0, 1fr)' }
181089
+ }
180691
181090
  return {
180692
181091
  'grid-template-columns': 'repeat(auto-fit, minmax(200px, 1fr))',
180693
181092
  }
@@ -181412,6 +181811,24 @@ var script$5J = {
181412
181811
  return this.isPlainObject(raw)
181413
181812
  },
181414
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
+
181415
181832
  /**
181416
181833
  * Check if a field is editable.
181417
181834
  *
@@ -182536,7 +182953,7 @@ function render$5I(_ctx, _cache, $props, $setup, $data, $options) {
182536
182953
  }
182537
182954
 
182538
182955
  script$5J.render = render$5I;
182539
- script$5J.__scopeId = "data-v-1042b83e";
182956
+ script$5J.__scopeId = "data-v-702c7c98";
182540
182957
  const __sfc_main$5D = script$5J;
182541
182958
 
182542
182959
  /**
@@ -190939,7 +191356,7 @@ const _hoisted_24$F = { class: "cn-timeline-manage__heading" };
190939
191356
  const _hoisted_25$z = { class: "cn-timeline-manage__title" };
190940
191357
  const _hoisted_26$w = { class: "cn-timeline-manage__subtitle" };
190941
191358
  const _hoisted_27$o = { class: "cn-timeline-manage__composer-title" };
190942
- const _hoisted_28$m = { class: "cn-timeline-manage__composer-hint" };
191359
+ const _hoisted_28$n = { class: "cn-timeline-manage__composer-hint" };
190943
191360
  const _hoisted_29$l = ["for"];
190944
191361
  const _hoisted_30$j = ["id", "placeholder"];
190945
191362
  const _hoisted_31$h = { class: "cn-timeline-manage__composer-foot" };
@@ -191195,7 +191612,7 @@ function render$5r(_ctx, _cache, $props, $setup, $data, $options) {
191195
191612
  (!$options.composerOnly)
191196
191613
  ? (vue$1.openBlock(), vue$1.createElementBlock(vue$1.Fragment, { key: 0 }, [
191197
191614
  vue$1.createElementVNode("h3", _hoisted_27$o, vue$1.toDisplayString($options.t('nextcloud-vue', 'Add note')), 1 /* TEXT */),
191198
- 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 */)
191199
191616
  ], 64 /* STABLE_FRAGMENT */))
191200
191617
  : vue$1.createCommentVNode("v-if", true),
191201
191618
  ($data.textBlocks.length)
@@ -216334,7 +216751,7 @@ const _hoisted_24$B = { value: "icon-only" };
216334
216751
  const _hoisted_25$w = { class: "cn-links-form__option" };
216335
216752
  const _hoisted_26$u = ["value"];
216336
216753
  const _hoisted_27$n = { value: "small" };
216337
- const _hoisted_28$l = { value: "medium" };
216754
+ const _hoisted_28$m = { value: "medium" };
216338
216755
  const _hoisted_29$k = { value: "large" };
216339
216756
  const _hoisted_30$i = { class: "cn-links-form__option cn-links-form__option--checkbox" };
216340
216757
  const _hoisted_31$g = ["checked"];
@@ -216492,7 +216909,7 @@ function render$4I(_ctx, _cache, $props, $setup, $data, $options) {
216492
216909
  onChange: _cache[3] || (_cache[3] = $event => ($options.updateOption('iconSize', $event.target.value)))
216493
216910
  }, [
216494
216911
  vue$1.createElementVNode("option", _hoisted_27$n, vue$1.toDisplayString(_ctx.t('nextcloud-vue', 'Small (24 px)')), 1 /* TEXT */),
216495
- 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 */),
216496
216913
  vue$1.createElementVNode("option", _hoisted_29$k, vue$1.toDisplayString(_ctx.t('nextcloud-vue', 'Large (64 px)')), 1 /* TEXT */)
216497
216914
  ], 40 /* PROPS, NEED_HYDRATION */, _hoisted_26$u)
216498
216915
  ]),
@@ -222366,7 +222783,7 @@ const _hoisted_25$v = {
222366
222783
  };
222367
222784
  const _hoisted_26$t = { class: "cn-calendar-widget__agenda-header" };
222368
222785
  const _hoisted_27$m = { class: "cn-calendar-widget__agenda-time" };
222369
- const _hoisted_28$k = { class: "cn-calendar-widget__agenda-title" };
222786
+ const _hoisted_28$l = { class: "cn-calendar-widget__agenda-title" };
222370
222787
  const _hoisted_29$j = {
222371
222788
  key: 0,
222372
222789
  class: "cn-calendar-widget__agenda-cal"
@@ -222484,7 +222901,7 @@ function render$4u(_ctx, _cache, $props, $setup, $data, $options) {
222484
222901
  style: vue$1.normalizeStyle($options.eventStyle(event))
222485
222902
  }, [
222486
222903
  vue$1.createElementVNode("span", _hoisted_27$m, vue$1.toDisplayString($options.formatTime(event)), 1 /* TEXT */),
222487
- 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 */),
222488
222905
  (event.calendarName)
222489
222906
  ? (vue$1.openBlock(), vue$1.createElementBlock("span", _hoisted_29$j, vue$1.toDisplayString(event.calendarName), 1 /* TEXT */))
222490
222907
  : vue$1.createCommentVNode("v-if", true)
@@ -223571,7 +223988,7 @@ const _hoisted_26$s = {
223571
223988
  class: "cn-spend-analytics-widget__panel"
223572
223989
  };
223573
223990
  const _hoisted_27$l = { class: "cn-spend-analytics-widget__panel-title" };
223574
- const _hoisted_28$j = {
223991
+ const _hoisted_28$k = {
223575
223992
  key: 0,
223576
223993
  class: "cn-spend-analytics-widget__inline-empty"
223577
223994
  };
@@ -223687,7 +224104,7 @@ function render$4s(_ctx, _cache, $props, $setup, $data, $options) {
223687
224104
  ? (vue$1.openBlock(), vue$1.createElementBlock("section", _hoisted_26$s, [
223688
224105
  vue$1.createElementVNode("h3", _hoisted_27$l, vue$1.toDisplayString($options.t('nextcloud-vue', 'Top vendors')), 1 /* TEXT */),
223689
224106
  ($options.vendorMissing)
223690
- ? (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 */))
223691
224108
  : ($options.vendorEmpty)
223692
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 */))
223693
224110
  : (vue$1.openBlock(), vue$1.createElementBlock("ul", _hoisted_30$h, [
@@ -229936,7 +230353,7 @@ const _hoisted_24$x = { class: "cn-tasks-widget__others-title" };
229936
230353
  const _hoisted_25$t = { class: "cn-tasks-widget__others-list" };
229937
230354
  const _hoisted_26$r = ["data-testid"];
229938
230355
  const _hoisted_27$k = { class: "cn-tasks-widget__body" };
229939
- const _hoisted_28$i = {
230356
+ const _hoisted_28$j = {
229940
230357
  key: 1,
229941
230358
  class: "cn-tasks-widget__name"
229942
230359
  };
@@ -230260,7 +230677,7 @@ function render$4g(_ctx, _cache, $props, $setup, $data, $options) {
230260
230677
  ]),
230261
230678
  _: 2 /* DYNAMIC */
230262
230679
  }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["to"]))
230263
- : (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 */)),
230264
230681
  vue$1.createElementVNode("span", _hoisted_29$h, vue$1.toDisplayString($options.dueLabel(task) || $options.tr('No due date')), 1 /* TEXT */)
230265
230682
  ]),
230266
230683
  vue$1.createElementVNode("span", _hoisted_30$g, [
@@ -236440,7 +236857,7 @@ var script$48 = {
236440
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`.
236441
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.
236442
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.
236443
- * 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.
236444
236861
  * A part may be `{ text }`, a constant; a `line` or `text` entry without `path` or `parts` shows its `text`.
236445
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.
236446
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.
@@ -236494,6 +236911,9 @@ var script$48 = {
236494
236911
  error: false,
236495
236912
  /** Keys of the masked rows the reader chose to show. */
236496
236913
  revealed: {},
236914
+ revealedValues: {},
236915
+ revealErrors: {},
236916
+ revealing: '',
236497
236917
  /** Key of the row whose value was just copied. */
236498
236918
  copied: '',
236499
236919
  /** Labels of the referenced objects read so far, by `refKey`. */
@@ -237341,7 +237761,12 @@ var script$48 = {
237341
237761
  extras.external = true;
237342
237762
  } else if (field.format === 'masked') {
237343
237763
  extras.masked = value.length > 4 ? '*'.repeat(Math.min(value.length - 4, 5)) + value.slice(-4) : value;
237344
- 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
+ }
237345
237770
  }
237346
237771
  return extras
237347
237772
  },
@@ -237353,7 +237778,14 @@ var script$48 = {
237353
237778
  * @param {object} row The row.
237354
237779
  * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-the-facts-card-draws-contact-details-identity-and-a-lead
237355
237780
  */
237356
- 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
+ }
237357
237789
  this.revealed = { ...this.revealed, [row.key]: true };
237358
237790
  const detail = { path: row.path };
237359
237791
  /**
@@ -237364,6 +237796,47 @@ var script$48 = {
237364
237796
  eventBus.emit(FACTS_REVEAL_CHANNEL, { ...detail, objectId: this.tokenCtx.objectId, register: this.tokenCtx.register || null, schema: this.tokenCtx.schema || null });
237365
237797
  },
237366
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
+
237367
237840
  /**
237368
237841
  * Copy a row's value to the clipboard.
237369
237842
  *
@@ -237866,26 +238339,27 @@ const _hoisted_7$2l = ["data-testid"];
237866
238339
  const _hoisted_8$27 = { class: "cn-facts-widget__value" };
237867
238340
  const _hoisted_9$1Y = ["href", "target", "rel"];
237868
238341
  const _hoisted_10$1L = { key: 2 };
237869
- const _hoisted_11$1y = ["aria-label", "data-testid", "onClick"];
237870
- const _hoisted_12$1l = ["aria-label", "title", "data-testid", "onClick"];
237871
- 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"];
237872
238345
  const _hoisted_14$16 = ["data-testid"];
237873
238346
  const _hoisted_15$12 = ["data-testid"];
237874
- const _hoisted_16$X = { class: "cn-facts-widget__lead-text" };
237875
- const _hoisted_17$S = { class: "cn-facts-widget__lead-title" };
237876
- 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 = {
237877
238351
  key: 0,
237878
238352
  class: "cn-facts-widget__lead-subtitle"
237879
238353
  };
237880
- const _hoisted_19$I = ["data-testid"];
237881
- const _hoisted_20$D = { class: "cn-facts-widget__meter-head" };
237882
- const _hoisted_21$C = { class: "cn-facts-widget__meter-label" };
237883
- const _hoisted_22$C = { class: "cn-facts-widget__meter-value" };
237884
- const _hoisted_23$z = ["aria-label", "aria-valuenow", "aria-valuemax", "aria-valuetext", "data-testid"];
237885
- const _hoisted_24$w = ["data-testid"];
237886
- const _hoisted_25$s = ["aria-label", "aria-valuenow", "data-testid"];
237887
- const _hoisted_26$q = { class: "cn-facts-widget__line" };
237888
- 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 };
237889
238363
 
237890
238364
  function render$47(_ctx, _cache, $props, $setup, $data, $options) {
237891
238365
  const _component_NcLoadingIcon = vue$1.resolveComponent("NcLoadingIcon");
@@ -237981,11 +238455,11 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
237981
238455
  class: "cn-facts-widget__value-link"
237982
238456
  }, {
237983
238457
  default: vue$1.withCtx(() => [
237984
- 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 */)
237985
238459
  ]),
237986
238460
  _: 2 /* DYNAMIC */
237987
238461
  }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["to"]))
237988
- : (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 */)),
237989
238463
  (row.reveal && !$data.revealed[row.key])
237990
238464
  ? (vue$1.openBlock(), vue$1.createElementBlock("button", {
237991
238465
  key: 3,
@@ -237993,6 +238467,7 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
237993
238467
  class: "cn-facts-widget__inline-button",
237994
238468
  "aria-label": $options.revealLabel(row),
237995
238469
  "data-testid": `cn-fact-reveal-${row.key}`,
238470
+ disabled: $data.revealing === row.key,
237996
238471
  onClick: $event => ($options.reveal(row))
237997
238472
  }, [
237998
238473
  vue$1.createVNode(_component_EyeOutline, {
@@ -238002,9 +238477,17 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
238002
238477
  vue$1.createTextVNode(" " + vue$1.toDisplayString($options.showLabel), 1 /* TEXT */)
238003
238478
  ], 8 /* PROPS */, _hoisted_11$1y))
238004
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),
238005
238488
  (row.copy)
238006
238489
  ? (vue$1.openBlock(), vue$1.createElementBlock("button", {
238007
- key: 4,
238490
+ key: 5,
238008
238491
  type: "button",
238009
238492
  class: "cn-facts-widget__icon-button",
238010
238493
  "aria-label": $options.copyLabel(row),
@@ -238023,7 +238506,7 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
238023
238506
  size: 16,
238024
238507
  "aria-hidden": "true"
238025
238508
  }))
238026
- ], 8 /* PROPS */, _hoisted_12$1l))
238509
+ ], 8 /* PROPS */, _hoisted_13$19))
238027
238510
  : vue$1.createCommentVNode("v-if", true)
238028
238511
  ])
238029
238512
  ], 8 /* PROPS */, _hoisted_7$2l))
@@ -238046,13 +238529,13 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
238046
238529
  key: 2,
238047
238530
  class: "cn-facts-widget__line",
238048
238531
  "data-testid": `cn-fact-${block.key}`
238049
- }, vue$1.toDisplayString(block.value), 9 /* TEXT, PROPS */, _hoisted_13$19))
238532
+ }, vue$1.toDisplayString(block.value), 9 /* TEXT, PROPS */, _hoisted_14$16))
238050
238533
  : (block.kind === 'text')
238051
238534
  ? (vue$1.openBlock(), vue$1.createElementBlock("span", {
238052
238535
  key: 3,
238053
238536
  class: "cn-facts-widget__text",
238054
238537
  "data-testid": `cn-fact-${block.key}`
238055
- }, vue$1.toDisplayString(block.value), 9 /* TEXT, PROPS */, _hoisted_14$16))
238538
+ }, vue$1.toDisplayString(block.value), 9 /* TEXT, PROPS */, _hoisted_15$12))
238056
238539
  : (block.kind === 'lead')
238057
238540
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", {
238058
238541
  key: 4,
@@ -238073,22 +238556,22 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
238073
238556
  vue$1.createTextVNode(vue$1.toDisplayString(block.initials), 1 /* TEXT */)
238074
238557
  ], 64 /* STABLE_FRAGMENT */))
238075
238558
  ], 2 /* CLASS */),
238076
- vue$1.createElementVNode("span", _hoisted_16$X, [
238077
- 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 */),
238078
238561
  (block.subtitle)
238079
- ? (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 */))
238080
238563
  : vue$1.createCommentVNode("v-if", true)
238081
238564
  ])
238082
- ], 8 /* PROPS */, _hoisted_15$12))
238565
+ ], 8 /* PROPS */, _hoisted_16$X))
238083
238566
  : (block.kind === 'meter')
238084
238567
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", {
238085
238568
  key: 5,
238086
238569
  class: vue$1.normalizeClass(["cn-facts-widget__meter", { 'cn-facts-widget__meter--over': block.over }]),
238087
238570
  "data-testid": `cn-fact-${block.key}`
238088
238571
  }, [
238089
- vue$1.createElementVNode("div", _hoisted_20$D, [
238090
- vue$1.createElementVNode("span", _hoisted_21$C, vue$1.toDisplayString(block.label), 1 /* TEXT */),
238091
- 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 */)
238092
238575
  ]),
238093
238576
  (block.limit !== null)
238094
238577
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", {
@@ -238106,9 +238589,9 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
238106
238589
  class: "cn-facts-widget__meter-bar",
238107
238590
  style: vue$1.normalizeStyle({ width: `${block.percent}%` })
238108
238591
  }, null, 4 /* STYLE */)
238109
- ], 8 /* PROPS */, _hoisted_23$z))
238592
+ ], 8 /* PROPS */, _hoisted_24$w))
238110
238593
  : vue$1.createCommentVNode("v-if", true)
238111
- ], 10 /* CLASS, PROPS */, _hoisted_19$I))
238594
+ ], 10 /* CLASS, PROPS */, _hoisted_20$D))
238112
238595
  : (block.kind === 'progress')
238113
238596
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", {
238114
238597
  key: 6,
@@ -238130,12 +238613,12 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
238130
238613
  class: "cn-facts-widget__meter-bar",
238131
238614
  style: vue$1.normalizeStyle({ width: `${block.percent}%` })
238132
238615
  }, null, 4 /* STYLE */)
238133
- ], 8 /* PROPS */, _hoisted_25$s))
238616
+ ], 8 /* PROPS */, _hoisted_26$q))
238134
238617
  : vue$1.createCommentVNode("v-if", true),
238135
- vue$1.createElementVNode("span", _hoisted_26$q, vue$1.toDisplayString(block.value), 1 /* TEXT */)
238136
- ], 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))
238137
238620
  : (block.kind === 'pill')
238138
- ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_27$j, [
238621
+ ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_28$i, [
238139
238622
  vue$1.createVNode(_component_CnStatusBadge, {
238140
238623
  label: block.value,
238141
238624
  variant: block.variant,
@@ -238229,7 +238712,7 @@ function render$47(_ctx, _cache, $props, $setup, $data, $options) {
238229
238712
  }
238230
238713
 
238231
238714
  script$48.render = render$47;
238232
- script$48.__scopeId = "data-v-4fd0d81c";
238715
+ script$48.__scopeId = "data-v-1b8f8e6f";
238233
238716
  const __sfc_main$48 = script$48;
238234
238717
 
238235
238718
  /**
@@ -250062,6 +250545,13 @@ registerDashboardWidget('choice-picker', {
250062
250545
  /** The bus channel every endpoint-bound widget on the page refetches on. */
250063
250546
  const PAGE_REFRESH_CHANNEL$3 = 'cn:page:refresh';
250064
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
+
250065
250555
  /**
250066
250556
  * The rows of a record list drawn as notes to record (`presentation:
250067
250557
  * "notes"`, DqDagAfsluiten "Nog open van vandaag"): per row its pill, title,
@@ -250296,6 +250786,9 @@ var script$3R = {
250296
250786
  try {
250297
250787
  const { default: axios } = await import('@nextcloud/axios');
250298
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
+ }
250299
250792
  const { [item.key]: done, ...rest } = this.drafts;
250300
250793
  this.drafts = rest;
250301
250794
  if (refresh) {
@@ -250527,7 +251020,7 @@ function render$3Q(_ctx, _cache, $props, $setup, $data, $options) {
250527
251020
  }
250528
251021
 
250529
251022
  script$3R.render = render$3Q;
250530
- script$3R.__scopeId = "data-v-6d7ff53b";
251023
+ script$3R.__scopeId = "data-v-56a223c4";
250531
251024
  const __sfc_main$3R = script$3R;
250532
251025
 
250533
251026
  const PAGE_REFRESH_CHANNEL$2 = 'cn:page:refresh';
@@ -320602,6 +321095,27 @@ function formatMasked(value, _row, _property, options) {
320602
321095
  return '*'.repeat(Math.min(text.length - visible, 5)) + (visible > 0 ? text.slice(-visible) : '')
320603
321096
  }
320604
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
+
320605
321119
  /**
320606
321120
  * Built-in formatter registry merged under any consumer-registered
320607
321121
  * `formatters` in `CnAppRoot`'s `cnFormatters` provide.
@@ -320648,6 +321162,7 @@ const BUILT_IN_FORMATTERS = {
320648
321162
  connectionSettingsLabel: formatConnectionSettingsLabel,
320649
321163
  masked: formatMasked,
320650
321164
  isoDuration: formatIsoDuration,
321165
+ valueMap: formatValueMap,
320651
321166
  };
320652
321167
 
320653
321168
  /**
@@ -354334,6 +354849,8 @@ var script$20 = {
354334
354849
  savedViews: [],
354335
354850
  savedViewsLoading: false,
354336
354851
  showSaveViewDialog: false,
354852
+ /** Whether the reader changed the view since it was opened or saved. */
354853
+ viewChanged: false,
354337
354854
  viewPendingDelete: null,
354338
354855
  /** An `api-call` header action waiting on its confirmation, or null. */
354339
354856
  headerApiCallPending: null,
@@ -358370,12 +358887,20 @@ var script$20 = {
358370
358887
 
358371
358888
  /**
358372
358889
  * Whether the toolbar draws Save view: not when the page switches it
358373
- * 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.
358374
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
358375
358894
  * @return {boolean}
358376
358895
  */
358377
358896
  toolbarSavesView() {
358378
- 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)
358379
358904
  },
358380
358905
 
358381
358906
  /**
@@ -360513,6 +361038,7 @@ var script$20 = {
360513
361038
  * @return {void}
360514
361039
  */
360515
361040
  onSearchEvent(value) {
361041
+ this.viewChanged = true;
360516
361042
  if (this.namedSourceSearches) {
360517
361043
  this.namedSearchTerm = value || '';
360518
361044
  }
@@ -360532,6 +361058,7 @@ var script$20 = {
360532
361058
  * @return {void}
360533
361059
  */
360534
361060
  onQuickFilterChange(index) {
361061
+ this.viewChanged = true;
360535
361062
  // `activeQuickFilterIndex` is a setup-returned ref; the
360536
361063
  // Vue 2 ref-unwrap proxy makes plain assignment work.
360537
361064
  this.activeQuickFilterIndex = index;
@@ -360548,6 +361075,7 @@ var script$20 = {
360548
361075
  * @return {void}
360549
361076
  */
360550
361077
  onQuickFilterMultiChange(indices) {
361078
+ this.viewChanged = true;
360551
361079
  this.selectedQuickFilterIndices = Array.isArray(indices) ? indices : [];
360552
361080
  this.$emit('quick-filter-change', this.selectedQuickFilterIndices);
360553
361081
  },
@@ -360563,6 +361091,7 @@ var script$20 = {
360563
361091
  * @return {void}
360564
361092
  */
360565
361093
  onSidebarSortChange(keys, value) {
361094
+ this.viewChanged = true;
360566
361095
  if (value !== undefined && this.selectSortOption(value)) {
360567
361096
  return
360568
361097
  }
@@ -360642,6 +361171,7 @@ var script$20 = {
360642
361171
  * @return {void}
360643
361172
  */
360644
361173
  onSortEvent(payload) {
361174
+ this.viewChanged = true;
360645
361175
  // An order by keys (a column header, a field in the Sort section)
360646
361176
  // leaves an endpoint-ranked order.
360647
361177
  if (this.sortSource.value) {
@@ -360845,6 +361375,7 @@ var script$20 = {
360845
361375
  * @return {void}
360846
361376
  */
360847
361377
  onClearFilters() {
361378
+ this.viewChanged = true;
360848
361379
  // "Clear all" on the Active line also undoes the quick filters it
360849
361380
  // listed (a tab without an `activeLabel` is not a filter there).
360850
361381
  for (const chip of this.quickFilterActiveChips) {
@@ -360953,6 +361484,7 @@ var script$20 = {
360953
361484
  * @spec openspec/changes/screens-board-reference-columns/specs/board-view/spec.md#requirement-a-board-can-switch-the-field-its-columns-come-from
360954
361485
  */
360955
361486
  onBoardColumnFieldChange(field) {
361487
+ this.viewChanged = true;
360956
361488
  this.boardColumnFieldChoice = field;
360957
361489
  /**
360958
361490
  * @event board-column-field-change The board's column switch picked another field. Payload: the field name.
@@ -360968,6 +361500,7 @@ var script$20 = {
360968
361500
  * @return {void}
360969
361501
  */
360970
361502
  onBoardNarrowChange(value) {
361503
+ this.viewChanged = true;
360971
361504
  this.onFilterEvent({ key: this.boardNarrow.field, values: value === '' ? [] : [value] });
360972
361505
  },
360973
361506
 
@@ -361030,6 +361563,7 @@ var script$20 = {
361030
361563
  * @return {void}
361031
361564
  */
361032
361565
  onFilterEvent(payload) {
361566
+ this.viewChanged = true;
361033
361567
  if (this.isSelfFetchMode && typeof this.list.onFilterChange === 'function') {
361034
361568
  this.list.onFilterChange(payload.key, payload.values);
361035
361569
  this.persistViewStateToRoute(this.currentViewState());
@@ -361052,6 +361586,7 @@ var script$20 = {
361052
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
361053
361587
  */
361054
361588
  onRemoveActiveFilter(chip) {
361589
+ this.viewChanged = true;
361055
361590
  if (chip && chip.searchTerm === true) {
361056
361591
  this.onSearchEvent('');
361057
361592
  return
@@ -362020,6 +362555,7 @@ var script$20 = {
362020
362555
  * @return {void}
362021
362556
  */
362022
362557
  onColumnsEvent(columns) {
362558
+ this.viewChanged = true;
362023
362559
  if (this.isSelfFetchMode && this.list.visibleColumns) {
362024
362560
  this.list.visibleColumns.value = columns;
362025
362561
  }
@@ -362067,6 +362603,7 @@ var script$20 = {
362067
362603
  * @return {void}
362068
362604
  */
362069
362605
  onColumnsReorder(columns) {
362606
+ this.viewChanged = true;
362070
362607
  this.onColumnsEvent(columns);
362071
362608
  },
362072
362609
 
@@ -362077,6 +362614,7 @@ var script$20 = {
362077
362614
  * @return {void}
362078
362615
  */
362079
362616
  onPinChange(count) {
362617
+ this.viewChanged = true;
362080
362618
  if (!this.personalColumns || Array.isArray(this.appliedViewColumns)) {
362081
362619
  return
362082
362620
  }
@@ -362980,6 +363518,10 @@ var script$20 = {
362980
363518
  loadMyTeam().then(() => this.onApplySavedView(view, { keepSavedViewKey }));
362981
363519
  return
362982
363520
  }
363521
+ // An opened view is unchanged until the reader changes it.
363522
+ this.$nextTick(() => {
363523
+ this.viewChanged = false;
363524
+ });
362983
363525
  const ctx = typeof this.selfFetchTokenCtx === 'function' ? this.selfFetchTokenCtx() : {};
362984
363526
  state.filters = dropOptionalUnresolved(resolveFilterTokens(state.filters, ctx));
362985
363527
  this.appliedViewFiltersKey = this.viewFiltersKey(state.filters);
@@ -363105,6 +363647,7 @@ var script$20 = {
363105
363647
  }
363106
363648
  this.savedViews = [...this.savedViews, view];
363107
363649
  this.showSaveViewDialog = false;
363650
+ this.viewChanged = false;
363108
363651
  this.sidebarActiveTab = 'filters-tab';
363109
363652
  this.sidebarOpen = true;
363110
363653
  this.toastSavedView('success', l10n.translate('nextcloud-vue', 'View "{name}" saved', { name }));
@@ -435749,7 +436292,13 @@ var script$n = {
435749
436292
  async _fetchMenuItemCount(item) {
435750
436293
  try {
435751
436294
  const { register, schema, filter } = item.count;
435752
- 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, {}));
435753
436302
  const url = router.generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(register)}/${encodeURIComponent(schema)}`);
435754
436303
  const { data } = await axios$1.get(url, { params: { _limit: 1, ...resolved } });
435755
436304
  const total = data?.total;