@conduction/nextcloud-vue 2.147.0 → 2.149.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 (130) hide show
  1. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  2. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +24 -7
  3. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue.js +1 -1
  6. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js +12 -1
  7. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  8. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +2 -1
  9. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  10. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js +1 -1
  11. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js +23 -1
  12. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js.map +1 -1
  13. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js +1 -1
  14. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js.map +1 -1
  15. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +34 -1
  16. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  17. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  18. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +71 -8
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  20. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +2 -2
  21. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  22. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js +1 -1
  23. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js.map +1 -1
  24. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue.js +1 -1
  26. package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue3.js.map +1 -1
  28. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue.js +1 -1
  29. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue2.js +64 -8
  30. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue3.js +14 -4
  32. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue3.js.map +1 -1
  33. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  34. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +32 -4
  35. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  36. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +10 -2
  37. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  38. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
  39. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +84 -1
  40. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  41. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +61 -33
  42. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  43. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue.js +1 -1
  44. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +49 -2
  45. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  46. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +90 -59
  47. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  48. package/dist/esm/components/CnFollowingWidgetHost/CnFollowingWidgetHost.vue2.js +3 -1
  49. package/dist/esm/components/CnFollowingWidgetHost/CnFollowingWidgetHost.vue2.js.map +1 -1
  50. package/dist/esm/components/CnFollowingWidgetHost/CnFollowingWidgetHost.vue3.js.map +1 -1
  51. package/dist/esm/components/CnNotesCard/CnNotesCard.vue.js +1 -1
  52. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js +10 -0
  53. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
  54. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
  55. package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
  56. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +11 -0
  57. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  58. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +28 -21
  59. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  60. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  61. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +8 -0
  62. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  64. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  65. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +1 -1
  66. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  67. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  68. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  69. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +3 -15
  70. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  71. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +123 -115
  72. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  73. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
  74. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +4 -0
  75. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
  76. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
  77. package/dist/esm/components/CnStepper/CnStepper.vue.js +1 -1
  78. package/dist/esm/components/CnStepper/CnStepper.vue2.js.map +1 -1
  79. package/dist/esm/components/CnStepper/CnStepper.vue3.js.map +1 -1
  80. package/dist/esm/dialogs/CnAccessPurposeDialog.vue.js +1 -1
  81. package/dist/esm/dialogs/CnAccessPurposeDialog.vue2.js +3 -1
  82. package/dist/esm/dialogs/CnAccessPurposeDialog.vue2.js.map +1 -1
  83. package/dist/esm/dialogs/CnAccessPurposeDialog.vue3.js +26 -15
  84. package/dist/esm/dialogs/CnAccessPurposeDialog.vue3.js.map +1 -1
  85. package/dist/esm/l10n/en.json.js +3 -1
  86. package/dist/esm/l10n/en.json.js.map +1 -1
  87. package/dist/esm/l10n/nl.json.js +3 -1
  88. package/dist/esm/l10n/nl.json.js.map +1 -1
  89. package/dist/esm/library-style.js +1 -1
  90. package/dist/esm/nextcloud-vue.css +785 -518
  91. package/dist/esm/utils/builtInFormatters.js +53 -1
  92. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  93. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  94. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  95. package/dist/nextcloud-vue.cjs.js +2688 -2154
  96. package/dist/nextcloud-vue.cjs.js.map +2 -2
  97. package/dist/nextcloud-vue.css +785 -518
  98. package/l10n/en.json +3 -1
  99. package/l10n/nl.json +3 -1
  100. package/package.json +1 -1
  101. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +24 -7
  102. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +13 -1
  103. package/src/components/CnChecklistRunWidget/CnChecklistRunWidget.vue +29 -2
  104. package/src/components/CnDataTable/CnDataTable.vue +34 -1
  105. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  106. package/src/components/CnDetailPage/CnDetailPage.vue +73 -10
  107. package/src/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue +6 -1
  108. package/src/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue +1 -0
  109. package/src/components/CnDuplicateWarning/CnDuplicateWarning.vue +74 -10
  110. package/src/components/CnFactsWidget/CnFactsWidget.vue +41 -4
  111. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +131 -3
  112. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +103 -5
  113. package/src/components/CnFollowingWidgetHost/CnFollowingWidgetHost.vue +3 -1
  114. package/src/components/CnNotesCard/CnNotesCard.vue +10 -0
  115. package/src/components/CnObjectCard/CnObjectCard.vue +16 -0
  116. package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -1
  117. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +8 -0
  118. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +1 -1
  119. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +19 -19
  120. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +4 -0
  121. package/src/components/CnStepper/CnStepper.vue +10 -2
  122. package/src/css/dialog.css +22 -2
  123. package/src/css/look-board-detail.css +52 -3
  124. package/src/css/look-board-index.css +51 -1
  125. package/src/css/look-board.css +48 -5
  126. package/src/css/table.css +5 -2
  127. package/src/dialogs/CnAccessPurposeDialog.vue +27 -12
  128. package/src/schemas/app-manifest-v2.schema.json +4 -2
  129. package/src/utils/builtInFormatters.js +52 -0
  130. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"CnFlowRunsWidget.vue3.js","sources":["../../../../src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue?vue&type=template&id=76cc4912&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-flow-runs-widget\">\n\t\t<!-- One quiet line on failure, never a leaked axios status string\n\t\t (ADR-062). The real error goes to the console. -->\n\t\t<p v-if=\"error\" class=\"cn-flow-runs-widget__error\">\n\t\t\t{{ tr('Could not load the running flows') }}\n\t\t</p>\n\t\t<!-- A configured-but-unresolved subject token also shows the loading\n\t\t state: \"no runs\" would be a claim about a case the widget has not\n\t\t identified yet. -->\n\t\t<div v-else-if=\"(loading || subjectPending) && rows.length === 0\" class=\"cn-flow-runs-widget__loading\">\n\t\t\t<NcLoadingIcon :size=\"24\" />\n\t\t</div>\n\t\t<!-- A subject where nothing EVER ran gets its own line: distinct from\n\t\t a case that is merely quiet right now. -->\n\t\t<p v-else-if=\"neverRan\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t{{ tr('No flows have run yet') }}\n\t\t</p>\n\t\t<!-- Nothing running is the NORMAL state for this widget, not an\n\t\t error and not a void: one muted line, no illustration. -->\n\t\t<p v-else-if=\"rows.length === 0 && showHistory === false\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<template v-else>\n\t\t\t<p v-if=\"rows.length === 0\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-flow-runs-widget__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"(run, index) in rows\"\n\t\t\t\t\t:key=\"run.uuid || `row-${index}`\"\n\t\t\t\t\tclass=\"cn-flow-runs-widget__row\"\n\t\t\t\t\t:class=\"{ 'cn-flow-runs-widget__row--linked': isLinked }\"\n\t\t\t\t\t:data-status=\"run.status\">\n\t\t\t\t\t<component\n\t\t\t\t\t\t:is=\"runHref(run) ? 'a' : 'div'\"\n\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row-content\"\n\t\t\t\t\t\t:href=\"runHref(run) || undefined\"\n\t\t\t\t\t\t@click=\"onRowClick($event, run)\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__dot\"\n\t\t\t\t\t\t\t:class=\"`cn-flow-runs-widget__dot--${run.status}`\"\n\t\t\t\t\t\t\t:title=\"statusLabel(run.status)\" />\n\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__body\">\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__name\">{{ run.flowName }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__meta\">{{ metaLine(run) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__age\">{{ ageLabel(run) }}</span>\n\t\t\t\t\t</component>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<!-- The remainder is a count, never a scrollbar: the widget shows\n\t\t\t what fits its cell and states the honest total (ADR-062). -->\n\t\t\t<p v-if=\"hiddenCount > 0\" class=\"cn-flow-runs-widget__more\">\n\t\t\t\t{{ moreLabel }}\n\t\t\t</p>\n\t\t\t<!-- The subject's run history. Terminal rows sit under their own\n\t\t\t labelled section with a hollow dot and a muted name, so live\n\t\t\t and finished never rely on colour alone to tell apart. -->\n\t\t\t<template v-if=\"showHistory\">\n\t\t\t\t<p class=\"cn-flow-runs-widget__history-title\">\n\t\t\t\t\t{{ tr('Earlier runs') }}\n\t\t\t\t</p>\n\t\t\t\t<p v-if=\"completedError\" class=\"cn-flow-runs-widget__error\">\n\t\t\t\t\t{{ tr('Could not load the run history') }}\n\t\t\t\t</p>\n\t\t\t\t<ul v-else class=\"cn-flow-runs-widget__list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"(run, index) in completedRows\"\n\t\t\t\t\t\t:key=\"run.uuid || `completed-${index}`\"\n\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row cn-flow-runs-widget__row--terminal\"\n\t\t\t\t\t\t:class=\"{ 'cn-flow-runs-widget__row--linked': isLinked }\"\n\t\t\t\t\t\t:data-status=\"run.status\">\n\t\t\t\t\t\t<component\n\t\t\t\t\t\t\t:is=\"runHref(run) ? 'a' : 'div'\"\n\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row-content\"\n\t\t\t\t\t\t\t:href=\"runHref(run) || undefined\"\n\t\t\t\t\t\t\t@click=\"onRowClick($event, run)\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__dot cn-flow-runs-widget__dot--terminal\"\n\t\t\t\t\t\t\t\t:class=\"`cn-flow-runs-widget__dot--${run.status}`\"\n\t\t\t\t\t\t\t\t:title=\"statusLabel(run.status)\" />\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__body\">\n\t\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__name\">{{ run.flowName }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__meta\">{{ metaLine(run) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__age\">{{ ageLabel(run) }}</span>\n\t\t\t\t\t\t</component>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p v-if=\"completedError === '' && completedHiddenCount > 0\" class=\"cn-flow-runs-widget__more\">\n\t\t\t\t\t{{ completedMoreLabel }}\n\t\t\t\t</p>\n\t\t\t</template>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { computed, inject, ref } from 'vue'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { dispatchAction } from '../../utils/actionsDispatcher.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'\nimport { resolveDeepTokens, resolveFilterValue } from '../../utils/resolveFilterTokens.js'\n\n/**\n * The OpenRegister endpoint that lists the caller's live flow runs.\n *\n * Every Conduction app runs its flows on OpenRegister's ONE flow engine\n * (ADR-065), so \"what is running right now\" has a single answer for the whole\n * instance and this widget can be app-agnostic: an app places it and gets its\n * users' live runs without shipping a controller, a store or a view.\n *\n * @type {string}\n */\nconst ACTIVE_RUNS_URL = '/apps/openregister/api/flow-runs/active'\n\n/**\n * The OpenRegister endpoint that lists the finished runs for ONE subject\n * object. Subject-required by contract (openregister change\n * `flow-runs-subject-scope`): there is no org-wide \"all finished runs\" here,\n * so the widget only ever calls it with a resolved subject uuid.\n *\n * @type {string}\n */\nconst COMPLETED_RUNS_URL = '/apps/openregister/api/flow-runs/completed'\n\n/**\n * Statuses, in the order a person reads them: what is executing, what is\n * queued behind it, what is parked waiting on a timer or a child run, and\n * then the terminal set a subject's run history renders.\n *\n * @type {Record<string, string>}\n */\nconst STATUS_LABELS = {\n\trunning: 'Running',\n\tqueued: 'Queued',\n\tsuspended: 'Waiting',\n\tcompleted: 'Completed',\n\tstopped: 'Stopped',\n\tfailed: 'Failed',\n\tdead_letter: 'Failed after retries',\n}\n\n/**\n * The terminal statuses (FlowRun::TERMINAL on the OpenRegister side). Rows\n * with one of these are HISTORY: rendered in the earlier-runs section with a\n * hollow dot and a muted name, never mixed into the live list.\n *\n * @type {string[]}\n */\nconst TERMINAL_STATUSES = ['completed', 'stopped', 'failed', 'dead_letter']\n\n/**\n * CnFlowRunsWidget — the live flow runs for the viewer's organisation.\n *\n * Reads OpenRegister's `flow-runs/active` surface (non-terminal runs, scoped\n * server-side to the caller's organisation) and renders one row per run: its\n * flow's NAME, what triggered it, the step it currently sits on, and how long\n * it has been going. Placed by its registry type key `flow-runs`, so any app\n * gets the widget from a single manifest placement:\n *\n * ```js\n * { id: 'running-flows', type: 'flow-runs', title: 'Running flows',\n * content: { limit: 6, pollSeconds: 15, rowRoute: 'GraphDetail' } }\n * ```\n *\n * Why it polls: a run is a moving thing. A widget titled \"running flows\" that\n * only reflects the moment the dashboard mounted would be wrong within\n * seconds and would look identical to being right — so it refetches on an\n * interval, and stops while the tab is hidden so an idle dashboard is not a\n * background request loop.\n *\n * Statuses shown are ALL the non-terminal ones (queued / running / suspended),\n * because filtering to literally `running` would show an empty widget almost\n * always: a run holds that status only during a worker pass, while queued and\n * suspended are where live runs actually wait. See FlowRun::ACTIVE.\n *\n * Subject mode: a `content.subject` (a subject object uuid, or the\n * `@objectId` / `@object.<field>` token a detail placement authors) narrows\n * the live list to one case AND adds that case's finished runs below it, so\n * a flow that completed does not look like nothing ever happened. Tokens\n * resolve against the detail surface's injected object context, the same\n * route CnStatWidget and CnChartWidget take.\n *\n * @spec openspec/changes/cn-flow-runs-widget/specs/cn-flow-runs-widget/spec.md\n * @spec openspec/changes/cn-flow-runs-widget-subject/specs/cn-flow-runs-widget-subject/spec.md\n */\nexport default {\n\tname: 'CnFlowRunsWidget',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function, provided by CnAppRoot (identity by default)\n\t\t * so a library string localises through the consuming app's l10n.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/** The page's action dispatcher (CnPageRenderer), for `headerAction`. */\n\t\tcnDispatchAction: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's persisted configuration blob.\n\t\t *\n\t\t * - `limit` — rows to request AND show (1–50, default 6). The total is\n\t\t * always reported honestly regardless of this cap.\n\t\t * - `pollSeconds` — refetch interval (default 15; `0` disables polling).\n\t\t * - `rowRoute` — vue-router route NAME to open on a row click, receiving\n\t\t * the run's flow id as `:id` and the clicked run's uuid as `?run=`, so\n\t\t * the flow opens with that run inspected rather than merely open.\n\t\t * Omitted = rows are not clickable, which is correct for an app that\n\t\t * has no flow-detail page.\n\t\t * - `runRoute` — vue-router route NAME to open on a row click, receiving\n\t\t * the RUN's uuid as `:id`. Takes precedence over `rowRoute`; a row\n\t\t * without a run uuid falls back to the `rowRoute` flow-id behaviour.\n\t\t * - `subject` — a subject object uuid, or an object-context token\n\t\t * (`@objectId`, `@object.<field>`) a detail placement authors. When\n\t\t * set, the live list is filtered to that subject server-side and the\n\t\t * subject's finished runs render below it. Absent = today's org-wide\n\t\t * widget, unchanged.\n\t\t * - `emptyText` — override for the \"nothing running\" line.\n\t\t * - `headerAction` — an action descriptor drawn in the card header\n\t\t * (`{ label, icon?, ...action }`, \"Flow uitvoeren\"), reported through\n\t\t * the `header` event and dispatched with the page's dispatcher; its\n\t\t * tokens resolve against the record. The runs are read again after it.\n\t\t *\n\t\t * @type {{limit?: number, pollSeconds?: number, rowRoute?: string, runRoute?: string, subject?: string, emptyText?: string, title?: string}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Widget placement id, so the per-widget Refresh action\n\t\t * (`cn:widget:refresh`) reaches this instance's fetch.\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`.\n\t\t * Provide explicitly when mounting outside a CnAppRoot ancestor.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['header'],\n\n\tsetup(props) {\n\t\t// The shared endpoint engine owns request dedup, the short-TTL cache\n\t\t// and the cn:page:refresh / cn:widget:refresh subscriptions — the same\n\t\t// binding CnStatWidget's endpoint mode uses, so a page-level Refresh\n\t\t// updates this widget with everything else on the page.\n\t\tconst widgetIdRef = ref(props.widgetId)\n\n\t\t// The detail surface's object context, injected the same way the other\n\t\t// endpoint-bound widgets (stat / chart / audit-trail) receive it, so a\n\t\t// `subject: '@objectId'` placement binds the CURRENT object without the\n\t\t// manifest hardcoding a uuid. Null on plain dashboards — the token then\n\t\t// stays unresolved and the endpoint engine's `blocked` semantics hold\n\t\t// the fetch instead of sending a literal `@objectId` to the server.\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst detailCtxRaw = inject('cnDetailObjectContext', null)\n\t\tconst tokenCtx = () => ({\n\t\t\tworkspace: {},\n\t\t\tconfig: {},\n\t\t\t...(resolveObjectTokenContext(objectCtxRaw, detailCtxRaw) || {}),\n\t\t})\n\n\t\tconst configuredSubject = () => {\n\t\t\tconst subject = props.content && props.content.subject\n\t\t\treturn (typeof subject === 'string' && subject !== '') ? subject : ''\n\t\t}\n\n\t\t/**\n\t\t * The subject uuid after token resolution: `''` when no subject is\n\t\t * configured, `null` while a token still waits on the object context,\n\t\t * else the concrete uuid.\n\t\t */\n\t\tconst resolvedSubject = computed(() => {\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = resolveFilterValue(subject, tokenCtx())\n\t\t\tif (typeof value === 'string' && value.charAt(0) === '@') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (value === undefined || value === null) ? null : String(value)\n\t\t})\n\n\t\tconst source = () => {\n\t\t\tconst params = { limit: normaliseLimit(props.content && props.content.limit) }\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject !== '') {\n\t\t\t\tparams.subject = subject\n\t\t\t}\n\t\t\treturn { url: ACTIVE_RUNS_URL, method: 'GET', params }\n\t\t}\n\t\tconst { data, loading, error, refetch } = useEndpointSource(source, {\n\t\t\twidgetId: widgetIdRef,\n\t\t\tctx: tokenCtx,\n\t\t})\n\n\t\t// The history half of the subject view. A null config (no subject)\n\t\t// means the composable never queries: the org-wide widget stays a\n\t\t// single-request surface, bit-identical to before.\n\t\tconst completedSource = () => {\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn {\n\t\t\t\turl: COMPLETED_RUNS_URL,\n\t\t\t\tmethod: 'GET',\n\t\t\t\tparams: { subject, limit: normaliseLimit(props.content && props.content.limit) },\n\t\t\t}\n\t\t}\n\t\tconst {\n\t\t\tdata: completedData,\n\t\t\tloading: completedLoading,\n\t\t\terror: completedError,\n\t\t\trefetch: refetchCompleted,\n\t\t} = useEndpointSource(completedSource, {\n\t\t\twidgetId: widgetIdRef,\n\t\t\tctx: tokenCtx,\n\t\t})\n\n\t\t// `inject` is re-read in setup so Vue 2.7 and Vue 3 resolve it\n\t\t// identically to the Options `inject` block (CnStatWidget precedent).\n\t\tconst injectedTranslate = inject('cnTranslate', null)\n\n\t\treturn {\n\t\t\tpayload: data,\n\t\t\tloading,\n\t\t\terror,\n\t\t\trefetch,\n\t\t\tcompletedPayload: completedData,\n\t\t\tcompletedLoading,\n\t\t\tcompletedError,\n\t\t\trefetchCompleted,\n\t\t\tresolvedSubject,\n\t\t\tinjectedTranslate,\n\t\t\ttokenCtx,\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpollTimer: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective translate function: the explicit prop, then the injected\n\t\t * host one, then `@nextcloud/l10n` under the library's own app id.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\ttr() {\n\t\t\tconst fn = this.translate ?? this.injectedTranslate ?? this.cnTranslate\n\t\t\tif (typeof fn === 'function') {\n\t\t\t\treturn (key) => fn(key)\n\t\t\t}\n\t\t\treturn (key) => t('nextcloud-vue', key)\n\t\t},\n\n\t\t/**\n\t\t * The rows to render — the endpoint's bounded page, defensively\n\t\t * re-capped so a wider server default can never overflow the cell.\n\t\t *\n\t\t * @return {Array<object>} The run rows.\n\t\t */\n\t\trows() {\n\t\t\tconst results = (this.payload && this.payload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * How many live runs exist beyond the rendered rows.\n\t\t *\n\t\t * Read off the server's `total`, NOT the array length — the point of\n\t\t * the total is to be honest about what the cell could not fit.\n\t\t *\n\t\t * @return {number} The hidden run count (never negative).\n\t\t */\n\t\thiddenCount() {\n\t\t\tconst total = Number((this.payload && this.payload.total) ?? this.rows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total - this.rows.length)\n\t\t},\n\n\t\t/** The \"+N more\" line. */\n\t\tmoreLabel() {\n\t\t\treturn this.tr('+{count} more').replace('{count}', String(this.hiddenCount))\n\t\t},\n\n\t\t/** The nothing-running line. */\n\t\temptyLabel() {\n\t\t\tconst override = this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn this.tr(override)\n\t\t\t}\n\t\t\treturn this.tr('No flows are running')\n\t\t},\n\n\t\t/** Whether this placement is scoped to one subject object. */\n\t\thasSubject() {\n\t\t\treturn typeof this.content.subject === 'string' && this.content.subject !== ''\n\t\t},\n\n\t\t/**\n\t\t * Whether a configured subject token still waits on the object\n\t\t * context. The widget shows its loading state instead of an empty\n\t\t * line then: \"no runs\" would be a claim about a case it has not\n\t\t * identified yet.\n\t\t *\n\t\t * @return {boolean} True while the subject is configured but unresolved.\n\t\t */\n\t\tsubjectPending() {\n\t\t\treturn this.hasSubject && this.resolvedSubject === null\n\t\t},\n\n\t\t/**\n\t\t * The subject's finished runs — the completed-runs read's bounded\n\t\t * page, re-capped like the live rows.\n\t\t *\n\t\t * @return {Array<object>} The terminal run rows (empty without a subject).\n\t\t */\n\t\tcompletedRows() {\n\t\t\tif (this.hasSubject === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst results = (this.completedPayload && this.completedPayload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * How many finished runs exist beyond the rendered history rows, read\n\t\t * off the completed read's honest total.\n\t\t *\n\t\t * @return {number} The hidden finished-run count (never negative).\n\t\t */\n\t\tcompletedHiddenCount() {\n\t\t\tconst total = Number((this.completedPayload && this.completedPayload.total) ?? this.completedRows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total - this.completedRows.length)\n\t\t},\n\n\t\t/** The \"+N earlier\" line under the history section. */\n\t\tcompletedMoreLabel() {\n\t\t\treturn this.tr('+{count} earlier').replace('{count}', String(this.completedHiddenCount))\n\t\t},\n\n\t\t/**\n\t\t * Whether this subject has NO runs at all, live or finished. Rendered\n\t\t * as its own line: a case where nothing ever ran reads differently\n\t\t * from a case that is merely quiet right now.\n\t\t *\n\t\t * @return {boolean} True when both reads settled empty for a subject.\n\t\t */\n\t\tneverRan() {\n\t\t\treturn this.hasSubject\n\t\t\t\t&& this.subjectPending === false\n\t\t\t\t&& this.loading === false\n\t\t\t\t&& this.completedLoading === false\n\t\t\t\t&& this.completedError === ''\n\t\t\t\t&& this.rows.length === 0\n\t\t\t\t&& this.completedRows.length === 0\n\t\t},\n\n\t\t/** Whether the history section renders (rows or a failed history read). */\n\t\tshowHistory() {\n\t\t\treturn this.hasSubject\n\t\t\t\t&& this.subjectPending === false\n\t\t\t\t&& (this.completedRows.length > 0 || this.completedError !== '')\n\t\t},\n\n\t\t/** Whether a row click navigates (a `runRoute` or `rowRoute` is configured). */\n\t\tisLinked() {\n\t\t\treturn (typeof this.content.rowRoute === 'string' && this.content.rowRoute !== '')\n\t\t\t\t|| (typeof this.content.runRoute === 'string' && this.content.runRoute !== '')\n\t\t},\n\n\t\t/**\n\t\t * Poll interval in ms, or 0 when polling is off.\n\t\t *\n\t\t * Floored at 5s so a mis-authored `pollSeconds: 0.1` cannot turn a\n\t\t * dashboard into a request storm; `0` remains an explicit opt-out.\n\t\t *\n\t\t * @return {number} The interval in milliseconds.\n\t\t */\n\t\tpollMs() {\n\t\t\tconst raw = this.content.pollSeconds\n\t\t\tconst seconds = (raw === undefined || raw === null) ? 15 : Number(raw)\n\t\t\tif (Number.isFinite(seconds) === false || seconds <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(5, seconds) * 1000\n\t\t},\n\t},\n\n\twatch: {\n\t\t'content.headerAction': {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tthis.emitHeader()\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.emitHeader()\n\t\tthis.startPolling()\n\t\tdocument.addEventListener('visibilitychange', this.onVisibilityChange)\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.teardown()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Tell the card around the runs what its header shows: the header\n\t\t * action, if any.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\temitHeader() {\n\t\t\tconst action = this.content && this.content.headerAction\n\t\t\tconst usable = action && typeof action === 'object' && typeof action.label === 'string' && action.label !== ''\n\t\t\t/**\n\t\t\t * @event header The card header's parts: `{ link, note, action, pill }`, `action` being `{ label, icon, run }` or null.\n\t\t\t */\n\t\t\tthis.$emit('header', {\n\t\t\t\tlink: null,\n\t\t\t\tnote: '',\n\t\t\t\taction: usable ? { label: this.tr(action.label), icon: action.icon || '', run: () => this.runHeaderAction() } : null,\n\t\t\t\tpill: null,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Run the header action with the record's tokens resolved, then read the\n\t\t * runs again so a started run shows.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync runHeaderAction() {\n\t\t\tconst { label, icon, ...action } = resolveDeepTokens(this.content.headerAction, this.tokenCtx())\n\t\t\ttry {\n\t\t\t\tconst outcome = await (typeof this.cnDispatchAction === 'function'\n\t\t\t\t\t? this.cnDispatchAction(action)\n\t\t\t\t\t: dispatchAction(action, { router: this.$router || null }))\n\t\t\t\tif (outcome === false) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.refetchAll()\n\t\t},\n\n\t\t/**\n\t\t * Start (or restart) the refetch interval, unless polling is disabled.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartPolling() {\n\t\t\tthis.stopPolling()\n\t\t\tif (this.pollMs === 0 || document.hidden === true) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.pollTimer = setInterval(() => {\n\t\t\t\tthis.refetchAll()\n\t\t\t}, this.pollMs)\n\t\t},\n\n\t\t/**\n\t\t * Refetch the live read, and the history read when a subject is\n\t\t * configured — a run that finishes between polls must MOVE to the\n\t\t * history section, not vanish from the widget.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\trefetchAll() {\n\t\t\tthis.refetch(true)\n\t\t\tif (this.hasSubject) {\n\t\t\t\tthis.refetchCompleted(true)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Clear the refetch interval.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstopPolling() {\n\t\t\tif (this.pollTimer !== null) {\n\t\t\t\tclearInterval(this.pollTimer)\n\t\t\t\tthis.pollTimer = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove the timer and the visibility listener.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tteardown() {\n\t\t\tthis.stopPolling()\n\t\t\tdocument.removeEventListener('visibilitychange', this.onVisibilityChange)\n\t\t},\n\n\t\t/**\n\t\t * Pause polling while the tab is hidden; refetch once and resume when\n\t\t * it comes back, so returning to the dashboard shows current state\n\t\t * rather than whatever was true when the tab was backgrounded.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonVisibilityChange() {\n\t\t\tif (document.hidden === true) {\n\t\t\t\tthis.stopPolling()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.refetchAll()\n\t\t\tthis.startPolling()\n\t\t},\n\n\t\t/**\n\t\t * Whether a run's status is terminal — the history half of the\n\t\t * subject view. Drives the hollow-dot / muted-name row treatment.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {boolean} True for a finished run.\n\t\t */\n\t\tisTerminal(run) {\n\t\t\treturn TERMINAL_STATUSES.includes(run && run.status)\n\t\t},\n\n\t\t/**\n\t\t * The human label for a status, falling back to the raw value so an\n\t\t * engine that gains a status still renders something truthful.\n\t\t *\n\t\t * @param {string} status The run status.\n\t\t * @return {string} The label.\n\t\t */\n\t\tstatusLabel(status) {\n\t\t\tconst key = STATUS_LABELS[status]\n\t\t\treturn key ? this.tr(key) : String(status || '')\n\t\t},\n\n\t\t/**\n\t\t * The secondary line: status, the step the run sits on, and what\n\t\t * triggered it — whichever of those the run actually carries.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The meta line.\n\t\t */\n\t\tmetaLine(run) {\n\t\t\tconst parts = [this.statusLabel(run.status)]\n\t\t\tif (run.step) {\n\t\t\t\tparts.push(String(run.step))\n\t\t\t}\n\t\t\tif (run.trigger) {\n\t\t\t\tparts.push(String(run.trigger))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\t/**\n\t\t * How long the run has been going, as a coarse relative label.\n\t\t *\n\t\t * Coarse on purpose: a dashboard reader wants \"8m\", not a timestamp,\n\t\t * and a second-precision label on a 15s poll would read as stale.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The age label, or '' when the run has no start time.\n\t\t */\n\t\tageLabel(run) {\n\t\t\tconst started = Date.parse(run.created || '')\n\t\t\tif (Number.isFinite(started) === false) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst seconds = Math.max(0, Math.round((Date.now() - started) / 1000))\n\t\t\tif (seconds < 60) {\n\t\t\t\treturn this.tr('now')\n\t\t\t}\n\t\t\tconst minutes = Math.round(seconds / 60)\n\t\t\tif (minutes < 60) {\n\t\t\t\treturn `${minutes}m`\n\t\t\t}\n\t\t\tconst hours = Math.round(minutes / 60)\n\t\t\tif (hours < 24) {\n\t\t\t\treturn `${hours}h`\n\t\t\t}\n\t\t\treturn `${Math.round(hours / 24)}d`\n\t\t},\n\n\t\t/**\n\t\t * The route a run row links to, or null when it links nowhere.\n\t\t *\n\t\t * `runRoute` wins when the row carries a run uuid: on a case page a\n\t\t * click means \"show me THIS run\", and the run uuid is the deep link\n\t\t * the row contract carries. Without a `runRoute` (or on a row with no\n\t\t * uuid) `rowRoute` receives the FLOW id, the surface every\n\t\t * flow-authoring app has.\n\t\t *\n\t\t * The flow id alone answers the wrong question, so the run travels as\n\t\t * `?run=`, the address CnFlowDetail documents its `run` prop as\n\t\t * reading: a `rowRoute` click and a pasted run link open the same\n\t\t * screen.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {object|null} A vue-router location, or null.\n\t\t */\n\t\trunTarget(run) {\n\t\t\tif (this.isLinked === false || !this.$router) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst hasUuid = run.uuid !== undefined && run.uuid !== null && run.uuid !== ''\n\t\t\tconst runRoute = this.content.runRoute\n\t\t\tif (typeof runRoute === 'string' && runRoute !== '' && hasUuid) {\n\t\t\t\treturn { name: runRoute, params: { id: String(run.uuid) } }\n\t\t\t}\n\t\t\tif (typeof this.content.rowRoute !== 'string' || this.content.rowRoute === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst id = run.flowId\n\t\t\tif (id === undefined || id === null || id === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst target = { name: this.content.rowRoute, params: { id: String(id) } }\n\t\t\t// An empty `?run=` is not a deep link; CnFlowDetail's `run` prop\n\t\t\t// defaults to '' so \"no run named\" stays one value.\n\t\t\tif (hasUuid) {\n\t\t\t\ttarget.query = { run: String(run.uuid) }\n\t\t\t}\n\t\t\treturn target\n\t\t},\n\n\t\t/**\n\t\t * The href of a run row's link, or '' when the row links nowhere.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The resolved href.\n\t\t */\n\t\trunHref(run) {\n\t\t\treturn resolveHref(this.runTarget(run), this.$router)\n\t\t},\n\n\t\t/**\n\t\t * Route a plain click on a run row's link in-app; modified clicks\n\t\t * are left to the browser.\n\t\t *\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @param {object} run The clicked run row.\n\t\t * @return {void}\n\t\t */\n\t\tonRowClick(event, run) {\n\t\t\tfollowLinkClick(event, this.runTarget(run), this.$router)\n\t\t},\n\t},\n}\n\n/**\n * Clamp a configured row limit into the range the endpoint accepts.\n *\n * @param {number|string|undefined} raw The configured limit.\n * @return {number} A limit between 1 and 50 (default 6).\n */\nfunction normaliseLimit(raw) {\n\tconst n = Number(raw)\n\tif (Number.isFinite(n) === false || n <= 0) {\n\t\treturn 6\n\t}\n\treturn Math.min(50, Math.round(n))\n}\n</script>\n\n<style scoped>\n.cn-flow-runs-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 8px 0;\n}\n\n.cn-flow-runs-widget__empty,\n.cn-flow-runs-widget__error,\n.cn-flow-runs-widget__more {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-flow-runs-widget__more {\n\tpadding-top: 4px;\n}\n\n.cn-flow-runs-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__row {\n\tborder-bottom: 1px solid var(--color-border);\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-flow-runs-widget__row-content {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 6px 0;\n\tmin-width: 0;\n\tcolor: inherit;\n\ttext-decoration: none;\n}\n\na.cn-flow-runs-widget__row-content {\n\tcursor: pointer;\n}\n\n.cn-flow-runs-widget__dot {\n\tflex: 0 0 auto;\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--color-text-maxcontrast);\n}\n\n/* Executing now — the primary colour, and the only row that animates. */\n.cn-flow-runs-widget__dot--running {\n\tbackground: var(--color-primary-element, #0082c9);\n\tanimation: cn-flow-runs-pulse 1.6s ease-in-out infinite;\n}\n\n.cn-flow-runs-widget__dot--queued {\n\tbackground: var(--color-warning, #c28900);\n}\n\n.cn-flow-runs-widget__dot--suspended {\n\tbackground: var(--color-text-maxcontrast);\n}\n\n/* Terminal rows: the dot is a hollow RING, so live (filled) and finished\n (hollow) never differ by colour alone. The per-status colour still rides\n on the ring's border. */\n.cn-flow-runs-widget__dot--terminal {\n\twidth: 6px;\n\theight: 6px;\n\tbackground: transparent;\n\tborder: 2px solid var(--color-text-maxcontrast);\n}\n\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--completed {\n\tborder-color: var(--color-success, #2d7b41);\n}\n\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--failed,\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--dead_letter {\n\tborder-color: var(--color-error, #d91f2d);\n}\n\n.cn-flow-runs-widget__body {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-width: 0;\n\tflex: 1 1 auto;\n}\n\n.cn-flow-runs-widget__name {\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* Finished runs read as history: regular weight, muted colour. The hollow\n dot and the section label carry the distinction for anyone who cannot\n rely on colour. */\n.cn-flow-runs-widget__row--terminal .cn-flow-runs-widget__name {\n\tfont-weight: 400;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* Must sit AFTER the plain `__name` rule: the compound selector is more\n specific, and stylelint's no-descending-specificity flags the reverse\n order because the cascade then depends on specificity rather than\n source order — the shape that quietly stops working when someone adds\n a competing rule. */\na.cn-flow-runs-widget__row-content:hover .cn-flow-runs-widget__name {\n\ttext-decoration: underline;\n}\n\n.cn-flow-runs-widget__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-flow-runs-widget__age {\n\tflex: 0 0 auto;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-variant-numeric: tabular-nums;\n}\n\n/* The history section label. Small caps text, not a colour: the section\n boundary must survive monochrome. */\n.cn-flow-runs-widget__history-title {\n\tmargin: 0;\n\tpadding-top: 8px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n}\n\n@keyframes cn-flow-runs-pulse {\n\t0%, 100% { opacity: 1; }\n\t50% { opacity: 0.35; }\n}\n\n/* Respect a reduced-motion preference: the pulse is decoration, and the dot\n colour already carries the status. */\n@media (prefers-reduced-motion: reduce) {\n\t.cn-flow-runs-widget__dot--running {\n\t\tanimation: none;\n\t}\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_toDisplayString","_createVNode","_Fragment","_renderList","_normalizeClass","_createBlock","_resolveDynamicComponent","_createElementVNode"],"mappings":"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"}
