@conduction/nextcloud-vue 2.99.0 → 2.100.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 (79) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +6 -1
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +2 -1
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  6. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +25 -8
  7. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  8. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +5 -3
  9. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  10. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  11. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +11 -8
  12. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  13. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +13 -4
  14. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  15. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  16. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +54 -3
  17. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  18. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +16 -2
  19. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  20. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
  21. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +42 -7
  22. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
  23. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +121 -57
  24. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
  25. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js +10 -0
  26. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js.map +1 -1
  27. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js +2 -1
  28. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js.map +1 -1
  29. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +136 -7
  30. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  31. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  32. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  33. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +68 -7
  34. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  35. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +256 -240
  36. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  37. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js +1 -1
  38. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js +38 -9
  39. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js.map +1 -1
  40. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js.map +1 -1
  41. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  42. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +16 -2
  43. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  44. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +40 -25
  45. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  46. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue.js +1 -1
  47. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +7 -7
  48. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
  49. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
  50. package/dist/esm/l10n/en.json.js +7 -1
  51. package/dist/esm/l10n/en.json.js.map +1 -1
  52. package/dist/esm/l10n/nl.json.js +7 -1
  53. package/dist/esm/l10n/nl.json.js.map +1 -1
  54. package/dist/esm/library-style.js +1 -1
  55. package/dist/esm/nextcloud-vue.css +348 -161
  56. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  57. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  58. package/dist/nextcloud-vue.cjs.js +1482 -998
  59. package/dist/nextcloud-vue.cjs.js.map +2 -2
  60. package/dist/nextcloud-vue.css +348 -161
  61. package/l10n/en.json +7 -1
  62. package/l10n/nl.json +7 -1
  63. package/package.json +1 -1
  64. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +7 -1
  65. package/src/components/CnDetailPage/CnDetailPage.vue +28 -9
  66. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +13 -4
  67. package/src/components/CnFactsWidget/CnFactsWidget.vue +65 -3
  68. package/src/components/CnFlowsPage/CnFlowBoard.vue +77 -2
  69. package/src/components/CnFlowsPage/CnFlowEditorPage.vue +11 -0
  70. package/src/components/CnIndexPage/CnIndexPage.vue +136 -7
  71. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +114 -7
  72. package/src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue +38 -9
  73. package/src/components/CnSetupWizard/CnSetupWizard.vue +37 -3
  74. package/src/components/CnWizardDialog/CnWizardDialog.vue +7 -7
  75. package/src/css/detail-page.css +15 -0
  76. package/src/css/dialog.css +48 -0
  77. package/src/css/look-board.css +83 -1
  78. package/src/schemas/app-manifest-v2.schema.json +25 -1
  79. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"CnFlowEditorPage.vue3.js","sources":["../../../../src/components/CnFlowsPage/CnFlowEditorPage.vue?vue&type=template&id=b6544712&lang.js"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- Copyright (C) 2026 Conduction B.V. -->\n<template>\n\t<CnFlowBoard v-if=\"isBoard\"\n\t\t:id=\"flowId\"\n\t\t:app=\"app\"\n\t\t:run=\"runUuid\"\n\t\t:detailRoute=\"detailRoute\"\n\t\t:listLabel=\"listLabel ? cnTranslate(listLabel) : ''\"\n\t\t:palette=\"palette\"\n\t\t:paletteFootnote=\"paletteFootnote ? cnTranslate(paletteFootnote) : ''\"\n\t\t:notice=\"notice ? cnTranslate(notice) : ''\"\n\t\t:pill=\"pill ? cnTranslate(pill) : ''\"\n\t\t@save=\"onSave\"\n\t\t@run=\"onRun\" />\n\t<CnFlowDetail v-else\n\t\t:id=\"flowId\"\n\t\t:app=\"app\"\n\t\t:run=\"runUuid\"\n\t\t@save=\"onSave\"\n\t\t@run=\"onRun\" />\n</template>\n\n<script>\nimport CnFlowDetail from '../CnFlowDetail/CnFlowDetail.vue'\nimport CnFlowBoard from './CnFlowBoard.vue'\nimport { useFlowStore } from '../../composables/useFlowStore.js'\nimport { useLook } from '../../composables/useLook.js'\n\n/**\n * The flow canvas — manifest page type `flow-detail`.\n *\n * Mounts the shared `CnFlowDetail` node/edge canvas over OpenRegister's one\n * native flow store (ADR-065, ADR-098), scoped to the owning app. Pair it with\n * a `type: \"flows\"` page at `/flows`; this one takes `/flows/:id`.\n *\n * Controls belong in Nextcloud's app sidebar so the canvas keeps the full\n * width — declare `\"sidebarComponent\"` on the manifest page, or mount\n * `CnFlowSidebar` yourself. Canvas and sidebar share `useFlowStore`, which is\n * why neither needs props from the other.\n *\n * Manifest declaration:\n *\n * { \"id\": \"FlowDetail\", \"route\": \"/flows/:id\", \"type\": \"flow-detail\",\n * \"title\": \"Flow\", \"config\": { \"app\": \"dossiq\" } }\n */\nexport default {\n\tname: 'CnFlowEditorPage',\n\n\tcomponents: { CnFlowBoard, CnFlowDetail },\n\n\tinject: {\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The owning app id, stamped on a flow created here so the app-scoped\n\t\t * list finds it again. Omit only on the fleet-wide surface.\n\t\t */\n\t\tapp: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The flow id to open. Defaults to the `id` route param, which is the\n\t\t * normal case; pass it explicitly only when mounting outside a route.\n\t\t * The literal `new` opens an unsaved flow.\n\t\t */\n\t\tid: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Route path the editor lives under, used to swap the URL once a new\n\t\t * flow gets its server-assigned id.\n\t\t */\n\t\tdetailRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '/flows',\n\t\t},\n\n\t\t/**\n\t\t * A run to open on arrival. Defaults to the `run` query parameter,\n\t\t * which is where a run's own address lands: `/flow-runs/{uuid}` reads\n\t\t * the run, finds its flow and redirects to `/flows/{id}?run={uuid}`.\n\t\t * Pass it explicitly only when mounting outside a route.\n\t\t */\n\t\trun: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Draw this page in the `board` look (or `nextcloud`) whatever the app\n\t\t * does. Empty follows the app's look. Manifest key `config.look`.\n\t\t */\n\t\tlook: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Board look: where steps come from. `picker` (default) keeps \"Add a\n\t\t * step\" in the canvas toolbar; `column` draws the palette as a card\n\t\t * beside the canvas, grouped by the app that provides each step.\n\t\t * Manifest key `config.palette`.\n\t\t */\n\t\tpalette: {\n\t\t\ttype: String,\n\t\t\tdefault: 'picker',\n\t\t},\n\n\t\t/**\n\t\t * Board look: a line under the palette card. Manifest key\n\t\t * `config.paletteFootnote`, translated with the app's catalogue.\n\t\t */\n\t\tpaletteFootnote: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Board look: an info notice under the path. Manifest key\n\t\t * `config.notice`, translated with the app's catalogue.\n\t\t */\n\t\tnotice: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Board look: the pill beside the flow's name (\"Case type flow\").\n\t\t * Empty shows the lifecycle for a draft or retired flow. Manifest key\n\t\t * `config.pill`.\n\t\t */\n\t\tpill: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Board look: the list's name at the start of the path (\"Settings /\n\t\t * Flows\"). Empty reads \"Flows\". Manifest key `config.listLabel`.\n\t\t */\n\t\tlistLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\t/**\n\t * Share the one flow store with the toolbar's save/run handlers, and\n\t * resolve the look (`look` is read here, through `useLook`).\n\t *\n\t * @param {object} props The component props.\n\t * @return {object} The setup bindings.\n\t */\n\tsetup(props) {\n\t\tconst { isBoard } = useLook(props)\n\t\treturn { store: useFlowStore(), isBoard }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {string} The flow id being edited.\n\t\t */\n\t\tflowId() {\n\t\t\treturn this.id || this.$route?.params?.id || ''\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The run to open on arrival, or an empty string.\n\t\t */\n\t\trunUuid() {\n\t\t\treturn this.run || this.$route?.query?.run || ''\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onSave() {\n\t\t\tconst saved = await this.store.save()\n\t\t\t// A newly created flow gets its id from the server, so the route has\n\t\t\t// to catch up or a reload would land back on `new` and lose it.\n\t\t\tif (saved?.id && this.flowId === 'new') {\n\t\t\t\tthis.$router.replace(`${this.detailRoute}/${saved.id}`)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onRun() {\n\t\t\tawait this.store.run({})\n\t\t},\n\t},\n}\n</script>\n"],"names":["_createBlock"],"mappings":";;;;;;UAKW,MAAA,CAAA,OAAA;oBAATA,WAWW,CAAA,sBAAA,EAAA;;AAVV,QAAA,EAAE,EAAE,QAAM,CAAA,MAAA;AACV,QAAA,GAAG,EAAE,MAAG,CAAA,GAAA;AACR,QAAA,GAAG,EAAE,QAAO,CAAA,OAAA;AACZ,QAAA,WAAW,EAAE,MAAG,CAAA,WAAA;AAChB,QAAA,SAAS,EAAE,MAAA,CAAA,SAAS,GAAG,QAAA,CAAA,WAAU,CAAE,MAAS,CAAA,SAAA,CAAA,GAAA,EAAA;AAC5C,QAAA,OAAO,EAAE,MAAM,CAAA,OAAA;AACf,QAAA,eAAe,EAAE,MAAA,CAAA,eAAc,GAAI,QAAA,CAAA,WAAA,CAAA,MAAA,CAAA,eAAA,CAAA,GAAA,EAAA;AACnC,QAAA,MAAM,EAAE,MAAA,CAAA,MAAI,GAAA,QAAA,CAAA,WAAA,CAAA,MAAA,CAAA,MAAA,CAAA,GAAA,EAAA;AACZ,QAAA,IAAI,EAAE,MAAA,CAAA,IAAI,GAAG,QAAA,CAAA,WAAA,CAAA,MAAA,CAAA,IAAA,CAAA,GAAA,EAAA;AACb,QAAA,MAAI,EAAE,QAAM,CAAA,MAAA;AACZ,QAAA,KAAG,EAAE,QAAK,CAAA,KAAA;;oBACXA;;AACC,QAAA,EAAE,EAAE,QAAM,CAAA,MAAA;AACV,QAAA,GAAG,EAAE,MAAG,CAAA,GAAA;AACR,QAAA,GAAG,EAAE,QAAO,CAAA,OAAA;;;;;;;;"}
