@conduction/nextcloud-vue 2.135.0 → 2.137.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 (123) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +2 -1
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +65 -50
  5. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  7. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +9 -0
  8. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +4 -3
  10. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  11. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +4 -1
  12. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  13. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +61 -28
  14. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  15. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  16. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +6 -0
  17. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  18. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js +9 -1
  19. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  20. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue.js +1 -1
  21. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  22. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +4 -11
  23. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  24. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +32 -0
  25. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  26. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +34 -26
  27. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  28. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +24 -0
  29. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  30. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +2 -2
  31. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  32. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  33. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  34. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
  35. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  36. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +39 -2
  37. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  38. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -5
  39. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  40. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  41. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +11 -0
  42. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  43. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +10 -2
  44. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  45. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  46. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +18 -0
  47. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  48. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +38 -23
  49. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  50. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
  51. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
  52. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +1 -0
  53. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
  54. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +1 -1
  55. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -1
  56. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -1
  57. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue.js +1 -1
  58. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue2.js +14 -4
  59. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue2.js.map +1 -1
  60. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue3.js.map +1 -1
  61. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue.js +1 -1
  62. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js +10 -0
  63. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js.map +1 -1
  64. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js +29 -13
  65. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js.map +1 -1
  66. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +3 -1
  67. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -1
  68. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue.js +1 -1
  69. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue2.js.map +1 -1
  70. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue3.js.map +1 -1
  71. package/dist/esm/composables/indexSources.js +69 -3
  72. package/dist/esm/composables/indexSources.js.map +1 -1
  73. package/dist/esm/l10n/en.json.js +20 -20
  74. package/dist/esm/l10n/nl.json.js +21 -21
  75. package/dist/esm/library-style.js +1 -1
  76. package/dist/esm/nextcloud-vue.css +580 -324
  77. package/dist/esm/utils/contentHeaderLink.js +7 -1
  78. package/dist/esm/utils/contentHeaderLink.js.map +1 -1
  79. package/dist/esm/utils/savedViewMenu.js +4 -1
  80. package/dist/esm/utils/savedViewMenu.js.map +1 -1
  81. package/dist/esm/utils/savedViewSummary.js +11 -4
  82. package/dist/esm/utils/savedViewSummary.js.map +1 -1
  83. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  84. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  85. package/dist/esm/utils/widgetDispatch.js +3 -1
  86. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  87. package/dist/nextcloud-vue.cjs.js +1159 -812
  88. package/dist/nextcloud-vue.cjs.js.map +2 -2
  89. package/dist/nextcloud-vue.css +580 -324
  90. package/l10n/en.json +20 -20
  91. package/l10n/nl.json +21 -21
  92. package/package.json +1 -1
  93. package/src/components/CnActionDialog/CnActionDialog.vue +45 -4
  94. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +13 -1
  95. package/src/components/CnAppNav/CnAppNav.vue +28 -3
  96. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +15 -0
  97. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +2 -10
  98. package/src/components/CnDataTable/CnDataTable.md +1 -0
  99. package/src/components/CnDataTable/CnDataTable.vue +38 -1
  100. package/src/components/CnDetailPage/CnDetailPage.md +1 -0
  101. package/src/components/CnDetailPage/CnDetailPage.vue +26 -2
  102. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +1 -0
  103. package/src/components/CnIndexPage/CnIndexPage.vue +44 -6
  104. package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -0
  105. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +21 -0
  106. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +38 -2
  107. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +7 -0
  108. package/src/components/CnRetentionWidget/CnRetentionWidget.vue +13 -0
  109. package/src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue +14 -4
  110. package/src/components/CnTimelineWidget/CnTimelineManage.vue +23 -0
  111. package/src/components/CnTimelineWidget/dashboardRegistration.js +3 -1
  112. package/src/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue +7 -0
  113. package/src/composables/indexSources.js +69 -3
  114. package/src/css/look-board-detail.css +57 -1
  115. package/src/css/look-board-index.css +76 -1
  116. package/src/css/look-board.css +51 -4
  117. package/src/css/table.css +5 -0
  118. package/src/schemas/app-manifest-v2.schema.json +4 -2
  119. package/src/utils/contentHeaderLink.js +7 -1
  120. package/src/utils/savedViewMenu.js +4 -1
  121. package/src/utils/savedViewSummary.js +11 -4
  122. package/src/utils/validateManifestV2.compiled.js +2 -2
  123. package/src/utils/widgetDispatch.js +3 -1