1
+ {"version":3,"file":"CnFlowRunsWidget.vue3.js","sources":["../../../../src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue?vue&type=template&id=2f2e3e42&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-flow-runs-widget\" :class=\"{ 'cn-flow-runs-widget--pills': pills }\">\n\t\t<!-- One quiet line on failure, never a leaked axios status string\n\t\t (ADR-062). The real error goes to the console. -->\n\t\t<p v-if=\"error\" class=\"cn-flow-runs-widget__error\">\n\t\t\t{{ tr('Could not load the running flows') }}\n\t\t</p>\n\t\t<!-- A configured-but-unresolved subject token also shows the loading\n\t\t state: \"no runs\" would be a claim about a case the widget has not\n\t\t identified yet. -->\n\t\t<div v-else-if=\"(loading || subjectPending) && rows.length === 0\" class=\"cn-flow-runs-widget__loading\">\n\t\t\t<NcLoadingIcon :size=\"24\" />\n\t\t</div>\n\t\t<!-- A subject where nothing EVER ran gets its own line: distinct from\n\t\t a case that is merely quiet right now. -->\n\t\t<p v-else-if=\"neverRan\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t{{ tr('No flows have run yet') }}\n\t\t</p>\n\t\t<!-- Nothing running is the NORMAL state for this widget, not an\n\t\t error and not a void: one muted line, no illustration. -->\n\t\t<p v-else-if=\"rows.length === 0 && showHistory === false\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<template v-else>\n\t\t\t<p v-if=\"rows.length === 0 && content.quietLineWithHistory !== false\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-flow-runs-widget__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"(run, index) in rows\"\n\t\t\t\t\t:key=\"run.uuid || `row-${index}`\"\n\t\t\t\t\tclass=\"cn-flow-runs-widget__row\"\n\t\t\t\t\t:class=\"{ 'cn-flow-runs-widget__row--linked': isLinked }\"\n\t\t\t\t\t:data-status=\"run.status\">\n\t\t\t\t\t<component\n\t\t\t\t\t\t:is=\"runHref(run) ? 'a' : 'div'\"\n\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row-content\"\n\t\t\t\t\t\t:href=\"runHref(run) || undefined\"\n\t\t\t\t\t\t@click=\"onRowClick($event, run)\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"!pills\"\n\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__dot\"\n\t\t\t\t\t\t\t:class=\"`cn-flow-runs-widget__dot--${run.status}`\"\n\t\t\t\t\t\t\t:title=\"statusLabel(run.status)\" />\n\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__body\">\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__name\">{{ run.flowName }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__meta\">{{ metaLine(run) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tv-if=\"pills\"\n\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__pill\"\n\t\t\t\t\t\t\t:label=\"statusLabel(run.status)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(run.status)\"\n\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__age\">{{ ageLabel(run) }}</span>\n\t\t\t\t\t</component>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<!-- The remainder is a count, never a scrollbar: the widget shows\n\t\t\t what fits its cell and states the honest total (ADR-062). -->\n\t\t\t<p v-if=\"hiddenCount > 0\" class=\"cn-flow-runs-widget__more\">\n\t\t\t\t{{ moreLabel }}\n\t\t\t</p>\n\t\t\t<!-- The subject's run history. Terminal rows sit under their own\n\t\t\t labelled section with a hollow dot and a muted name, so live\n\t\t\t and finished never rely on colour alone to tell apart. -->\n\t\t\t<template v-if=\"showHistory\">\n\t\t\t\t<p v-if=\"content.historyTitle !== false\" class=\"cn-flow-runs-widget__history-title\">\n\t\t\t\t\t{{ tr('Earlier runs') }}\n\t\t\t\t</p>\n\t\t\t\t<p v-if=\"completedError\" class=\"cn-flow-runs-widget__error\">\n\t\t\t\t\t{{ tr('Could not load the run history') }}\n\t\t\t\t</p>\n\t\t\t\t<ul v-else class=\"cn-flow-runs-widget__list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"(run, index) in completedRows\"\n\t\t\t\t\t\t:key=\"run.uuid || `completed-${index}`\"\n\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row cn-flow-runs-widget__row--terminal\"\n\t\t\t\t\t\t:class=\"{ 'cn-flow-runs-widget__row--linked': isLinked }\"\n\t\t\t\t\t\t:data-status=\"run.status\">\n\t\t\t\t\t\t<component\n\t\t\t\t\t\t\t:is=\"runHref(run) ? 'a' : 'div'\"\n\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row-content\"\n\t\t\t\t\t\t\t:href=\"runHref(run) || undefined\"\n\t\t\t\t\t\t\t@click=\"onRowClick($event, run)\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"!pills\"\n\t\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__dot cn-flow-runs-widget__dot--terminal\"\n\t\t\t\t\t\t\t\t:class=\"`cn-flow-runs-widget__dot--${run.status}`\"\n\t\t\t\t\t\t\t\t:title=\"statusLabel(run.status)\" />\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__body\">\n\t\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__name\">{{ run.flowName }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__meta\">{{ metaLine(run) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tv-if=\"pills\"\n\t\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__pill\"\n\t\t\t\t\t\t\t\t:label=\"statusLabel(run.status)\"\n\t\t\t\t\t\t\t\t:variant=\"statusVariant(run.status)\"\n\t\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__age\">{{ ageLabel(run) }}</span>\n\t\t\t\t\t\t</component>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p v-if=\"completedError === '' && completedHiddenCount > 0\" class=\"cn-flow-runs-widget__more\">\n\t\t\t\t\t{{ completedMoreLabel }}\n\t\t\t\t</p>\n\t\t\t</template>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { computed, inject, ref } from 'vue'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { dispatchAction } from '../../utils/actionsDispatcher.js'\nimport { formatBoardDate } from '../../utils/boardDate.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'\nimport { resolveDeepTokens, resolveFilterValue } from '../../utils/resolveFilterTokens.js'\nimport { CnStatusBadge } from '../CnStatusBadge/index.js'\n\n/**\n * The OpenRegister endpoint that lists the caller's live flow runs.\n *\n * Every Conduction app runs its flows on OpenRegister's ONE flow engine\n * (ADR-065), so \"what is running right now\" has a single answer for the whole\n * instance and this widget can be app-agnostic: an app places it and gets its\n * users' live runs without shipping a controller, a store or a view.\n *\n * @type {string}\n */\nconst ACTIVE_RUNS_URL = '/apps/openregister/api/flow-runs/active'\n\n/**\n * The OpenRegister endpoint that lists the finished runs for ONE subject\n * object. Subject-required by contract (openregister change\n * `flow-runs-subject-scope`): there is no org-wide \"all finished runs\" here,\n * so the widget only ever calls it with a resolved subject uuid.\n *\n * @type {string}\n */\nconst COMPLETED_RUNS_URL = '/apps/openregister/api/flow-runs/completed'\n\n/**\n * Statuses, in the order a person reads them: what is executing, what is\n * queued behind it, what is parked waiting on a timer or a child run, and\n * then the terminal set a subject's run history renders.\n *\n * @type {Record<string, string>}\n */\nconst STATUS_LABELS = {\n\trunning: 'Running',\n\tqueued: 'Queued',\n\tsuspended: 'Waiting',\n\tcompleted: 'Completed',\n\tstopped: 'Stopped',\n\tfailed: 'Failed',\n\tdead_letter: 'Failed after retries',\n}\n\n/**\n * The terminal statuses (FlowRun::TERMINAL on the OpenRegister side). Rows\n * with one of these are HISTORY: rendered in the earlier-runs section with a\n * hollow dot and a muted name, never mixed into the live list.\n *\n * @type {string[]}\n */\nconst TERMINAL_STATUSES = ['completed', 'stopped', 'failed', 'dead_letter']\n\n/**\n * CnFlowRunsWidget — the live flow runs for the viewer's organisation.\n *\n * Reads OpenRegister's `flow-runs/active` surface (non-terminal runs, scoped\n * server-side to the caller's organisation) and renders one row per run: its\n * flow's NAME, what triggered it, the step it currently sits on, and how long\n * it has been going. Placed by its registry type key `flow-runs`, so any app\n * gets the widget from a single manifest placement:\n *\n * ```js\n * { id: 'running-flows', type: 'flow-runs', title: 'Running flows',\n * content: { limit: 6, pollSeconds: 15, rowRoute: 'GraphDetail' } }\n * ```\n *\n * Why it polls: a run is a moving thing. A widget titled \"running flows\" that\n * only reflects the moment the dashboard mounted would be wrong within\n * seconds and would look identical to being right — so it refetches on an\n * interval, and stops while the tab is hidden so an idle dashboard is not a\n * background request loop.\n *\n * Statuses shown are ALL the non-terminal ones (queued / running / suspended),\n * because filtering to literally `running` would show an empty widget almost\n * always: a run holds that status only during a worker pass, while queued and\n * suspended are where live runs actually wait. See FlowRun::ACTIVE.\n *\n * Subject mode: a `content.subject` (a subject object uuid, or the\n * `@objectId` / `@object.<field>` token a detail placement authors) narrows\n * the live list to one case AND adds that case's finished runs below it, so\n * a flow that completed does not look like nothing ever happened. Tokens\n * resolve against the detail surface's injected object context, the same\n * route CnStatWidget and CnChartWidget take.\n *\n * @spec openspec/changes/cn-flow-runs-widget/specs/cn-flow-runs-widget/spec.md\n * @spec openspec/changes/cn-flow-runs-widget-subject/specs/cn-flow-runs-widget-subject/spec.md\n */\nexport default {\n\tname: 'CnFlowRunsWidget',\n\n\tcomponents: {\n\t\tCnStatusBadge,\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function, provided by CnAppRoot (identity by default)\n\t\t * so a library string localises through the consuming app's l10n.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/** The page's action dispatcher (CnPageRenderer), for `headerAction`. */\n\t\tcnDispatchAction: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's persisted configuration blob.\n\t\t *\n\t\t * - `limit` — rows to request AND show (1–50, default 6). The total is\n\t\t * always reported honestly regardless of this cap.\n\t\t * - `pollSeconds` — refetch interval (default 15; `0` disables polling).\n\t\t * - `rowRoute` — vue-router route NAME to open on a row click, receiving\n\t\t * the run's flow id as `:id` and the clicked run's uuid as `?run=`, so\n\t\t * the flow opens with that run inspected rather than merely open.\n\t\t * Omitted = rows are not clickable, which is correct for an app that\n\t\t * has no flow-detail page.\n\t\t * - `runRoute` — vue-router route NAME to open on a row click, receiving\n\t\t * the RUN's uuid as `:id`. Takes precedence over `rowRoute`; a row\n\t\t * without a run uuid falls back to the `rowRoute` flow-id behaviour.\n\t\t * - `subject` — a subject object uuid, or an object-context token\n\t\t * (`@objectId`, `@object.<field>`) a detail placement authors. When\n\t\t * set, the live list is filtered to that subject server-side and the\n\t\t * subject's finished runs render below it. Absent = today's org-wide\n\t\t * widget, unchanged.\n\t\t * - `emptyText` — override for the \"nothing running\" line.\n\t\t * - `quietLineWithHistory` — `false` leaves out the \"nothing running\"\n\t\t * line while the subject has finished runs to list.\n\t\t * - `historyTitle` — `false` leaves out the \"Earlier runs\" heading.\n\t\t * - `statusPill` — `true` shows the status as a pill on each row\n\t\t * (no dot, and the meta line leaves the status out), rows ruled.\n\t\t * - `statusLabels` — words per status (`{ completed: 'Succeeded' }`),\n\t\t * translated, over the built-in ones.\n\t\t * - `triggerLabels` — the meta line per trigger in words\n\t\t * (`{ 'object.created': 'Started when the case was created' }`), translated.\n\t\t * - `age` — `'datetime'` shows when the run started (\"3 okt, 14:32\")\n\t\t * instead of how long ago (\"13d\").\n\t\t * - `headerAction` — an action descriptor drawn in the card header\n\t\t * (`{ label, icon?, ...action }`, \"Flow uitvoeren\"), reported through\n\t\t * the `header` event and dispatched with the page's dispatcher; its\n\t\t * tokens resolve against the record. The runs are read again after it.\n\t\t *\n\t\t * @type {{limit?: number, pollSeconds?: number, rowRoute?: string, runRoute?: string, subject?: string, emptyText?: string, title?: string}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Widget placement id, so the per-widget Refresh action\n\t\t * (`cn:widget:refresh`) reaches this instance's fetch.\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`.\n\t\t * Provide explicitly when mounting outside a CnAppRoot ancestor.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['header'],\n\n\tsetup(props) {\n\t\t// The shared endpoint engine owns request dedup, the short-TTL cache\n\t\t// and the cn:page:refresh / cn:widget:refresh subscriptions — the same\n\t\t// binding CnStatWidget's endpoint mode uses, so a page-level Refresh\n\t\t// updates this widget with everything else on the page.\n\t\tconst widgetIdRef = ref(props.widgetId)\n\n\t\t// The detail surface's object context, injected the same way the other\n\t\t// endpoint-bound widgets (stat / chart / audit-trail) receive it, so a\n\t\t// `subject: '@objectId'` placement binds the CURRENT object without the\n\t\t// manifest hardcoding a uuid. Null on plain dashboards — the token then\n\t\t// stays unresolved and the endpoint engine's `blocked` semantics hold\n\t\t// the fetch instead of sending a literal `@objectId` to the server.\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst detailCtxRaw = inject('cnDetailObjectContext', null)\n\t\tconst tokenCtx = () => ({\n\t\t\tworkspace: {},\n\t\t\tconfig: {},\n\t\t\t...(resolveObjectTokenContext(objectCtxRaw, detailCtxRaw) || {}),\n\t\t})\n\n\t\tconst configuredSubject = () => {\n\t\t\tconst subject = props.content && props.content.subject\n\t\t\treturn (typeof subject === 'string' && subject !== '') ? subject : ''\n\t\t}\n\n\t\t/**\n\t\t * The subject uuid after token resolution: `''` when no subject is\n\t\t * configured, `null` while a token still waits on the object context,\n\t\t * else the concrete uuid.\n\t\t */\n\t\tconst resolvedSubject = computed(() => {\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = resolveFilterValue(subject, tokenCtx())\n\t\t\tif (typeof value === 'string' && value.charAt(0) === '@') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (value === undefined || value === null) ? null : String(value)\n\t\t})\n\n\t\tconst source = () => {\n\t\t\tconst params = { limit: normaliseLimit(props.content && props.content.limit) }\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject !== '') {\n\t\t\t\tparams.subject = subject\n\t\t\t}\n\t\t\treturn { url: ACTIVE_RUNS_URL, method: 'GET', params }\n\t\t}\n\t\tconst { data, loading, error, refetch } = useEndpointSource(source, {\n\t\t\twidgetId: widgetIdRef,\n\t\t\tctx: tokenCtx,\n\t\t})\n\n\t\t// The history half of the subject view. A null config (no subject)\n\t\t// means the composable never queries: the org-wide widget stays a\n\t\t// single-request surface, bit-identical to before.\n\t\tconst completedSource = () => {\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn {\n\t\t\t\turl: COMPLETED_RUNS_URL,\n\t\t\t\tmethod: 'GET',\n\t\t\t\tparams: { subject, limit: normaliseLimit(props.content && props.content.limit) },\n\t\t\t}\n\t\t}\n\t\tconst {\n\t\t\tdata: completedData,\n\t\t\tloading: completedLoading,\n\t\t\terror: completedError,\n\t\t\trefetch: refetchCompleted,\n\t\t} = useEndpointSource(completedSource, {\n\t\t\twidgetId: widgetIdRef,\n\t\t\tctx: tokenCtx,\n\t\t})\n\n\t\t// `inject` is re-read in setup so Vue 2.7 and Vue 3 resolve it\n\t\t// identically to the Options `inject` block (CnStatWidget precedent).\n\t\tconst injectedTranslate = inject('cnTranslate', null)\n\n\t\treturn {\n\t\t\tpayload: data,\n\t\t\tloading,\n\t\t\terror,\n\t\t\trefetch,\n\t\t\tcompletedPayload: completedData,\n\t\t\tcompletedLoading,\n\t\t\tcompletedError,\n\t\t\trefetchCompleted,\n\t\t\tresolvedSubject,\n\t\t\tinjectedTranslate,\n\t\t\ttokenCtx,\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpollTimer: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective translate function: the explicit prop, then the injected\n\t\t * host one, then `@nextcloud/l10n` under the library's own app id.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\ttr() {\n\t\t\tconst fn = this.translate ?? this.injectedTranslate ?? this.cnTranslate\n\t\t\tif (typeof fn === 'function') {\n\t\t\t\treturn (key) => fn(key)\n\t\t\t}\n\t\t\treturn (key) => t('nextcloud-vue', key)\n\t\t},\n\n\t\t/**\n\t\t * The rows to render — the endpoint's bounded page, defensively\n\t\t * re-capped so a wider server default can never overflow the cell.\n\t\t *\n\t\t * @return {Array<object>} The run rows.\n\t\t */\n\t\trows() {\n\t\t\tconst results = (this.payload && this.payload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * How many live runs exist beyond the rendered rows.\n\t\t *\n\t\t * Read off the server's `total`, NOT the array length — the point of\n\t\t * the total is to be honest about what the cell could not fit.\n\t\t *\n\t\t * @return {number} The hidden run count (never negative).\n\t\t */\n\t\thiddenCount() {\n\t\t\tconst total = Number((this.payload && this.payload.total) ?? this.rows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total - this.rows.length)\n\t\t},\n\n\t\t/** The \"+N more\" line. */\n\t\tmoreLabel() {\n\t\t\treturn this.tr('+{count} more').replace('{count}', String(this.hiddenCount))\n\t\t},\n\n\t\t/** Whether each row shows its status as a pill (`content.statusPill`). */\n\t\tpills() {\n\t\t\treturn this.content.statusPill === true\n\t\t},\n\n\t\t/** The nothing-running line. */\n\t\temptyLabel() {\n\t\t\tconst override = this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn this.tr(override)\n\t\t\t}\n\t\t\treturn this.tr('No flows are running')\n\t\t},\n\n\t\t/** Whether this placement is scoped to one subject object. */\n\t\thasSubject() {\n\t\t\treturn typeof this.content.subject === 'string' && this.content.subject !== ''\n\t\t},\n\n\t\t/**\n\t\t * Whether a configured subject token still waits on the object\n\t\t * context. The widget shows its loading state instead of an empty\n\t\t * line then: \"no runs\" would be a claim about a case it has not\n\t\t * identified yet.\n\t\t *\n\t\t * @return {boolean} True while the subject is configured but unresolved.\n\t\t */\n\t\tsubjectPending() {\n\t\t\treturn this.hasSubject && this.resolvedSubject === null\n\t\t},\n\n\t\t/**\n\t\t * The subject's finished runs — the completed-runs read's bounded\n\t\t * page, re-capped like the live rows.\n\t\t *\n\t\t * @return {Array<object>} The terminal run rows (empty without a subject).\n\t\t */\n\t\tcompletedRows() {\n\t\t\tif (this.hasSubject === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst results = (this.completedPayload && this.completedPayload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * How many finished runs exist beyond the rendered history rows, read\n\t\t * off the completed read's honest total.\n\t\t *\n\t\t * @return {number} The hidden finished-run count (never negative).\n\t\t */\n\t\tcompletedHiddenCount() {\n\t\t\tconst total = Number((this.completedPayload && this.completedPayload.total) ?? this.completedRows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total - this.completedRows.length)\n\t\t},\n\n\t\t/** The \"+N earlier\" line under the history section. */\n\t\tcompletedMoreLabel() {\n\t\t\treturn this.tr('+{count} earlier').replace('{count}', String(this.completedHiddenCount))\n\t\t},\n\n\t\t/**\n\t\t * Whether this subject has NO runs at all, live or finished. Rendered\n\t\t * as its own line: a case where nothing ever ran reads differently\n\t\t * from a case that is merely quiet right now.\n\t\t *\n\t\t * @return {boolean} True when both reads settled empty for a subject.\n\t\t */\n\t\tneverRan() {\n\t\t\treturn this.hasSubject\n\t\t\t\t&& this.subjectPending === false\n\t\t\t\t&& this.loading === false\n\t\t\t\t&& this.completedLoading === false\n\t\t\t\t&& this.completedError === ''\n\t\t\t\t&& this.rows.length === 0\n\t\t\t\t&& this.completedRows.length === 0\n\t\t},\n\n\t\t/** Whether the history section renders (rows or a failed history read). */\n\t\tshowHistory() {\n\t\t\treturn this.hasSubject\n\t\t\t\t&& this.subjectPending === false\n\t\t\t\t&& (this.completedRows.length > 0 || this.completedError !== '')\n\t\t},\n\n\t\t/** Whether a row click navigates (a `runRoute` or `rowRoute` is configured). */\n\t\tisLinked() {\n\t\t\treturn (typeof this.content.rowRoute === 'string' && this.content.rowRoute !== '')\n\t\t\t\t|| (typeof this.content.runRoute === 'string' && this.content.runRoute !== '')\n\t\t},\n\n\t\t/**\n\t\t * Poll interval in ms, or 0 when polling is off.\n\t\t *\n\t\t * Floored at 5s so a mis-authored `pollSeconds: 0.1` cannot turn a\n\t\t * dashboard into a request storm; `0` remains an explicit opt-out.\n\t\t *\n\t\t * @return {number} The interval in milliseconds.\n\t\t */\n\t\tpollMs() {\n\t\t\tconst raw = this.content.pollSeconds\n\t\t\tconst seconds = (raw === undefined || raw === null) ? 15 : Number(raw)\n\t\t\tif (Number.isFinite(seconds) === false || seconds <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(5, seconds) * 1000\n\t\t},\n\t},\n\n\twatch: {\n\t\t'content.headerAction': {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tthis.emitHeader()\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.emitHeader()\n\t\tthis.startPolling()\n\t\tdocument.addEventListener('visibilitychange', this.onVisibilityChange)\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.teardown()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Tell the card around the runs what its header shows: the header\n\t\t * action, if any.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\temitHeader() {\n\t\t\tconst action = this.content && this.content.headerAction\n\t\t\tconst usable = action && typeof action === 'object' && typeof action.label === 'string' && action.label !== ''\n\t\t\t/**\n\t\t\t * @event header The card header's parts: `{ link, note, action, pill }`, `action` being `{ label, icon, run }` or null.\n\t\t\t */\n\t\t\tthis.$emit('header', {\n\t\t\t\tlink: null,\n\t\t\t\tnote: '',\n\t\t\t\taction: usable ? { label: this.tr(action.label), icon: action.icon || '', run: () => this.runHeaderAction() } : null,\n\t\t\t\tpill: null,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Run the header action with the record's tokens resolved, then read the\n\t\t * runs again so a started run shows.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync runHeaderAction() {\n\t\t\tconst { label, icon, ...action } = resolveDeepTokens(this.content.headerAction, this.tokenCtx())\n\t\t\ttry {\n\t\t\t\tconst outcome = await (typeof this.cnDispatchAction === 'function'\n\t\t\t\t\t? this.cnDispatchAction(action)\n\t\t\t\t\t: dispatchAction(action, { router: this.$router || null }))\n\t\t\t\tif (outcome === false) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.refetchAll()\n\t\t},\n\n\t\t/**\n\t\t * Start (or restart) the refetch interval, unless polling is disabled.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartPolling() {\n\t\t\tthis.stopPolling()\n\t\t\tif (this.pollMs === 0 || document.hidden === true) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.pollTimer = setInterval(() => {\n\t\t\t\tthis.refetchAll()\n\t\t\t}, this.pollMs)\n\t\t},\n\n\t\t/**\n\t\t * Refetch the live read, and the history read when a subject is\n\t\t * configured — a run that finishes between polls must MOVE to the\n\t\t * history section, not vanish from the widget.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\trefetchAll() {\n\t\t\tthis.refetch(true)\n\t\t\tif (this.hasSubject) {\n\t\t\t\tthis.refetchCompleted(true)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Clear the refetch interval.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstopPolling() {\n\t\t\tif (this.pollTimer !== null) {\n\t\t\t\tclearInterval(this.pollTimer)\n\t\t\t\tthis.pollTimer = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove the timer and the visibility listener.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tteardown() {\n\t\t\tthis.stopPolling()\n\t\t\tdocument.removeEventListener('visibilitychange', this.onVisibilityChange)\n\t\t},\n\n\t\t/**\n\t\t * Pause polling while the tab is hidden; refetch once and resume when\n\t\t * it comes back, so returning to the dashboard shows current state\n\t\t * rather than whatever was true when the tab was backgrounded.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonVisibilityChange() {\n\t\t\tif (document.hidden === true) {\n\t\t\t\tthis.stopPolling()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.refetchAll()\n\t\t\tthis.startPolling()\n\t\t},\n\n\t\t/**\n\t\t * Whether a run's status is terminal — the history half of the\n\t\t * subject view. Drives the hollow-dot / muted-name row treatment.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {boolean} True for a finished run.\n\t\t */\n\t\tisTerminal(run) {\n\t\t\treturn TERMINAL_STATUSES.includes(run && run.status)\n\t\t},\n\n\t\t/**\n\t\t * The human label for a status, falling back to the raw value so an\n\t\t * engine that gains a status still renders something truthful.\n\t\t *\n\t\t * @param {string} status The run status.\n\t\t * @return {string} The label.\n\t\t */\n\t\tstatusLabel(status) {\n\t\t\tconst own = this.content.statusLabels && this.content.statusLabels[status]\n\t\t\tconst key = (typeof own === 'string' && own !== '') ? own : STATUS_LABELS[status]\n\t\t\treturn key ? this.tr(key) : String(status || '')\n\t\t},\n\n\t\t/**\n\t\t * The pill tone of a status: finished well is success, failed is error,\n\t\t * anything still going is info.\n\t\t *\n\t\t * @param {string} status The run status.\n\t\t * @return {string} A CnStatusBadge variant.\n\t\t * @spec openspec/changes/flow-runs-ruled-with-pills/specs/cn-flow-runs-widget/spec.md\n\t\t */\n\t\tstatusVariant(status) {\n\t\t\tif (status === 'completed') {\n\t\t\t\treturn 'success'\n\t\t\t}\n\t\t\tif (status === 'failed' || status === 'dead_letter') {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\treturn status === 'stopped' ? 'default' : 'info'\n\t\t},\n\n\t\t/**\n\t\t * The secondary line: status, the step the run sits on, and what\n\t\t * triggered it — whichever of those the run actually carries.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The meta line.\n\t\t */\n\t\tmetaLine(run) {\n\t\t\tconst words = this.content.triggerLabels && this.content.triggerLabels[run.trigger]\n\t\t\tif (typeof words === 'string' && words !== '') {\n\t\t\t\treturn this.tr(words)\n\t\t\t}\n\t\t\tconst parts = this.pills ? [] : [this.statusLabel(run.status)]\n\t\t\tif (run.step) {\n\t\t\t\tparts.push(String(run.step))\n\t\t\t}\n\t\t\tif (run.trigger) {\n\t\t\t\tparts.push(String(run.trigger))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\t/**\n\t\t * How long the run has been going, as a coarse relative label.\n\t\t *\n\t\t * Coarse on purpose: a dashboard reader wants \"8m\", not a timestamp,\n\t\t * and a second-precision label on a 15s poll would read as stale.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The age label, or '' when the run has no start time.\n\t\t */\n\t\tageLabel(run) {\n\t\t\tconst started = Date.parse(run.created || '')\n\t\t\tif (Number.isFinite(started) === false) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (this.content.age === 'datetime') {\n\t\t\t\tconst at = new Date(started)\n\t\t\t\tconst pad = (value) => String(value).padStart(2, '0')\n\t\t\t\treturn `${formatBoardDate(at)}, ${pad(at.getHours())}:${pad(at.getMinutes())}`\n\t\t\t}\n\t\t\tconst seconds = Math.max(0, Math.round((Date.now() - started) / 1000))\n\t\t\tif (seconds < 60) {\n\t\t\t\treturn this.tr('now')\n\t\t\t}\n\t\t\tconst minutes = Math.round(seconds / 60)\n\t\t\tif (minutes < 60) {\n\t\t\t\treturn `${minutes}m`\n\t\t\t}\n\t\t\tconst hours = Math.round(minutes / 60)\n\t\t\tif (hours < 24) {\n\t\t\t\treturn `${hours}h`\n\t\t\t}\n\t\t\treturn `${Math.round(hours / 24)}d`\n\t\t},\n\n\t\t/**\n\t\t * The route a run row links to, or null when it links nowhere.\n\t\t *\n\t\t * `runRoute` wins when the row carries a run uuid: on a case page a\n\t\t * click means \"show me THIS run\", and the run uuid is the deep link\n\t\t * the row contract carries. Without a `runRoute` (or on a row with no\n\t\t * uuid) `rowRoute` receives the FLOW id, the surface every\n\t\t * flow-authoring app has.\n\t\t *\n\t\t * The flow id alone answers the wrong question, so the run travels as\n\t\t * `?run=`, the address CnFlowDetail documents its `run` prop as\n\t\t * reading: a `rowRoute` click and a pasted run link open the same\n\t\t * screen.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {object|null} A vue-router location, or null.\n\t\t */\n\t\trunTarget(run) {\n\t\t\tif (this.isLinked === false || !this.$router) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst hasUuid = run.uuid !== undefined && run.uuid !== null && run.uuid !== ''\n\t\t\tconst runRoute = this.content.runRoute\n\t\t\tif (typeof runRoute === 'string' && runRoute !== '' && hasUuid) {\n\t\t\t\treturn { name: runRoute, params: { id: String(run.uuid) } }\n\t\t\t}\n\t\t\tif (typeof this.content.rowRoute !== 'string' || this.content.rowRoute === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst id = run.flowId\n\t\t\tif (id === undefined || id === null || id === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst target = { name: this.content.rowRoute, params: { id: String(id) } }\n\t\t\t// An empty `?run=` is not a deep link; CnFlowDetail's `run` prop\n\t\t\t// defaults to '' so \"no run named\" stays one value.\n\t\t\tif (hasUuid) {\n\t\t\t\ttarget.query = { run: String(run.uuid) }\n\t\t\t}\n\t\t\treturn target\n\t\t},\n\n\t\t/**\n\t\t * The href of a run row's link, or '' when the row links nowhere.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The resolved href.\n\t\t */\n\t\trunHref(run) {\n\t\t\treturn resolveHref(this.runTarget(run), this.$router)\n\t\t},\n\n\t\t/**\n\t\t * Route a plain click on a run row's link in-app; modified clicks\n\t\t * are left to the browser.\n\t\t *\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @param {object} run The clicked run row.\n\t\t * @return {void}\n\t\t */\n\t\tonRowClick(event, run) {\n\t\t\tfollowLinkClick(event, this.runTarget(run), this.$router)\n\t\t},\n\t},\n}\n\n/**\n * Clamp a configured row limit into the range the endpoint accepts.\n *\n * @param {number|string|undefined} raw The configured limit.\n * @return {number} A limit between 1 and 50 (default 6).\n */\nfunction normaliseLimit(raw) {\n\tconst n = Number(raw)\n\tif (Number.isFinite(n) === false || n <= 0) {\n\t\treturn 6\n\t}\n\treturn Math.min(50, Math.round(n))\n}\n</script>\n\n<style scoped>\n.cn-flow-runs-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 8px 0;\n}\n\n.cn-flow-runs-widget__empty,\n.cn-flow-runs-widget__error,\n.cn-flow-runs-widget__more {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-flow-runs-widget__more {\n\tpadding-top: 4px;\n}\n\n.cn-flow-runs-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__row {\n\tborder-bottom: 1px solid var(--color-border);\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-flow-runs-widget__row-content {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 6px 0;\n\tmin-width: 0;\n\tcolor: inherit;\n\ttext-decoration: none;\n}\n\na.cn-flow-runs-widget__row-content {\n\tcursor: pointer;\n}\n\n.cn-flow-runs-widget__dot {\n\tflex: 0 0 auto;\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--color-text-maxcontrast);\n}\n\n/* Executing now — the primary colour, and the only row that animates. */\n.cn-flow-runs-widget__dot--running {\n\tbackground: var(--color-primary-element, #0082c9);\n\tanimation: cn-flow-runs-pulse 1.6s ease-in-out infinite;\n}\n\n.cn-flow-runs-widget__dot--queued {\n\tbackground: var(--color-warning, #c28900);\n}\n\n.cn-flow-runs-widget__dot--suspended {\n\tbackground: var(--color-text-maxcontrast);\n}\n\n/* Terminal rows: the dot is a hollow RING, so live (filled) and finished\n (hollow) never differ by colour alone. The per-status colour still rides\n on the ring's border. */\n.cn-flow-runs-widget__dot--terminal {\n\twidth: 6px;\n\theight: 6px;\n\tbackground: transparent;\n\tborder: 2px solid var(--color-text-maxcontrast);\n}\n\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--completed {\n\tborder-color: var(--color-success, #2d7b41);\n}\n\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--failed,\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--dead_letter {\n\tborder-color: var(--color-error, #d91f2d);\n}\n\n.cn-flow-runs-widget__body {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-width: 0;\n\tflex: 1 1 auto;\n}\n\n.cn-flow-runs-widget__name {\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* Finished runs read as history: regular weight, muted colour. The hollow\n dot and the section label carry the distinction for anyone who cannot\n rely on colour. */\n.cn-flow-runs-widget__row--terminal .cn-flow-runs-widget__name {\n\tfont-weight: 400;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* Must sit AFTER the plain `__name` rule: the compound selector is more\n specific, and stylelint's no-descending-specificity flags the reverse\n order because the cascade then depends on specificity rather than\n source order — the shape that quietly stops working when someone adds\n a competing rule. */\na.cn-flow-runs-widget__row-content:hover .cn-flow-runs-widget__name {\n\ttext-decoration: underline;\n}\n\n.cn-flow-runs-widget__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-flow-runs-widget__age {\n\tflex: 0 0 auto;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-variant-numeric: tabular-nums;\n}\n\n/* The history section label. Small caps text, not a colour: the section\n boundary must survive monochrome. */\n.cn-flow-runs-widget__history-title {\n\tmargin: 0;\n\tpadding-top: 8px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n}\n\n/* Status pills: each row ruled on top, the name over its meta line, the pill\n and the start moment at the end, as the Dq case boards draw the Flows card. */\n.cn-flow-runs-widget--pills .cn-flow-runs-widget__row {\n\tborder-top: 1px solid var(--color-border);\n\tborder-bottom: none;\n}\n\n.cn-flow-runs-widget--pills .cn-flow-runs-widget__row-content {\n\tflex-wrap: wrap;\n\tgap: 6px 16px;\n\tpadding: 11px 0;\n}\n\n.cn-flow-runs-widget--pills .cn-flow-runs-widget__body {\n\tflex: 1 1 260px;\n\tgap: 2px;\n}\n\n.cn-flow-runs-widget--pills .cn-flow-runs-widget__name,\n.cn-flow-runs-widget--pills .cn-flow-runs-widget__row--terminal .cn-flow-runs-widget__name {\n\tfont-size: 15px;\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n\twhite-space: normal;\n}\n\n.cn-flow-runs-widget--pills .cn-flow-runs-widget__meta {\n\tfont-size: 13px;\n\twhite-space: normal;\n}\n\n.cn-flow-runs-widget--pills .cn-flow-runs-widget__age {\n\tflex: 0 0 104px;\n\tfont-size: 13px;\n\ttext-align: end;\n}\n\n@keyframes cn-flow-runs-pulse {\n\t0%, 100% { opacity: 1; }\n\t50% { opacity: 0.35; }\n}\n\n/* Respect a reduced-motion preference: the pulse is decoration, and the dot\n colour already carries the status. */\n@media (prefers-reduced-motion: reduce) {\n\t.cn-flow-runs-widget__dot--running {\n\t\tanimation: none;\n\t}\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_toDisplayString","_openBlock","_createVNode","_Fragment","_renderList","_createBlock","_resolveDynamicComponent","_createElementVNode"],"mappings":"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"}
@@ -24,6 +24,8 @@ var script = {
24
24
  cnWidgetSelection: { from: WIDGET_SELECTION, default: null },
25
25
  /** The host's translate (CnAppRoot provides `cnTranslate`), applied to the title before it is filled. */
26
26
  cnTranslate: { default: () => (key) => key },
27
+ /** The app's named formatters (CnAppRoot provides `cnFormatters`), for a `{field|formatter}` token in the title. */
28
+ cnFormatters: { default: () => ({}) },
27
29
  },
