@conduction/nextcloud-vue 2.146.0 → 2.148.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 (58) hide show
  1. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +71 -8
  2. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  3. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +2 -2
  4. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  5. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  6. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +73 -9
  7. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  8. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +37 -13
  9. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  10. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  11. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +8 -0
  12. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  13. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  14. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue.js +1 -1
  15. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js +2 -2
  16. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js.map +1 -1
  17. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js.map +1 -1
  18. package/dist/esm/components/CnStepper/CnStepper.vue.js +1 -1
  19. package/dist/esm/components/CnStepper/CnStepper.vue2.js.map +1 -1
  20. package/dist/esm/components/CnStepper/CnStepper.vue3.js.map +1 -1
  21. package/dist/esm/dialogs/CnAccessPurposeDialog.vue.js +1 -1
  22. package/dist/esm/dialogs/CnAccessPurposeDialog.vue2.js +3 -1
  23. package/dist/esm/dialogs/CnAccessPurposeDialog.vue2.js.map +1 -1
  24. package/dist/esm/dialogs/CnAccessPurposeDialog.vue3.js +26 -15
  25. package/dist/esm/dialogs/CnAccessPurposeDialog.vue3.js.map +1 -1
  26. package/dist/esm/l10n/en.json.js +2 -1
  27. package/dist/esm/l10n/en.json.js.map +1 -1
  28. package/dist/esm/l10n/nl.json.js +2 -1
  29. package/dist/esm/l10n/nl.json.js.map +1 -1
  30. package/dist/esm/library-style.js +1 -1
  31. package/dist/esm/nextcloud-vue.css +191 -145
  32. package/dist/esm/utils/builtInFormatters.js +53 -1
  33. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  34. package/dist/esm/utils/objectParties.js +30 -1
  35. package/dist/esm/utils/objectParties.js.map +1 -1
  36. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  37. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  38. package/dist/nextcloud-vue.cjs.js +1548 -1296
  39. package/dist/nextcloud-vue.cjs.js.map +2 -2
  40. package/dist/nextcloud-vue.css +191 -145
  41. package/l10n/en.json +2 -1
  42. package/l10n/nl.json +2 -1
  43. package/package.json +1 -1
  44. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  45. package/src/components/CnDetailPage/CnDetailPage.vue +73 -10
  46. package/src/components/CnFactsWidget/CnFactsWidget.vue +101 -9
  47. package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -1
  48. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +8 -0
  49. package/src/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue +2 -1
  50. package/src/components/CnStepper/CnStepper.vue +10 -2
  51. package/src/css/dialog.css +5 -2
  52. package/src/css/look-board-index.css +14 -0
  53. package/src/css/look-board.css +3 -0
  54. package/src/dialogs/CnAccessPurposeDialog.vue +27 -12
  55. package/src/schemas/app-manifest-v2.schema.json +4 -2
  56. package/src/utils/builtInFormatters.js +52 -0
  57. package/src/utils/objectParties.js +29 -0
  58. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"CnObjectPartiesWidget.vue3.js","sources":["../../../../src/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue?vue&type=template&id=44731ec4&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-object-parties\" :class=\"{ 'cn-object-parties--board': isBoard }\" data-testid=\"cn-object-parties\">\n\t\t<div v-if=\"canAdd\" class=\"cn-object-parties__manage\">\n\t\t\t<NcButton data-testid=\"cn-object-parties-add\" @click=\"adding = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ manageWord('add', 'label', t('nextcloud-vue', 'Add a party')) }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcNoteCard\n\t\t\tv-if=\"manageError\"\n\t\t\ttype=\"error\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-object-parties-manage-error\">\n\t\t\t{{ manageError }}\n\t\t</NcNoteCard>\n\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\n\t\t<NcEmptyContent\n\t\t\tv-else-if=\"failed\"\n\t\t\t:name=\"label('failed')\"\n\t\t\t:description=\"label('failedDetail')\"\n\t\t\tdata-testid=\"cn-object-parties-failed\" />\n\n\t\t<div\n\t\t\tv-else-if=\"isPrimaryPresentation\"\n\t\t\tclass=\"cn-object-parties__main-card\"\n\t\t\tdata-testid=\"cn-object-parties-main-card\">\n\t\t\t<div\n\t\t\t\tv-if=\"mainParty\"\n\t\t\t\tclass=\"cn-object-parties__main\"\n\t\t\t\t:data-party=\"mainParty.partyUuid\"\n\t\t\t\tdata-testid=\"cn-object-parties-main\">\n\t\t\t\t<span class=\"cn-object-parties__main-initials\" aria-hidden=\"true\" data-testid=\"cn-object-parties-main-initials\">{{ initialsOf(nameOf(mainParty)) }}</span>\n\t\t\t\t<span class=\"cn-object-parties__main-text\">\n\t\t\t\t\t<strong class=\"cn-object-parties__main-name\" data-testid=\"cn-object-parties-main-name\">{{ nameOf(mainParty) }}</strong>\n\t\t\t\t\t<span v-if=\"mainRole\" class=\"cn-object-parties__muted cn-object-parties__main-role\" data-testid=\"cn-object-parties-main-role\">{{ mainRole }}</span>\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t\t<span v-else class=\"cn-object-parties__muted\" data-testid=\"cn-object-parties-main-none\">{{ label('noPrimary') }}</span>\n\t\t\t<a\n\t\t\t\tv-if=\"mainParty && mainParty.email\"\n\t\t\t\tclass=\"cn-object-parties__main-email\"\n\t\t\t\t:href=\"`mailto:${mainParty.email}`\"\n\t\t\t\tdata-testid=\"cn-object-parties-main-email\">\n\t\t\t\t<EmailOutline :size=\"16\" aria-hidden=\"true\" />{{ mainParty.email }}</a>\n\t\t\t<a\n\t\t\t\tv-if=\"footLink\"\n\t\t\t\tclass=\"cn-object-parties__main-link\"\n\t\t\t\t:href=\"footLink.href\"\n\t\t\t\tdata-testid=\"cn-object-parties-main-link\">{{ footLink.label }}</a>\n\t\t</div>\n\n\t\t<NcEmptyContent\n\t\t\tv-else-if=\"groups.length === 0\"\n\t\t\t:name=\"label('empty')\"\n\t\t\t:description=\"label('emptyDetail')\"\n\t\t\tdata-testid=\"cn-object-parties-empty\" />\n\n\t\t<template v-else>\n\t\t\t<div\n\t\t\t\tv-if=\"kinds\"\n\t\t\t\tclass=\"cn-object-parties__accepted\"\n\t\t\t\t:data-source=\"kinds.source\"\n\t\t\t\tdata-testid=\"cn-object-parties-accepted\">\n\t\t\t\t<span class=\"cn-object-parties__accepted-label\">{{ label('accepted') }}</span>\n\t\t\t\t<span\n\t\t\t\t\tv-for=\"(kind, index) in kinds.kinds\"\n\t\t\t\t\t:key=\"kindCode(kind)\"\n\t\t\t\t\tclass=\"cn-status-badge cn-status-badge--default cn-object-parties__accepted-kind\"\n\t\t\t\t\tdata-testid=\"cn-object-parties-accepted-kind\">{{ index + 1 }}. {{ kind.label || kindCode(kind) }}</span>\n\t\t\t\t<span v-if=\"kindsSourceText\" class=\"cn-object-parties__muted\">{{ kindsSourceText }}</span>\n\t\t\t</div>\n\n\t\t\t<div\n\t\t\t\tv-if=\"verdicts.length > 0\"\n\t\t\t\tclass=\"cn-object-parties__verdicts\"\n\t\t\t\tdata-testid=\"cn-object-parties-verdicts\">\n\t\t\t\t<NcNoteCard\n\t\t\t\t\tv-for=\"verdict in verdicts\"\n\t\t\t\t\t:key=\"`${verdict.party}-${verdict.key}`\"\n\t\t\t\t\t:type=\"verdict.severity\"\n\t\t\t\t\t:data-effect=\"verdict.effect\"\n\t\t\t\t\tdata-testid=\"cn-object-parties-verdict\">\n\t\t\t\t\t<strong>{{ verdict.label }}</strong>\n\t\t\t\t\t<span>{{ verdict.sentence }}</span>\n\t\t\t\t</NcNoteCard>\n\t\t\t</div>\n\n\t\t\t<section\n\t\t\t\tv-for=\"group in groups\"\n\t\t\t\t:key=\"group.key\"\n\t\t\t\tclass=\"cn-object-parties__role\"\n\t\t\t\t:data-role=\"group.key\"\n\t\t\t\t:aria-labelledby=\"`${idPrefix}-${group.key}`\"\n\t\t\t\tdata-testid=\"cn-object-parties-role\">\n\t\t\t\t<component :is=\"innerHeadingTag\" :id=\"`${idPrefix}-${group.key}`\" class=\"cn-object-parties__role-label cn-inner-heading\">\n\t\t\t\t\t{{ group.label }}\n\t\t\t\t</component>\n\t\t\t\t<ul class=\"cn-object-parties__list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"party in group.parties\"\n\t\t\t\t\t\t:key=\"`${group.key}-${party.partyUuid || party.contactUid}`\"\n\t\t\t\t\t\tclass=\"cn-object-parties__party\"\n\t\t\t\t\t\t:data-party=\"party.partyUuid || party.contactUid\"\n\t\t\t\t\t\tdata-testid=\"cn-object-parties-party\">\n\t\t\t\t\t\t<!-- The board look draws a party as one ruled row (DqZaakBetrokkenen\n\t\t\t\t\t\t \"Rollen\"): an initials tile, the name over one meta line that\n\t\t\t\t\t\t names the role, the pills at the end. -->\n\t\t\t\t\t\t<template v-if=\"isBoard\">\n\t\t\t\t\t\t\t<span class=\"cn-object-parties__tile\" aria-hidden=\"true\">{{ initialsOf(nameOf(party)) }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-object-parties__text\">\n\t\t\t\t\t\t\t\t<span class=\"cn-object-parties__name\">{{ nameOf(party) }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-object-parties__meta\" data-testid=\"cn-object-parties-meta\">{{ boardMetaOf(party, group) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"cn-object-parties__end\">\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\tv-if=\"notAccepted(party)\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-status-badge cn-status-badge--warning\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-not-accepted\">{{ label('notAccepted') }}</span>\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\tv-for=\"indicator in indicatorsFor(party)\"\n\t\t\t\t\t\t\t\t\t:key=\"`${party.partyUuid}-${indicator.key}`\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-status-badge cn-status-badge--default\"\n\t\t\t\t\t\t\t\t\t:class=\"`cn-object-parties__indicator--${indicator.severity}`\"\n\t\t\t\t\t\t\t\t\t:data-effect=\"indicator.effect\"\n\t\t\t\t\t\t\t\t\t:title=\"indicator.verdict\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-indicator\">{{ indicator.label }}</span>\n\t\t\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\t\t\tv-if=\"canRemove && party.partyUuid\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-object-parties__remove\"\n\t\t\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t\t\t:aria-label=\"removeLabel(party, group)\"\n\t\t\t\t\t\t\t\t\t:title=\"removeLabel(party, group)\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-remove\"\n\t\t\t\t\t\t\t\t\t@click=\"removing = { party, group }\">\n\t\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t\t<TrashCanOutline :size=\"20\" />\n\t\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template v-else>\n\t\t\t\t\t\t\t<span class=\"cn-object-parties__name\">{{ nameOf(party) }}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"primary && party.partyUuid === primary\"\n\t\t\t\t\t\t\t\tclass=\"cn-status-badge cn-status-badge--primary\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-primary\">{{ label('primary') }}</span>\n\t\t\t\t\t\t\t<span v-if=\"kindOf(party)\" class=\"cn-object-parties__muted\">{{ kindOf(party) }}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"notAccepted(party)\"\n\t\t\t\t\t\t\t\tclass=\"cn-status-badge cn-status-badge--warning\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-not-accepted\">{{ label('notAccepted') }}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"(line, index) in extraLinesOf(party)\"\n\t\t\t\t\t\t\t\t:key=\"`line-${index}`\"\n\t\t\t\t\t\t\t\tclass=\"cn-object-parties__muted\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-extra-line\">{{ line }}</span>\n\t\t\t\t\t\t\t<span v-if=\"party.email\" class=\"cn-object-parties__muted\">{{ party.email }}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"representedBy(party)\"\n\t\t\t\t\t\t\t\tclass=\"cn-object-parties__muted\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-represented\">{{ label('representedBy') }}: {{ representedBy(party) }}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"correspondenceOf(party)\"\n\t\t\t\t\t\t\t\tclass=\"cn-object-parties__muted\"\n\t\t\t\t\t\t\t\t:data-party-documents=\"party.partyUuid || party.contactUid\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-documents\">{{ correspondenceOf(party) }}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"indicator in indicatorsFor(party)\"\n\t\t\t\t\t\t\t\t:key=\"`${party.partyUuid}-${indicator.key}`\"\n\t\t\t\t\t\t\t\tclass=\"cn-status-badge cn-status-badge--default\"\n\t\t\t\t\t\t\t\t:class=\"`cn-object-parties__indicator--${indicator.severity}`\"\n\t\t\t\t\t\t\t\t:data-effect=\"indicator.effect\"\n\t\t\t\t\t\t\t\t:title=\"indicator.verdict\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-indicator\">{{ indicator.label }} · {{ indicator.verdict }}</span>\n\t\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\t\tv-if=\"canRemove && party.partyUuid\"\n\t\t\t\t\t\t\t\tclass=\"cn-object-parties__remove\"\n\t\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t\t:aria-label=\"removeLabel(party, group)\"\n\t\t\t\t\t\t\t\t:title=\"removeLabel(party, group)\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-remove\"\n\t\t\t\t\t\t\t\t@click=\"removing = { party, group }\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<TrashCanOutline :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t</template>\n\n\t\t<CnPartyAddDialog\n\t\t\tv-if=\"adding\"\n\t\t\tref=\"addDialog\"\n\t\t\t:dialogTitle=\"manageWord('add', 'dialogTitle', t('nextcloud-vue', 'Add a party'))\"\n\t\t\t:roles=\"addRoles\"\n\t\t\t:sources=\"addSources\"\n\t\t\t:createFields=\"createFields\"\n\t\t\t:createLabel=\"manageWord('add', 'createLabel', t('nextcloud-vue', 'Someone new'))\"\n\t\t\t:allowPrimary=\"content.manage.add.primary === true\"\n\t\t\t:primaryLabel=\"label('primary')\"\n\t\t\t:confirmLabel=\"manageWord('add', 'confirmLabel', t('nextcloud-vue', 'Add'))\"\n\t\t\t@close=\"adding = false\"\n\t\t\t@confirm=\"addParty\" />\n\n\t\t<CnConfirmDialog\n\t\t\tv-if=\"removing\"\n\t\t\tref=\"removeDialog\"\n\t\t\t:dialogTitle=\"manageWord('remove', 'title', t('nextcloud-vue', 'Remove party'))\"\n\t\t\t:message=\"removeMessage\"\n\t\t\t:confirmLabel=\"manageWord('remove', 'confirmLabel', t('nextcloud-vue', 'Remove'))\"\n\t\t\tvariant=\"error\"\n\t\t\t@close=\"removing = null\"\n\t\t\t@confirm=\"removeParty\" />\n\t</div>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcEmptyContent, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport EmailOutline from 'vue-material-design-icons/EmailOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'\nimport CnConfirmDialog from '../../dialogs/CnConfirmDialog.vue'\nimport CnPartyAddDialog from '../../dialogs/CnPartyAddDialog.vue'\nimport { interpolateUrlTokens } from '../../composables/useEndpointSource.js'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport innerHeading from '../../mixins/innerHeading.js'\nimport { conditionalLine } from '../../utils/conditionalLine.js'\nimport { initialsOf } from '../../utils/initials.js'\nimport {\n\tdocumentsOfParty,\n\tindicatorsOf,\n\tindicatorVerdict,\n\tkindCode,\n\tnewPartyRecord,\n\trepresentedByMap,\n\troleLabel,\n\trolesInOrder,\n\tsavedUuid,\n} from '../../utils/objectParties.js'\nimport { fetchCallerScope } from '../../utils/objectPermissions.js'\nimport { readPath } from '../../utils/readPath.js'\n\nlet uid = 0\n\n/**\n * The default words, keyed as `content.labels` overrides them.\n *\n * @return {object} The labels.\n */\nfunction defaultLabels() {\n\treturn {\n\t\tfailed: t('nextcloud-vue', 'The parties could not be read'),\n\t\tfailedDetail: t('nextcloud-vue', 'OpenRegister did not answer. Nothing has been removed.'),\n\t\tempty: t('nextcloud-vue', 'No parties yet'),\n\t\temptyDetail: t('nextcloud-vue', 'Add a party to record who this is for and who acts on it.'),\n\t\taccepted: t('nextcloud-vue', 'Accepted kinds'),\n\t\tprimary: t('nextcloud-vue', 'Primary party'),\n\t\tnotAccepted: t('nextcloud-vue', 'Kind not accepted here'),\n\t\trepresentedBy: t('nextcloud-vue', 'Represented by'),\n\t\tnoPrimary: t('nextcloud-vue', 'No main contact yet'),\n\t}\n}\n\n/**\n * CnObjectPartiesWidget — the `parties` widget: a record's parties as\n * OpenRegister's party model answers them, grouped by role with the primary\n * party first. Each party shows its kind, its representative and the\n * indicators on it, each with its verdict. A blocking verdict is also a note\n * above the list, so nobody publishes or sends past it.\n *\n * ```json\n * {\n * \"type\": \"parties\",\n * \"content\": {\n * \"rolesObjects\": { \"register\": \"myapp\", \"schema\": \"role\", \"field\": \"item\" },\n * \"roleLabels\": { \"applicant\": \"Applicant\" },\n * \"kindsSource\": { \"url\": \"/apps/myapp/api/items/@objectId/party-kinds\", \"enforce\": \"crm\" },\n * \"extraLines\": [{ \"endpoint\": \"/apps/myapp/api/items/@objectId/parties/{party}/language\", \"lines\": [\"Language: {language}\"] }],\n * \"correspondence\": { \"endpoint\": \"/apps/myapp/api/items/@objectId/documents\", \"path\": \"documents\" }\n * }\n * }\n * ```\n *\n * `manage` lets a reader change the parties through OpenRegister's party\n * API, which asks the record's update right on every write: `add` (`{ label?,\n * dialogTitle?, kind?, sources, create?, primary?, confirmLabel? }`) opens a\n * dialog to pick somebody known or describe somebody new (`create: {\n * register, schema, label?, fields, compose? }`, see `newPartyRecord`), choose\n * a role and optionally make them the primary party; `remove` (`{ title?,\n * message?, confirmLabel? }`, `{name}` and `{role}` in the message) takes a\n * party off in one role. Both are hidden when OpenRegister says the reader\n * may not update the schema; a refusal is shown, never swallowed.\n *\n * `presentation: \"primary\"` draws only the primary party, as a side card\n * does: initials, name and role label, its correspondence e-mail, and\n * `link` (`{ label, href? | tab? }`, `{total}` = the number of parties) at\n * the foot; `tab` opens that tab of the page through the route hash. It\n * reads the listing only.\n *\n * A failed read says so and is never shown as \"no parties\".\n *\n * @spec openspec/changes/dq-abstract-record-widgets/specs/record-widgets/spec.md#requirement-a-record-shows-its-parties-by-role\n */\nexport default {\n\tname: 'CnObjectPartiesWidget',\n\n\tcomponents: { CnConfirmDialog, CnPartyAddDialog, EmailOutline, NcButton, NcEmptyContent, NcLoadingIcon, NcNoteCard, Plus, TrashCanOutline },\n\n\tmixins: [innerHeading],\n\n\tinject: {\n\t\tcnTranslate: { default: () => (key) => key },\n\t\tcnLook: { default: 'nextcloud' },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The configuration: `rolesSource` (the listing URL, default\n\t\t * OpenRegister's `/parties` of this record), `partySource` (one\n\t\t * party, `{uuid}` filled in), `rolesObjects` (`{ register, schema,\n\t\t * field }`, the role rows that say who represents whom), `roleLabels`,\n\t\t * `kindsSource` (`{ url, enforce?, sourceText? }`), `extraLines`\n\t\t * (`[{ endpoint, path?, valueLabels?, lines }]`, one request per\n\t\t * party, `{party}` filled in), `correspondence` (`{ endpoint, path?,\n\t\t * senderField?, recipientsField? }`), `labels`, `presentation`\n\t\t * (`\"primary\"`: only the primary party) and `link` (`{ label,\n\t\t * href? | tab? }`, the primary presentation's foot link) and\n\t\t * `manage` (`{ add?, remove? }`, adding and removing parties).\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The record's id, bound by the detail host. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The record's register slug. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The record's schema slug. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\tuid += 1\n\t\treturn {\n\t\t\tidPrefix: `cn-object-parties-${uid}`,\n\t\t\tloading: true,\n\t\t\tfailed: false,\n\t\t\tlisting: null,\n\t\t\tpartyRecords: {},\n\t\t\trepresented: {},\n\t\t\tkinds: null,\n\t\t\textra: {},\n\t\t\tdocuments: [],\n\t\t\tmayUpdate: true,\n\t\t\tadding: false,\n\t\t\tremoving: null,\n\t\t\tmanageError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {boolean} The board look draws one ruled row per party. */\n\t\tisBoard() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/** @return {Array<object>} The role groups, primary first. */\n\t\tgroups() {\n\t\t\treturn rolesInOrder(this.listing, this.translatedMap(this.content?.roleLabels))\n\t\t},\n\n\t\t/** @return {object} The `manage` declaration, or an empty one. */\n\t\tmanage() {\n\t\t\tconst manage = this.content?.manage\n\t\t\treturn manage && typeof manage === 'object' ? manage : {}\n\t\t},\n\n\t\t/** @return {boolean} Whether the add button shows. */\n\t\tcanAdd() {\n\t\t\treturn Boolean(this.manage.add) && this.mayUpdate && !this.isPrimaryPresentation && !this.loading && !this.failed\n\t\t},\n\n\t\t/** @return {boolean} Whether each party offers its remove button. */\n\t\tcanRemove() {\n\t\t\treturn Boolean(this.manage.remove) && this.mayUpdate && !this.isPrimaryPresentation\n\t\t},\n\n\t\t/** @return {Array<{key: string, label: string}>} The roles a new party may hold, in the reader's words. */\n\t\taddRoles() {\n\t\t\tconst declared = Array.isArray(this.listing?.roles) ? this.listing.roles : []\n\t\t\tconst kind = this.manage.add?.kind\n\t\t\tconst accepted = kind ? (this.listing?.kinds || []).find((entry) => kindCode(entry) === kind)?.roles : null\n\t\t\tconst keys = Array.isArray(accepted) && accepted.length > 0 ? accepted : declared.map((role) => role?.key).filter(Boolean)\n\t\t\tconst own = this.translatedMap(this.content?.roleLabels)\n\t\t\treturn keys.map((key) => ({ key, label: own[key] || this.cnTranslate(roleLabel(key, declared, {})) }))\n\t\t},\n\n\t\t/** @return {Array<object>} The picker sources, their words translated by the picker. */\n\t\taddSources() {\n\t\t\treturn Array.isArray(this.manage.add?.sources) ? this.manage.add.sources : []\n\t\t},\n\n\t\t/** @return {Array<object>} The new-person fields, labels translated. */\n\t\tcreateFields() {\n\t\t\tconst fields = this.manage.add?.create?.fields\n\t\t\treturn (Array.isArray(fields) ? fields : [])\n\t\t\t\t.filter((field) => field && field.key)\n\t\t\t\t.map((field) => ({ ...field, label: this.cnTranslate(field.label || field.key) }))\n\t\t},\n\n\t\t/** @return {string} The question the remove dialog asks. */\n\t\tremoveMessage() {\n\t\t\tif (!this.removing) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst name = this.nameOf(this.removing.party)\n\t\t\tconst role = this.removing.group.label\n\t\t\tconst own = this.manage.remove?.message\n\t\t\treturn own\n\t\t\t\t? this.cnTranslate(own).replaceAll('{name}', name).replaceAll('{role}', role)\n\t\t\t\t: t('nextcloud-vue', 'Remove {name} as {role}?', { name, role }, undefined, { escape: false })\n\t\t},\n\n\t\t/** @return {boolean} Whether only the primary party is drawn. */\n\t\tisPrimaryPresentation() {\n\t\t\treturn this.content?.presentation === 'primary'\n\t\t},\n\n\t\t/** @return {object|null} The primary party's listing row. */\n\t\tmainParty() {\n\t\t\tif (!this.primary) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (this.listing?.results || []).find((row) => row && row.partyUuid === this.primary) || null\n\t\t},\n\n\t\t/** @return {string} The primary party's role, in the app's words. */\n\t\tmainRole() {\n\t\t\tconst key = this.mainParty?.role\n\t\t\tif (!key) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst own = this.translatedMap(this.content?.roleLabels)\n\t\t\tif (typeof own[key] === 'string' && own[key] !== '') {\n\t\t\t\treturn own[key]\n\t\t\t}\n\t\t\treturn this.cnTranslate(roleLabel(key, this.listing?.roles, {}))\n\t\t},\n\n\t\t/** @return {{label: string, href: string}|null} The primary presentation's foot link; none without parties. */\n\t\tfootLink() {\n\t\t\tconst link = this.content?.link\n\t\t\tconst total = Number(this.listing?.total ?? (this.listing?.results || []).length)\n\t\t\tif (!link || typeof link !== 'object' || !link.label || !(total > 0)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst href = link.tab ? `#${encodeURIComponent(link.tab)}` : (link.href || '')\n\t\t\tif (href === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn { label: this.cnTranslate(link.label).replaceAll('{total}', String(total)), href }\n\t\t},\n\n\t\t/** @return {string|null} The primary party's uuid. */\n\t\tprimary() {\n\t\t\treturn this.listing?.primary || null\n\t\t},\n\n\t\t/** @return {object} The verdict words, translated. */\n\t\tverdictLabels() {\n\t\t\tconst own = this.content?.labels || {}\n\t\t\tconst out = {}\n\t\t\tfor (const key of ['verdictPublication', 'verdictSend', 'verdictWarn']) {\n\t\t\t\tif (typeof own[key] === 'string' && own[key] !== '') {\n\t\t\t\t\tout[key] = this.cnTranslate(own[key])\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/** @return {Array<object>} The blocking verdicts, as notes above the list. */\n\t\tverdicts() {\n\t\t\treturn indicatorsOf(Object.values(this.partyRecords))\n\t\t\t\t.map((indicator) => ({\n\t\t\t\t\t...indicatorVerdict(indicator, this.verdictLabels),\n\t\t\t\t\tparty: indicator.party,\n\t\t\t\t\tkey: indicator.key,\n\t\t\t\t\tsentence: this.sentenceFor(indicator),\n\t\t\t\t}))\n\t\t\t\t.filter((verdict) => verdict.severity === 'error')\n\t\t},\n\n\t\t/** @return {string} Where the accepted kinds come from. */\n\t\tkindsSourceText() {\n\t\t\tconst texts = this.content?.kindsSource?.sourceText\n\t\t\tif (!this.kinds || !texts || typeof texts !== 'object') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst text = texts[this.kinds.source] || texts.default || ''\n\t\t\treturn text ? this.cnTranslate(text) : ''\n\t\t},\n\n\t\t/** @return {Record<string, string>} The kind labels the listing declares. */\n\t\tkindLabels() {\n\t\t\tconst labels = {}\n\t\t\tfor (const kind of this.listing?.kinds || []) {\n\t\t\t\tif (kind && kind.key) {\n\t\t\t\t\tlabels[kind.key] = kind.label || kind.key\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn labels\n\t\t},\n\n\t\t/** @return {string} A key that changes when the record does. */\n\t\trecordKey() {\n\t\t\treturn `${this.register}|${this.schema}|${this.objectId}`\n\t\t},\n\t},\n\n\twatch: {\n\t\trecordKey: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.onPageRefresh = (payload) => {\n\t\t\tif (payload?.source !== this.idPrefix) {\n\t\t\t\tthis.load()\n\t\t\t}\n\t\t}\n\t\tsubscribe('cn:page:refresh', this.onPageRefresh)\n\t},\n\n\tbeforeUnmount() {\n\t\tunsubscribe('cn:page:refresh', this.onPageRefresh)\n\t},\n\n\tmethods: {\n\t\tt,\n\t\tinitialsOf,\n\t\tkindCode,\n\n\t\t/**\n\t\t * A label: the content's own (translated), else the default.\n\t\t *\n\t\t * @param {string} key The label key.\n\t\t * @return {string}\n\t\t */\n\t\tlabel(key) {\n\t\t\tconst own = this.content?.labels && typeof this.content.labels[key] === 'string' ? this.content.labels[key] : ''\n\t\t\treturn own !== '' ? this.cnTranslate(own) : defaultLabels()[key]\n\t\t},\n\n\t\t/**\n\t\t * A word of the `manage` declaration, translated, else the default.\n\t\t *\n\t\t * @param {string} part `add` or `remove`.\n\t\t * @param {string} key The word's key.\n\t\t * @param {string} fallback The default.\n\t\t * @return {string}\n\t\t */\n\t\tmanageWord(part, key, fallback) {\n\t\t\tconst own = this.manage[part]?.[key]\n\t\t\treturn typeof own === 'string' && own !== '' ? this.cnTranslate(own) : fallback\n\t\t},\n\n\t\t/**\n\t\t * The remove button's accessible name.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @param {object} group The role group.\n\t\t * @return {string}\n\t\t */\n\t\tremoveLabel(party, group) {\n\t\t\treturn t('nextcloud-vue', 'Remove {name} as {role}', { name: this.nameOf(party), role: group.label }, undefined, { escape: false })\n\t\t},\n\n\t\t/**\n\t\t * Why a write failed, in the reader's words.\n\t\t *\n\t\t * @param {unknown} error The axios error.\n\t\t * @return {string}\n\t\t */\n\t\trefusalOf(error) {\n\t\t\tif (error?.response?.status === 403) {\n\t\t\t\treturn t('nextcloud-vue', 'You may not change who is on this record.')\n\t\t\t}\n\t\t\tconst said = error?.response?.data?.error\n\t\t\treturn typeof said === 'string' && said !== '' ? said : t('nextcloud-vue', 'The change could not be saved. Nothing was changed.')\n\t\t},\n\n\t\t/** @return {string} The record's parties URL. */\n\t\tpartiesUrl() {\n\t\t\treturn this.urlOf(this.content?.rolesSource || '/apps/openregister/api/objects/@register/@schema/@objectId/parties')\n\t\t},\n\n\t\t/** @return {Promise<void>} Whether OpenRegister lets the reader update this schema; unknown counts as yes, the server decides. */\n\t\tasync loadMayUpdate() {\n\t\t\tif (!this.manage.add && !this.manage.remove) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst slug = (value) => (value && typeof value === 'object' ? String(value.slug || value.id || '') : String(value || ''))\n\t\t\tconst scope = await fetchCallerScope({ register: slug(this.register), schema: slug(this.schema) })\n\t\t\tthis.mayUpdate = !scope || !Array.isArray(scope.actions) || scope.actions.includes('update')\n\t\t},\n\n\t\t/**\n\t\t * Create the person when they are new, then give them the role; a\n\t\t * primary party is set through the primary route, which records the\n\t\t * replacement.\n\t\t *\n\t\t * @param {{mode: string, partyUuid: string|null, values: object, role: string, primary: boolean}} choice The dialog's choice.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync addParty(choice) {\n\t\t\ttry {\n\t\t\t\tlet partyUuid = choice.partyUuid\n\t\t\t\tif (choice.mode === 'new') {\n\t\t\t\t\tconst create = this.manage.add?.create || {}\n\t\t\t\t\tconst { data } = await axios.post(\n\t\t\t\t\t\tgenerateUrl(`/apps/openregister/api/objects/${encodeURIComponent(create.register || '')}/${encodeURIComponent(create.schema || '')}`),\n\t\t\t\t\t\tnewPartyRecord(create, choice.values),\n\t\t\t\t\t)\n\t\t\t\t\tpartyUuid = savedUuid(data)\n\t\t\t\t\tif (partyUuid === '') {\n\t\t\t\t\t\tthrow new Error(t('nextcloud-vue', 'The new person was not saved.'))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (choice.primary) {\n\t\t\t\t\tawait axios.put(generateUrl(`${this.partiesUrl()}/primary`), { partyUuid, role: choice.role })\n\t\t\t\t} else {\n\t\t\t\t\tawait axios.post(generateUrl(this.partiesUrl()), { partyUuid, role: choice.role })\n\t\t\t\t}\n\t\t\t\tthis.adding = false\n\t\t\t\tthis.changed()\n\t\t\t} catch (error) {\n\t\t\t\tthis.$refs.addDialog?.fail(error?.response ? this.refusalOf(error) : (error?.message || this.refusalOf(error)))\n\t\t\t}\n\t\t},\n\n\t\t/** @return {Promise<void>} Take the chosen party off in the chosen role. */\n\t\tasync removeParty() {\n\t\t\tconst { party, group } = this.removing || {}\n\t\t\tif (!party) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait axios.delete(generateUrl(`${this.partiesUrl()}/${encodeURIComponent(party.partyUuid)}`), { params: { role: group.key } })\n\t\t\t\tthis.removing = null\n\t\t\t\tthis.changed()\n\t\t\t} catch (error) {\n\t\t\t\tthis.$refs.removeDialog?.setResult({ error: this.refusalOf(error) })\n\t\t\t}\n\t\t},\n\n\t\t/** @return {void} Read the parties again and tell the page (tab counts, the side card). */\n\t\tchanged() {\n\t\t\tthis.manageError = ''\n\t\t\tthis.load()\n\t\t\temit('cn:page:refresh', { source: this.idPrefix })\n\t\t},\n\n\t\t/**\n\t\t * A map of manifest words, translated.\n\t\t *\n\t\t * @param {object} map The map.\n\t\t * @return {Record<string, string>}\n\t\t */\n\t\ttranslatedMap(map) {\n\t\t\tconst out = {}\n\t\t\tfor (const [key, value] of Object.entries(map && typeof map === 'object' ? map : {})) {\n\t\t\t\tout[key] = typeof value === 'string' ? this.cnTranslate(value) : value\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * An app path with the record's tokens and `{party}` filled in.\n\t\t *\n\t\t * @param {string} template The path.\n\t\t * @param {string} [party] A party uuid.\n\t\t * @return {string}\n\t\t */\n\t\turlOf(template, party = '') {\n\t\t\tconst withSlugs = String(template)\n\t\t\t\t.replaceAll('@register', String(this.register || ''))\n\t\t\t\t.replaceAll('@schema', String(this.schema || ''))\n\t\t\treturn interpolateUrlTokens(withSlugs, { objectId: this.objectId }).replaceAll('{party}', encodeURIComponent(party)).replaceAll('{uuid}', encodeURIComponent(party))\n\t\t},\n\n\t\t/**\n\t\t * GET an app path; null when it fails.\n\t\t *\n\t\t * @param {string} path The app path.\n\t\t * @param {object} [params] Query parameters.\n\t\t * @return {Promise<unknown>}\n\t\t */\n\t\tasync read(path, params) {\n\t\t\ttry {\n\t\t\t\tconst { data } = await axios.get(generateUrl(path), params ? { params } : undefined)\n\t\t\t\treturn data ?? null\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read the listing, then each party, the representation, the kinds,\n\t\t * the extra lines and the correspondence.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tif (!this.objectId) {\n\t\t\t\tthis.loading = false\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.failed = false\n\t\t\tthis.partyRecords = {}\n\t\t\tthis.represented = {}\n\t\t\tthis.extra = {}\n\t\t\tthis.documents = []\n\t\t\tconst listing = await this.read(this.urlOf(this.content?.rolesSource || '/apps/openregister/api/objects/@register/@schema/@objectId/parties'))\n\t\t\tif (!listing || typeof listing !== 'object') {\n\t\t\t\tthis.failed = true\n\t\t\t\tthis.listing = null\n\t\t\t\tthis.loading = false\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.listing = listing\n\t\t\tawait this.loadMayUpdate()\n\t\t\tif (this.isPrimaryPresentation) {\n\t\t\t\tthis.loading = false\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst uuids = [...new Set((listing.results || []).map((row) => row && row.partyUuid).filter(Boolean))]\n\t\t\tconst partyUrl = this.content?.partySource || '/apps/openregister/api/parties/{uuid}'\n\t\t\tconst records = await Promise.all(uuids.map((uuid) => this.read(this.urlOf(partyUrl, uuid))))\n\t\t\tconst byUuid = {}\n\t\t\tuuids.forEach((uuid, index) => {\n\t\t\t\tif (records[index] && typeof records[index] === 'object') {\n\t\t\t\t\tbyUuid[uuid] = records[index]\n\t\t\t\t}\n\t\t\t})\n\t\t\tthis.partyRecords = byUuid\n\t\t\tawait Promise.all([this.loadRepresentation(), this.loadKinds(), this.loadExtraLines(uuids), this.loadCorrespondence()])\n\t\t\tthis.loading = false\n\t\t},\n\n\t\t/** @return {Promise<void>} Who represents whom, from the role rows. */\n\t\tasync loadRepresentation() {\n\t\t\tconst source = this.content?.rolesObjects\n\t\t\tif (!source || !source.register || !source.schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst data = await this.read(`/apps/openregister/api/objects/${encodeURIComponent(source.register)}/${encodeURIComponent(source.schema)}`, { [source.field || 'object']: String(this.objectId), _limit: 200 })\n\t\t\tconst rows = Array.isArray(data?.results) ? data.results : (Array.isArray(data) ? data : [])\n\t\t\tthis.represented = representedByMap(rows)\n\t\t},\n\n\t\t/** @return {Promise<void>} The kinds this record accepts. */\n\t\tasync loadKinds() {\n\t\t\tconst source = this.content?.kindsSource\n\t\t\tthis.kinds = null\n\t\t\tif (!source || !source.url) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst data = await this.read(this.urlOf(source.url))\n\t\t\tthis.kinds = data && Array.isArray(data.kinds) ? data : null\n\t\t},\n\n\t\t/**\n\t\t * One answer per party per extra line.\n\t\t *\n\t\t * @param {Array<string>} uuids The parties.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadExtraLines(uuids) {\n\t\t\tconst lines = Array.isArray(this.content?.extraLines) ? this.content.extraLines : []\n\t\t\tconst extra = {}\n\t\t\tawait Promise.all(lines.map(async (spec, index) => {\n\t\t\t\tif (!spec || !spec.endpoint) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst answers = await Promise.all(uuids.map((uuid) => this.read(this.urlOf(spec.endpoint, uuid))))\n\t\t\t\tuuids.forEach((uuid, i) => {\n\t\t\t\t\tconst answer = spec.path ? readPath(answers[i], spec.path) : answers[i]\n\t\t\t\t\tif (answer && typeof answer === 'object') {\n\t\t\t\t\t\textra[`${index}|${uuid}`] = answer\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}))\n\t\t\tthis.extra = extra\n\t\t},\n\n\t\t/** @return {Promise<void>} The documents the correspondence counts read. */\n\t\tasync loadCorrespondence() {\n\t\t\tconst spec = this.content?.correspondence\n\t\t\tif (!spec || !spec.endpoint) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst data = await this.read(this.urlOf(spec.endpoint))\n\t\t\tconst list = spec.path ? readPath(data, spec.path) : data\n\t\t\tthis.documents = Array.isArray(list) ? list : []\n\t\t},\n\n\t\t/**\n\t\t * The extra lines for a party, each the first of its `lines` that applies.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @return {Array<string>}\n\t\t */\n\t\textraLinesOf(party) {\n\t\t\tconst specs = Array.isArray(this.content?.extraLines) ? this.content.extraLines : []\n\t\t\treturn specs.map((spec, index) => {\n\t\t\t\tconst answer = this.extra[`${index}|${party.partyUuid}`]\n\t\t\t\tif (!answer) {\n\t\t\t\t\treturn ''\n\t\t\t\t}\n\t\t\t\treturn conditionalLine(spec.lines || [], this.labelled(answer, spec.valueLabels), (text) => this.cnTranslate(text))\n\t\t\t}).filter((line) => line !== '')\n\t\t},\n\n\t\t/**\n\t\t * An answer with its coded values replaced by their labels\n\t\t * (`valueLabels: { language: { nl: \"Dutch\" } }`; `nl-NL` reads as `nl`).\n\t\t *\n\t\t * @param {object} answer The answer.\n\t\t * @param {object} valueLabels The labels by field and value.\n\t\t * @return {object}\n\t\t */\n\t\tlabelled(answer, valueLabels) {\n\t\t\tif (!valueLabels || typeof valueLabels !== 'object') {\n\t\t\t\treturn answer\n\t\t\t}\n\t\t\tconst out = { ...answer }\n\t\t\tfor (const [field, map] of Object.entries(valueLabels)) {\n\t\t\t\tconst raw = answer[field]\n\t\t\t\tif (typeof raw !== 'string' || !map) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst hit = map[raw] ?? map[raw.toLowerCase().split('-')[0]]\n\t\t\t\tif (typeof hit === 'string') {\n\t\t\t\t\tout[field] = this.cnTranslate(hit)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Whether a party's kind is one this record does not accept. Only when\n\t\t * the kinds come from the source named by `kindsSource.enforce`.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @return {boolean}\n\t\t */\n\t\tnotAccepted(party) {\n\t\t\tconst enforce = this.content?.kindsSource?.enforce\n\t\t\tif (!party?.partyKind || !this.kinds || (enforce && this.kinds.source !== enforce)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn !this.kinds.kinds.some((kind) => kindCode(kind) === party.partyKind)\n\t\t},\n\n\t\t/**\n\t\t * A party's kind: the accepted kind's label, else the listing's, else the code.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @return {string}\n\t\t */\n\t\tkindOf(party) {\n\t\t\tif (!party.partyKind) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst offered = (this.kinds?.kinds || []).find((kind) => kindCode(kind) === party.partyKind)\n\t\t\treturn offered?.label || this.kindLabels[party.partyKind] || party.partyKind\n\t\t},\n\n\t\t/**\n\t\t * Who represents this party, by name.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @return {string}\n\t\t */\n\t\trepresentedBy(party) {\n\t\t\tconst uuid = this.represented[party.partyUuid || party.contactUid || ''] || ''\n\t\t\tif (uuid === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst row = (this.listing?.results || []).find((candidate) => candidate?.partyUuid === uuid)\n\t\t\treturn row ? this.nameOf(row) : (this.partyRecords[uuid]?.name || uuid)\n\t\t},\n\n\t\t/**\n\t\t * A party's name: the listing's, else the party record's, else its id.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @return {string}\n\t\t */\n\t\tnameOf(party) {\n\t\t\treturn party.displayName || this.partyRecords[party.partyUuid]?.name || party.contactUid || party.partyUuid || ''\n\t\t},\n\n\t\t/**\n\t\t * How many documents a party sent and received.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @return {string}\n\t\t */\n\t\tcorrespondenceOf(party) {\n\t\t\tconst spec = this.content?.correspondence || {}\n\t\t\tconst { sent, received } = documentsOfParty(this.documents, party.partyUuid || party.contactUid || '', spec)\n\t\t\tif (sent.length === 0 && received.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (received.length === 0) {\n\t\t\t\treturn n('nextcloud-vue', '%n document sent', '%n documents sent', sent.length)\n\t\t\t}\n\t\t\tif (sent.length === 0) {\n\t\t\t\treturn n('nextcloud-vue', '%n document received', '%n documents received', received.length)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{sent} sent, {received} received', { sent: sent.length, received: received.length })\n\t\t},\n\n\t\t/**\n\t\t * The indicators on a party, each with its verdict.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @return {Array<object>}\n\t\t */\n\t\t/**\n\t\t * The board row's meta line: the primary mark, the role, the kind,\n\t\t * whom the party represents, its extra lines, e-mail and documents,\n\t\t * separated by middots.\n\t\t *\n\t\t * @param {object} party The party.\n\t\t * @param {object} group Its role group.\n\t\t * @return {string}\n\t\t */\n\t\tboardMetaOf(party, group) {\n\t\t\tconst parts = []\n\t\t\tif (this.primary && party.partyUuid === this.primary) {\n\t\t\t\tparts.push(this.label('primary'))\n\t\t\t}\n\t\t\tparts.push(group.label, this.kindOf(party))\n\t\t\tconst represented = this.representedBy(party)\n\t\t\tif (represented) {\n\t\t\t\tparts.push(`${this.label('representedBy')}: ${represented}`)\n\t\t\t}\n\t\t\tparts.push(...this.extraLinesOf(party), party.email, this.correspondenceOf(party))\n\t\t\treturn parts.filter((part) => typeof part === 'string' && part !== '').join(' · ')\n\t\t},\n\n\t\tindicatorsFor(party) {\n\t\t\tconst record = this.partyRecords[party.partyUuid]\n\t\t\tif (!record) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn (record.indicators || []).filter(Boolean).map((indicator) => ({ ...indicatorVerdict(indicator, this.verdictLabels), key: indicator.key }))\n\t\t},\n\n\t\t/**\n\t\t * The sentence of a blocking verdict, naming the party.\n\t\t *\n\t\t * @param {object} indicator The indicator with `partyName`.\n\t\t * @return {string}\n\t\t */\n\t\tsentenceFor(indicator) {\n\t\t\tconst name = indicator.partyName || indicator.party\n\t\t\tconst own = this.content?.labels || {}\n\t\t\tif (indicatorVerdict(indicator).effect === 'refuse-publication') {\n\t\t\t\treturn own.sentencePublication\n\t\t\t\t\t? this.cnTranslate(own.sentencePublication).replaceAll('{party}', name)\n\t\t\t\t\t: t('nextcloud-vue', 'Publishing a file on this record is refused, because of {party}.', { party: name }, undefined, { escape: false })\n\t\t\t}\n\t\t\treturn own.sentenceSend\n\t\t\t\t? this.cnTranslate(own.sentenceSend).replaceAll('{party}', name)\n\t\t\t\t: t('nextcloud-vue', 'A message to {party} is refused.', { party: name }, undefined, { escape: false })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-object-parties {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(var(--default-grid-baseline, 4px) * 3);\n}\n\n.cn-object-parties__verdicts {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: var(--default-grid-baseline, 4px);\n}\n\n.cn-object-parties__role-label {\n\tmargin: 0 0 var(--default-grid-baseline, 4px) 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-object-parties__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: var(--default-grid-baseline, 4px);\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-object-parties__party {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: calc(var(--default-grid-baseline, 4px) * 2);\n\tpadding: var(--default-grid-baseline, 4px);\n\tborder-radius: var(--border-radius, 4px);\n}\n\n.cn-object-parties__party:hover {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-object-parties__name {\n\tfont-weight: bold;\n}\n\n.cn-object-parties__muted {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-object-parties__manage {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n}\n\n.cn-object-parties__remove {\n\tmargin-inline-start: auto;\n}\n\n.cn-object-parties__main-card {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(var(--default-grid-baseline, 4px) * 3);\n}\n\n.cn-object-parties__main {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: calc(var(--default-grid-baseline, 4px) * 3);\n}\n\n.cn-object-parties__main-initials {\n\tdisplay: flex;\n\tflex: none;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 40px;\n\theight: 40px;\n\tborder-radius: 50%;\n\tbackground-color: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\tfont-weight: 700;\n}\n\n.cn-object-parties__main-text {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-object-parties__main-name {\n\tfont-size: 15px;\n}\n\n.cn-object-parties__main-role {\n\tfont-size: 13px;\n}\n\n.cn-object-parties__main-email {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: calc(var(--default-grid-baseline, 4px) * 2.5);\n\tcolor: var(--color-main-text);\n\toverflow-wrap: anywhere;\n}\n\n.cn-object-parties__main-link {\n\tcolor: var(--color-primary-element);\n\tfont-weight: 600;\n}\n\n.cn-object-parties__accepted {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: calc(var(--default-grid-baseline, 4px) * 2);\n\tpadding: calc(var(--default-grid-baseline, 4px) * 2) calc(var(--default-grid-baseline, 4px) * 3);\n\tborder-radius: var(--border-radius-large, 10px);\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-object-parties__accepted-label {\n\tfont-weight: 600;\n}\n\n.cn-object-parties__accepted-kind {\n\tborder: 1px solid var(--color-border-dark);\n}\n</style>\n"],"names":["_normalizeClass","_createVNode","_toDisplayString","_createBlock","_openBlock","_createElementBlock","_createElementVNode","_createTextVNode","_Fragment","_renderList","_resolveDynamicComponent"],"mappings":"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"}