@@ -1 +1 @@
1
- {"version":3,"file":"CnRetentionWidget.vue3.js","sources":["../../../../src/components/CnRetentionWidget/CnRetentionWidget.vue?vue&type=template&id=1d53d545&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-retention\" data-testid=\"cn-retention\">\n\t\t<header class=\"cn-retention__head\">\n\t\t\t<div class=\"cn-retention__head-text\">\n\t\t\t\t<h3 class=\"cn-retention__title\">\n\t\t\t\t\t{{ text(content.title) || t('nextcloud-vue', 'Archiving') }}\n\t\t\t\t</h3>\n\t\t\t\t<p class=\"cn-retention__muted\">\n\t\t\t\t\t{{ text(content.subtitle) || t('nextcloud-vue', 'What happens to this record once it is closed') }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<!-- Recompute starts in the header (board DqZaakArchivering); the\n\t\t\t dialog asks for the reason OpenRegister records with it. -->\n\t\t\t<NcButton\n\t\t\t\tv-if=\"content.recompute !== false && !loading && !error\"\n\t\t\t\t:disabled=\"!mayRecompute || busy\"\n\t\t\t\t:title=\"mayRecompute ? undefined : t('nextcloud-vue', 'Recomputing needs the archivist role')\"\n\t\t\t\t:aria-description=\"mayRecompute ? undefined : t('nextcloud-vue', 'Recomputing needs the archivist role')\"\n\t\t\t\t:data-testid=\"mayRecompute ? 'cn-retention-recompute-open' : 'cn-retention-recompute-denied'\"\n\t\t\t\t@click=\"recomputeOpen = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Refresh :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Recompute nomination') }}\n\t\t\t</NcButton>\n\t\t</header>\n\t\t<CnRetentionRecomputeDialog\n\t\t\tv-if=\"recomputeOpen\"\n\t\t\t:user=\"currentUid\"\n\t\t\t:busy=\"busy\"\n\t\t\t@close=\"recomputeOpen = false\"\n\t\t\t@confirm=\"recompute\" />\n\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\t\t<NcEmptyContent v-else-if=\"error\"\n\t\t\t:name=\"t('nextcloud-vue', 'The archival facts are unavailable')\"\n\t\t\t:description=\"error\"\n\t\t\tdata-testid=\"cn-retention-error\">\n\t\t\t<template #action>\n\t\t\t\t<NcButton data-testid=\"cn-retention-retry\" @click=\"load\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Try again') }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<template v-else>\n\t\t\t<p v-if=\"archived\" class=\"cn-retention__archived\" data-testid=\"cn-retention-archived\">\n\t\t\t\t{{ t('nextcloud-vue', 'Archived on {at} by {by}. {reason}', { at: String(archived.at ?? ''), by: String(archived.by ?? ''), reason: String(archived.reason ?? '') }) }}\n\t\t\t</p>\n\t\t\t<NcNoteCard v-if=\"disagrees\" type=\"warning\" data-testid=\"cn-retention-disagreement\">\n\t\t\t\t{{ t('nextcloud-vue', 'This record and OpenRegister do not agree on whether it is archived.') }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<NcNoteCard v-if=\"nomination && nomination.status === 'unnominatable'\" type=\"warning\" data-testid=\"cn-retention-unnominatable\">\n\t\t\t\t{{ t('nextcloud-vue', 'Nothing could decide what happens to this record.') }}\n\t\t\t\t{{ String(nomination.unnominatableReason ?? '') }}\n\t\t\t</NcNoteCard>\n\t\t\t<div v-else-if=\"!nomination\" data-testid=\"cn-retention-pending\">\n\t\t\t\t<dl class=\"cn-retention__facts\">\n\t\t\t\t\t<div v-for=\"fact in pendingFacts\" :key=\"fact.key\" class=\"cn-retention__fact\">\n\t\t\t\t\t\t<dt>{{ fact.label }}</dt>\n\t\t\t\t\t\t<dd>{{ fact.value }}</dd>\n\t\t\t\t\t</div>\n\t\t\t\t</dl>\n\t\t\t\t<NcNoteCard type=\"info\">\n\t\t\t\t\t{{ text(content.pendingNote) || t('nextcloud-vue', 'This record has not been closed yet. Its archival future is recorded when it closes.') }}\n\t\t\t\t</NcNoteCard>\n\t\t\t</div>\n\t\t\t<dl v-else class=\"cn-retention__facts\" data-testid=\"cn-retention-facts\">\n\t\t\t\t<div v-for=\"fact in facts\" :key=\"fact.key\" class=\"cn-retention__fact\">\n\t\t\t\t\t<dt>{{ fact.label }}</dt>\n\t\t\t\t\t<dd :data-testid=\"`cn-retention-${fact.key}`\">\n\t\t\t\t\t\t{{ fact.value }}\n\t\t\t\t\t</dd>\n\t\t\t\t</div>\n\t\t\t</dl>\n\n\t\t\t<section v-if=\"content.legalHold\" class=\"cn-retention__section\" data-testid=\"cn-retention-hold\">\n\t\t\t\t<component :is=\"innerHeadingTag\" class=\"cn-inner-heading\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Kept for a procedure') }}\n\t\t\t\t</component>\n\t\t\t\t<ul class=\"cn-retention__rows\">\n\t\t\t\t\t<li v-for=\"hold in holds\" :key=\"hold.id\" class=\"cn-retention__row\">\n\t\t\t\t\t\t<span class=\"cn-retention__row-text\">\n\t\t\t\t\t\t\t<span class=\"cn-retention__row-title\">{{ hold.reason || t('nextcloud-vue', 'Legal hold') }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-retention__muted\">{{ t('nextcloud-vue', 'Placed on {date} by {by}', { date: hold.placedDate, by: hold.placedBy }) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<CnStatusBadge :label=\"t('nextcloud-vue', 'Active')\" variant=\"warning\" />\n\t\t\t\t\t</li>\n\t\t\t\t\t<li v-if=\"holds.length === 0\" class=\"cn-retention__row\">\n\t\t\t\t\t\t<span class=\"cn-retention__row-text\">\n\t\t\t\t\t\t\t<span class=\"cn-retention__row-title\">{{ t('nextcloud-vue', 'No legal hold') }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-retention__muted\">{{ t('nextcloud-vue', 'If an objection or appeal runs, destruction is suspended') }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<CnStatusBadge :label=\"t('nextcloud-vue', 'None')\" />\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<section v-if=\"content.transfer\" class=\"cn-retention__section\" data-testid=\"cn-retention-transfer\">\n\t\t\t\t<component :is=\"innerHeadingTag\" class=\"cn-inner-heading\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Transfer to the e-Depot') }}\n\t\t\t\t</component>\n\t\t\t\t<ul class=\"cn-retention__rows\">\n\t\t\t\t\t<li class=\"cn-retention__row\">\n\t\t\t\t\t\t<span class=\"cn-retention__row-text\">\n\t\t\t\t\t\t\t<span class=\"cn-retention__row-title\">{{ transfers ? t('nextcloud-vue', 'Goes to the e-Depot') : t('nextcloud-vue', 'Not applicable') }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-retention__muted\">{{ transfers ? t('nextcloud-vue', 'After the retention period the record is transferred') : t('nextcloud-vue', 'Only records appraised to keep go to the e-Depot') }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<CnStatusBadge :label=\"transfers ? t('nextcloud-vue', 'Transfer') : t('nextcloud-vue', 'No transfer')\" :variant=\"transfers ? 'info' : 'default'\" />\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<div v-if=\"outcome\" class=\"cn-retention__outcome\" data-testid=\"cn-retention-outcome\">\n\t\t\t\t<component :is=\"innerHeadingTag\" class=\"cn-inner-heading\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Outcome') }}\n\t\t\t\t</component>\n\t\t\t\t<p>{{ t('nextcloud-vue', 'Handed over as {kind} on {at} by {by}.', { kind: String(outcome.kind ?? ''), at: String(outcome.at ?? ''), by: String(outcome.by ?? '') }) }}</p>\n\t\t\t\t<p v-if=\"outcome.transferListUuid\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Transfer list') }}: {{ outcome.transferListUuid }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcEmptyContent, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport Refresh from 'vue-material-design-icons/Refresh.vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport CnRetentionRecomputeDialog from './CnRetentionRecomputeDialog.vue'\nimport innerHeading from '../../mixins/innerHeading.js'\nimport { parseDuration } from '../../utils/isoDuration.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\n\n/**\n * CnRetentionWidget — the `retention` detail widget: what happens to a\n * record when its business use ends, as OpenRegister decided it. Every value\n * is read off the record's `@self._retention` (appraisal, disposal date,\n * retention period, selection list row, the rule and when it was written)\n * and `@self.archived`; the widget derives nothing, so it can never disagree\n * with the stored decision. A failed read is an error with a retry, never an\n * empty block. A record nothing could nominate is drawn apart from one that\n * is not closed yet. An archivist (or an admin) may ask OpenRegister to\n * recompute the nomination, with a reason. Any app on OpenRegister uses it;\n * dossiq's case Archiving tab is the first.\n *\n * `content`:\n * - `title`, `subtitle`: texts, through the host translate.\n * - `statusField`, `archivedValues`: a record field and the values that mean\n * archived (`archiveStatus`, `[\"archived\", ...]`); when set, a mismatch with\n * `@self.archived` is said out loud.\n * - `archivistGroups`: groups that may recompute (default `[\"archivaris\"]`;\n * admins always may); `recompute: false` hides the control.\n * - `expected`: before the record closes, the outcome its type expects.\n * `source: { register, schema, filter }` lists the candidate rules (a case\n * type's result types, `filter` tokens such as `@object.caseType`\n * resolved against the record); `fields: { appraisal, period,\n * selectionListRow }` names their fields; `appraisalLabels` names the\n * appraisal values; `periodText` writes the period (`{period} after the\n * decision`); `decidedBy` says who decides. When the candidates agree the\n * facts show that outcome, otherwise \"Depends on the result\". A failed\n * read keeps the plain facts.\n * - `pendingNote`: the note under the facts of a record not closed yet.\n * - `legalHold: true`: a section with the record's active legal holds\n * (`@self._retention.legalHold.holds`), or that none applies.\n * - `transfer: { keepValues }`: a section saying whether the record goes to\n * the e-Depot, from the stored or expected appraisal.\n *\n * The record is read fresh from OpenRegister by `register`, `schema` and\n * `objectId` (props, else the bound record's `@self`).\n */\nexport default {\n\tname: 'CnRetentionWidget',\n\n\tcomponents: { CnRetentionRecomputeDialog, CnStatusBadge, NcButton, NcEmptyContent, NcLoadingIcon, NcNoteCard, Refresh },\n\n\tmixins: [innerHeading],\n\n\tinject: {\n\t\t/** The host app's translate (CnAppRoot `cnTranslate`). */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's configuration (see the component docs).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * The bound record, on a detail page.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The record's register (else the bound record's `@self.register`). */\n\t\tregister: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The record's schema (else the bound record's `@self.schema`); a schema object gives its slug.\n\t\t *\n\t\t * @type {string|number|object}\n\t\t */\n\t\tschema: {\n\t\t\ttype: [String, Number, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The record's id (else the bound record's). */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: true,\n\t\t\terror: '',\n\t\t\trecord: null,\n\t\t\t/** Whether the recompute dialog is open. */\n\t\t\trecomputeOpen: false,\n\t\t\tbusy: false,\n\t\t\t/** The candidate rules `content.expected` reads, null until read or when unreadable. */\n\t\t\texpectedRows: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {object} The bound record's `@self`. */\n\t\tboundSelf() {\n\t\t\treturn (this.objectData && this.objectData['@self']) || {}\n\t\t},\n\n\t\t/** @return {string} The record id. */\n\t\tid() {\n\t\t\treturn String(this.objectId || (this.objectData && this.objectData.id) || this.boundSelf.id || '')\n\t\t},\n\n\t\t/** @return {string} The register. */\n\t\tregisterRef() {\n\t\t\treturn String(this.register || this.boundSelf.register || '')\n\t\t},\n\n\t\t/** @return {string} The schema. */\n\t\tschemaRef() {\n\t\t\tconst s = this.schema || this.boundSelf.schema || ''\n\t\t\treturn String(typeof s === 'object' ? (s.slug || s.id || '') : s)\n\t\t},\n\n\t\t/** @return {object} The record's `@self`. */\n\t\tself() {\n\t\t\treturn (this.record && this.record['@self']) || {}\n\t\t},\n\n\t\t/** @return {object|null} OpenRegister's stored retention decision. */\n\t\tretention() {\n\t\t\treturn this.self._retention || null\n\t\t},\n\n\t\t/** @return {object|null} The nomination. */\n\t\tnomination() {\n\t\t\treturn (this.retention && this.retention.nomination) || null\n\t\t},\n\n\t\t/** @return {object|null} The hand-over outcome. */\n\t\toutcome() {\n\t\t\treturn (this.retention && this.retention.outcome) || null\n\t\t},\n\n\t\t/** @return {object|null} OpenRegister's archived marker. */\n\t\tarchived() {\n\t\t\treturn this.self.archived || null\n\t\t},\n\n\t\t/**\n\t\t * Whether the record's own field and the platform marker disagree.\n\t\t *\n\t\t * @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention\n\t\t * @return {boolean}\n\t\t */\n\t\tdisagrees() {\n\t\t\tconst field = this.content.statusField\n\t\t\tif (!field || !this.record) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst values = Array.isArray(this.content.archivedValues) ? this.content.archivedValues.map(String) : ['archived']\n\t\t\tconst saysArchived = values.includes(String(readPath(this.record, field) ?? ''))\n\t\t\treturn saysArchived !== Boolean(this.archived)\n\t\t},\n\n\t\t/**\n\t\t * The stored facts that have a value, in reading order.\n\t\t *\n\t\t * @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention\n\t\t * @return {Array<{key: string, label: string, value: string}>}\n\t\t */\n\t\tfacts() {\n\t\t\tconst r = this.retention || {}\n\t\t\tconst n = this.nomination || {}\n\t\t\treturn [\n\t\t\t\t{ key: 'appraisal', label: t('nextcloud-vue', 'Appraisal'), value: r.appraisal },\n\t\t\t\t{ key: 'disposal-date', label: t('nextcloud-vue', 'Disposal date'), value: r.disposalDate },\n\t\t\t\t{ key: 'retention-period', label: t('nextcloud-vue', 'Retention period'), value: r.retentionPeriod },\n\t\t\t\t{ key: 'selection-list-row', label: t('nextcloud-vue', 'Selection list row'), value: r.selectionListRow },\n\t\t\t\t{ key: 'rule', label: t('nextcloud-vue', 'Decided by'), value: n.rule },\n\t\t\t\t{ key: 'decided-at', label: t('nextcloud-vue', 'Written on'), value: n.at },\n\t\t\t].filter((fact) => fact.value !== undefined && fact.value !== null && fact.value !== '').map((fact) => ({ ...fact, value: String(fact.value) }))\n\t\t},\n\n\t\t/**\n\t\t * The outcome the record's type expects before it closes: the agreed\n\t\t * appraisal, period and selection list row of the candidate rules, or\n\t\t * `mixed` when they disagree; null when nothing was read.\n\t\t *\n\t\t * @spec openspec/changes/retention-expected-outcome/specs/retention-widget/spec.md#requirement-an-open-record-shows-the-outcome-its-type-expects\n\t\t * @return {{appraisal: string, period: string, selectionListRow: string, mixed: boolean}|null}\n\t\t */\n\t\texpectedOutcome() {\n\t\t\tconst rows = this.expectedRows\n\t\t\tif (!Array.isArray(rows) || rows.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst fields = { appraisal: 'appraisal', period: 'period', selectionListRow: 'selectionListRow', ...(this.content.expected?.fields || {}) }\n\t\t\tconst pick = (row, key) => String(readPath(row, fields[key]) ?? '')\n\t\t\tconst outcomes = rows.map((row) => ({ appraisal: pick(row, 'appraisal'), period: pick(row, 'period'), selectionListRow: pick(row, 'selectionListRow') }))\n\t\t\tconst first = outcomes[0]\n\t\t\tconst agree = outcomes.every((o) => o.appraisal === first.appraisal && o.period === first.period)\n\t\t\tif (first.appraisal === '' && first.period === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn agree\n\t\t\t\t? { ...first, selectionListRow: outcomes.every((o) => o.selectionListRow === first.selectionListRow) ? first.selectionListRow : '', mixed: false }\n\t\t\t\t: { appraisal: '', period: '', selectionListRow: '', mixed: true }\n\t\t},\n\n\t\t/**\n\t\t * The facts of a record not closed yet: the expected outcome where the\n\t\t * type gives one, \"decided when it closes\" otherwise.\n\t\t *\n\t\t * @spec openspec/changes/retention-expected-outcome/specs/retention-widget/spec.md#requirement-an-open-record-shows-the-outcome-its-type-expects\n\t\t * @return {Array<{key: string, label: string, value: string}>}\n\t\t */\n\t\tpendingFacts() {\n\t\t\tconst atClosure = t('nextcloud-vue', 'Decided when it closes')\n\t\t\tconst expected = this.expectedOutcome\n\t\t\tif (!expected) {\n\t\t\t\treturn [\n\t\t\t\t\t{ key: 'appraisal', label: t('nextcloud-vue', 'Appraisal'), value: atClosure },\n\t\t\t\t\t{ key: 'retention-period', label: t('nextcloud-vue', 'Retention period'), value: atClosure },\n\t\t\t\t\t{ key: 'disposal-date', label: t('nextcloud-vue', 'Disposal date'), value: atClosure },\n\t\t\t\t\t{ key: 'decided-at', label: t('nextcloud-vue', 'Written on'), value: t('nextcloud-vue', 'Not yet') },\n\t\t\t\t]\n\t\t\t}\n\t\t\tconst depends = t('nextcloud-vue', 'Depends on the result')\n\t\t\tconst config = this.content.expected || {}\n\t\t\tconst facts = [\n\t\t\t\t{ key: 'appraisal', label: t('nextcloud-vue', 'Appraisal'), value: expected.mixed ? depends : this.appraisalLabel(expected.appraisal) },\n\t\t\t\t{ key: 'retention-period', label: t('nextcloud-vue', 'Retention period'), value: expected.mixed ? depends : this.periodLabel(expected.period) },\n\t\t\t]\n\t\t\tif (expected.selectionListRow) {\n\t\t\t\tfacts.push({ key: 'selection-list-row', label: t('nextcloud-vue', 'Selection list row'), value: expected.selectionListRow })\n\t\t\t}\n\t\t\tfacts.push({ key: 'disposal-date', label: t('nextcloud-vue', 'Disposal date'), value: atClosure })\n\t\t\tif (config.decidedBy) {\n\t\t\t\tfacts.push({ key: 'rule', label: t('nextcloud-vue', 'Decided by'), value: this.text(config.decidedBy) })\n\t\t\t}\n\t\t\tfacts.push({ key: 'decided-at', label: t('nextcloud-vue', 'Written on'), value: t('nextcloud-vue', 'Not yet') })\n\t\t\treturn facts.filter((fact) => fact.value !== '')\n\t\t},\n\n\t\t/** @return {Array<{id: string, reason: string, placedBy: string, placedDate: string}>} The active legal holds. */\n\t\tholds() {\n\t\t\tconst hold = (this.retention && this.retention.legalHold) || {}\n\t\t\tconst list = Array.isArray(hold.holds) ? hold.holds : []\n\t\t\treturn list.map((h, index) => ({ id: String(h.id ?? index), reason: String(h.reason ?? ''), placedBy: String(h.placedBy ?? ''), placedDate: String(h.placedDate ?? '') }))\n\t\t},\n\n\t\t/**\n\t\t * Whether the record goes to the e-Depot: its stored appraisal, else\n\t\t * the expected one, is one of `transfer.keepValues`.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\ttransfers() {\n\t\t\tconst keep = Array.isArray(this.content.transfer?.keepValues) ? this.content.transfer.keepValues.map(String) : ['bewaren', 'blijvend_bewaren', 'retain', 'keep']\n\t\t\tconst stored = this.retention && this.retention.appraisal\n\t\t\tconst appraisal = stored || (this.expectedOutcome && !this.expectedOutcome.mixed ? this.expectedOutcome.appraisal : '')\n\t\t\treturn keep.includes(String(appraisal || ''))\n\t\t},\n\n\t\t/** @return {string} The signed-in user's id. */\n\t\tcurrentUid() {\n\t\t\tconst user = getCurrentUser()\n\t\t\treturn String((user && user.uid) || '')\n\t\t},\n\n\t\t/**\n\t\t * Whether this user may recompute: an admin or an archivist group member.\n\t\t * Shown disabled otherwise, so the user learns which role it needs.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tmayRecompute() {\n\t\t\tconst user = getCurrentUser()\n\t\t\tif (user && user.isAdmin === true) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst groups = Array.isArray(user && user.groups) ? user.groups : []\n\t\t\tconst allowed = Array.isArray(this.content.archivistGroups) ? this.content.archivistGroups : ['archivaris']\n\t\t\treturn allowed.some((g) => groups.includes(g))\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * A config text through the host translate.\n\t\t *\n\t\t * @param {unknown} value The text.\n\t\t * @return {string}\n\t\t */\n\t\ttext(value) {\n\t\t\treturn typeof value === 'string' && value !== '' ? this.cnTranslate(value) : ''\n\t\t},\n\n\t\t/**\n\t\t * An appraisal value in words, through `expected.appraisalLabels`.\n\t\t *\n\t\t * @param {string} value The stored value.\n\t\t * @return {string}\n\t\t */\n\t\tappraisalLabel(value) {\n\t\t\tconst labels = this.content.expected?.appraisalLabels || {}\n\t\t\treturn labels[value] ? this.text(labels[value]) : value\n\t\t},\n\n\t\t/**\n\t\t * An ISO period in words (\"5 years\"), placed in `expected.periodText`.\n\t\t *\n\t\t * @param {string} value The ISO 8601 duration.\n\t\t * @return {string}\n\t\t */\n\t\tperiodLabel(value) {\n\t\t\tconst parsed = parseDuration(value)\n\t\t\tconst words = parsed\n\t\t\t\t? {\n\t\t\t\t\t\tyears: () => n('nextcloud-vue', '%n year', '%n years', parsed.amount),\n\t\t\t\t\t\tmonths: () => n('nextcloud-vue', '%n month', '%n months', parsed.amount),\n\t\t\t\t\t\tweeks: () => n('nextcloud-vue', '%n week', '%n weeks', parsed.amount),\n\t\t\t\t\t\tdays: () => n('nextcloud-vue', '%n day', '%n days', parsed.amount),\n\t\t\t\t\t}[parsed.unit]\n\t\t\t\t: null\n\t\t\tconst period = words ? words() : value\n\t\t\tconst template = this.content.expected?.periodText\n\t\t\treturn template ? this.text(template).replace('{period}', period) : period\n\t\t},\n\n\t\t/**\n\t\t * Read the record fresh from OpenRegister; a failure is an error, never an empty block.\n\t\t *\n\t\t * @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tif (!this.registerRef || !this.schemaRef || !this.id) {\n\t\t\t\t\tthrow new Error(t('nextcloud-vue', 'This widget has no record to read.'))\n\t\t\t\t}\n\t\t\t\tconst { data } = await axios.get(generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(this.registerRef)}/${encodeURIComponent(this.schemaRef)}/${encodeURIComponent(this.id)}`))\n\t\t\t\tthis.record = data && typeof data === 'object' ? data : null\n\t\t\t\tawait this.loadExpected()\n\t\t\t} catch (failure) {\n\t\t\t\tconst body = failure && failure.response && failure.response.data\n\t\t\t\tthis.error = String((body && (body.message || body.error)) || (failure && failure.message) || failure)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read the candidate rules `content.expected` names, for a record that\n\t\t * is not nominated yet. A failure leaves the plain pending facts.\n\t\t *\n\t\t * @spec openspec/changes/retention-expected-outcome/specs/retention-widget/spec.md#requirement-an-open-record-shows-the-outcome-its-type-expects\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadExpected() {\n\t\t\tconst source = this.content.expected && this.content.expected.source\n\t\t\tthis.expectedRows = null\n\t\t\tif (!source || !source.register || !source.schema || this.nomination) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst filter = resolveFilterTokens(source.filter || {}, { objectId: this.id, object: this.record })\n\t\t\tif (hasUnresolvedTokens(filter)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { data } = await axios.get(generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(source.register)}/${encodeURIComponent(source.schema)}`), { params: { ...filter, _limit: 100 } })\n\t\t\t\tthis.expectedRows = Array.isArray(data?.results) ? data.results : []\n\t\t\t} catch {\n\t\t\t\tthis.expectedRows = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Ask OpenRegister to derive the nomination again with the reason the\n\t\t * dialog asked for, then read it back.\n\t\t *\n\t\t * @spec openspec/changes/retention-recompute-header/specs/retention-recompute-header/spec.md#requirement-recomputing-the-nomination-starts-in-the-card-header-and-asks-for-a-reason\n\t\t * @param {string} reason Why it is recomputed.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync recompute(reason) {\n\t\t\tconst why = String(reason ?? '').trim()\n\t\t\tif (this.busy || why === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.busy = true\n\t\t\ttry {\n\t\t\t\tawait axios.post(generateUrl(`/apps/openregister/api/archival/objects/${encodeURIComponent(this.id)}/nomination/recompute`), { reason: why })\n\t\t\t\tthis.recomputeOpen = false\n\t\t\t\tawait this.load()\n\t\t\t} catch (failure) {\n\t\t\t\tconst body = failure && failure.response && failure.response.data\n\t\t\t\tthis.error = String((body && (body.message || body.error)) || (failure && failure.message) || failure)\n\t\t\t} finally {\n\t\t\t\tthis.busy = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-retention {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n\n.cn-retention__head {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-start;\n\tjustify-content: space-between;\n\tgap: 10px 16px;\n}\n\n.cn-retention__head-text {\n\tdisplay: flex;\n\tflex: 1 1 260px;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-retention__title {\n\tmargin: 0;\n\tfont-size: 18px;\n\tfont-weight: 700;\n}\n\n.cn-retention__muted {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-retention__facts {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n\tgap: 12px 24px;\n\tmargin: 0;\n}\n\n.cn-retention__fact dt {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-retention__fact dd {\n\tmargin: 0;\n\tfont-weight: 600;\n}\n\n.cn-retention__section {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-retention__rows {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-retention__row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tpadding: 10px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-retention__row-text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-retention__row-title {\n\tfont-weight: 600;\n}\n\n</style>\n"],"names":["_createElementVNode","_toDisplayString","_createVNode","_createTextVNode","_createBlock","_createElementBlock","_Fragment","_renderList","_resolveDynamicComponent","_openBlock"],"mappings":"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"}
1
+ {"version":3,"file":"CnRetentionWidget.vue3.js","sources":["../../../../src/components/CnRetentionWidget/CnRetentionWidget.vue?vue&type=template&id=5415dbb4&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-retention\" data-testid=\"cn-retention\">\n\t\t<header class=\"cn-retention__head\">\n\t\t\t<div class=\"cn-retention__head-text\">\n\t\t\t\t<h3 class=\"cn-retention__title\">\n\t\t\t\t\t{{ text(content.title) || t('nextcloud-vue', 'Archiving') }}\n\t\t\t\t</h3>\n\t\t\t\t<p class=\"cn-retention__muted\">\n\t\t\t\t\t{{ text(content.subtitle) || t('nextcloud-vue', 'What happens to this record once it is closed') }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<!-- Recompute starts in the header (board DqZaakArchivering); the\n\t\t\t dialog asks for the reason OpenRegister records with it. -->\n\t\t\t<NcButton\n\t\t\t\tv-if=\"content.recompute !== false && !loading && !error\"\n\t\t\t\t:disabled=\"!mayRecompute || busy\"\n\t\t\t\t:title=\"mayRecompute ? undefined : t('nextcloud-vue', 'Recomputing needs the archivist role')\"\n\t\t\t\t:aria-description=\"mayRecompute ? undefined : t('nextcloud-vue', 'Recomputing needs the archivist role')\"\n\t\t\t\t:data-testid=\"mayRecompute ? 'cn-retention-recompute-open' : 'cn-retention-recompute-denied'\"\n\t\t\t\t@click=\"recomputeOpen = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Refresh :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Recompute nomination') }}\n\t\t\t</NcButton>\n\t\t</header>\n\t\t<CnRetentionRecomputeDialog\n\t\t\tv-if=\"recomputeOpen\"\n\t\t\t:user=\"currentUid\"\n\t\t\t:busy=\"busy\"\n\t\t\t@close=\"recomputeOpen = false\"\n\t\t\t@confirm=\"recompute\" />\n\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\t\t<NcEmptyContent v-else-if=\"error\"\n\t\t\t:name=\"t('nextcloud-vue', 'The archival facts are unavailable')\"\n\t\t\t:description=\"error\"\n\t\t\tdata-testid=\"cn-retention-error\">\n\t\t\t<template #action>\n\t\t\t\t<NcButton data-testid=\"cn-retention-retry\" @click=\"load\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Try again') }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<template v-else>\n\t\t\t<p v-if=\"archived\" class=\"cn-retention__archived\" data-testid=\"cn-retention-archived\">\n\t\t\t\t{{ t('nextcloud-vue', 'Archived on {at} by {by}. {reason}', { at: String(archived.at ?? ''), by: String(archived.by ?? ''), reason: String(archived.reason ?? '') }) }}\n\t\t\t</p>\n\t\t\t<NcNoteCard v-if=\"disagrees\" type=\"warning\" data-testid=\"cn-retention-disagreement\">\n\t\t\t\t{{ t('nextcloud-vue', 'This record and OpenRegister do not agree on whether it is archived.') }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<NcNoteCard v-if=\"nomination && nomination.status === 'unnominatable'\" type=\"warning\" data-testid=\"cn-retention-unnominatable\">\n\t\t\t\t{{ t('nextcloud-vue', 'Nothing could decide what happens to this record.') }}\n\t\t\t\t{{ String(nomination.unnominatableReason ?? '') }}\n\t\t\t</NcNoteCard>\n\t\t\t<div v-else-if=\"!nomination\" data-testid=\"cn-retention-pending\">\n\t\t\t\t<dl class=\"cn-retention__facts\">\n\t\t\t\t\t<div v-for=\"fact in pendingFacts\" :key=\"fact.key\" class=\"cn-retention__fact\">\n\t\t\t\t\t\t<dt>{{ fact.label }}</dt>\n\t\t\t\t\t\t<dd>{{ fact.value }}</dd>\n\t\t\t\t\t</div>\n\t\t\t\t</dl>\n\t\t\t\t<NcNoteCard type=\"info\">\n\t\t\t\t\t{{ text(content.pendingNote) || t('nextcloud-vue', 'This record has not been closed yet. Its archival future is recorded when it closes.') }}\n\t\t\t\t</NcNoteCard>\n\t\t\t</div>\n\t\t\t<dl v-else class=\"cn-retention__facts\" data-testid=\"cn-retention-facts\">\n\t\t\t\t<div v-for=\"fact in facts\" :key=\"fact.key\" class=\"cn-retention__fact\">\n\t\t\t\t\t<dt>{{ fact.label }}</dt>\n\t\t\t\t\t<dd :data-testid=\"`cn-retention-${fact.key}`\">\n\t\t\t\t\t\t{{ fact.value }}\n\t\t\t\t\t</dd>\n\t\t\t\t</div>\n\t\t\t</dl>\n\n\t\t\t<section v-if=\"content.legalHold\" class=\"cn-retention__section\" data-testid=\"cn-retention-hold\">\n\t\t\t\t<component :is=\"innerHeadingTag\" class=\"cn-inner-heading\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Kept for a procedure') }}\n\t\t\t\t</component>\n\t\t\t\t<ul class=\"cn-retention__rows\">\n\t\t\t\t\t<li v-for=\"hold in holds\" :key=\"hold.id\" class=\"cn-retention__row\">\n\t\t\t\t\t\t<span class=\"cn-retention__row-text\">\n\t\t\t\t\t\t\t<span class=\"cn-retention__row-title\">{{ hold.reason || t('nextcloud-vue', 'Legal hold') }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-retention__muted\">{{ t('nextcloud-vue', 'Placed on {date} by {by}', { date: hold.placedDate, by: hold.placedBy }) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<CnStatusBadge :label=\"t('nextcloud-vue', 'Active')\" variant=\"warning\" />\n\t\t\t\t\t</li>\n\t\t\t\t\t<li v-if=\"holds.length === 0\" class=\"cn-retention__row\">\n\t\t\t\t\t\t<span class=\"cn-retention__row-text\">\n\t\t\t\t\t\t\t<span class=\"cn-retention__row-title\">{{ t('nextcloud-vue', 'No legal hold') }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-retention__muted\">{{ t('nextcloud-vue', 'If an objection or appeal runs, destruction is suspended') }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<CnStatusBadge :label=\"t('nextcloud-vue', 'None')\" />\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<section v-if=\"content.transfer\" class=\"cn-retention__section\" data-testid=\"cn-retention-transfer\">\n\t\t\t\t<component :is=\"innerHeadingTag\" class=\"cn-inner-heading\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Transfer to the e-Depot') }}\n\t\t\t\t</component>\n\t\t\t\t<ul class=\"cn-retention__rows\">\n\t\t\t\t\t<li class=\"cn-retention__row\">\n\t\t\t\t\t\t<span class=\"cn-retention__row-text\">\n\t\t\t\t\t\t\t<span class=\"cn-retention__row-title\">{{ transfers ? t('nextcloud-vue', 'Goes to the e-Depot') : t('nextcloud-vue', 'Not applicable') }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-retention__muted\">{{ transfers ? t('nextcloud-vue', 'After the retention period the record is transferred') : t('nextcloud-vue', 'Only records appraised to keep go to the e-Depot') }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<CnStatusBadge :label=\"transfers ? t('nextcloud-vue', 'Transfer') : t('nextcloud-vue', 'No transfer')\" :variant=\"transfers ? 'info' : 'default'\" />\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<div v-if=\"outcome\" class=\"cn-retention__outcome\" data-testid=\"cn-retention-outcome\">\n\t\t\t\t<component :is=\"innerHeadingTag\" class=\"cn-inner-heading\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Outcome') }}\n\t\t\t\t</component>\n\t\t\t\t<p>{{ t('nextcloud-vue', 'Handed over as {kind} on {at} by {by}.', { kind: String(outcome.kind ?? ''), at: String(outcome.at ?? ''), by: String(outcome.by ?? '') }) }}</p>\n\t\t\t\t<p v-if=\"outcome.transferListUuid\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Transfer list') }}: {{ outcome.transferListUuid }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcEmptyContent, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport Refresh from 'vue-material-design-icons/Refresh.vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport CnRetentionRecomputeDialog from './CnRetentionRecomputeDialog.vue'\nimport innerHeading from '../../mixins/innerHeading.js'\nimport { parseDuration } from '../../utils/isoDuration.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\n\n/**\n * CnRetentionWidget — the `retention` detail widget: what happens to a\n * record when its business use ends, as OpenRegister decided it. Every value\n * is read off the record's `@self._retention` (appraisal, disposal date,\n * retention period, selection list row, the rule and when it was written)\n * and `@self.archived`; the widget derives nothing, so it can never disagree\n * with the stored decision. A failed read is an error with a retry, never an\n * empty block. A record nothing could nominate is drawn apart from one that\n * is not closed yet. An archivist (or an admin) may ask OpenRegister to\n * recompute the nomination, with a reason. Any app on OpenRegister uses it;\n * dossiq's case Archiving tab is the first.\n *\n * `content`:\n * - `title`, `subtitle`: texts, through the host translate.\n * - `statusField`, `archivedValues`: a record field and the values that mean\n * archived (`archiveStatus`, `[\"archived\", ...]`); when set, a mismatch with\n * `@self.archived` is said out loud.\n * - `archivistGroups`: groups that may recompute (default `[\"archivaris\"]`;\n * admins always may); `recompute: false` hides the control.\n * - `expected`: before the record closes, the outcome its type expects.\n * `source: { register, schema, filter }` lists the candidate rules (a case\n * type's result types, `filter` tokens such as `@object.caseType`\n * resolved against the record); `fields: { appraisal, period,\n * selectionListRow }` names their fields; `appraisalLabels` names the\n * appraisal values; `periodText` writes the period (`{period} after the\n * decision`); `decidedBy` says who decides. When the candidates agree the\n * facts show that outcome, otherwise \"Depends on the result\". A failed\n * read keeps the plain facts.\n * - `pendingNote`: the note under the facts of a record not closed yet.\n * - `legalHold: true`: a section with the record's active legal holds\n * (`@self._retention.legalHold.holds`), or that none applies.\n * - `transfer: { keepValues }`: a section saying whether the record goes to\n * the e-Depot, from the stored or expected appraisal.\n *\n * The record is read fresh from OpenRegister by `register`, `schema` and\n * `objectId` (props, else the bound record's `@self`).\n */\nexport default {\n\tname: 'CnRetentionWidget',\n\n\tcomponents: { CnRetentionRecomputeDialog, CnStatusBadge, NcButton, NcEmptyContent, NcLoadingIcon, NcNoteCard, Refresh },\n\n\tmixins: [innerHeading],\n\n\tinject: {\n\t\t/** The host app's translate (CnAppRoot `cnTranslate`). */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's configuration (see the component docs).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * The bound record, on a detail page.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The record's register (else the bound record's `@self.register`). */\n\t\tregister: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The record's schema (else the bound record's `@self.schema`); a schema object gives its slug.\n\t\t *\n\t\t * @type {string|number|object}\n\t\t */\n\t\tschema: {\n\t\t\ttype: [String, Number, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The record's id (else the bound record's). */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: true,\n\t\t\terror: '',\n\t\t\trecord: null,\n\t\t\t/** Whether the recompute dialog is open. */\n\t\t\trecomputeOpen: false,\n\t\t\tbusy: false,\n\t\t\t/** The candidate rules `content.expected` reads, null until read or when unreadable. */\n\t\t\texpectedRows: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {object} The bound record's `@self`. */\n\t\tboundSelf() {\n\t\t\treturn (this.objectData && this.objectData['@self']) || {}\n\t\t},\n\n\t\t/** @return {string} The record id. */\n\t\tid() {\n\t\t\treturn String(this.objectId || (this.objectData && this.objectData.id) || this.boundSelf.id || '')\n\t\t},\n\n\t\t/** @return {string} The register. */\n\t\tregisterRef() {\n\t\t\treturn String(this.register || this.boundSelf.register || '')\n\t\t},\n\n\t\t/** @return {string} The schema. */\n\t\tschemaRef() {\n\t\t\tconst s = this.schema || this.boundSelf.schema || ''\n\t\t\treturn String(typeof s === 'object' ? (s.slug || s.id || '') : s)\n\t\t},\n\n\t\t/** @return {object} The record's `@self`. */\n\t\tself() {\n\t\t\treturn (this.record && this.record['@self']) || {}\n\t\t},\n\n\t\t/** @return {object|null} OpenRegister's stored retention decision. */\n\t\tretention() {\n\t\t\treturn this.self._retention || null\n\t\t},\n\n\t\t/** @return {object|null} The nomination. */\n\t\tnomination() {\n\t\t\treturn (this.retention && this.retention.nomination) || null\n\t\t},\n\n\t\t/** @return {object|null} The hand-over outcome. */\n\t\toutcome() {\n\t\t\treturn (this.retention && this.retention.outcome) || null\n\t\t},\n\n\t\t/** @return {object|null} OpenRegister's archived marker. */\n\t\tarchived() {\n\t\t\treturn this.self.archived || null\n\t\t},\n\n\t\t/**\n\t\t * Whether the record's own field and the platform marker disagree.\n\t\t *\n\t\t * @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention\n\t\t * @return {boolean}\n\t\t */\n\t\tdisagrees() {\n\t\t\tconst field = this.content.statusField\n\t\t\tif (!field || !this.record) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst values = Array.isArray(this.content.archivedValues) ? this.content.archivedValues.map(String) : ['archived']\n\t\t\tconst saysArchived = values.includes(String(readPath(this.record, field) ?? ''))\n\t\t\treturn saysArchived !== Boolean(this.archived)\n\t\t},\n\n\t\t/**\n\t\t * The stored facts that have a value, in reading order.\n\t\t *\n\t\t * @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention\n\t\t * @return {Array<{key: string, label: string, value: string}>}\n\t\t */\n\t\tfacts() {\n\t\t\tconst r = this.retention || {}\n\t\t\tconst n = this.nomination || {}\n\t\t\treturn [\n\t\t\t\t{ key: 'appraisal', label: t('nextcloud-vue', 'Appraisal'), value: r.appraisal },\n\t\t\t\t{ key: 'disposal-date', label: t('nextcloud-vue', 'Disposal date'), value: r.disposalDate },\n\t\t\t\t{ key: 'retention-period', label: t('nextcloud-vue', 'Retention period'), value: r.retentionPeriod },\n\t\t\t\t{ key: 'selection-list-row', label: t('nextcloud-vue', 'Selection list row'), value: r.selectionListRow },\n\t\t\t\t{ key: 'rule', label: t('nextcloud-vue', 'Decided by'), value: n.rule },\n\t\t\t\t{ key: 'decided-at', label: t('nextcloud-vue', 'Written on'), value: n.at },\n\t\t\t].filter((fact) => fact.value !== undefined && fact.value !== null && fact.value !== '').map((fact) => ({ ...fact, value: String(fact.value) }))\n\t\t},\n\n\t\t/**\n\t\t * The outcome the record's type expects before it closes: the agreed\n\t\t * appraisal, period and selection list row of the candidate rules, or\n\t\t * `mixed` when they disagree; null when nothing was read.\n\t\t *\n\t\t * @spec openspec/changes/retention-expected-outcome/specs/retention-widget/spec.md#requirement-an-open-record-shows-the-outcome-its-type-expects\n\t\t * @return {{appraisal: string, period: string, selectionListRow: string, mixed: boolean}|null}\n\t\t */\n\t\texpectedOutcome() {\n\t\t\tconst rows = this.expectedRows\n\t\t\tif (!Array.isArray(rows) || rows.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst fields = { appraisal: 'appraisal', period: 'period', selectionListRow: 'selectionListRow', ...(this.content.expected?.fields || {}) }\n\t\t\tconst pick = (row, key) => String(readPath(row, fields[key]) ?? '')\n\t\t\tconst outcomes = rows.map((row) => ({ appraisal: pick(row, 'appraisal'), period: pick(row, 'period'), selectionListRow: pick(row, 'selectionListRow') }))\n\t\t\tconst first = outcomes[0]\n\t\t\tconst agree = outcomes.every((o) => o.appraisal === first.appraisal && o.period === first.period)\n\t\t\tif (first.appraisal === '' && first.period === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn agree\n\t\t\t\t? { ...first, selectionListRow: outcomes.every((o) => o.selectionListRow === first.selectionListRow) ? first.selectionListRow : '', mixed: false }\n\t\t\t\t: { appraisal: '', period: '', selectionListRow: '', mixed: true }\n\t\t},\n\n\t\t/**\n\t\t * The facts of a record not closed yet: the expected outcome where the\n\t\t * type gives one, \"decided when it closes\" otherwise.\n\t\t *\n\t\t * @spec openspec/changes/retention-expected-outcome/specs/retention-widget/spec.md#requirement-an-open-record-shows-the-outcome-its-type-expects\n\t\t * @return {Array<{key: string, label: string, value: string}>}\n\t\t */\n\t\tpendingFacts() {\n\t\t\tconst atClosure = t('nextcloud-vue', 'Decided when it closes')\n\t\t\tconst expected = this.expectedOutcome\n\t\t\tif (!expected) {\n\t\t\t\treturn [\n\t\t\t\t\t{ key: 'appraisal', label: t('nextcloud-vue', 'Appraisal'), value: atClosure },\n\t\t\t\t\t{ key: 'retention-period', label: t('nextcloud-vue', 'Retention period'), value: atClosure },\n\t\t\t\t\t{ key: 'disposal-date', label: t('nextcloud-vue', 'Disposal date'), value: atClosure },\n\t\t\t\t\t{ key: 'decided-at', label: t('nextcloud-vue', 'Written on'), value: t('nextcloud-vue', 'Not yet') },\n\t\t\t\t]\n\t\t\t}\n\t\t\tconst depends = t('nextcloud-vue', 'Depends on the result')\n\t\t\tconst config = this.content.expected || {}\n\t\t\tconst facts = [\n\t\t\t\t{ key: 'appraisal', label: t('nextcloud-vue', 'Appraisal'), value: expected.mixed ? depends : this.appraisalLabel(expected.appraisal) },\n\t\t\t\t{ key: 'retention-period', label: t('nextcloud-vue', 'Retention period'), value: expected.mixed ? depends : this.periodLabel(expected.period) },\n\t\t\t]\n\t\t\tif (expected.selectionListRow) {\n\t\t\t\tfacts.push({ key: 'selection-list-row', label: t('nextcloud-vue', 'Selection list row'), value: expected.selectionListRow })\n\t\t\t}\n\t\t\tfacts.push({ key: 'disposal-date', label: t('nextcloud-vue', 'Disposal date'), value: atClosure })\n\t\t\tif (config.decidedBy) {\n\t\t\t\tfacts.push({ key: 'rule', label: t('nextcloud-vue', 'Decided by'), value: this.text(config.decidedBy) })\n\t\t\t}\n\t\t\tfacts.push({ key: 'decided-at', label: t('nextcloud-vue', 'Written on'), value: t('nextcloud-vue', 'Not yet') })\n\t\t\treturn facts.filter((fact) => fact.value !== '')\n\t\t},\n\n\t\t/** @return {Array<{id: string, reason: string, placedBy: string, placedDate: string}>} The active legal holds. */\n\t\tholds() {\n\t\t\tconst hold = (this.retention && this.retention.legalHold) || {}\n\t\t\tconst list = Array.isArray(hold.holds) ? hold.holds : []\n\t\t\treturn list.map((h, index) => ({ id: String(h.id ?? index), reason: String(h.reason ?? ''), placedBy: String(h.placedBy ?? ''), placedDate: String(h.placedDate ?? '') }))\n\t\t},\n\n\t\t/**\n\t\t * Whether the record goes to the e-Depot: its stored appraisal, else\n\t\t * the expected one, is one of `transfer.keepValues`.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\ttransfers() {\n\t\t\tconst keep = Array.isArray(this.content.transfer?.keepValues) ? this.content.transfer.keepValues.map(String) : ['bewaren', 'blijvend_bewaren', 'retain', 'keep']\n\t\t\tconst stored = this.retention && this.retention.appraisal\n\t\t\tconst appraisal = stored || (this.expectedOutcome && !this.expectedOutcome.mixed ? this.expectedOutcome.appraisal : '')\n\t\t\treturn keep.includes(String(appraisal || ''))\n\t\t},\n\n\t\t/** @return {string} The signed-in user's id. */\n\t\tcurrentUid() {\n\t\t\tconst user = getCurrentUser()\n\t\t\treturn String((user && user.uid) || '')\n\t\t},\n\n\t\t/**\n\t\t * Whether this user may recompute: an admin or an archivist group member.\n\t\t * Shown disabled otherwise, so the user learns which role it needs.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tmayRecompute() {\n\t\t\tconst user = getCurrentUser()\n\t\t\tif (user && user.isAdmin === true) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst groups = Array.isArray(user && user.groups) ? user.groups : []\n\t\t\tconst allowed = Array.isArray(this.content.archivistGroups) ? this.content.archivistGroups : ['archivaris']\n\t\t\treturn allowed.some((g) => groups.includes(g))\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * A config text through the host translate.\n\t\t *\n\t\t * @param {unknown} value The text.\n\t\t * @return {string}\n\t\t */\n\t\ttext(value) {\n\t\t\treturn typeof value === 'string' && value !== '' ? this.cnTranslate(value) : ''\n\t\t},\n\n\t\t/**\n\t\t * An appraisal value in words, through `expected.appraisalLabels`.\n\t\t *\n\t\t * @param {string} value The stored value.\n\t\t * @return {string}\n\t\t */\n\t\tappraisalLabel(value) {\n\t\t\tconst labels = this.content.expected?.appraisalLabels || {}\n\t\t\treturn labels[value] ? this.text(labels[value]) : value\n\t\t},\n\n\t\t/**\n\t\t * An ISO period in words (\"5 years\"), placed in `expected.periodText`.\n\t\t *\n\t\t * @param {string} value The ISO 8601 duration.\n\t\t * @return {string}\n\t\t */\n\t\tperiodLabel(value) {\n\t\t\tconst parsed = parseDuration(value)\n\t\t\tconst words = parsed\n\t\t\t\t? {\n\t\t\t\t\t\tyears: () => n('nextcloud-vue', '%n year', '%n years', parsed.amount),\n\t\t\t\t\t\tmonths: () => n('nextcloud-vue', '%n month', '%n months', parsed.amount),\n\t\t\t\t\t\tweeks: () => n('nextcloud-vue', '%n week', '%n weeks', parsed.amount),\n\t\t\t\t\t\tdays: () => n('nextcloud-vue', '%n day', '%n days', parsed.amount),\n\t\t\t\t\t}[parsed.unit]\n\t\t\t\t: null\n\t\t\tconst period = words ? words() : value\n\t\t\tconst template = this.content.expected?.periodText\n\t\t\treturn template ? this.text(template).replace('{period}', period) : period\n\t\t},\n\n\t\t/**\n\t\t * Read the record fresh from OpenRegister; a failure is an error, never an empty block.\n\t\t *\n\t\t * @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tif (!this.registerRef || !this.schemaRef || !this.id) {\n\t\t\t\t\tthrow new Error(t('nextcloud-vue', 'This widget has no record to read.'))\n\t\t\t\t}\n\t\t\t\tconst { data } = await axios.get(generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(this.registerRef)}/${encodeURIComponent(this.schemaRef)}/${encodeURIComponent(this.id)}`))\n\t\t\t\tthis.record = data && typeof data === 'object' ? data : null\n\t\t\t\tawait this.loadExpected()\n\t\t\t} catch (failure) {\n\t\t\t\tconst body = failure && failure.response && failure.response.data\n\t\t\t\tthis.error = String((body && (body.message || body.error)) || (failure && failure.message) || failure)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read the candidate rules `content.expected` names, for a record that\n\t\t * is not nominated yet. A failure leaves the plain pending facts.\n\t\t *\n\t\t * @spec openspec/changes/retention-expected-outcome/specs/retention-widget/spec.md#requirement-an-open-record-shows-the-outcome-its-type-expects\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadExpected() {\n\t\t\tconst source = this.content.expected && this.content.expected.source\n\t\t\tthis.expectedRows = null\n\t\t\tif (!source || !source.register || !source.schema || this.nomination) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst filter = resolveFilterTokens(source.filter || {}, { objectId: this.id, object: this.record })\n\t\t\tif (hasUnresolvedTokens(filter)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { data } = await axios.get(generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(source.register)}/${encodeURIComponent(source.schema)}`), { params: { ...filter, _limit: 100 } })\n\t\t\t\tthis.expectedRows = Array.isArray(data?.results) ? data.results : []\n\t\t\t} catch {\n\t\t\t\tthis.expectedRows = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Ask OpenRegister to derive the nomination again with the reason the\n\t\t * dialog asked for, then read it back.\n\t\t *\n\t\t * @spec openspec/changes/retention-recompute-header/specs/retention-recompute-header/spec.md#requirement-recomputing-the-nomination-starts-in-the-card-header-and-asks-for-a-reason\n\t\t * @param {string} reason Why it is recomputed.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync recompute(reason) {\n\t\t\tconst why = String(reason ?? '').trim()\n\t\t\tif (this.busy || why === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.busy = true\n\t\t\ttry {\n\t\t\t\tawait axios.post(generateUrl(`/apps/openregister/api/archival/objects/${encodeURIComponent(this.id)}/nomination/recompute`), { reason: why })\n\t\t\t\tthis.recomputeOpen = false\n\t\t\t\tawait this.load()\n\t\t\t} catch (failure) {\n\t\t\t\tconst body = failure && failure.response && failure.response.data\n\t\t\t\tthis.error = String((body && (body.message || body.error)) || (failure && failure.message) || failure)\n\t\t\t} finally {\n\t\t\t\tthis.busy = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-retention {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n\n.cn-retention__head {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-start;\n\tjustify-content: space-between;\n\tgap: 10px 16px;\n}\n\n.cn-retention__head-text {\n\tdisplay: flex;\n\tflex: 1 1 260px;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-retention__title {\n\tmargin: 0;\n\tfont-size: 18px;\n\tfont-weight: 700;\n}\n\n.cn-retention__muted {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-retention__facts {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n\tgap: 12px 24px;\n\tmargin: 0;\n}\n\n/* The server draws every dt as a 130px right-aligned inline block and pads\n dt and dd 12px; a fact is a label over its value. */\n.cn-retention__fact dt,\n.cn-retention__fact dd {\n\tdisplay: block;\n\twidth: auto;\n\twhite-space: normal;\n\ttext-align: start;\n}\n\n.cn-retention__fact dt {\n\tmargin: 0;\n\tpadding: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-retention__fact dd {\n\tmargin: 0;\n\tpadding: 0;\n\tfont-weight: 600;\n}\n\n.cn-retention__section {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-retention__rows {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-retention__row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tpadding: 10px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-retention__row-text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-retention__row-title {\n\tfont-weight: 600;\n}\n\n</style>\n"],"names":["_createElementVNode","_toDisplayString","_createVNode","_createTextVNode","_createBlock","_createElementBlock","_Fragment","_renderList","_resolveDynamicComponent","_openBlock"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnSavedViewMenuSettings.vue2.js';
2
2
  import { render } from './CnSavedViewMenuSettings.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-71fee42c";
5
+ script.__scopeId = "data-v-dfcd17c4";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -67,13 +67,23 @@ function loadSchema(register, schema) {
67
67
  /**
68
68
  * The names of referenced records, each asked once.
69
69
  *
70
- * @param {{register: string, schema: string, labelField: string, ids: Array<string>}} ref The reference filter.
70
+ * @param {{register: string, schema: string, labelField: string, matchField?: string, ids: Array<string>}} ref The reference filter; with `matchField` the ids are that field's values.
71
71
  * @return {Promise<{[id: string]: string}>}
72
72
  */
73
73
  async function loadLabels(ref) {
74
- const prefix = `${ref.register}|${ref.schema}|${ref.labelField}|`;
74
+ const prefix = `${ref.register}|${ref.schema}|${ref.labelField}|${ref.matchField || ''}|`;
75
75
  const missing = ref.ids.filter((id) => !labelCache.has(prefix + id));
76
- if (missing.length > 0) {
76
+ // A value that identifies a record by another field: one read per value.
77
+ if (missing.length > 0 && ref.matchField) {
78
+ for (const value of missing) {
79
+ labelCache.set(prefix + value, getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { [ref.matchField]: value, _limit: 1 })
80
+ .then((data) => {
81
+ const row = Array.isArray(data && data.results) ? data.results[0] : null;
82
+ return row ? pickRefLabel(row, ref.labelField) : null
83
+ })
84
+ .catch(() => null));
85
+ }
86
+ } else if (missing.length > 0) {
77
87
  const request = getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { _ids: missing.join(','), _limit: missing.length })
78
88
  .then((data) => {
79
89
  const byId = {};
@@ -346,7 +356,7 @@ var script = {
346
356
  const fields = fieldsFromSchema(schema, { includeReadOnly: true });
347
357
  const refs = this.candidates
348
358
  .filter((c) => c.page === page.id)
349
- .flatMap((c) => viewFilterReferences(c.state, fields));
359
+ .flatMap((c) => viewFilterReferences(c.state, fields, page.filterLabels));
350
360
  for (const ref of refs) {
351
361
  const labels = await loadLabels({ ...ref, register: ref.register || page.register });
352
362
  if (Object.keys(labels).length === 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"CnSavedViewMenuSettings.vue2.js","sources":["../../../../src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-saved-view-menu-settings\" data-testid=\"cn-saved-view-menu-settings\">\n\t\t<p class=\"cn-saved-view-menu-settings__intro\">\n\t\t\t{{ t('nextcloud-vue', 'These saved views are at the bottom of your menu under My views, in this order. Drag a row or use the arrow keys. You put a view in the menu with Add to menu in the side panel of a list.') }}\n\t\t</p>\n\t\t<CnOrderedListField\n\t\t\t:modelValue=\"values\"\n\t\t\t:options=\"options\"\n\t\t\t:label=\"t('nextcloud-vue', 'Views in my menu')\"\n\t\t\t:addLabel=\"t('nextcloud-vue', 'Add view')\"\n\t\t\t:addPlaceholder=\"t('nextcloud-vue', 'Pick a saved view')\"\n\t\t\t:addHint=\"t('nextcloud-vue', 'You see your own views, the views shared with you and the default views. A new view goes to the bottom.')\"\n\t\t\t:removeLabel=\"t('nextcloud-vue', 'Remove from menu')\"\n\t\t\t:emptyText=\"t('nextcloud-vue', 'Your menu has no views yet.')\"\n\t\t\t:loading=\"loading\"\n\t\t\t:disabled=\"!menu\"\n\t\t\t@update:modelValue=\"onChange\" />\n\t</div>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport CnOrderedListField from '../CnOrderedListField/CnOrderedListField.vue'\nimport { useSavedViewsApi } from '../../composables/useSavedViewsApi.js'\nimport { countLine } from '../../utils/countLine.js'\nimport { facetFilterParams } from '../../utils/facets.js'\nimport { fetchFilterCounts } from '../../utils/fetchFilterCounts.js'\nimport { pickRefLabel } from '../../utils/headerFieldChips.js'\nimport { buildHeaders, buildQueryString, prefixUrl } from '../../utils/headers.js'\nimport { resolveFilterMap } from '../../utils/routeFilters.js'\nimport { defaultFilterViews, extractViewState, pageOffersSavedViews, viewMatchesScope } from '../../utils/savedViewHelpers.js'\nimport { normalizeSavedViewMenu } from '../../utils/savedViewMenu.js'\nimport { summarizeViewFilters, viewFilterReferences } from '../../utils/savedViewSummary.js'\nimport { fieldsFromSchema } from '../../utils/schema.js'\n\n// Per session: a schema, a referenced record's name and a view's count are\n// asked once, however often the dialog opens.\nconst schemaCache = new Map()\nconst labelCache = new Map()\nconst countCache = new Map()\n\n/**\n * The key a menu entry or a candidate view is listed under.\n *\n * @param {string} page The page's route name.\n * @param {string} id The view id.\n * @return {string}\n */\nfunction keyOf(page, id) {\n\treturn `${page}|${id}`\n}\n\n/**\n * GET a JSON document from OpenRegister.\n *\n * @param {string} path The app-absolute path.\n * @param {object} [params] Query parameters.\n * @return {Promise<object>}\n */\nasync function getJson(path, params = {}) {\n\tconst response = await fetch(prefixUrl(path + buildQueryString(params)), { headers: buildHeaders() })\n\tif (!response.ok) {\n\t\tthrow new Error(`${path} returned ${response.status}`)\n\t}\n\treturn response.json()\n}\n\n/**\n * A page's schema, asked once.\n *\n * @param {string} register The register slug.\n * @param {string} schema The schema slug.\n * @return {Promise<object|null>}\n */\nfunction loadSchema(register, schema) {\n\tconst key = `${register}|${schema}`\n\tif (!schemaCache.has(key)) {\n\t\t// A failed read is forgotten, so the next opening asks again instead\n\t\t// of naming every filter by its raw key for the rest of the session.\n\t\tschemaCache.set(key, getJson(`/apps/openregister/api/schemas/${encodeURIComponent(schema)}`, { register }).catch(() => {\n\t\t\tschemaCache.delete(key)\n\t\t\treturn null\n\t\t}))\n\t}\n\treturn schemaCache.get(key)\n}\n\n/**\n * The names of referenced records, each asked once.\n *\n * @param {{register: string, schema: string, labelField: string, ids: Array<string>}} ref The reference filter.\n * @return {Promise<{[id: string]: string}>}\n */\nasync function loadLabels(ref) {\n\tconst prefix = `${ref.register}|${ref.schema}|${ref.labelField}|`\n\tconst missing = ref.ids.filter((id) => !labelCache.has(prefix + id))\n\tif (missing.length > 0) {\n\t\tconst request = getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { _ids: missing.join(','), _limit: missing.length })\n\t\t\t.then((data) => {\n\t\t\t\tconst byId = {}\n\t\t\t\tfor (const row of (Array.isArray(data && data.results) ? data.results : [])) {\n\t\t\t\t\tconst id = row && (row.id ?? row.uuid ?? (row['@self'] && row['@self'].id))\n\t\t\t\t\tif (id !== undefined && id !== null) {\n\t\t\t\t\t\tbyId[String(id)] = pickRefLabel(row, ref.labelField)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn byId\n\t\t\t})\n\t\t\t.catch(() => ({}))\n\t\tfor (const id of missing) {\n\t\t\tlabelCache.set(prefix + id, request.then((byId) => byId[id] || null))\n\t\t}\n\t}\n\tconst out = {}\n\tawait Promise.all(ref.ids.map(async (id) => {\n\t\tconst label = await labelCache.get(prefix + id)\n\t\tif (label) {\n\t\t\tout[id] = label\n\t\t}\n\t}))\n\treturn out\n}\n\n/**\n * How many records a view's filters match on its page, asked once per view.\n *\n * @param {{register: string, schema: string, baseFilter: object}} page The page.\n * @param {object} filter The view's filters as query parameters.\n * @return {Promise<number|null>}\n */\nfunction loadCount(page, filter) {\n\tconst key = JSON.stringify([page.register, page.schema, page.baseFilter, filter])\n\tif (!countCache.has(key)) {\n\t\tcountCache.set(key, fetchFilterCounts({ register: page.register, schema: page.schema, entries: [{ key: 'view', filter }], baseFilter: page.baseFilter })\n\t\t\t.then((counts) => (typeof counts.view === 'number' ? counts.view : null))\n\t\t\t.catch(() => null))\n\t}\n\treturn countCache.get(key)\n}\n\n/**\n * A view's filters as the list request sends them.\n *\n * @param {{filters: object, search: string}} state The view's state.\n * @return {object}\n */\nfunction filterParams(state) {\n\tconst out = {}\n\tfor (const [key, raw] of Object.entries(state.filters || {})) {\n\t\tObject.assign(out, facetFilterParams(key, Array.isArray(raw) ? raw : [raw]))\n\t}\n\tif (state.search) {\n\t\tout._search = state.search\n\t}\n\treturn out\n}\n\n/**\n * CnSavedViewMenuSettings - the user's \"My views\" menu entries as a settings\n * section body (board DqPersoonlijkeInstellingen, \"Views in my menu\"): the\n * entries in their menu order, reordered by dragging or the arrow keys,\n * taken out of the menu with \"Remove from menu\", and a picker that adds any\n * view the user can see on a page of the app (their own, shared ones and the\n * pages' default views). Removing an entry never deletes the view.\n *\n * Each row says what its view shows and how many records that is: \"Case\n * type Woo request, status open · 19 cases\". The line is built from the\n * view's filters with the page schema's titles and the names of the records\n * and options they point at; the count is one request per view, cached for\n * the session, and fills in when it arrives without holding up the list.\n *\n * Reads and writes the list CnAppRoot holds (`cnSavedViewMenu`, stored in\n * the `cn_saved_view_menu` preference). CnAppRoot draws it as the `views`\n * section of its user settings dialog; an app with a dialog of its own can\n * mount it inside an NcAppSettingsSection.\n *\n * @spec openspec/changes/saved-view-menu-settings/specs/saved-view-menu-settings/spec.md#requirement-a-user-orders-and-picks-the-views-in-their-menu\n */\nexport default {\n\tname: 'CnSavedViewMenuSettings',\n\tcomponents: { CnOrderedListField },\n\tinject: {\n\t\tcnSavedViewMenu: { default: null },\n\t\tcnManifest: { default: null },\n\t\tcnTranslate: { default: () => (key) => key },\n\t\tcnAppId: { default: '' },\n\t\tcnAppConfig: { default: null },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tviews: [],\n\t\t\tloading: false,\n\t\t\t/** The schema per page id. */\n\t\t\tschemas: {},\n\t\t\t/** Looked-up display names per page id, filter key and value. */\n\t\t\tvalueLabels: {},\n\t\t\t/** The count per list value; undefined while it is asked. */\n\t\t\tcounts: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {object|null} The menu holder, when it can be changed. */\n\t\tmenu() {\n\t\t\tconst menu = this.cnSavedViewMenu\n\t\t\treturn menu && typeof menu.set === 'function' ? menu : null\n\t\t},\n\n\t\t/** @return {Array<object>} The stored entries. */\n\t\tentries() {\n\t\t\treturn normalizeSavedViewMenu(this.cnSavedViewMenu ? this.cnSavedViewMenu.entries : [])\n\t\t},\n\n\t\t/** @return {Array<object>} The pages that offer saved views. */\n\t\tpages() {\n\t\t\tconst pages = this.cnManifest && Array.isArray(this.cnManifest.pages) ? this.cnManifest.pages : []\n\t\t\treturn pages\n\t\t\t\t.filter((page) => page && page.type === 'index' && typeof page.id === 'string' && pageOffersSavedViews(page.config))\n\t\t\t\t.map((page) => {\n\t\t\t\t\tconst config = page.config || {}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: page.id,\n\t\t\t\t\t\ttitle: typeof page.title === 'string' ? this.cnTranslate(page.title) : page.id,\n\t\t\t\t\t\tscope: { scope: config.savedViewsScope, register: config.register, schema: config.schema, source: config.entitySource },\n\t\t\t\t\t\tdefaults: Array.isArray(config.defaultSavedViews) ? config.defaultSavedViews : [],\n\t\t\t\t\t\tregister: typeof config.register === 'string' ? config.register : '',\n\t\t\t\t\t\tschema: typeof config.schema === 'string' ? config.schema : '',\n\t\t\t\t\t\tbaseFilter: resolveFilterMap(config.filter, {}, { config: this.cnAppConfig || {} }),\n\t\t\t\t\t\tcountSubtitle: config.countSubtitle || null,\n\t\t\t\t\t\tfilterLabels: (config.filterLabels && typeof config.filterLabels === 'object') ? config.filterLabels : {},\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/** @return {Array<object>} Every view the user can put in the menu, with its page and state. */\n\t\tcandidates() {\n\t\t\tconst out = []\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst defaults = page.defaults.map((entry) => (entry && typeof entry.name === 'string' ? { ...entry, name: this.cnTranslate(entry.name) } : entry))\n\t\t\t\tconst views = [...defaultFilterViews(defaults, page.scope), ...this.views.filter((view) => viewMatchesScope(view, page.scope))]\n\t\t\t\tfor (const view of views) {\n\t\t\t\t\tconst id = String(view.id ?? view.slug ?? '')\n\t\t\t\t\tif (id === '' || typeof view.name !== 'string' || view.name === '') {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tout.push({ value: keyOf(page.id, id), id, page: page.id, label: view.name, state: extractViewState(view) })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/** @return {Array<string>} The entries as list values, in menu order. */\n\t\tvalues() {\n\t\t\treturn this.entries.map((entry) => keyOf(entry.page, entry.id))\n\t\t},\n\n\t\t/**\n\t\t * The choices: the candidates, and the entries no candidate covers. A\n\t\t * choice's second line says what the view shows; a row in the menu\n\t\t * adds how many records that is, once the count is in.\n\t\t *\n\t\t * @return {Array<{value: string, label: string, description: string}>}\n\t\t */\n\t\toptions() {\n\t\t\tconst byId = new Map(this.pages.map((page) => [page.id, page]))\n\t\t\tconst inMenu = new Set(this.values)\n\t\t\tconst known = new Set(this.candidates.map((c) => c.value))\n\t\t\tconst candidates = this.candidates.map((c) => {\n\t\t\t\tconst summary = this.summaryOf(byId.get(c.page), c.state)\n\t\t\t\tconst count = inMenu.has(c.value) ? this.countText(byId.get(c.page), this.counts[c.value]) : ''\n\t\t\t\treturn { value: c.value, label: c.label, description: count ? `${summary} · ${count}` : summary }\n\t\t\t})\n\t\t\tconst stored = this.entries\n\t\t\t\t.filter((entry) => !known.has(keyOf(entry.page, entry.id)))\n\t\t\t\t.map((entry) => ({ value: keyOf(entry.page, entry.id), label: entry.name, description: byId.has(entry.page) ? byId.get(entry.page).title : '' }))\n\t\t\treturn [...candidates, ...stored]\n\t\t},\n\t},\n\n\twatch: {\n\t\t/** Look up the names a summary needs once the views are in. */\n\t\tcandidates() {\n\t\t\tthis.loadValueLabels()\n\t\t\tthis.loadCounts()\n\t\t},\n\n\t\t/** Count a view as soon as it is in the menu. */\n\t\tvalues() {\n\t\t\tthis.loadCounts()\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchViews()\n\t\tthis.loadSchemas()\n\t\tthis.loadCounts()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/** Read the saved views the user can see; without them the defaults stay. */\n\t\tasync fetchViews() {\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst views = await useSavedViewsApi().fetchViews()\n\t\t\t\tthis.views = Array.isArray(views) ? views : []\n\t\t\t} catch {\n\t\t\t\tthis.views = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A view's filters as one line, else the page's title.\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {{filters: object, search: string}} state The view's state.\n\t\t * @return {string}\n\t\t */\n\t\tsummaryOf(page, state) {\n\t\t\tconst schema = this.schemas[page.id]\n\t\t\tconst summary = summarizeViewFilters(state, {\n\t\t\t\tproperties: (schema && schema.properties) || {},\n\t\t\t\tvalueLabels: this.valueLabels[page.id] || {},\n\t\t\t\tfilterLabels: page.filterLabels,\n\t\t\t\ttranslate: this.cnTranslate,\n\t\t\t})\n\t\t\treturn summary || page.title\n\t\t},\n\n\t\t/**\n\t\t * \"19 cases\": the page's `countSubtitle` for the total, else \"19 results\".\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {number|undefined} total The count, when it is in.\n\t\t * @return {string} The text, or '' while the count is unknown.\n\t\t */\n\t\tcountText(page, total) {\n\t\t\tif (typeof total !== 'number') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst line = page.countSubtitle ? countLine(page.countSubtitle, total, { translate: this.cnTranslate, appId: this.cnAppId }) : ''\n\t\t\treturn line || n('nextcloud-vue', '%n result', '%n results', total)\n\t\t},\n\n\t\t/** Read the schema of every page that offers saved views, for field titles and value names. */\n\t\tasync loadSchemas() {\n\t\t\tawait Promise.all(this.pages.filter((page) => page.register && page.schema).map(async (page) => {\n\t\t\t\tconst schema = await loadSchema(page.register, page.schema)\n\t\t\t\tif (schema) {\n\t\t\t\t\tthis.schemas = { ...this.schemas, [page.id]: schema }\n\t\t\t\t}\n\t\t\t}))\n\t\t\tthis.loadValueLabels()\n\t\t},\n\n\t\t/** Look up the names of the referenced records the views filter on. */\n\t\tasync loadValueLabels() {\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst schema = this.schemas[page.id]\n\t\t\t\tif (!schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst fields = fieldsFromSchema(schema, { includeReadOnly: true })\n\t\t\t\tconst refs = this.candidates\n\t\t\t\t\t.filter((c) => c.page === page.id)\n\t\t\t\t\t.flatMap((c) => viewFilterReferences(c.state, fields))\n\t\t\t\tfor (const ref of refs) {\n\t\t\t\t\tconst labels = await loadLabels({ ...ref, register: ref.register || page.register })\n\t\t\t\t\tif (Object.keys(labels).length === 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst forPage = this.valueLabels[page.id] || {}\n\t\t\t\t\tthis.valueLabels = { ...this.valueLabels, [page.id]: { ...forPage, [ref.key]: { ...(forPage[ref.key] || {}), ...labels } } }\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/** Count every view in the menu; each count fills in on its own. */\n\t\tloadCounts() {\n\t\t\tfor (const value of this.values) {\n\t\t\t\tif (Object.hasOwn(this.counts, value)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst candidate = this.candidates.find((c) => c.value === value)\n\t\t\t\tconst page = candidate && this.pages.find((p) => p.id === candidate.page)\n\t\t\t\tif (!page || !page.register || !page.schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tthis.counts = { ...this.counts, [value]: undefined }\n\t\t\t\tloadCount(page, filterParams(candidate.state)).then((total) => {\n\t\t\t\t\tif (typeof total === 'number') {\n\t\t\t\t\t\tthis.counts = { ...this.counts, [value]: total }\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Store the list in its new order, with entries removed or added.\n\t\t *\n\t\t * @param {Array<string>} values The list values in order.\n\t\t */\n\t\tonChange(values) {\n\t\t\tif (!this.menu) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst byKey = new Map(this.entries.map((entry) => [keyOf(entry.page, entry.id), entry]))\n\t\t\tconst candidates = new Map(this.candidates.map((c) => [c.value, c]))\n\t\t\tconst next = values\n\t\t\t\t.map((value) => {\n\t\t\t\t\tif (byKey.has(value)) {\n\t\t\t\t\t\treturn byKey.get(value)\n\t\t\t\t\t}\n\t\t\t\t\tconst candidate = candidates.get(value)\n\t\t\t\t\treturn candidate ? { id: candidate.id, page: candidate.page, name: candidate.label } : null\n\t\t\t\t})\n\t\t\t\t.filter(Boolean)\n\t\t\tthis.menu.set(next)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-saved-view-menu-settings__intro {\n\tmargin: 0 0 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["CnOrderedListField","t","n"],"mappings":";;;;;;;;;;;;;;AAwCA;AACA;AACA,MAAM,cAAc,IAAI,GAAG,GAAC;AAC5B,MAAM,aAAa,IAAI,GAAG,GAAC;AAC3B,MAAM,aAAa,IAAI,GAAG,GAAC;;AAE3B;;AAEC;AACA;;AAEA;;AAED,SAAS,KAAK,CAAC,IAAI,EAAE,EAAE,EAAE;AACxB,CAAA,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AACtB,CAAA;;AAEA;AACC;AACA;AACA;AACA;;;AAGD,eAAe,OAAO,CAAC,IAAI,EAAE,MAAK,GAAI,EAAE,EAAE;AACzC,CAAA,MAAM,QAAO,GAAI,MAAM,KAAK,CAAC,SAAS,CAAC,IAAG,GAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,IAAI,EAAA;AACpG,CAAA,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE;EACjB,MAAM,IAAI,KAAK,CAAC,CAAC,EAAE,IAAI,CAAA,UAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;AACtD,EAAA;AACA,CAAA,OAAO,QAAQ,CAAC,IAAI,EAAC;AACtB,CAAA;;AAEA;;AAEC;AACA;AACA;;;AAGD,SAAS,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE;AACrC,CAAA,MAAM,GAAI,GAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,EAAA;CAClC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;;;EAG1B,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,+BAA+B,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC,KAAK,CAAC,MAAM;GACtH,WAAW,CAAC,MAAM,CAAC,GAAG,EAAA;AACtB,GAAA,OAAO,IAAG;AACX,GAAC,CAAC,EAAA;AACH,EAAA;CACA,OAAO,WAAW,CAAC,GAAG,CAAC,GAAG,CAAA;AAC3B,CAAA;;AAEA;AACC;AACA;;AAEA;;AAED,eAAe,UAAU,CAAC,GAAG,EAAE;CAC9B,MAAM,MAAO,GAAE,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC,EAAA;CAChE,MAAM,OAAM,GAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,MAAO,GAAE,EAAE,CAAC,EAAA;AACnE,CAAA,IAAI,OAAO,CAAC,MAAO,GAAE,CAAC,EAAE;AACvB,EAAA,MAAM,UAAU,OAAO,CAAC,CAAC,+BAA+B,EAAE,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAA;AAClL,IAAC,IAAI,CAAC,CAAC,IAAI,KAAK;IACf,MAAM,IAAK,GAAE,GAAC;AACd,IAAA,KAAK,MAAM,GAAI,KAAI,KAAK,CAAC,OAAO,CAAC,IAAG,IAAK,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAQ,GAAE,EAAE,GAAG;AAC5E,KAAA,MAAM,EAAC,GAAI,GAAE,KAAM,GAAG,CAAC,EAAG,IAAG,GAAG,CAAC,IAAK,KAAI,GAAG,CAAC,OAAO,CAAA,IAAK,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,EAAA;KAC1E,IAAI,EAAG,KAAI,SAAU,IAAG,EAAG,KAAI,IAAI,EAAE;MACpC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAE,GAAE,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,UAAU,EAAA;AACpD,MAAA;AACD,KAAA;AACA,IAAA,OAAO,IAAG;IACV,CAAA;AACD,IAAC,KAAK,CAAC,OAAO,EAAE,CAAC,EAAA;AAClB,EAAA,KAAK,MAAM,MAAM,OAAO,EAAE;GACzB,UAAU,CAAC,GAAG,CAAC,MAAO,GAAE,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAA,IAAK,IAAI,CAAC,EAAA;AACrE,GAAA;AACD,EAAA;CACA,MAAM,GAAE,GAAI,GAAC;AACb,CAAA,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK;AAC3C,EAAA,MAAM,KAAM,GAAE,MAAM,UAAU,CAAC,GAAG,CAAC,MAAO,GAAE,EAAE,EAAA;AAC9C,EAAA,IAAI,KAAK,EAAE;GACV,GAAG,CAAC,EAAE,IAAI,MAAI;AACf,GAAA;AACD,EAAC,CAAC,EAAA;AACF,CAAA,OAAO,GAAE;AACV,CAAA;;AAEA;;AAEC;AACA;;;;AAID,SAAS,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE;AAChC,CAAA,MAAM,GAAE,GAAI,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,EAAA;CAChF,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AACzB,EAAA,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,iBAAiB,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAK,EAAG,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,YAAY,CAAA;AACtJ,IAAC,IAAI,CAAC,CAAC,MAAM,MAAM,OAAO,MAAM,CAAC,IAAK,KAAI,QAAO,GAAI,MAAM,CAAC,IAAG,GAAI,IAAI,CAAC,CAAA;AACxE,IAAC,KAAK,CAAC,MAAM,IAAI,CAAC,EAAA;AACpB,EAAA;CACA,OAAO,UAAU,CAAC,GAAG,CAAC,GAAG,CAAA;AAC1B,CAAA;;AAEA;AACC;AACA;;AAEA;;AAED,SAAS,YAAY,CAAC,KAAK,EAAE;CAC5B,MAAM,GAAE,GAAI,GAAC;CACb,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAA,IAAK,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAM,IAAK,EAAE,CAAC,EAAE;AAC7D,EAAA,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,iBAAiB,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAA,GAAI,GAAI,GAAE,CAAC,GAAG,CAAC,CAAC,EAAA;AAC5E,EAAA;AACA,CAAA,IAAI,KAAK,CAAC,MAAM,EAAE;EACjB,GAAG,CAAC,OAAM,GAAI,KAAK,CAAC,OAAK;AAC1B,EAAA;AACA,CAAA,OAAO,GAAE;AACV,CAAA;;AAEA;AACC;AACA;;;;;AAKA;;;;;;AAMA;AACA;AACA;;AAEA;AACA;AACA;;AAED,aAAe;AACd,CAAA,IAAI,EAAE,yBAAyB;AAC/B,CAAA,UAAU,EAAE,sBAAEA,YAAoB;AAClC,CAAA,MAAM,EAAE;AACP,EAAA,eAAe,EAAE,EAAE,OAAO,EAAE,IAAG,EAAG;EAClC,UAAU,EAAE,EAAE,OAAO,EAAE,IAAG,EAAG;AAC7B,EAAA,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,KAAK,KAAK;AAC5C,EAAA,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI;EACxB,WAAW,EAAE,EAAE,OAAO,EAAE,IAAG,EAAG;EAC9B;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;AACN,GAAA,KAAK,EAAE,EAAE;GACT,OAAO,EAAE,KAAK;;GAEd,OAAO,EAAE,EAAE;;AAEX,GAAA,WAAW,EAAE,EAAE;;AAEf,GAAA,MAAM,EAAE,EAAE;AACX,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;;AAET,EAAA,IAAI,GAAG;GACN,MAAM,IAAK,GAAE,IAAI,CAAC,gBAAc;GAChC,OAAO,IAAK,IAAG,OAAO,IAAI,CAAC,GAAI,KAAI,UAAS,GAAI,IAAK,GAAE,IAAG;GAC1D;;AAED;AACA,EAAA,OAAO,GAAG;AACT,GAAA,OAAO,sBAAsB,CAAC,IAAI,CAAC,eAAgB,GAAE,IAAI,CAAC,eAAe,CAAC,OAAQ,GAAE,EAAE,CAAA;GACtF;;;AAGD,EAAA,KAAK,GAAG;AACP,GAAA,MAAM,KAAI,GAAI,IAAI,CAAC,UAAS,IAAK,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,KAAI,GAAI,GAAC;GACjG,OAAO,KAAI;KACT,MAAM,CAAC,CAAC,IAAI,KAAK,IAAK,IAAG,IAAI,CAAC,IAAG,KAAM,OAAM,IAAK,OAAO,IAAI,CAAC,EAAG,KAAI,QAAO,IAAK,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;KAClH,GAAG,CAAC,CAAC,IAAI,KAAK;KACd,MAAM,MAAK,GAAI,IAAI,CAAC,MAAK,IAAK,GAAC;AAC/B,KAAA,OAAO;MACN,EAAE,EAAE,IAAI,CAAC,EAAE;AACX,MAAA,KAAK,EAAE,OAAO,IAAI,CAAC,KAAM,KAAI,WAAW,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAA,GAAI,IAAI,CAAC,EAAE;MAC9E,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,eAAe,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,YAAW,EAAG;AACvH,MAAA,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAE,GAAE,MAAM,CAAC,iBAAkB,GAAE,EAAE;MACjF,QAAQ,EAAE,OAAO,MAAM,CAAC,QAAO,KAAM,QAAO,GAAI,MAAM,CAAC,QAAO,GAAI,EAAE;MACpE,MAAM,EAAE,OAAO,MAAM,CAAC,MAAO,KAAI,QAAO,GAAI,MAAM,CAAC,MAAK,GAAI,EAAE;AAC9D,MAAA,UAAU,EAAE,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,eAAe,EAAC,EAAG,CAAC;AACnF,MAAA,aAAa,EAAE,MAAM,CAAC,aAAc,IAAG,IAAI;MAC3C,YAAY,EAAE,CAAC,MAAM,CAAC,gBAAgB,OAAO,MAAM,CAAC,iBAAiB,QAAQ,IAAI,MAAM,CAAC,YAAW,GAAI,EAAE;AAC1G,MAAA;KACA,CAAA;GACF;;AAED;EACA,UAAU,GAAG;GACZ,MAAM,GAAE,GAAI,GAAC;GACb,KAAK,MAAM,IAAG,IAAK,IAAI,CAAC,KAAK,EAAE;AAC9B,IAAA,MAAM,QAAO,GAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,MAAM,KAAI,IAAK,OAAO,KAAK,CAAC,IAAK,KAAI,QAAO,GAAI,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAA,EAAI,GAAE,KAAK,CAAC,EAAA;IAClJ,MAAM,KAAI,GAAI,CAAC,GAAG,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAA;AAC9H,IAAA,KAAK,MAAM,IAAG,IAAK,KAAK,EAAE;AACzB,KAAA,MAAM,EAAG,GAAE,MAAM,CAAC,IAAI,CAAC,EAAG,IAAG,IAAI,CAAC,IAAG,IAAK,EAAE,EAAA;KAC5C,IAAI,EAAG,KAAI,EAAG,IAAG,OAAO,IAAI,CAAC,IAAG,KAAM,QAAS,IAAG,IAAI,CAAC,IAAK,KAAI,EAAE,EAAE;AACnE,MAAA,QAAO;AACR,MAAA;KACA,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,gBAAgB,CAAC,IAAI,CAAA,EAAG,EAAA;AAC3G,KAAA;AACD,IAAA;AACA,GAAA,OAAO,GAAE;GACT;;;AAGD,EAAA,MAAM,GAAG;GACR,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,CAAA;GAC9D;;AAED;;;AAGC;AACA;AACA;;AAED,EAAA,OAAO,GAAG;GACT,MAAM,IAAG,GAAI,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,EAAA;AAC9D,GAAA,MAAM,MAAK,GAAI,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,EAAA;GAClC,MAAM,KAAI,GAAI,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,EAAA;AACzD,GAAA,MAAM,aAAa,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK;IAC7C,MAAM,OAAM,GAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,EAAA;AACxD,IAAA,MAAM,KAAI,GAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA,GAAI,GAAC;AAC9F,IAAA,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,KAAM,GAAE,CAAC,EAAE,OAAO,CAAA,GAAI,EAAE,KAAK,CAAC,CAAA,GAAI,OAAQ,EAAA;IAChG,EAAA;GACD,MAAM,MAAK,GAAI,IAAI,CAAC,OAAM;KACxB,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;AAC1D,KAAC,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAA,GAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAM,GAAE,EAAC,EAAG,CAAC,EAAA;AACjJ,GAAA,OAAO,CAAC,GAAG,UAAU,EAAE,GAAG,MAAM,CAAA;GAChC;EACD;;AAED,CAAA,KAAK,EAAE;;EAEN,UAAU,GAAG;AACZ,GAAA,IAAI,CAAC,eAAe,GAAC;AACrB,GAAA,IAAI,CAAC,UAAU,GAAC;GAChB;;AAED;AACA,EAAA,MAAM,GAAG;AACR,GAAA,IAAI,CAAC,UAAU,GAAC;GAChB;EACD;;AAED,CAAA,OAAO,GAAG;AACT,EAAA,IAAI,CAAC,UAAU,GAAC;AAChB,EAAA,IAAI,CAAC,WAAW,GAAC;AACjB,EAAA,IAAI,CAAC,UAAU,GAAC;EAChB;;AAED,CAAA,OAAO,EAAE;KACRC,SAAC;;AAED;AACA,EAAA,MAAM,UAAU,GAAG;AAClB,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;AAClB,GAAA,IAAI;IACH,MAAM,KAAM,GAAE,MAAM,gBAAgB,EAAE,CAAC,UAAU,GAAC;AAClD,IAAA,IAAI,CAAC,KAAM,GAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAA,GAAI,KAAI,GAAI,GAAC;AAC9C,IAAA,CAAE,MAAM;IACP,IAAI,CAAC,KAAM,GAAE,GAAC;AACf,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;;AAED;AACC;AACA;;;AAGA;;AAED,EAAA,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE;AACtB,GAAA,MAAM,MAAK,GAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAA;AACnC,GAAA,MAAM,UAAU,oBAAoB,CAAC,KAAK,EAAE;AAC3C,IAAA,UAAU,EAAE,CAAC,MAAK,IAAK,MAAM,CAAC,UAAU,KAAK,EAAE;AAC/C,IAAA,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE;AAC5C,IAAA,YAAY,EAAE,IAAI,CAAC,YAAY;IAC/B,SAAS,EAAE,IAAI,CAAC,WAAW;IAC3B,EAAA;GACD,OAAO,WAAW,IAAI,CAAC,KAAI;GAC3B;;AAED;AACC;AACA;;;;;AAKD,EAAA,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE;GACtB,IAAI,OAAO,UAAU,QAAQ,EAAE;AAC9B,IAAA,OAAO,EAAC;AACT,IAAA;GACA,MAAM,IAAG,GAAI,IAAI,CAAC,aAAY,GAAI,SAAS,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,IAAI,CAAC,OAAQ,EAAC,CAAE,GAAE,GAAC;GAChI,OAAO,IAAG,IAAKC,eAAC,CAAC,eAAe,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,CAAA;GAClE;;AAED;AACA,EAAA,MAAM,WAAW,GAAG;AACnB,GAAA,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,QAAS,IAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,OAAO,IAAI,KAAK;IAC/F,MAAM,MAAO,GAAE,MAAM,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAA;IAC1D,IAAI,MAAM,EAAE;KACX,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,MAAO,GAAA;AACrD,KAAA;AACD,IAAC,CAAC,EAAA;AACF,GAAA,IAAI,CAAC,eAAe,GAAC;GACrB;;;EAGD,MAAM,eAAe,GAAG;GACvB,KAAK,MAAM,IAAG,IAAK,IAAI,CAAC,KAAK,EAAE;AAC9B,IAAA,MAAM,MAAK,GAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAA;IACnC,IAAI,CAAC,MAAM,EAAE;AACZ,KAAA,QAAO;AACR,KAAA;IACA,MAAM,MAAO,GAAE,gBAAgB,CAAC,MAAM,EAAE,EAAE,eAAe,EAAE,IAAG,EAAG,EAAA;IACjE,MAAM,IAAK,GAAE,IAAI,CAAC,UAAS;MACzB,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAK,KAAI,IAAI,CAAC,EAAE,CAAA;MAChC,OAAO,CAAC,CAAC,CAAC,KAAK,oBAAoB,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,EAAA;AACtD,IAAA,KAAK,MAAM,OAAO,IAAI,EAAE;AACvB,KAAA,MAAM,MAAO,GAAE,MAAM,UAAU,CAAC,EAAE,GAAG,GAAG,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAO,IAAK,IAAI,CAAC,UAAU,EAAA;AACnF,KAAA,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAO,KAAI,CAAC,EAAE;AACrC,MAAA,QAAO;AACR,MAAA;AACA,KAAA,MAAM,UAAU,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAE,IAAG,GAAC;KAC9C,IAAI,CAAC,WAAY,GAAE,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAA,IAAK,EAAE,CAAC,EAAE,GAAG,MAAO,EAAA,EAAI,GAAA;AAC5H,KAAA;AACD,IAAA;GACA;;;EAGD,UAAU,GAAG;GACZ,KAAK,MAAM,KAAI,IAAK,IAAI,CAAC,MAAM,EAAE;AAChC,IAAA,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE;AACtC,KAAA,QAAO;AACR,KAAA;IACA,MAAM,YAAY,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAM,KAAI,KAAK,EAAA;AAC/D,IAAA,MAAM,IAAG,GAAI,SAAQ,IAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAG,KAAI,SAAS,CAAC,IAAI,EAAA;AACxE,IAAA,IAAI,CAAC,IAAK,IAAG,CAAC,IAAI,CAAC,QAAO,IAAK,CAAC,IAAI,CAAC,MAAM,EAAE;AAC5C,KAAA,QAAO;AACR,KAAA;AACA,IAAA,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,GAAG,SAAU,GAAA;IACnD,SAAS,CAAC,IAAI,EAAE,YAAY,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK;KAC9D,IAAI,OAAO,UAAU,QAAQ,EAAE;AAC9B,MAAA,IAAI,CAAC,MAAK,GAAI,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,GAAG,KAAM,GAAA;AAChD,MAAA;KACA,EAAA;AACF,IAAA;GACA;;AAED;;AAEC;;;AAGD,EAAA,QAAQ,CAAC,MAAM,EAAE;AAChB,GAAA,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;IACf,MAAK;AACN,IAAA;AACA,GAAA,MAAM,KAAM,GAAE,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,EAAA;GACvF,MAAM,UAAW,GAAE,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAA;AACnE,GAAA,MAAM,IAAG,GAAI,MAAK;AACjB,KAAC,GAAG,CAAC,CAAC,KAAK,KAAK;AACf,KAAA,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;AACrB,MAAA,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,CAAA;AACvB,MAAA;AACA,KAAA,MAAM,SAAQ,GAAI,UAAU,CAAC,GAAG,CAAC,KAAK,EAAA;AACtC,KAAA,OAAO,SAAU,GAAE,EAAE,EAAE,EAAE,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,KAAM,EAAA,GAAI,IAAG;KAC1F,CAAA;AACD,KAAC,MAAM,CAAC,OAAO,EAAA;AAChB,GAAA,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAA;GAClB;EACD;AACF;;;;"}
1
+ {"version":3,"file":"CnSavedViewMenuSettings.vue2.js","sources":["../../../../src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-saved-view-menu-settings\" data-testid=\"cn-saved-view-menu-settings\">\n\t\t<p class=\"cn-saved-view-menu-settings__intro\">\n\t\t\t{{ t('nextcloud-vue', 'These saved views are at the bottom of your menu under My views, in this order. Drag a row or use the arrow keys. You put a view in the menu with Add to menu in the side panel of a list.') }}\n\t\t</p>\n\t\t<CnOrderedListField\n\t\t\t:modelValue=\"values\"\n\t\t\t:options=\"options\"\n\t\t\t:label=\"t('nextcloud-vue', 'Views in my menu')\"\n\t\t\t:addLabel=\"t('nextcloud-vue', 'Add view')\"\n\t\t\t:addPlaceholder=\"t('nextcloud-vue', 'Pick a saved view')\"\n\t\t\t:addHint=\"t('nextcloud-vue', 'You see your own views, the views shared with you and the default views. A new view goes to the bottom.')\"\n\t\t\t:removeLabel=\"t('nextcloud-vue', 'Remove from menu')\"\n\t\t\t:emptyText=\"t('nextcloud-vue', 'Your menu has no views yet.')\"\n\t\t\t:loading=\"loading\"\n\t\t\t:disabled=\"!menu\"\n\t\t\t@update:modelValue=\"onChange\" />\n\t</div>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport CnOrderedListField from '../CnOrderedListField/CnOrderedListField.vue'\nimport { useSavedViewsApi } from '../../composables/useSavedViewsApi.js'\nimport { countLine } from '../../utils/countLine.js'\nimport { facetFilterParams } from '../../utils/facets.js'\nimport { fetchFilterCounts } from '../../utils/fetchFilterCounts.js'\nimport { pickRefLabel } from '../../utils/headerFieldChips.js'\nimport { buildHeaders, buildQueryString, prefixUrl } from '../../utils/headers.js'\nimport { resolveFilterMap } from '../../utils/routeFilters.js'\nimport { defaultFilterViews, extractViewState, pageOffersSavedViews, viewMatchesScope } from '../../utils/savedViewHelpers.js'\nimport { normalizeSavedViewMenu } from '../../utils/savedViewMenu.js'\nimport { summarizeViewFilters, viewFilterReferences } from '../../utils/savedViewSummary.js'\nimport { fieldsFromSchema } from '../../utils/schema.js'\n\n// Per session: a schema, a referenced record's name and a view's count are\n// asked once, however often the dialog opens.\nconst schemaCache = new Map()\nconst labelCache = new Map()\nconst countCache = new Map()\n\n/**\n * The key a menu entry or a candidate view is listed under.\n *\n * @param {string} page The page's route name.\n * @param {string} id The view id.\n * @return {string}\n */\nfunction keyOf(page, id) {\n\treturn `${page}|${id}`\n}\n\n/**\n * GET a JSON document from OpenRegister.\n *\n * @param {string} path The app-absolute path.\n * @param {object} [params] Query parameters.\n * @return {Promise<object>}\n */\nasync function getJson(path, params = {}) {\n\tconst response = await fetch(prefixUrl(path + buildQueryString(params)), { headers: buildHeaders() })\n\tif (!response.ok) {\n\t\tthrow new Error(`${path} returned ${response.status}`)\n\t}\n\treturn response.json()\n}\n\n/**\n * A page's schema, asked once.\n *\n * @param {string} register The register slug.\n * @param {string} schema The schema slug.\n * @return {Promise<object|null>}\n */\nfunction loadSchema(register, schema) {\n\tconst key = `${register}|${schema}`\n\tif (!schemaCache.has(key)) {\n\t\t// A failed read is forgotten, so the next opening asks again instead\n\t\t// of naming every filter by its raw key for the rest of the session.\n\t\tschemaCache.set(key, getJson(`/apps/openregister/api/schemas/${encodeURIComponent(schema)}`, { register }).catch(() => {\n\t\t\tschemaCache.delete(key)\n\t\t\treturn null\n\t\t}))\n\t}\n\treturn schemaCache.get(key)\n}\n\n/**\n * The names of referenced records, each asked once.\n *\n * @param {{register: string, schema: string, labelField: string, matchField?: string, ids: Array<string>}} ref The reference filter; with `matchField` the ids are that field's values.\n * @return {Promise<{[id: string]: string}>}\n */\nasync function loadLabels(ref) {\n\tconst prefix = `${ref.register}|${ref.schema}|${ref.labelField}|${ref.matchField || ''}|`\n\tconst missing = ref.ids.filter((id) => !labelCache.has(prefix + id))\n\t// A value that identifies a record by another field: one read per value.\n\tif (missing.length > 0 && ref.matchField) {\n\t\tfor (const value of missing) {\n\t\t\tlabelCache.set(prefix + value, getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { [ref.matchField]: value, _limit: 1 })\n\t\t\t\t.then((data) => {\n\t\t\t\t\tconst row = Array.isArray(data && data.results) ? data.results[0] : null\n\t\t\t\t\treturn row ? pickRefLabel(row, ref.labelField) : null\n\t\t\t\t})\n\t\t\t\t.catch(() => null))\n\t\t}\n\t} else if (missing.length > 0) {\n\t\tconst request = getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { _ids: missing.join(','), _limit: missing.length })\n\t\t\t.then((data) => {\n\t\t\t\tconst byId = {}\n\t\t\t\tfor (const row of (Array.isArray(data && data.results) ? data.results : [])) {\n\t\t\t\t\tconst id = row && (row.id ?? row.uuid ?? (row['@self'] && row['@self'].id))\n\t\t\t\t\tif (id !== undefined && id !== null) {\n\t\t\t\t\t\tbyId[String(id)] = pickRefLabel(row, ref.labelField)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn byId\n\t\t\t})\n\t\t\t.catch(() => ({}))\n\t\tfor (const id of missing) {\n\t\t\tlabelCache.set(prefix + id, request.then((byId) => byId[id] || null))\n\t\t}\n\t}\n\tconst out = {}\n\tawait Promise.all(ref.ids.map(async (id) => {\n\t\tconst label = await labelCache.get(prefix + id)\n\t\tif (label) {\n\t\t\tout[id] = label\n\t\t}\n\t}))\n\treturn out\n}\n\n/**\n * How many records a view's filters match on its page, asked once per view.\n *\n * @param {{register: string, schema: string, baseFilter: object}} page The page.\n * @param {object} filter The view's filters as query parameters.\n * @return {Promise<number|null>}\n */\nfunction loadCount(page, filter) {\n\tconst key = JSON.stringify([page.register, page.schema, page.baseFilter, filter])\n\tif (!countCache.has(key)) {\n\t\tcountCache.set(key, fetchFilterCounts({ register: page.register, schema: page.schema, entries: [{ key: 'view', filter }], baseFilter: page.baseFilter })\n\t\t\t.then((counts) => (typeof counts.view === 'number' ? counts.view : null))\n\t\t\t.catch(() => null))\n\t}\n\treturn countCache.get(key)\n}\n\n/**\n * A view's filters as the list request sends them.\n *\n * @param {{filters: object, search: string}} state The view's state.\n * @return {object}\n */\nfunction filterParams(state) {\n\tconst out = {}\n\tfor (const [key, raw] of Object.entries(state.filters || {})) {\n\t\tObject.assign(out, facetFilterParams(key, Array.isArray(raw) ? raw : [raw]))\n\t}\n\tif (state.search) {\n\t\tout._search = state.search\n\t}\n\treturn out\n}\n\n/**\n * CnSavedViewMenuSettings - the user's \"My views\" menu entries as a settings\n * section body (board DqPersoonlijkeInstellingen, \"Views in my menu\"): the\n * entries in their menu order, reordered by dragging or the arrow keys,\n * taken out of the menu with \"Remove from menu\", and a picker that adds any\n * view the user can see on a page of the app (their own, shared ones and the\n * pages' default views). Removing an entry never deletes the view.\n *\n * Each row says what its view shows and how many records that is: \"Case\n * type Woo request, status open · 19 cases\". The line is built from the\n * view's filters with the page schema's titles and the names of the records\n * and options they point at; the count is one request per view, cached for\n * the session, and fills in when it arrives without holding up the list.\n *\n * Reads and writes the list CnAppRoot holds (`cnSavedViewMenu`, stored in\n * the `cn_saved_view_menu` preference). CnAppRoot draws it as the `views`\n * section of its user settings dialog; an app with a dialog of its own can\n * mount it inside an NcAppSettingsSection.\n *\n * @spec openspec/changes/saved-view-menu-settings/specs/saved-view-menu-settings/spec.md#requirement-a-user-orders-and-picks-the-views-in-their-menu\n */\nexport default {\n\tname: 'CnSavedViewMenuSettings',\n\tcomponents: { CnOrderedListField },\n\tinject: {\n\t\tcnSavedViewMenu: { default: null },\n\t\tcnManifest: { default: null },\n\t\tcnTranslate: { default: () => (key) => key },\n\t\tcnAppId: { default: '' },\n\t\tcnAppConfig: { default: null },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tviews: [],\n\t\t\tloading: false,\n\t\t\t/** The schema per page id. */\n\t\t\tschemas: {},\n\t\t\t/** Looked-up display names per page id, filter key and value. */\n\t\t\tvalueLabels: {},\n\t\t\t/** The count per list value; undefined while it is asked. */\n\t\t\tcounts: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {object|null} The menu holder, when it can be changed. */\n\t\tmenu() {\n\t\t\tconst menu = this.cnSavedViewMenu\n\t\t\treturn menu && typeof menu.set === 'function' ? menu : null\n\t\t},\n\n\t\t/** @return {Array<object>} The stored entries. */\n\t\tentries() {\n\t\t\treturn normalizeSavedViewMenu(this.cnSavedViewMenu ? this.cnSavedViewMenu.entries : [])\n\t\t},\n\n\t\t/** @return {Array<object>} The pages that offer saved views. */\n\t\tpages() {\n\t\t\tconst pages = this.cnManifest && Array.isArray(this.cnManifest.pages) ? this.cnManifest.pages : []\n\t\t\treturn pages\n\t\t\t\t.filter((page) => page && page.type === 'index' && typeof page.id === 'string' && pageOffersSavedViews(page.config))\n\t\t\t\t.map((page) => {\n\t\t\t\t\tconst config = page.config || {}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: page.id,\n\t\t\t\t\t\ttitle: typeof page.title === 'string' ? this.cnTranslate(page.title) : page.id,\n\t\t\t\t\t\tscope: { scope: config.savedViewsScope, register: config.register, schema: config.schema, source: config.entitySource },\n\t\t\t\t\t\tdefaults: Array.isArray(config.defaultSavedViews) ? config.defaultSavedViews : [],\n\t\t\t\t\t\tregister: typeof config.register === 'string' ? config.register : '',\n\t\t\t\t\t\tschema: typeof config.schema === 'string' ? config.schema : '',\n\t\t\t\t\t\tbaseFilter: resolveFilterMap(config.filter, {}, { config: this.cnAppConfig || {} }),\n\t\t\t\t\t\tcountSubtitle: config.countSubtitle || null,\n\t\t\t\t\t\tfilterLabels: (config.filterLabels && typeof config.filterLabels === 'object') ? config.filterLabels : {},\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/** @return {Array<object>} Every view the user can put in the menu, with its page and state. */\n\t\tcandidates() {\n\t\t\tconst out = []\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst defaults = page.defaults.map((entry) => (entry && typeof entry.name === 'string' ? { ...entry, name: this.cnTranslate(entry.name) } : entry))\n\t\t\t\tconst views = [...defaultFilterViews(defaults, page.scope), ...this.views.filter((view) => viewMatchesScope(view, page.scope))]\n\t\t\t\tfor (const view of views) {\n\t\t\t\t\tconst id = String(view.id ?? view.slug ?? '')\n\t\t\t\t\tif (id === '' || typeof view.name !== 'string' || view.name === '') {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tout.push({ value: keyOf(page.id, id), id, page: page.id, label: view.name, state: extractViewState(view) })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/** @return {Array<string>} The entries as list values, in menu order. */\n\t\tvalues() {\n\t\t\treturn this.entries.map((entry) => keyOf(entry.page, entry.id))\n\t\t},\n\n\t\t/**\n\t\t * The choices: the candidates, and the entries no candidate covers. A\n\t\t * choice's second line says what the view shows; a row in the menu\n\t\t * adds how many records that is, once the count is in.\n\t\t *\n\t\t * @return {Array<{value: string, label: string, description: string}>}\n\t\t */\n\t\toptions() {\n\t\t\tconst byId = new Map(this.pages.map((page) => [page.id, page]))\n\t\t\tconst inMenu = new Set(this.values)\n\t\t\tconst known = new Set(this.candidates.map((c) => c.value))\n\t\t\tconst candidates = this.candidates.map((c) => {\n\t\t\t\tconst summary = this.summaryOf(byId.get(c.page), c.state)\n\t\t\t\tconst count = inMenu.has(c.value) ? this.countText(byId.get(c.page), this.counts[c.value]) : ''\n\t\t\t\treturn { value: c.value, label: c.label, description: count ? `${summary} · ${count}` : summary }\n\t\t\t})\n\t\t\tconst stored = this.entries\n\t\t\t\t.filter((entry) => !known.has(keyOf(entry.page, entry.id)))\n\t\t\t\t.map((entry) => ({ value: keyOf(entry.page, entry.id), label: entry.name, description: byId.has(entry.page) ? byId.get(entry.page).title : '' }))\n\t\t\treturn [...candidates, ...stored]\n\t\t},\n\t},\n\n\twatch: {\n\t\t/** Look up the names a summary needs once the views are in. */\n\t\tcandidates() {\n\t\t\tthis.loadValueLabels()\n\t\t\tthis.loadCounts()\n\t\t},\n\n\t\t/** Count a view as soon as it is in the menu. */\n\t\tvalues() {\n\t\t\tthis.loadCounts()\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchViews()\n\t\tthis.loadSchemas()\n\t\tthis.loadCounts()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/** Read the saved views the user can see; without them the defaults stay. */\n\t\tasync fetchViews() {\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst views = await useSavedViewsApi().fetchViews()\n\t\t\t\tthis.views = Array.isArray(views) ? views : []\n\t\t\t} catch {\n\t\t\t\tthis.views = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A view's filters as one line, else the page's title.\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {{filters: object, search: string}} state The view's state.\n\t\t * @return {string}\n\t\t */\n\t\tsummaryOf(page, state) {\n\t\t\tconst schema = this.schemas[page.id]\n\t\t\tconst summary = summarizeViewFilters(state, {\n\t\t\t\tproperties: (schema && schema.properties) || {},\n\t\t\t\tvalueLabels: this.valueLabels[page.id] || {},\n\t\t\t\tfilterLabels: page.filterLabels,\n\t\t\t\ttranslate: this.cnTranslate,\n\t\t\t})\n\t\t\treturn summary || page.title\n\t\t},\n\n\t\t/**\n\t\t * \"19 cases\": the page's `countSubtitle` for the total, else \"19 results\".\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {number|undefined} total The count, when it is in.\n\t\t * @return {string} The text, or '' while the count is unknown.\n\t\t */\n\t\tcountText(page, total) {\n\t\t\tif (typeof total !== 'number') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst line = page.countSubtitle ? countLine(page.countSubtitle, total, { translate: this.cnTranslate, appId: this.cnAppId }) : ''\n\t\t\treturn line || n('nextcloud-vue', '%n result', '%n results', total)\n\t\t},\n\n\t\t/** Read the schema of every page that offers saved views, for field titles and value names. */\n\t\tasync loadSchemas() {\n\t\t\tawait Promise.all(this.pages.filter((page) => page.register && page.schema).map(async (page) => {\n\t\t\t\tconst schema = await loadSchema(page.register, page.schema)\n\t\t\t\tif (schema) {\n\t\t\t\t\tthis.schemas = { ...this.schemas, [page.id]: schema }\n\t\t\t\t}\n\t\t\t}))\n\t\t\tthis.loadValueLabels()\n\t\t},\n\n\t\t/** Look up the names of the referenced records the views filter on. */\n\t\tasync loadValueLabels() {\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst schema = this.schemas[page.id]\n\t\t\t\tif (!schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst fields = fieldsFromSchema(schema, { includeReadOnly: true })\n\t\t\t\tconst refs = this.candidates\n\t\t\t\t\t.filter((c) => c.page === page.id)\n\t\t\t\t\t.flatMap((c) => viewFilterReferences(c.state, fields, page.filterLabels))\n\t\t\t\tfor (const ref of refs) {\n\t\t\t\t\tconst labels = await loadLabels({ ...ref, register: ref.register || page.register })\n\t\t\t\t\tif (Object.keys(labels).length === 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst forPage = this.valueLabels[page.id] || {}\n\t\t\t\t\tthis.valueLabels = { ...this.valueLabels, [page.id]: { ...forPage, [ref.key]: { ...(forPage[ref.key] || {}), ...labels } } }\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/** Count every view in the menu; each count fills in on its own. */\n\t\tloadCounts() {\n\t\t\tfor (const value of this.values) {\n\t\t\t\tif (Object.hasOwn(this.counts, value)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst candidate = this.candidates.find((c) => c.value === value)\n\t\t\t\tconst page = candidate && this.pages.find((p) => p.id === candidate.page)\n\t\t\t\tif (!page || !page.register || !page.schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tthis.counts = { ...this.counts, [value]: undefined }\n\t\t\t\tloadCount(page, filterParams(candidate.state)).then((total) => {\n\t\t\t\t\tif (typeof total === 'number') {\n\t\t\t\t\t\tthis.counts = { ...this.counts, [value]: total }\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Store the list in its new order, with entries removed or added.\n\t\t *\n\t\t * @param {Array<string>} values The list values in order.\n\t\t */\n\t\tonChange(values) {\n\t\t\tif (!this.menu) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst byKey = new Map(this.entries.map((entry) => [keyOf(entry.page, entry.id), entry]))\n\t\t\tconst candidates = new Map(this.candidates.map((c) => [c.value, c]))\n\t\t\tconst next = values\n\t\t\t\t.map((value) => {\n\t\t\t\t\tif (byKey.has(value)) {\n\t\t\t\t\t\treturn byKey.get(value)\n\t\t\t\t\t}\n\t\t\t\t\tconst candidate = candidates.get(value)\n\t\t\t\t\treturn candidate ? { id: candidate.id, page: candidate.page, name: candidate.label } : null\n\t\t\t\t})\n\t\t\t\t.filter(Boolean)\n\t\t\tthis.menu.set(next)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-saved-view-menu-settings__intro {\n\tmargin: 0 0 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["CnOrderedListField","t","n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnSavedViewMenuSettings.vue3.js","sources":["../../../../src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue?vue&type=template&id=71fee42c&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-saved-view-menu-settings\" data-testid=\"cn-saved-view-menu-settings\">\n\t\t<p class=\"cn-saved-view-menu-settings__intro\">\n\t\t\t{{ t('nextcloud-vue', 'These saved views are at the bottom of your menu under My views, in this order. Drag a row or use the arrow keys. You put a view in the menu with Add to menu in the side panel of a list.') }}\n\t\t</p>\n\t\t<CnOrderedListField\n\t\t\t:modelValue=\"values\"\n\t\t\t:options=\"options\"\n\t\t\t:label=\"t('nextcloud-vue', 'Views in my menu')\"\n\t\t\t:addLabel=\"t('nextcloud-vue', 'Add view')\"\n\t\t\t:addPlaceholder=\"t('nextcloud-vue', 'Pick a saved view')\"\n\t\t\t:addHint=\"t('nextcloud-vue', 'You see your own views, the views shared with you and the default views. A new view goes to the bottom.')\"\n\t\t\t:removeLabel=\"t('nextcloud-vue', 'Remove from menu')\"\n\t\t\t:emptyText=\"t('nextcloud-vue', 'Your menu has no views yet.')\"\n\t\t\t:loading=\"loading\"\n\t\t\t:disabled=\"!menu\"\n\t\t\t@update:modelValue=\"onChange\" />\n\t</div>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport CnOrderedListField from '../CnOrderedListField/CnOrderedListField.vue'\nimport { useSavedViewsApi } from '../../composables/useSavedViewsApi.js'\nimport { countLine } from '../../utils/countLine.js'\nimport { facetFilterParams } from '../../utils/facets.js'\nimport { fetchFilterCounts } from '../../utils/fetchFilterCounts.js'\nimport { pickRefLabel } from '../../utils/headerFieldChips.js'\nimport { buildHeaders, buildQueryString, prefixUrl } from '../../utils/headers.js'\nimport { resolveFilterMap } from '../../utils/routeFilters.js'\nimport { defaultFilterViews, extractViewState, pageOffersSavedViews, viewMatchesScope } from '../../utils/savedViewHelpers.js'\nimport { normalizeSavedViewMenu } from '../../utils/savedViewMenu.js'\nimport { summarizeViewFilters, viewFilterReferences } from '../../utils/savedViewSummary.js'\nimport { fieldsFromSchema } from '../../utils/schema.js'\n\n// Per session: a schema, a referenced record's name and a view's count are\n// asked once, however often the dialog opens.\nconst schemaCache = new Map()\nconst labelCache = new Map()\nconst countCache = new Map()\n\n/**\n * The key a menu entry or a candidate view is listed under.\n *\n * @param {string} page The page's route name.\n * @param {string} id The view id.\n * @return {string}\n */\nfunction keyOf(page, id) {\n\treturn `${page}|${id}`\n}\n\n/**\n * GET a JSON document from OpenRegister.\n *\n * @param {string} path The app-absolute path.\n * @param {object} [params] Query parameters.\n * @return {Promise<object>}\n */\nasync function getJson(path, params = {}) {\n\tconst response = await fetch(prefixUrl(path + buildQueryString(params)), { headers: buildHeaders() })\n\tif (!response.ok) {\n\t\tthrow new Error(`${path} returned ${response.status}`)\n\t}\n\treturn response.json()\n}\n\n/**\n * A page's schema, asked once.\n *\n * @param {string} register The register slug.\n * @param {string} schema The schema slug.\n * @return {Promise<object|null>}\n */\nfunction loadSchema(register, schema) {\n\tconst key = `${register}|${schema}`\n\tif (!schemaCache.has(key)) {\n\t\t// A failed read is forgotten, so the next opening asks again instead\n\t\t// of naming every filter by its raw key for the rest of the session.\n\t\tschemaCache.set(key, getJson(`/apps/openregister/api/schemas/${encodeURIComponent(schema)}`, { register }).catch(() => {\n\t\t\tschemaCache.delete(key)\n\t\t\treturn null\n\t\t}))\n\t}\n\treturn schemaCache.get(key)\n}\n\n/**\n * The names of referenced records, each asked once.\n *\n * @param {{register: string, schema: string, labelField: string, ids: Array<string>}} ref The reference filter.\n * @return {Promise<{[id: string]: string}>}\n */\nasync function loadLabels(ref) {\n\tconst prefix = `${ref.register}|${ref.schema}|${ref.labelField}|`\n\tconst missing = ref.ids.filter((id) => !labelCache.has(prefix + id))\n\tif (missing.length > 0) {\n\t\tconst request = getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { _ids: missing.join(','), _limit: missing.length })\n\t\t\t.then((data) => {\n\t\t\t\tconst byId = {}\n\t\t\t\tfor (const row of (Array.isArray(data && data.results) ? data.results : [])) {\n\t\t\t\t\tconst id = row && (row.id ?? row.uuid ?? (row['@self'] && row['@self'].id))\n\t\t\t\t\tif (id !== undefined && id !== null) {\n\t\t\t\t\t\tbyId[String(id)] = pickRefLabel(row, ref.labelField)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn byId\n\t\t\t})\n\t\t\t.catch(() => ({}))\n\t\tfor (const id of missing) {\n\t\t\tlabelCache.set(prefix + id, request.then((byId) => byId[id] || null))\n\t\t}\n\t}\n\tconst out = {}\n\tawait Promise.all(ref.ids.map(async (id) => {\n\t\tconst label = await labelCache.get(prefix + id)\n\t\tif (label) {\n\t\t\tout[id] = label\n\t\t}\n\t}))\n\treturn out\n}\n\n/**\n * How many records a view's filters match on its page, asked once per view.\n *\n * @param {{register: string, schema: string, baseFilter: object}} page The page.\n * @param {object} filter The view's filters as query parameters.\n * @return {Promise<number|null>}\n */\nfunction loadCount(page, filter) {\n\tconst key = JSON.stringify([page.register, page.schema, page.baseFilter, filter])\n\tif (!countCache.has(key)) {\n\t\tcountCache.set(key, fetchFilterCounts({ register: page.register, schema: page.schema, entries: [{ key: 'view', filter }], baseFilter: page.baseFilter })\n\t\t\t.then((counts) => (typeof counts.view === 'number' ? counts.view : null))\n\t\t\t.catch(() => null))\n\t}\n\treturn countCache.get(key)\n}\n\n/**\n * A view's filters as the list request sends them.\n *\n * @param {{filters: object, search: string}} state The view's state.\n * @return {object}\n */\nfunction filterParams(state) {\n\tconst out = {}\n\tfor (const [key, raw] of Object.entries(state.filters || {})) {\n\t\tObject.assign(out, facetFilterParams(key, Array.isArray(raw) ? raw : [raw]))\n\t}\n\tif (state.search) {\n\t\tout._search = state.search\n\t}\n\treturn out\n}\n\n/**\n * CnSavedViewMenuSettings - the user's \"My views\" menu entries as a settings\n * section body (board DqPersoonlijkeInstellingen, \"Views in my menu\"): the\n * entries in their menu order, reordered by dragging or the arrow keys,\n * taken out of the menu with \"Remove from menu\", and a picker that adds any\n * view the user can see on a page of the app (their own, shared ones and the\n * pages' default views). Removing an entry never deletes the view.\n *\n * Each row says what its view shows and how many records that is: \"Case\n * type Woo request, status open · 19 cases\". The line is built from the\n * view's filters with the page schema's titles and the names of the records\n * and options they point at; the count is one request per view, cached for\n * the session, and fills in when it arrives without holding up the list.\n *\n * Reads and writes the list CnAppRoot holds (`cnSavedViewMenu`, stored in\n * the `cn_saved_view_menu` preference). CnAppRoot draws it as the `views`\n * section of its user settings dialog; an app with a dialog of its own can\n * mount it inside an NcAppSettingsSection.\n *\n * @spec openspec/changes/saved-view-menu-settings/specs/saved-view-menu-settings/spec.md#requirement-a-user-orders-and-picks-the-views-in-their-menu\n */\nexport default {\n\tname: 'CnSavedViewMenuSettings',\n\tcomponents: { CnOrderedListField },\n\tinject: {\n\t\tcnSavedViewMenu: { default: null },\n\t\tcnManifest: { default: null },\n\t\tcnTranslate: { default: () => (key) => key },\n\t\tcnAppId: { default: '' },\n\t\tcnAppConfig: { default: null },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tviews: [],\n\t\t\tloading: false,\n\t\t\t/** The schema per page id. */\n\t\t\tschemas: {},\n\t\t\t/** Looked-up display names per page id, filter key and value. */\n\t\t\tvalueLabels: {},\n\t\t\t/** The count per list value; undefined while it is asked. */\n\t\t\tcounts: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {object|null} The menu holder, when it can be changed. */\n\t\tmenu() {\n\t\t\tconst menu = this.cnSavedViewMenu\n\t\t\treturn menu && typeof menu.set === 'function' ? menu : null\n\t\t},\n\n\t\t/** @return {Array<object>} The stored entries. */\n\t\tentries() {\n\t\t\treturn normalizeSavedViewMenu(this.cnSavedViewMenu ? this.cnSavedViewMenu.entries : [])\n\t\t},\n\n\t\t/** @return {Array<object>} The pages that offer saved views. */\n\t\tpages() {\n\t\t\tconst pages = this.cnManifest && Array.isArray(this.cnManifest.pages) ? this.cnManifest.pages : []\n\t\t\treturn pages\n\t\t\t\t.filter((page) => page && page.type === 'index' && typeof page.id === 'string' && pageOffersSavedViews(page.config))\n\t\t\t\t.map((page) => {\n\t\t\t\t\tconst config = page.config || {}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: page.id,\n\t\t\t\t\t\ttitle: typeof page.title === 'string' ? this.cnTranslate(page.title) : page.id,\n\t\t\t\t\t\tscope: { scope: config.savedViewsScope, register: config.register, schema: config.schema, source: config.entitySource },\n\t\t\t\t\t\tdefaults: Array.isArray(config.defaultSavedViews) ? config.defaultSavedViews : [],\n\t\t\t\t\t\tregister: typeof config.register === 'string' ? config.register : '',\n\t\t\t\t\t\tschema: typeof config.schema === 'string' ? config.schema : '',\n\t\t\t\t\t\tbaseFilter: resolveFilterMap(config.filter, {}, { config: this.cnAppConfig || {} }),\n\t\t\t\t\t\tcountSubtitle: config.countSubtitle || null,\n\t\t\t\t\t\tfilterLabels: (config.filterLabels && typeof config.filterLabels === 'object') ? config.filterLabels : {},\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/** @return {Array<object>} Every view the user can put in the menu, with its page and state. */\n\t\tcandidates() {\n\t\t\tconst out = []\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst defaults = page.defaults.map((entry) => (entry && typeof entry.name === 'string' ? { ...entry, name: this.cnTranslate(entry.name) } : entry))\n\t\t\t\tconst views = [...defaultFilterViews(defaults, page.scope), ...this.views.filter((view) => viewMatchesScope(view, page.scope))]\n\t\t\t\tfor (const view of views) {\n\t\t\t\t\tconst id = String(view.id ?? view.slug ?? '')\n\t\t\t\t\tif (id === '' || typeof view.name !== 'string' || view.name === '') {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tout.push({ value: keyOf(page.id, id), id, page: page.id, label: view.name, state: extractViewState(view) })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/** @return {Array<string>} The entries as list values, in menu order. */\n\t\tvalues() {\n\t\t\treturn this.entries.map((entry) => keyOf(entry.page, entry.id))\n\t\t},\n\n\t\t/**\n\t\t * The choices: the candidates, and the entries no candidate covers. A\n\t\t * choice's second line says what the view shows; a row in the menu\n\t\t * adds how many records that is, once the count is in.\n\t\t *\n\t\t * @return {Array<{value: string, label: string, description: string}>}\n\t\t */\n\t\toptions() {\n\t\t\tconst byId = new Map(this.pages.map((page) => [page.id, page]))\n\t\t\tconst inMenu = new Set(this.values)\n\t\t\tconst known = new Set(this.candidates.map((c) => c.value))\n\t\t\tconst candidates = this.candidates.map((c) => {\n\t\t\t\tconst summary = this.summaryOf(byId.get(c.page), c.state)\n\t\t\t\tconst count = inMenu.has(c.value) ? this.countText(byId.get(c.page), this.counts[c.value]) : ''\n\t\t\t\treturn { value: c.value, label: c.label, description: count ? `${summary} · ${count}` : summary }\n\t\t\t})\n\t\t\tconst stored = this.entries\n\t\t\t\t.filter((entry) => !known.has(keyOf(entry.page, entry.id)))\n\t\t\t\t.map((entry) => ({ value: keyOf(entry.page, entry.id), label: entry.name, description: byId.has(entry.page) ? byId.get(entry.page).title : '' }))\n\t\t\treturn [...candidates, ...stored]\n\t\t},\n\t},\n\n\twatch: {\n\t\t/** Look up the names a summary needs once the views are in. */\n\t\tcandidates() {\n\t\t\tthis.loadValueLabels()\n\t\t\tthis.loadCounts()\n\t\t},\n\n\t\t/** Count a view as soon as it is in the menu. */\n\t\tvalues() {\n\t\t\tthis.loadCounts()\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchViews()\n\t\tthis.loadSchemas()\n\t\tthis.loadCounts()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/** Read the saved views the user can see; without them the defaults stay. */\n\t\tasync fetchViews() {\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst views = await useSavedViewsApi().fetchViews()\n\t\t\t\tthis.views = Array.isArray(views) ? views : []\n\t\t\t} catch {\n\t\t\t\tthis.views = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A view's filters as one line, else the page's title.\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {{filters: object, search: string}} state The view's state.\n\t\t * @return {string}\n\t\t */\n\t\tsummaryOf(page, state) {\n\t\t\tconst schema = this.schemas[page.id]\n\t\t\tconst summary = summarizeViewFilters(state, {\n\t\t\t\tproperties: (schema && schema.properties) || {},\n\t\t\t\tvalueLabels: this.valueLabels[page.id] || {},\n\t\t\t\tfilterLabels: page.filterLabels,\n\t\t\t\ttranslate: this.cnTranslate,\n\t\t\t})\n\t\t\treturn summary || page.title\n\t\t},\n\n\t\t/**\n\t\t * \"19 cases\": the page's `countSubtitle` for the total, else \"19 results\".\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {number|undefined} total The count, when it is in.\n\t\t * @return {string} The text, or '' while the count is unknown.\n\t\t */\n\t\tcountText(page, total) {\n\t\t\tif (typeof total !== 'number') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst line = page.countSubtitle ? countLine(page.countSubtitle, total, { translate: this.cnTranslate, appId: this.cnAppId }) : ''\n\t\t\treturn line || n('nextcloud-vue', '%n result', '%n results', total)\n\t\t},\n\n\t\t/** Read the schema of every page that offers saved views, for field titles and value names. */\n\t\tasync loadSchemas() {\n\t\t\tawait Promise.all(this.pages.filter((page) => page.register && page.schema).map(async (page) => {\n\t\t\t\tconst schema = await loadSchema(page.register, page.schema)\n\t\t\t\tif (schema) {\n\t\t\t\t\tthis.schemas = { ...this.schemas, [page.id]: schema }\n\t\t\t\t}\n\t\t\t}))\n\t\t\tthis.loadValueLabels()\n\t\t},\n\n\t\t/** Look up the names of the referenced records the views filter on. */\n\t\tasync loadValueLabels() {\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst schema = this.schemas[page.id]\n\t\t\t\tif (!schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst fields = fieldsFromSchema(schema, { includeReadOnly: true })\n\t\t\t\tconst refs = this.candidates\n\t\t\t\t\t.filter((c) => c.page === page.id)\n\t\t\t\t\t.flatMap((c) => viewFilterReferences(c.state, fields))\n\t\t\t\tfor (const ref of refs) {\n\t\t\t\t\tconst labels = await loadLabels({ ...ref, register: ref.register || page.register })\n\t\t\t\t\tif (Object.keys(labels).length === 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst forPage = this.valueLabels[page.id] || {}\n\t\t\t\t\tthis.valueLabels = { ...this.valueLabels, [page.id]: { ...forPage, [ref.key]: { ...(forPage[ref.key] || {}), ...labels } } }\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/** Count every view in the menu; each count fills in on its own. */\n\t\tloadCounts() {\n\t\t\tfor (const value of this.values) {\n\t\t\t\tif (Object.hasOwn(this.counts, value)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst candidate = this.candidates.find((c) => c.value === value)\n\t\t\t\tconst page = candidate && this.pages.find((p) => p.id === candidate.page)\n\t\t\t\tif (!page || !page.register || !page.schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tthis.counts = { ...this.counts, [value]: undefined }\n\t\t\t\tloadCount(page, filterParams(candidate.state)).then((total) => {\n\t\t\t\t\tif (typeof total === 'number') {\n\t\t\t\t\t\tthis.counts = { ...this.counts, [value]: total }\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Store the list in its new order, with entries removed or added.\n\t\t *\n\t\t * @param {Array<string>} values The list values in order.\n\t\t */\n\t\tonChange(values) {\n\t\t\tif (!this.menu) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst byKey = new Map(this.entries.map((entry) => [keyOf(entry.page, entry.id), entry]))\n\t\t\tconst candidates = new Map(this.candidates.map((c) => [c.value, c]))\n\t\t\tconst next = values\n\t\t\t\t.map((value) => {\n\t\t\t\t\tif (byKey.has(value)) {\n\t\t\t\t\t\treturn byKey.get(value)\n\t\t\t\t\t}\n\t\t\t\t\tconst candidate = candidates.get(value)\n\t\t\t\t\treturn candidate ? { id: candidate.id, page: candidate.page, name: candidate.label } : null\n\t\t\t\t})\n\t\t\t\t.filter(Boolean)\n\t\t\tthis.menu.set(next)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-saved-view-menu-settings__intro {\n\tmargin: 0 0 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_toDisplayString"],"mappings":";;;AAWM,EAAA,KAAK,EAAC,6BAAU;AAAA,EAAA,aAAA,EAAA,6BAAA;;AACjB,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,oCAAS,GAAA;;;;;AADjB,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;IACAC,kBAEG,CAAA,GAAA,EAFH,UAEG,EAAAC,eAAA,CADA,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,4LAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;AAGH,MAAA,UAAU,EAAE,QAAM,CAAA,MAAA;AAClB,MAAA,OAAO,EAAE,QAAO,CAAA,OAAA;AAChB,MAAA,KAAK,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,kBAAA,CAAA;AACR,MAAA,QAAQ,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,UAAA,CAAA;AACX,MAAA,cAAc,EAAC,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,mBAAA,CAAA;AACf,MAAA,OAAO,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,yGAAA,CAAA;AACV,MAAA,WAAE,EAAA,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,kBAAA,CAAA;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnSavedViewMenuSettings.vue3.js","sources":["../../../../src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue?vue&type=template&id=dfcd17c4&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-saved-view-menu-settings\" data-testid=\"cn-saved-view-menu-settings\">\n\t\t<p class=\"cn-saved-view-menu-settings__intro\">\n\t\t\t{{ t('nextcloud-vue', 'These saved views are at the bottom of your menu under My views, in this order. Drag a row or use the arrow keys. You put a view in the menu with Add to menu in the side panel of a list.') }}\n\t\t</p>\n\t\t<CnOrderedListField\n\t\t\t:modelValue=\"values\"\n\t\t\t:options=\"options\"\n\t\t\t:label=\"t('nextcloud-vue', 'Views in my menu')\"\n\t\t\t:addLabel=\"t('nextcloud-vue', 'Add view')\"\n\t\t\t:addPlaceholder=\"t('nextcloud-vue', 'Pick a saved view')\"\n\t\t\t:addHint=\"t('nextcloud-vue', 'You see your own views, the views shared with you and the default views. A new view goes to the bottom.')\"\n\t\t\t:removeLabel=\"t('nextcloud-vue', 'Remove from menu')\"\n\t\t\t:emptyText=\"t('nextcloud-vue', 'Your menu has no views yet.')\"\n\t\t\t:loading=\"loading\"\n\t\t\t:disabled=\"!menu\"\n\t\t\t@update:modelValue=\"onChange\" />\n\t</div>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport CnOrderedListField from '../CnOrderedListField/CnOrderedListField.vue'\nimport { useSavedViewsApi } from '../../composables/useSavedViewsApi.js'\nimport { countLine } from '../../utils/countLine.js'\nimport { facetFilterParams } from '../../utils/facets.js'\nimport { fetchFilterCounts } from '../../utils/fetchFilterCounts.js'\nimport { pickRefLabel } from '../../utils/headerFieldChips.js'\nimport { buildHeaders, buildQueryString, prefixUrl } from '../../utils/headers.js'\nimport { resolveFilterMap } from '../../utils/routeFilters.js'\nimport { defaultFilterViews, extractViewState, pageOffersSavedViews, viewMatchesScope } from '../../utils/savedViewHelpers.js'\nimport { normalizeSavedViewMenu } from '../../utils/savedViewMenu.js'\nimport { summarizeViewFilters, viewFilterReferences } from '../../utils/savedViewSummary.js'\nimport { fieldsFromSchema } from '../../utils/schema.js'\n\n// Per session: a schema, a referenced record's name and a view's count are\n// asked once, however often the dialog opens.\nconst schemaCache = new Map()\nconst labelCache = new Map()\nconst countCache = new Map()\n\n/**\n * The key a menu entry or a candidate view is listed under.\n *\n * @param {string} page The page's route name.\n * @param {string} id The view id.\n * @return {string}\n */\nfunction keyOf(page, id) {\n\treturn `${page}|${id}`\n}\n\n/**\n * GET a JSON document from OpenRegister.\n *\n * @param {string} path The app-absolute path.\n * @param {object} [params] Query parameters.\n * @return {Promise<object>}\n */\nasync function getJson(path, params = {}) {\n\tconst response = await fetch(prefixUrl(path + buildQueryString(params)), { headers: buildHeaders() })\n\tif (!response.ok) {\n\t\tthrow new Error(`${path} returned ${response.status}`)\n\t}\n\treturn response.json()\n}\n\n/**\n * A page's schema, asked once.\n *\n * @param {string} register The register slug.\n * @param {string} schema The schema slug.\n * @return {Promise<object|null>}\n */\nfunction loadSchema(register, schema) {\n\tconst key = `${register}|${schema}`\n\tif (!schemaCache.has(key)) {\n\t\t// A failed read is forgotten, so the next opening asks again instead\n\t\t// of naming every filter by its raw key for the rest of the session.\n\t\tschemaCache.set(key, getJson(`/apps/openregister/api/schemas/${encodeURIComponent(schema)}`, { register }).catch(() => {\n\t\t\tschemaCache.delete(key)\n\t\t\treturn null\n\t\t}))\n\t}\n\treturn schemaCache.get(key)\n}\n\n/**\n * The names of referenced records, each asked once.\n *\n * @param {{register: string, schema: string, labelField: string, matchField?: string, ids: Array<string>}} ref The reference filter; with `matchField` the ids are that field's values.\n * @return {Promise<{[id: string]: string}>}\n */\nasync function loadLabels(ref) {\n\tconst prefix = `${ref.register}|${ref.schema}|${ref.labelField}|${ref.matchField || ''}|`\n\tconst missing = ref.ids.filter((id) => !labelCache.has(prefix + id))\n\t// A value that identifies a record by another field: one read per value.\n\tif (missing.length > 0 && ref.matchField) {\n\t\tfor (const value of missing) {\n\t\t\tlabelCache.set(prefix + value, getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { [ref.matchField]: value, _limit: 1 })\n\t\t\t\t.then((data) => {\n\t\t\t\t\tconst row = Array.isArray(data && data.results) ? data.results[0] : null\n\t\t\t\t\treturn row ? pickRefLabel(row, ref.labelField) : null\n\t\t\t\t})\n\t\t\t\t.catch(() => null))\n\t\t}\n\t} else if (missing.length > 0) {\n\t\tconst request = getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { _ids: missing.join(','), _limit: missing.length })\n\t\t\t.then((data) => {\n\t\t\t\tconst byId = {}\n\t\t\t\tfor (const row of (Array.isArray(data && data.results) ? data.results : [])) {\n\t\t\t\t\tconst id = row && (row.id ?? row.uuid ?? (row['@self'] && row['@self'].id))\n\t\t\t\t\tif (id !== undefined && id !== null) {\n\t\t\t\t\t\tbyId[String(id)] = pickRefLabel(row, ref.labelField)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn byId\n\t\t\t})\n\t\t\t.catch(() => ({}))\n\t\tfor (const id of missing) {\n\t\t\tlabelCache.set(prefix + id, request.then((byId) => byId[id] || null))\n\t\t}\n\t}\n\tconst out = {}\n\tawait Promise.all(ref.ids.map(async (id) => {\n\t\tconst label = await labelCache.get(prefix + id)\n\t\tif (label) {\n\t\t\tout[id] = label\n\t\t}\n\t}))\n\treturn out\n}\n\n/**\n * How many records a view's filters match on its page, asked once per view.\n *\n * @param {{register: string, schema: string, baseFilter: object}} page The page.\n * @param {object} filter The view's filters as query parameters.\n * @return {Promise<number|null>}\n */\nfunction loadCount(page, filter) {\n\tconst key = JSON.stringify([page.register, page.schema, page.baseFilter, filter])\n\tif (!countCache.has(key)) {\n\t\tcountCache.set(key, fetchFilterCounts({ register: page.register, schema: page.schema, entries: [{ key: 'view', filter }], baseFilter: page.baseFilter })\n\t\t\t.then((counts) => (typeof counts.view === 'number' ? counts.view : null))\n\t\t\t.catch(() => null))\n\t}\n\treturn countCache.get(key)\n}\n\n/**\n * A view's filters as the list request sends them.\n *\n * @param {{filters: object, search: string}} state The view's state.\n * @return {object}\n */\nfunction filterParams(state) {\n\tconst out = {}\n\tfor (const [key, raw] of Object.entries(state.filters || {})) {\n\t\tObject.assign(out, facetFilterParams(key, Array.isArray(raw) ? raw : [raw]))\n\t}\n\tif (state.search) {\n\t\tout._search = state.search\n\t}\n\treturn out\n}\n\n/**\n * CnSavedViewMenuSettings - the user's \"My views\" menu entries as a settings\n * section body (board DqPersoonlijkeInstellingen, \"Views in my menu\"): the\n * entries in their menu order, reordered by dragging or the arrow keys,\n * taken out of the menu with \"Remove from menu\", and a picker that adds any\n * view the user can see on a page of the app (their own, shared ones and the\n * pages' default views). Removing an entry never deletes the view.\n *\n * Each row says what its view shows and how many records that is: \"Case\n * type Woo request, status open · 19 cases\". The line is built from the\n * view's filters with the page schema's titles and the names of the records\n * and options they point at; the count is one request per view, cached for\n * the session, and fills in when it arrives without holding up the list.\n *\n * Reads and writes the list CnAppRoot holds (`cnSavedViewMenu`, stored in\n * the `cn_saved_view_menu` preference). CnAppRoot draws it as the `views`\n * section of its user settings dialog; an app with a dialog of its own can\n * mount it inside an NcAppSettingsSection.\n *\n * @spec openspec/changes/saved-view-menu-settings/specs/saved-view-menu-settings/spec.md#requirement-a-user-orders-and-picks-the-views-in-their-menu\n */\nexport default {\n\tname: 'CnSavedViewMenuSettings',\n\tcomponents: { CnOrderedListField },\n\tinject: {\n\t\tcnSavedViewMenu: { default: null },\n\t\tcnManifest: { default: null },\n\t\tcnTranslate: { default: () => (key) => key },\n\t\tcnAppId: { default: '' },\n\t\tcnAppConfig: { default: null },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tviews: [],\n\t\t\tloading: false,\n\t\t\t/** The schema per page id. */\n\t\t\tschemas: {},\n\t\t\t/** Looked-up display names per page id, filter key and value. */\n\t\t\tvalueLabels: {},\n\t\t\t/** The count per list value; undefined while it is asked. */\n\t\t\tcounts: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {object|null} The menu holder, when it can be changed. */\n\t\tmenu() {\n\t\t\tconst menu = this.cnSavedViewMenu\n\t\t\treturn menu && typeof menu.set === 'function' ? menu : null\n\t\t},\n\n\t\t/** @return {Array<object>} The stored entries. */\n\t\tentries() {\n\t\t\treturn normalizeSavedViewMenu(this.cnSavedViewMenu ? this.cnSavedViewMenu.entries : [])\n\t\t},\n\n\t\t/** @return {Array<object>} The pages that offer saved views. */\n\t\tpages() {\n\t\t\tconst pages = this.cnManifest && Array.isArray(this.cnManifest.pages) ? this.cnManifest.pages : []\n\t\t\treturn pages\n\t\t\t\t.filter((page) => page && page.type === 'index' && typeof page.id === 'string' && pageOffersSavedViews(page.config))\n\t\t\t\t.map((page) => {\n\t\t\t\t\tconst config = page.config || {}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: page.id,\n\t\t\t\t\t\ttitle: typeof page.title === 'string' ? this.cnTranslate(page.title) : page.id,\n\t\t\t\t\t\tscope: { scope: config.savedViewsScope, register: config.register, schema: config.schema, source: config.entitySource },\n\t\t\t\t\t\tdefaults: Array.isArray(config.defaultSavedViews) ? config.defaultSavedViews : [],\n\t\t\t\t\t\tregister: typeof config.register === 'string' ? config.register : '',\n\t\t\t\t\t\tschema: typeof config.schema === 'string' ? config.schema : '',\n\t\t\t\t\t\tbaseFilter: resolveFilterMap(config.filter, {}, { config: this.cnAppConfig || {} }),\n\t\t\t\t\t\tcountSubtitle: config.countSubtitle || null,\n\t\t\t\t\t\tfilterLabels: (config.filterLabels && typeof config.filterLabels === 'object') ? config.filterLabels : {},\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/** @return {Array<object>} Every view the user can put in the menu, with its page and state. */\n\t\tcandidates() {\n\t\t\tconst out = []\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst defaults = page.defaults.map((entry) => (entry && typeof entry.name === 'string' ? { ...entry, name: this.cnTranslate(entry.name) } : entry))\n\t\t\t\tconst views = [...defaultFilterViews(defaults, page.scope), ...this.views.filter((view) => viewMatchesScope(view, page.scope))]\n\t\t\t\tfor (const view of views) {\n\t\t\t\t\tconst id = String(view.id ?? view.slug ?? '')\n\t\t\t\t\tif (id === '' || typeof view.name !== 'string' || view.name === '') {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tout.push({ value: keyOf(page.id, id), id, page: page.id, label: view.name, state: extractViewState(view) })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/** @return {Array<string>} The entries as list values, in menu order. */\n\t\tvalues() {\n\t\t\treturn this.entries.map((entry) => keyOf(entry.page, entry.id))\n\t\t},\n\n\t\t/**\n\t\t * The choices: the candidates, and the entries no candidate covers. A\n\t\t * choice's second line says what the view shows; a row in the menu\n\t\t * adds how many records that is, once the count is in.\n\t\t *\n\t\t * @return {Array<{value: string, label: string, description: string}>}\n\t\t */\n\t\toptions() {\n\t\t\tconst byId = new Map(this.pages.map((page) => [page.id, page]))\n\t\t\tconst inMenu = new Set(this.values)\n\t\t\tconst known = new Set(this.candidates.map((c) => c.value))\n\t\t\tconst candidates = this.candidates.map((c) => {\n\t\t\t\tconst summary = this.summaryOf(byId.get(c.page), c.state)\n\t\t\t\tconst count = inMenu.has(c.value) ? this.countText(byId.get(c.page), this.counts[c.value]) : ''\n\t\t\t\treturn { value: c.value, label: c.label, description: count ? `${summary} · ${count}` : summary }\n\t\t\t})\n\t\t\tconst stored = this.entries\n\t\t\t\t.filter((entry) => !known.has(keyOf(entry.page, entry.id)))\n\t\t\t\t.map((entry) => ({ value: keyOf(entry.page, entry.id), label: entry.name, description: byId.has(entry.page) ? byId.get(entry.page).title : '' }))\n\t\t\treturn [...candidates, ...stored]\n\t\t},\n\t},\n\n\twatch: {\n\t\t/** Look up the names a summary needs once the views are in. */\n\t\tcandidates() {\n\t\t\tthis.loadValueLabels()\n\t\t\tthis.loadCounts()\n\t\t},\n\n\t\t/** Count a view as soon as it is in the menu. */\n\t\tvalues() {\n\t\t\tthis.loadCounts()\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchViews()\n\t\tthis.loadSchemas()\n\t\tthis.loadCounts()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/** Read the saved views the user can see; without them the defaults stay. */\n\t\tasync fetchViews() {\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst views = await useSavedViewsApi().fetchViews()\n\t\t\t\tthis.views = Array.isArray(views) ? views : []\n\t\t\t} catch {\n\t\t\t\tthis.views = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A view's filters as one line, else the page's title.\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {{filters: object, search: string}} state The view's state.\n\t\t * @return {string}\n\t\t */\n\t\tsummaryOf(page, state) {\n\t\t\tconst schema = this.schemas[page.id]\n\t\t\tconst summary = summarizeViewFilters(state, {\n\t\t\t\tproperties: (schema && schema.properties) || {},\n\t\t\t\tvalueLabels: this.valueLabels[page.id] || {},\n\t\t\t\tfilterLabels: page.filterLabels,\n\t\t\t\ttranslate: this.cnTranslate,\n\t\t\t})\n\t\t\treturn summary || page.title\n\t\t},\n\n\t\t/**\n\t\t * \"19 cases\": the page's `countSubtitle` for the total, else \"19 results\".\n\t\t *\n\t\t * @param {object} page The page.\n\t\t * @param {number|undefined} total The count, when it is in.\n\t\t * @return {string} The text, or '' while the count is unknown.\n\t\t */\n\t\tcountText(page, total) {\n\t\t\tif (typeof total !== 'number') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst line = page.countSubtitle ? countLine(page.countSubtitle, total, { translate: this.cnTranslate, appId: this.cnAppId }) : ''\n\t\t\treturn line || n('nextcloud-vue', '%n result', '%n results', total)\n\t\t},\n\n\t\t/** Read the schema of every page that offers saved views, for field titles and value names. */\n\t\tasync loadSchemas() {\n\t\t\tawait Promise.all(this.pages.filter((page) => page.register && page.schema).map(async (page) => {\n\t\t\t\tconst schema = await loadSchema(page.register, page.schema)\n\t\t\t\tif (schema) {\n\t\t\t\t\tthis.schemas = { ...this.schemas, [page.id]: schema }\n\t\t\t\t}\n\t\t\t}))\n\t\t\tthis.loadValueLabels()\n\t\t},\n\n\t\t/** Look up the names of the referenced records the views filter on. */\n\t\tasync loadValueLabels() {\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst schema = this.schemas[page.id]\n\t\t\t\tif (!schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst fields = fieldsFromSchema(schema, { includeReadOnly: true })\n\t\t\t\tconst refs = this.candidates\n\t\t\t\t\t.filter((c) => c.page === page.id)\n\t\t\t\t\t.flatMap((c) => viewFilterReferences(c.state, fields, page.filterLabels))\n\t\t\t\tfor (const ref of refs) {\n\t\t\t\t\tconst labels = await loadLabels({ ...ref, register: ref.register || page.register })\n\t\t\t\t\tif (Object.keys(labels).length === 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst forPage = this.valueLabels[page.id] || {}\n\t\t\t\t\tthis.valueLabels = { ...this.valueLabels, [page.id]: { ...forPage, [ref.key]: { ...(forPage[ref.key] || {}), ...labels } } }\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/** Count every view in the menu; each count fills in on its own. */\n\t\tloadCounts() {\n\t\t\tfor (const value of this.values) {\n\t\t\t\tif (Object.hasOwn(this.counts, value)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst candidate = this.candidates.find((c) => c.value === value)\n\t\t\t\tconst page = candidate && this.pages.find((p) => p.id === candidate.page)\n\t\t\t\tif (!page || !page.register || !page.schema) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tthis.counts = { ...this.counts, [value]: undefined }\n\t\t\t\tloadCount(page, filterParams(candidate.state)).then((total) => {\n\t\t\t\t\tif (typeof total === 'number') {\n\t\t\t\t\t\tthis.counts = { ...this.counts, [value]: total }\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Store the list in its new order, with entries removed or added.\n\t\t *\n\t\t * @param {Array<string>} values The list values in order.\n\t\t */\n\t\tonChange(values) {\n\t\t\tif (!this.menu) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst byKey = new Map(this.entries.map((entry) => [keyOf(entry.page, entry.id), entry]))\n\t\t\tconst candidates = new Map(this.candidates.map((c) => [c.value, c]))\n\t\t\tconst next = values\n\t\t\t\t.map((value) => {\n\t\t\t\t\tif (byKey.has(value)) {\n\t\t\t\t\t\treturn byKey.get(value)\n\t\t\t\t\t}\n\t\t\t\t\tconst candidate = candidates.get(value)\n\t\t\t\t\treturn candidate ? { id: candidate.id, page: candidate.page, name: candidate.label } : null\n\t\t\t\t})\n\t\t\t\t.filter(Boolean)\n\t\t\tthis.menu.set(next)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-saved-view-menu-settings__intro {\n\tmargin: 0 0 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_toDisplayString"],"mappings":";;;AAWM,EAAA,KAAK,EAAC,6BAAU;AAAA,EAAA,aAAA,EAAA,6BAAA;;AACjB,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,oCAAS,GAAA;;;;;AADjB,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;IACAC,kBAEG,CAAA,GAAA,EAFH,UAEG,EAAAC,eAAA,CADA,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,4LAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;AAGH,MAAA,UAAU,EAAE,QAAM,CAAA,MAAA;AAClB,MAAA,OAAO,EAAE,QAAO,CAAA,OAAA;AAChB,MAAA,KAAK,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,kBAAA,CAAA;AACR,MAAA,QAAQ,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,UAAA,CAAA;AACX,MAAA,cAAc,EAAC,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,mBAAA,CAAA;AACf,MAAA,OAAO,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,yGAAA,CAAA;AACV,MAAA,WAAE,EAAA,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,kBAAA,CAAA;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnTimelineManage.vue2.js';
2
2
  import { render } from './CnTimelineManage.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-b7edba04";
5
+ script.__scopeId = "data-v-a4c73a6a";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -102,6 +102,16 @@ var script = {
102
102
  return this.content.subtitle ? this.translate(this.content.subtitle) : translate('nextcloud-vue', 'What happened, who did it and when, newest first')
103
103
  },
104
104
 
105
+ /** @return {string} The title of the note form's own card (`composer: "only"`): `content.title`, else Add note. */
106
+ composerTitleText() {
107
+ return this.content.title ? this.translate(this.content.title) : translate('nextcloud-vue', 'Add note')
108
+ },
109
+
110
+ /** @return {string} The line under that title: `content.subtitle`, else where the note goes. */
111
+ composerSubtitleText() {
112
+ return this.content.subtitle ? this.translate(this.content.subtitle) : translate('nextcloud-vue', 'Goes on the timeline above, internal unless you make it visible')
113
+ },
114
+
105
115
  publicLabel() {
106
116
  return this.content.publicLabel ? this.translate(this.content.publicLabel) : translate('nextcloud-vue', 'Visible to the requester')
107
117
  },