28
30
 
29
31
  inheritAttrs: false,
@@ -75,7 +77,7 @@ var script = {
75
77
  return this.widget
76
78
  }
77
79
  const translated = this.cnTranslate(title);
78
- return { ...this.widget, title: fillTemplate(translated, this.row) || translated.replace(/\s*\{[^}]*\}/g, '') }
80
+ return { ...this.widget, title: fillTemplate(translated, this.row, new Date(), { formatters: this.cnFormatters }) || translated.replace(/\s*\{[^}]*\}/g, '') }
79
81
  },
80
82
  },
81
83
  };
@@ -1 +1 @@
1
- {"version":3,"file":"CnFollowingWidgetHost.vue2.js","sources":["../../../../src/components/CnFollowingWidgetHost/CnFollowingWidgetHost.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<CnDetailWidgetHost\n\t\tv-if=\"row\"\n\t\tv-bind=\"$attrs\"\n\t\t:widget=\"followedWidget\"\n\t\t:object=\"row\"\n\t\t:objectId=\"rowId\"\n\t\t:objectType=\"follows.schema || ''\"\n\t\t:register=\"follows.register || register\"\n\t\t:schema=\"follows.schema || schema\" />\n</template>\n\n<script>\nimport CnDetailWidgetHost from '../CnDetailWidgetHost/CnDetailWidgetHost.vue'\nimport { fillTemplate } from '../../utils/fillTemplate.js'\nimport { followedRow, WIDGET_SELECTION } from '../../utils/widgetSelection.js'\n\n/**\n * CnFollowingWidgetHost: hosts a widget on the row another list on the page\n * has selected, instead of on the page's own record (widget `follows`).\n *\n * `follows: { widget, register, schema, when? }` names the list (by widget\n * id) and the register and schema its rows belong to. The card shows the\n * selected row when `when` holds for it, else, while nothing is selected, the\n * first row for which it holds, and draws nothing without such a row. Its\n * title's `{field}` placeholders are filled from the row. Every other prop\n * passes through to CnDetailWidgetHost.\n *\n * @spec openspec/changes/side-card-follows-row/specs/side-card-follows-row/spec.md#requirement-a-card-follows-the-selected-row\n */\nexport default {\n\tname: 'CnFollowingWidgetHost',\n\n\tcomponents: { CnDetailWidgetHost },\n\n\tinject: {\n\t\tcnWidgetSelection: { from: WIDGET_SELECTION, default: null },\n\t\t/** The host's translate (CnAppRoot provides `cnTranslate`), applied to the title before it is filled. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tinheritAttrs: false,\n\n\tprops: {\n\t\t/** The widget definition, with `follows`. */\n\t\twidget: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** The page's register, used when `follows` names none. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The page's schema, used when `follows` names none. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/** @return {object} The widget's `follows` declaration. */\n\t\tfollows() {\n\t\t\treturn (this.widget && typeof this.widget.follows === 'object' && this.widget.follows) || {}\n\t\t},\n\n\t\t/** @return {object|null} The row the card is hosted on. */\n\t\trow() {\n\t\t\tif (!this.cnWidgetSelection || !this.follows.widget) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn followedRow(this.cnWidgetSelection.entry(this.follows.widget), this.follows)\n\t\t},\n\n\t\t/** @return {string} The row's id. */\n\t\trowId() {\n\t\t\tconst row = this.row || {}\n\t\t\treturn String(row.id ?? row['@self']?.id ?? '')\n\t\t},\n\n\t\t/** @return {object} The widget, its title filled from the row. */\n\t\tfollowedWidget() {\n\t\t\tconst title = this.widget.title\n\t\t\tif (typeof title !== 'string' || !title.includes('{')) {\n\t\t\t\treturn this.widget\n\t\t\t}\n\t\t\tconst translated = this.cnTranslate(title)\n\t\t\treturn { ...this.widget, title: fillTemplate(translated, this.row) || translated.replace(/\\s*\\{[^}]*\\}/g, '') }\n\t\t},\n\t},\n}\n</script>\n"],"names":["CnDetailWidgetHost"],"mappings":";;;;AAsBA;AACC;;AAEA;;;AAGA;;AAEA;AACA;AACA;;;AAGD,aAAe;CACd,IAAI,EAAE,uBAAuB;;AAE7B,CAAA,UAAU,EAAE,sBAAEA,YAAoB;;AAElC,CAAA,MAAM,EAAE;EACP,iBAAiB,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,IAAG,EAAG;AAC5D;AACA,EAAA,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,KAAK,KAAK;EAC5C;;AAED,CAAA,YAAY,EAAE,KAAK;;AAEnB,CAAA,KAAK,EAAE;AACN;AACA,EAAA,MAAM,EAAE;GACP,IAAI,EAAE,MAAM;GACZ,QAAQ,EAAE,IAAI;GACd;;;AAGD,EAAA,QAAQ,EAAE;AACT,GAAA,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;GACtB,OAAO,EAAE,EAAE;GACX;;;AAGD,EAAA,MAAM,EAAE;AACP,GAAA,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;GACtB,OAAO,EAAE,EAAE;GACX;EACD;;AAED,CAAA,QAAQ,EAAE;;AAET,EAAA,OAAO,GAAG;AACT,GAAA,OAAO,CAAC,IAAI,CAAC,MAAO,IAAG,OAAO,IAAI,CAAC,MAAM,CAAC,OAAM,KAAM,YAAY,IAAI,CAAC,MAAM,CAAC,OAAO,KAAK,EAAC;GAC3F;;;EAGD,GAAG,GAAG;GACL,IAAI,CAAC,IAAI,CAAC,iBAAkB,IAAG,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;AACpD,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,OAAO,WAAW,CAAC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,CAAA;GAClF;;AAED;AACA,EAAA,KAAK,GAAG;AACP,GAAA,MAAM,MAAM,IAAI,CAAC,GAAI,IAAG,GAAC;AACzB,GAAA,OAAO,MAAM,CAAC,GAAG,CAAC,EAAG,IAAG,GAAG,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;GAC9C;;;AAGD,EAAA,cAAc,GAAG;GAChB,MAAM,KAAM,GAAE,IAAI,CAAC,MAAM,CAAC,MAAI;GAC9B,IAAI,OAAO,KAAI,KAAM,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AACtD,IAAA,OAAO,IAAI,CAAC,MAAK;AAClB,IAAA;AACA,GAAA,MAAM,UAAW,GAAE,IAAI,CAAC,WAAW,CAAC,KAAK,EAAA;GACzC,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAA,IAAK,UAAU,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAE,EAAA;GAC9G;EACD;AACF;;;;"}
1
+ {"version":3,"file":"CnFollowingWidgetHost.vue2.js","sources":["../../../../src/components/CnFollowingWidgetHost/CnFollowingWidgetHost.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<CnDetailWidgetHost\n\t\tv-if=\"row\"\n\t\tv-bind=\"$attrs\"\n\t\t:widget=\"followedWidget\"\n\t\t:object=\"row\"\n\t\t:objectId=\"rowId\"\n\t\t:objectType=\"follows.schema || ''\"\n\t\t:register=\"follows.register || register\"\n\t\t:schema=\"follows.schema || schema\" />\n</template>\n\n<script>\nimport CnDetailWidgetHost from '../CnDetailWidgetHost/CnDetailWidgetHost.vue'\nimport { fillTemplate } from '../../utils/fillTemplate.js'\nimport { followedRow, WIDGET_SELECTION } from '../../utils/widgetSelection.js'\n\n/**\n * CnFollowingWidgetHost: hosts a widget on the row another list on the page\n * has selected, instead of on the page's own record (widget `follows`).\n *\n * `follows: { widget, register, schema, when? }` names the list (by widget\n * id) and the register and schema its rows belong to. The card shows the\n * selected row when `when` holds for it, else, while nothing is selected, the\n * first row for which it holds, and draws nothing without such a row. Its\n * title's `{field}` placeholders are filled from the row. Every other prop\n * passes through to CnDetailWidgetHost.\n *\n * @spec openspec/changes/side-card-follows-row/specs/side-card-follows-row/spec.md#requirement-a-card-follows-the-selected-row\n */\nexport default {\n\tname: 'CnFollowingWidgetHost',\n\n\tcomponents: { CnDetailWidgetHost },\n\n\tinject: {\n\t\tcnWidgetSelection: { from: WIDGET_SELECTION, default: null },\n\t\t/** The host's translate (CnAppRoot provides `cnTranslate`), applied to the title before it is filled. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/** The app's named formatters (CnAppRoot provides `cnFormatters`), for a `{field|formatter}` token in the title. */\n\t\tcnFormatters: { default: () => ({}) },\n\t},\n\n\tinheritAttrs: false,\n\n\tprops: {\n\t\t/** The widget definition, with `follows`. */\n\t\twidget: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** The page's register, used when `follows` names none. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The page's schema, used when `follows` names none. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/** @return {object} The widget's `follows` declaration. */\n\t\tfollows() {\n\t\t\treturn (this.widget && typeof this.widget.follows === 'object' && this.widget.follows) || {}\n\t\t},\n\n\t\t/** @return {object|null} The row the card is hosted on. */\n\t\trow() {\n\t\t\tif (!this.cnWidgetSelection || !this.follows.widget) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn followedRow(this.cnWidgetSelection.entry(this.follows.widget), this.follows)\n\t\t},\n\n\t\t/** @return {string} The row's id. */\n\t\trowId() {\n\t\t\tconst row = this.row || {}\n\t\t\treturn String(row.id ?? row['@self']?.id ?? '')\n\t\t},\n\n\t\t/** @return {object} The widget, its title filled from the row. */\n\t\tfollowedWidget() {\n\t\t\tconst title = this.widget.title\n\t\t\tif (typeof title !== 'string' || !title.includes('{')) {\n\t\t\t\treturn this.widget\n\t\t\t}\n\t\t\tconst translated = this.cnTranslate(title)\n\t\t\treturn { ...this.widget, title: fillTemplate(translated, this.row, new Date(), { formatters: this.cnFormatters }) || translated.replace(/\\s*\\{[^}]*\\}/g, '') }\n\t\t},\n\t},\n}\n</script>\n"],"names":["CnDetailWidgetHost"],"mappings":";;;;AAsBA;AACC;;AAEA;;;AAGA;;AAEA;AACA;AACA;;;AAGD,aAAe;CACd,IAAI,EAAE,uBAAuB;;AAE7B,CAAA,UAAU,EAAE,sBAAEA,YAAoB;;AAElC,CAAA,MAAM,EAAE;EACP,iBAAiB,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,IAAG,EAAG;AAC5D;AACA,EAAA,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,KAAK,KAAK;;AAE5C,EAAA,YAAY,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG;EACrC;;AAED,CAAA,YAAY,EAAE,KAAK;;AAEnB,CAAA,KAAK,EAAE;AACN;AACA,EAAA,MAAM,EAAE;GACP,IAAI,EAAE,MAAM;GACZ,QAAQ,EAAE,IAAI;GACd;;;AAGD,EAAA,QAAQ,EAAE;AACT,GAAA,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;GACtB,OAAO,EAAE,EAAE;GACX;;;AAGD,EAAA,MAAM,EAAE;AACP,GAAA,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;GACtB,OAAO,EAAE,EAAE;GACX;EACD;;AAED,CAAA,QAAQ,EAAE;;AAET,EAAA,OAAO,GAAG;AACT,GAAA,OAAO,CAAC,IAAI,CAAC,MAAO,IAAG,OAAO,IAAI,CAAC,MAAM,CAAC,OAAM,KAAM,YAAY,IAAI,CAAC,MAAM,CAAC,OAAO,KAAK,EAAC;GAC3F;;;EAGD,GAAG,GAAG;GACL,IAAI,CAAC,IAAI,CAAC,iBAAkB,IAAG,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;AACpD,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,OAAO,WAAW,CAAC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,CAAA;GAClF;;AAED;AACA,EAAA,KAAK,GAAG;AACP,GAAA,MAAM,MAAM,IAAI,CAAC,GAAI,IAAG,GAAC;AACzB,GAAA,OAAO,MAAM,CAAC,GAAG,CAAC,EAAG,IAAG,GAAG,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;GAC9C;;;AAGD,EAAA,cAAc,GAAG;GAChB,MAAM,KAAM,GAAE,IAAI,CAAC,MAAM,CAAC,MAAI;GAC9B,IAAI,OAAO,KAAI,KAAM,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AACtD,IAAA,OAAO,IAAI,CAAC,MAAK;AAClB,IAAA;AACA,GAAA,MAAM,UAAW,GAAE,IAAI,CAAC,WAAW,CAAC,KAAK,EAAA;AACzC,GAAA,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,YAAW,EAAG,CAAA,IAAK,UAAU,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAE,EAAA;GAC7J;EACD;AACF;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnFollowingWidgetHost.vue3.js","sources":["../../../../src/components/CnFollowingWidgetHost/CnFollowingWidgetHost.vue?vue&type=template&id=c28a4078&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<CnDetailWidgetHost\n\t\tv-if=\"row\"\n\t\tv-bind=\"$attrs\"\n\t\t:widget=\"followedWidget\"\n\t\t:object=\"row\"\n\t\t:objectId=\"rowId\"\n\t\t:objectType=\"follows.schema || ''\"\n\t\t:register=\"follows.register || register\"\n\t\t:schema=\"follows.schema || schema\" />\n</template>\n\n<script>\nimport CnDetailWidgetHost from '../CnDetailWidgetHost/CnDetailWidgetHost.vue'\nimport { fillTemplate } from '../../utils/fillTemplate.js'\nimport { followedRow, WIDGET_SELECTION } from '../../utils/widgetSelection.js'\n\n/**\n * CnFollowingWidgetHost: hosts a widget on the row another list on the page\n * has selected, instead of on the page's own record (widget `follows`).\n *\n * `follows: { widget, register, schema, when? }` names the list (by widget\n * id) and the register and schema its rows belong to. The card shows the\n * selected row when `when` holds for it, else, while nothing is selected, the\n * first row for which it holds, and draws nothing without such a row. Its\n * title's `{field}` placeholders are filled from the row. Every other prop\n * passes through to CnDetailWidgetHost.\n *\n * @spec openspec/changes/side-card-follows-row/specs/side-card-follows-row/spec.md#requirement-a-card-follows-the-selected-row\n */\nexport default {\n\tname: 'CnFollowingWidgetHost',\n\n\tcomponents: { CnDetailWidgetHost },\n\n\tinject: {\n\t\tcnWidgetSelection: { from: WIDGET_SELECTION, default: null },\n\t\t/** The host's translate (CnAppRoot provides `cnTranslate`), applied to the title before it is filled. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tinheritAttrs: false,\n\n\tprops: {\n\t\t/** The widget definition, with `follows`. */\n\t\twidget: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** The page's register, used when `follows` names none. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The page's schema, used when `follows` names none. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/** @return {object} The widget's `follows` declaration. */\n\t\tfollows() {\n\t\t\treturn (this.widget && typeof this.widget.follows === 'object' && this.widget.follows) || {}\n\t\t},\n\n\t\t/** @return {object|null} The row the card is hosted on. */\n\t\trow() {\n\t\t\tif (!this.cnWidgetSelection || !this.follows.widget) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn followedRow(this.cnWidgetSelection.entry(this.follows.widget), this.follows)\n\t\t},\n\n\t\t/** @return {string} The row's id. */\n\t\trowId() {\n\t\t\tconst row = this.row || {}\n\t\t\treturn String(row.id ?? row['@self']?.id ?? '')\n\t\t},\n\n\t\t/** @return {object} The widget, its title filled from the row. */\n\t\tfollowedWidget() {\n\t\t\tconst title = this.widget.title\n\t\t\tif (typeof title !== 'string' || !title.includes('{')) {\n\t\t\t\treturn this.widget\n\t\t\t}\n\t\t\tconst translated = this.cnTranslate(title)\n\t\t\treturn { ...this.widget, title: fillTemplate(translated, this.row) || translated.replace(/\\s*\\{[^}]*\\}/g, '') }\n\t\t},\n\t},\n}\n</script>\n"],"names":["_createBlock","_mergeProps"],"mappings":";;;;;UAYQ,QAAG,CAAA,GAAA;oBADTA,2CAAAC,uBAEQ,IAAM,CAAA,MAAA,EAAA;AACb,QAAA,MAAM,EAAE;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnFollowingWidgetHost.vue3.js","sources":["../../../../src/components/CnFollowingWidgetHost/CnFollowingWidgetHost.vue?vue&type=template&id=1e55eb14&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<CnDetailWidgetHost\n\t\tv-if=\"row\"\n\t\tv-bind=\"$attrs\"\n\t\t:widget=\"followedWidget\"\n\t\t:object=\"row\"\n\t\t:objectId=\"rowId\"\n\t\t:objectType=\"follows.schema || ''\"\n\t\t:register=\"follows.register || register\"\n\t\t:schema=\"follows.schema || schema\" />\n</template>\n\n<script>\nimport CnDetailWidgetHost from '../CnDetailWidgetHost/CnDetailWidgetHost.vue'\nimport { fillTemplate } from '../../utils/fillTemplate.js'\nimport { followedRow, WIDGET_SELECTION } from '../../utils/widgetSelection.js'\n\n/**\n * CnFollowingWidgetHost: hosts a widget on the row another list on the page\n * has selected, instead of on the page's own record (widget `follows`).\n *\n * `follows: { widget, register, schema, when? }` names the list (by widget\n * id) and the register and schema its rows belong to. The card shows the\n * selected row when `when` holds for it, else, while nothing is selected, the\n * first row for which it holds, and draws nothing without such a row. Its\n * title's `{field}` placeholders are filled from the row. Every other prop\n * passes through to CnDetailWidgetHost.\n *\n * @spec openspec/changes/side-card-follows-row/specs/side-card-follows-row/spec.md#requirement-a-card-follows-the-selected-row\n */\nexport default {\n\tname: 'CnFollowingWidgetHost',\n\n\tcomponents: { CnDetailWidgetHost },\n\n\tinject: {\n\t\tcnWidgetSelection: { from: WIDGET_SELECTION, default: null },\n\t\t/** The host's translate (CnAppRoot provides `cnTranslate`), applied to the title before it is filled. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/** The app's named formatters (CnAppRoot provides `cnFormatters`), for a `{field|formatter}` token in the title. */\n\t\tcnFormatters: { default: () => ({}) },\n\t},\n\n\tinheritAttrs: false,\n\n\tprops: {\n\t\t/** The widget definition, with `follows`. */\n\t\twidget: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** The page's register, used when `follows` names none. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The page's schema, used when `follows` names none. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/** @return {object} The widget's `follows` declaration. */\n\t\tfollows() {\n\t\t\treturn (this.widget && typeof this.widget.follows === 'object' && this.widget.follows) || {}\n\t\t},\n\n\t\t/** @return {object|null} The row the card is hosted on. */\n\t\trow() {\n\t\t\tif (!this.cnWidgetSelection || !this.follows.widget) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn followedRow(this.cnWidgetSelection.entry(this.follows.widget), this.follows)\n\t\t},\n\n\t\t/** @return {string} The row's id. */\n\t\trowId() {\n\t\t\tconst row = this.row || {}\n\t\t\treturn String(row.id ?? row['@self']?.id ?? '')\n\t\t},\n\n\t\t/** @return {object} The widget, its title filled from the row. */\n\t\tfollowedWidget() {\n\t\t\tconst title = this.widget.title\n\t\t\tif (typeof title !== 'string' || !title.includes('{')) {\n\t\t\t\treturn this.widget\n\t\t\t}\n\t\t\tconst translated = this.cnTranslate(title)\n\t\t\treturn { ...this.widget, title: fillTemplate(translated, this.row, new Date(), { formatters: this.cnFormatters }) || translated.replace(/\\s*\\{[^}]*\\}/g, '') }\n\t\t},\n\t},\n}\n</script>\n"],"names":["_createBlock","_mergeProps"],"mappings":";;;;;UAYQ,QAAG,CAAA,GAAA;oBADTA,2CAAAC,uBAEQ,IAAM,CAAA,MAAA,EAAA;AACb,QAAA,MAAM,EAAE;;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnNotesCard.vue2.js';
2
2
  import { render } from './CnNotesCard.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-25437d6c";
5
+ script.__scopeId = "data-v-23928d29";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -11,6 +11,8 @@ import __sfc_main$2 from '../CnNoteComposer/CnNoteComposer.vue.js';
11
11
  import __sfc_main$3 from '../CnUserActionMenu/CnUserActionMenu.vue.js';
12
12
  import __sfc_main$4 from '../CnVisibilityChip/CnVisibilityChip.vue.js';
13
13
  import { useFileComments } from '../../composables/useFileComments.js';
14
+ import { normalizeLook } from '../../composables/useLook.js';
15
+ import { formatBoardDateTime } from '../../utils/fillTemplate.js';
14
16
  import { initialsOf } from '../../utils/initials.js';
15
17
  import { leafSourceUrl } from '../../utils/leafSourceUrl.js';
16
18
  import { extractMentionedIds, extractMentionedGroupIds } from '../../utils/mentions.js';
@@ -83,6 +85,10 @@ var script = {
83
85
  Delete,
84
86
  },
85
87
 
88
+ inject: {
89
+ cnLook: { default: 'nextcloud' },
90
+ },
91
+
86
92
  props: {
87
93
  /** OpenRegister register ID */
88
94
  registerId: {
@@ -630,6 +636,10 @@ var script = {
630
636
  if (!dateStr) {
631
637
  return ''
632
638
  }
639
+ // The board look writes a note's moment as the boards do: "6 okt 11:10", the year only outside this year.
640
+ if (normalizeLook(this.cnLook) === 'board') {
641
+ return formatBoardDateTime(dateStr) || dateStr
642
+ }
633
643
  try {
634
644
  return new Date(dateStr).toLocaleString(userLocale(), {
635
645
  year: 'numeric',
@@ -1 +1 @@
1
- {"version":3,"file":"CnNotesCard.vue2.js","sources":["../../../../src/components/CnNotesCard/CnNotesCard.vue"],"sourcesContent":["<!--\n CnNotesCard — Inline notes card for detail pages.\n\n Displays up to 5 recent notes with author, content, and timestamp.\n Includes an add-note form and integrates CnUserActionMenu on author names.\n Wraps CnDetailCard for consistent styling.\n-->\n<template>\n\t<CnDetailCard :title=\"titleLabel\"\n\t\t:icon=\"CommentTextOutline\"\n\t\t:collapsible=\"collapsible\"\n\t\t:chromeless=\"chromeless\">\n\t\t<template v-if=\"composerBehindButton\" #actions>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\t:aria-expanded=\"composerShown ? 'true' : 'false'\"\n\t\t\t\tdata-testid=\"cn-notes-card-open-composer\"\n\t\t\t\t@click=\"composerOpen = !composerOpen\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NotePlusOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ addNoteLabel }}\n\t\t\t</NcButton>\n\t\t</template>\n\t\t<div class=\"cn-notes-card\">\n\t\t\t<!-- Add note input -->\n\t\t\t<div v-if=\"!unavailable && composerShown\" class=\"cn-notes-card__add-form\">\n\t\t\t\t<!-- Replying: the form says to whom; a reply to a reply goes to the top of its thread. -->\n\t\t\t\t<p\n\t\t\t\t\tv-if=\"replyTo\"\n\t\t\t\t\tclass=\"cn-notes-card__replying\"\n\t\t\t\t\trole=\"status\"\n\t\t\t\t\tdata-testid=\"cn-notes-card-replying\">\n\t\t\t\t\t{{ replyingLabel }}\n\t\t\t\t\t<NcButton variant=\"tertiary\" @click=\"replyTo = null\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</p>\n\t\t\t\t<CnNoteComposer\n\t\t\t\t\tclass=\"cn-notes-card__textarea\"\n\t\t\t\t\t:modelValue=\"newNoteText\"\n\t\t\t\t\t:placeholder=\"addNotePlaceholder\"\n\t\t\t\t\t:register=\"isFileSource || isSourceMode ? '' : registerId\"\n\t\t\t\t\t:schema=\"isFileSource || isSourceMode ? '' : schemaId\"\n\t\t\t\t\t:objectId=\"isFileSource || isSourceMode ? '' : objectId\"\n\t\t\t\t\t:apiBase=\"apiBase\"\n\t\t\t\t\t@update:modelValue=\"newNoteText = $event\"\n\t\t\t\t\t@submit=\"submitNote\" />\n\t\t\t\t<!-- Internal or public, chosen before the note is written (only when the host says the caller may set it). -->\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-if=\"visibilityToggleShown\"\n\t\t\t\t\t:modelValue=\"newNoteVisibility === 'public'\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\tdata-testid=\"cn-notes-card-visibility-switch\"\n\t\t\t\t\t@update:modelValue=\"(on) => newNoteVisibility = on ? 'public' : 'internal'\">\n\t\t\t\t\t{{ publicSwitchLabel }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t:disabled=\"!newNoteText.trim() || noteSaving\"\n\t\t\t\t\t@click=\"submitNote\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<Send :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ addNoteLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\n\t\t\t<!-- Loading state -->\n\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t\t<!-- Empty state -->\n\t\t\t<div v-else-if=\"unavailable\" class=\"cn-notes-card__empty\" data-testid=\"cn-notes-card-unavailable\">\n\t\t\t\t{{ unavailableLabel }}\n\t\t\t</div>\n\t\t\t<p v-else-if=\"failed\"\n\t\t\t\tclass=\"cn-notes-card__empty\"\n\t\t\t\trole=\"alert\"\n\t\t\t\tdata-testid=\"cn-notes-card-failed\">\n\t\t\t\t{{ failedLabel }}\n\t\t\t</p>\n\t\t\t<div v-else-if=\"allNotes.length === 0\" class=\"cn-notes-card__empty\">\n\t\t\t\t{{ noNotesLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Notes list (last 5), threaded: a note, then its replies. -->\n\t\t\t<div v-else class=\"cn-notes-card__list\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"thread in threads\"\n\t\t\t\t\t:key=\"thread.note.id\"\n\t\t\t\t\tclass=\"cn-notes-card__thread\"\n\t\t\t\t\trole=\"group\"\n\t\t\t\t\t:aria-label=\"threadLabel(thread.note)\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tv-for=\"row in threadRows(thread)\"\n\t\t\t\t\t\t:key=\"row.note.id\"\n\t\t\t\t\t\tclass=\"cn-notes-card__note\"\n\t\t\t\t\t\t:class=\"{ 'cn-notes-card__note--reply': row.isReply }\">\n\t\t\t\t\t\t<span class=\"cn-notes-card__initials\" aria-hidden=\"true\">{{ initialsOf(getNoteAuthorName(row.note)) }}</span>\n\t\t\t\t\t\t<div class=\"cn-notes-card__note-header\">\n\t\t\t\t\t\t\t<CnUserActionMenu\n\t\t\t\t\t\t\t\tv-if=\"!isCurrentUser(row.note)\"\n\t\t\t\t\t\t\t\t:userId=\"getNoteAuthorId(row.note)\"\n\t\t\t\t\t\t\t\t:displayName=\"getNoteAuthorName(row.note)\">\n\t\t\t\t\t\t\t\t<strong class=\"cn-notes-card__author\">{{ getNoteAuthorName(row.note) }}</strong>\n\t\t\t\t\t\t\t</CnUserActionMenu>\n\t\t\t\t\t\t\t<strong v-else class=\"cn-notes-card__author cn-notes-card__author--self\">\n\t\t\t\t\t\t\t\t{{ getNoteAuthorName(row.note) }}\n\t\t\t\t\t\t\t</strong>\n\t\t\t\t\t\t\t<span class=\"cn-notes-card__time\">{{ formatDate(row.note.creationDateTime || row.note.created || row.note.createdAt) }}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div v-if=\"visibilityShown\" class=\"cn-notes-card__visibility\">\n\t\t\t\t\t\t\t<CnVisibilityChip :visibility=\"row.note.visibility\" />\n\t\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\t\tv-if=\"visibilityToggleShown\"\n\t\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-notes-card-visibility-toggle\"\n\t\t\t\t\t\t\t\t@click=\"toggleVisibility(row.note)\">\n\t\t\t\t\t\t\t\t{{ noteVisibility(row.note) === 'public' ? makeInternalLabel : makePublicLabel }}\n\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<p v-if=\"row.quote\" class=\"cn-notes-card__quote\" data-testid=\"cn-notes-card-quote\">\n\t\t\t\t\t\t\t{{ row.quote }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p class=\"cn-notes-card__body\">\n\t\t\t\t\t\t\t<CnNoteBody :message=\"row.note.message || row.note.content || ''\" :record=\"isFileSource || isSourceMode ? null : record\" />\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tv-if=\"canReply\"\n\t\t\t\t\t\t\tclass=\"cn-notes-card__reply-btn\"\n\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t:aria-label=\"replyAria(row.note)\"\n\t\t\t\t\t\t\tdata-testid=\"cn-notes-card-reply\"\n\t\t\t\t\t\t\t@click=\"replyTo = row.note\">\n\t\t\t\t\t\t\t{{ replyLabel }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tv-if=\"canDeleteNote(row.note)\"\n\t\t\t\t\t\t\tvariant=\"tertiary-no-background\"\n\t\t\t\t\t\t\tclass=\"cn-notes-card__delete-btn\"\n\t\t\t\t\t\t\t:aria-label=\"deleteLabel\"\n\t\t\t\t\t\t\t@click=\"confirmDelete(row.note)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Delete :size=\"16\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Footer: \"Show all\" link -->\n\t\t<template v-if=\"allNotes.length > maxDisplay\" #footer>\n\t\t\t<button\n\t\t\t\tclass=\"cn-notes-card__show-all\"\n\t\t\t\t@click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ allNotes.length }})\n\t\t\t</button>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport CommentTextOutline from 'vue-material-design-icons/CommentTextOutline.vue'\nimport Delete from 'vue-material-design-icons/Delete.vue'\nimport NotePlusOutline from 'vue-material-design-icons/NotePlusOutline.vue'\nimport Send from 'vue-material-design-icons/Send.vue'\nimport CnDetailCard from '../CnDetailCard/CnDetailCard.vue'\nimport CnNoteBody from '../CnNoteBody/CnNoteBody.vue'\nimport CnNoteComposer from '../CnNoteComposer/CnNoteComposer.vue'\nimport CnUserActionMenu from '../CnUserActionMenu/CnUserActionMenu.vue'\nimport CnVisibilityChip from '../CnVisibilityChip/CnVisibilityChip.vue'\nimport { useFileComments } from '../../composables/useFileComments.js'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { initialsOf } from '../../utils/initials.js'\nimport { leafSourceUrl } from '../../utils/leafSourceUrl.js'\nimport { extractMentionedGroupIds, extractMentionedIds } from '../../utils/mentions.js'\nimport { quoteFor, supportsReplies, threadNotes, threadRootId } from '../../utils/noteThreads.js'\nimport { serverErrorMessage } from '../../utils/serverErrorMessage.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnNotesCard — Inline notes widget for detail pages.\n *\n * Shows up to 5 recent notes with add/delete functionality.\n * Integrates CnUserActionMenu on author names for quick communication.\n *\n * Basic usage\n * ```vue\n * <CnNotesCard\n * register-id=\"uuid-register\"\n * schema-id=\"uuid-schema\"\n * object-id=\"uuid-object\" />\n * ```\n *\n * With sidebar sync\n * ```vue\n * <CnNotesCard\n * register-id=\"reg\"\n * schema-id=\"schema\"\n * object-id=\"obj\"\n * @note-added=\"refreshNotes\"\n * @note-deleted=\"refreshNotes\"\n * @show-all=\"openSidebarNotesTab\" />\n * ```\n *\n * @event note-added Emitted after a new note is successfully persisted. Payload: the created note object.\n * @event note-deleted Emitted after a note is successfully deleted. Payload: the deleted note ID.\n * @event visibility-changed Emitted after a note's visibility was changed. Payload: `{ id, visibility }`.\n * @event mention Emitted after a note that mentions someone was added. Payload: `{ objectId, register, schema, noteId, mentionedUserIds }`, plus `mentionedGroupIds` when a group (`@\"group/<gid>\"`) is mentioned. nc-vue notifies nobody; the listener does.\n * @event show-all Emitted when the user clicks the \"Show all\" button — parents typically open a full notes sidebar tab.\n */\n/**\n * The server's own sentence on a refused note write, '' when it gave none.\n *\n * @param {Response} response The refused response.\n * @return {Promise<string>} The message.\n */\nasync function refusalMessage(response) {\n\ttry {\n\t\tconst data = await response.json()\n\t\tconst message = serverErrorMessage({ message: '-', response: { data } })\n\t\treturn message === '-' ? '' : message\n\t} catch {\n\t\treturn ''\n\t}\n}\n\nexport default {\n\tname: 'CnNotesCard',\n\n\tcomponents: {\n\t\tCnDetailCard,\n\t\tCnNoteBody,\n\t\tCnNoteComposer,\n\t\tCnUserActionMenu,\n\t\tCnVisibilityChip,\n\t\tNcButton,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcLoadingIcon,\n\t\tNotePlusOutline,\n\t\tSend,\n\t\tDelete,\n\t},\n\n\tprops: {\n\t\t/** OpenRegister register ID */\n\t\tregisterId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema ID */\n\t\tschemaId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Object UUID */\n\t\tobjectId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Show the notes of a plain Nextcloud file instead of an OpenRegister\n\t\t * object: the file's id. Used when no `objectId` is given; the notes\n\t\t * are the file's comments (`/remote.php/dav/comments/files/{fileId}`),\n\t\t * the same ones the Files sidebar shows.\n\t\t */\n\t\tfileId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Base API URL for OpenRegister */\n\t\tapiBase: {\n\t\t\ttype: String,\n\t\t\tdefault: '/apps/openregister/api',\n\t\t},\n\n\t\t/**\n\t\t * Read the notes of something that is not an OpenRegister object:\n\t\t * `{ kind: 'flow-task', id }` reads `${apiBase}/flow-tasks/{id}/notes`.\n\t\t * `id` may be left out or written `@objectId` for `objectId`.\n\t\t *\n\t\t * @type {{kind: string, id?: (string|number)}|null}\n\t\t */\n\t\tsource: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * A notes URL that replaces the object's own: the list and the add go\n\t\t * to it, a delete to `{endpoint}/{noteId}`. `{id}` is filled with the\n\t\t * source's id or `objectId`.\n\t\t */\n\t\tendpoint: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Maximum number of notes to display */\n\t\tmaxDisplay: {\n\t\t\ttype: Number,\n\t\t\tdefault: 5,\n\t\t},\n\n\t\t/** Whether the card is collapsible */\n\t\tcollapsible: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Render the notes body without the surrounding CnDetailCard.\n\t\t *\n\t\t * Set when a surface already supplies the card and the title, such as a\n\t\t * tab panel whose open tab names the panel. Leaving the card on there\n\t\t * nests a card inside a card and shows the label twice.\n\t\t */\n\t\tchromeless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Where the add form lives: `inline` (default) keeps it open above the\n\t\t * notes; `button` puts it behind an \"Add note\" button in the card's\n\t\t * header (DqTaak \"Notities\"), and closes it again once the note is\n\t\t * saved. A reply opens it either way. Without the card's header\n\t\t * (`chromeless`) the form stays inline.\n\t\t *\n\t\t * @spec openspec/changes/notes-card-composer-button/specs/notes-card-composer-button/spec.md#requirement-a-notes-card-can-keep-its-composer-behind-a-header-button\n\t\t */\n\t\tcomposer: {\n\t\t\ttype: String,\n\t\t\tdefault: 'inline',\n\t\t\tvalidator: (v) => ['inline', 'button'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Show a chip on every note reading internal or public. A note without a\n\t\t * value reads as internal. Off by default, so a host that passes nothing\n\t\t * renders the card as before. Not used for a file source (file comments\n\t\t * carry no flag).\n\t\t */\n\t\tshowVisibility: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Whether the caller may set visibility on this object: the answer the\n\t\t * server gives for `update` on it. The host passes it; the card never\n\t\t * infers it from the current user. When true the card offers the\n\t\t * public switch in the add-note form and a toggle on each note.\n\t\t */\n\t\tcanSetVisibility: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t// --- Pre-translated labels ---\n\t\t/** Card header title. */\n\t\ttitleLabel: { type: String, default: () => t('nextcloud-vue', 'Notes') },\n\t\t/** Label for the submit button that creates a new note. */\n\t\taddNoteLabel: { type: String, default: () => t('nextcloud-vue', 'Add note') },\n\t\t/** Placeholder shown inside the new-note textarea before any input. */\n\t\taddNotePlaceholder: { type: String, default: () => t('nextcloud-vue', 'Write a note…') },\n\t\t/** Empty-state text shown when the object has zero notes. */\n\t\tnoNotesLabel: { type: String, default: () => t('nextcloud-vue', 'No notes yet') },\n\t\t/** Label for the \"Show all\" button rendered when the note list is truncated. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Text shown instead of the notes when Nextcloud refuses a file's comments (no access). */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'Notes are not available for this file') },\n\t\t/** Label of the add-note switch that makes the note public. */\n\t\tpublicSwitchLabel: { type: String, default: () => t('nextcloud-vue', 'Public (visible to the customer)') },\n\t\t/** Label of the per-note action that makes an internal note public. */\n\t\tmakePublicLabel: { type: String, default: () => t('nextcloud-vue', 'Make public') },\n\t\t/** Label of the per-note action that makes a public note internal. */\n\t\tmakeInternalLabel: { type: String, default: () => t('nextcloud-vue', 'Make internal') },\n\t\t/** Label of the Reply button (shown only when the backend supports replies). */\n\t\treplyLabel: { type: String, default: () => t('nextcloud-vue', 'Reply') },\n\t\t/** Aria label for the per-note delete icon button. */\n\t\tdeleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete note') },\n\t\t/** Text shown when a `source` or `endpoint` read failed. */\n\t\tfailedLabel: { type: String, default: () => t('nextcloud-vue', 'The notes could not be loaded') },\n\t},\n\n\temits: ['mention', 'note-added', 'note-deleted', 'show-all', 'visibility-changed'],\n\n\tdata() {\n\t\treturn {\n\t\t\tCommentTextOutline: markRaw(CommentTextOutline),\n\t\t\tallNotes: [],\n\t\t\tloading: false,\n\t\t\tnewNoteText: '',\n\t\t\t/** Whether the add form is open while `composer` is `button`. */\n\t\t\tcomposerOpen: false,\n\t\t\t/** Visibility the next note is written with (only sent when the caller may set it). */\n\t\t\tnewNoteVisibility: 'internal',\n\t\t\tnoteSaving: false,\n\t\t\tdeleteConfirmId: null,\n\t\t\t/** The file source answered 403 or 404: no notes and no add field. */\n\t\t\tunavailable: false,\n\t\t\t/** A `source` or `endpoint` read failed: said, rather than shown as no notes. */\n\t\t\tfailed: false,\n\t\t\t/** The note a reply is being written to, or null. */\n\t\t\treplyTo: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {boolean} Whether the add form sits behind the header's button. */\n\t\tcomposerBehindButton() {\n\t\t\treturn this.composer === 'button' && !this.chromeless && !this.unavailable\n\t\t},\n\n\t\t/** @return {boolean} Whether the add form shows. */\n\t\tcomposerShown() {\n\t\t\treturn !this.composerBehindButton || this.composerOpen || Boolean(this.replyTo)\n\t\t},\n\n\t\t/** Chips are shown: the host asked for them (or lets the caller set visibility) and the notes are object notes. */\n\t\tvisibilityShown() {\n\t\t\treturn (this.showVisibility || this.canSetVisibility) && !this.isFileSource\n\t\t},\n\n\t\t/** The switch and the per-note toggle are offered: only when the host says the caller may set it. */\n\t\tvisibilityToggleShown() {\n\t\t\treturn this.canSetVisibility && !this.isFileSource\n\t\t},\n\n\t\t/**\n\t\t * The notes URL a `source` or `endpoint` names, '' while its record id\n\t\t * is unknown, or null when the card reads an object.\n\t\t *\n\t\t * @return {string|null}\n\t\t * @spec openspec/changes/leaf-cards-read-a-source/specs/leaf-cards-read-a-source/spec.md#requirement-a-leaf-card-reads-a-source-or-an-endpoint\n\t\t */\n\t\tsourceUrl() {\n\t\t\treturn leafSourceUrl({ source: this.source, endpoint: this.endpoint, apiBase: this.apiBase, objectId: this.objectId }, 'notes')\n\t\t},\n\n\t\t/** The notes come from a `source` or `endpoint`, not from an object. */\n\t\tisSourceMode() {\n\t\t\treturn this.sourceUrl !== null\n\t\t},\n\n\t\t/** @return {string} The notes URL the list, add and delete use. */\n\t\tnotesUrl() {\n\t\t\tif (this.isSourceMode) {\n\t\t\t\treturn this.sourceUrl\n\t\t\t}\n\t\t\treturn `${this.apiBase}/objects/${this.registerId}/${this.schemaId}/${this.objectId}/notes`\n\t\t},\n\n\t\t/** The file source applies: a file id and no object. */\n\t\tisFileSource() {\n\t\t\treturn this.fileId !== null && this.fileId !== undefined && this.fileId !== '' && !this.objectId\n\t\t},\n\n\t\t/** @return {{apiBase: string, register: string, schema: string, objectId: string}} Where this record's files live, for pasted images. */\n\t\trecord() {\n\t\t\treturn { apiBase: this.apiBase, register: this.registerId, schema: this.schemaId, objectId: this.objectId }\n\t\t},\n\n\t\t/** @return {boolean} Whether the backend supports replies: its notes carry a `parentId` key. Not for a file's comments. */\n\t\tcanReply() {\n\t\t\treturn !this.isFileSource && supportsReplies(this.allNotes)\n\t\t},\n\n\t\t/** @return {Array<{note: object, replies: object[]}>} The shown notes as threads. */\n\t\tthreads() {\n\t\t\treturn threadNotes(this.displayedNotes)\n\t\t},\n\n\t\t/** @return {string} The line above the form while replying. */\n\t\treplyingLabel() {\n\t\t\treturn this.replyTo ? t('nextcloud-vue', 'Replying to {author}', { author: this.getNoteAuthorName(this.replyTo) }) : ''\n\t\t},\n\n\t\tdisplayedNotes() {\n\t\t\t// Reverse chronological, limited to maxDisplay\n\t\t\tconst sorted = [...this.allNotes].sort((a, b) => {\n\t\t\t\tconst dateA = new Date(a.creationDateTime || a.created || a.createdAt || 0)\n\t\t\t\tconst dateB = new Date(b.creationDateTime || b.created || b.createdAt || 0)\n\t\t\t\treturn dateB - dateA\n\t\t\t})\n\t\t\treturn sorted.slice(0, this.maxDisplay)\n\t\t},\n\t},\n\n\twatch: {\n\t\tfileId: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tif (this.isFileSource) {\n\t\t\t\t\tthis.fetchNotes()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(newId) {\n\t\t\t\tif (newId && this.registerId && this.schemaId && !this.isSourceMode) {\n\t\t\t\t\tthis.fetchNotes()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\n\t\tsourceUrl: {\n\t\t\timmediate: true,\n\t\t\thandler(url) {\n\t\t\t\tif (typeof url === 'string' && url !== '') {\n\t\t\t\t\tthis.fetchNotes()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A note's visibility; an entry with no value reads as internal.\n\t\t *\n\t\t * @param {object} note The note.\n\t\t * @return {'public'|'internal'} The visibility.\n\t\t */\n\t\tnoteVisibility(note) {\n\t\t\treturn note && note.visibility === 'public' ? 'public' : 'internal'\n\t\t},\n\n\t\t/**\n\t\t * The one place a note write goes out: creating a note and changing its\n\t\t * visibility both send through here, so the value is sent from one spot.\n\t\t *\n\t\t * @param {'POST'|'PATCH'} method The HTTP method.\n\t\t * @param {string} url The notes URL (with the note id for a change).\n\t\t * @param {object} body The JSON body.\n\t\t * @return {Promise<Response>} The response.\n\t\t */\n\t\twriteNote(method, url, body) {\n\t\t\treturn fetch(prefixUrl(url), { method, headers: buildHeaders(), body: JSON.stringify(body) })\n\t\t},\n\n\t\t/**\n\t\t * Flip a note between internal and public.\n\t\t *\n\t\t * @param {object} note The note.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync toggleVisibility(note) {\n\t\t\tif (!this.visibilityToggleShown) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst next = this.noteVisibility(note) === 'public' ? 'internal' : 'public'\n\t\t\ttry {\n\t\t\t\tconst url = `${this.notesUrl}/${note.id}`\n\t\t\t\tconst response = await this.writeNote('PATCH', url, { visibility: next })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.allNotes = this.allNotes.map((n) => (n.id === note.id ? { ...n, visibility: next } : n))\n\t\t\t\t\t/** @event visibility-changed Emitted after a note's visibility was changed. Payload: `{ id, visibility }`. */\n\t\t\t\t\tthis.$emit('visibility-changed', { id: note.id, visibility: next })\n\t\t\t\t} else {\n\t\t\t\t\tthis.showError('Failed to change visibility')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\tconsole.error('CnNotesCard: Failed to change visibility', err)\n\t\t\t\tthis.showError('Failed to change visibility')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The rows of one thread: the note, then its replies. A reply whose parent\n\t\t * is far up the list carries a one-line quote of it.\n\t\t *\n\t\t * @param {{note: object, replies: object[]}} thread The thread.\n\t\t * @return {Array<{note: object, isReply: boolean, quote: string}>} The rows.\n\t\t */\n\t\tthreadRows(thread) {\n\t\t\tconst flat = this.threads.flatMap((t2) => [t2.note, ...t2.replies])\n\t\t\treturn [\n\t\t\t\t{ note: thread.note, isReply: false, quote: '' },\n\t\t\t\t...thread.replies.map((reply) => ({ note: reply, isReply: true, quote: quoteFor(reply, flat) })),\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * @param {object} note A top-level note.\n\t\t * @return {string} The name of its thread for assistive technology.\n\t\t */\n\t\tthreadLabel(note) {\n\t\t\treturn t('nextcloud-vue', 'Note by {author} and its replies', { author: this.getNoteAuthorName(note) })\n\t\t},\n\n\t\t/**\n\t\t * @param {object} note The note a Reply button answers.\n\t\t * @return {string} The button's accessible name, naming the author.\n\t\t */\n\t\treplyAria(note) {\n\t\t\treturn t('nextcloud-vue', 'Reply to {author}', { author: this.getNoteAuthorName(note) })\n\t\t},\n\n\t\t/**\n\t\t * Tell the host a saved note mentions someone. The payload keeps the\n\t\t * user ids and adds the group ids only when a group is mentioned.\n\t\t *\n\t\t * @param {string} text The saved text.\n\t\t * @param {string|null} noteId The note's id.\n\t\t * @return {void}\n\t\t */\n\t\temitMentionEvent(text, noteId) {\n\t\t\tconst mentionedUserIds = extractMentionedIds(text)\n\t\t\tconst mentionedGroupIds = extractMentionedGroupIds(text)\n\t\t\tif (mentionedUserIds.length === 0 && mentionedGroupIds.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('mention', {\n\t\t\t\tobjectId: this.objectId,\n\t\t\t\tregister: this.registerId,\n\t\t\t\tschema: this.schemaId,\n\t\t\t\tnoteId,\n\t\t\t\tmentionedUserIds,\n\t\t\t\t...(mentionedGroupIds.length > 0 ? { mentionedGroupIds } : {}),\n\t\t\t})\n\t\t},\n\n\t\tgetNoteAuthorId(note) {\n\t\t\treturn note.actorId || note.author || ''\n\t\t},\n\n\t\tinitialsOf,\n\n\t\tgetNoteAuthorName(note) {\n\t\t\treturn note.actorDisplayName || note.author || 'Unknown'\n\t\t},\n\n\t\tisCurrentUser(note) {\n\t\t\tif (note.isCurrentUser === true) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst authorId = this.getNoteAuthorId(note)\n\t\t\tconst currentUser = typeof OC !== 'undefined' ? OC?.currentUser : null\n\t\t\treturn authorId === currentUser\n\t\t},\n\n\t\tcanDeleteNote(note) {\n\t\t\treturn this.isCurrentUser(note)\n\t\t},\n\n\t\tfileComments() {\n\t\t\treturn useFileComments(this.fileId)\n\t\t},\n\n\t\tasync fetchNotes() {\n\t\t\tif (this.isFileSource) {\n\t\t\t\tthis.loading = true\n\t\t\t\tthis.unavailable = false\n\t\t\t\ttry {\n\t\t\t\t\tthis.allNotes = await this.fileComments().list()\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.allNotes = []\n\t\t\t\t\tthis.unavailable = err && (err.status === 403 || err.status === 404)\n\t\t\t\t\tif (!this.unavailable) {\n\t\t\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\t\t\tconsole.error('CnNotesCard: Failed to fetch file comments', err)\n\t\t\t\t\t}\n\t\t\t\t} finally {\n\t\t\t\t\tthis.loading = false\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.isSourceMode ? this.notesUrl === '' : (!this.registerId || !this.schemaId || !this.objectId)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.failed = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.notesUrl), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.allNotes = data.results || data || []\n\t\t\t\t} else if (this.isSourceMode) {\n\t\t\t\t\tthis.allNotes = []\n\t\t\t\t\tthis.failed = true\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tthis.failed = this.isSourceMode\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\tconsole.error('CnNotesCard: Failed to fetch notes', err)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync submitNote() {\n\t\t\tif (!this.newNoteText.trim() || this.noteSaving) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.noteSaving = true\n\t\t\tif (this.isFileSource) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.fileComments().add(this.newNoteText.trim())\n\t\t\t\t\tthis.composerOpen = false\n\t\t\t\t\tthis.newNoteText = ''\n\t\t\t\t\tawait this.fetchNotes()\n\t\t\t\t\tthis.$emit('note-added')\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\t\tconsole.error('CnNotesCard: Failed to add file comment', err)\n\t\t\t\t\tthis.showError('Failed to add note')\n\t\t\t\t} finally {\n\t\t\t\t\tthis.noteSaving = false\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst url = this.notesUrl\n\t\t\t\tconst body = { message: this.newNoteText.trim() }\n\t\t\t\tif (this.visibilityToggleShown) {\n\t\t\t\t\tbody.visibility = this.newNoteVisibility\n\t\t\t\t}\n\t\t\t\tif (this.replyTo) {\n\t\t\t\t\tbody.parentId = threadRootId(this.replyTo, this.allNotes)\n\t\t\t\t}\n\t\t\t\tconst response = await this.writeNote('POST', url, body)\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tlet noteId = null\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst created = await response.json()\n\t\t\t\t\t\tnoteId = (created && (created.id || (created.results && created.results.id))) || null\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Body not JSON: the event still fires with noteId null.\n\t\t\t\t\t}\n\t\t\t\t\tthis.emitMentionEvent(body.message, noteId)\n\t\t\t\t\tthis.replyTo = null\n\t\t\t\t\tthis.composerOpen = false\n\t\t\t\t\tthis.newNoteText = ''\n\t\t\t\t\tthis.newNoteVisibility = 'internal'\n\t\t\t\t\tawait this.fetchNotes()\n\t\t\t\t\tthis.$emit('note-added')\n\t\t\t\t} else {\n\t\t\t\t\tthis.showError(await refusalMessage(response) || 'Failed to add note')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\tconsole.error('CnNotesCard: Failed to add note', err)\n\t\t\t\tthis.showError('Failed to add note')\n\t\t\t} finally {\n\t\t\t\tthis.noteSaving = false\n\t\t\t}\n\t\t},\n\n\t\tasync confirmDelete(note) {\n\t\t\t// Simple inline confirmation — delete directly\n\t\t\tif (this.isFileSource) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.fileComments().remove(note.id)\n\t\t\t\t\tthis.allNotes = this.allNotes.filter((n) => n.id !== note.id)\n\t\t\t\t\tthis.$emit('note-deleted')\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\t\tconsole.error('CnNotesCard: Failed to delete file comment', err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst url = `${this.notesUrl}/${note.id}`\n\t\t\t\tconst response = await fetch(prefixUrl(url), {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.allNotes = this.allNotes.filter((n) => n.id !== note.id)\n\t\t\t\t\tthis.$emit('note-deleted')\n\t\t\t\t} else {\n\t\t\t\t\tthis.showError(await refusalMessage(response) || 'Failed to delete note')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\tconsole.error('CnNotesCard: Failed to delete note', err)\n\t\t\t}\n\t\t},\n\n\t\tformatDate(dateStr) {\n\t\t\tif (!dateStr) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(dateStr).toLocaleString(userLocale(), {\n\t\t\t\t\tyear: 'numeric',\n\t\t\t\t\tmonth: 'short',\n\t\t\t\t\tday: 'numeric',\n\t\t\t\t\thour: '2-digit',\n\t\t\t\t\tminute: '2-digit',\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\treturn dateStr\n\t\t\t}\n\t\t},\n\n\t\tshowError(message) {\n\t\t\ttry {\n\t\t\t\timport('@nextcloud/dialogs').then(({ showError }) => {\n\t\t\t\t\tshowError(message)\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\tconsole.error(message)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-notes-card__add-form {\n\tmargin-bottom: 12px;\n}\n\n.cn-notes-card__textarea {\n\twidth: 100%;\n\tmargin-bottom: 8px;\n\tbox-sizing: border-box;\n}\n\n.cn-notes-card__replying {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tmargin: 0 0 4px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-notes-card__note--reply {\n\tmargin-inline-start: 24px;\n}\n\n.cn-notes-card__quote {\n\tmargin: 0 0 2px;\n\tpadding-inline-start: 8px;\n\tborder-inline-start: 2px solid var(--color-border-dark);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-notes-card__empty {\n\ttext-align: center;\n\tpadding: 16px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-notes-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-notes-card__note {\n\tpadding: 10px 0;\n\tborder-bottom: 1px solid var(--color-border);\n\tposition: relative;\n}\n\n.cn-notes-card__note:last-child {\n\tborder-bottom: none;\n}\n\n.cn-notes-card__initials {\n\tdisplay: none;\n}\n\n.cn-notes-card__note-header {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: baseline;\n\tmargin-bottom: 4px;\n}\n\n.cn-notes-card__author {\n\tfont-size: 13px;\n}\n\n.cn-notes-card__author--self {\n\tcolor: var(--color-main-text);\n}\n\n.cn-notes-card__visibility {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-notes-card__time {\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n\tmargin-left: 8px;\n}\n\n.cn-notes-card__body {\n\tfont-size: 13px;\n\tmargin: 0;\n\twhite-space: pre-wrap;\n\toverflow-wrap: anywhere;\n\tpadding-right: 32px;\n}\n\n/* The note's own class leads so the position outranks NcButton's\n `position: relative`, which loads later at the same weight and put the\n button in the flow under the note. */\n.cn-notes-card__note > .cn-notes-card__delete-btn {\n\tposition: absolute;\n\ttop: 8px;\n\tright: -4px;\n\topacity: 0;\n\ttransition: opacity 0.15s ease;\n}\n\n.cn-notes-card__note:hover > .cn-notes-card__delete-btn,\n.cn-notes-card__note:focus-within > .cn-notes-card__delete-btn {\n\topacity: 1;\n}\n\n.cn-notes-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\tpadding: 0;\n\twidth: 100%;\n\ttext-align: center;\n}\n\n.cn-notes-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","CnNoteBody","CnNoteComposer","CnUserActionMenu","CnVisibilityChip","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnNotesCard.vue2.js","sources":["../../../../src/components/CnNotesCard/CnNotesCard.vue"],"sourcesContent":["<!--\n CnNotesCard — Inline notes card for detail pages.\n\n Displays up to 5 recent notes with author, content, and timestamp.\n Includes an add-note form and integrates CnUserActionMenu on author names.\n Wraps CnDetailCard for consistent styling.\n-->\n<template>\n\t<CnDetailCard :title=\"titleLabel\"\n\t\t:icon=\"CommentTextOutline\"\n\t\t:collapsible=\"collapsible\"\n\t\t:chromeless=\"chromeless\">\n\t\t<template v-if=\"composerBehindButton\" #actions>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\t:aria-expanded=\"composerShown ? 'true' : 'false'\"\n\t\t\t\tdata-testid=\"cn-notes-card-open-composer\"\n\t\t\t\t@click=\"composerOpen = !composerOpen\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NotePlusOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ addNoteLabel }}\n\t\t\t</NcButton>\n\t\t</template>\n\t\t<div class=\"cn-notes-card\">\n\t\t\t<!-- Add note input -->\n\t\t\t<div v-if=\"!unavailable && composerShown\" class=\"cn-notes-card__add-form\">\n\t\t\t\t<!-- Replying: the form says to whom; a reply to a reply goes to the top of its thread. -->\n\t\t\t\t<p\n\t\t\t\t\tv-if=\"replyTo\"\n\t\t\t\t\tclass=\"cn-notes-card__replying\"\n\t\t\t\t\trole=\"status\"\n\t\t\t\t\tdata-testid=\"cn-notes-card-replying\">\n\t\t\t\t\t{{ replyingLabel }}\n\t\t\t\t\t<NcButton variant=\"tertiary\" @click=\"replyTo = null\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</p>\n\t\t\t\t<CnNoteComposer\n\t\t\t\t\tclass=\"cn-notes-card__textarea\"\n\t\t\t\t\t:modelValue=\"newNoteText\"\n\t\t\t\t\t:placeholder=\"addNotePlaceholder\"\n\t\t\t\t\t:register=\"isFileSource || isSourceMode ? '' : registerId\"\n\t\t\t\t\t:schema=\"isFileSource || isSourceMode ? '' : schemaId\"\n\t\t\t\t\t:objectId=\"isFileSource || isSourceMode ? '' : objectId\"\n\t\t\t\t\t:apiBase=\"apiBase\"\n\t\t\t\t\t@update:modelValue=\"newNoteText = $event\"\n\t\t\t\t\t@submit=\"submitNote\" />\n\t\t\t\t<!-- Internal or public, chosen before the note is written (only when the host says the caller may set it). -->\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-if=\"visibilityToggleShown\"\n\t\t\t\t\t:modelValue=\"newNoteVisibility === 'public'\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\tdata-testid=\"cn-notes-card-visibility-switch\"\n\t\t\t\t\t@update:modelValue=\"(on) => newNoteVisibility = on ? 'public' : 'internal'\">\n\t\t\t\t\t{{ publicSwitchLabel }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t:disabled=\"!newNoteText.trim() || noteSaving\"\n\t\t\t\t\t@click=\"submitNote\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<Send :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ addNoteLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\n\t\t\t<!-- Loading state -->\n\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t\t<!-- Empty state -->\n\t\t\t<div v-else-if=\"unavailable\" class=\"cn-notes-card__empty\" data-testid=\"cn-notes-card-unavailable\">\n\t\t\t\t{{ unavailableLabel }}\n\t\t\t</div>\n\t\t\t<p v-else-if=\"failed\"\n\t\t\t\tclass=\"cn-notes-card__empty\"\n\t\t\t\trole=\"alert\"\n\t\t\t\tdata-testid=\"cn-notes-card-failed\">\n\t\t\t\t{{ failedLabel }}\n\t\t\t</p>\n\t\t\t<div v-else-if=\"allNotes.length === 0\" class=\"cn-notes-card__empty\">\n\t\t\t\t{{ noNotesLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Notes list (last 5), threaded: a note, then its replies. -->\n\t\t\t<div v-else class=\"cn-notes-card__list\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"thread in threads\"\n\t\t\t\t\t:key=\"thread.note.id\"\n\t\t\t\t\tclass=\"cn-notes-card__thread\"\n\t\t\t\t\trole=\"group\"\n\t\t\t\t\t:aria-label=\"threadLabel(thread.note)\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tv-for=\"row in threadRows(thread)\"\n\t\t\t\t\t\t:key=\"row.note.id\"\n\t\t\t\t\t\tclass=\"cn-notes-card__note\"\n\t\t\t\t\t\t:class=\"{ 'cn-notes-card__note--reply': row.isReply }\">\n\t\t\t\t\t\t<span class=\"cn-notes-card__initials\" aria-hidden=\"true\">{{ initialsOf(getNoteAuthorName(row.note)) }}</span>\n\t\t\t\t\t\t<div class=\"cn-notes-card__note-header\">\n\t\t\t\t\t\t\t<CnUserActionMenu\n\t\t\t\t\t\t\t\tv-if=\"!isCurrentUser(row.note)\"\n\t\t\t\t\t\t\t\t:userId=\"getNoteAuthorId(row.note)\"\n\t\t\t\t\t\t\t\t:displayName=\"getNoteAuthorName(row.note)\">\n\t\t\t\t\t\t\t\t<strong class=\"cn-notes-card__author\">{{ getNoteAuthorName(row.note) }}</strong>\n\t\t\t\t\t\t\t</CnUserActionMenu>\n\t\t\t\t\t\t\t<strong v-else class=\"cn-notes-card__author cn-notes-card__author--self\">\n\t\t\t\t\t\t\t\t{{ getNoteAuthorName(row.note) }}\n\t\t\t\t\t\t\t</strong>\n\t\t\t\t\t\t\t<span class=\"cn-notes-card__time\">{{ formatDate(row.note.creationDateTime || row.note.created || row.note.createdAt) }}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div v-if=\"visibilityShown\" class=\"cn-notes-card__visibility\">\n\t\t\t\t\t\t\t<CnVisibilityChip :visibility=\"row.note.visibility\" />\n\t\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\t\tv-if=\"visibilityToggleShown\"\n\t\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-notes-card-visibility-toggle\"\n\t\t\t\t\t\t\t\t@click=\"toggleVisibility(row.note)\">\n\t\t\t\t\t\t\t\t{{ noteVisibility(row.note) === 'public' ? makeInternalLabel : makePublicLabel }}\n\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<p v-if=\"row.quote\" class=\"cn-notes-card__quote\" data-testid=\"cn-notes-card-quote\">\n\t\t\t\t\t\t\t{{ row.quote }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p class=\"cn-notes-card__body\">\n\t\t\t\t\t\t\t<CnNoteBody :message=\"row.note.message || row.note.content || ''\" :record=\"isFileSource || isSourceMode ? null : record\" />\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tv-if=\"canReply\"\n\t\t\t\t\t\t\tclass=\"cn-notes-card__reply-btn\"\n\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t:aria-label=\"replyAria(row.note)\"\n\t\t\t\t\t\t\tdata-testid=\"cn-notes-card-reply\"\n\t\t\t\t\t\t\t@click=\"replyTo = row.note\">\n\t\t\t\t\t\t\t{{ replyLabel }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tv-if=\"canDeleteNote(row.note)\"\n\t\t\t\t\t\t\tvariant=\"tertiary-no-background\"\n\t\t\t\t\t\t\tclass=\"cn-notes-card__delete-btn\"\n\t\t\t\t\t\t\t:aria-label=\"deleteLabel\"\n\t\t\t\t\t\t\t@click=\"confirmDelete(row.note)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Delete :size=\"16\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Footer: \"Show all\" link -->\n\t\t<template v-if=\"allNotes.length > maxDisplay\" #footer>\n\t\t\t<button\n\t\t\t\tclass=\"cn-notes-card__show-all\"\n\t\t\t\t@click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ allNotes.length }})\n\t\t\t</button>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport CommentTextOutline from 'vue-material-design-icons/CommentTextOutline.vue'\nimport Delete from 'vue-material-design-icons/Delete.vue'\nimport NotePlusOutline from 'vue-material-design-icons/NotePlusOutline.vue'\nimport Send from 'vue-material-design-icons/Send.vue'\nimport CnDetailCard from '../CnDetailCard/CnDetailCard.vue'\nimport CnNoteBody from '../CnNoteBody/CnNoteBody.vue'\nimport CnNoteComposer from '../CnNoteComposer/CnNoteComposer.vue'\nimport CnUserActionMenu from '../CnUserActionMenu/CnUserActionMenu.vue'\nimport CnVisibilityChip from '../CnVisibilityChip/CnVisibilityChip.vue'\nimport { useFileComments } from '../../composables/useFileComments.js'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport { formatBoardDateTime } from '../../utils/fillTemplate.js'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { initialsOf } from '../../utils/initials.js'\nimport { leafSourceUrl } from '../../utils/leafSourceUrl.js'\nimport { extractMentionedGroupIds, extractMentionedIds } from '../../utils/mentions.js'\nimport { quoteFor, supportsReplies, threadNotes, threadRootId } from '../../utils/noteThreads.js'\nimport { serverErrorMessage } from '../../utils/serverErrorMessage.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnNotesCard — Inline notes widget for detail pages.\n *\n * Shows up to 5 recent notes with add/delete functionality.\n * Integrates CnUserActionMenu on author names for quick communication.\n *\n * Basic usage\n * ```vue\n * <CnNotesCard\n * register-id=\"uuid-register\"\n * schema-id=\"uuid-schema\"\n * object-id=\"uuid-object\" />\n * ```\n *\n * With sidebar sync\n * ```vue\n * <CnNotesCard\n * register-id=\"reg\"\n * schema-id=\"schema\"\n * object-id=\"obj\"\n * @note-added=\"refreshNotes\"\n * @note-deleted=\"refreshNotes\"\n * @show-all=\"openSidebarNotesTab\" />\n * ```\n *\n * @event note-added Emitted after a new note is successfully persisted. Payload: the created note object.\n * @event note-deleted Emitted after a note is successfully deleted. Payload: the deleted note ID.\n * @event visibility-changed Emitted after a note's visibility was changed. Payload: `{ id, visibility }`.\n * @event mention Emitted after a note that mentions someone was added. Payload: `{ objectId, register, schema, noteId, mentionedUserIds }`, plus `mentionedGroupIds` when a group (`@\"group/<gid>\"`) is mentioned. nc-vue notifies nobody; the listener does.\n * @event show-all Emitted when the user clicks the \"Show all\" button — parents typically open a full notes sidebar tab.\n */\n/**\n * The server's own sentence on a refused note write, '' when it gave none.\n *\n * @param {Response} response The refused response.\n * @return {Promise<string>} The message.\n */\nasync function refusalMessage(response) {\n\ttry {\n\t\tconst data = await response.json()\n\t\tconst message = serverErrorMessage({ message: '-', response: { data } })\n\t\treturn message === '-' ? '' : message\n\t} catch {\n\t\treturn ''\n\t}\n}\n\nexport default {\n\tname: 'CnNotesCard',\n\n\tcomponents: {\n\t\tCnDetailCard,\n\t\tCnNoteBody,\n\t\tCnNoteComposer,\n\t\tCnUserActionMenu,\n\t\tCnVisibilityChip,\n\t\tNcButton,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcLoadingIcon,\n\t\tNotePlusOutline,\n\t\tSend,\n\t\tDelete,\n\t},\n\n\tinject: {\n\t\tcnLook: { default: 'nextcloud' },\n\t},\n\n\tprops: {\n\t\t/** OpenRegister register ID */\n\t\tregisterId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema ID */\n\t\tschemaId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Object UUID */\n\t\tobjectId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Show the notes of a plain Nextcloud file instead of an OpenRegister\n\t\t * object: the file's id. Used when no `objectId` is given; the notes\n\t\t * are the file's comments (`/remote.php/dav/comments/files/{fileId}`),\n\t\t * the same ones the Files sidebar shows.\n\t\t */\n\t\tfileId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Base API URL for OpenRegister */\n\t\tapiBase: {\n\t\t\ttype: String,\n\t\t\tdefault: '/apps/openregister/api',\n\t\t},\n\n\t\t/**\n\t\t * Read the notes of something that is not an OpenRegister object:\n\t\t * `{ kind: 'flow-task', id }` reads `${apiBase}/flow-tasks/{id}/notes`.\n\t\t * `id` may be left out or written `@objectId` for `objectId`.\n\t\t *\n\t\t * @type {{kind: string, id?: (string|number)}|null}\n\t\t */\n\t\tsource: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * A notes URL that replaces the object's own: the list and the add go\n\t\t * to it, a delete to `{endpoint}/{noteId}`. `{id}` is filled with the\n\t\t * source's id or `objectId`.\n\t\t */\n\t\tendpoint: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Maximum number of notes to display */\n\t\tmaxDisplay: {\n\t\t\ttype: Number,\n\t\t\tdefault: 5,\n\t\t},\n\n\t\t/** Whether the card is collapsible */\n\t\tcollapsible: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Render the notes body without the surrounding CnDetailCard.\n\t\t *\n\t\t * Set when a surface already supplies the card and the title, such as a\n\t\t * tab panel whose open tab names the panel. Leaving the card on there\n\t\t * nests a card inside a card and shows the label twice.\n\t\t */\n\t\tchromeless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Where the add form lives: `inline` (default) keeps it open above the\n\t\t * notes; `button` puts it behind an \"Add note\" button in the card's\n\t\t * header (DqTaak \"Notities\"), and closes it again once the note is\n\t\t * saved. A reply opens it either way. Without the card's header\n\t\t * (`chromeless`) the form stays inline.\n\t\t *\n\t\t * @spec openspec/changes/notes-card-composer-button/specs/notes-card-composer-button/spec.md#requirement-a-notes-card-can-keep-its-composer-behind-a-header-button\n\t\t */\n\t\tcomposer: {\n\t\t\ttype: String,\n\t\t\tdefault: 'inline',\n\t\t\tvalidator: (v) => ['inline', 'button'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Show a chip on every note reading internal or public. A note without a\n\t\t * value reads as internal. Off by default, so a host that passes nothing\n\t\t * renders the card as before. Not used for a file source (file comments\n\t\t * carry no flag).\n\t\t */\n\t\tshowVisibility: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Whether the caller may set visibility on this object: the answer the\n\t\t * server gives for `update` on it. The host passes it; the card never\n\t\t * infers it from the current user. When true the card offers the\n\t\t * public switch in the add-note form and a toggle on each note.\n\t\t */\n\t\tcanSetVisibility: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t// --- Pre-translated labels ---\n\t\t/** Card header title. */\n\t\ttitleLabel: { type: String, default: () => t('nextcloud-vue', 'Notes') },\n\t\t/** Label for the submit button that creates a new note. */\n\t\taddNoteLabel: { type: String, default: () => t('nextcloud-vue', 'Add note') },\n\t\t/** Placeholder shown inside the new-note textarea before any input. */\n\t\taddNotePlaceholder: { type: String, default: () => t('nextcloud-vue', 'Write a note…') },\n\t\t/** Empty-state text shown when the object has zero notes. */\n\t\tnoNotesLabel: { type: String, default: () => t('nextcloud-vue', 'No notes yet') },\n\t\t/** Label for the \"Show all\" button rendered when the note list is truncated. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Text shown instead of the notes when Nextcloud refuses a file's comments (no access). */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'Notes are not available for this file') },\n\t\t/** Label of the add-note switch that makes the note public. */\n\t\tpublicSwitchLabel: { type: String, default: () => t('nextcloud-vue', 'Public (visible to the customer)') },\n\t\t/** Label of the per-note action that makes an internal note public. */\n\t\tmakePublicLabel: { type: String, default: () => t('nextcloud-vue', 'Make public') },\n\t\t/** Label of the per-note action that makes a public note internal. */\n\t\tmakeInternalLabel: { type: String, default: () => t('nextcloud-vue', 'Make internal') },\n\t\t/** Label of the Reply button (shown only when the backend supports replies). */\n\t\treplyLabel: { type: String, default: () => t('nextcloud-vue', 'Reply') },\n\t\t/** Aria label for the per-note delete icon button. */\n\t\tdeleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete note') },\n\t\t/** Text shown when a `source` or `endpoint` read failed. */\n\t\tfailedLabel: { type: String, default: () => t('nextcloud-vue', 'The notes could not be loaded') },\n\t},\n\n\temits: ['mention', 'note-added', 'note-deleted', 'show-all', 'visibility-changed'],\n\n\tdata() {\n\t\treturn {\n\t\t\tCommentTextOutline: markRaw(CommentTextOutline),\n\t\t\tallNotes: [],\n\t\t\tloading: false,\n\t\t\tnewNoteText: '',\n\t\t\t/** Whether the add form is open while `composer` is `button`. */\n\t\t\tcomposerOpen: false,\n\t\t\t/** Visibility the next note is written with (only sent when the caller may set it). */\n\t\t\tnewNoteVisibility: 'internal',\n\t\t\tnoteSaving: false,\n\t\t\tdeleteConfirmId: null,\n\t\t\t/** The file source answered 403 or 404: no notes and no add field. */\n\t\t\tunavailable: false,\n\t\t\t/** A `source` or `endpoint` read failed: said, rather than shown as no notes. */\n\t\t\tfailed: false,\n\t\t\t/** The note a reply is being written to, or null. */\n\t\t\treplyTo: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {boolean} Whether the add form sits behind the header's button. */\n\t\tcomposerBehindButton() {\n\t\t\treturn this.composer === 'button' && !this.chromeless && !this.unavailable\n\t\t},\n\n\t\t/** @return {boolean} Whether the add form shows. */\n\t\tcomposerShown() {\n\t\t\treturn !this.composerBehindButton || this.composerOpen || Boolean(this.replyTo)\n\t\t},\n\n\t\t/** Chips are shown: the host asked for them (or lets the caller set visibility) and the notes are object notes. */\n\t\tvisibilityShown() {\n\t\t\treturn (this.showVisibility || this.canSetVisibility) && !this.isFileSource\n\t\t},\n\n\t\t/** The switch and the per-note toggle are offered: only when the host says the caller may set it. */\n\t\tvisibilityToggleShown() {\n\t\t\treturn this.canSetVisibility && !this.isFileSource\n\t\t},\n\n\t\t/**\n\t\t * The notes URL a `source` or `endpoint` names, '' while its record id\n\t\t * is unknown, or null when the card reads an object.\n\t\t *\n\t\t * @return {string|null}\n\t\t * @spec openspec/changes/leaf-cards-read-a-source/specs/leaf-cards-read-a-source/spec.md#requirement-a-leaf-card-reads-a-source-or-an-endpoint\n\t\t */\n\t\tsourceUrl() {\n\t\t\treturn leafSourceUrl({ source: this.source, endpoint: this.endpoint, apiBase: this.apiBase, objectId: this.objectId }, 'notes')\n\t\t},\n\n\t\t/** The notes come from a `source` or `endpoint`, not from an object. */\n\t\tisSourceMode() {\n\t\t\treturn this.sourceUrl !== null\n\t\t},\n\n\t\t/** @return {string} The notes URL the list, add and delete use. */\n\t\tnotesUrl() {\n\t\t\tif (this.isSourceMode) {\n\t\t\t\treturn this.sourceUrl\n\t\t\t}\n\t\t\treturn `${this.apiBase}/objects/${this.registerId}/${this.schemaId}/${this.objectId}/notes`\n\t\t},\n\n\t\t/** The file source applies: a file id and no object. */\n\t\tisFileSource() {\n\t\t\treturn this.fileId !== null && this.fileId !== undefined && this.fileId !== '' && !this.objectId\n\t\t},\n\n\t\t/** @return {{apiBase: string, register: string, schema: string, objectId: string}} Where this record's files live, for pasted images. */\n\t\trecord() {\n\t\t\treturn { apiBase: this.apiBase, register: this.registerId, schema: this.schemaId, objectId: this.objectId }\n\t\t},\n\n\t\t/** @return {boolean} Whether the backend supports replies: its notes carry a `parentId` key. Not for a file's comments. */\n\t\tcanReply() {\n\t\t\treturn !this.isFileSource && supportsReplies(this.allNotes)\n\t\t},\n\n\t\t/** @return {Array<{note: object, replies: object[]}>} The shown notes as threads. */\n\t\tthreads() {\n\t\t\treturn threadNotes(this.displayedNotes)\n\t\t},\n\n\t\t/** @return {string} The line above the form while replying. */\n\t\treplyingLabel() {\n\t\t\treturn this.replyTo ? t('nextcloud-vue', 'Replying to {author}', { author: this.getNoteAuthorName(this.replyTo) }) : ''\n\t\t},\n\n\t\tdisplayedNotes() {\n\t\t\t// Reverse chronological, limited to maxDisplay\n\t\t\tconst sorted = [...this.allNotes].sort((a, b) => {\n\t\t\t\tconst dateA = new Date(a.creationDateTime || a.created || a.createdAt || 0)\n\t\t\t\tconst dateB = new Date(b.creationDateTime || b.created || b.createdAt || 0)\n\t\t\t\treturn dateB - dateA\n\t\t\t})\n\t\t\treturn sorted.slice(0, this.maxDisplay)\n\t\t},\n\t},\n\n\twatch: {\n\t\tfileId: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tif (this.isFileSource) {\n\t\t\t\t\tthis.fetchNotes()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(newId) {\n\t\t\t\tif (newId && this.registerId && this.schemaId && !this.isSourceMode) {\n\t\t\t\t\tthis.fetchNotes()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\n\t\tsourceUrl: {\n\t\t\timmediate: true,\n\t\t\thandler(url) {\n\t\t\t\tif (typeof url === 'string' && url !== '') {\n\t\t\t\t\tthis.fetchNotes()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A note's visibility; an entry with no value reads as internal.\n\t\t *\n\t\t * @param {object} note The note.\n\t\t * @return {'public'|'internal'} The visibility.\n\t\t */\n\t\tnoteVisibility(note) {\n\t\t\treturn note && note.visibility === 'public' ? 'public' : 'internal'\n\t\t},\n\n\t\t/**\n\t\t * The one place a note write goes out: creating a note and changing its\n\t\t * visibility both send through here, so the value is sent from one spot.\n\t\t *\n\t\t * @param {'POST'|'PATCH'} method The HTTP method.\n\t\t * @param {string} url The notes URL (with the note id for a change).\n\t\t * @param {object} body The JSON body.\n\t\t * @return {Promise<Response>} The response.\n\t\t */\n\t\twriteNote(method, url, body) {\n\t\t\treturn fetch(prefixUrl(url), { method, headers: buildHeaders(), body: JSON.stringify(body) })\n\t\t},\n\n\t\t/**\n\t\t * Flip a note between internal and public.\n\t\t *\n\t\t * @param {object} note The note.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync toggleVisibility(note) {\n\t\t\tif (!this.visibilityToggleShown) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst next = this.noteVisibility(note) === 'public' ? 'internal' : 'public'\n\t\t\ttry {\n\t\t\t\tconst url = `${this.notesUrl}/${note.id}`\n\t\t\t\tconst response = await this.writeNote('PATCH', url, { visibility: next })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.allNotes = this.allNotes.map((n) => (n.id === note.id ? { ...n, visibility: next } : n))\n\t\t\t\t\t/** @event visibility-changed Emitted after a note's visibility was changed. Payload: `{ id, visibility }`. */\n\t\t\t\t\tthis.$emit('visibility-changed', { id: note.id, visibility: next })\n\t\t\t\t} else {\n\t\t\t\t\tthis.showError('Failed to change visibility')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\tconsole.error('CnNotesCard: Failed to change visibility', err)\n\t\t\t\tthis.showError('Failed to change visibility')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The rows of one thread: the note, then its replies. A reply whose parent\n\t\t * is far up the list carries a one-line quote of it.\n\t\t *\n\t\t * @param {{note: object, replies: object[]}} thread The thread.\n\t\t * @return {Array<{note: object, isReply: boolean, quote: string}>} The rows.\n\t\t */\n\t\tthreadRows(thread) {\n\t\t\tconst flat = this.threads.flatMap((t2) => [t2.note, ...t2.replies])\n\t\t\treturn [\n\t\t\t\t{ note: thread.note, isReply: false, quote: '' },\n\t\t\t\t...thread.replies.map((reply) => ({ note: reply, isReply: true, quote: quoteFor(reply, flat) })),\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * @param {object} note A top-level note.\n\t\t * @return {string} The name of its thread for assistive technology.\n\t\t */\n\t\tthreadLabel(note) {\n\t\t\treturn t('nextcloud-vue', 'Note by {author} and its replies', { author: this.getNoteAuthorName(note) })\n\t\t},\n\n\t\t/**\n\t\t * @param {object} note The note a Reply button answers.\n\t\t * @return {string} The button's accessible name, naming the author.\n\t\t */\n\t\treplyAria(note) {\n\t\t\treturn t('nextcloud-vue', 'Reply to {author}', { author: this.getNoteAuthorName(note) })\n\t\t},\n\n\t\t/**\n\t\t * Tell the host a saved note mentions someone. The payload keeps the\n\t\t * user ids and adds the group ids only when a group is mentioned.\n\t\t *\n\t\t * @param {string} text The saved text.\n\t\t * @param {string|null} noteId The note's id.\n\t\t * @return {void}\n\t\t */\n\t\temitMentionEvent(text, noteId) {\n\t\t\tconst mentionedUserIds = extractMentionedIds(text)\n\t\t\tconst mentionedGroupIds = extractMentionedGroupIds(text)\n\t\t\tif (mentionedUserIds.length === 0 && mentionedGroupIds.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('mention', {\n\t\t\t\tobjectId: this.objectId,\n\t\t\t\tregister: this.registerId,\n\t\t\t\tschema: this.schemaId,\n\t\t\t\tnoteId,\n\t\t\t\tmentionedUserIds,\n\t\t\t\t...(mentionedGroupIds.length > 0 ? { mentionedGroupIds } : {}),\n\t\t\t})\n\t\t},\n\n\t\tgetNoteAuthorId(note) {\n\t\t\treturn note.actorId || note.author || ''\n\t\t},\n\n\t\tinitialsOf,\n\n\t\tgetNoteAuthorName(note) {\n\t\t\treturn note.actorDisplayName || note.author || 'Unknown'\n\t\t},\n\n\t\tisCurrentUser(note) {\n\t\t\tif (note.isCurrentUser === true) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst authorId = this.getNoteAuthorId(note)\n\t\t\tconst currentUser = typeof OC !== 'undefined' ? OC?.currentUser : null\n\t\t\treturn authorId === currentUser\n\t\t},\n\n\t\tcanDeleteNote(note) {\n\t\t\treturn this.isCurrentUser(note)\n\t\t},\n\n\t\tfileComments() {\n\t\t\treturn useFileComments(this.fileId)\n\t\t},\n\n\t\tasync fetchNotes() {\n\t\t\tif (this.isFileSource) {\n\t\t\t\tthis.loading = true\n\t\t\t\tthis.unavailable = false\n\t\t\t\ttry {\n\t\t\t\t\tthis.allNotes = await this.fileComments().list()\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.allNotes = []\n\t\t\t\t\tthis.unavailable = err && (err.status === 403 || err.status === 404)\n\t\t\t\t\tif (!this.unavailable) {\n\t\t\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\t\t\tconsole.error('CnNotesCard: Failed to fetch file comments', err)\n\t\t\t\t\t}\n\t\t\t\t} finally {\n\t\t\t\t\tthis.loading = false\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.isSourceMode ? this.notesUrl === '' : (!this.registerId || !this.schemaId || !this.objectId)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.failed = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.notesUrl), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.allNotes = data.results || data || []\n\t\t\t\t} else if (this.isSourceMode) {\n\t\t\t\t\tthis.allNotes = []\n\t\t\t\t\tthis.failed = true\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tthis.failed = this.isSourceMode\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\tconsole.error('CnNotesCard: Failed to fetch notes', err)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync submitNote() {\n\t\t\tif (!this.newNoteText.trim() || this.noteSaving) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.noteSaving = true\n\t\t\tif (this.isFileSource) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.fileComments().add(this.newNoteText.trim())\n\t\t\t\t\tthis.composerOpen = false\n\t\t\t\t\tthis.newNoteText = ''\n\t\t\t\t\tawait this.fetchNotes()\n\t\t\t\t\tthis.$emit('note-added')\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\t\tconsole.error('CnNotesCard: Failed to add file comment', err)\n\t\t\t\t\tthis.showError('Failed to add note')\n\t\t\t\t} finally {\n\t\t\t\t\tthis.noteSaving = false\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst url = this.notesUrl\n\t\t\t\tconst body = { message: this.newNoteText.trim() }\n\t\t\t\tif (this.visibilityToggleShown) {\n\t\t\t\t\tbody.visibility = this.newNoteVisibility\n\t\t\t\t}\n\t\t\t\tif (this.replyTo) {\n\t\t\t\t\tbody.parentId = threadRootId(this.replyTo, this.allNotes)\n\t\t\t\t}\n\t\t\t\tconst response = await this.writeNote('POST', url, body)\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tlet noteId = null\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst created = await response.json()\n\t\t\t\t\t\tnoteId = (created && (created.id || (created.results && created.results.id))) || null\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Body not JSON: the event still fires with noteId null.\n\t\t\t\t\t}\n\t\t\t\t\tthis.emitMentionEvent(body.message, noteId)\n\t\t\t\t\tthis.replyTo = null\n\t\t\t\t\tthis.composerOpen = false\n\t\t\t\t\tthis.newNoteText = ''\n\t\t\t\t\tthis.newNoteVisibility = 'internal'\n\t\t\t\t\tawait this.fetchNotes()\n\t\t\t\t\tthis.$emit('note-added')\n\t\t\t\t} else {\n\t\t\t\t\tthis.showError(await refusalMessage(response) || 'Failed to add note')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\tconsole.error('CnNotesCard: Failed to add note', err)\n\t\t\t\tthis.showError('Failed to add note')\n\t\t\t} finally {\n\t\t\t\tthis.noteSaving = false\n\t\t\t}\n\t\t},\n\n\t\tasync confirmDelete(note) {\n\t\t\t// Simple inline confirmation — delete directly\n\t\t\tif (this.isFileSource) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.fileComments().remove(note.id)\n\t\t\t\t\tthis.allNotes = this.allNotes.filter((n) => n.id !== note.id)\n\t\t\t\t\tthis.$emit('note-deleted')\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\t\tconsole.error('CnNotesCard: Failed to delete file comment', err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst url = `${this.notesUrl}/${note.id}`\n\t\t\t\tconst response = await fetch(prefixUrl(url), {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.allNotes = this.allNotes.filter((n) => n.id !== note.id)\n\t\t\t\t\tthis.$emit('note-deleted')\n\t\t\t\t} else {\n\t\t\t\t\tthis.showError(await refusalMessage(response) || 'Failed to delete note')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\tconsole.error('CnNotesCard: Failed to delete note', err)\n\t\t\t}\n\t\t},\n\n\t\tformatDate(dateStr) {\n\t\t\tif (!dateStr) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// The board look writes a note's moment as the boards do: \"6 okt 11:10\", the year only outside this year.\n\t\t\tif (normalizeLook(this.cnLook) === 'board') {\n\t\t\t\treturn formatBoardDateTime(dateStr) || dateStr\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(dateStr).toLocaleString(userLocale(), {\n\t\t\t\t\tyear: 'numeric',\n\t\t\t\t\tmonth: 'short',\n\t\t\t\t\tday: 'numeric',\n\t\t\t\t\thour: '2-digit',\n\t\t\t\t\tminute: '2-digit',\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\treturn dateStr\n\t\t\t}\n\t\t},\n\n\t\tshowError(message) {\n\t\t\ttry {\n\t\t\t\timport('@nextcloud/dialogs').then(({ showError }) => {\n\t\t\t\t\tshowError(message)\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\tconsole.error(message)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-notes-card__add-form {\n\tmargin-bottom: 12px;\n}\n\n.cn-notes-card__textarea {\n\twidth: 100%;\n\tmargin-bottom: 8px;\n\tbox-sizing: border-box;\n}\n\n.cn-notes-card__replying {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tmargin: 0 0 4px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-notes-card__note--reply {\n\tmargin-inline-start: 24px;\n}\n\n.cn-notes-card__quote {\n\tmargin: 0 0 2px;\n\tpadding-inline-start: 8px;\n\tborder-inline-start: 2px solid var(--color-border-dark);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-notes-card__empty {\n\ttext-align: center;\n\tpadding: 16px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-notes-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-notes-card__note {\n\tpadding: 10px 0;\n\tborder-bottom: 1px solid var(--color-border);\n\tposition: relative;\n}\n\n.cn-notes-card__note:last-child {\n\tborder-bottom: none;\n}\n\n.cn-notes-card__initials {\n\tdisplay: none;\n}\n\n.cn-notes-card__note-header {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: baseline;\n\tmargin-bottom: 4px;\n}\n\n.cn-notes-card__author {\n\tfont-size: 13px;\n}\n\n.cn-notes-card__author--self {\n\tcolor: var(--color-main-text);\n}\n\n.cn-notes-card__visibility {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-notes-card__time {\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n\tmargin-left: 8px;\n}\n\n.cn-notes-card__body {\n\tfont-size: 13px;\n\tmargin: 0;\n\twhite-space: pre-wrap;\n\toverflow-wrap: anywhere;\n\tpadding-right: 32px;\n}\n\n/* The note's own class leads so the position outranks NcButton's\n `position: relative`, which loads later at the same weight and put the\n button in the flow under the note. */\n.cn-notes-card__note > .cn-notes-card__delete-btn {\n\tposition: absolute;\n\ttop: 8px;\n\tright: -4px;\n\topacity: 0;\n\ttransition: opacity 0.15s ease;\n}\n\n.cn-notes-card__note:hover > .cn-notes-card__delete-btn,\n.cn-notes-card__note:focus-within > .cn-notes-card__delete-btn {\n\topacity: 1;\n}\n\n.cn-notes-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\tpadding: 0;\n\twidth: 100%;\n\ttext-align: center;\n}\n\n.cn-notes-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","CnNoteBody","CnNoteComposer","CnUserActionMenu","CnVisibilityChip","t"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA4LA;AACC;AACA;;;AAGA;;AAEA;;;;;AAKA;AACA;AACA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;;;;AAID;;AAEC;AACA;AACA;;AAED,eAAe,cAAc,CAAC,QAAQ,EAAE;AACvC,CAAA,IAAI;EACH,MAAM,IAAK,GAAE,MAAM,QAAQ,CAAC,IAAI,GAAC;EACjC,MAAM,OAAM,GAAI,kBAAkB,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,IAAK,EAAA,EAAG,EAAA;EACvE,OAAO,OAAQ,KAAI,GAAI,GAAE,EAAC,GAAI,OAAM;AACrC,EAAA,CAAE,MAAM;AACP,EAAA,OAAO,EAAC;AACT,EAAA;AACD,CAAA;;AAEA,aAAe;AACd,CAAA,IAAI,EAAE,aAAa;;CAEnB,UAAU,EAAE;gBACXA,UAAY;cACZC,YAAU;AACV,kBAAAC,YAAc;AACd,oBAAAC,YAAgB;AAChB,oBAAAC,YAAgB;AAChB,EAAA,QAAQ;AACR,EAAA,qBAAqB;EACrB,aAAa;AACb,EAAA,eAAe;EACf,IAAI;AACJ,EAAA,MAAM;EACN;;AAED,CAAA,MAAM,EAAE;EACP,MAAM,EAAE,EAAE,OAAO,EAAE,WAAU,EAAG;EAChC;;AAED,CAAA,KAAK,EAAE;;EAEN,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;;AAGD,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;AACA,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;AAGC;AACA;;AAED,EAAA,MAAM,EAAE;AACP,GAAA,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;GACtB,OAAO,EAAE,IAAI;GACb;;;AAGD,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,wBAAwB;GACjC;;AAED;;AAEC;;AAEA;AACA;;AAED,EAAA,MAAM,EAAE;GACP,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;;;;;AAKA,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;EACA,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,CAAC;GACV;;AAED;EACA,WAAW,EAAE;GACZ,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;AAED;;AAEC;;;AAGA;;EAED,UAAU,EAAE;GACX,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;AAED;;;;;AAKC;AACA;AACA;;AAED,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,QAAQ;GACjB,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;GAClD;;AAED;;;;;;AAMA,EAAA,cAAc,EAAE;GACf,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;AAED;;;;;;AAMA,EAAA,gBAAgB,EAAE;GACjB,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;;AAGD;AACA,EAAA,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMC,SAAC,CAAC,eAAe,EAAE,OAAO,GAAG;;AAExE,EAAA,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,UAAU,GAAG;;AAE7E,EAAA,kBAAkB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,eAAe,GAAG;;AAExF,EAAA,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,cAAc,GAAG;AACjF;AACA,EAAA,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,UAAU,GAAG;AAC7E;EACA,gBAAgB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,uCAAuC,CAAA,EAAG;;AAE9G,EAAA,iBAAiB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,kCAAkC,GAAG;;AAE1G,EAAA,eAAe,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,aAAa,GAAG;;AAEnF,EAAA,iBAAiB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,eAAe,GAAG;AACvF;AACA,EAAA,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,OAAO,GAAG;AACxE;AACA,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,aAAa,GAAG;;AAE/E,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,+BAA+B,GAAG;EACjG;;AAED,CAAA,KAAK,EAAE,CAAC,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,oBAAoB,CAAC;;AAElF,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;GACN,kBAAkB,EAAE,OAAO,CAAC,kBAAkB,CAAC;GAC/C,QAAQ,EAAE,EAAE;GACZ,OAAO,EAAE,KAAK;AACd,GAAA,WAAW,EAAE,EAAE;;AAEf,GAAA,YAAY,EAAE,KAAK;AACnB;GACA,iBAAiB,EAAE,UAAU;AAC7B,GAAA,UAAU,EAAE,KAAK;AACjB,GAAA,eAAe,EAAE,IAAI;;AAErB,GAAA,WAAW,EAAE,KAAK;AAClB;GACA,MAAM,EAAE,KAAK;AACb;GACA,OAAO,EAAE,IAAI;AACd,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;EACA,oBAAoB,GAAG;AACtB,GAAA,OAAO,IAAI,CAAC,QAAS,KAAI,QAAO,IAAK,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,WAAU;GACzE;;AAED;AACA,EAAA,aAAa,GAAG;AACf,GAAA,OAAO,CAAC,IAAI,CAAC,oBAAqB,IAAG,IAAI,CAAC,YAAa,IAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAA;GAC9E;;AAED;AACA,EAAA,eAAe,GAAG;AACjB,GAAA,OAAO,CAAC,IAAI,CAAC,cAAe,IAAG,IAAI,CAAC,gBAAgB,KAAK,CAAC,IAAI,CAAC,YAAW;GAC1E;;AAED;EACA,qBAAqB,GAAG;GACvB,OAAO,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,YAAW;GACjD;;AAED;;AAEC;AACA;AACA;AACA;;EAED,SAAS,GAAG;GACX,OAAO,aAAa,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAO,EAAG,EAAE,OAAO,CAAA;GAC9H;;;AAGD,EAAA,YAAY,GAAG;GACd,OAAO,IAAI,CAAC,SAAQ,KAAM,IAAG;GAC7B;;;EAGD,QAAQ,GAAG;AACV,GAAA,IAAI,IAAI,CAAC,YAAY,EAAE;AACtB,IAAA,OAAO,IAAI,CAAC,SAAQ;AACrB,IAAA;AACA,GAAA,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAA;GAC1F;;AAED;AACA,EAAA,YAAY,GAAG;AACd,GAAA,OAAO,IAAI,CAAC,MAAK,KAAM,IAAK,IAAG,IAAI,CAAC,MAAO,KAAI,aAAa,IAAI,CAAC,MAAK,KAAM,EAAC,IAAK,CAAC,IAAI,CAAC,QAAO;GAC/F;;;AAGD,EAAA,MAAM,GAAG;GACR,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAS,EAAA;GAC1G;;;EAGD,QAAQ,GAAG;GACV,OAAO,CAAC,IAAI,CAAC,YAAa,IAAG,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAA;GAC1D;;AAED;AACA,EAAA,OAAO,GAAG;AACT,GAAA,OAAO,WAAW,CAAC,IAAI,CAAC,cAAc,CAAA;GACtC;;;AAGD,EAAA,aAAa,GAAG;GACf,OAAO,IAAI,CAAC,OAAQ,GAAEA,SAAC,CAAC,eAAe,EAAE,sBAAsB,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAE,EAAC,CAAE,GAAE,EAAC;GACtH;;AAED,EAAA,cAAc,GAAG;AAChB;AACA,GAAA,MAAM,MAAO,GAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK;AAChD,IAAA,MAAM,KAAM,GAAE,IAAI,IAAI,CAAC,CAAC,CAAC,gBAAiB,IAAG,CAAC,CAAC,OAAM,IAAK,CAAC,CAAC,SAAU,IAAG,CAAC,EAAA;AAC1E,IAAA,MAAM,KAAM,GAAE,IAAI,IAAI,CAAC,CAAC,CAAC,gBAAiB,IAAG,CAAC,CAAC,OAAM,IAAK,CAAC,CAAC,SAAU,IAAG,CAAC,EAAA;AAC1E,IAAA,OAAO,KAAM,GAAE,KAAI;IACnB,EAAA;AACD,GAAA,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAA;GACtC;EACD;;AAED,CAAA,KAAK,EAAE;AACN,EAAA,MAAM,EAAE;AACP,GAAA,SAAS,EAAE,IAAI;AACf,GAAA,OAAO,GAAG;AACT,IAAA,IAAI,IAAI,CAAC,YAAY,EAAE;AACtB,KAAA,IAAI,CAAC,UAAU,GAAC;AACjB,KAAA;IACA;GACD;;AAED,EAAA,QAAQ,EAAE;AACT,GAAA,SAAS,EAAE,IAAI;AACf,GAAA,OAAO,CAAC,KAAK,EAAE;IACd,IAAI,KAAI,IAAK,IAAI,CAAC,UAAW,IAAG,IAAI,CAAC,QAAO,IAAK,CAAC,IAAI,CAAC,YAAY,EAAE;AACpE,KAAA,IAAI,CAAC,UAAU,GAAC;AACjB,KAAA;IACA;GACD;;EAED,SAAS,EAAE;AACV,GAAA,SAAS,EAAE,IAAI;GACf,OAAO,CAAC,GAAG,EAAE;AACZ,IAAA,IAAI,OAAO,GAAE,KAAM,QAAO,IAAK,GAAE,KAAM,EAAE,EAAE;AAC1C,KAAA,IAAI,CAAC,UAAU,GAAC;AACjB,KAAA;IACA;GACD;EACD;;AAED,CAAA,OAAO,EAAE;AACR;;AAEC;;AAEA;;AAED,EAAA,cAAc,CAAC,IAAI,EAAE;GACpB,OAAO,IAAK,IAAG,IAAI,CAAC,UAAW,KAAI,QAAS,GAAE,QAAS,GAAE,UAAS;GAClE;;AAED;;;AAGC;AACA;;AAEA;AACA;;EAED,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE;AAC5B,GAAA,OAAO,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,CAAA;GAC5F;;AAED;AACC;AACA;;;;EAID,MAAM,gBAAgB,CAAC,IAAI,EAAE;AAC5B,GAAA,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE;IAChC,MAAK;AACN,IAAA;AACA,GAAA,MAAM,IAAG,GAAI,IAAI,CAAC,cAAc,CAAC,IAAI,CAAE,KAAI,QAAO,GAAI,UAAS,GAAI,SAAO;AAC1E,GAAA,IAAI;AACH,IAAA,MAAM,GAAE,GAAI,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,EAAA;AACxC,IAAA,MAAM,QAAO,GAAI,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,MAAM,EAAA;AACxE,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;AAChB,KAAA,IAAI,CAAC,QAAS,GAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,IAAG,EAAI,GAAE,CAAC,CAAC,EAAA;;KAE5F,IAAI,CAAC,KAAK,CAAC,oBAAoB,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,IAAG,EAAG,EAAA;KACjE,MAAK;AACN,KAAA,IAAI,CAAC,SAAS,CAAC,6BAA6B,EAAA;AAC7C,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE,GAAG,EAAA;AAC7D,IAAA,IAAI,CAAC,SAAS,CAAC,6BAA6B,EAAA;AAC7C,IAAA;GACA;;AAED;;AAEC;AACA;;AAEA;;AAED,EAAA,UAAU,CAAC,MAAM,EAAE;GAClB,MAAM,IAAK,GAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAA;AAClE,GAAA,OAAO;AACN,IAAA,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI;AAChD,IAAA,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAE,EAAC,CAAC,CAAC;AACjG,IAAA;GACA;;AAED;AACC;;;AAGD,EAAA,WAAW,CAAC,IAAI,EAAE;AACjB,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,kCAAkC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,iBAAiB,CAAC,IAAI,GAAG,CAAA;GACtG;;AAED;AACC;;;AAGD,EAAA,SAAS,CAAC,IAAI,EAAE;AACf,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,mBAAmB,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,iBAAiB,CAAC,IAAI,GAAG,CAAA;GACvF;;AAED;;;AAGC;AACA;AACA;;;EAGD,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE;GAC9B,MAAM,mBAAmB,mBAAmB,CAAC,IAAI,EAAA;GACjD,MAAM,iBAAgB,GAAI,wBAAwB,CAAC,IAAI,EAAA;GACvD,IAAI,gBAAgB,CAAC,MAAK,KAAM,CAAA,IAAK,iBAAiB,CAAC,MAAK,KAAM,CAAC,EAAE;IACpE,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE;IACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;IACvB,QAAQ,EAAE,IAAI,CAAC,UAAU;AACzB,IAAA,MAAM,EAAE,IAAI,CAAC,QAAQ;AACrB,IAAA,MAAM;AACN,IAAA,gBAAgB;IAChB,IAAI,iBAAiB,CAAC,SAAS,CAAA,GAAI,EAAE,mBAAoB,GAAE,EAAE,CAAC;IAC9D,EAAA;GACD;;AAED,EAAA,eAAe,CAAC,IAAI,EAAE;AACrB,GAAA,OAAO,IAAI,CAAC,OAAQ,IAAG,IAAI,CAAC,MAAO,IAAG,EAAC;GACvC;;EAED,UAAU;;EAEV,iBAAiB,CAAC,IAAI,EAAE;GACvB,OAAO,IAAI,CAAC,gBAAiB,IAAG,IAAI,CAAC,MAAO,IAAG,SAAQ;GACvD;;AAED,EAAA,aAAa,CAAC,IAAI,EAAE;GACnB,IAAI,IAAI,CAAC,aAAc,KAAI,IAAI,EAAE;AAChC,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,MAAM,QAAS,GAAE,IAAI,CAAC,eAAe,CAAC,IAAI,EAAA;GAC1C,MAAM,WAAY,GAAE,OAAO,EAAC,KAAM,WAAY,GAAE,EAAE,EAAE,WAAY,GAAE,KAAG;GACrE,OAAO,aAAa,WAAU;GAC9B;;AAED,EAAA,aAAa,CAAC,IAAI,EAAE;GACnB,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,CAAA;GAC9B;;AAED,EAAA,YAAY,GAAG;AACd,GAAA,OAAO,eAAe,CAAC,IAAI,CAAC,MAAM,CAAA;GAClC;;AAED,EAAA,MAAM,UAAU,GAAG;AAClB,GAAA,IAAI,IAAI,CAAC,YAAY,EAAE;AACtB,IAAA,IAAI,CAAC,OAAM,GAAI,KAAG;AAClB,IAAA,IAAI,CAAC,WAAU,GAAI,MAAI;AACvB,IAAA,IAAI;AACH,KAAA,IAAI,CAAC,QAAO,GAAI,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,IAAI,GAAC;KAC9C,CAAA,OAAO,GAAG,EAAE;AACb,KAAA,IAAI,CAAC,QAAO,GAAI,GAAC;KACjB,IAAI,CAAC,WAAU,GAAI,GAAE,KAAM,GAAG,CAAC,MAAK,KAAM,GAAE,IAAK,GAAG,CAAC,MAAO,KAAI,GAAG,EAAA;AACnE,KAAA,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;AACtB;MACA,OAAO,CAAC,KAAK,CAAC,4CAA4C,EAAE,GAAG,EAAA;AAChE,MAAA;AACD,KAAA,SAAU;AACT,KAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,KAAA;IACA,MAAK;AACN,IAAA;AACA,GAAA,IAAI,IAAI,CAAC,eAAe,IAAI,CAAC,aAAa,EAAC,IAAK,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAS,IAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;IACtG,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;AAClB,GAAA,IAAI,CAAC,MAAO,GAAE,MAAI;AAClB,GAAA,IAAI;AACH,IAAA,MAAM,QAAO,GAAI,MAAM,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,IAAI,EAAA;AAClF,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;KAChB,MAAM,IAAK,GAAE,MAAM,QAAQ,CAAC,IAAI,GAAC;AACjC,KAAA,IAAI,CAAC,QAAS,GAAE,IAAI,CAAC,OAAM,IAAK,IAAK,IAAG,GAAC;KACxC,MAAK,IAAI,IAAI,CAAC,YAAY,EAAE;AAC7B,KAAA,IAAI,CAAC,QAAO,GAAI,GAAC;AACjB,KAAA,IAAI,CAAC,MAAK,GAAI,KAAG;AAClB,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;IACb,IAAI,CAAC,MAAO,GAAE,IAAI,CAAC,aAAW;AAC9B;IACA,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,GAAG,EAAA;AACxD,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;;AAED,EAAA,MAAM,UAAU,GAAG;AAClB,GAAA,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAG,IAAG,IAAI,CAAC,UAAU,EAAE;IAChD,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,aAAa,KAAG;AACrB,GAAA,IAAI,IAAI,CAAC,YAAY,EAAE;AACtB,IAAA,IAAI;KACH,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,EAAA;KACrD,IAAI,CAAC,YAAW,GAAI,MAAI;AACxB,KAAA,IAAI,CAAC,WAAU,GAAI,GAAC;AACpB,KAAA,MAAM,IAAI,CAAC,UAAU,GAAC;KACtB,IAAI,CAAC,KAAK,CAAC,YAAY,EAAA;KACtB,CAAA,OAAO,GAAG,EAAE;AACb;KACA,OAAO,CAAC,KAAK,CAAC,yCAAyC,EAAE,GAAG,EAAA;AAC5D,KAAA,IAAI,CAAC,SAAS,CAAC,oBAAoB,EAAA;AACpC,KAAA,SAAU;AACT,KAAA,IAAI,CAAC,UAAS,GAAI,MAAI;AACvB,KAAA;IACA,MAAK;AACN,IAAA;AACA,GAAA,IAAI;AACH,IAAA,MAAM,MAAM,IAAI,CAAC,SAAO;AACxB,IAAA,MAAM,IAAK,GAAE,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAG,GAAA;AAChD,IAAA,IAAI,IAAI,CAAC,qBAAqB,EAAE;AAC/B,KAAA,IAAI,CAAC,UAAS,GAAI,IAAI,CAAC,kBAAgB;AACxC,KAAA;AACA,IAAA,IAAI,IAAI,CAAC,OAAO,EAAE;KACjB,IAAI,CAAC,WAAW,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAA;AACzD,KAAA;IACA,MAAM,QAAS,GAAE,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAA;AACvD,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;KAChB,IAAI,MAAO,GAAE,KAAG;AAChB,KAAA,IAAI;AACH,MAAA,MAAM,OAAQ,GAAE,MAAM,QAAQ,CAAC,IAAI,GAAC;AACpC,MAAA,SAAS,CAAC,OAAM,KAAM,OAAO,CAAC,EAAG,KAAI,OAAO,CAAC,OAAM,IAAK,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,KAAG;AACrF,MAAA,CAAE,MAAM;;AAER,MAAA;AACA,KAAA,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAA;AAC1C,KAAA,IAAI,CAAC,OAAM,GAAI,KAAG;KAClB,IAAI,CAAC,YAAW,GAAI,MAAI;AACxB,KAAA,IAAI,CAAC,WAAU,GAAI,GAAC;AACpB,KAAA,IAAI,CAAC,iBAAgB,GAAI,WAAS;AAClC,KAAA,MAAM,IAAI,CAAC,UAAU,GAAC;KACtB,IAAI,CAAC,KAAK,CAAC,YAAY,EAAA;KACtB,MAAK;AACN,KAAA,IAAI,CAAC,SAAS,CAAC,MAAM,cAAc,CAAC,QAAQ,CAAE,IAAG,oBAAoB,EAAA;AACtE,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,GAAG,EAAA;AACpD,IAAA,IAAI,CAAC,SAAS,CAAC,oBAAoB,EAAA;AACpC,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,UAAS,GAAI,MAAI;AACvB,IAAA;GACA;;EAED,MAAM,aAAa,CAAC,IAAI,EAAE;AACzB;AACA,GAAA,IAAI,IAAI,CAAC,YAAY,EAAE;AACtB,IAAA,IAAI;AACH,KAAA,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAA;KACxC,IAAI,CAAC,QAAO,GAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAC,KAAM,IAAI,CAAC,EAAE,EAAA;KAC5D,IAAI,CAAC,KAAK,CAAC,cAAc,EAAA;KACxB,CAAA,OAAO,GAAG,EAAE;AACb;KACA,OAAO,CAAC,KAAK,CAAC,4CAA4C,EAAE,GAAG,EAAA;AAChE,KAAA;IACA,MAAK;AACN,IAAA;AACA,GAAA,IAAI;AACH,IAAA,MAAM,GAAE,GAAI,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,EAAA;AACxC,IAAA,MAAM,QAAS,GAAE,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE;AAC5C,KAAA,MAAM,EAAE,QAAQ;KAChB,OAAO,EAAE,YAAY,EAAE;KACvB,EAAA;AACD,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;KAChB,IAAI,CAAC,QAAO,GAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAC,KAAM,IAAI,CAAC,EAAE,EAAA;KAC5D,IAAI,CAAC,KAAK,CAAC,cAAc,EAAA;KACxB,MAAK;AACN,KAAA,IAAI,CAAC,SAAS,CAAC,MAAM,cAAc,CAAC,QAAQ,CAAE,IAAG,uBAAuB,EAAA;AACzE,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,GAAG,EAAA;AACxD,IAAA;GACA;;AAED,EAAA,UAAU,CAAC,OAAO,EAAE;GACnB,IAAI,CAAC,OAAO,EAAE;AACb,IAAA,OAAO,EAAC;AACT,IAAA;AACA;AACA,GAAA,IAAI,aAAa,CAAC,IAAI,CAAC,MAAM,CAAE,KAAI,OAAO,EAAE;AAC3C,IAAA,OAAO,mBAAmB,CAAC,OAAO,CAAA,IAAK,OAAM;AAC9C,IAAA;AACA,GAAA,IAAI;IACH,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,UAAU,EAAE,EAAE;AACrD,KAAA,IAAI,EAAE,SAAS;KACf,KAAK,EAAE,OAAO;KACd,GAAG,EAAE,SAAS;AACd,KAAA,IAAI,EAAE,SAAS;AACf,KAAA,MAAM,EAAE,SAAS;KACjB,CAAA;AACF,IAAA,CAAE,MAAM;IACP,OAAO,OAAM;AACd,IAAA;GACA;;AAED,EAAA,SAAS,CAAC,OAAO,EAAE;AAClB,GAAA,IAAI;IACH,OAAO,qDAAoB,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,SAAU,EAAC,KAAK;AACpD,KAAA,SAAS,CAAC,OAAO,EAAA;KACjB,EAAA;AACF,IAAA,CAAE,MAAM;AACP;AACA,IAAA,OAAO,CAAC,KAAK,CAAC,OAAO,EAAA;AACtB,IAAA;GACA;EACD;AACF;;;;"}