1
+ {"version":3,"file":"CnObjectPartiesWidget.vue3.js","sources":["../../../../src/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue?vue&type=template&id=24a3d77a&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-object-parties\" :class=\"{ 'cn-object-parties--board': isBoard }\" data-testid=\"cn-object-parties\">\n\t\t<div v-if=\"canAdd\" class=\"cn-object-parties__manage\">\n\t\t\t<NcButton data-testid=\"cn-object-parties-add\" @click=\"adding = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ manageWord('add', 'label', t('nextcloud-vue', 'Add a party')) }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcNoteCard\n\t\t\tv-if=\"manageError\"\n\t\t\ttype=\"error\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-object-parties-manage-error\">\n\t\t\t{{ manageError }}\n\t\t</NcNoteCard>\n\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\n\t\t<NcEmptyContent\n\t\t\tv-else-if=\"failed\"\n\t\t\t:name=\"label('failed')\"\n\t\t\t:description=\"label('failedDetail')\"\n\t\t\tdata-testid=\"cn-object-parties-failed\" />\n\n\t\t<div\n\t\t\tv-else-if=\"isPrimaryPresentation\"\n\t\t\tclass=\"cn-object-parties__main-card\"\n\t\t\tdata-testid=\"cn-object-parties-main-card\">\n\t\t\t<div\n\t\t\t\tv-if=\"mainParty\"\n\t\t\t\tclass=\"cn-object-parties__main\"\n\t\t\t\t:data-party=\"mainParty.partyUuid\"\n\t\t\t\tdata-testid=\"cn-object-parties-main\">\n\t\t\t\t<span class=\"cn-object-parties__main-initials\" aria-hidden=\"true\" data-testid=\"cn-object-parties-main-initials\">{{ initialsOf(nameOf(mainParty)) }}</span>\n\t\t\t\t<span class=\"cn-object-parties__main-text\">\n\t\t\t\t\t<strong class=\"cn-object-parties__main-name\" data-testid=\"cn-object-parties-main-name\">{{ nameOf(mainParty) }}</strong>\n\t\t\t\t\t<span v-if=\"mainRole\" class=\"cn-object-parties__muted cn-object-parties__main-role\" data-testid=\"cn-object-parties-main-role\">{{ mainRole }}</span>\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t\t<span v-else class=\"cn-object-parties__muted\" data-testid=\"cn-object-parties-main-none\">{{ label('noPrimary') }}</span>\n\t\t\t<a\n\t\t\t\tv-if=\"mainParty && mainParty.email\"\n\t\t\t\tclass=\"cn-object-parties__main-email\"\n\t\t\t\t:href=\"`mailto:${mainParty.email}`\"\n\t\t\t\tdata-testid=\"cn-object-parties-main-email\">\n\t\t\t\t<EmailOutline :size=\"16\" aria-hidden=\"true\" />{{ mainParty.email }}</a>\n\t\t\t<a\n\t\t\t\tv-if=\"footLink\"\n\t\t\t\tclass=\"cn-object-parties__main-link\"\n\t\t\t\t:href=\"footLink.href\"\n\t\t\t\tdata-testid=\"cn-object-parties-main-link\">{{ footLink.label }}</a>\n\t\t</div>\n\n\t\t<NcEmptyContent\n\t\t\tv-else-if=\"groups.length === 0\"\n\t\t\t:name=\"label('empty')\"\n\t\t\t:description=\"label('emptyDetail')\"\n\t\t\tdata-testid=\"cn-object-parties-empty\" />\n\n\t\t<template v-else>\n\t\t\t<div\n\t\t\t\tv-if=\"kinds\"\n\t\t\t\tclass=\"cn-object-parties__accepted\"\n\t\t\t\t:data-source=\"kinds.source\"\n\t\t\t\tdata-testid=\"cn-object-parties-accepted\">\n\t\t\t\t<span class=\"cn-object-parties__accepted-label\">{{ label('accepted') }}</span>\n\t\t\t\t<span\n\t\t\t\t\tv-for=\"(kind, index) in kinds.kinds\"\n\t\t\t\t\t:key=\"kindCode(kind)\"\n\t\t\t\t\tclass=\"cn-status-badge cn-status-badge--default cn-object-parties__accepted-kind\"\n\t\t\t\t\tdata-testid=\"cn-object-parties-accepted-kind\">{{ index + 1 }}. {{ kind.label || kindCode(kind) }}</span>\n\t\t\t\t<span v-if=\"kindsSourceText\" class=\"cn-object-parties__muted\">{{ kindsSourceText }}</span>\n\t\t\t</div>\n\n\t\t\t<div\n\t\t\t\tv-if=\"verdicts.length > 0\"\n\t\t\t\tclass=\"cn-object-parties__verdicts\"\n\t\t\t\tdata-testid=\"cn-object-parties-verdicts\">\n\t\t\t\t<NcNoteCard\n\t\t\t\t\tv-for=\"verdict in verdicts\"\n\t\t\t\t\t:key=\"`${verdict.party}-${verdict.key}`\"\n\t\t\t\t\t:type=\"verdict.severity\"\n\t\t\t\t\t:data-effect=\"verdict.effect\"\n\t\t\t\t\tdata-testid=\"cn-object-parties-verdict\">\n\t\t\t\t\t<strong>{{ verdict.label }}</strong>\n\t\t\t\t\t<span>{{ verdict.sentence }}</span>\n\t\t\t\t</NcNoteCard>\n\t\t\t</div>\n\n\t\t\t<section\n\t\t\t\tv-for=\"group in groups\"\n\t\t\t\t:key=\"group.key\"\n\t\t\t\tclass=\"cn-object-parties__role\"\n\t\t\t\t:data-role=\"group.key\"\n\t\t\t\t:aria-labelledby=\"`${idPrefix}-${group.key}`\"\n\t\t\t\tdata-testid=\"cn-object-parties-role\">\n\t\t\t\t<component :is=\"innerHeadingTag\" :id=\"`${idPrefix}-${group.key}`\" class=\"cn-object-parties__role-label cn-inner-heading\">\n\t\t\t\t\t{{ group.label }}\n\t\t\t\t</component>\n\t\t\t\t<ul class=\"cn-object-parties__list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"party in group.parties\"\n\t\t\t\t\t\t:key=\"`${group.key}-${party.partyUuid || party.contactUid}`\"\n\t\t\t\t\t\tclass=\"cn-object-parties__party\"\n\t\t\t\t\t\t:data-party=\"party.partyUuid || party.contactUid\"\n\t\t\t\t\t\tdata-testid=\"cn-object-parties-party\">\n\t\t\t\t\t\t<!-- The board look draws a party as one ruled row (DqZaakBetrokkenen\n\t\t\t\t\t\t \"Rollen\"): an initials tile, the name over one meta line that\n\t\t\t\t\t\t names the role, the pills at the end. -->\n\t\t\t\t\t\t<template v-if=\"isBoard\">\n\t\t\t\t\t\t\t<span class=\"cn-object-parties__tile\" aria-hidden=\"true\">{{ initialsOf(nameOf(party)) }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-object-parties__text\">\n\t\t\t\t\t\t\t\t<span class=\"cn-object-parties__name\">{{ nameOf(party) }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-object-parties__meta\" data-testid=\"cn-object-parties-meta\">{{ boardMetaOf(party, group) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"cn-object-parties__end\">\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\tv-if=\"notAccepted(party)\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-status-badge cn-status-badge--warning\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-not-accepted\">{{ label('notAccepted') }}</span>\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\tv-for=\"indicator in indicatorsFor(party)\"\n\t\t\t\t\t\t\t\t\t:key=\"`${party.partyUuid}-${indicator.key}`\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-status-badge cn-status-badge--default\"\n\t\t\t\t\t\t\t\t\t:class=\"`cn-object-parties__indicator--${indicator.severity}`\"\n\t\t\t\t\t\t\t\t\t:data-effect=\"indicator.effect\"\n\t\t\t\t\t\t\t\t\t:title=\"indicator.verdict\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-indicator\">{{ indicator.label }}</span>\n\t\t\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\t\t\tv-if=\"canRemove && party.partyUuid\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-object-parties__remove\"\n\t\t\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t\t\t:aria-label=\"removeLabel(party, group)\"\n\t\t\t\t\t\t\t\t\t:title=\"removeLabel(party, group)\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-remove\"\n\t\t\t\t\t\t\t\t\t@click=\"removing = { party, group }\">\n\t\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t\t<TrashCanOutline :size=\"20\" />\n\t\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template v-else>\n\t\t\t\t\t\t\t<span class=\"cn-object-parties__name\">{{ nameOf(party) }}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"primary && party.partyUuid === primary\"\n\t\t\t\t\t\t\t\tclass=\"cn-status-badge cn-status-badge--primary\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-primary\">{{ label('primary') }}</span>\n\t\t\t\t\t\t\t<span v-if=\"kindOf(party)\" class=\"cn-object-parties__muted\">{{ kindOf(party) }}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"notAccepted(party)\"\n\t\t\t\t\t\t\t\tclass=\"cn-status-badge cn-status-badge--warning\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-not-accepted\">{{ label('notAccepted') }}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"(line, index) in extraLinesOf(party)\"\n\t\t\t\t\t\t\t\t:key=\"`line-${index}`\"\n\t\t\t\t\t\t\t\tclass=\"cn-object-parties__muted\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-extra-line\">{{ line }}</span>\n\t\t\t\t\t\t\t<span v-if=\"party.email\" class=\"cn-object-parties__muted\">{{ party.email }}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"representedBy(party)\"\n\t\t\t\t\t\t\t\tclass=\"cn-object-parties__muted\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-represented\">{{ label('representedBy') }}: {{ representedBy(party) }}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"correspondenceOf(party)\"\n\t\t\t\t\t\t\t\tclass=\"cn-object-parties__muted\"\n\t\t\t\t\t\t\t\t:data-party-documents=\"party.partyUuid || party.contactUid\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-documents\">{{ correspondenceOf(party) }}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"indicator in indicatorsFor(party)\"\n\t\t\t\t\t\t\t\t:key=\"`${party.partyUuid}-${indicator.key}`\"\n\t\t\t\t\t\t\t\tclass=\"cn-status-badge cn-status-badge--default\"\n\t\t\t\t\t\t\t\t:class=\"`cn-object-parties__indicator--${indicator.severity}`\"\n\t\t\t\t\t\t\t\t:data-effect=\"indicator.effect\"\n\t\t\t\t\t\t\t\t:title=\"indicator.verdict\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-indicator\">{{ indicator.label }} · {{ indicator.verdict }}</span>\n\t\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\t\tv-if=\"canRemove && party.partyUuid\"\n\t\t\t\t\t\t\t\tclass=\"cn-object-parties__remove\"\n\t\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t\t:aria-label=\"removeLabel(party, group)\"\n\t\t\t\t\t\t\t\t:title=\"removeLabel(party, group)\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-object-parties-remove\"\n\t\t\t\t\t\t\t\t@click=\"removing = { party, group }\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<TrashCanOutline :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t</template>\n\n\t\t<CnPartyAddDialog\n\t\t\tv-if=\"adding\"\n\t\t\tref=\"addDialog\"\n\t\t\t:dialogTitle=\"manageWord('add', 'dialogTitle', t('nextcloud-vue', 'Add a party'))\"\n\t\t\t:roles=\"addRoles\"\n\t\t\t:sources=\"addSources\"\n\t\t\t:createFields=\"createFields\"\n\t\t\t:createLabel=\"manageWord('add', 'createLabel', t('nextcloud-vue', 'Someone new'))\"\n\t\t\t:allowPrimary=\"content.manage.add.primary === true\"\n\t\t\t:primaryLabel=\"label('primary')\"\n\t\t\t:confirmLabel=\"manageWord('add', 'confirmLabel', t('nextcloud-vue', 'Add'))\"\n\t\t\t@close=\"adding = false\"\n\t\t\t@confirm=\"addParty\" />\n\n\t\t<CnConfirmDialog\n\t\t\tv-if=\"removing\"\n\t\t\tref=\"removeDialog\"\n\t\t\t:dialogTitle=\"manageWord('remove', 'title', t('nextcloud-vue', 'Remove party'))\"\n\t\t\t:message=\"removeMessage\"\n\t\t\t:confirmLabel=\"manageWord('remove', 'confirmLabel', t('nextcloud-vue', 'Remove'))\"\n\t\t\tvariant=\"error\"\n\t\t\t@close=\"removing = null\"\n\t\t\t@confirm=\"removeParty\" />\n\t</div>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcEmptyContent, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport EmailOutline from 'vue-material-design-icons/EmailOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'\nimport CnConfirmDialog from '../../dialogs/CnConfirmDialog.vue'\nimport CnPartyAddDialog from '../../dialogs/CnPartyAddDialog.vue'\nimport { interpolateUrlTokens } from '../../composables/useEndpointSource.js'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport innerHeading from '../../mixins/innerHeading.js'\nimport { conditionalLine } from '../../utils/conditionalLine.js'\nimport { initialsOf } from '../../utils/initials.js'\nimport {\n\tdocumentsOfParty,\n\tindicatorsOf,\n\tindicatorVerdict,\n\tkindCode,\n\tnewPartyRecord,\n\tpartyKindAccepted,\n\trepresentedByMap,\n\troleLabel,\n\trolesInOrder,\n\tsavedUuid,\n} from '../../utils/objectParties.js'\nimport { fetchCallerScope } from '../../utils/objectPermissions.js'\nimport { readPath } from '../../utils/readPath.js'\n\nlet uid = 0\n\n/**\n * The default words, keyed as `content.labels` overrides them.\n *\n * @return {object} The labels.\n */\nfunction defaultLabels() {\n\treturn {\n\t\tfailed: t('nextcloud-vue', 'The parties could not be read'),\n\t\tfailedDetail: t('nextcloud-vue', 'OpenRegister did not answer. Nothing has been removed.'),\n\t\tempty: t('nextcloud-vue', 'No parties yet'),\n\t\temptyDetail: t('nextcloud-vue', 'Add a party to record who this is for and who acts on it.'),\n\t\taccepted: t('nextcloud-vue', 'Accepted kinds'),\n\t\tprimary: t('nextcloud-vue', 'Primary party'),\n\t\tnotAccepted: t('nextcloud-vue', 'Kind not accepted here'),\n\t\trepresentedBy: t('nextcloud-vue', 'Represented by'),\n\t\tnoPrimary: t('nextcloud-vue', 'No main contact yet'),\n\t}\n}\n\n/**\n * CnObjectPartiesWidget — the `parties` widget: a record's parties as\n * OpenRegister's party model answers them, grouped by role with the primary\n * party first. Each party shows its kind, its representative and the\n * indicators on it, each with its verdict. A blocking verdict is also a note\n * above the list, so nobody publishes or sends past it.\n *\n * ```json\n * {\n * \"type\": \"parties\",\n * \"content\": {\n * \"rolesObjects\": { \"register\": \"myapp\", \"schema\": \"role\", \"field\": \"item\" },\n * \"roleLabels\": { \"applicant\": \"Applicant\" },\n * \"kindsSource\": { \"url\": \"/apps/myapp/api/items/@objectId/party-kinds\", \"enforce\": \"crm\" },\n * \"extraLines\": [{ \"endpoint\": \"/apps/myapp/api/items/@objectId/parties/{party}/language\", \"lines\": [\"Language: {language}\"] }],\n * \"correspondence\": { \"endpoint\": \"/apps/myapp/api/items/@objectId/documents\", \"path\": \"documents\" }\n * }\n * }\n * ```\n *\n * `manage` lets a reader change the parties through OpenRegister's party\n * API, which asks the record's update right on every write: `add` (`{ label?,\n * dialogTitle?, kind?, sources, create?, primary?, confirmLabel? }`) opens a\n * dialog to pick somebody known or describe somebody new (`create: {\n * register, schema, label?, fields, compose? }`, see `newPartyRecord`), choose\n * a role and optionally make them the primary party; `remove` (`{ title?,\n * message?, confirmLabel? }`, `{name}` and `{role}` in the message) takes a\n * party off in one role. Both are hidden when OpenRegister says the reader\n * may not update the schema; a refusal is shown, never swallowed.\n *\n * `presentation: \"primary\"` draws only the primary party, as a side card\n * does: initials, name and role label, its correspondence e-mail, and\n * `link` (`{ label, href? | tab? }`, `{total}` = the number of parties) at\n * the foot; `tab` opens that tab of the page through the route hash. It\n * reads the listing only.\n *\n * A failed read says so and is never shown as \"no parties\".\n *\n * @spec openspec/changes/dq-abstract-record-widgets/specs/record-widgets/spec.md#requirement-a-record-shows-its-parties-by-role\n */\nexport default {\n\tname: 'CnObjectPartiesWidget',\n\n\tcomponents: { CnConfirmDialog, CnPartyAddDialog, EmailOutline, NcButton, NcEmptyContent, NcLoadingIcon, NcNoteCard, Plus, TrashCanOutline },\n\n\tmixins: [innerHeading],\n\n\tinject: {\n\t\tcnTranslate: { default: () => (key) => key },\n\t\tcnLook: { default: 'nextcloud' },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The configuration: `rolesSource` (the listing URL, default\n\t\t * OpenRegister's `/parties` of this record), `partySource` (one\n\t\t * party, `{uuid}` filled in), `rolesObjects` (`{ register, schema,\n\t\t * field }`, the role rows that say who represents whom), `roleLabels`,\n\t\t * `kindsSource` (`{ url, enforce?, sourceText? }`), `extraLines`\n\t\t * (`[{ endpoint, path?, valueLabels?, lines }]`, one request per\n\t\t * party, `{party}` filled in), `correspondence` (`{ endpoint, path?,\n\t\t * senderField?, recipientsField? }`), `labels`, `presentation`\n\t\t * (`\"primary\"`: only the primary party) and `link` (`{ label,\n\t\t * href? | tab? }`, the primary presentation's foot link) and\n\t\t * `manage` (`{ add?, remove? }`, adding and removing parties).\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The record's id, bound by the detail host. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The record's register slug. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The record's schema slug. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\tuid += 1\n\t\treturn {\n\t\t\tidPrefix: `cn-object-parties-${uid}`,\n\t\t\tloading: true,\n\t\t\tfailed: false,\n\t\t\tlisting: null,\n\t\t\tpartyRecords: {},\n\t\t\trepresented: {},\n\t\t\tkinds: null,\n\t\t\textra: {},\n\t\t\tdocuments: [],\n\t\t\tmayUpdate: true,\n\t\t\tadding: false,\n\t\t\tremoving: null,\n\t\t\tmanageError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {boolean} The board look draws one ruled row per party. */\n\t\tisBoard() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/** @return {Array<object>} The role groups, primary first. */\n\t\tgroups() {\n\t\t\treturn rolesInOrder(this.listing, this.translatedMap(this.content?.roleLabels))\n\t\t},\n\n\t\t/** @return {object} The `manage` declaration, or an empty one. */\n\t\tmanage() {\n\t\t\tconst manage = this.content?.manage\n\t\t\treturn manage && typeof manage === 'object' ? manage : {}\n\t\t},\n\n\t\t/** @return {boolean} Whether the add button shows. */\n\t\tcanAdd() {\n\t\t\treturn Boolean(this.manage.add) && this.mayUpdate && !this.isPrimaryPresentation && !this.loading && !this.failed\n\t\t},\n\n\t\t/** @return {boolean} Whether each party offers its remove button. */\n\t\tcanRemove() {\n\t\t\treturn Boolean(this.manage.remove) && this.mayUpdate && !this.isPrimaryPresentation\n\t\t},\n\n\t\t/** @return {Array<{key: string, label: string}>} The roles a new party may hold, in the reader's words. */\n\t\taddRoles() {\n\t\t\tconst declared = Array.isArray(this.listing?.roles) ? this.listing.roles : []\n\t\t\tconst kind = this.manage.add?.kind\n\t\t\tconst accepted = kind ? (this.listing?.kinds || []).find((entry) => kindCode(entry) === kind)?.roles : null\n\t\t\tconst keys = Array.isArray(accepted) && accepted.length > 0 ? accepted : declared.map((role) => role?.key).filter(Boolean)\n\t\t\tconst own = this.translatedMap(this.content?.roleLabels)\n\t\t\treturn keys.map((key) => ({ key, label: own[key] || this.cnTranslate(roleLabel(key, declared, {})) }))\n\t\t},\n\n\t\t/** @return {Array<object>} The picker sources, their words translated by the picker. */\n\t\taddSources() {\n\t\t\treturn Array.isArray(this.manage.add?.sources) ? this.manage.add.sources : []\n\t\t},\n\n\t\t/** @return {Array<object>} The new-person fields, labels translated. */\n\t\tcreateFields() {\n\t\t\tconst fields = this.manage.add?.create?.fields\n\t\t\treturn (Array.isArray(fields) ? fields : [])\n\t\t\t\t.filter((field) => field && field.key)\n\t\t\t\t.map((field) => ({ ...field, label: this.cnTranslate(field.label || field.key) }))\n\t\t},\n\n\t\t/** @return {string} The question the remove dialog asks. */\n\t\tremoveMessage() {\n\t\t\tif (!this.removing) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst name = this.nameOf(this.removing.party)\n\t\t\tconst role = this.removing.group.label\n\t\t\tconst own = this.manage.remove?.message\n\t\t\treturn own\n\t\t\t\t? this.cnTranslate(own).replaceAll('{name}', name).replaceAll('{role}', role)\n\t\t\t\t: t('nextcloud-vue', 'Remove {name} as {role}?', { name, role }, undefined, { escape: false })\n\t\t},\n\n\t\t/** @return {boolean} Whether only the primary party is drawn. */\n\t\tisPrimaryPresentation() {\n\t\t\treturn this.content?.presentation === 'primary'\n\t\t},\n\n\t\t/** @return {object|null} The primary party's listing row. */\n\t\tmainParty() {\n\t\t\tif (!this.primary) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (this.listing?.results || []).find((row) => row && row.partyUuid === this.primary) || null\n\t\t},\n\n\t\t/** @return {string} The primary party's role, in the app's words. */\n\t\tmainRole() {\n\t\t\tconst key = this.mainParty?.role\n\t\t\tif (!key) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst own = this.translatedMap(this.content?.roleLabels)\n\t\t\tif (typeof own[key] === 'string' && own[key] !== '') {\n\t\t\t\treturn own[key]\n\t\t\t}\n\t\t\treturn this.cnTranslate(roleLabel(key, this.listing?.roles, {}))\n\t\t},\n\n\t\t/** @return {{label: string, href: string}|null} The primary presentation's foot link; none without parties. */\n\t\tfootLink() {\n\t\t\tconst link = this.content?.link\n\t\t\tconst total = Number(this.listing?.total ?? (this.listing?.results || []).length)\n\t\t\tif (!link || typeof link !== 'object' || !link.label || !(total > 0)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst href = link.tab ? `#${encodeURIComponent(link.tab)}` : (link.href || '')\n\t\t\tif (href === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn { label: this.cnTranslate(link.label).replaceAll('{total}', String(total)), href }\n\t\t},\n\n\t\t/** @return {string|null} The primary party's uuid. */\n\t\tprimary() {\n\t\t\treturn this.listing?.primary || null\n\t\t},\n\n\t\t/** @return {object} The verdict words, translated. */\n\t\tverdictLabels() {\n\t\t\tconst own = this.content?.labels || {}\n\t\t\tconst out = {}\n\t\t\tfor (const key of ['verdictPublication', 'verdictSend', 'verdictWarn']) {\n\t\t\t\tif (typeof own[key] === 'string' && own[key] !== '') {\n\t\t\t\t\tout[key] = this.cnTranslate(own[key])\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/** @return {Array<object>} The blocking verdicts, as notes above the list. */\n\t\tverdicts() {\n\t\t\treturn indicatorsOf(Object.values(this.partyRecords))\n\t\t\t\t.map((indicator) => ({\n\t\t\t\t\t...indicatorVerdict(indicator, this.verdictLabels),\n\t\t\t\t\tparty: indicator.party,\n\t\t\t\t\tkey: indicator.key,\n\t\t\t\t\tsentence: this.sentenceFor(indicator),\n\t\t\t\t}))\n\t\t\t\t.filter((verdict) => verdict.severity === 'error')\n\t\t},\n\n\t\t/** @return {string} Where the accepted kinds come from. */\n\t\tkindsSourceText() {\n\t\t\tconst texts = this.content?.kindsSource?.sourceText\n\t\t\tif (!this.kinds || !texts || typeof texts !== 'object') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst text = texts[this.kinds.source] || texts.default || ''\n\t\t\treturn text ? this.cnTranslate(text) : ''\n\t\t},\n\n\t\t/** @return {Record<string, string>} The kind labels the listing declares. */\n\t\tkindLabels() {\n\t\t\tconst labels = {}\n\t\t\tfor (const kind of this.listing?.kinds || []) {\n\t\t\t\tif (kind && kind.key) {\n\t\t\t\t\tlabels[kind.key] = kind.label || kind.key\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn labels\n\t\t},\n\n\t\t/** @return {string} A key that changes when the record does. */\n\t\trecordKey() {\n\t\t\treturn `${this.register}|${this.schema}|${this.objectId}`\n\t\t},\n\t},\n\n\twatch: {\n\t\trecordKey: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.onPageRefresh = (payload) => {\n\t\t\tif (payload?.source !== this.idPrefix) {\n\t\t\t\tthis.load()\n\t\t\t}\n\t\t}\n\t\tsubscribe('cn:page:refresh', this.onPageRefresh)\n\t},\n\n\tbeforeUnmount() {\n\t\tunsubscribe('cn:page:refresh', this.onPageRefresh)\n\t},\n\n\tmethods: {\n\t\tt,\n\t\tinitialsOf,\n\t\tkindCode,\n\n\t\t/**\n\t\t * A label: the content's own (translated), else the default.\n\t\t *\n\t\t * @param {string} key The label key.\n\t\t * @return {string}\n\t\t */\n\t\tlabel(key) {\n\t\t\tconst own = this.content?.labels && typeof this.content.labels[key] === 'string' ? this.content.labels[key] : ''\n\t\t\treturn own !== '' ? this.cnTranslate(own) : defaultLabels()[key]\n\t\t},\n\n\t\t/**\n\t\t * A word of the `manage` declaration, translated, else the default.\n\t\t *\n\t\t * @param {string} part `add` or `remove`.\n\t\t * @param {string} key The word's key.\n\t\t * @param {string} fallback The default.\n\t\t * @return {string}\n\t\t */\n\t\tmanageWord(part, key, fallback) {\n\t\t\tconst own = this.manage[part]?.[key]\n\t\t\treturn typeof own === 'string' && own !== '' ? this.cnTranslate(own) : fallback\n\t\t},\n\n\t\t/**\n\t\t * The remove button's accessible name.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @param {object} group The role group.\n\t\t * @return {string}\n\t\t */\n\t\tremoveLabel(party, group) {\n\t\t\treturn t('nextcloud-vue', 'Remove {name} as {role}', { name: this.nameOf(party), role: group.label }, undefined, { escape: false })\n\t\t},\n\n\t\t/**\n\t\t * Why a write failed, in the reader's words.\n\t\t *\n\t\t * @param {unknown} error The axios error.\n\t\t * @return {string}\n\t\t */\n\t\trefusalOf(error) {\n\t\t\tif (error?.response?.status === 403) {\n\t\t\t\treturn t('nextcloud-vue', 'You may not change who is on this record.')\n\t\t\t}\n\t\t\tconst said = error?.response?.data?.error\n\t\t\treturn typeof said === 'string' && said !== '' ? said : t('nextcloud-vue', 'The change could not be saved. Nothing was changed.')\n\t\t},\n\n\t\t/** @return {string} The record's parties URL. */\n\t\tpartiesUrl() {\n\t\t\treturn this.urlOf(this.content?.rolesSource || '/apps/openregister/api/objects/@register/@schema/@objectId/parties')\n\t\t},\n\n\t\t/** @return {Promise<void>} Whether OpenRegister lets the reader update this schema; unknown counts as yes, the server decides. */\n\t\tasync loadMayUpdate() {\n\t\t\tif (!this.manage.add && !this.manage.remove) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst slug = (value) => (value && typeof value === 'object' ? String(value.slug || value.id || '') : String(value || ''))\n\t\t\tconst scope = await fetchCallerScope({ register: slug(this.register), schema: slug(this.schema) })\n\t\t\tthis.mayUpdate = !scope || !Array.isArray(scope.actions) || scope.actions.includes('update')\n\t\t},\n\n\t\t/**\n\t\t * Create the person when they are new, then give them the role; a\n\t\t * primary party is set through the primary route, which records the\n\t\t * replacement.\n\t\t *\n\t\t * @param {{mode: string, partyUuid: string|null, values: object, role: string, primary: boolean}} choice The dialog's choice.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync addParty(choice) {\n\t\t\ttry {\n\t\t\t\tlet partyUuid = choice.partyUuid\n\t\t\t\tif (choice.mode === 'new') {\n\t\t\t\t\tconst create = this.manage.add?.create || {}\n\t\t\t\t\tconst { data } = await axios.post(\n\t\t\t\t\t\tgenerateUrl(`/apps/openregister/api/objects/${encodeURIComponent(create.register || '')}/${encodeURIComponent(create.schema || '')}`),\n\t\t\t\t\t\tnewPartyRecord(create, choice.values),\n\t\t\t\t\t)\n\t\t\t\t\tpartyUuid = savedUuid(data)\n\t\t\t\t\tif (partyUuid === '') {\n\t\t\t\t\t\tthrow new Error(t('nextcloud-vue', 'The new person was not saved.'))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (choice.primary) {\n\t\t\t\t\tawait axios.put(generateUrl(`${this.partiesUrl()}/primary`), { partyUuid, role: choice.role })\n\t\t\t\t} else {\n\t\t\t\t\tawait axios.post(generateUrl(this.partiesUrl()), { partyUuid, role: choice.role })\n\t\t\t\t}\n\t\t\t\tthis.adding = false\n\t\t\t\tthis.changed()\n\t\t\t} catch (error) {\n\t\t\t\tthis.$refs.addDialog?.fail(error?.response ? this.refusalOf(error) : (error?.message || this.refusalOf(error)))\n\t\t\t}\n\t\t},\n\n\t\t/** @return {Promise<void>} Take the chosen party off in the chosen role. */\n\t\tasync removeParty() {\n\t\t\tconst { party, group } = this.removing || {}\n\t\t\tif (!party) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait axios.delete(generateUrl(`${this.partiesUrl()}/${encodeURIComponent(party.partyUuid)}`), { params: { role: group.key } })\n\t\t\t\tthis.removing = null\n\t\t\t\tthis.changed()\n\t\t\t} catch (error) {\n\t\t\t\tthis.$refs.removeDialog?.setResult({ error: this.refusalOf(error) })\n\t\t\t}\n\t\t},\n\n\t\t/** @return {void} Read the parties again and tell the page (tab counts, the side card). */\n\t\tchanged() {\n\t\t\tthis.manageError = ''\n\t\t\tthis.load()\n\t\t\temit('cn:page:refresh', { source: this.idPrefix })\n\t\t},\n\n\t\t/**\n\t\t * A map of manifest words, translated.\n\t\t *\n\t\t * @param {object} map The map.\n\t\t * @return {Record<string, string>}\n\t\t */\n\t\ttranslatedMap(map) {\n\t\t\tconst out = {}\n\t\t\tfor (const [key, value] of Object.entries(map && typeof map === 'object' ? map : {})) {\n\t\t\t\tout[key] = typeof value === 'string' ? this.cnTranslate(value) : value\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * An app path with the record's tokens and `{party}` filled in.\n\t\t *\n\t\t * @param {string} template The path.\n\t\t * @param {string} [party] A party uuid.\n\t\t * @return {string}\n\t\t */\n\t\turlOf(template, party = '') {\n\t\t\tconst withSlugs = String(template)\n\t\t\t\t.replaceAll('@register', String(this.register || ''))\n\t\t\t\t.replaceAll('@schema', String(this.schema || ''))\n\t\t\treturn interpolateUrlTokens(withSlugs, { objectId: this.objectId }).replaceAll('{party}', encodeURIComponent(party)).replaceAll('{uuid}', encodeURIComponent(party))\n\t\t},\n\n\t\t/**\n\t\t * GET an app path; null when it fails.\n\t\t *\n\t\t * @param {string} path The app path.\n\t\t * @param {object} [params] Query parameters.\n\t\t * @return {Promise<unknown>}\n\t\t */\n\t\tasync read(path, params) {\n\t\t\ttry {\n\t\t\t\tconst { data } = await axios.get(generateUrl(path), params ? { params } : undefined)\n\t\t\t\treturn data ?? null\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read the listing, then each party, the representation, the kinds,\n\t\t * the extra lines and the correspondence.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tif (!this.objectId) {\n\t\t\t\tthis.loading = false\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.failed = false\n\t\t\tthis.partyRecords = {}\n\t\t\tthis.represented = {}\n\t\t\tthis.extra = {}\n\t\t\tthis.documents = []\n\t\t\tconst listing = await this.read(this.urlOf(this.content?.rolesSource || '/apps/openregister/api/objects/@register/@schema/@objectId/parties'))\n\t\t\tif (!listing || typeof listing !== 'object') {\n\t\t\t\tthis.failed = true\n\t\t\t\tthis.listing = null\n\t\t\t\tthis.loading = false\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.listing = listing\n\t\t\tawait this.loadMayUpdate()\n\t\t\tif (this.isPrimaryPresentation) {\n\t\t\t\tthis.loading = false\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst uuids = [...new Set((listing.results || []).map((row) => row && row.partyUuid).filter(Boolean))]\n\t\t\tconst partyUrl = this.content?.partySource || '/apps/openregister/api/parties/{uuid}'\n\t\t\tconst records = await Promise.all(uuids.map((uuid) => this.read(this.urlOf(partyUrl, uuid))))\n\t\t\tconst byUuid = {}\n\t\t\tuuids.forEach((uuid, index) => {\n\t\t\t\tif (records[index] && typeof records[index] === 'object') {\n\t\t\t\t\tbyUuid[uuid] = records[index]\n\t\t\t\t}\n\t\t\t})\n\t\t\tthis.partyRecords = byUuid\n\t\t\tawait Promise.all([this.loadRepresentation(), this.loadKinds(), this.loadExtraLines(uuids), this.loadCorrespondence()])\n\t\t\tthis.loading = false\n\t\t},\n\n\t\t/** @return {Promise<void>} Who represents whom, from the role rows. */\n\t\tasync loadRepresentation() {\n\t\t\tconst source = this.content?.rolesObjects\n\t\t\tif (!source || !source.register || !source.schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst data = await this.read(`/apps/openregister/api/objects/${encodeURIComponent(source.register)}/${encodeURIComponent(source.schema)}`, { [source.field || 'object']: String(this.objectId), _limit: 200 })\n\t\t\tconst rows = Array.isArray(data?.results) ? data.results : (Array.isArray(data) ? data : [])\n\t\t\tthis.represented = representedByMap(rows)\n\t\t},\n\n\t\t/** @return {Promise<void>} The kinds this record accepts. */\n\t\tasync loadKinds() {\n\t\t\tconst source = this.content?.kindsSource\n\t\t\tthis.kinds = null\n\t\t\tif (!source || !source.url) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst data = await this.read(this.urlOf(source.url))\n\t\t\tthis.kinds = data && Array.isArray(data.kinds) ? data : null\n\t\t},\n\n\t\t/**\n\t\t * One answer per party per extra line.\n\t\t *\n\t\t * @param {Array<string>} uuids The parties.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadExtraLines(uuids) {\n\t\t\tconst lines = Array.isArray(this.content?.extraLines) ? this.content.extraLines : []\n\t\t\tconst extra = {}\n\t\t\tawait Promise.all(lines.map(async (spec, index) => {\n\t\t\t\tif (!spec || !spec.endpoint) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst answers = await Promise.all(uuids.map((uuid) => this.read(this.urlOf(spec.endpoint, uuid))))\n\t\t\t\tuuids.forEach((uuid, i) => {\n\t\t\t\t\tconst answer = spec.path ? readPath(answers[i], spec.path) : answers[i]\n\t\t\t\t\tif (answer && typeof answer === 'object') {\n\t\t\t\t\t\textra[`${index}|${uuid}`] = answer\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}))\n\t\t\tthis.extra = extra\n\t\t},\n\n\t\t/** @return {Promise<void>} The documents the correspondence counts read. */\n\t\tasync loadCorrespondence() {\n\t\t\tconst spec = this.content?.correspondence\n\t\t\tif (!spec || !spec.endpoint) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst data = await this.read(this.urlOf(spec.endpoint))\n\t\t\tconst list = spec.path ? readPath(data, spec.path) : data\n\t\t\tthis.documents = Array.isArray(list) ? list : []\n\t\t},\n\n\t\t/**\n\t\t * The extra lines for a party, each the first of its `lines` that applies.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @return {Array<string>}\n\t\t */\n\t\textraLinesOf(party) {\n\t\t\tconst specs = Array.isArray(this.content?.extraLines) ? this.content.extraLines : []\n\t\t\treturn specs.map((spec, index) => {\n\t\t\t\tconst answer = this.extra[`${index}|${party.partyUuid}`]\n\t\t\t\tif (!answer) {\n\t\t\t\t\treturn ''\n\t\t\t\t}\n\t\t\t\treturn conditionalLine(spec.lines || [], this.labelled(answer, spec.valueLabels), (text) => this.cnTranslate(text))\n\t\t\t}).filter((line) => line !== '')\n\t\t},\n\n\t\t/**\n\t\t * An answer with its coded values replaced by their labels\n\t\t * (`valueLabels: { language: { nl: \"Dutch\" } }`; `nl-NL` reads as `nl`).\n\t\t *\n\t\t * @param {object} answer The answer.\n\t\t * @param {object} valueLabels The labels by field and value.\n\t\t * @return {object}\n\t\t */\n\t\tlabelled(answer, valueLabels) {\n\t\t\tif (!valueLabels || typeof valueLabels !== 'object') {\n\t\t\t\treturn answer\n\t\t\t}\n\t\t\tconst out = { ...answer }\n\t\t\tfor (const [field, map] of Object.entries(valueLabels)) {\n\t\t\t\tconst raw = answer[field]\n\t\t\t\tif (typeof raw !== 'string' || !map) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst hit = map[raw] ?? map[raw.toLowerCase().split('-')[0]]\n\t\t\t\tif (typeof hit === 'string') {\n\t\t\t\t\tout[field] = this.cnTranslate(hit)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Whether a party's kind is one this record does not accept. Only when\n\t\t * the kinds come from the source named by `kindsSource.enforce`.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @return {boolean}\n\t\t */\n\t\tnotAccepted(party) {\n\t\t\tconst enforce = this.content?.kindsSource?.enforce\n\t\t\tif (!party?.partyKind || !this.kinds || (enforce && this.kinds.source !== enforce)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn !partyKindAccepted(party.partyKind, this.kinds.kinds, this.listing?.kinds)\n\t\t},\n\n\t\t/**\n\t\t * A party's kind: the accepted kind's label, else the listing's, else the code.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @return {string}\n\t\t */\n\t\tkindOf(party) {\n\t\t\tif (!party.partyKind) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst offered = (this.kinds?.kinds || []).find((kind) => kindCode(kind) === party.partyKind)\n\t\t\treturn offered?.label || this.kindLabels[party.partyKind] || party.partyKind\n\t\t},\n\n\t\t/**\n\t\t * Who represents this party, by name.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @return {string}\n\t\t */\n\t\trepresentedBy(party) {\n\t\t\tconst uuid = this.represented[party.partyUuid || party.contactUid || ''] || ''\n\t\t\tif (uuid === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst row = (this.listing?.results || []).find((candidate) => candidate?.partyUuid === uuid)\n\t\t\treturn row ? this.nameOf(row) : (this.partyRecords[uuid]?.name || uuid)\n\t\t},\n\n\t\t/**\n\t\t * A party's name: the listing's, else the party record's, else its id.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @return {string}\n\t\t */\n\t\tnameOf(party) {\n\t\t\treturn party.displayName || this.partyRecords[party.partyUuid]?.name || party.contactUid || party.partyUuid || ''\n\t\t},\n\n\t\t/**\n\t\t * How many documents a party sent and received.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @return {string}\n\t\t */\n\t\tcorrespondenceOf(party) {\n\t\t\tconst spec = this.content?.correspondence || {}\n\t\t\tconst { sent, received } = documentsOfParty(this.documents, party.partyUuid || party.contactUid || '', spec)\n\t\t\tif (sent.length === 0 && received.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (received.length === 0) {\n\t\t\t\treturn n('nextcloud-vue', '%n document sent', '%n documents sent', sent.length)\n\t\t\t}\n\t\t\tif (sent.length === 0) {\n\t\t\t\treturn n('nextcloud-vue', '%n document received', '%n documents received', received.length)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{sent} sent, {received} received', { sent: sent.length, received: received.length })\n\t\t},\n\n\t\t/**\n\t\t * The indicators on a party, each with its verdict.\n\t\t *\n\t\t * @param {object} party The listing row.\n\t\t * @return {Array<object>}\n\t\t */\n\t\t/**\n\t\t * The board row's meta line: the primary mark, the role, the kind,\n\t\t * whom the party represents, its extra lines, e-mail and documents,\n\t\t * separated by middots.\n\t\t *\n\t\t * @param {object} party The party.\n\t\t * @param {object} group Its role group.\n\t\t * @return {string}\n\t\t */\n\t\tboardMetaOf(party, group) {\n\t\t\tconst parts = []\n\t\t\tif (this.primary && party.partyUuid === this.primary) {\n\t\t\t\tparts.push(this.label('primary'))\n\t\t\t}\n\t\t\tparts.push(group.label, this.kindOf(party))\n\t\t\tconst represented = this.representedBy(party)\n\t\t\tif (represented) {\n\t\t\t\tparts.push(`${this.label('representedBy')}: ${represented}`)\n\t\t\t}\n\t\t\tparts.push(...this.extraLinesOf(party), party.email, this.correspondenceOf(party))\n\t\t\treturn parts.filter((part) => typeof part === 'string' && part !== '').join(' · ')\n\t\t},\n\n\t\tindicatorsFor(party) {\n\t\t\tconst record = this.partyRecords[party.partyUuid]\n\t\t\tif (!record) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn (record.indicators || []).filter(Boolean).map((indicator) => ({ ...indicatorVerdict(indicator, this.verdictLabels), key: indicator.key }))\n\t\t},\n\n\t\t/**\n\t\t * The sentence of a blocking verdict, naming the party.\n\t\t *\n\t\t * @param {object} indicator The indicator with `partyName`.\n\t\t * @return {string}\n\t\t */\n\t\tsentenceFor(indicator) {\n\t\t\tconst name = indicator.partyName || indicator.party\n\t\t\tconst own = this.content?.labels || {}\n\t\t\tif (indicatorVerdict(indicator).effect === 'refuse-publication') {\n\t\t\t\treturn own.sentencePublication\n\t\t\t\t\t? this.cnTranslate(own.sentencePublication).replaceAll('{party}', name)\n\t\t\t\t\t: t('nextcloud-vue', 'Publishing a file on this record is refused, because of {party}.', { party: name }, undefined, { escape: false })\n\t\t\t}\n\t\t\treturn own.sentenceSend\n\t\t\t\t? this.cnTranslate(own.sentenceSend).replaceAll('{party}', name)\n\t\t\t\t: t('nextcloud-vue', 'A message to {party} is refused.', { party: name }, undefined, { escape: false })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-object-parties {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(var(--default-grid-baseline, 4px) * 3);\n}\n\n.cn-object-parties__verdicts {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: var(--default-grid-baseline, 4px);\n}\n\n.cn-object-parties__role-label {\n\tmargin: 0 0 var(--default-grid-baseline, 4px) 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-object-parties__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: var(--default-grid-baseline, 4px);\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-object-parties__party {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: calc(var(--default-grid-baseline, 4px) * 2);\n\tpadding: var(--default-grid-baseline, 4px);\n\tborder-radius: var(--border-radius, 4px);\n}\n\n.cn-object-parties__party:hover {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-object-parties__name {\n\tfont-weight: bold;\n}\n\n.cn-object-parties__muted {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-object-parties__manage {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n}\n\n.cn-object-parties__remove {\n\tmargin-inline-start: auto;\n}\n\n.cn-object-parties__main-card {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(var(--default-grid-baseline, 4px) * 3);\n}\n\n.cn-object-parties__main {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: calc(var(--default-grid-baseline, 4px) * 3);\n}\n\n.cn-object-parties__main-initials {\n\tdisplay: flex;\n\tflex: none;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 40px;\n\theight: 40px;\n\tborder-radius: 50%;\n\tbackground-color: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\tfont-weight: 700;\n}\n\n.cn-object-parties__main-text {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-object-parties__main-name {\n\tfont-size: 15px;\n}\n\n.cn-object-parties__main-role {\n\tfont-size: 13px;\n}\n\n.cn-object-parties__main-email {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: calc(var(--default-grid-baseline, 4px) * 2.5);\n\tcolor: var(--color-main-text);\n\toverflow-wrap: anywhere;\n}\n\n.cn-object-parties__main-link {\n\tcolor: var(--color-primary-element);\n\tfont-weight: 600;\n}\n\n.cn-object-parties__accepted {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: calc(var(--default-grid-baseline, 4px) * 2);\n\tpadding: calc(var(--default-grid-baseline, 4px) * 2) calc(var(--default-grid-baseline, 4px) * 3);\n\tborder-radius: var(--border-radius-large, 10px);\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-object-parties__accepted-label {\n\tfont-weight: 600;\n}\n\n.cn-object-parties__accepted-kind {\n\tborder: 1px solid var(--color-border-dark);\n}\n</style>\n"],"names":["_normalizeClass","_createVNode","_toDisplayString","_createBlock","_openBlock","_createElementBlock","_createElementVNode","_createTextVNode","_Fragment","_renderList","_resolveDynamicComponent"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnStepper.vue2.js';
2
2
  import { render } from './CnStepper.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-3cab4d7d";
5
+ script.__scopeId = "data-v-3d784176";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -1 +1 @@
1
- {"version":3,"file":"CnStepper.vue2.js","sources":["../../../../src/components/CnStepper/CnStepper.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<ol class=\"cn-stepper cn-wizard-dialog__progress\"\n\t\t:class=\"[{ 'cn-stepper--board': isBoard }, lookClass]\"\n\t\t:aria-label=\"ariaLabel\"\n\t\tdata-testid=\"cn-stepper\">\n\t\t<li v-for=\"(step, idx) in steps\"\n\t\t\t:key=\"step.id\"\n\t\t\tclass=\"cn-stepper__item cn-wizard-dialog__progress-item\"\n\t\t\t:class=\"{\n\t\t\t\t'cn-stepper__item--done': idx < currentIndex,\n\t\t\t\t'cn-stepper__item--current': idx === currentIndex,\n\t\t\t\t'cn-stepper__item--upcoming': idx > currentIndex,\n\t\t\t\t'cn-wizard-dialog__progress-item--active': idx === currentIndex,\n\t\t\t\t'cn-wizard-dialog__progress-item--done': idx < currentIndex,\n\t\t\t\t'cn-wizard-dialog__progress-item--clickable': isJumpable(idx),\n\t\t\t}\"\n\t\t\t:aria-current=\"idx === currentIndex ? 'step' : undefined\"\n\t\t\t:data-step-id=\"step.id\">\n\t\t\t<component :is=\"isJumpable(idx) ? 'button' : 'span'\"\n\t\t\t\tclass=\"cn-stepper__step\"\n\t\t\t\t:type=\"isJumpable(idx) ? 'button' : undefined\"\n\t\t\t\t@click=\"isJumpable(idx) ? onJump(step.id) : null\">\n\t\t\t\t<span class=\"cn-stepper__dot cn-wizard-dialog__progress-dot\" aria-hidden=\"true\">\n\t\t\t\t\t<template v-if=\"idx < currentIndex\">\n\t\t\t\t\t\t<Check v-if=\"isBoard\"\n\t\t\t\t\t\t\tclass=\"cn-stepper__check cn-wizard-dialog__progress-check\"\n\t\t\t\t\t\t\t:size=\"16\" />\n\t\t\t\t\t\t<span v-else class=\"cn-wizard-dialog__progress-check\">✓</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<span v-else>{{ idx + 1 }}</span>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-stepper__label cn-wizard-dialog__progress-label\">{{ step.label }}</span>\n\t\t\t\t<span v-if=\"idx < currentIndex\" class=\"cn-stepper__sr\">{{ ', ' + completedLabel }}</span>\n\t\t\t</component>\n\t\t\t<span v-if=\"idx < steps.length - 1\"\n\t\t\t\tclass=\"cn-stepper__connector cn-wizard-dialog__progress-connector\"\n\t\t\t\t:class=\"{\n\t\t\t\t\t'cn-stepper__connector--done': idx < currentIndex,\n\t\t\t\t\t'cn-wizard-dialog__progress-connector--done': idx < currentIndex,\n\t\t\t\t}\"\n\t\t\t\taria-hidden=\"true\" />\n\t\t</li>\n\t</ol>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport Check from 'vue-material-design-icons/Check.vue'\nimport { useLook } from '../../composables/useLook.js'\n\n/**\n * CnStepper (internal) - the step indicator of a wizard: an ordered list, the\n * current step marked with `aria-current=\"step\"`, finished steps as real\n * buttons only when `allowJumpBack` is set.\n *\n * In the board look (`look=\"board\"`, else the injected `cnLook`) it draws 28px\n * circles with the label beside them, a green check on a finished step, a\n * filled circle on the current one, an outlined circle on the rest and a 2px\n * connector coloured by state. In the Nextcloud look it draws the stacked\n * stepper CnWizardDialog always had, with the same class names.\n *\n * @spec openspec/changes/screens-wizard-parity/specs/wizard-dialog/spec.md#requirement-the-board-stepper\n * @event jump Emitted with a finished step's id when its button is pressed.\n *\n * @event jump Emitted with the step id when a finished step button is pressed.\n */\nexport default {\n\tname: 'CnStepper',\n\n\tcomponents: { Check },\n\n\tprops: {\n\t\t/** The steps: `{ id, label }`. */\n\t\tsteps: { type: Array, required: true },\n\t\t/** Index of the current step. */\n\t\tcurrentIndex: { type: Number, default: 0 },\n\t\t/** Let a finished step be pressed to go back to it. */\n\t\tallowJumpBack: { type: Boolean, default: false },\n\t\t/** The look; unset follows the injected `cnLook`. */\n\t\tlook: { type: String, default: undefined },\n\t\t/** Accessible name of the list. */\n\t\tariaLabel: { type: String, default: () => t('nextcloud-vue', 'Steps') },\n\t\t/** Visually hidden text after a finished step's label. */\n\t\tcompletedLabel: { type: String, default: () => t('nextcloud-vue', 'completed') },\n\t},\n\n\temits: ['jump'],\n\n\tsetup(props) {\n\t\tconst { isBoard, lookClass } = useLook(props)\n\t\treturn { isBoard, lookClass }\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Forward a press on a finished step.\n\t\t *\n\t\t * @param {string} id The step id.\n\t\t */\n\t\tonJump(id) {\n\t\t\t/**\n\t\t\t * @event jump Emitted with the step id when a finished step button is pressed.\n\t\t\t */\n\t\t\tthis.$emit('jump', id)\n\t\t},\n\n\t\tisJumpable(idx) {\n\t\t\treturn this.allowJumpBack && idx < this.currentIndex\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* The Nextcloud look: the stacked stepper, unchanged. */\n.cn-stepper {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tgap: 0;\n\tpadding: 8px 0 4px;\n\tmargin: 0 0 20px;\n\tlist-style: none;\n}\n\n.cn-stepper__item {\n\tposition: relative;\n\tflex: 1 1 0;\n\tmin-width: 0;\n\tmax-width: 180px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8125rem;\n}\n\n.cn-stepper__step {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 6px;\n\tmax-width: 100%;\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tbackground: none;\n\tcolor: inherit;\n\tfont: inherit;\n}\n\nbutton.cn-stepper__step {\n\tcursor: pointer;\n}\n\n.cn-stepper__dot {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 34px;\n\theight: 34px;\n\tborder-radius: 50%;\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttransition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n\tz-index: 1;\n}\n\n.cn-wizard-dialog__progress-check {\n\tfont-size: 16px;\n\tline-height: 1;\n}\n\n.cn-stepper__label {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-stepper__connector {\n\tposition: absolute;\n\ttop: 17px;\n\tleft: calc(50% + 21px);\n\tright: calc(-50% + 21px);\n\theight: 2px;\n\tbackground: var(--color-border-dark);\n}\n\n.cn-stepper__connector--done {\n\tbackground: var(--color-primary-element);\n}\n\n.cn-stepper__item--current .cn-stepper__dot,\n.cn-stepper__item--done .cn-stepper__dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-stepper__item--current .cn-stepper__label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-stepper__item--done .cn-stepper__label {\n\tcolor: var(--color-main-text);\n}\n\n.cn-stepper__item--done button.cn-stepper__step:hover .cn-stepper__label {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-stepper__sr {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tmargin: -1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\twhite-space: nowrap;\n}\n\n/* The board look: one row, 28px circles, the label beside the circle. */\n/* The board leaves 20px between the stepper and the step's body (DqInstellen). */\n.cn-stepper--board {\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: flex-start;\n\tgap: 8px;\n\tpadding: 0;\n\tmargin: 0 0 20px;\n}\n\n.cn-stepper--board .cn-stepper__item {\n\tflex: 0 1 auto;\n\tflex-direction: row;\n\talign-items: center;\n\tgap: 8px;\n\tmax-width: none;\n\tfont-size: 14px;\n}\n\n.cn-stepper--board .cn-stepper__item:not(:last-child) {\n\tflex: 1 1 auto;\n}\n\n.cn-stepper--board .cn-stepper__step {\n\tflex-direction: row;\n\tgap: 8px;\n}\n\n.cn-stepper--board .cn-stepper__dot {\n\tflex: 0 0 auto;\n\twidth: 28px;\n\theight: 28px;\n\tfont-size: 14px;\n}\n\n.cn-stepper--board .cn-stepper__label {\n\toverflow: visible;\n\ttext-overflow: clip;\n\twhite-space: nowrap;\n\tfont-size: 14px;\n}\n\n.cn-stepper--board .cn-stepper__connector {\n\tposition: static;\n\tflex: 1 1 24px;\n\tmin-width: 16px;\n\theight: 2px;\n\tbackground: var(--color-border);\n}\n\n.cn-stepper--board .cn-stepper__connector--done {\n\tbackground: var(--color-success);\n}\n\n/* Done: filled success circle, 16px check, secondary label. */\n.cn-stepper--board .cn-stepper__item--done .cn-stepper__dot {\n\tborder-color: var(--color-success);\n\tbackground: var(--color-success);\n\tcolor: var(--color-success-text, #fff);\n}\n\n.cn-stepper--board .cn-stepper__item--done .cn-stepper__label {\n\tcolor: var(--cn-board-text-soft, var(--color-main-text));\n\tfont-weight: 400;\n}\n\n/* Current: filled primary, number 14/700, label 14/700 in the main text colour. */\n.cn-stepper--board .cn-stepper__item--current .cn-stepper__dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n\tfont-weight: 700;\n}\n\n.cn-stepper--board .cn-stepper__item--current .cn-stepper__label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 700;\n}\n\n/* Upcoming: 2px outline, number 14/600, label in the max-contrast tone. */\n.cn-stepper--board .cn-stepper__item--upcoming .cn-stepper__dot {\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: transparent;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 600;\n}\n\n.cn-stepper--board .cn-stepper__item--upcoming .cn-stepper__label {\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["t"],"mappings":";;;;AAsDA;AACC;AACA;AACA;AACA;AACA;;;;;AAKA;;;AAGA;AACA;;AAED,aAAe;AACd,CAAA,IAAI,EAAE,WAAW;;AAEjB,CAAA,UAAU,EAAE,EAAE,OAAO;;AAErB,CAAA,KAAK,EAAE;;AAEN,EAAA,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM;;AAEtC,EAAA,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG;AAC1C;AACA,EAAA,aAAa,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;AAChD;AACA,EAAA,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW;;AAE1C,EAAA,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,OAAO,CAAA,EAAG;;AAEvE,EAAA,cAAc,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,WAAW,GAAG;EAChF;;AAED,CAAA,KAAK,EAAE,CAAC,MAAM,CAAC;;CAEf,KAAK,CAAC,KAAK,EAAE;AACZ,EAAA,MAAM,EAAE,OAAO,EAAE,SAAU,EAAA,GAAI,OAAO,CAAC,KAAK,EAAA;EAC5C,OAAO,EAAE,OAAO,EAAE,SAAU,EAAA;EAC5B;;AAED,CAAA,OAAO,EAAE;AACR;;AAEC;;;EAGD,MAAM,CAAC,EAAE,EAAE;AACV;AACC;;AAED,GAAA,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,EAAA;GACrB;;AAED,EAAA,UAAU,CAAC,GAAG,EAAE;GACf,OAAO,IAAI,CAAC,iBAAiB,GAAE,GAAI,IAAI,CAAC,YAAW;GACnD;EACD;AACF;;;;"}
1
+ {"version":3,"file":"CnStepper.vue2.js","sources":["../../../../src/components/CnStepper/CnStepper.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<ol class=\"cn-stepper cn-wizard-dialog__progress\"\n\t\t:class=\"[{ 'cn-stepper--board': isBoard }, lookClass]\"\n\t\t:aria-label=\"ariaLabel\"\n\t\tdata-testid=\"cn-stepper\">\n\t\t<li v-for=\"(step, idx) in steps\"\n\t\t\t:key=\"step.id\"\n\t\t\tclass=\"cn-stepper__item cn-wizard-dialog__progress-item\"\n\t\t\t:class=\"{\n\t\t\t\t'cn-stepper__item--done': idx < currentIndex,\n\t\t\t\t'cn-stepper__item--current': idx === currentIndex,\n\t\t\t\t'cn-stepper__item--upcoming': idx > currentIndex,\n\t\t\t\t'cn-wizard-dialog__progress-item--active': idx === currentIndex,\n\t\t\t\t'cn-wizard-dialog__progress-item--done': idx < currentIndex,\n\t\t\t\t'cn-wizard-dialog__progress-item--clickable': isJumpable(idx),\n\t\t\t}\"\n\t\t\t:aria-current=\"idx === currentIndex ? 'step' : undefined\"\n\t\t\t:data-step-id=\"step.id\">\n\t\t\t<component :is=\"isJumpable(idx) ? 'button' : 'span'\"\n\t\t\t\tclass=\"cn-stepper__step\"\n\t\t\t\t:type=\"isJumpable(idx) ? 'button' : undefined\"\n\t\t\t\t@click=\"isJumpable(idx) ? onJump(step.id) : null\">\n\t\t\t\t<span class=\"cn-stepper__dot cn-wizard-dialog__progress-dot\" aria-hidden=\"true\">\n\t\t\t\t\t<template v-if=\"idx < currentIndex\">\n\t\t\t\t\t\t<Check v-if=\"isBoard\"\n\t\t\t\t\t\t\tclass=\"cn-stepper__check cn-wizard-dialog__progress-check\"\n\t\t\t\t\t\t\t:size=\"16\" />\n\t\t\t\t\t\t<span v-else class=\"cn-wizard-dialog__progress-check\">✓</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<span v-else>{{ idx + 1 }}</span>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-stepper__label cn-wizard-dialog__progress-label\">{{ step.label }}</span>\n\t\t\t\t<span v-if=\"idx < currentIndex\" class=\"cn-stepper__sr\">{{ ', ' + completedLabel }}</span>\n\t\t\t</component>\n\t\t\t<span v-if=\"idx < steps.length - 1\"\n\t\t\t\tclass=\"cn-stepper__connector cn-wizard-dialog__progress-connector\"\n\t\t\t\t:class=\"{\n\t\t\t\t\t'cn-stepper__connector--done': idx < currentIndex,\n\t\t\t\t\t'cn-wizard-dialog__progress-connector--done': idx < currentIndex,\n\t\t\t\t}\"\n\t\t\t\taria-hidden=\"true\" />\n\t\t</li>\n\t</ol>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport Check from 'vue-material-design-icons/Check.vue'\nimport { useLook } from '../../composables/useLook.js'\n\n/**\n * CnStepper (internal) - the step indicator of a wizard: an ordered list, the\n * current step marked with `aria-current=\"step\"`, finished steps as real\n * buttons only when `allowJumpBack` is set.\n *\n * In the board look (`look=\"board\"`, else the injected `cnLook`) it draws 28px\n * circles with the label beside them, a green check on a finished step, a\n * filled circle on the current one, an outlined circle on the rest and a 2px\n * connector coloured by state. In the Nextcloud look it draws the stacked\n * stepper CnWizardDialog always had, with the same class names.\n *\n * @spec openspec/changes/screens-wizard-parity/specs/wizard-dialog/spec.md#requirement-the-board-stepper\n * @event jump Emitted with a finished step's id when its button is pressed.\n *\n * @event jump Emitted with the step id when a finished step button is pressed.\n */\nexport default {\n\tname: 'CnStepper',\n\n\tcomponents: { Check },\n\n\tprops: {\n\t\t/** The steps: `{ id, label }`. */\n\t\tsteps: { type: Array, required: true },\n\t\t/** Index of the current step. */\n\t\tcurrentIndex: { type: Number, default: 0 },\n\t\t/** Let a finished step be pressed to go back to it. */\n\t\tallowJumpBack: { type: Boolean, default: false },\n\t\t/** The look; unset follows the injected `cnLook`. */\n\t\tlook: { type: String, default: undefined },\n\t\t/** Accessible name of the list. */\n\t\tariaLabel: { type: String, default: () => t('nextcloud-vue', 'Steps') },\n\t\t/** Visually hidden text after a finished step's label. */\n\t\tcompletedLabel: { type: String, default: () => t('nextcloud-vue', 'completed') },\n\t},\n\n\temits: ['jump'],\n\n\tsetup(props) {\n\t\tconst { isBoard, lookClass } = useLook(props)\n\t\treturn { isBoard, lookClass }\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Forward a press on a finished step.\n\t\t *\n\t\t * @param {string} id The step id.\n\t\t */\n\t\tonJump(id) {\n\t\t\t/**\n\t\t\t * @event jump Emitted with the step id when a finished step button is pressed.\n\t\t\t */\n\t\t\tthis.$emit('jump', id)\n\t\t},\n\n\t\tisJumpable(idx) {\n\t\t\treturn this.allowJumpBack && idx < this.currentIndex\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* The Nextcloud look: the stacked stepper, unchanged. */\n.cn-stepper {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tgap: 0;\n\tpadding: 8px 0 4px;\n\tmargin: 0 0 20px;\n\tlist-style: none;\n}\n\n.cn-stepper__item {\n\tposition: relative;\n\tflex: 1 1 0;\n\tmin-width: 0;\n\tmax-width: 180px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8125rem;\n}\n\n.cn-stepper__step {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 6px;\n\tmax-width: 100%;\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tbackground: none;\n\tcolor: inherit;\n\tfont: inherit;\n}\n\n/* Nextcloud's server.css gives every plain button a 3px margin and the\n clickable-area height (`button:not(.button-vue, …):not(…)`, 0,2,1), which\n made a done step 40px tall around its 28px circle. Two classes outrank it. */\n.cn-stepper button.cn-stepper__step {\n\tmargin: 0;\n\tmin-height: 0;\n}\n\nbutton.cn-stepper__step {\n\tcursor: pointer;\n}\n\n.cn-stepper__dot {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 34px;\n\theight: 34px;\n\tborder-radius: 50%;\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttransition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n\tz-index: 1;\n}\n\n.cn-wizard-dialog__progress-check {\n\tfont-size: 16px;\n\tline-height: 1;\n}\n\n.cn-stepper__label {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-stepper__connector {\n\tposition: absolute;\n\ttop: 17px;\n\tleft: calc(50% + 21px);\n\tright: calc(-50% + 21px);\n\theight: 2px;\n\tbackground: var(--color-border-dark);\n}\n\n.cn-stepper__connector--done {\n\tbackground: var(--color-primary-element);\n}\n\n.cn-stepper__item--current .cn-stepper__dot,\n.cn-stepper__item--done .cn-stepper__dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-stepper__item--current .cn-stepper__label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-stepper__item--done .cn-stepper__label {\n\tcolor: var(--color-main-text);\n}\n\n.cn-stepper__item--done button.cn-stepper__step:hover .cn-stepper__label {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-stepper__sr {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tmargin: -1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\twhite-space: nowrap;\n}\n\n/* The board look: one row, 28px circles, the label beside the circle. */\n/* The board leaves 16px between the stepper and the step's body (DqInstellen). */\n.cn-stepper--board {\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: flex-start;\n\tgap: 8px;\n\tpadding: 0;\n\tmargin: 0 0 16px;\n}\n\n.cn-stepper--board .cn-stepper__item {\n\tflex: 0 1 auto;\n\tflex-direction: row;\n\talign-items: center;\n\tgap: 8px;\n\tmax-width: none;\n\tfont-size: 14px;\n}\n\n.cn-stepper--board .cn-stepper__item:not(:last-child) {\n\tflex: 1 1 auto;\n}\n\n.cn-stepper--board .cn-stepper__step {\n\tflex-direction: row;\n\tgap: 8px;\n}\n\n.cn-stepper--board .cn-stepper__dot {\n\tflex: 0 0 auto;\n\twidth: 28px;\n\theight: 28px;\n\tfont-size: 14px;\n}\n\n.cn-stepper--board .cn-stepper__label {\n\toverflow: visible;\n\ttext-overflow: clip;\n\twhite-space: nowrap;\n\tfont-size: 14px;\n}\n\n.cn-stepper--board .cn-stepper__connector {\n\tposition: static;\n\tflex: 1 1 24px;\n\tmin-width: 16px;\n\theight: 2px;\n\tbackground: var(--color-border);\n}\n\n.cn-stepper--board .cn-stepper__connector--done {\n\tbackground: var(--color-success);\n}\n\n/* Done: filled success circle, 16px check, secondary label. */\n.cn-stepper--board .cn-stepper__item--done .cn-stepper__dot {\n\tborder-color: var(--color-success);\n\tbackground: var(--color-success);\n\tcolor: var(--color-success-text, #fff);\n}\n\n.cn-stepper--board .cn-stepper__item--done .cn-stepper__label {\n\tcolor: var(--cn-board-text-soft, var(--color-main-text));\n\tfont-weight: 400;\n}\n\n/* Current: filled primary, number 14/700, label 14/700 in the main text colour. */\n.cn-stepper--board .cn-stepper__item--current .cn-stepper__dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n\tfont-weight: 700;\n}\n\n.cn-stepper--board .cn-stepper__item--current .cn-stepper__label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 700;\n}\n\n/* Upcoming: 2px outline, number 14/600, label in the max-contrast tone. */\n.cn-stepper--board .cn-stepper__item--upcoming .cn-stepper__dot {\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: transparent;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 600;\n}\n\n.cn-stepper--board .cn-stepper__item--upcoming .cn-stepper__label {\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["t"],"mappings":";;;;AAsDA;AACC;AACA;AACA;AACA;AACA;;;;;AAKA;;;AAGA;AACA;;AAED,aAAe;AACd,CAAA,IAAI,EAAE,WAAW;;AAEjB,CAAA,UAAU,EAAE,EAAE,OAAO;;AAErB,CAAA,KAAK,EAAE;;AAEN,EAAA,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM;;AAEtC,EAAA,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG;AAC1C;AACA,EAAA,aAAa,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;AAChD;AACA,EAAA,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW;;AAE1C,EAAA,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,OAAO,CAAA,EAAG;;AAEvE,EAAA,cAAc,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,WAAW,GAAG;EAChF;;AAED,CAAA,KAAK,EAAE,CAAC,MAAM,CAAC;;CAEf,KAAK,CAAC,KAAK,EAAE;AACZ,EAAA,MAAM,EAAE,OAAO,EAAE,SAAU,EAAA,GAAI,OAAO,CAAC,KAAK,EAAA;EAC5C,OAAO,EAAE,OAAO,EAAE,SAAU,EAAA;EAC5B;;AAED,CAAA,OAAO,EAAE;AACR;;AAEC;;;EAGD,MAAM,CAAC,EAAE,EAAE;AACV;AACC;;AAED,GAAA,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,EAAA;GACrB;;AAED,EAAA,UAAU,CAAC,GAAG,EAAE;GACf,OAAO,IAAI,CAAC,iBAAiB,GAAE,GAAI,IAAI,CAAC,YAAW;GACnD;EACD;AACF;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnStepper.vue3.js","sources":["../../../../src/components/CnStepper/CnStepper.vue?vue&type=template&id=3cab4d7d&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<ol class=\"cn-stepper cn-wizard-dialog__progress\"\n\t\t:class=\"[{ 'cn-stepper--board': isBoard }, lookClass]\"\n\t\t:aria-label=\"ariaLabel\"\n\t\tdata-testid=\"cn-stepper\">\n\t\t<li v-for=\"(step, idx) in steps\"\n\t\t\t:key=\"step.id\"\n\t\t\tclass=\"cn-stepper__item cn-wizard-dialog__progress-item\"\n\t\t\t:class=\"{\n\t\t\t\t'cn-stepper__item--done': idx < currentIndex,\n\t\t\t\t'cn-stepper__item--current': idx === currentIndex,\n\t\t\t\t'cn-stepper__item--upcoming': idx > currentIndex,\n\t\t\t\t'cn-wizard-dialog__progress-item--active': idx === currentIndex,\n\t\t\t\t'cn-wizard-dialog__progress-item--done': idx < currentIndex,\n\t\t\t\t'cn-wizard-dialog__progress-item--clickable': isJumpable(idx),\n\t\t\t}\"\n\t\t\t:aria-current=\"idx === currentIndex ? 'step' : undefined\"\n\t\t\t:data-step-id=\"step.id\">\n\t\t\t<component :is=\"isJumpable(idx) ? 'button' : 'span'\"\n\t\t\t\tclass=\"cn-stepper__step\"\n\t\t\t\t:type=\"isJumpable(idx) ? 'button' : undefined\"\n\t\t\t\t@click=\"isJumpable(idx) ? onJump(step.id) : null\">\n\t\t\t\t<span class=\"cn-stepper__dot cn-wizard-dialog__progress-dot\" aria-hidden=\"true\">\n\t\t\t\t\t<template v-if=\"idx < currentIndex\">\n\t\t\t\t\t\t<Check v-if=\"isBoard\"\n\t\t\t\t\t\t\tclass=\"cn-stepper__check cn-wizard-dialog__progress-check\"\n\t\t\t\t\t\t\t:size=\"16\" />\n\t\t\t\t\t\t<span v-else class=\"cn-wizard-dialog__progress-check\">✓</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<span v-else>{{ idx + 1 }}</span>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-stepper__label cn-wizard-dialog__progress-label\">{{ step.label }}</span>\n\t\t\t\t<span v-if=\"idx < currentIndex\" class=\"cn-stepper__sr\">{{ ', ' + completedLabel }}</span>\n\t\t\t</component>\n\t\t\t<span v-if=\"idx < steps.length - 1\"\n\t\t\t\tclass=\"cn-stepper__connector cn-wizard-dialog__progress-connector\"\n\t\t\t\t:class=\"{\n\t\t\t\t\t'cn-stepper__connector--done': idx < currentIndex,\n\t\t\t\t\t'cn-wizard-dialog__progress-connector--done': idx < currentIndex,\n\t\t\t\t}\"\n\t\t\t\taria-hidden=\"true\" />\n\t\t</li>\n\t</ol>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport Check from 'vue-material-design-icons/Check.vue'\nimport { useLook } from '../../composables/useLook.js'\n\n/**\n * CnStepper (internal) - the step indicator of a wizard: an ordered list, the\n * current step marked with `aria-current=\"step\"`, finished steps as real\n * buttons only when `allowJumpBack` is set.\n *\n * In the board look (`look=\"board\"`, else the injected `cnLook`) it draws 28px\n * circles with the label beside them, a green check on a finished step, a\n * filled circle on the current one, an outlined circle on the rest and a 2px\n * connector coloured by state. In the Nextcloud look it draws the stacked\n * stepper CnWizardDialog always had, with the same class names.\n *\n * @spec openspec/changes/screens-wizard-parity/specs/wizard-dialog/spec.md#requirement-the-board-stepper\n * @event jump Emitted with a finished step's id when its button is pressed.\n *\n * @event jump Emitted with the step id when a finished step button is pressed.\n */\nexport default {\n\tname: 'CnStepper',\n\n\tcomponents: { Check },\n\n\tprops: {\n\t\t/** The steps: `{ id, label }`. */\n\t\tsteps: { type: Array, required: true },\n\t\t/** Index of the current step. */\n\t\tcurrentIndex: { type: Number, default: 0 },\n\t\t/** Let a finished step be pressed to go back to it. */\n\t\tallowJumpBack: { type: Boolean, default: false },\n\t\t/** The look; unset follows the injected `cnLook`. */\n\t\tlook: { type: String, default: undefined },\n\t\t/** Accessible name of the list. */\n\t\tariaLabel: { type: String, default: () => t('nextcloud-vue', 'Steps') },\n\t\t/** Visually hidden text after a finished step's label. */\n\t\tcompletedLabel: { type: String, default: () => t('nextcloud-vue', 'completed') },\n\t},\n\n\temits: ['jump'],\n\n\tsetup(props) {\n\t\tconst { isBoard, lookClass } = useLook(props)\n\t\treturn { isBoard, lookClass }\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Forward a press on a finished step.\n\t\t *\n\t\t * @param {string} id The step id.\n\t\t */\n\t\tonJump(id) {\n\t\t\t/**\n\t\t\t * @event jump Emitted with the step id when a finished step button is pressed.\n\t\t\t */\n\t\t\tthis.$emit('jump', id)\n\t\t},\n\n\t\tisJumpable(idx) {\n\t\t\treturn this.allowJumpBack && idx < this.currentIndex\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* The Nextcloud look: the stacked stepper, unchanged. */\n.cn-stepper {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tgap: 0;\n\tpadding: 8px 0 4px;\n\tmargin: 0 0 20px;\n\tlist-style: none;\n}\n\n.cn-stepper__item {\n\tposition: relative;\n\tflex: 1 1 0;\n\tmin-width: 0;\n\tmax-width: 180px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8125rem;\n}\n\n.cn-stepper__step {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 6px;\n\tmax-width: 100%;\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tbackground: none;\n\tcolor: inherit;\n\tfont: inherit;\n}\n\nbutton.cn-stepper__step {\n\tcursor: pointer;\n}\n\n.cn-stepper__dot {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 34px;\n\theight: 34px;\n\tborder-radius: 50%;\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttransition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n\tz-index: 1;\n}\n\n.cn-wizard-dialog__progress-check {\n\tfont-size: 16px;\n\tline-height: 1;\n}\n\n.cn-stepper__label {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-stepper__connector {\n\tposition: absolute;\n\ttop: 17px;\n\tleft: calc(50% + 21px);\n\tright: calc(-50% + 21px);\n\theight: 2px;\n\tbackground: var(--color-border-dark);\n}\n\n.cn-stepper__connector--done {\n\tbackground: var(--color-primary-element);\n}\n\n.cn-stepper__item--current .cn-stepper__dot,\n.cn-stepper__item--done .cn-stepper__dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-stepper__item--current .cn-stepper__label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-stepper__item--done .cn-stepper__label {\n\tcolor: var(--color-main-text);\n}\n\n.cn-stepper__item--done button.cn-stepper__step:hover .cn-stepper__label {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-stepper__sr {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tmargin: -1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\twhite-space: nowrap;\n}\n\n/* The board look: one row, 28px circles, the label beside the circle. */\n/* The board leaves 20px between the stepper and the step's body (DqInstellen). */\n.cn-stepper--board {\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: flex-start;\n\tgap: 8px;\n\tpadding: 0;\n\tmargin: 0 0 20px;\n}\n\n.cn-stepper--board .cn-stepper__item {\n\tflex: 0 1 auto;\n\tflex-direction: row;\n\talign-items: center;\n\tgap: 8px;\n\tmax-width: none;\n\tfont-size: 14px;\n}\n\n.cn-stepper--board .cn-stepper__item:not(:last-child) {\n\tflex: 1 1 auto;\n}\n\n.cn-stepper--board .cn-stepper__step {\n\tflex-direction: row;\n\tgap: 8px;\n}\n\n.cn-stepper--board .cn-stepper__dot {\n\tflex: 0 0 auto;\n\twidth: 28px;\n\theight: 28px;\n\tfont-size: 14px;\n}\n\n.cn-stepper--board .cn-stepper__label {\n\toverflow: visible;\n\ttext-overflow: clip;\n\twhite-space: nowrap;\n\tfont-size: 14px;\n}\n\n.cn-stepper--board .cn-stepper__connector {\n\tposition: static;\n\tflex: 1 1 24px;\n\tmin-width: 16px;\n\theight: 2px;\n\tbackground: var(--color-border);\n}\n\n.cn-stepper--board .cn-stepper__connector--done {\n\tbackground: var(--color-success);\n}\n\n/* Done: filled success circle, 16px check, secondary label. */\n.cn-stepper--board .cn-stepper__item--done .cn-stepper__dot {\n\tborder-color: var(--color-success);\n\tbackground: var(--color-success);\n\tcolor: var(--color-success-text, #fff);\n}\n\n.cn-stepper--board .cn-stepper__item--done .cn-stepper__label {\n\tcolor: var(--cn-board-text-soft, var(--color-main-text));\n\tfont-weight: 400;\n}\n\n/* Current: filled primary, number 14/700, label 14/700 in the main text colour. */\n.cn-stepper--board .cn-stepper__item--current .cn-stepper__dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n\tfont-weight: 700;\n}\n\n.cn-stepper--board .cn-stepper__item--current .cn-stepper__label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 700;\n}\n\n/* Upcoming: 2px outline, number 14/600, label in the max-contrast tone. */\n.cn-stepper--board .cn-stepper__item--upcoming .cn-stepper__dot {\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: transparent;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 600;\n}\n\n.cn-stepper--board .cn-stepper__item--upcoming .cn-stepper__label {\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_normalizeClass","_openBlock","_createElementBlock","_renderList","_createBlock","_resolveDynamicComponent","_Fragment","_createElementVNode","_toDisplayString"],"mappings":";;;;;AA8BU,EAAA,KAAK,EAAC,gDAAG;AAAA,EAAA,aAAA,EAAA,MAAA;;;;AAKA,EAAA,KAAK,EAAC,kCAAkC;;;AAIjD,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,oDAAoD,GAAA;;;AACxD,EAAA,KAAA,EAAA,gBAAA;;;;;;;AA/BN,IAAA,KAAI,EAACA,cAAA,CAAA,CAAA,uCAAsB,EACjB,CAAA,EAAA,mBAAA,EAAA,MAAA,CAAA,OAAA,IAAA,MAAA,CAAA,SAAA,CAAA,CAAA,CAAA;AACb,IAAA,YAAU,EAAE,MAAS,CAAA,SAAA;AACrB,IAAA,aAAQ,EAAA,YAAA;;AACP,KAAAC,SAAA,CAAA,IAAA,CAAA,EAAAC,kBAAA,SAAwB,EAAA,IAAA,EAAAC,UAAA,CAAA,MAAA,CAAA,KAAI,EAAlB,CAAA,IAAI,EAAE,GAAG,KAAA;2BAAnBD;QACA,GAAG,EAAE,IAAI,CAAC,EAAE;AACZ,QAAA,KAAI,kBAAC,kDAA2C,EAAA;AACT,IAAA,wBAAA,EAAA,GAAI,GAAE,MAAY,CAAA,YAAA;AAAM,IAAA,2BAAA,EAAA,GAAA,KAAA,MAAA,CAAA,YAAA;AAAA,IAAA,4BAAA,EAAA,GAAA,GAAA,MAAA,CAAA,YAAA;AAAA,IAAA,yCAAA,EAAA,GAAA,KAAA,MAAA,CAAA,YAAA;AAAA,IAAA,uCAAA,EAAA,GAAA,GAAA,MAAA,CAAA,YAAA;AAAA,IAAA,4CAAA,EAAA,QAAA,CAAA,UAAA,CAAA,GAAA,CAAA;;AAQ/D,QAAA,cAAY,EAAE,GAAG,KAAI,MAAA,CAAA,YAAa,YAAW,SAAA;QAC7C,cAAY,EAAE,IAAI,CAAC,EAAE;;sBACrBE,WAeW,CAAAC,uBAAA,CAfI,QAAU,CAAA,UAAA,CAAC,GAAG,CAAA,GAAA,QAAA,GAAA,MAAA,CAAA,EAAA;AAC5B,UAAA,KAAI,EAAC,kBAAkB;AACtB,UAAA,IAAG,EAAE,QAAA,CAAA,UAAU,CAAC,GAAG,eAAC,SAAA;iBAChB,EAAA,MAAA,KAAE,mBAAU,CAAC,GAAG,IAAI,QAAM,CAAA,MAAA,CAAC,IAAI,CAAC,EAAE,CAAA,GAAA,IAAA,CAAA;;iCASjC;+BAAA,kBAAA,EAAA;AAPU,cAAA,CAAA,GAAG,GAAG,MAAY,CAAA,YAAA;kDAKvB,CAAAC,QAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,EAAA;qBAJD,MAAA,CAAA,OAAA;sCAATF,WAEI,CAAA,gBAAA,EAAA;;AADH,0BAAA,KAAK,EAAC,oDAAwB;AAC7B,0BAAA,IAAE,EAAA,EAAA;;sCACJF,kBAA8D,CAAA,MAAA,EAA9D,UAA8D,EAAR,GAAC,CAAA,CAAA;;AAExD,mBAAAD,SAAA,EAAA,EAAAC,kBAAA,CAAS,oCAAA,GAAA,GAAA,CAAA,CAAA,EAAA,CAAA,YAAA,CAAA;;AAEV,YAAAK,kBAAA,CAAiE,MAAjE,EAAA,UAAiE,EAAAC,eAAA,CAAA,IAAA,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;AACzD,YAAA,CAAA,GAAA,GAAA,MAAA,CAAA,YAAA;8BAARN,kBAAQ,CAAA,MAAA,EAAR,UAAQ,EAAAM,eAAA,CAAA,IAAA,GAAA,MAAA,CAAA,cAAA,CAAA,EAAA,CAAA,YAAA;;;;;SAEG,GAAG,GAAG,MAAK,CAAA,KAAA,CAAC,MAAM,GAAA,CAAA;;;AAC7B,cAAA,KAAC,kBAAA,4DAAA,EAAA;AACmB,KAAA,6BAAA,EAAA,GAAA,GAAA,MAAA,CAAA,YAAA;AAAA,KAAA,4CAAA,EAAA,GAAA,GAAA,MAAA,CAAA,YAAA;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnStepper.vue3.js","sources":["../../../../src/components/CnStepper/CnStepper.vue?vue&type=template&id=3d784176&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<ol class=\"cn-stepper cn-wizard-dialog__progress\"\n\t\t:class=\"[{ 'cn-stepper--board': isBoard }, lookClass]\"\n\t\t:aria-label=\"ariaLabel\"\n\t\tdata-testid=\"cn-stepper\">\n\t\t<li v-for=\"(step, idx) in steps\"\n\t\t\t:key=\"step.id\"\n\t\t\tclass=\"cn-stepper__item cn-wizard-dialog__progress-item\"\n\t\t\t:class=\"{\n\t\t\t\t'cn-stepper__item--done': idx < currentIndex,\n\t\t\t\t'cn-stepper__item--current': idx === currentIndex,\n\t\t\t\t'cn-stepper__item--upcoming': idx > currentIndex,\n\t\t\t\t'cn-wizard-dialog__progress-item--active': idx === currentIndex,\n\t\t\t\t'cn-wizard-dialog__progress-item--done': idx < currentIndex,\n\t\t\t\t'cn-wizard-dialog__progress-item--clickable': isJumpable(idx),\n\t\t\t}\"\n\t\t\t:aria-current=\"idx === currentIndex ? 'step' : undefined\"\n\t\t\t:data-step-id=\"step.id\">\n\t\t\t<component :is=\"isJumpable(idx) ? 'button' : 'span'\"\n\t\t\t\tclass=\"cn-stepper__step\"\n\t\t\t\t:type=\"isJumpable(idx) ? 'button' : undefined\"\n\t\t\t\t@click=\"isJumpable(idx) ? onJump(step.id) : null\">\n\t\t\t\t<span class=\"cn-stepper__dot cn-wizard-dialog__progress-dot\" aria-hidden=\"true\">\n\t\t\t\t\t<template v-if=\"idx < currentIndex\">\n\t\t\t\t\t\t<Check v-if=\"isBoard\"\n\t\t\t\t\t\t\tclass=\"cn-stepper__check cn-wizard-dialog__progress-check\"\n\t\t\t\t\t\t\t:size=\"16\" />\n\t\t\t\t\t\t<span v-else class=\"cn-wizard-dialog__progress-check\">✓</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<span v-else>{{ idx + 1 }}</span>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-stepper__label cn-wizard-dialog__progress-label\">{{ step.label }}</span>\n\t\t\t\t<span v-if=\"idx < currentIndex\" class=\"cn-stepper__sr\">{{ ', ' + completedLabel }}</span>\n\t\t\t</component>\n\t\t\t<span v-if=\"idx < steps.length - 1\"\n\t\t\t\tclass=\"cn-stepper__connector cn-wizard-dialog__progress-connector\"\n\t\t\t\t:class=\"{\n\t\t\t\t\t'cn-stepper__connector--done': idx < currentIndex,\n\t\t\t\t\t'cn-wizard-dialog__progress-connector--done': idx < currentIndex,\n\t\t\t\t}\"\n\t\t\t\taria-hidden=\"true\" />\n\t\t</li>\n\t</ol>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport Check from 'vue-material-design-icons/Check.vue'\nimport { useLook } from '../../composables/useLook.js'\n\n/**\n * CnStepper (internal) - the step indicator of a wizard: an ordered list, the\n * current step marked with `aria-current=\"step\"`, finished steps as real\n * buttons only when `allowJumpBack` is set.\n *\n * In the board look (`look=\"board\"`, else the injected `cnLook`) it draws 28px\n * circles with the label beside them, a green check on a finished step, a\n * filled circle on the current one, an outlined circle on the rest and a 2px\n * connector coloured by state. In the Nextcloud look it draws the stacked\n * stepper CnWizardDialog always had, with the same class names.\n *\n * @spec openspec/changes/screens-wizard-parity/specs/wizard-dialog/spec.md#requirement-the-board-stepper\n * @event jump Emitted with a finished step's id when its button is pressed.\n *\n * @event jump Emitted with the step id when a finished step button is pressed.\n */\nexport default {\n\tname: 'CnStepper',\n\n\tcomponents: { Check },\n\n\tprops: {\n\t\t/** The steps: `{ id, label }`. */\n\t\tsteps: { type: Array, required: true },\n\t\t/** Index of the current step. */\n\t\tcurrentIndex: { type: Number, default: 0 },\n\t\t/** Let a finished step be pressed to go back to it. */\n\t\tallowJumpBack: { type: Boolean, default: false },\n\t\t/** The look; unset follows the injected `cnLook`. */\n\t\tlook: { type: String, default: undefined },\n\t\t/** Accessible name of the list. */\n\t\tariaLabel: { type: String, default: () => t('nextcloud-vue', 'Steps') },\n\t\t/** Visually hidden text after a finished step's label. */\n\t\tcompletedLabel: { type: String, default: () => t('nextcloud-vue', 'completed') },\n\t},\n\n\temits: ['jump'],\n\n\tsetup(props) {\n\t\tconst { isBoard, lookClass } = useLook(props)\n\t\treturn { isBoard, lookClass }\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Forward a press on a finished step.\n\t\t *\n\t\t * @param {string} id The step id.\n\t\t */\n\t\tonJump(id) {\n\t\t\t/**\n\t\t\t * @event jump Emitted with the step id when a finished step button is pressed.\n\t\t\t */\n\t\t\tthis.$emit('jump', id)\n\t\t},\n\n\t\tisJumpable(idx) {\n\t\t\treturn this.allowJumpBack && idx < this.currentIndex\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* The Nextcloud look: the stacked stepper, unchanged. */\n.cn-stepper {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tgap: 0;\n\tpadding: 8px 0 4px;\n\tmargin: 0 0 20px;\n\tlist-style: none;\n}\n\n.cn-stepper__item {\n\tposition: relative;\n\tflex: 1 1 0;\n\tmin-width: 0;\n\tmax-width: 180px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8125rem;\n}\n\n.cn-stepper__step {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 6px;\n\tmax-width: 100%;\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tbackground: none;\n\tcolor: inherit;\n\tfont: inherit;\n}\n\n/* Nextcloud's server.css gives every plain button a 3px margin and the\n clickable-area height (`button:not(.button-vue, …):not(…)`, 0,2,1), which\n made a done step 40px tall around its 28px circle. Two classes outrank it. */\n.cn-stepper button.cn-stepper__step {\n\tmargin: 0;\n\tmin-height: 0;\n}\n\nbutton.cn-stepper__step {\n\tcursor: pointer;\n}\n\n.cn-stepper__dot {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 34px;\n\theight: 34px;\n\tborder-radius: 50%;\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttransition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n\tz-index: 1;\n}\n\n.cn-wizard-dialog__progress-check {\n\tfont-size: 16px;\n\tline-height: 1;\n}\n\n.cn-stepper__label {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-stepper__connector {\n\tposition: absolute;\n\ttop: 17px;\n\tleft: calc(50% + 21px);\n\tright: calc(-50% + 21px);\n\theight: 2px;\n\tbackground: var(--color-border-dark);\n}\n\n.cn-stepper__connector--done {\n\tbackground: var(--color-primary-element);\n}\n\n.cn-stepper__item--current .cn-stepper__dot,\n.cn-stepper__item--done .cn-stepper__dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-stepper__item--current .cn-stepper__label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-stepper__item--done .cn-stepper__label {\n\tcolor: var(--color-main-text);\n}\n\n.cn-stepper__item--done button.cn-stepper__step:hover .cn-stepper__label {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-stepper__sr {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tmargin: -1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\twhite-space: nowrap;\n}\n\n/* The board look: one row, 28px circles, the label beside the circle. */\n/* The board leaves 16px between the stepper and the step's body (DqInstellen). */\n.cn-stepper--board {\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: flex-start;\n\tgap: 8px;\n\tpadding: 0;\n\tmargin: 0 0 16px;\n}\n\n.cn-stepper--board .cn-stepper__item {\n\tflex: 0 1 auto;\n\tflex-direction: row;\n\talign-items: center;\n\tgap: 8px;\n\tmax-width: none;\n\tfont-size: 14px;\n}\n\n.cn-stepper--board .cn-stepper__item:not(:last-child) {\n\tflex: 1 1 auto;\n}\n\n.cn-stepper--board .cn-stepper__step {\n\tflex-direction: row;\n\tgap: 8px;\n}\n\n.cn-stepper--board .cn-stepper__dot {\n\tflex: 0 0 auto;\n\twidth: 28px;\n\theight: 28px;\n\tfont-size: 14px;\n}\n\n.cn-stepper--board .cn-stepper__label {\n\toverflow: visible;\n\ttext-overflow: clip;\n\twhite-space: nowrap;\n\tfont-size: 14px;\n}\n\n.cn-stepper--board .cn-stepper__connector {\n\tposition: static;\n\tflex: 1 1 24px;\n\tmin-width: 16px;\n\theight: 2px;\n\tbackground: var(--color-border);\n}\n\n.cn-stepper--board .cn-stepper__connector--done {\n\tbackground: var(--color-success);\n}\n\n/* Done: filled success circle, 16px check, secondary label. */\n.cn-stepper--board .cn-stepper__item--done .cn-stepper__dot {\n\tborder-color: var(--color-success);\n\tbackground: var(--color-success);\n\tcolor: var(--color-success-text, #fff);\n}\n\n.cn-stepper--board .cn-stepper__item--done .cn-stepper__label {\n\tcolor: var(--cn-board-text-soft, var(--color-main-text));\n\tfont-weight: 400;\n}\n\n/* Current: filled primary, number 14/700, label 14/700 in the main text colour. */\n.cn-stepper--board .cn-stepper__item--current .cn-stepper__dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n\tfont-weight: 700;\n}\n\n.cn-stepper--board .cn-stepper__item--current .cn-stepper__label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 700;\n}\n\n/* Upcoming: 2px outline, number 14/600, label in the max-contrast tone. */\n.cn-stepper--board .cn-stepper__item--upcoming .cn-stepper__dot {\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: transparent;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 600;\n}\n\n.cn-stepper--board .cn-stepper__item--upcoming .cn-stepper__label {\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_normalizeClass","_openBlock","_createElementBlock","_renderList","_createBlock","_resolveDynamicComponent","_Fragment","_createElementVNode","_toDisplayString"],"mappings":";;;;;AA8BU,EAAA,KAAK,EAAC,gDAAG;AAAA,EAAA,aAAA,EAAA,MAAA;;;;AAKA,EAAA,KAAK,EAAC,kCAAkC;;;AAIjD,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,oDAAoD,GAAA;;;AACxD,EAAA,KAAA,EAAA,gBAAA;;;;;;;AA/BN,IAAA,KAAI,EAACA,cAAA,CAAA,CAAA,uCAAsB,EACjB,CAAA,EAAA,mBAAA,EAAA,MAAA,CAAA,OAAA,IAAA,MAAA,CAAA,SAAA,CAAA,CAAA,CAAA;AACb,IAAA,YAAU,EAAE,MAAS,CAAA,SAAA;AACrB,IAAA,aAAQ,EAAA,YAAA;;AACP,KAAAC,SAAA,CAAA,IAAA,CAAA,EAAAC,kBAAA,SAAwB,EAAA,IAAA,EAAAC,UAAA,CAAA,MAAA,CAAA,KAAI,EAAlB,CAAA,IAAI,EAAE,GAAG,KAAA;2BAAnBD;QACA,GAAG,EAAE,IAAI,CAAC,EAAE;AACZ,QAAA,KAAI,kBAAC,kDAA2C,EAAA;AACT,IAAA,wBAAA,EAAA,GAAI,GAAE,MAAY,CAAA,YAAA;AAAM,IAAA,2BAAA,EAAA,GAAA,KAAA,MAAA,CAAA,YAAA;AAAA,IAAA,4BAAA,EAAA,GAAA,GAAA,MAAA,CAAA,YAAA;AAAA,IAAA,yCAAA,EAAA,GAAA,KAAA,MAAA,CAAA,YAAA;AAAA,IAAA,uCAAA,EAAA,GAAA,GAAA,MAAA,CAAA,YAAA;AAAA,IAAA,4CAAA,EAAA,QAAA,CAAA,UAAA,CAAA,GAAA,CAAA;;AAQ/D,QAAA,cAAY,EAAE,GAAG,KAAI,MAAA,CAAA,YAAa,YAAW,SAAA;QAC7C,cAAY,EAAE,IAAI,CAAC,EAAE;;sBACrBE,WAeW,CAAAC,uBAAA,CAfI,QAAU,CAAA,UAAA,CAAC,GAAG,CAAA,GAAA,QAAA,GAAA,MAAA,CAAA,EAAA;AAC5B,UAAA,KAAI,EAAC,kBAAkB;AACtB,UAAA,IAAG,EAAE,QAAA,CAAA,UAAU,CAAC,GAAG,eAAC,SAAA;iBAChB,EAAA,MAAA,KAAE,mBAAU,CAAC,GAAG,IAAI,QAAM,CAAA,MAAA,CAAC,IAAI,CAAC,EAAE,CAAA,GAAA,IAAA,CAAA;;iCASjC;+BAAA,kBAAA,EAAA;AAPU,cAAA,CAAA,GAAG,GAAG,MAAY,CAAA,YAAA;kDAKvB,CAAAC,QAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,EAAA;qBAJD,MAAA,CAAA,OAAA;sCAATF,WAEI,CAAA,gBAAA,EAAA;;AADH,0BAAA,KAAK,EAAC,oDAAwB;AAC7B,0BAAA,IAAE,EAAA,EAAA;;sCACJF,kBAA8D,CAAA,MAAA,EAA9D,UAA8D,EAAR,GAAC,CAAA,CAAA;;AAExD,mBAAAD,SAAA,EAAA,EAAAC,kBAAA,CAAS,oCAAA,GAAA,GAAA,CAAA,CAAA,EAAA,CAAA,YAAA,CAAA;;AAEV,YAAAK,kBAAA,CAAiE,MAAjE,EAAA,UAAiE,EAAAC,eAAA,CAAA,IAAA,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;AACzD,YAAA,CAAA,GAAA,GAAA,MAAA,CAAA,YAAA;8BAARN,kBAAQ,CAAA,MAAA,EAAR,UAAQ,EAAAM,eAAA,CAAA,IAAA,GAAA,MAAA,CAAA,cAAA,CAAA,EAAA,CAAA,YAAA;;;;;SAEG,GAAG,GAAG,MAAK,CAAA,KAAA,CAAC,MAAM,GAAA,CAAA;;;AAC7B,cAAA,KAAC,kBAAA,4DAAA,EAAA;AACmB,KAAA,6BAAA,EAAA,GAAA,GAAA,MAAA,CAAA,YAAA;AAAA,KAAA,4CAAA,EAAA,GAAA,GAAA,MAAA,CAAA,YAAA;;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnAccessPurposeDialog.vue2.js';
2
2
  import { render } from './CnAccessPurposeDialog.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-7fa35363";
5
+ script.__scopeId = "data-v-64fad2b3";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -33,8 +33,10 @@ var script = {
33
33
 
34
34
  /**
35
35
  * The purposes offered, already translated. "Other" is added at the end.
36
+ * A `hint` is a line under the option that says what choosing it does
37
+ * besides recording it.
36
38
  *
37
- * @type {Array<{value: string, label: string}>}
39
+ * @type {Array<{value: string, label: string, hint?: string}>}
38
40
  */
39
41
  options: {
40
42
  type: Array,
@@ -1 +1 @@
1
- {"version":3,"file":"CnAccessPurposeDialog.vue2.js","sources":["../../../src/dialogs/CnAccessPurposeDialog.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<CnDialog\n\t\t:name=\"title || t('nextcloud-vue', 'Why are you opening this?')\"\n\t\tdefaultWidth=\"confirm\"\n\t\tsize=\"small\"\n\t\t:noClose=\"loading\"\n\t\tdata-testid=\"cn-access-purpose-dialog\"\n\t\t@closing=\"$emit('cancel')\">\n\t\t<p class=\"cn-access-purpose__sentence\">\n\t\t\t{{ description || t('nextcloud-vue', 'This record holds special categories of personal data. Your purpose is recorded with your name and the time.') }}\n\t\t</p>\n\t\t<fieldset class=\"cn-access-purpose__choices\">\n\t\t\t<legend class=\"cn-access-purpose__legend\">\n\t\t\t\t{{ t('nextcloud-vue', 'Purpose') }}\n\t\t\t</legend>\n\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\tv-for=\"option in choices\"\n\t\t\t\t:key=\"option.value\"\n\t\t\t\tv-model=\"purpose\"\n\t\t\t\ttype=\"radio\"\n\t\t\t\tname=\"cn-access-purpose\"\n\t\t\t\t:value=\"option.value\"\n\t\t\t\t:disabled=\"loading\"\n\t\t\t\t:data-testid=\"`cn-access-purpose-option-${option.value}`\">\n\t\t\t\t{{ option.label }}\n\t\t\t</NcCheckboxRadioSwitch>\n\t\t</fieldset>\n\t\t<NcTextArea\n\t\t\tv-if=\"purpose === OTHER\"\n\t\t\tv-model=\"note\"\n\t\t\t:label=\"t('nextcloud-vue', 'Describe your purpose')\"\n\t\t\t:maxlength=\"500\"\n\t\t\t:disabled=\"loading\"\n\t\t\tdata-testid=\"cn-access-purpose-note\" />\n\t\t<NcNoteCard v-if=\"error\" type=\"error\" data-testid=\"cn-access-purpose-error\">\n\t\t\t{{ error }}\n\t\t</NcNoteCard>\n\n\t\t<template #actions>\n\t\t\t<NcButton :disabled=\"loading\" data-testid=\"cn-access-purpose-cancel\" @click=\"$emit('cancel')\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"!complete || loading\"\n\t\t\t\tdata-testid=\"cn-access-purpose-submit\"\n\t\t\t\t@click=\"submit\">\n\t\t\t\t<template v-if=\"loading\" #icon>\n\t\t\t\t\t<NcLoadingIcon :size=\"20\" :name=\"t('nextcloud-vue', 'Loading …')\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Open') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcTextArea } from '@nextcloud/vue'\nimport CnDialog from '../components/CnDialog/CnDialog.vue'\n\n/** The value of the \"Other\" choice, which asks for a written purpose. */\nconst OTHER = 'other'\n\n/**\n * CnAccessPurposeDialog — asks why the reader opens a record before the page\n * shows it (detail page `accessPurpose`). A short list of purposes plus\n * \"Other\", which needs a written purpose. Emits the choice; the page records\n * it and decides whether to open.\n *\n * @spec openspec/changes/detail-access-purpose/specs/detail-access-purpose/spec.md#requirement-a-detail-page-can-ask-for-a-purpose-before-it-opens\n */\nexport default {\n\tname: 'CnAccessPurposeDialog',\n\n\tcomponents: { CnDialog, NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcTextArea },\n\n\tprops: {\n\t\t/** The dialog's title; a default question when empty. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The sentence under the title; a default when empty. */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The purposes offered, already translated. \"Other\" is added at the end.\n\t\t *\n\t\t * @type {Array<{value: string, label: string}>}\n\t\t */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Whether the purpose is being recorded. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Why the last attempt was refused, shown in the dialog. */\n\t\terror: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * The reader chose a purpose.\n\t\t *\n\t\t * @type {{purpose: string, note: string}}\n\t\t */\n\t\t'submit',\n\t\t/** The reader closed the dialog without a purpose. */\n\t\t'cancel',\n\t],\n\n\tdata() {\n\t\treturn { purpose: '', note: '', OTHER }\n\t},\n\n\tcomputed: {\n\t\t/** @return {Array<{value: string, label: string}>} The offered purposes and \"Other\". */\n\t\tchoices() {\n\t\t\tconst own = (Array.isArray(this.options) ? this.options : [])\n\t\t\t\t.filter((option) => option && typeof option.value === 'string' && option.value !== '' && option.value !== OTHER)\n\t\t\treturn [...own, { value: OTHER, label: t('nextcloud-vue', 'Other') }]\n\t\t},\n\n\t\t/** @return {boolean} Whether a purpose is chosen and, for \"Other\", written. */\n\t\tcomplete() {\n\t\t\tif (this.purpose === '') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn this.purpose !== OTHER || this.note.trim() !== ''\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Emit the chosen purpose; the written note only goes with \"Other\".\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tsubmit() {\n\t\t\tif (!this.complete) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('submit', { purpose: this.purpose, note: this.purpose === OTHER ? this.note.trim() : '' })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-access-purpose__sentence {\n\tmargin: 0 0 12px;\n}\n\n.cn-access-purpose__choices {\n\tmargin: 0 0 12px;\n\tpadding: 0;\n\tborder: 0;\n}\n\n.cn-access-purpose__legend {\n\tmargin-bottom: 4px;\n\tfont-weight: 600;\n}\n</style>\n"],"names":["CnDialog","t"],"mappings":";;;;AAiEA;AACA,MAAM,KAAI,GAAI,QAAM;;AAEpB;AACC;;AAEA;;AAEA;AACA;;AAED,aAAe;CACd,IAAI,EAAE,uBAAuB;;AAE7B,CAAA,UAAU,EAAE,YAAEA,UAAQ,EAAE,QAAQ,EAAE,qBAAqB,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY;;AAEhG,CAAA,KAAK,EAAE;;AAEN,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;;EAGD,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;AACA;;AAED,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,KAAK;AACX,GAAA,OAAO,EAAE,MAAM,EAAE;GACjB;;AAED;AACA,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;;AAGD,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;EACD;;AAED,CAAA,KAAK,EAAE;AACN;;AAEC;AACA;;AAED,EAAA,QAAQ;AACR;AACA,EAAA,QAAQ;EACR;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAM,EAAA;EACtC;;AAED,CAAA,QAAQ,EAAE;AACT;AACA,EAAA,OAAO,GAAG;GACT,MAAM,GAAE,GAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAE,GAAE,IAAI,CAAC,OAAM,GAAI,EAAE;KAC1D,MAAM,CAAC,CAAC,MAAM,KAAK,UAAU,OAAO,MAAM,CAAC,KAAI,KAAM,YAAY,MAAM,CAAC,KAAI,KAAM,EAAG,IAAG,MAAM,CAAC,UAAU,KAAK,EAAA;GAChH,OAAO,CAAC,GAAG,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAEC,SAAC,CAAC,eAAe,EAAE,OAAO,CAAA,EAAG,CAAA;GACpE;;AAED;EACA,QAAQ,GAAG;AACV,GAAA,IAAI,IAAI,CAAC,OAAQ,KAAI,EAAE,EAAE;IACxB,OAAO,KAAI;AACZ,IAAA;GACA,OAAO,IAAI,CAAC,YAAY,KAAM,IAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAG,KAAI,EAAC;GACvD;EACD;;AAED,CAAA,OAAO,EAAE;KACRA,SAAC;;AAED;;AAEC;;;AAGD,EAAA,MAAM,GAAG;AACR,GAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;IACnB,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,OAAQ,KAAI,KAAM,GAAE,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,EAAA;GACpG;EACD;AACF;;;;"}
1
+ {"version":3,"file":"CnAccessPurposeDialog.vue2.js","sources":["../../../src/dialogs/CnAccessPurposeDialog.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<CnDialog\n\t\t:name=\"title || t('nextcloud-vue', 'Why are you opening this?')\"\n\t\tdefaultWidth=\"confirm\"\n\t\tsize=\"small\"\n\t\t:noClose=\"loading\"\n\t\tdata-testid=\"cn-access-purpose-dialog\"\n\t\t@closing=\"$emit('cancel')\">\n\t\t<p class=\"cn-access-purpose__sentence\">\n\t\t\t{{ description || t('nextcloud-vue', 'This record holds special categories of personal data. Your purpose is recorded with your name and the time.') }}\n\t\t</p>\n\t\t<fieldset class=\"cn-access-purpose__choices\">\n\t\t\t<legend class=\"cn-access-purpose__legend\">\n\t\t\t\t{{ t('nextcloud-vue', 'Purpose') }}\n\t\t\t</legend>\n\t\t\t<template v-for=\"option in choices\" :key=\"option.value\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-model=\"purpose\"\n\t\t\t\t\ttype=\"radio\"\n\t\t\t\t\tname=\"cn-access-purpose\"\n\t\t\t\t\t:value=\"option.value\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t:data-testid=\"`cn-access-purpose-option-${option.value}`\">\n\t\t\t\t\t{{ option.label }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<!-- What choosing this purpose does besides recording it (\"you become this case's handler\"). -->\n\t\t\t\t<p v-if=\"option.hint\"\n\t\t\t\t\tclass=\"cn-access-purpose__hint\"\n\t\t\t\t\t:data-testid=\"`cn-access-purpose-hint-${option.value}`\">\n\t\t\t\t\t{{ option.hint }}\n\t\t\t\t</p>\n\t\t\t</template>\n\t\t</fieldset>\n\t\t<NcTextArea\n\t\t\tv-if=\"purpose === OTHER\"\n\t\t\tv-model=\"note\"\n\t\t\t:label=\"t('nextcloud-vue', 'Describe your purpose')\"\n\t\t\t:maxlength=\"500\"\n\t\t\t:disabled=\"loading\"\n\t\t\tdata-testid=\"cn-access-purpose-note\" />\n\t\t<NcNoteCard v-if=\"error\" type=\"error\" data-testid=\"cn-access-purpose-error\">\n\t\t\t{{ error }}\n\t\t</NcNoteCard>\n\n\t\t<template #actions>\n\t\t\t<NcButton :disabled=\"loading\" data-testid=\"cn-access-purpose-cancel\" @click=\"$emit('cancel')\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"!complete || loading\"\n\t\t\t\tdata-testid=\"cn-access-purpose-submit\"\n\t\t\t\t@click=\"submit\">\n\t\t\t\t<template v-if=\"loading\" #icon>\n\t\t\t\t\t<NcLoadingIcon :size=\"20\" :name=\"t('nextcloud-vue', 'Loading …')\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Open') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcTextArea } from '@nextcloud/vue'\nimport CnDialog from '../components/CnDialog/CnDialog.vue'\n\n/** The value of the \"Other\" choice, which asks for a written purpose. */\nconst OTHER = 'other'\n\n/**\n * CnAccessPurposeDialog — asks why the reader opens a record before the page\n * shows it (detail page `accessPurpose`). A short list of purposes plus\n * \"Other\", which needs a written purpose. Emits the choice; the page records\n * it and decides whether to open.\n *\n * @spec openspec/changes/detail-access-purpose/specs/detail-access-purpose/spec.md#requirement-a-detail-page-can-ask-for-a-purpose-before-it-opens\n */\nexport default {\n\tname: 'CnAccessPurposeDialog',\n\n\tcomponents: { CnDialog, NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcTextArea },\n\n\tprops: {\n\t\t/** The dialog's title; a default question when empty. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The sentence under the title; a default when empty. */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The purposes offered, already translated. \"Other\" is added at the end.\n\t\t * A `hint` is a line under the option that says what choosing it does\n\t\t * besides recording it.\n\t\t *\n\t\t * @type {Array<{value: string, label: string, hint?: string}>}\n\t\t */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Whether the purpose is being recorded. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Why the last attempt was refused, shown in the dialog. */\n\t\terror: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * The reader chose a purpose.\n\t\t *\n\t\t * @type {{purpose: string, note: string}}\n\t\t */\n\t\t'submit',\n\t\t/** The reader closed the dialog without a purpose. */\n\t\t'cancel',\n\t],\n\n\tdata() {\n\t\treturn { purpose: '', note: '', OTHER }\n\t},\n\n\tcomputed: {\n\t\t/** @return {Array<{value: string, label: string}>} The offered purposes and \"Other\". */\n\t\tchoices() {\n\t\t\tconst own = (Array.isArray(this.options) ? this.options : [])\n\t\t\t\t.filter((option) => option && typeof option.value === 'string' && option.value !== '' && option.value !== OTHER)\n\t\t\treturn [...own, { value: OTHER, label: t('nextcloud-vue', 'Other') }]\n\t\t},\n\n\t\t/** @return {boolean} Whether a purpose is chosen and, for \"Other\", written. */\n\t\tcomplete() {\n\t\t\tif (this.purpose === '') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn this.purpose !== OTHER || this.note.trim() !== ''\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Emit the chosen purpose; the written note only goes with \"Other\".\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tsubmit() {\n\t\t\tif (!this.complete) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('submit', { purpose: this.purpose, note: this.purpose === OTHER ? this.note.trim() : '' })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-access-purpose__sentence {\n\tmargin: 0 0 12px;\n}\n\n.cn-access-purpose__choices {\n\tmargin: 0 0 12px;\n\tpadding: 0;\n\tborder: 0;\n}\n\n.cn-access-purpose__hint {\n\tmargin: 0 0 6px;\n\tpadding-inline-start: 36px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-access-purpose__legend {\n\tmargin-bottom: 4px;\n\tfont-weight: 600;\n}\n</style>\n"],"names":["CnDialog","t"],"mappings":";;;;AAuEA;AACA,MAAM,KAAI,GAAI,QAAM;;AAEpB;AACC;;AAEA;;AAEA;AACA;;AAED,aAAe;CACd,IAAI,EAAE,uBAAuB;;AAE7B,CAAA,UAAU,EAAE,YAAEA,UAAQ,EAAE,QAAQ,EAAE,qBAAqB,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY;;AAEhG,CAAA,KAAK,EAAE;;AAEN,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;;EAGD,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;AAGC;AACA;;;AAGD,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,KAAK;AACX,GAAA,OAAO,EAAE,MAAM,EAAE;GACjB;;AAED;AACA,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;;AAGD,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;EACD;;AAED,CAAA,KAAK,EAAE;AACN;;AAEC;AACA;;AAED,EAAA,QAAQ;AACR;AACA,EAAA,QAAQ;EACR;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAM,EAAA;EACtC;;AAED,CAAA,QAAQ,EAAE;AACT;AACA,EAAA,OAAO,GAAG;GACT,MAAM,GAAE,GAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAE,GAAE,IAAI,CAAC,OAAM,GAAI,EAAE;KAC1D,MAAM,CAAC,CAAC,MAAM,KAAK,UAAU,OAAO,MAAM,CAAC,KAAI,KAAM,YAAY,MAAM,CAAC,KAAI,KAAM,EAAG,IAAG,MAAM,CAAC,UAAU,KAAK,EAAA;GAChH,OAAO,CAAC,GAAG,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAEC,SAAC,CAAC,eAAe,EAAE,OAAO,CAAA,EAAG,CAAA;GACpE;;AAED;EACA,QAAQ,GAAG;AACV,GAAA,IAAI,IAAI,CAAC,OAAQ,KAAI,EAAE,EAAE;IACxB,OAAO,KAAI;AACZ,IAAA;GACA,OAAO,IAAI,CAAC,YAAY,KAAM,IAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAG,KAAI,EAAC;GACvD;EACD;;AAED,CAAA,OAAO,EAAE;KACRA,SAAC;;AAED;;AAEC;;;AAGD,EAAA,MAAM,GAAG;AACR,GAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;IACnB,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,OAAQ,KAAI,KAAM,GAAE,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,EAAA;GACpG;EACD;AACF;;;;"}
@@ -3,6 +3,7 @@ import { resolveComponent, openBlock, createBlock, withCtx, createVNode, createT
3
3
  const _hoisted_1 = { class: "cn-access-purpose__sentence" };
4
4
  const _hoisted_2 = { class: "cn-access-purpose__choices" };
5
5
  const _hoisted_3 = { class: "cn-access-purpose__legend" };
6
+ const _hoisted_4 = ["data-testid"];
6
7
 
7
8
  function render(_ctx, _cache, $props, $setup, $data, $options) {
8
9
  const _component_NcCheckboxRadioSwitch = resolveComponent("NcCheckboxRadioSwitch");
@@ -61,21 +62,31 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
61
62
  createElementVNode("fieldset", _hoisted_2, [
62
63
  createElementVNode("legend", _hoisted_3, toDisplayString($options.t('nextcloud-vue', 'Purpose')), 1 /* TEXT */),
63
64
  (openBlock(true), createElementBlock(Fragment, null, renderList($options.choices, (option) => {
64
- return (openBlock(), createBlock(_component_NcCheckboxRadioSwitch, {
65
- key: option.value,
66
- modelValue: $data.purpose,
67
- "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => (($data.purpose) = $event)),
68
- type: "radio",
69
- name: "cn-access-purpose",
70
- value: option.value,
71
- disabled: $props.loading,
72
- "data-testid": `cn-access-purpose-option-${option.value}`
73
- }, {
74
- default: withCtx(() => [
75
- createTextVNode(toDisplayString(option.label), 1 /* TEXT */)
76
- ]),
77
- _: 2 /* DYNAMIC */
78
- }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["modelValue", "value", "disabled", "data-testid"]))
65
+ return (openBlock(), createElementBlock(Fragment, {
66
+ key: option.value
67
+ }, [
68
+ createVNode(_component_NcCheckboxRadioSwitch, {
69
+ modelValue: $data.purpose,
70
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => (($data.purpose) = $event)),
71
+ type: "radio",
72
+ name: "cn-access-purpose",
73
+ value: option.value,
74
+ disabled: $props.loading,
75
+ "data-testid": `cn-access-purpose-option-${option.value}`
76
+ }, {
77
+ default: withCtx(() => [
78
+ createTextVNode(toDisplayString(option.label), 1 /* TEXT */)
79
+ ]),
80
+ _: 2 /* DYNAMIC */
81
+ }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["modelValue", "value", "disabled", "data-testid"]),
82
+ (option.hint)
83
+ ? (openBlock(), createElementBlock("p", {
84
+ key: 0,
85
+ class: "cn-access-purpose__hint",
86
+ "data-testid": `cn-access-purpose-hint-${option.value}`
87
+ }, toDisplayString(option.hint), 9 /* TEXT, PROPS */, _hoisted_4))
88
+ : createCommentVNode("v-if", true)
89
+ ], 64 /* STABLE_FRAGMENT */))
79
90
  }), 128 /* KEYED_FRAGMENT */))
80
91
  ]),
81
92
  ($data.purpose === $data.OTHER)
@@ -1 +1 @@
1
- {"version":3,"file":"CnAccessPurposeDialog.vue3.js","sources":["../../../src/dialogs/CnAccessPurposeDialog.vue?vue&type=template&id=7fa35363&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<CnDialog\n\t\t:name=\"title || t('nextcloud-vue', 'Why are you opening this?')\"\n\t\tdefaultWidth=\"confirm\"\n\t\tsize=\"small\"\n\t\t:noClose=\"loading\"\n\t\tdata-testid=\"cn-access-purpose-dialog\"\n\t\t@closing=\"$emit('cancel')\">\n\t\t<p class=\"cn-access-purpose__sentence\">\n\t\t\t{{ description || t('nextcloud-vue', 'This record holds special categories of personal data. Your purpose is recorded with your name and the time.') }}\n\t\t</p>\n\t\t<fieldset class=\"cn-access-purpose__choices\">\n\t\t\t<legend class=\"cn-access-purpose__legend\">\n\t\t\t\t{{ t('nextcloud-vue', 'Purpose') }}\n\t\t\t</legend>\n\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\tv-for=\"option in choices\"\n\t\t\t\t:key=\"option.value\"\n\t\t\t\tv-model=\"purpose\"\n\t\t\t\ttype=\"radio\"\n\t\t\t\tname=\"cn-access-purpose\"\n\t\t\t\t:value=\"option.value\"\n\t\t\t\t:disabled=\"loading\"\n\t\t\t\t:data-testid=\"`cn-access-purpose-option-${option.value}`\">\n\t\t\t\t{{ option.label }}\n\t\t\t</NcCheckboxRadioSwitch>\n\t\t</fieldset>\n\t\t<NcTextArea\n\t\t\tv-if=\"purpose === OTHER\"\n\t\t\tv-model=\"note\"\n\t\t\t:label=\"t('nextcloud-vue', 'Describe your purpose')\"\n\t\t\t:maxlength=\"500\"\n\t\t\t:disabled=\"loading\"\n\t\t\tdata-testid=\"cn-access-purpose-note\" />\n\t\t<NcNoteCard v-if=\"error\" type=\"error\" data-testid=\"cn-access-purpose-error\">\n\t\t\t{{ error }}\n\t\t</NcNoteCard>\n\n\t\t<template #actions>\n\t\t\t<NcButton :disabled=\"loading\" data-testid=\"cn-access-purpose-cancel\" @click=\"$emit('cancel')\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"!complete || loading\"\n\t\t\t\tdata-testid=\"cn-access-purpose-submit\"\n\t\t\t\t@click=\"submit\">\n\t\t\t\t<template v-if=\"loading\" #icon>\n\t\t\t\t\t<NcLoadingIcon :size=\"20\" :name=\"t('nextcloud-vue', 'Loading …')\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Open') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcTextArea } from '@nextcloud/vue'\nimport CnDialog from '../components/CnDialog/CnDialog.vue'\n\n/** The value of the \"Other\" choice, which asks for a written purpose. */\nconst OTHER = 'other'\n\n/**\n * CnAccessPurposeDialog — asks why the reader opens a record before the page\n * shows it (detail page `accessPurpose`). A short list of purposes plus\n * \"Other\", which needs a written purpose. Emits the choice; the page records\n * it and decides whether to open.\n *\n * @spec openspec/changes/detail-access-purpose/specs/detail-access-purpose/spec.md#requirement-a-detail-page-can-ask-for-a-purpose-before-it-opens\n */\nexport default {\n\tname: 'CnAccessPurposeDialog',\n\n\tcomponents: { CnDialog, NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcTextArea },\n\n\tprops: {\n\t\t/** The dialog's title; a default question when empty. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The sentence under the title; a default when empty. */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The purposes offered, already translated. \"Other\" is added at the end.\n\t\t *\n\t\t * @type {Array<{value: string, label: string}>}\n\t\t */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Whether the purpose is being recorded. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Why the last attempt was refused, shown in the dialog. */\n\t\terror: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * The reader chose a purpose.\n\t\t *\n\t\t * @type {{purpose: string, note: string}}\n\t\t */\n\t\t'submit',\n\t\t/** The reader closed the dialog without a purpose. */\n\t\t'cancel',\n\t],\n\n\tdata() {\n\t\treturn { purpose: '', note: '', OTHER }\n\t},\n\n\tcomputed: {\n\t\t/** @return {Array<{value: string, label: string}>} The offered purposes and \"Other\". */\n\t\tchoices() {\n\t\t\tconst own = (Array.isArray(this.options) ? this.options : [])\n\t\t\t\t.filter((option) => option && typeof option.value === 'string' && option.value !== '' && option.value !== OTHER)\n\t\t\treturn [...own, { value: OTHER, label: t('nextcloud-vue', 'Other') }]\n\t\t},\n\n\t\t/** @return {boolean} Whether a purpose is chosen and, for \"Other\", written. */\n\t\tcomplete() {\n\t\t\tif (this.purpose === '') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn this.purpose !== OTHER || this.note.trim() !== ''\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Emit the chosen purpose; the written note only goes with \"Other\".\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tsubmit() {\n\t\t\tif (!this.complete) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('submit', { purpose: this.purpose, note: this.purpose === OTHER ? this.note.trim() : '' })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-access-purpose__sentence {\n\tmargin: 0 0 12px;\n}\n\n.cn-access-purpose__choices {\n\tmargin: 0 0 12px;\n\tpadding: 0;\n\tborder: 0;\n}\n\n.cn-access-purpose__legend {\n\tmargin-bottom: 4px;\n\tfont-weight: 600;\n}\n</style>\n"],"names":["_createBlock","_createVNode","_createTextVNode","_createElementVNode","_toDisplayString","_Fragment","_renderList"],"mappings":"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"}
1
+ {"version":3,"file":"CnAccessPurposeDialog.vue3.js","sources":["../../../src/dialogs/CnAccessPurposeDialog.vue?vue&type=template&id=64fad2b3&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<CnDialog\n\t\t:name=\"title || t('nextcloud-vue', 'Why are you opening this?')\"\n\t\tdefaultWidth=\"confirm\"\n\t\tsize=\"small\"\n\t\t:noClose=\"loading\"\n\t\tdata-testid=\"cn-access-purpose-dialog\"\n\t\t@closing=\"$emit('cancel')\">\n\t\t<p class=\"cn-access-purpose__sentence\">\n\t\t\t{{ description || t('nextcloud-vue', 'This record holds special categories of personal data. Your purpose is recorded with your name and the time.') }}\n\t\t</p>\n\t\t<fieldset class=\"cn-access-purpose__choices\">\n\t\t\t<legend class=\"cn-access-purpose__legend\">\n\t\t\t\t{{ t('nextcloud-vue', 'Purpose') }}\n\t\t\t</legend>\n\t\t\t<template v-for=\"option in choices\" :key=\"option.value\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-model=\"purpose\"\n\t\t\t\t\ttype=\"radio\"\n\t\t\t\t\tname=\"cn-access-purpose\"\n\t\t\t\t\t:value=\"option.value\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t:data-testid=\"`cn-access-purpose-option-${option.value}`\">\n\t\t\t\t\t{{ option.label }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<!-- What choosing this purpose does besides recording it (\"you become this case's handler\"). -->\n\t\t\t\t<p v-if=\"option.hint\"\n\t\t\t\t\tclass=\"cn-access-purpose__hint\"\n\t\t\t\t\t:data-testid=\"`cn-access-purpose-hint-${option.value}`\">\n\t\t\t\t\t{{ option.hint }}\n\t\t\t\t</p>\n\t\t\t</template>\n\t\t</fieldset>\n\t\t<NcTextArea\n\t\t\tv-if=\"purpose === OTHER\"\n\t\t\tv-model=\"note\"\n\t\t\t:label=\"t('nextcloud-vue', 'Describe your purpose')\"\n\t\t\t:maxlength=\"500\"\n\t\t\t:disabled=\"loading\"\n\t\t\tdata-testid=\"cn-access-purpose-note\" />\n\t\t<NcNoteCard v-if=\"error\" type=\"error\" data-testid=\"cn-access-purpose-error\">\n\t\t\t{{ error }}\n\t\t</NcNoteCard>\n\n\t\t<template #actions>\n\t\t\t<NcButton :disabled=\"loading\" data-testid=\"cn-access-purpose-cancel\" @click=\"$emit('cancel')\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"!complete || loading\"\n\t\t\t\tdata-testid=\"cn-access-purpose-submit\"\n\t\t\t\t@click=\"submit\">\n\t\t\t\t<template v-if=\"loading\" #icon>\n\t\t\t\t\t<NcLoadingIcon :size=\"20\" :name=\"t('nextcloud-vue', 'Loading …')\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Open') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcTextArea } from '@nextcloud/vue'\nimport CnDialog from '../components/CnDialog/CnDialog.vue'\n\n/** The value of the \"Other\" choice, which asks for a written purpose. */\nconst OTHER = 'other'\n\n/**\n * CnAccessPurposeDialog — asks why the reader opens a record before the page\n * shows it (detail page `accessPurpose`). A short list of purposes plus\n * \"Other\", which needs a written purpose. Emits the choice; the page records\n * it and decides whether to open.\n *\n * @spec openspec/changes/detail-access-purpose/specs/detail-access-purpose/spec.md#requirement-a-detail-page-can-ask-for-a-purpose-before-it-opens\n */\nexport default {\n\tname: 'CnAccessPurposeDialog',\n\n\tcomponents: { CnDialog, NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcTextArea },\n\n\tprops: {\n\t\t/** The dialog's title; a default question when empty. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The sentence under the title; a default when empty. */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The purposes offered, already translated. \"Other\" is added at the end.\n\t\t * A `hint` is a line under the option that says what choosing it does\n\t\t * besides recording it.\n\t\t *\n\t\t * @type {Array<{value: string, label: string, hint?: string}>}\n\t\t */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Whether the purpose is being recorded. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Why the last attempt was refused, shown in the dialog. */\n\t\terror: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * The reader chose a purpose.\n\t\t *\n\t\t * @type {{purpose: string, note: string}}\n\t\t */\n\t\t'submit',\n\t\t/** The reader closed the dialog without a purpose. */\n\t\t'cancel',\n\t],\n\n\tdata() {\n\t\treturn { purpose: '', note: '', OTHER }\n\t},\n\n\tcomputed: {\n\t\t/** @return {Array<{value: string, label: string}>} The offered purposes and \"Other\". */\n\t\tchoices() {\n\t\t\tconst own = (Array.isArray(this.options) ? this.options : [])\n\t\t\t\t.filter((option) => option && typeof option.value === 'string' && option.value !== '' && option.value !== OTHER)\n\t\t\treturn [...own, { value: OTHER, label: t('nextcloud-vue', 'Other') }]\n\t\t},\n\n\t\t/** @return {boolean} Whether a purpose is chosen and, for \"Other\", written. */\n\t\tcomplete() {\n\t\t\tif (this.purpose === '') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn this.purpose !== OTHER || this.note.trim() !== ''\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Emit the chosen purpose; the written note only goes with \"Other\".\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tsubmit() {\n\t\t\tif (!this.complete) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('submit', { purpose: this.purpose, note: this.purpose === OTHER ? this.note.trim() : '' })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-access-purpose__sentence {\n\tmargin: 0 0 12px;\n}\n\n.cn-access-purpose__choices {\n\tmargin: 0 0 12px;\n\tpadding: 0;\n\tborder: 0;\n}\n\n.cn-access-purpose__hint {\n\tmargin: 0 0 6px;\n\tpadding-inline-start: 36px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-access-purpose__legend {\n\tmargin-bottom: 4px;\n\tfont-weight: 600;\n}\n</style>\n"],"names":["_createBlock","_createVNode","_createTextVNode","_createElementVNode","_toDisplayString","_Fragment","_renderList","_createElementBlock"],"mappings":"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"}
@@ -2396,7 +2396,8 @@ var translations = {
2396
2396
  "From {date}": "From {date}",
2397
2397
  "Until {date}": "Until {date}",
2398
2398
  "More actions for {name}": "More actions for {name}",
2399
- "Unlink contact": "Unlink contact"
2399
+ "Unlink contact": "Unlink contact",
2400
+ "{term}, extendable by {extension}": "{term}, extendable by {extension}"
2400
2401
  };
2401
2402
  var plurals = {
2402
2403
  "{count} member": [