1
+ {"version":3,"file":"CnFlowEditorPage.vue3.js","sources":["../../../../src/components/CnFlowsPage/CnFlowEditorPage.vue?vue&type=template&id=4509996d&lang.js"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- Copyright (C) 2026 Conduction B.V. -->\n<template>\n\t<CnFlowBoard v-if=\"isBoard\"\n\t\t:id=\"flowId\"\n\t\t:app=\"app\"\n\t\t:run=\"runUuid\"\n\t\t:detailRoute=\"detailRoute\"\n\t\t:listLabel=\"listLabel ? cnTranslate(listLabel) : ''\"\n\t\t:palette=\"palette\"\n\t\t:paletteFootnote=\"paletteFootnote ? cnTranslate(paletteFootnote) : ''\"\n\t\t:notice=\"notice ? cnTranslate(notice) : ''\"\n\t\t:pill=\"pill ? cnTranslate(pill) : ''\"\n\t\t:testSubject=\"testSubject ? { ...testSubject, label: testSubject.label ? cnTranslate(testSubject.label) : '' } : null\"\n\t\t@save=\"onSave\"\n\t\t@run=\"onRun\" />\n\t<CnFlowDetail v-else\n\t\t:id=\"flowId\"\n\t\t:app=\"app\"\n\t\t:run=\"runUuid\"\n\t\t@save=\"onSave\"\n\t\t@run=\"onRun\" />\n</template>\n\n<script>\nimport CnFlowDetail from '../CnFlowDetail/CnFlowDetail.vue'\nimport CnFlowBoard from './CnFlowBoard.vue'\nimport { useFlowStore } from '../../composables/useFlowStore.js'\nimport { useLook } from '../../composables/useLook.js'\n\n/**\n * The flow canvas — manifest page type `flow-detail`.\n *\n * Mounts the shared `CnFlowDetail` node/edge canvas over OpenRegister's one\n * native flow store (ADR-065, ADR-098), scoped to the owning app. Pair it with\n * a `type: \"flows\"` page at `/flows`; this one takes `/flows/:id`.\n *\n * Controls belong in Nextcloud's app sidebar so the canvas keeps the full\n * width — declare `\"sidebarComponent\"` on the manifest page, or mount\n * `CnFlowSidebar` yourself. Canvas and sidebar share `useFlowStore`, which is\n * why neither needs props from the other.\n *\n * Manifest declaration:\n *\n * { \"id\": \"FlowDetail\", \"route\": \"/flows/:id\", \"type\": \"flow-detail\",\n * \"title\": \"Flow\", \"config\": { \"app\": \"dossiq\" } }\n */\nexport default {\n\tname: 'CnFlowEditorPage',\n\n\tcomponents: { CnFlowBoard, CnFlowDetail },\n\n\tinject: {\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The owning app id, stamped on a flow created here so the app-scoped\n\t\t * list finds it again. Omit only on the fleet-wide surface.\n\t\t */\n\t\tapp: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The flow id to open. Defaults to the `id` route param, which is the\n\t\t * normal case; pass it explicitly only when mounting outside a route.\n\t\t * The literal `new` opens an unsaved flow.\n\t\t */\n\t\tid: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Route path the editor lives under, used to swap the URL once a new\n\t\t * flow gets its server-assigned id.\n\t\t */\n\t\tdetailRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '/flows',\n\t\t},\n\n\t\t/**\n\t\t * A run to open on arrival. Defaults to the `run` query parameter,\n\t\t * which is where a run's own address lands: `/flow-runs/{uuid}` reads\n\t\t * the run, finds its flow and redirects to `/flows/{id}?run={uuid}`.\n\t\t * Pass it explicitly only when mounting outside a route.\n\t\t */\n\t\trun: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Draw this page in the `board` look (or `nextcloud`) whatever the app\n\t\t * does. Empty follows the app's look. Manifest key `config.look`.\n\t\t */\n\t\tlook: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Board look: where steps come from. `picker` (default) keeps \"Add a\n\t\t * step\" in the canvas toolbar; `column` draws the palette as a card\n\t\t * beside the canvas, grouped by the app that provides each step.\n\t\t * Manifest key `config.palette`.\n\t\t */\n\t\tpalette: {\n\t\t\ttype: String,\n\t\t\tdefault: 'picker',\n\t\t},\n\n\t\t/**\n\t\t * Board look: a line under the palette card. Manifest key\n\t\t * `config.paletteFootnote`, translated with the app's catalogue.\n\t\t */\n\t\tpaletteFootnote: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Board look: the records a test run can take as its subject,\n\t\t * `{ register, schema, labelField?, label? }`. Manifest key\n\t\t * `config.testSubject`; `label` is translated with the app's catalogue.\n\t\t */\n\t\ttestSubject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Board look: an info notice under the path. Manifest key\n\t\t * `config.notice`, translated with the app's catalogue.\n\t\t */\n\t\tnotice: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Board look: the pill beside the flow's name (\"Case type flow\").\n\t\t * Empty shows the lifecycle for a draft or retired flow. Manifest key\n\t\t * `config.pill`.\n\t\t */\n\t\tpill: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Board look: the list's name at the start of the path (\"Settings /\n\t\t * Flows\"). Empty reads \"Flows\". Manifest key `config.listLabel`.\n\t\t */\n\t\tlistLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\t/**\n\t * Share the one flow store with the toolbar's save/run handlers, and\n\t * resolve the look (`look` is read here, through `useLook`).\n\t *\n\t * @param {object} props The component props.\n\t * @return {object} The setup bindings.\n\t */\n\tsetup(props) {\n\t\tconst { isBoard } = useLook(props)\n\t\treturn { store: useFlowStore(), isBoard }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {string} The flow id being edited.\n\t\t */\n\t\tflowId() {\n\t\t\treturn this.id || this.$route?.params?.id || ''\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The run to open on arrival, or an empty string.\n\t\t */\n\t\trunUuid() {\n\t\t\treturn this.run || this.$route?.query?.run || ''\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onSave() {\n\t\t\tconst saved = await this.store.save()\n\t\t\t// A newly created flow gets its id from the server, so the route has\n\t\t\t// to catch up or a reload would land back on `new` and lose it.\n\t\t\tif (saved?.id && this.flowId === 'new') {\n\t\t\t\tthis.$router.replace(`${this.detailRoute}/${saved.id}`)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onRun() {\n\t\t\tawait this.store.run({})\n\t\t},\n\t},\n}\n</script>\n"],"names":["_createBlock"],"mappings":";;;;;;UAKW,MAAA,CAAA,OAAA;oBAATA,WAYW,CAAA,sBAAA,EAAA;;AAXV,QAAA,EAAE,EAAE,QAAM,CAAA,MAAA;AACV,QAAA,GAAG,EAAE,MAAG,CAAA,GAAA;AACR,QAAA,GAAG,EAAE,QAAO,CAAA,OAAA;AACZ,QAAA,WAAW,EAAE,MAAG,CAAA,WAAA;AAChB,QAAA,SAAS,EAAE,MAAA,CAAA,SAAS,GAAG,QAAA,CAAA,WAAU,CAAE,MAAS,CAAA,SAAA,CAAA,GAAA,EAAA;AAC5C,QAAA,OAAO,EAAE,MAAM,CAAA,OAAA;AACf,QAAA,eAAe,EAAE,MAAA,CAAA,eAAc,GAAI,QAAA,CAAA,WAAA,CAAA,MAAA,CAAA,eAAA,CAAA,GAAA,EAAA;AACnC,QAAA,MAAM,EAAE,MAAA,CAAA,MAAM,GAAG,QAAA,CAAA,WAAY,CAAA,MAAM,CAAA,MAAA,CAAA,GAAA,EAAA;AACnC,QAAA,IAAI,EAAE,MAAA,CAAA,IAAI,GAAE,QAAA,CAAA,WAAA,CAAA,MAAA,CAAA,IAAA,CAAA,GAAA,EAAA;AACZ,QAAA,WAAU,EAAG,MAAA,CAAA,WAAA,GAAA,EAAA,GAAA,MAAA,CAAA,WAAA,EAAA,KAAA,EAAA,MAAA,CAAA,WAAA,CAAA,KAAA,GAAA,QAAA,CAAA,WAAA,CAAA,MAAA,CAAA,WAAA,CAAA,KAAA,CAAA,GAAA,EAAA,EAAA,GAAA,IAAA;AACb,QAAA,MAAI,EAAE,QAAM,CAAA,MAAA;AACZ,QAAA,KAAG,EAAE,QAAK,CAAA,KAAA;;oBACXA;;AACC,QAAA,EAAE,EAAE,QAAM,CAAA,MAAA;AACV,QAAA,GAAG,EAAE,MAAG,CAAA,GAAA;AACR,QAAA,GAAG,EAAE,QAAO,CAAA,OAAA;;;;;;;;"}
@@ -396,6 +396,8 @@ var script = {
396
396
  */
397
397
  inject: {
398
398
  cnCustomComponents: { default: () => ({}) },
399
+ /** The permissions the user holds, provided by CnAppRoot; for gated page tabs. */
400
+ cnPermissions: { default: () => [] },
399
401
  /** The app id CnAppRoot provides, for the plural form of `countSubtitle`. */
400
402
  cnAppId: { default: '' },
401
403
  /**
@@ -486,7 +488,9 @@ var script = {
486
488
 
487
489
  /**
488
490
  * A note after the breadcrumb and a middle dot (manifest `config.headerMeta`),
489
- * such as "{total} open": `{total}` and `{shown}` are the list's counts.
491
+ * such as "{total} open": `{total}` and `{shown}` are the list's counts,
492
+ * and `{count:<name>}` the count of a list named in `headerMetaCounts`
493
+ * ("{total} contracten bij {count:suppliers} leveranciers").
490
494
  *
491
495
  * @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
492
496
  */
@@ -495,12 +499,29 @@ var script = {
495
499
  default: '',
496
500
  },
497
501
 
502
+ /**
503
+ * The other lists `headerMeta` counts (manifest `config.headerMetaCounts`):
504
+ * `{ <name>: { register, schema, filter? } }`, each read as one list
505
+ * total and filled in for `{count:<name>}`. While a count loads, or when
506
+ * it fails, the note is left out rather than shown with a gap.
507
+ *
508
+ * @type {Record<string, {register: string, schema: string, filter?: object}>|null}
509
+ */
510
+ headerMetaCounts: {
511
+ type: Object,
512
+ default: null,
513
+ },
514
+
498
515
  /**
499
516
  * Tabs to sibling pages above the toolbar (manifest `config.pageTabs`),
500
517
  * each `{ id?, label, route?, params?, query?, href?, countFrom?: {
501
- * register, schema, filter? } }`: DqPrivacy's "AVG-verzoeken |
502
- * Datalekken". The current page's tab is marked; `countFrom` adds the
503
- * count of that list. Wins over a named source's own page tabs.
518
+ * register, schema, filter? }, adminOnly?, permission? }`: DqPrivacy's
519
+ * "AVG-verzoeken | Datalekken". The current page's tab is marked;
520
+ * `countFrom` adds the count of that list. A tab with `adminOnly` shows
521
+ * only to an instance admin, one with `permission` only to a user who
522
+ * holds it. That hides the tab and nothing more: the page it points to
523
+ * must still refuse the user on the server. Wins over a named source's
524
+ * own page tabs.
504
525
  *
505
526
  * @type {Array<object>|null}
506
527
  * @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs
@@ -2540,6 +2561,8 @@ var script = {
2540
2561
  filterStripCounts: null,
2541
2562
  /** The `countFrom` counts of the manifest page tabs, by index. */
2542
2563
  pageTabCounts: {},
2564
+ /** The `headerMetaCounts` totals, by name; a failed one is null. */
2565
+ headerMetaCountValues: {},
2543
2566
  /** The tabs `quickFiltersFrom` produced, or null before they load. */
2544
2567
  generatedQuickFilters: null,
2545
2568
  /** Count per saved-view id, for the views control; null = none. */
@@ -5020,9 +5043,13 @@ var script = {
5020
5043
  if (!Array.isArray(this.pageTabs) || this.pageTabs.length === 0) {
5021
5044
  return this.sourcePageTabs
5022
5045
  }
5046
+ // The index is the tab's place in `pageTabs`, which is how its count
5047
+ // is keyed, so the tabs are numbered before any is left out.
5023
5048
  return this.pageTabs
5024
- .filter((tab) => tab && typeof tab.label === 'string' && tab.label !== '' && (tab.route || tab.to || tab.href))
5025
- .map((tab, index) => {
5049
+ .map((tab, index) => ({ tab, index }))
5050
+ .filter(({ tab }) => tab && typeof tab.label === 'string' && tab.label !== '' && (tab.route || tab.to || tab.href))
5051
+ .filter(({ tab }) => this.pageTabVisible(tab))
5052
+ .map(({ tab, index }) => {
5026
5053
  const to = tab.route
5027
5054
  ? { name: tab.route, ...(tab.params ? { params: tab.params } : {}), ...(tab.query ? { query: tab.query } : {}) }
5028
5055
  : (tab.to || tab.href);
@@ -5464,9 +5491,41 @@ var script = {
5464
5491
  return ''
5465
5492
  }
5466
5493
  const total = this.effectivePagination?.total;
5467
- return this.cnTranslate(headerMeta)
5494
+ let missing = false;
5495
+ const text = this.cnTranslate(headerMeta)
5468
5496
  .replace('{shown}', String(this.effectiveObjects.length))
5469
5497
  .replace('{total}', typeof total === 'number' ? String(total) : '')
5498
+ .replace(/\{count:([\w-]+)\}/g, (match, name) => {
5499
+ const value = this.headerMetaCountValues[name];
5500
+ if (typeof value !== 'number') {
5501
+ missing = true;
5502
+ return ''
5503
+ }
5504
+ return String(value)
5505
+ });
5506
+ return missing ? '' : text
5507
+ },
5508
+
5509
+ /**
5510
+ * Whether the header note carries a count, so the board leaves out its
5511
+ * own "{shown} of {total}" line (DqPrivacy, DqContracten).
5512
+ *
5513
+ * @return {boolean}
5514
+ */
5515
+ headerMetaCarriesCount() {
5516
+ const headerMeta = this.presented('headerMeta');
5517
+ return typeof headerMeta === 'string' && /\{(shown|total|count:[\w-]+)\}/.test(headerMeta)
5518
+ },
5519
+
5520
+ /**
5521
+ * What the header note's other counts depend on, as one key.
5522
+ *
5523
+ * @return {string}
5524
+ */
5525
+ headerMetaCountKey() {
5526
+ const sources = this.headerMetaCounts && typeof this.headerMetaCounts === 'object' ? this.headerMetaCounts : {};
5527
+ const valid = Object.entries(sources).filter(([, from]) => from && from.register && from.schema);
5528
+ return valid.length > 0 ? JSON.stringify(valid) : ''
5470
5529
  },
5471
5530
 
5472
5531
  headerDescription() {
@@ -5479,6 +5538,10 @@ var script = {
5479
5538
  // reads "14 van 14" in Dutch; a page's own template goes
5480
5539
  // through the app's label lookup.
5481
5540
  const countText = this.presented('countText');
5541
+ // The note after the breadcrumb already says how many there are.
5542
+ if (!countText && !this.countSubtitle && this.headerMetaCarriesCount) {
5543
+ return this.description
5544
+ }
5482
5545
  if (!countText && !this.countSubtitle) {
5483
5546
  return translate('nextcloud-vue', '{shown} of {total}', { shown, total })
5484
5547
  }
@@ -5725,6 +5788,13 @@ var script = {
5725
5788
  },
5726
5789
  },
5727
5790
 
5791
+ headerMetaCountKey: {
5792
+ immediate: true,
5793
+ handler() {
5794
+ this.loadHeaderMetaCounts();
5795
+ },
5796
+ },
5797
+
5728
5798
  pageTabCountKey: {
5729
5799
  immediate: true,
5730
5800
  /** @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs */
@@ -6144,6 +6214,65 @@ var script = {
6144
6214
  ];
6145
6215
  },
6146
6216
 
6217
+ /**
6218
+ * Whether Nextcloud says the user administers this instance; false
6219
+ * when the user cannot be read.
6220
+ *
6221
+ * @return {boolean}
6222
+ */
6223
+ viewerIsAdmin() {
6224
+ try {
6225
+ return getCurrentUser()?.isAdmin === true
6226
+ } catch {
6227
+ return false
6228
+ }
6229
+ },
6230
+
6231
+ /**
6232
+ * Whether a manifest page tab shows to this user: an `adminOnly` tab to
6233
+ * an instance admin, a tab with a `permission` to a user who holds it
6234
+ * (as a menu entry: with no permissions known, it shows). This only
6235
+ * decides whether the tab is drawn. It is not access control; the
6236
+ * page behind the tab must enforce its own access on the server.
6237
+ *
6238
+ * @param {object} tab The tab declaration.
6239
+ * @return {boolean} True when the tab is drawn.
6240
+ */
6241
+ pageTabVisible(tab) {
6242
+ if (tab.adminOnly === true && !this.viewerIsAdmin()) {
6243
+ return false
6244
+ }
6245
+ if (tab.permission) {
6246
+ const held = Array.isArray(this.cnPermissions) ? this.cnPermissions : [];
6247
+ if (held.length > 0 && !held.includes(tab.permission)) {
6248
+ return false
6249
+ }
6250
+ }
6251
+ return true
6252
+ },
6253
+
6254
+ /**
6255
+ * Read the totals of the lists `headerMetaCounts` names, one list
6256
+ * request each. A failed count is null and leaves the note out.
6257
+ *
6258
+ * @return {Promise<void>}
6259
+ */
6260
+ async loadHeaderMetaCounts() {
6261
+ const key = this.headerMetaCountKey;
6262
+ if (key === '') {
6263
+ this.headerMetaCountValues = {};
6264
+ return
6265
+ }
6266
+ const ctx = typeof this.selfFetchTokenCtx === 'function' ? this.selfFetchTokenCtx() : {};
6267
+ const values = {};
6268
+ await Promise.all(JSON.parse(key).map(async ([name, from]) => {
6269
+ values[name] = await fetchListTotal(from.register, from.schema, from.filter || {}, ctx).catch(() => null);
6270
+ }));
6271
+ if (key === this.headerMetaCountKey) {
6272
+ this.headerMetaCountValues = values;
6273
+ }
6274
+ },
6275
+
6147
6276
  /**
6148
6277
  * Count the lists behind the manifest page tabs (`countFrom`), one list
6149
6278
  * request each. A failed count shows no number.