@conduction/nextcloud-vue 2.74.0 → 2.75.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.
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +19 -14
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +2 -2
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +77 -26
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +22 -14
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +23 -4
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue.js +9 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue.js.map +1 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue2.js +107 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue2.js.map +1 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue3.js +79 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue3.js.map +1 -0
- package/dist/esm/components/CnEnvironmentBanner/CnEnvironmentBanner.vue2.js +4 -1
- package/dist/esm/components/CnEnvironmentBanner/CnEnvironmentBanner.vue2.js.map +1 -1
- package/dist/esm/components/CnEnvironmentBanner/CnEnvironmentBanner.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +7 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +14 -7
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +32 -0
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +6 -3
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +18 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +2 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +16 -0
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js +2 -2
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
- package/dist/esm/directives/cnSelectAria.js +55 -0
- package/dist/esm/directives/cnSelectAria.js.map +1 -0
- package/dist/esm/nextcloud-vue.css +66 -1
- package/dist/esm/utils/cnFetch.js +17 -2
- package/dist/esm/utils/cnFetch.js.map +1 -1
- package/dist/esm/utils/resolveBrand.js +38 -0
- package/dist/esm/utils/resolveBrand.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +8775 -8327
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +66 -1
- package/package.json +1 -1
- package/src/components/CnAppNav/CnAppNav.vue +21 -16
- package/src/components/CnAppRoot/CnAppRoot.vue +53 -0
- package/src/components/CnBannerWidget/CnBannerWidget.vue +23 -4
- package/src/components/CnBrandBar/CnBrandBar.vue +230 -0
- package/src/components/CnEnvironmentBanner/CnEnvironmentBanner.vue +4 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +10 -0
- package/src/components/CnFormPage/CnFormPage.vue +33 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +2 -2
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +20 -4
- package/src/components/CnStatsBlock/CnStatsBlock.vue +18 -2
- package/src/css/form-field.css +1 -1
- package/src/directives/cnSelectAria.js +54 -0
- package/src/schemas/app-manifest-v2.schema.json +6 -1
- package/src/utils/cnFetch.js +16 -1
- package/src/utils/resolveBrand.js +37 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -78,6 +78,14 @@ var script = {
|
|
|
78
78
|
|
|
79
79
|
components: { NcNoteCard },
|
|
80
80
|
|
|
81
|
+
inject: {
|
|
82
|
+
/**
|
|
83
|
+
* The host's label lookup (CnAppRoot provides it); identity without one, so a
|
|
84
|
+
* label that is not a key renders as written.
|
|
85
|
+
*/
|
|
86
|
+
cnTranslate: { default: () => (key) => key },
|
|
87
|
+
},
|
|
88
|
+
|
|
81
89
|
props: {
|
|
82
90
|
/**
|
|
83
91
|
* Banner severity variant: `info | warning | error` (plus `success`
|
|
@@ -298,7 +306,7 @@ var script = {
|
|
|
298
306
|
*/
|
|
299
307
|
resolvedKicker() {
|
|
300
308
|
const kicker = this.kicker || (this.content && this.content.kicker) || '';
|
|
301
|
-
return kicker
|
|
309
|
+
return kicker ? this.tl(kicker) : ''
|
|
302
310
|
},
|
|
303
311
|
|
|
304
312
|
/**
|
|
@@ -309,7 +317,7 @@ var script = {
|
|
|
309
317
|
*/
|
|
310
318
|
displayTitle() {
|
|
311
319
|
const title = this.title || (this.content && this.content.title) || '';
|
|
312
|
-
return title ? this.fillValue(title) : this.displayText
|
|
320
|
+
return title ? this.fillValue(this.tl(title)) : this.displayText
|
|
313
321
|
},
|
|
314
322
|
|
|
315
323
|
/**
|
|
@@ -322,7 +330,7 @@ var script = {
|
|
|
322
330
|
displayReason() {
|
|
323
331
|
const reason = this.reason || (this.content && this.content.reason) || '';
|
|
324
332
|
if (reason) {
|
|
325
|
-
return this.fillValue(reason)
|
|
333
|
+
return this.fillValue(this.tl(reason))
|
|
326
334
|
}
|
|
327
335
|
const title = this.title || (this.content && this.content.title) || '';
|
|
328
336
|
// A card that sets `text` to the same words as its `title` says
|
|
@@ -360,7 +368,7 @@ var script = {
|
|
|
360
368
|
return {
|
|
361
369
|
key: `${index}-${action.id || action.label}`,
|
|
362
370
|
id: action.id,
|
|
363
|
-
label: action.label,
|
|
371
|
+
label: this.tl(action.label),
|
|
364
372
|
href,
|
|
365
373
|
target,
|
|
366
374
|
primary: index === primaryIndex,
|
|
@@ -462,6 +470,17 @@ var script = {
|
|
|
462
470
|
},
|
|
463
471
|
|
|
464
472
|
methods: {
|
|
473
|
+
/**
|
|
474
|
+
* A manifest string through the host's label lookup (`cnTranslate`), so a key
|
|
475
|
+
* such as "Mine" reads in the user's language.
|
|
476
|
+
*
|
|
477
|
+
* @param {string} text The text as written in the manifest.
|
|
478
|
+
* @return {string} The translated text.
|
|
479
|
+
*/
|
|
480
|
+
tl(text) {
|
|
481
|
+
return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
|
|
482
|
+
},
|
|
483
|
+
|
|
465
484
|
/**
|
|
466
485
|
* Replace `{value}` in a text by the value the predicate read, once
|
|
467
486
|
* one is known.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnBannerWidget.vue2.js","sources":["../../../../src/components/CnBannerWidget/CnBannerWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<section\n\t\tv-if=\"visible && isAttention\"\n\t\tclass=\"cn-banner-widget cn-banner-widget--attention\"\n\t\t:class=\"'cn-banner-widget--' + resolvedVariant\"\n\t\t:aria-labelledby=\"headingId\"\n\t\tdata-testid=\"cn-banner-widget-attention\">\n\t\t<div class=\"cn-banner-widget__body\">\n\t\t\t<p v-if=\"resolvedKicker\" class=\"cn-banner-widget__kicker\">\n\t\t\t\t{{ resolvedKicker }}\n\t\t\t</p>\n\t\t\t<h3 :id=\"headingId\" class=\"cn-banner-widget__title\">\n\t\t\t\t{{ displayTitle }}\n\t\t\t</h3>\n\t\t\t<p v-if=\"displayReason\" class=\"cn-banner-widget__reason\">\n\t\t\t\t{{ displayReason }}\n\t\t\t</p>\n\t\t</div>\n\t\t<div v-if=\"resolvedActions.length > 0\" class=\"cn-banner-widget__actions\">\n\t\t\t<component\n\t\t\t\t:is=\"action.href ? 'a' : 'button'\"\n\t\t\t\tv-for=\"action in resolvedActions\"\n\t\t\t\t:key=\"action.key\"\n\t\t\t\tclass=\"cn-banner-widget__action\"\n\t\t\t\t:class=\"action.primary ? 'cn-banner-widget__action--primary' : 'cn-banner-widget__action--secondary'\"\n\t\t\t\t:href=\"action.href || null\"\n\t\t\t\t:type=\"action.href ? null : 'button'\"\n\t\t\t\tdata-testid=\"cn-banner-widget-action\"\n\t\t\t\t@click=\"onActionClick($event, action)\">\n\t\t\t\t{{ action.label }}\n\t\t\t</component>\n\t\t</div>\n\t</section>\n\t<!-- The count could not be read. One quiet line, not the card and not an\n\t alarm: hidden would read as \"nothing to report\", which is not known. -->\n\t<p\n\t\tv-else-if=\"checkFailed\"\n\t\tclass=\"cn-banner-widget cn-banner-widget--unchecked\"\n\t\t:title=\"failureTooltip\"\n\t\tdata-testid=\"cn-banner-widget-unchecked\">\n\t\t<span class=\"cn-banner-widget__unchecked-text\">{{ uncheckedText }}</span>\n\t\t<span class=\"cn-banner-widget__unchecked-reason\">{{ failureTooltip }}</span>\n\t</p>\n\t<div v-else-if=\"visible\" class=\"cn-banner-widget\">\n\t\t<NcNoteCard :type=\"resolvedVariant\" class=\"cn-banner-widget__card\">\n\t\t\t<component\n\t\t\t\t:is=\"clickable ? 'a' : 'span'\"\n\t\t\t\tclass=\"cn-banner-widget__text\"\n\t\t\t\t:class=\"{ 'cn-banner-widget__text--clickable': clickable }\"\n\t\t\t\t:href=\"clickable ? routeHref : null\"\n\t\t\t\tdata-testid=\"cn-banner-widget-text\"\n\t\t\t\t@click=\"onClick\">\n\t\t\t\t{{ displayText }}\n\t\t\t</component>\n\t\t</NcNoteCard>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcNoteCard } from '@nextcloud/vue'\nimport { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'\nimport { safeHref } from '../../utils/safeHref.js'\nimport { nextUid } from '../../utils/uid.js'\nimport { compareVisibleWhen, readVisibleWhenValue } from '../../utils/visibleWhen.js'\n\n/** Variants understood by NcNoteCard. */\nconst VARIANTS = ['info', 'warning', 'error', 'success']\n\n/** Layouts: the note card (default) or the attention card. */\nconst LAYOUTS = ['banner', 'attention']\n\n/** An attention card offers a primary and a secondary action, no more. */\nconst MAX_ACTIONS = 2\n\n/**\n * CnBannerWidget — declarative notice banner for dashboards and v2 pages\n * (`banner` widget type, Wave 1 of nextcloud-vue#91).\n *\n * Renders a themed NcNoteCard with a variant (`info | warning | error`),\n * a text, an optional click-through route, and an optional `visibleWhen`\n * condition: a simple `{ field, op, value }` predicate evaluated against\n * an endpoint response or an OpenRegister source (the doriath\n * migration-banner case — \"show while `pending > 0`\").\n *\n * Config arrives either as flat props (v2 grid spreads `props`) or as a\n * stored `content` blob (CnDashboardPage's registry branch) — explicit\n * flat props win on collision.\n *\n * ```json\n * {\n * \"widgetKey\": \"banner\",\n * \"props\": {\n * \"variant\": \"warning\",\n * \"text\": \"Migrations pending — open the migration overview.\",\n * \"visibleWhen\": { \"endpoint\": \"/apps/doriath/api/migrations/status\", \"field\": \"pending\", \"op\": \"gt\", \"value\": 0 },\n * \"route\": \"migrations\"\n * }\n * }\n * ```\n *\n * Fail-safe: with a `visibleWhen`, the banner stays HIDDEN until the\n * condition evaluates true — a failed fetch never breaks (or spams) a\n * dashboard.\n *\n * An attention card (`layout: \"attention\"`) whose `visibleWhen` request\n * FAILS renders one quiet line instead, \"Could not check\", with the reason as\n * a tooltip. A hidden card reads as \"nothing needs attention\", and a failed\n * count does not know that. A request that succeeds with a value that does\n * not meet the condition still renders nothing.\n *\n * `layout: \"attention\"` turns the banner into an attention card: a card with\n * a coloured edge by severity, a `kicker` label, a `title`, a `reason` line\n * and up to two `actions` (the first is the primary one unless an action says\n * `primary` itself). `visibleWhen` works the same way. Without `layout` the\n * note card above renders, as it always did.\n *\n * ```json\n * {\n * \"widgetKey\": \"banner\",\n * \"props\": {\n * \"layout\": \"attention\",\n * \"variant\": \"error\",\n * \"kicker\": \"First today\",\n * \"title\": \"Parking permits city centre\",\n * \"reason\": \"The deadline ends today.\",\n * \"actions\": [\n * { \"label\": \"Open case\", \"route\": { \"name\": \"CaseDetail\", \"params\": { \"id\": \"2026-0061\" } } },\n * { \"label\": \"Suspend deadline\", \"route\": \"CaseSuspend\" }\n * ]\n * }\n * }\n * ```\n */\nexport default {\n\tname: 'CnBannerWidget',\n\n\tcomponents: { NcNoteCard },\n\n\tprops: {\n\t\t/**\n\t\t * Banner severity variant: `info | warning | error` (plus `success`\n\t\t * for completeness — NcNoteCard's set). Empty falls back to the\n\t\t * `content` blob, then `info`.\n\t\t */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t\tvalidator: (v) => v === '' || VARIANTS.includes(v),\n\t\t},\n\n\t\t/** Pre-translated banner text. Empty falls back to the `content` blob. */\n\t\ttext: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional visibility condition. Shape:\n\t\t * `{ endpoint?, source?, field?, op?, value }` — exactly one of\n\t\t * `endpoint` (a same-origin URL returning JSON) or `source`\n\t\t * (`{ register, schema, filter? }`, an OpenRegister object query whose\n\t\t * `filter` supports the shared @-token grammar). `field` is a\n\t\t * dot-path into the response (for a `source`, into the first result;\n\t\t * omit it — or use `@total` — to compare the collection total).\n\t\t * `op` is `eq | neq | gt | gte | lt | lte` (default `eq`); `value` is\n\t\t * the literal right-hand side. `null` (the default) shows the banner\n\t\t * unconditionally.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tvisibleWhen: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Optional click-through route: a vue-router route NAME (string) or\n\t\t * a full location object. When set, the banner text is a link to that\n\t\t * route. `null` renders static text.\n\t\t *\n\t\t * @type {string|object|null}\n\t\t */\n\t\troute: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * How the banner renders: `banner` (the note card, default) or\n\t\t * `attention` (a card with a severity edge, kicker, title, reason and\n\t\t * up to two actions). Empty falls back to the `content` blob, then\n\t\t * `banner`.\n\t\t *\n\t\t * @type {''|'banner'|'attention'}\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t\tvalidator: (v) => v === '' || LAYOUTS.includes(v),\n\t\t},\n\n\t\t/** Attention card: the small label above the title (\"First today\"). */\n\t\tkicker: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Attention card: the title. Falls back to `text` when empty. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Attention card: the line under the title that says why this needs attention. `{value}` is replaced like in `text`. */\n\t\treason: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Attention card: at most two actions. Each is `{ label, route?,\n\t\t * href?, primary?, id? }`: `route` is a route name or location,\n\t\t * `href` an URL. An action with neither renders a button that emits\n\t\t * `action`. The first action is the primary one unless one sets\n\t\t * `primary: true`.\n\t\t *\n\t\t * @type {Array<{label: string, route?: (string|object), href?: string, primary?: boolean, id?: string}>|null}\n\t\t */\n\t\tactions: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Stored content blob (CnDashboardPage registry branch) carrying the\n\t\t * same keys as the flat props: `{ variant, text, visibleWhen, route,\n\t\t * layout, kicker, title, reason, actions }`. Explicit flat props win\n\t\t * on collision.\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 * Pre-evaluated `visibleWhen` outcome `{ met, value }`, injected by a\n\t\t * host that already ran the predicate (CnDashboardPage evaluates it\n\t\t * to know whether to collapse the banner's grid cell). When present,\n\t\t * the banner renders from this verdict instead of fetching again —\n\t\t * no duplicate request and no hidden-until-self-evaluated flash —\n\t\t * and `value` feeds the `{value}` text placeholder. `null` (the\n\t\t * default) keeps the banner self-evaluating. A host whose request\n\t\t * failed passes `{ met: false, value: null, failed: true, reason }`,\n\t\t * and an attention card then says it could not check.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tconditionOutcome: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted when an attention-card action without a `route` or `href`\n\t\t * is clicked, so the host can run it.\n\t\t *\n\t\t * @event action\n\t\t * @type {{id: (string|undefined), label: string, index: number}}\n\t\t */\n\t\t'action',\n\t],\n\n\t/**\n\t * The banner's own state: the evaluated condition, the value it read,\n\t * and whether the request for it failed.\n\t *\n\t * @return {object} The state.\n\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t */\n\tdata() {\n\t\treturn {\n\t\t\t/** Id that ties the attention card's region to its title. */\n\t\t\theadingId: `cn-banner-title-${nextUid()}`,\n\t\t\t/** Evaluated visibleWhen outcome (null = not yet evaluated). */\n\t\t\tconditionMet: null,\n\t\t\t/**\n\t\t\t * The raw field value the predicate read (from the injected\n\t\t\t * outcome or the banner's own evaluation) — interpolated into\n\t\t\t * the text wherever it says `{value}`.\n\t\t\t */\n\t\t\tconditionValue: null,\n\t\t\t/** Whether the visibleWhen request itself failed (not: answered \"no\"). */\n\t\t\tconditionFailed: false,\n\t\t\t/** Why it failed, as the request reported it. */\n\t\t\tconditionFailureReason: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The effective variant (prop → content → 'info'). */\n\t\tresolvedVariant() {\n\t\t\tconst v = this.variant || (this.content && this.content.variant) || 'info'\n\t\t\treturn VARIANTS.includes(v) ? v : 'info'\n\t\t},\n\n\t\t/** The effective banner text (prop → content → ''). */\n\t\tresolvedText() {\n\t\t\treturn this.text || (this.content && this.content.text) || ''\n\t\t},\n\n\t\t/** The effective visibleWhen condition (prop → content → null). */\n\t\tresolvedVisibleWhen() {\n\t\t\treturn this.visibleWhen || (this.content && this.content.visibleWhen) || null\n\t\t},\n\n\t\t/** The effective injected outcome (prop → content → null). */\n\t\tresolvedConditionOutcome() {\n\t\t\treturn this.conditionOutcome || (this.content && this.content.conditionOutcome) || null\n\t\t},\n\n\t\t/**\n\t\t * The rendered text: `resolvedText` with `{value}` replaced by the\n\t\t * predicate's field value once one is known. Without a known value\n\t\t * the text renders as written — a `{value}` placeholder only makes\n\t\t * sense on a conditional banner, which stays hidden until its\n\t\t * predicate (and therefore its value) resolves.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tdisplayText() {\n\t\t\treturn this.fillValue(this.resolvedText)\n\t\t},\n\n\t\t/** The effective click-through route (prop → content → null). */\n\t\tresolvedRoute() {\n\t\t\treturn this.route || (this.content && this.content.route) || null\n\t\t},\n\n\t\t/**\n\t\t * Whether the attention card renders instead of the note card.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tisAttention() {\n\t\t\treturn (this.layout || (this.content && this.content.layout) || 'banner') === 'attention'\n\t\t},\n\n\t\t/**\n\t\t * The attention card's kicker (prop, else content).\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tresolvedKicker() {\n\t\t\tconst kicker = this.kicker || (this.content && this.content.kicker) || ''\n\t\t\treturn kicker\n\t\t},\n\n\t\t/**\n\t\t * The attention card's title: `title`, else `text`, with `{value}` filled in.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tdisplayTitle() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\treturn title ? this.fillValue(title) : this.displayText\n\t\t},\n\n\t\t/**\n\t\t * The attention card's reason line. When the title already used\n\t\t * `text`, the text is not repeated here.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tdisplayReason() {\n\t\t\tconst reason = this.reason || (this.content && this.content.reason) || ''\n\t\t\tif (reason) {\n\t\t\t\treturn this.fillValue(reason)\n\t\t\t}\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\t// A card that sets `text` to the same words as its `title` says\n\t\t\t// them once.\n\t\t\treturn title && this.displayText !== this.displayTitle ? this.displayText : ''\n\t\t},\n\n\t\t/**\n\t\t * The attention card's actions: the first two that have a label, each\n\t\t * with its href and whether it is the primary one.\n\t\t *\n\t\t * @return {Array<{key: string, id: (string|undefined), label: string, href: string, target: (object|null), primary: boolean, index: number}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tresolvedActions() {\n\t\t\tconst raw = this.actions || (this.content && this.content.actions) || []\n\t\t\tif (!Array.isArray(raw)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst usable = raw\n\t\t\t\t.filter((action) => action && typeof action === 'object' && typeof action.label === 'string' && action.label !== '')\n\t\t\t\t.slice(0, MAX_ACTIONS)\n\t\t\tconst declaredPrimary = usable.findIndex((action) => action.primary === true)\n\t\t\tconst primaryIndex = declaredPrimary === -1 ? 0 : declaredPrimary\n\t\t\treturn usable.map((action, index) => {\n\t\t\t\tlet target = null\n\t\t\t\tlet href = ''\n\t\t\t\tif (action.route) {\n\t\t\t\t\ttarget = typeof action.route === 'string' ? { name: action.route } : action.route\n\t\t\t\t\thref = resolveHref(target, this.$router)\n\t\t\t\t} else if (typeof action.href === 'string' && action.href !== '') {\n\t\t\t\t\tconst safe = safeHref(action.href)\n\t\t\t\t\thref = safe === '#' ? '' : safe\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tkey: `${index}-${action.id || action.label}`,\n\t\t\t\t\tid: action.id,\n\t\t\t\t\tlabel: action.label,\n\t\t\t\t\thref,\n\t\t\t\t\ttarget,\n\t\t\t\t\tprimary: index === primaryIndex,\n\t\t\t\t\tindex,\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether the banner renders: no condition = always; else the evaluated outcome.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tvisible() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\tif (this.resolvedText === '' && !(this.isAttention && title !== '')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (!this.resolvedVisibleWhen) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.conditionMet === true\n\t\t},\n\n\t\t/**\n\t\t * Whether to say the check failed: an attention card, with words to\n\t\t * show, whose `visibleWhen` request failed. A plain banner stays\n\t\t * hidden on failure, as it always did.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tcheckFailed() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\treturn this.isAttention\n\t\t\t\t&& !!this.resolvedVisibleWhen\n\t\t\t\t&& this.conditionFailed\n\t\t\t\t&& (title !== '' || this.resolvedText !== '')\n\t\t},\n\n\t\t/**\n\t\t * The one line shown when the check failed. It names the card by its\n\t\t * title, unless the title needs the value that could not be read.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tuncheckedText() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || this.resolvedText\n\t\t\tif (!title || title.includes('{value}')) {\n\t\t\t\treturn t('nextcloud-vue', 'Could not check')\n\t\t\t}\n\t\t\t// `escape: false`: the result is rendered through `{{ }}`, which escapes.\n\t\t\treturn t('nextcloud-vue', 'Could not check: {subject}', { subject: title }, undefined, { escape: false })\n\t\t},\n\n\t\t/**\n\t\t * The reason the check failed, for the tooltip and for assistive\n\t\t * technology.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tfailureTooltip() {\n\t\t\tif (!this.conditionFailureReason) {\n\t\t\t\treturn t('nextcloud-vue', 'The check failed.')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'The check failed: {reason}', { reason: this.conditionFailureReason }, undefined, { escape: false })\n\t\t},\n\n\t\t/** Whether the banner navigates on click (route set + router present). */\n\t\tclickable() {\n\t\t\treturn !!this.resolvedRoute && !!this.$router\n\t\t},\n\n\t\t/** The click-through route as a router location (a string is a route name). */\n\t\trouteLocation() {\n\t\t\tconst route = this.resolvedRoute\n\t\t\treturn typeof route === 'string' ? { name: route } : route\n\t\t},\n\n\t\t/** The href of the click-through link. */\n\t\trouteHref() {\n\t\t\treturn resolveHref(this.routeLocation, this.$router)\n\t\t},\n\t},\n\n\twatch: {\n\t\tresolvedVisibleWhen: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.evaluateCondition()\n\t\t\t},\n\t\t},\n\n\t\tresolvedConditionOutcome() {\n\t\t\tthis.evaluateCondition()\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Replace `{value}` in a text by the value the predicate read, once\n\t\t * one is known.\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {string} The text with the value filled in.\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tfillValue(text) {\n\t\t\tif (this.conditionValue === null || !text.includes('{value}')) {\n\t\t\t\treturn text\n\t\t\t}\n\t\t\treturn text.replaceAll('{value}', String(this.conditionValue))\n\t\t},\n\n\t\t/**\n\t\t * Run an attention-card action: route an in-app link, leave an\n\t\t * external link to the browser, and emit `action` for a button.\n\t\t *\n\t\t * @param {MouseEvent} event The click.\n\t\t * @param {object} action The resolved action.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tonActionClick(event, action) {\n\t\t\tif (action.target) {\n\t\t\t\tfollowLinkClick(event, action.target, this.$router)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!action.href) {\n\t\t\t\tthis.$emit('action', { id: action.id, label: action.label, index: action.index })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Resolve the `visibleWhen` verdict. A host-injected\n\t\t * `conditionOutcome` wins outright — the host already ran the\n\t\t * predicate, so re-fetching would double the request and flash the\n\t\t * banner hidden until its own copy resolved. Otherwise evaluate\n\t\t * through the shared util primitives (endpoint / OpenRegister-source\n\t\t * modes — the Wave-1 banner shape is the canonical one, extracted to\n\t\t * `utils/visibleWhen.js` in Wave 3 so manifest actions reuse it),\n\t\t * keeping the read VALUE for the `{value}` text placeholder.\n\t\t * Fail-safe: any fetch/shape error leaves the banner hidden. The\n\t\t * failure is kept (`conditionFailed`, with its reason) so an\n\t\t * attention card can say it could not check.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tasync evaluateCondition() {\n\t\t\tconst outcome = this.resolvedConditionOutcome\n\t\t\tif (outcome) {\n\t\t\t\tthis.conditionMet = outcome.met === true\n\t\t\t\tthis.conditionValue = outcome.value !== undefined ? outcome.value : null\n\t\t\t\tthis.conditionFailed = outcome.failed === true\n\t\t\t\tthis.conditionFailureReason = outcome.failed === true && typeof outcome.reason === 'string' ? outcome.reason : ''\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst cond = this.resolvedVisibleWhen\n\t\t\tif (!cond) {\n\t\t\t\tthis.conditionMet = null\n\t\t\t\tthis.conditionValue = null\n\t\t\t\tthis.conditionFailed = false\n\t\t\t\tthis.conditionFailureReason = ''\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst value = await readVisibleWhenValue(cond)\n\t\t\t\tthis.conditionValue = value\n\t\t\t\tthis.conditionMet = compareVisibleWhen(value, cond.op || 'eq', cond.value)\n\t\t\t\tthis.conditionFailed = false\n\t\t\t\tthis.conditionFailureReason = ''\n\t\t\t} catch (error) {\n\t\t\t\t// Still hidden: a banner that cannot tell whether it applies\n\t\t\t\t// should not claim attention. But not silently, because a\n\t\t\t\t// failed count and \"nothing to report\" look the same on screen.\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn('[CnBannerWidget] visibleWhen could not be evaluated, banner hidden:', error?.message || error)\n\t\t\t\tthis.conditionMet = false\n\t\t\t\tthis.conditionValue = null\n\t\t\t\tthis.conditionFailed = true\n\t\t\t\tthis.conditionFailureReason = typeof error?.message === 'string' ? error.message : String(error ?? '')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Route a plain click on the link; a modified click opens the href\n\t\t * in the browser.\n\t\t *\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonClick(event) {\n\t\t\tif (!this.clickable) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfollowLinkClick(event, this.routeLocation, this.$router)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-banner-widget {\n\twidth: 100%;\n}\n\n.cn-banner-widget__card {\n\tmargin: 0;\n}\n\n.cn-banner-widget__text--clickable {\n\tcursor: pointer;\n\ttext-decoration: underline;\n\tcolor: inherit;\n}\n\n/* The check failed. Muted text, no fill, no severity colour: it is a note\n that something is not known, not a warning. */\n.cn-banner-widget--unchecked {\n\tbox-sizing: border-box;\n\tmargin: 0;\n\tpadding: 8px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tcursor: help;\n}\n\n/* The reason, for assistive technology; sighted readers get the tooltip. */\n.cn-banner-widget__unchecked-reason {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tmargin: -1px;\n\tpadding: 0;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\twhite-space: nowrap;\n\tborder: 0;\n}\n\n/* Attention card. The severity edge is an inset shadow, so it takes no width. */\n.cn-banner-widget--attention {\n\t--cn-banner-accent: var(--color-primary-element);\n\t--cn-banner-accent-text: var(--color-primary-element);\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 16px 24px;\n\tbox-sizing: border-box;\n\tpadding: 20px 24px 20px 28px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-container, var(--border-radius-large, 12px));\n\tbackground: var(--color-main-background);\n\tbox-shadow: inset 4px 0 0 0 var(--cn-banner-accent);\n}\n\n.cn-banner-widget--attention.cn-banner-widget--warning {\n\t--cn-banner-accent: var(--color-element-warning, var(--color-warning));\n\t--cn-banner-accent-text: var(--color-text-warning, var(--color-warning-text, var(--color-main-text)));\n}\n\n.cn-banner-widget--attention.cn-banner-widget--error {\n\t--cn-banner-accent: var(--color-element-error, var(--color-error));\n\t--cn-banner-accent-text: var(--color-text-error, var(--color-error-text));\n}\n\n.cn-banner-widget--attention.cn-banner-widget--success {\n\t--cn-banner-accent: var(--color-element-success, var(--color-success));\n\t--cn-banner-accent-text: var(--color-text-success, var(--color-success-text));\n}\n\n.cn-banner-widget__body {\n\tdisplay: flex;\n\tflex: 1 1 320px;\n\tflex-direction: column;\n\tgap: 6px;\n\tmin-width: 0;\n}\n\n.cn-banner-widget__kicker {\n\tmargin: 0;\n\tfont-size: 0.85em;\n\tfont-weight: 700;\n\tletter-spacing: 0.06em;\n\ttext-transform: uppercase;\n\tcolor: var(--cn-banner-accent-text);\n}\n\n.cn-banner-widget__title {\n\tmargin: 0;\n\tfont-size: 1.3em;\n\tfont-weight: 700;\n\tline-height: 1.3;\n\tcolor: var(--color-main-text);\n}\n\n.cn-banner-widget__reason {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-banner-widget__actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 10px;\n}\n\n.cn-banner-widget__action {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tbox-sizing: border-box;\n\tmin-height: var(--default-clickable-area, 40px);\n\tmargin: 0;\n\tpadding: 0 16px;\n\tborder: 1px solid var(--color-border-maxcontrast);\n\tborder-radius: var(--border-radius-element, var(--border-radius-large, 8px));\n\tfont: inherit;\n\tfont-weight: 600;\n\ttext-decoration: none;\n\tcursor: pointer;\n}\n\n.cn-banner-widget__action--secondary {\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-banner-widget__action--secondary:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-banner-widget__action--primary {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n}\n\n.cn-banner-widget__action--primary:hover {\n\tborder-color: var(--color-primary-element-hover);\n\tbackground: var(--color-primary-element-hover);\n}\n\n.cn-banner-widget__action:focus-visible {\n\toutline: 2px solid var(--color-main-text);\n\toutline-offset: 2px;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnBannerWidget.vue2.js","sources":["../../../../src/components/CnBannerWidget/CnBannerWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<section\n\t\tv-if=\"visible && isAttention\"\n\t\tclass=\"cn-banner-widget cn-banner-widget--attention\"\n\t\t:class=\"'cn-banner-widget--' + resolvedVariant\"\n\t\t:aria-labelledby=\"headingId\"\n\t\tdata-testid=\"cn-banner-widget-attention\">\n\t\t<div class=\"cn-banner-widget__body\">\n\t\t\t<p v-if=\"resolvedKicker\" class=\"cn-banner-widget__kicker\">\n\t\t\t\t{{ resolvedKicker }}\n\t\t\t</p>\n\t\t\t<h3 :id=\"headingId\" class=\"cn-banner-widget__title\">\n\t\t\t\t{{ displayTitle }}\n\t\t\t</h3>\n\t\t\t<p v-if=\"displayReason\" class=\"cn-banner-widget__reason\">\n\t\t\t\t{{ displayReason }}\n\t\t\t</p>\n\t\t</div>\n\t\t<div v-if=\"resolvedActions.length > 0\" class=\"cn-banner-widget__actions\">\n\t\t\t<component\n\t\t\t\t:is=\"action.href ? 'a' : 'button'\"\n\t\t\t\tv-for=\"action in resolvedActions\"\n\t\t\t\t:key=\"action.key\"\n\t\t\t\tclass=\"cn-banner-widget__action\"\n\t\t\t\t:class=\"action.primary ? 'cn-banner-widget__action--primary' : 'cn-banner-widget__action--secondary'\"\n\t\t\t\t:href=\"action.href || null\"\n\t\t\t\t:type=\"action.href ? null : 'button'\"\n\t\t\t\tdata-testid=\"cn-banner-widget-action\"\n\t\t\t\t@click=\"onActionClick($event, action)\">\n\t\t\t\t{{ action.label }}\n\t\t\t</component>\n\t\t</div>\n\t</section>\n\t<!-- The count could not be read. One quiet line, not the card and not an\n\t alarm: hidden would read as \"nothing to report\", which is not known. -->\n\t<p\n\t\tv-else-if=\"checkFailed\"\n\t\tclass=\"cn-banner-widget cn-banner-widget--unchecked\"\n\t\t:title=\"failureTooltip\"\n\t\tdata-testid=\"cn-banner-widget-unchecked\">\n\t\t<span class=\"cn-banner-widget__unchecked-text\">{{ uncheckedText }}</span>\n\t\t<span class=\"cn-banner-widget__unchecked-reason\">{{ failureTooltip }}</span>\n\t</p>\n\t<div v-else-if=\"visible\" class=\"cn-banner-widget\">\n\t\t<NcNoteCard :type=\"resolvedVariant\" class=\"cn-banner-widget__card\">\n\t\t\t<component\n\t\t\t\t:is=\"clickable ? 'a' : 'span'\"\n\t\t\t\tclass=\"cn-banner-widget__text\"\n\t\t\t\t:class=\"{ 'cn-banner-widget__text--clickable': clickable }\"\n\t\t\t\t:href=\"clickable ? routeHref : null\"\n\t\t\t\tdata-testid=\"cn-banner-widget-text\"\n\t\t\t\t@click=\"onClick\">\n\t\t\t\t{{ displayText }}\n\t\t\t</component>\n\t\t</NcNoteCard>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcNoteCard } from '@nextcloud/vue'\nimport { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'\nimport { safeHref } from '../../utils/safeHref.js'\nimport { nextUid } from '../../utils/uid.js'\nimport { compareVisibleWhen, readVisibleWhenValue } from '../../utils/visibleWhen.js'\n\n/** Variants understood by NcNoteCard. */\nconst VARIANTS = ['info', 'warning', 'error', 'success']\n\n/** Layouts: the note card (default) or the attention card. */\nconst LAYOUTS = ['banner', 'attention']\n\n/** An attention card offers a primary and a secondary action, no more. */\nconst MAX_ACTIONS = 2\n\n/**\n * CnBannerWidget — declarative notice banner for dashboards and v2 pages\n * (`banner` widget type, Wave 1 of nextcloud-vue#91).\n *\n * Renders a themed NcNoteCard with a variant (`info | warning | error`),\n * a text, an optional click-through route, and an optional `visibleWhen`\n * condition: a simple `{ field, op, value }` predicate evaluated against\n * an endpoint response or an OpenRegister source (the doriath\n * migration-banner case — \"show while `pending > 0`\").\n *\n * Config arrives either as flat props (v2 grid spreads `props`) or as a\n * stored `content` blob (CnDashboardPage's registry branch) — explicit\n * flat props win on collision.\n *\n * ```json\n * {\n * \"widgetKey\": \"banner\",\n * \"props\": {\n * \"variant\": \"warning\",\n * \"text\": \"Migrations pending — open the migration overview.\",\n * \"visibleWhen\": { \"endpoint\": \"/apps/doriath/api/migrations/status\", \"field\": \"pending\", \"op\": \"gt\", \"value\": 0 },\n * \"route\": \"migrations\"\n * }\n * }\n * ```\n *\n * Fail-safe: with a `visibleWhen`, the banner stays HIDDEN until the\n * condition evaluates true — a failed fetch never breaks (or spams) a\n * dashboard.\n *\n * An attention card (`layout: \"attention\"`) whose `visibleWhen` request\n * FAILS renders one quiet line instead, \"Could not check\", with the reason as\n * a tooltip. A hidden card reads as \"nothing needs attention\", and a failed\n * count does not know that. A request that succeeds with a value that does\n * not meet the condition still renders nothing.\n *\n * `layout: \"attention\"` turns the banner into an attention card: a card with\n * a coloured edge by severity, a `kicker` label, a `title`, a `reason` line\n * and up to two `actions` (the first is the primary one unless an action says\n * `primary` itself). `visibleWhen` works the same way. Without `layout` the\n * note card above renders, as it always did.\n *\n * ```json\n * {\n * \"widgetKey\": \"banner\",\n * \"props\": {\n * \"layout\": \"attention\",\n * \"variant\": \"error\",\n * \"kicker\": \"First today\",\n * \"title\": \"Parking permits city centre\",\n * \"reason\": \"The deadline ends today.\",\n * \"actions\": [\n * { \"label\": \"Open case\", \"route\": { \"name\": \"CaseDetail\", \"params\": { \"id\": \"2026-0061\" } } },\n * { \"label\": \"Suspend deadline\", \"route\": \"CaseSuspend\" }\n * ]\n * }\n * }\n * ```\n */\nexport default {\n\tname: 'CnBannerWidget',\n\n\tcomponents: { NcNoteCard },\n\n\tinject: {\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Banner severity variant: `info | warning | error` (plus `success`\n\t\t * for completeness — NcNoteCard's set). Empty falls back to the\n\t\t * `content` blob, then `info`.\n\t\t */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t\tvalidator: (v) => v === '' || VARIANTS.includes(v),\n\t\t},\n\n\t\t/** Pre-translated banner text. Empty falls back to the `content` blob. */\n\t\ttext: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional visibility condition. Shape:\n\t\t * `{ endpoint?, source?, field?, op?, value }` — exactly one of\n\t\t * `endpoint` (a same-origin URL returning JSON) or `source`\n\t\t * (`{ register, schema, filter? }`, an OpenRegister object query whose\n\t\t * `filter` supports the shared @-token grammar). `field` is a\n\t\t * dot-path into the response (for a `source`, into the first result;\n\t\t * omit it — or use `@total` — to compare the collection total).\n\t\t * `op` is `eq | neq | gt | gte | lt | lte` (default `eq`); `value` is\n\t\t * the literal right-hand side. `null` (the default) shows the banner\n\t\t * unconditionally.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tvisibleWhen: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Optional click-through route: a vue-router route NAME (string) or\n\t\t * a full location object. When set, the banner text is a link to that\n\t\t * route. `null` renders static text.\n\t\t *\n\t\t * @type {string|object|null}\n\t\t */\n\t\troute: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * How the banner renders: `banner` (the note card, default) or\n\t\t * `attention` (a card with a severity edge, kicker, title, reason and\n\t\t * up to two actions). Empty falls back to the `content` blob, then\n\t\t * `banner`.\n\t\t *\n\t\t * @type {''|'banner'|'attention'}\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t\tvalidator: (v) => v === '' || LAYOUTS.includes(v),\n\t\t},\n\n\t\t/** Attention card: the small label above the title (\"First today\"). */\n\t\tkicker: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Attention card: the title. Falls back to `text` when empty. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Attention card: the line under the title that says why this needs attention. `{value}` is replaced like in `text`. */\n\t\treason: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Attention card: at most two actions. Each is `{ label, route?,\n\t\t * href?, primary?, id? }`: `route` is a route name or location,\n\t\t * `href` an URL. An action with neither renders a button that emits\n\t\t * `action`. The first action is the primary one unless one sets\n\t\t * `primary: true`.\n\t\t *\n\t\t * @type {Array<{label: string, route?: (string|object), href?: string, primary?: boolean, id?: string}>|null}\n\t\t */\n\t\tactions: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Stored content blob (CnDashboardPage registry branch) carrying the\n\t\t * same keys as the flat props: `{ variant, text, visibleWhen, route,\n\t\t * layout, kicker, title, reason, actions }`. Explicit flat props win\n\t\t * on collision.\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 * Pre-evaluated `visibleWhen` outcome `{ met, value }`, injected by a\n\t\t * host that already ran the predicate (CnDashboardPage evaluates it\n\t\t * to know whether to collapse the banner's grid cell). When present,\n\t\t * the banner renders from this verdict instead of fetching again —\n\t\t * no duplicate request and no hidden-until-self-evaluated flash —\n\t\t * and `value` feeds the `{value}` text placeholder. `null` (the\n\t\t * default) keeps the banner self-evaluating. A host whose request\n\t\t * failed passes `{ met: false, value: null, failed: true, reason }`,\n\t\t * and an attention card then says it could not check.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tconditionOutcome: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted when an attention-card action without a `route` or `href`\n\t\t * is clicked, so the host can run it.\n\t\t *\n\t\t * @event action\n\t\t * @type {{id: (string|undefined), label: string, index: number}}\n\t\t */\n\t\t'action',\n\t],\n\n\t/**\n\t * The banner's own state: the evaluated condition, the value it read,\n\t * and whether the request for it failed.\n\t *\n\t * @return {object} The state.\n\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t */\n\tdata() {\n\t\treturn {\n\t\t\t/** Id that ties the attention card's region to its title. */\n\t\t\theadingId: `cn-banner-title-${nextUid()}`,\n\t\t\t/** Evaluated visibleWhen outcome (null = not yet evaluated). */\n\t\t\tconditionMet: null,\n\t\t\t/**\n\t\t\t * The raw field value the predicate read (from the injected\n\t\t\t * outcome or the banner's own evaluation) — interpolated into\n\t\t\t * the text wherever it says `{value}`.\n\t\t\t */\n\t\t\tconditionValue: null,\n\t\t\t/** Whether the visibleWhen request itself failed (not: answered \"no\"). */\n\t\t\tconditionFailed: false,\n\t\t\t/** Why it failed, as the request reported it. */\n\t\t\tconditionFailureReason: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The effective variant (prop → content → 'info'). */\n\t\tresolvedVariant() {\n\t\t\tconst v = this.variant || (this.content && this.content.variant) || 'info'\n\t\t\treturn VARIANTS.includes(v) ? v : 'info'\n\t\t},\n\n\t\t/** The effective banner text (prop → content → ''). */\n\t\tresolvedText() {\n\t\t\treturn this.text || (this.content && this.content.text) || ''\n\t\t},\n\n\t\t/** The effective visibleWhen condition (prop → content → null). */\n\t\tresolvedVisibleWhen() {\n\t\t\treturn this.visibleWhen || (this.content && this.content.visibleWhen) || null\n\t\t},\n\n\t\t/** The effective injected outcome (prop → content → null). */\n\t\tresolvedConditionOutcome() {\n\t\t\treturn this.conditionOutcome || (this.content && this.content.conditionOutcome) || null\n\t\t},\n\n\t\t/**\n\t\t * The rendered text: `resolvedText` with `{value}` replaced by the\n\t\t * predicate's field value once one is known. Without a known value\n\t\t * the text renders as written — a `{value}` placeholder only makes\n\t\t * sense on a conditional banner, which stays hidden until its\n\t\t * predicate (and therefore its value) resolves.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tdisplayText() {\n\t\t\treturn this.fillValue(this.resolvedText)\n\t\t},\n\n\t\t/** The effective click-through route (prop → content → null). */\n\t\tresolvedRoute() {\n\t\t\treturn this.route || (this.content && this.content.route) || null\n\t\t},\n\n\t\t/**\n\t\t * Whether the attention card renders instead of the note card.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tisAttention() {\n\t\t\treturn (this.layout || (this.content && this.content.layout) || 'banner') === 'attention'\n\t\t},\n\n\t\t/**\n\t\t * The attention card's kicker (prop, else content).\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tresolvedKicker() {\n\t\t\tconst kicker = this.kicker || (this.content && this.content.kicker) || ''\n\t\t\treturn kicker ? this.tl(kicker) : ''\n\t\t},\n\n\t\t/**\n\t\t * The attention card's title: `title`, else `text`, with `{value}` filled in.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tdisplayTitle() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\treturn title ? this.fillValue(this.tl(title)) : this.displayText\n\t\t},\n\n\t\t/**\n\t\t * The attention card's reason line. When the title already used\n\t\t * `text`, the text is not repeated here.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tdisplayReason() {\n\t\t\tconst reason = this.reason || (this.content && this.content.reason) || ''\n\t\t\tif (reason) {\n\t\t\t\treturn this.fillValue(this.tl(reason))\n\t\t\t}\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\t// A card that sets `text` to the same words as its `title` says\n\t\t\t// them once.\n\t\t\treturn title && this.displayText !== this.displayTitle ? this.displayText : ''\n\t\t},\n\n\t\t/**\n\t\t * The attention card's actions: the first two that have a label, each\n\t\t * with its href and whether it is the primary one.\n\t\t *\n\t\t * @return {Array<{key: string, id: (string|undefined), label: string, href: string, target: (object|null), primary: boolean, index: number}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tresolvedActions() {\n\t\t\tconst raw = this.actions || (this.content && this.content.actions) || []\n\t\t\tif (!Array.isArray(raw)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst usable = raw\n\t\t\t\t.filter((action) => action && typeof action === 'object' && typeof action.label === 'string' && action.label !== '')\n\t\t\t\t.slice(0, MAX_ACTIONS)\n\t\t\tconst declaredPrimary = usable.findIndex((action) => action.primary === true)\n\t\t\tconst primaryIndex = declaredPrimary === -1 ? 0 : declaredPrimary\n\t\t\treturn usable.map((action, index) => {\n\t\t\t\tlet target = null\n\t\t\t\tlet href = ''\n\t\t\t\tif (action.route) {\n\t\t\t\t\ttarget = typeof action.route === 'string' ? { name: action.route } : action.route\n\t\t\t\t\thref = resolveHref(target, this.$router)\n\t\t\t\t} else if (typeof action.href === 'string' && action.href !== '') {\n\t\t\t\t\tconst safe = safeHref(action.href)\n\t\t\t\t\thref = safe === '#' ? '' : safe\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tkey: `${index}-${action.id || action.label}`,\n\t\t\t\t\tid: action.id,\n\t\t\t\t\tlabel: this.tl(action.label),\n\t\t\t\t\thref,\n\t\t\t\t\ttarget,\n\t\t\t\t\tprimary: index === primaryIndex,\n\t\t\t\t\tindex,\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether the banner renders: no condition = always; else the evaluated outcome.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tvisible() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\tif (this.resolvedText === '' && !(this.isAttention && title !== '')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (!this.resolvedVisibleWhen) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.conditionMet === true\n\t\t},\n\n\t\t/**\n\t\t * Whether to say the check failed: an attention card, with words to\n\t\t * show, whose `visibleWhen` request failed. A plain banner stays\n\t\t * hidden on failure, as it always did.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tcheckFailed() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\treturn this.isAttention\n\t\t\t\t&& !!this.resolvedVisibleWhen\n\t\t\t\t&& this.conditionFailed\n\t\t\t\t&& (title !== '' || this.resolvedText !== '')\n\t\t},\n\n\t\t/**\n\t\t * The one line shown when the check failed. It names the card by its\n\t\t * title, unless the title needs the value that could not be read.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tuncheckedText() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || this.resolvedText\n\t\t\tif (!title || title.includes('{value}')) {\n\t\t\t\treturn t('nextcloud-vue', 'Could not check')\n\t\t\t}\n\t\t\t// `escape: false`: the result is rendered through `{{ }}`, which escapes.\n\t\t\treturn t('nextcloud-vue', 'Could not check: {subject}', { subject: title }, undefined, { escape: false })\n\t\t},\n\n\t\t/**\n\t\t * The reason the check failed, for the tooltip and for assistive\n\t\t * technology.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tfailureTooltip() {\n\t\t\tif (!this.conditionFailureReason) {\n\t\t\t\treturn t('nextcloud-vue', 'The check failed.')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'The check failed: {reason}', { reason: this.conditionFailureReason }, undefined, { escape: false })\n\t\t},\n\n\t\t/** Whether the banner navigates on click (route set + router present). */\n\t\tclickable() {\n\t\t\treturn !!this.resolvedRoute && !!this.$router\n\t\t},\n\n\t\t/** The click-through route as a router location (a string is a route name). */\n\t\trouteLocation() {\n\t\t\tconst route = this.resolvedRoute\n\t\t\treturn typeof route === 'string' ? { name: route } : route\n\t\t},\n\n\t\t/** The href of the click-through link. */\n\t\trouteHref() {\n\t\t\treturn resolveHref(this.routeLocation, this.$router)\n\t\t},\n\t},\n\n\twatch: {\n\t\tresolvedVisibleWhen: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.evaluateCondition()\n\t\t\t},\n\t\t},\n\n\t\tresolvedConditionOutcome() {\n\t\t\tthis.evaluateCondition()\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\t/**\n\t\t * Replace `{value}` in a text by the value the predicate read, once\n\t\t * one is known.\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {string} The text with the value filled in.\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tfillValue(text) {\n\t\t\tif (this.conditionValue === null || !text.includes('{value}')) {\n\t\t\t\treturn text\n\t\t\t}\n\t\t\treturn text.replaceAll('{value}', String(this.conditionValue))\n\t\t},\n\n\t\t/**\n\t\t * Run an attention-card action: route an in-app link, leave an\n\t\t * external link to the browser, and emit `action` for a button.\n\t\t *\n\t\t * @param {MouseEvent} event The click.\n\t\t * @param {object} action The resolved action.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tonActionClick(event, action) {\n\t\t\tif (action.target) {\n\t\t\t\tfollowLinkClick(event, action.target, this.$router)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!action.href) {\n\t\t\t\tthis.$emit('action', { id: action.id, label: action.label, index: action.index })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Resolve the `visibleWhen` verdict. A host-injected\n\t\t * `conditionOutcome` wins outright — the host already ran the\n\t\t * predicate, so re-fetching would double the request and flash the\n\t\t * banner hidden until its own copy resolved. Otherwise evaluate\n\t\t * through the shared util primitives (endpoint / OpenRegister-source\n\t\t * modes — the Wave-1 banner shape is the canonical one, extracted to\n\t\t * `utils/visibleWhen.js` in Wave 3 so manifest actions reuse it),\n\t\t * keeping the read VALUE for the `{value}` text placeholder.\n\t\t * Fail-safe: any fetch/shape error leaves the banner hidden. The\n\t\t * failure is kept (`conditionFailed`, with its reason) so an\n\t\t * attention card can say it could not check.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tasync evaluateCondition() {\n\t\t\tconst outcome = this.resolvedConditionOutcome\n\t\t\tif (outcome) {\n\t\t\t\tthis.conditionMet = outcome.met === true\n\t\t\t\tthis.conditionValue = outcome.value !== undefined ? outcome.value : null\n\t\t\t\tthis.conditionFailed = outcome.failed === true\n\t\t\t\tthis.conditionFailureReason = outcome.failed === true && typeof outcome.reason === 'string' ? outcome.reason : ''\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst cond = this.resolvedVisibleWhen\n\t\t\tif (!cond) {\n\t\t\t\tthis.conditionMet = null\n\t\t\t\tthis.conditionValue = null\n\t\t\t\tthis.conditionFailed = false\n\t\t\t\tthis.conditionFailureReason = ''\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst value = await readVisibleWhenValue(cond)\n\t\t\t\tthis.conditionValue = value\n\t\t\t\tthis.conditionMet = compareVisibleWhen(value, cond.op || 'eq', cond.value)\n\t\t\t\tthis.conditionFailed = false\n\t\t\t\tthis.conditionFailureReason = ''\n\t\t\t} catch (error) {\n\t\t\t\t// Still hidden: a banner that cannot tell whether it applies\n\t\t\t\t// should not claim attention. But not silently, because a\n\t\t\t\t// failed count and \"nothing to report\" look the same on screen.\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn('[CnBannerWidget] visibleWhen could not be evaluated, banner hidden:', error?.message || error)\n\t\t\t\tthis.conditionMet = false\n\t\t\t\tthis.conditionValue = null\n\t\t\t\tthis.conditionFailed = true\n\t\t\t\tthis.conditionFailureReason = typeof error?.message === 'string' ? error.message : String(error ?? '')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Route a plain click on the link; a modified click opens the href\n\t\t * in the browser.\n\t\t *\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonClick(event) {\n\t\t\tif (!this.clickable) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfollowLinkClick(event, this.routeLocation, this.$router)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-banner-widget {\n\twidth: 100%;\n}\n\n.cn-banner-widget__card {\n\tmargin: 0;\n}\n\n.cn-banner-widget__text--clickable {\n\tcursor: pointer;\n\ttext-decoration: underline;\n\tcolor: inherit;\n}\n\n/* The check failed. Muted text, no fill, no severity colour: it is a note\n that something is not known, not a warning. */\n.cn-banner-widget--unchecked {\n\tbox-sizing: border-box;\n\tmargin: 0;\n\tpadding: 8px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tcursor: help;\n}\n\n/* The reason, for assistive technology; sighted readers get the tooltip. */\n.cn-banner-widget__unchecked-reason {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tmargin: -1px;\n\tpadding: 0;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\twhite-space: nowrap;\n\tborder: 0;\n}\n\n/* Attention card. The severity edge is an inset shadow, so it takes no width. */\n.cn-banner-widget--attention {\n\t--cn-banner-accent: var(--color-primary-element);\n\t--cn-banner-accent-text: var(--color-primary-element);\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 16px 24px;\n\tbox-sizing: border-box;\n\tpadding: 20px 24px 20px 28px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-container, var(--border-radius-large, 12px));\n\tbackground: var(--color-main-background);\n\tbox-shadow: inset 4px 0 0 0 var(--cn-banner-accent);\n}\n\n.cn-banner-widget--attention.cn-banner-widget--warning {\n\t--cn-banner-accent: var(--color-element-warning, var(--color-warning));\n\t--cn-banner-accent-text: var(--color-text-warning, var(--color-warning-text, var(--color-main-text)));\n}\n\n.cn-banner-widget--attention.cn-banner-widget--error {\n\t--cn-banner-accent: var(--color-element-error, var(--color-error));\n\t--cn-banner-accent-text: var(--color-text-error, var(--color-error-text));\n}\n\n.cn-banner-widget--attention.cn-banner-widget--success {\n\t--cn-banner-accent: var(--color-element-success, var(--color-success));\n\t--cn-banner-accent-text: var(--color-text-success, var(--color-success-text));\n}\n\n.cn-banner-widget__body {\n\tdisplay: flex;\n\tflex: 1 1 320px;\n\tflex-direction: column;\n\tgap: 6px;\n\tmin-width: 0;\n}\n\n.cn-banner-widget__kicker {\n\tmargin: 0;\n\tfont-size: 0.85em;\n\tfont-weight: 700;\n\tletter-spacing: 0.06em;\n\ttext-transform: uppercase;\n\tcolor: var(--cn-banner-accent-text);\n}\n\n.cn-banner-widget__title {\n\tmargin: 0;\n\tfont-size: 1.3em;\n\tfont-weight: 700;\n\tline-height: 1.3;\n\tcolor: var(--color-main-text);\n}\n\n.cn-banner-widget__reason {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-banner-widget__actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 10px;\n}\n\n.cn-banner-widget__action {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tbox-sizing: border-box;\n\tmin-height: var(--default-clickable-area, 40px);\n\tmargin: 0;\n\tpadding: 0 16px;\n\tborder: 1px solid var(--color-border-maxcontrast);\n\tborder-radius: var(--border-radius-element, var(--border-radius-large, 8px));\n\tfont: inherit;\n\tfont-weight: 600;\n\ttext-decoration: none;\n\tcursor: pointer;\n}\n\n.cn-banner-widget__action--secondary {\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-banner-widget__action--secondary:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-banner-widget__action--primary {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n}\n\n.cn-banner-widget__action--primary:hover {\n\tborder-color: var(--color-primary-element-hover);\n\tbackground: var(--color-primary-element-hover);\n}\n\n.cn-banner-widget__action:focus-visible {\n\toutline: 2px solid var(--color-main-text);\n\toutline-offset: 2px;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnBannerWidget.vue3.js","sources":["../../../../src/components/CnBannerWidget/CnBannerWidget.vue?vue&type=template&id=3531692f&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<section\n\t\tv-if=\"visible && isAttention\"\n\t\tclass=\"cn-banner-widget cn-banner-widget--attention\"\n\t\t:class=\"'cn-banner-widget--' + resolvedVariant\"\n\t\t:aria-labelledby=\"headingId\"\n\t\tdata-testid=\"cn-banner-widget-attention\">\n\t\t<div class=\"cn-banner-widget__body\">\n\t\t\t<p v-if=\"resolvedKicker\" class=\"cn-banner-widget__kicker\">\n\t\t\t\t{{ resolvedKicker }}\n\t\t\t</p>\n\t\t\t<h3 :id=\"headingId\" class=\"cn-banner-widget__title\">\n\t\t\t\t{{ displayTitle }}\n\t\t\t</h3>\n\t\t\t<p v-if=\"displayReason\" class=\"cn-banner-widget__reason\">\n\t\t\t\t{{ displayReason }}\n\t\t\t</p>\n\t\t</div>\n\t\t<div v-if=\"resolvedActions.length > 0\" class=\"cn-banner-widget__actions\">\n\t\t\t<component\n\t\t\t\t:is=\"action.href ? 'a' : 'button'\"\n\t\t\t\tv-for=\"action in resolvedActions\"\n\t\t\t\t:key=\"action.key\"\n\t\t\t\tclass=\"cn-banner-widget__action\"\n\t\t\t\t:class=\"action.primary ? 'cn-banner-widget__action--primary' : 'cn-banner-widget__action--secondary'\"\n\t\t\t\t:href=\"action.href || null\"\n\t\t\t\t:type=\"action.href ? null : 'button'\"\n\t\t\t\tdata-testid=\"cn-banner-widget-action\"\n\t\t\t\t@click=\"onActionClick($event, action)\">\n\t\t\t\t{{ action.label }}\n\t\t\t</component>\n\t\t</div>\n\t</section>\n\t<!-- The count could not be read. One quiet line, not the card and not an\n\t alarm: hidden would read as \"nothing to report\", which is not known. -->\n\t<p\n\t\tv-else-if=\"checkFailed\"\n\t\tclass=\"cn-banner-widget cn-banner-widget--unchecked\"\n\t\t:title=\"failureTooltip\"\n\t\tdata-testid=\"cn-banner-widget-unchecked\">\n\t\t<span class=\"cn-banner-widget__unchecked-text\">{{ uncheckedText }}</span>\n\t\t<span class=\"cn-banner-widget__unchecked-reason\">{{ failureTooltip }}</span>\n\t</p>\n\t<div v-else-if=\"visible\" class=\"cn-banner-widget\">\n\t\t<NcNoteCard :type=\"resolvedVariant\" class=\"cn-banner-widget__card\">\n\t\t\t<component\n\t\t\t\t:is=\"clickable ? 'a' : 'span'\"\n\t\t\t\tclass=\"cn-banner-widget__text\"\n\t\t\t\t:class=\"{ 'cn-banner-widget__text--clickable': clickable }\"\n\t\t\t\t:href=\"clickable ? routeHref : null\"\n\t\t\t\tdata-testid=\"cn-banner-widget-text\"\n\t\t\t\t@click=\"onClick\">\n\t\t\t\t{{ displayText }}\n\t\t\t</component>\n\t\t</NcNoteCard>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcNoteCard } from '@nextcloud/vue'\nimport { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'\nimport { safeHref } from '../../utils/safeHref.js'\nimport { nextUid } from '../../utils/uid.js'\nimport { compareVisibleWhen, readVisibleWhenValue } from '../../utils/visibleWhen.js'\n\n/** Variants understood by NcNoteCard. */\nconst VARIANTS = ['info', 'warning', 'error', 'success']\n\n/** Layouts: the note card (default) or the attention card. */\nconst LAYOUTS = ['banner', 'attention']\n\n/** An attention card offers a primary and a secondary action, no more. */\nconst MAX_ACTIONS = 2\n\n/**\n * CnBannerWidget — declarative notice banner for dashboards and v2 pages\n * (`banner` widget type, Wave 1 of nextcloud-vue#91).\n *\n * Renders a themed NcNoteCard with a variant (`info | warning | error`),\n * a text, an optional click-through route, and an optional `visibleWhen`\n * condition: a simple `{ field, op, value }` predicate evaluated against\n * an endpoint response or an OpenRegister source (the doriath\n * migration-banner case — \"show while `pending > 0`\").\n *\n * Config arrives either as flat props (v2 grid spreads `props`) or as a\n * stored `content` blob (CnDashboardPage's registry branch) — explicit\n * flat props win on collision.\n *\n * ```json\n * {\n * \"widgetKey\": \"banner\",\n * \"props\": {\n * \"variant\": \"warning\",\n * \"text\": \"Migrations pending — open the migration overview.\",\n * \"visibleWhen\": { \"endpoint\": \"/apps/doriath/api/migrations/status\", \"field\": \"pending\", \"op\": \"gt\", \"value\": 0 },\n * \"route\": \"migrations\"\n * }\n * }\n * ```\n *\n * Fail-safe: with a `visibleWhen`, the banner stays HIDDEN until the\n * condition evaluates true — a failed fetch never breaks (or spams) a\n * dashboard.\n *\n * An attention card (`layout: \"attention\"`) whose `visibleWhen` request\n * FAILS renders one quiet line instead, \"Could not check\", with the reason as\n * a tooltip. A hidden card reads as \"nothing needs attention\", and a failed\n * count does not know that. A request that succeeds with a value that does\n * not meet the condition still renders nothing.\n *\n * `layout: \"attention\"` turns the banner into an attention card: a card with\n * a coloured edge by severity, a `kicker` label, a `title`, a `reason` line\n * and up to two `actions` (the first is the primary one unless an action says\n * `primary` itself). `visibleWhen` works the same way. Without `layout` the\n * note card above renders, as it always did.\n *\n * ```json\n * {\n * \"widgetKey\": \"banner\",\n * \"props\": {\n * \"layout\": \"attention\",\n * \"variant\": \"error\",\n * \"kicker\": \"First today\",\n * \"title\": \"Parking permits city centre\",\n * \"reason\": \"The deadline ends today.\",\n * \"actions\": [\n * { \"label\": \"Open case\", \"route\": { \"name\": \"CaseDetail\", \"params\": { \"id\": \"2026-0061\" } } },\n * { \"label\": \"Suspend deadline\", \"route\": \"CaseSuspend\" }\n * ]\n * }\n * }\n * ```\n */\nexport default {\n\tname: 'CnBannerWidget',\n\n\tcomponents: { NcNoteCard },\n\n\tprops: {\n\t\t/**\n\t\t * Banner severity variant: `info | warning | error` (plus `success`\n\t\t * for completeness — NcNoteCard's set). Empty falls back to the\n\t\t * `content` blob, then `info`.\n\t\t */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t\tvalidator: (v) => v === '' || VARIANTS.includes(v),\n\t\t},\n\n\t\t/** Pre-translated banner text. Empty falls back to the `content` blob. */\n\t\ttext: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional visibility condition. Shape:\n\t\t * `{ endpoint?, source?, field?, op?, value }` — exactly one of\n\t\t * `endpoint` (a same-origin URL returning JSON) or `source`\n\t\t * (`{ register, schema, filter? }`, an OpenRegister object query whose\n\t\t * `filter` supports the shared @-token grammar). `field` is a\n\t\t * dot-path into the response (for a `source`, into the first result;\n\t\t * omit it — or use `@total` — to compare the collection total).\n\t\t * `op` is `eq | neq | gt | gte | lt | lte` (default `eq`); `value` is\n\t\t * the literal right-hand side. `null` (the default) shows the banner\n\t\t * unconditionally.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tvisibleWhen: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Optional click-through route: a vue-router route NAME (string) or\n\t\t * a full location object. When set, the banner text is a link to that\n\t\t * route. `null` renders static text.\n\t\t *\n\t\t * @type {string|object|null}\n\t\t */\n\t\troute: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * How the banner renders: `banner` (the note card, default) or\n\t\t * `attention` (a card with a severity edge, kicker, title, reason and\n\t\t * up to two actions). Empty falls back to the `content` blob, then\n\t\t * `banner`.\n\t\t *\n\t\t * @type {''|'banner'|'attention'}\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t\tvalidator: (v) => v === '' || LAYOUTS.includes(v),\n\t\t},\n\n\t\t/** Attention card: the small label above the title (\"First today\"). */\n\t\tkicker: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Attention card: the title. Falls back to `text` when empty. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Attention card: the line under the title that says why this needs attention. `{value}` is replaced like in `text`. */\n\t\treason: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Attention card: at most two actions. Each is `{ label, route?,\n\t\t * href?, primary?, id? }`: `route` is a route name or location,\n\t\t * `href` an URL. An action with neither renders a button that emits\n\t\t * `action`. The first action is the primary one unless one sets\n\t\t * `primary: true`.\n\t\t *\n\t\t * @type {Array<{label: string, route?: (string|object), href?: string, primary?: boolean, id?: string}>|null}\n\t\t */\n\t\tactions: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Stored content blob (CnDashboardPage registry branch) carrying the\n\t\t * same keys as the flat props: `{ variant, text, visibleWhen, route,\n\t\t * layout, kicker, title, reason, actions }`. Explicit flat props win\n\t\t * on collision.\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 * Pre-evaluated `visibleWhen` outcome `{ met, value }`, injected by a\n\t\t * host that already ran the predicate (CnDashboardPage evaluates it\n\t\t * to know whether to collapse the banner's grid cell). When present,\n\t\t * the banner renders from this verdict instead of fetching again —\n\t\t * no duplicate request and no hidden-until-self-evaluated flash —\n\t\t * and `value` feeds the `{value}` text placeholder. `null` (the\n\t\t * default) keeps the banner self-evaluating. A host whose request\n\t\t * failed passes `{ met: false, value: null, failed: true, reason }`,\n\t\t * and an attention card then says it could not check.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tconditionOutcome: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted when an attention-card action without a `route` or `href`\n\t\t * is clicked, so the host can run it.\n\t\t *\n\t\t * @event action\n\t\t * @type {{id: (string|undefined), label: string, index: number}}\n\t\t */\n\t\t'action',\n\t],\n\n\t/**\n\t * The banner's own state: the evaluated condition, the value it read,\n\t * and whether the request for it failed.\n\t *\n\t * @return {object} The state.\n\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t */\n\tdata() {\n\t\treturn {\n\t\t\t/** Id that ties the attention card's region to its title. */\n\t\t\theadingId: `cn-banner-title-${nextUid()}`,\n\t\t\t/** Evaluated visibleWhen outcome (null = not yet evaluated). */\n\t\t\tconditionMet: null,\n\t\t\t/**\n\t\t\t * The raw field value the predicate read (from the injected\n\t\t\t * outcome or the banner's own evaluation) — interpolated into\n\t\t\t * the text wherever it says `{value}`.\n\t\t\t */\n\t\t\tconditionValue: null,\n\t\t\t/** Whether the visibleWhen request itself failed (not: answered \"no\"). */\n\t\t\tconditionFailed: false,\n\t\t\t/** Why it failed, as the request reported it. */\n\t\t\tconditionFailureReason: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The effective variant (prop → content → 'info'). */\n\t\tresolvedVariant() {\n\t\t\tconst v = this.variant || (this.content && this.content.variant) || 'info'\n\t\t\treturn VARIANTS.includes(v) ? v : 'info'\n\t\t},\n\n\t\t/** The effective banner text (prop → content → ''). */\n\t\tresolvedText() {\n\t\t\treturn this.text || (this.content && this.content.text) || ''\n\t\t},\n\n\t\t/** The effective visibleWhen condition (prop → content → null). */\n\t\tresolvedVisibleWhen() {\n\t\t\treturn this.visibleWhen || (this.content && this.content.visibleWhen) || null\n\t\t},\n\n\t\t/** The effective injected outcome (prop → content → null). */\n\t\tresolvedConditionOutcome() {\n\t\t\treturn this.conditionOutcome || (this.content && this.content.conditionOutcome) || null\n\t\t},\n\n\t\t/**\n\t\t * The rendered text: `resolvedText` with `{value}` replaced by the\n\t\t * predicate's field value once one is known. Without a known value\n\t\t * the text renders as written — a `{value}` placeholder only makes\n\t\t * sense on a conditional banner, which stays hidden until its\n\t\t * predicate (and therefore its value) resolves.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tdisplayText() {\n\t\t\treturn this.fillValue(this.resolvedText)\n\t\t},\n\n\t\t/** The effective click-through route (prop → content → null). */\n\t\tresolvedRoute() {\n\t\t\treturn this.route || (this.content && this.content.route) || null\n\t\t},\n\n\t\t/**\n\t\t * Whether the attention card renders instead of the note card.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tisAttention() {\n\t\t\treturn (this.layout || (this.content && this.content.layout) || 'banner') === 'attention'\n\t\t},\n\n\t\t/**\n\t\t * The attention card's kicker (prop, else content).\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tresolvedKicker() {\n\t\t\tconst kicker = this.kicker || (this.content && this.content.kicker) || ''\n\t\t\treturn kicker\n\t\t},\n\n\t\t/**\n\t\t * The attention card's title: `title`, else `text`, with `{value}` filled in.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tdisplayTitle() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\treturn title ? this.fillValue(title) : this.displayText\n\t\t},\n\n\t\t/**\n\t\t * The attention card's reason line. When the title already used\n\t\t * `text`, the text is not repeated here.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tdisplayReason() {\n\t\t\tconst reason = this.reason || (this.content && this.content.reason) || ''\n\t\t\tif (reason) {\n\t\t\t\treturn this.fillValue(reason)\n\t\t\t}\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\t// A card that sets `text` to the same words as its `title` says\n\t\t\t// them once.\n\t\t\treturn title && this.displayText !== this.displayTitle ? this.displayText : ''\n\t\t},\n\n\t\t/**\n\t\t * The attention card's actions: the first two that have a label, each\n\t\t * with its href and whether it is the primary one.\n\t\t *\n\t\t * @return {Array<{key: string, id: (string|undefined), label: string, href: string, target: (object|null), primary: boolean, index: number}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tresolvedActions() {\n\t\t\tconst raw = this.actions || (this.content && this.content.actions) || []\n\t\t\tif (!Array.isArray(raw)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst usable = raw\n\t\t\t\t.filter((action) => action && typeof action === 'object' && typeof action.label === 'string' && action.label !== '')\n\t\t\t\t.slice(0, MAX_ACTIONS)\n\t\t\tconst declaredPrimary = usable.findIndex((action) => action.primary === true)\n\t\t\tconst primaryIndex = declaredPrimary === -1 ? 0 : declaredPrimary\n\t\t\treturn usable.map((action, index) => {\n\t\t\t\tlet target = null\n\t\t\t\tlet href = ''\n\t\t\t\tif (action.route) {\n\t\t\t\t\ttarget = typeof action.route === 'string' ? { name: action.route } : action.route\n\t\t\t\t\thref = resolveHref(target, this.$router)\n\t\t\t\t} else if (typeof action.href === 'string' && action.href !== '') {\n\t\t\t\t\tconst safe = safeHref(action.href)\n\t\t\t\t\thref = safe === '#' ? '' : safe\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tkey: `${index}-${action.id || action.label}`,\n\t\t\t\t\tid: action.id,\n\t\t\t\t\tlabel: action.label,\n\t\t\t\t\thref,\n\t\t\t\t\ttarget,\n\t\t\t\t\tprimary: index === primaryIndex,\n\t\t\t\t\tindex,\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether the banner renders: no condition = always; else the evaluated outcome.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tvisible() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\tif (this.resolvedText === '' && !(this.isAttention && title !== '')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (!this.resolvedVisibleWhen) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.conditionMet === true\n\t\t},\n\n\t\t/**\n\t\t * Whether to say the check failed: an attention card, with words to\n\t\t * show, whose `visibleWhen` request failed. A plain banner stays\n\t\t * hidden on failure, as it always did.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tcheckFailed() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\treturn this.isAttention\n\t\t\t\t&& !!this.resolvedVisibleWhen\n\t\t\t\t&& this.conditionFailed\n\t\t\t\t&& (title !== '' || this.resolvedText !== '')\n\t\t},\n\n\t\t/**\n\t\t * The one line shown when the check failed. It names the card by its\n\t\t * title, unless the title needs the value that could not be read.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tuncheckedText() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || this.resolvedText\n\t\t\tif (!title || title.includes('{value}')) {\n\t\t\t\treturn t('nextcloud-vue', 'Could not check')\n\t\t\t}\n\t\t\t// `escape: false`: the result is rendered through `{{ }}`, which escapes.\n\t\t\treturn t('nextcloud-vue', 'Could not check: {subject}', { subject: title }, undefined, { escape: false })\n\t\t},\n\n\t\t/**\n\t\t * The reason the check failed, for the tooltip and for assistive\n\t\t * technology.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tfailureTooltip() {\n\t\t\tif (!this.conditionFailureReason) {\n\t\t\t\treturn t('nextcloud-vue', 'The check failed.')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'The check failed: {reason}', { reason: this.conditionFailureReason }, undefined, { escape: false })\n\t\t},\n\n\t\t/** Whether the banner navigates on click (route set + router present). */\n\t\tclickable() {\n\t\t\treturn !!this.resolvedRoute && !!this.$router\n\t\t},\n\n\t\t/** The click-through route as a router location (a string is a route name). */\n\t\trouteLocation() {\n\t\t\tconst route = this.resolvedRoute\n\t\t\treturn typeof route === 'string' ? { name: route } : route\n\t\t},\n\n\t\t/** The href of the click-through link. */\n\t\trouteHref() {\n\t\t\treturn resolveHref(this.routeLocation, this.$router)\n\t\t},\n\t},\n\n\twatch: {\n\t\tresolvedVisibleWhen: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.evaluateCondition()\n\t\t\t},\n\t\t},\n\n\t\tresolvedConditionOutcome() {\n\t\t\tthis.evaluateCondition()\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Replace `{value}` in a text by the value the predicate read, once\n\t\t * one is known.\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {string} The text with the value filled in.\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tfillValue(text) {\n\t\t\tif (this.conditionValue === null || !text.includes('{value}')) {\n\t\t\t\treturn text\n\t\t\t}\n\t\t\treturn text.replaceAll('{value}', String(this.conditionValue))\n\t\t},\n\n\t\t/**\n\t\t * Run an attention-card action: route an in-app link, leave an\n\t\t * external link to the browser, and emit `action` for a button.\n\t\t *\n\t\t * @param {MouseEvent} event The click.\n\t\t * @param {object} action The resolved action.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tonActionClick(event, action) {\n\t\t\tif (action.target) {\n\t\t\t\tfollowLinkClick(event, action.target, this.$router)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!action.href) {\n\t\t\t\tthis.$emit('action', { id: action.id, label: action.label, index: action.index })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Resolve the `visibleWhen` verdict. A host-injected\n\t\t * `conditionOutcome` wins outright — the host already ran the\n\t\t * predicate, so re-fetching would double the request and flash the\n\t\t * banner hidden until its own copy resolved. Otherwise evaluate\n\t\t * through the shared util primitives (endpoint / OpenRegister-source\n\t\t * modes — the Wave-1 banner shape is the canonical one, extracted to\n\t\t * `utils/visibleWhen.js` in Wave 3 so manifest actions reuse it),\n\t\t * keeping the read VALUE for the `{value}` text placeholder.\n\t\t * Fail-safe: any fetch/shape error leaves the banner hidden. The\n\t\t * failure is kept (`conditionFailed`, with its reason) so an\n\t\t * attention card can say it could not check.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tasync evaluateCondition() {\n\t\t\tconst outcome = this.resolvedConditionOutcome\n\t\t\tif (outcome) {\n\t\t\t\tthis.conditionMet = outcome.met === true\n\t\t\t\tthis.conditionValue = outcome.value !== undefined ? outcome.value : null\n\t\t\t\tthis.conditionFailed = outcome.failed === true\n\t\t\t\tthis.conditionFailureReason = outcome.failed === true && typeof outcome.reason === 'string' ? outcome.reason : ''\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst cond = this.resolvedVisibleWhen\n\t\t\tif (!cond) {\n\t\t\t\tthis.conditionMet = null\n\t\t\t\tthis.conditionValue = null\n\t\t\t\tthis.conditionFailed = false\n\t\t\t\tthis.conditionFailureReason = ''\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst value = await readVisibleWhenValue(cond)\n\t\t\t\tthis.conditionValue = value\n\t\t\t\tthis.conditionMet = compareVisibleWhen(value, cond.op || 'eq', cond.value)\n\t\t\t\tthis.conditionFailed = false\n\t\t\t\tthis.conditionFailureReason = ''\n\t\t\t} catch (error) {\n\t\t\t\t// Still hidden: a banner that cannot tell whether it applies\n\t\t\t\t// should not claim attention. But not silently, because a\n\t\t\t\t// failed count and \"nothing to report\" look the same on screen.\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn('[CnBannerWidget] visibleWhen could not be evaluated, banner hidden:', error?.message || error)\n\t\t\t\tthis.conditionMet = false\n\t\t\t\tthis.conditionValue = null\n\t\t\t\tthis.conditionFailed = true\n\t\t\t\tthis.conditionFailureReason = typeof error?.message === 'string' ? error.message : String(error ?? '')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Route a plain click on the link; a modified click opens the href\n\t\t * in the browser.\n\t\t *\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonClick(event) {\n\t\t\tif (!this.clickable) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfollowLinkClick(event, this.routeLocation, this.$router)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-banner-widget {\n\twidth: 100%;\n}\n\n.cn-banner-widget__card {\n\tmargin: 0;\n}\n\n.cn-banner-widget__text--clickable {\n\tcursor: pointer;\n\ttext-decoration: underline;\n\tcolor: inherit;\n}\n\n/* The check failed. Muted text, no fill, no severity colour: it is a note\n that something is not known, not a warning. */\n.cn-banner-widget--unchecked {\n\tbox-sizing: border-box;\n\tmargin: 0;\n\tpadding: 8px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tcursor: help;\n}\n\n/* The reason, for assistive technology; sighted readers get the tooltip. */\n.cn-banner-widget__unchecked-reason {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tmargin: -1px;\n\tpadding: 0;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\twhite-space: nowrap;\n\tborder: 0;\n}\n\n/* Attention card. The severity edge is an inset shadow, so it takes no width. */\n.cn-banner-widget--attention {\n\t--cn-banner-accent: var(--color-primary-element);\n\t--cn-banner-accent-text: var(--color-primary-element);\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 16px 24px;\n\tbox-sizing: border-box;\n\tpadding: 20px 24px 20px 28px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-container, var(--border-radius-large, 12px));\n\tbackground: var(--color-main-background);\n\tbox-shadow: inset 4px 0 0 0 var(--cn-banner-accent);\n}\n\n.cn-banner-widget--attention.cn-banner-widget--warning {\n\t--cn-banner-accent: var(--color-element-warning, var(--color-warning));\n\t--cn-banner-accent-text: var(--color-text-warning, var(--color-warning-text, var(--color-main-text)));\n}\n\n.cn-banner-widget--attention.cn-banner-widget--error {\n\t--cn-banner-accent: var(--color-element-error, var(--color-error));\n\t--cn-banner-accent-text: var(--color-text-error, var(--color-error-text));\n}\n\n.cn-banner-widget--attention.cn-banner-widget--success {\n\t--cn-banner-accent: var(--color-element-success, var(--color-success));\n\t--cn-banner-accent-text: var(--color-text-success, var(--color-success-text));\n}\n\n.cn-banner-widget__body {\n\tdisplay: flex;\n\tflex: 1 1 320px;\n\tflex-direction: column;\n\tgap: 6px;\n\tmin-width: 0;\n}\n\n.cn-banner-widget__kicker {\n\tmargin: 0;\n\tfont-size: 0.85em;\n\tfont-weight: 700;\n\tletter-spacing: 0.06em;\n\ttext-transform: uppercase;\n\tcolor: var(--cn-banner-accent-text);\n}\n\n.cn-banner-widget__title {\n\tmargin: 0;\n\tfont-size: 1.3em;\n\tfont-weight: 700;\n\tline-height: 1.3;\n\tcolor: var(--color-main-text);\n}\n\n.cn-banner-widget__reason {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-banner-widget__actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 10px;\n}\n\n.cn-banner-widget__action {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tbox-sizing: border-box;\n\tmin-height: var(--default-clickable-area, 40px);\n\tmargin: 0;\n\tpadding: 0 16px;\n\tborder: 1px solid var(--color-border-maxcontrast);\n\tborder-radius: var(--border-radius-element, var(--border-radius-large, 8px));\n\tfont: inherit;\n\tfont-weight: 600;\n\ttext-decoration: none;\n\tcursor: pointer;\n}\n\n.cn-banner-widget__action--secondary {\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-banner-widget__action--secondary:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-banner-widget__action--primary {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n}\n\n.cn-banner-widget__action--primary:hover {\n\tborder-color: var(--color-primary-element-hover);\n\tbackground: var(--color-primary-element-hover);\n}\n\n.cn-banner-widget__action:focus-visible {\n\toutline: 2px solid var(--color-main-text);\n\toutline-offset: 2px;\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createElementVNode","_toDisplayString","_openBlock","_Fragment","_renderList","_resolveDynamicComponent","_createTextVNode","_createVNode","_createBlock"],"mappings":";;;AAiBO,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,wBAAQ,GAAA;;;AACd,EAAA,KAAA,EAAA,0BAAA;;;;;AAMK,EAAA,KAAA,EAAA,0BAAA;;;;AAIuB,EAAA,KAAA,EAAA,2BAAA;;;AAsB3B,MAAA,UAAA,GAAA,EAAA,KAAI,EAAA,kCAAA,GAAA;AACJ,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,oCAAmB,GAAA;;;AAEP,EAAA,KAAK,EAAC,kBAAmB;;;;;;AAzC3C,EAAA,OAAA,CAAA,QAAA,CAAA,OAAO,IAAI,QAAW,CAAA,WAAA;oBAD5BA,kBA+BS,CAAA,SAAA,EAAA;;aA7BJ,EAAAC,cAAA,CAAA,CAAC,8CAA8C,EAAA,oBAAA,GAC/B,QAAA,CAAA,eAAA,CAAA,CAAA;AACpB,QAAA,iBAAG,EAAA,KAAA,CAAA,SAAA;qBACO,EAAC,4BAA4B;;AACtC,QAAAC,kBAAA,CAUI,OAVJ,UAUI,EAAA;WATD,QAAA,CAAA,cAAA;4BAAJF,kBAEI,CAAA,GAAA,EAFJ,UAEI,EAAAG,eAAA,CADA,QAAc,CAAA,cAAA,CAAA,EAAA,CAAA,YAAA;;UAElBD,kBAEK,CAAA,IAAA,EAAA;AAFF,YAAA,EAAA,EAAA,KAAA,CAAA,SAAA;AAAA,YAAA,KAAA,EAAA,yBAAA;6BACC,QAAA,CAAA,YAAA,CAAA,EAAA,CAAA,oBAAA,UAAA,CAAA;WAEK,QAAA,CAAA,aAAA;4BAATF,kBAEI,CAAA,GAAA,EAFJ,UAEI,EAAAG,eAAA,CADA,QAAY,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;;;AAGN,QAAA,CAAA,QAAA,CAAA,eAAe,CAAC,MAAM,GAAA,CAAA;AAA/B,aAAAC,SAAA,EAAA,EAAAJ,kBAAA,CAaI,OAbJ,UAaI,EAAA;kDADN,CAAAK,QAAA,EAAA,IAAA,EAAAC,UAAA,CATmB,QAAe,CAAA,eAAA,EAAA,CAAzB,MAAM,KAAA;gDASf,CAAAC,uBAAA,CAVO,MAAM,CAAC,IAAI,GAAA,GAAA,GAAA,QAAA,CAAA,EAAA;kBAEf,GAAG,EAAE,MAAM,CAAC,GAAG;AAChB,kBAAA,KAAK,EAACN,cAAA,CAAA,CAAA,0BAA0B,EACxB,MAAM,QAAG,GAAA,mCAAA,GAAA,qCAAA,CAAA,CAAA;kBAChB,IAAI,EAAE,MAAG,CAAA,IAAA,IAAA,IAAA;kBACT,IAAE,EAAA,MAAA,CAAA,IAAA,GAAA,IAAA,GAAA,QAAA;AACH,kBAAA,aAAM,EAAA,yBAAA;AACN,kBAAA,OAAM,EAAE,MAAA,KAAA,QAAA,CAAA,aAAY,CAAE,MAAM,EAAG,MAAK,CAAA,CAAA;;yCAClB;AAAf,oBAAAO,eAAA,CAAAL,eAAA,CAAA,MAAM,CAAC,KAAI,CAAA,EAAA,CAAA,YAAA;;;;;;;;OAOL,QAAW,CAAA,WAAA;sBADtBH,kBAOG,CAAA,GAAA,EAAA;;AALH,UAAA,KAAE,EAAA,8CAAA;AACD,UAAA,KAAK,EAAE,QAAc,CAAA,cAAA;AACtB,UAAA,aAAU,EAAE,4BAA4B;;UACvCE,kBAAS,CAAA,MAAA,EAAT,UAAS,EAAAC,eAAA,CAAA,QAAA,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;4BACqB,CAAA,MAAA,YAAA,EAAAA,eAAA,CAAA,QAAA,CAAA,cAAA,CAAA,EAAA,CAAA,YAAA;;SAEhB,QAAO,CAAA,OAAA;;YACpBM;AAAW,cAAA,IAAI,EAAE,QAAe,CAAA,eAAA;AAAE,cAAA,KAAC,EAAA,wBAAA;;+BACnC;AAAA,iBAAAL,SAAA,EAAA,EAAAM,WAAA,yBACK,QAAS,CAAA,SAAA,GAAA,GAAA,GAAA,MAAA,CAAA,EAAA;kBACd,KAAK,EAAAT,cAAA,CAAA,CAAC,wBAAU,EAAA,EAAA,mCAAA,EACN,QAAA,CAAA,SAAA,EAAA,CAAA,CAAA;kBACT,IAAI,EAAE,QAAG,CAAA,SAAA,GAAA,QAAA,CAAA,SAAA,GAAA,IAAA;AACV,kBAAA,aAAE,EAAA,uBAAA;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnBannerWidget.vue3.js","sources":["../../../../src/components/CnBannerWidget/CnBannerWidget.vue?vue&type=template&id=3531692f&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<section\n\t\tv-if=\"visible && isAttention\"\n\t\tclass=\"cn-banner-widget cn-banner-widget--attention\"\n\t\t:class=\"'cn-banner-widget--' + resolvedVariant\"\n\t\t:aria-labelledby=\"headingId\"\n\t\tdata-testid=\"cn-banner-widget-attention\">\n\t\t<div class=\"cn-banner-widget__body\">\n\t\t\t<p v-if=\"resolvedKicker\" class=\"cn-banner-widget__kicker\">\n\t\t\t\t{{ resolvedKicker }}\n\t\t\t</p>\n\t\t\t<h3 :id=\"headingId\" class=\"cn-banner-widget__title\">\n\t\t\t\t{{ displayTitle }}\n\t\t\t</h3>\n\t\t\t<p v-if=\"displayReason\" class=\"cn-banner-widget__reason\">\n\t\t\t\t{{ displayReason }}\n\t\t\t</p>\n\t\t</div>\n\t\t<div v-if=\"resolvedActions.length > 0\" class=\"cn-banner-widget__actions\">\n\t\t\t<component\n\t\t\t\t:is=\"action.href ? 'a' : 'button'\"\n\t\t\t\tv-for=\"action in resolvedActions\"\n\t\t\t\t:key=\"action.key\"\n\t\t\t\tclass=\"cn-banner-widget__action\"\n\t\t\t\t:class=\"action.primary ? 'cn-banner-widget__action--primary' : 'cn-banner-widget__action--secondary'\"\n\t\t\t\t:href=\"action.href || null\"\n\t\t\t\t:type=\"action.href ? null : 'button'\"\n\t\t\t\tdata-testid=\"cn-banner-widget-action\"\n\t\t\t\t@click=\"onActionClick($event, action)\">\n\t\t\t\t{{ action.label }}\n\t\t\t</component>\n\t\t</div>\n\t</section>\n\t<!-- The count could not be read. One quiet line, not the card and not an\n\t alarm: hidden would read as \"nothing to report\", which is not known. -->\n\t<p\n\t\tv-else-if=\"checkFailed\"\n\t\tclass=\"cn-banner-widget cn-banner-widget--unchecked\"\n\t\t:title=\"failureTooltip\"\n\t\tdata-testid=\"cn-banner-widget-unchecked\">\n\t\t<span class=\"cn-banner-widget__unchecked-text\">{{ uncheckedText }}</span>\n\t\t<span class=\"cn-banner-widget__unchecked-reason\">{{ failureTooltip }}</span>\n\t</p>\n\t<div v-else-if=\"visible\" class=\"cn-banner-widget\">\n\t\t<NcNoteCard :type=\"resolvedVariant\" class=\"cn-banner-widget__card\">\n\t\t\t<component\n\t\t\t\t:is=\"clickable ? 'a' : 'span'\"\n\t\t\t\tclass=\"cn-banner-widget__text\"\n\t\t\t\t:class=\"{ 'cn-banner-widget__text--clickable': clickable }\"\n\t\t\t\t:href=\"clickable ? routeHref : null\"\n\t\t\t\tdata-testid=\"cn-banner-widget-text\"\n\t\t\t\t@click=\"onClick\">\n\t\t\t\t{{ displayText }}\n\t\t\t</component>\n\t\t</NcNoteCard>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcNoteCard } from '@nextcloud/vue'\nimport { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'\nimport { safeHref } from '../../utils/safeHref.js'\nimport { nextUid } from '../../utils/uid.js'\nimport { compareVisibleWhen, readVisibleWhenValue } from '../../utils/visibleWhen.js'\n\n/** Variants understood by NcNoteCard. */\nconst VARIANTS = ['info', 'warning', 'error', 'success']\n\n/** Layouts: the note card (default) or the attention card. */\nconst LAYOUTS = ['banner', 'attention']\n\n/** An attention card offers a primary and a secondary action, no more. */\nconst MAX_ACTIONS = 2\n\n/**\n * CnBannerWidget — declarative notice banner for dashboards and v2 pages\n * (`banner` widget type, Wave 1 of nextcloud-vue#91).\n *\n * Renders a themed NcNoteCard with a variant (`info | warning | error`),\n * a text, an optional click-through route, and an optional `visibleWhen`\n * condition: a simple `{ field, op, value }` predicate evaluated against\n * an endpoint response or an OpenRegister source (the doriath\n * migration-banner case — \"show while `pending > 0`\").\n *\n * Config arrives either as flat props (v2 grid spreads `props`) or as a\n * stored `content` blob (CnDashboardPage's registry branch) — explicit\n * flat props win on collision.\n *\n * ```json\n * {\n * \"widgetKey\": \"banner\",\n * \"props\": {\n * \"variant\": \"warning\",\n * \"text\": \"Migrations pending — open the migration overview.\",\n * \"visibleWhen\": { \"endpoint\": \"/apps/doriath/api/migrations/status\", \"field\": \"pending\", \"op\": \"gt\", \"value\": 0 },\n * \"route\": \"migrations\"\n * }\n * }\n * ```\n *\n * Fail-safe: with a `visibleWhen`, the banner stays HIDDEN until the\n * condition evaluates true — a failed fetch never breaks (or spams) a\n * dashboard.\n *\n * An attention card (`layout: \"attention\"`) whose `visibleWhen` request\n * FAILS renders one quiet line instead, \"Could not check\", with the reason as\n * a tooltip. A hidden card reads as \"nothing needs attention\", and a failed\n * count does not know that. A request that succeeds with a value that does\n * not meet the condition still renders nothing.\n *\n * `layout: \"attention\"` turns the banner into an attention card: a card with\n * a coloured edge by severity, a `kicker` label, a `title`, a `reason` line\n * and up to two `actions` (the first is the primary one unless an action says\n * `primary` itself). `visibleWhen` works the same way. Without `layout` the\n * note card above renders, as it always did.\n *\n * ```json\n * {\n * \"widgetKey\": \"banner\",\n * \"props\": {\n * \"layout\": \"attention\",\n * \"variant\": \"error\",\n * \"kicker\": \"First today\",\n * \"title\": \"Parking permits city centre\",\n * \"reason\": \"The deadline ends today.\",\n * \"actions\": [\n * { \"label\": \"Open case\", \"route\": { \"name\": \"CaseDetail\", \"params\": { \"id\": \"2026-0061\" } } },\n * { \"label\": \"Suspend deadline\", \"route\": \"CaseSuspend\" }\n * ]\n * }\n * }\n * ```\n */\nexport default {\n\tname: 'CnBannerWidget',\n\n\tcomponents: { NcNoteCard },\n\n\tinject: {\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Banner severity variant: `info | warning | error` (plus `success`\n\t\t * for completeness — NcNoteCard's set). Empty falls back to the\n\t\t * `content` blob, then `info`.\n\t\t */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t\tvalidator: (v) => v === '' || VARIANTS.includes(v),\n\t\t},\n\n\t\t/** Pre-translated banner text. Empty falls back to the `content` blob. */\n\t\ttext: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional visibility condition. Shape:\n\t\t * `{ endpoint?, source?, field?, op?, value }` — exactly one of\n\t\t * `endpoint` (a same-origin URL returning JSON) or `source`\n\t\t * (`{ register, schema, filter? }`, an OpenRegister object query whose\n\t\t * `filter` supports the shared @-token grammar). `field` is a\n\t\t * dot-path into the response (for a `source`, into the first result;\n\t\t * omit it — or use `@total` — to compare the collection total).\n\t\t * `op` is `eq | neq | gt | gte | lt | lte` (default `eq`); `value` is\n\t\t * the literal right-hand side. `null` (the default) shows the banner\n\t\t * unconditionally.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tvisibleWhen: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Optional click-through route: a vue-router route NAME (string) or\n\t\t * a full location object. When set, the banner text is a link to that\n\t\t * route. `null` renders static text.\n\t\t *\n\t\t * @type {string|object|null}\n\t\t */\n\t\troute: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * How the banner renders: `banner` (the note card, default) or\n\t\t * `attention` (a card with a severity edge, kicker, title, reason and\n\t\t * up to two actions). Empty falls back to the `content` blob, then\n\t\t * `banner`.\n\t\t *\n\t\t * @type {''|'banner'|'attention'}\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t\tvalidator: (v) => v === '' || LAYOUTS.includes(v),\n\t\t},\n\n\t\t/** Attention card: the small label above the title (\"First today\"). */\n\t\tkicker: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Attention card: the title. Falls back to `text` when empty. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Attention card: the line under the title that says why this needs attention. `{value}` is replaced like in `text`. */\n\t\treason: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Attention card: at most two actions. Each is `{ label, route?,\n\t\t * href?, primary?, id? }`: `route` is a route name or location,\n\t\t * `href` an URL. An action with neither renders a button that emits\n\t\t * `action`. The first action is the primary one unless one sets\n\t\t * `primary: true`.\n\t\t *\n\t\t * @type {Array<{label: string, route?: (string|object), href?: string, primary?: boolean, id?: string}>|null}\n\t\t */\n\t\tactions: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Stored content blob (CnDashboardPage registry branch) carrying the\n\t\t * same keys as the flat props: `{ variant, text, visibleWhen, route,\n\t\t * layout, kicker, title, reason, actions }`. Explicit flat props win\n\t\t * on collision.\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 * Pre-evaluated `visibleWhen` outcome `{ met, value }`, injected by a\n\t\t * host that already ran the predicate (CnDashboardPage evaluates it\n\t\t * to know whether to collapse the banner's grid cell). When present,\n\t\t * the banner renders from this verdict instead of fetching again —\n\t\t * no duplicate request and no hidden-until-self-evaluated flash —\n\t\t * and `value` feeds the `{value}` text placeholder. `null` (the\n\t\t * default) keeps the banner self-evaluating. A host whose request\n\t\t * failed passes `{ met: false, value: null, failed: true, reason }`,\n\t\t * and an attention card then says it could not check.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tconditionOutcome: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted when an attention-card action without a `route` or `href`\n\t\t * is clicked, so the host can run it.\n\t\t *\n\t\t * @event action\n\t\t * @type {{id: (string|undefined), label: string, index: number}}\n\t\t */\n\t\t'action',\n\t],\n\n\t/**\n\t * The banner's own state: the evaluated condition, the value it read,\n\t * and whether the request for it failed.\n\t *\n\t * @return {object} The state.\n\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t */\n\tdata() {\n\t\treturn {\n\t\t\t/** Id that ties the attention card's region to its title. */\n\t\t\theadingId: `cn-banner-title-${nextUid()}`,\n\t\t\t/** Evaluated visibleWhen outcome (null = not yet evaluated). */\n\t\t\tconditionMet: null,\n\t\t\t/**\n\t\t\t * The raw field value the predicate read (from the injected\n\t\t\t * outcome or the banner's own evaluation) — interpolated into\n\t\t\t * the text wherever it says `{value}`.\n\t\t\t */\n\t\t\tconditionValue: null,\n\t\t\t/** Whether the visibleWhen request itself failed (not: answered \"no\"). */\n\t\t\tconditionFailed: false,\n\t\t\t/** Why it failed, as the request reported it. */\n\t\t\tconditionFailureReason: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The effective variant (prop → content → 'info'). */\n\t\tresolvedVariant() {\n\t\t\tconst v = this.variant || (this.content && this.content.variant) || 'info'\n\t\t\treturn VARIANTS.includes(v) ? v : 'info'\n\t\t},\n\n\t\t/** The effective banner text (prop → content → ''). */\n\t\tresolvedText() {\n\t\t\treturn this.text || (this.content && this.content.text) || ''\n\t\t},\n\n\t\t/** The effective visibleWhen condition (prop → content → null). */\n\t\tresolvedVisibleWhen() {\n\t\t\treturn this.visibleWhen || (this.content && this.content.visibleWhen) || null\n\t\t},\n\n\t\t/** The effective injected outcome (prop → content → null). */\n\t\tresolvedConditionOutcome() {\n\t\t\treturn this.conditionOutcome || (this.content && this.content.conditionOutcome) || null\n\t\t},\n\n\t\t/**\n\t\t * The rendered text: `resolvedText` with `{value}` replaced by the\n\t\t * predicate's field value once one is known. Without a known value\n\t\t * the text renders as written — a `{value}` placeholder only makes\n\t\t * sense on a conditional banner, which stays hidden until its\n\t\t * predicate (and therefore its value) resolves.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tdisplayText() {\n\t\t\treturn this.fillValue(this.resolvedText)\n\t\t},\n\n\t\t/** The effective click-through route (prop → content → null). */\n\t\tresolvedRoute() {\n\t\t\treturn this.route || (this.content && this.content.route) || null\n\t\t},\n\n\t\t/**\n\t\t * Whether the attention card renders instead of the note card.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tisAttention() {\n\t\t\treturn (this.layout || (this.content && this.content.layout) || 'banner') === 'attention'\n\t\t},\n\n\t\t/**\n\t\t * The attention card's kicker (prop, else content).\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tresolvedKicker() {\n\t\t\tconst kicker = this.kicker || (this.content && this.content.kicker) || ''\n\t\t\treturn kicker ? this.tl(kicker) : ''\n\t\t},\n\n\t\t/**\n\t\t * The attention card's title: `title`, else `text`, with `{value}` filled in.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tdisplayTitle() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\treturn title ? this.fillValue(this.tl(title)) : this.displayText\n\t\t},\n\n\t\t/**\n\t\t * The attention card's reason line. When the title already used\n\t\t * `text`, the text is not repeated here.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tdisplayReason() {\n\t\t\tconst reason = this.reason || (this.content && this.content.reason) || ''\n\t\t\tif (reason) {\n\t\t\t\treturn this.fillValue(this.tl(reason))\n\t\t\t}\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\t// A card that sets `text` to the same words as its `title` says\n\t\t\t// them once.\n\t\t\treturn title && this.displayText !== this.displayTitle ? this.displayText : ''\n\t\t},\n\n\t\t/**\n\t\t * The attention card's actions: the first two that have a label, each\n\t\t * with its href and whether it is the primary one.\n\t\t *\n\t\t * @return {Array<{key: string, id: (string|undefined), label: string, href: string, target: (object|null), primary: boolean, index: number}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tresolvedActions() {\n\t\t\tconst raw = this.actions || (this.content && this.content.actions) || []\n\t\t\tif (!Array.isArray(raw)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst usable = raw\n\t\t\t\t.filter((action) => action && typeof action === 'object' && typeof action.label === 'string' && action.label !== '')\n\t\t\t\t.slice(0, MAX_ACTIONS)\n\t\t\tconst declaredPrimary = usable.findIndex((action) => action.primary === true)\n\t\t\tconst primaryIndex = declaredPrimary === -1 ? 0 : declaredPrimary\n\t\t\treturn usable.map((action, index) => {\n\t\t\t\tlet target = null\n\t\t\t\tlet href = ''\n\t\t\t\tif (action.route) {\n\t\t\t\t\ttarget = typeof action.route === 'string' ? { name: action.route } : action.route\n\t\t\t\t\thref = resolveHref(target, this.$router)\n\t\t\t\t} else if (typeof action.href === 'string' && action.href !== '') {\n\t\t\t\t\tconst safe = safeHref(action.href)\n\t\t\t\t\thref = safe === '#' ? '' : safe\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tkey: `${index}-${action.id || action.label}`,\n\t\t\t\t\tid: action.id,\n\t\t\t\t\tlabel: this.tl(action.label),\n\t\t\t\t\thref,\n\t\t\t\t\ttarget,\n\t\t\t\t\tprimary: index === primaryIndex,\n\t\t\t\t\tindex,\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether the banner renders: no condition = always; else the evaluated outcome.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tvisible() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\tif (this.resolvedText === '' && !(this.isAttention && title !== '')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (!this.resolvedVisibleWhen) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.conditionMet === true\n\t\t},\n\n\t\t/**\n\t\t * Whether to say the check failed: an attention card, with words to\n\t\t * show, whose `visibleWhen` request failed. A plain banner stays\n\t\t * hidden on failure, as it always did.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tcheckFailed() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || ''\n\t\t\treturn this.isAttention\n\t\t\t\t&& !!this.resolvedVisibleWhen\n\t\t\t\t&& this.conditionFailed\n\t\t\t\t&& (title !== '' || this.resolvedText !== '')\n\t\t},\n\n\t\t/**\n\t\t * The one line shown when the check failed. It names the card by its\n\t\t * title, unless the title needs the value that could not be read.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tuncheckedText() {\n\t\t\tconst title = this.title || (this.content && this.content.title) || this.resolvedText\n\t\t\tif (!title || title.includes('{value}')) {\n\t\t\t\treturn t('nextcloud-vue', 'Could not check')\n\t\t\t}\n\t\t\t// `escape: false`: the result is rendered through `{{ }}`, which escapes.\n\t\t\treturn t('nextcloud-vue', 'Could not check: {subject}', { subject: title }, undefined, { escape: false })\n\t\t},\n\n\t\t/**\n\t\t * The reason the check failed, for the tooltip and for assistive\n\t\t * technology.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tfailureTooltip() {\n\t\t\tif (!this.conditionFailureReason) {\n\t\t\t\treturn t('nextcloud-vue', 'The check failed.')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'The check failed: {reason}', { reason: this.conditionFailureReason }, undefined, { escape: false })\n\t\t},\n\n\t\t/** Whether the banner navigates on click (route set + router present). */\n\t\tclickable() {\n\t\t\treturn !!this.resolvedRoute && !!this.$router\n\t\t},\n\n\t\t/** The click-through route as a router location (a string is a route name). */\n\t\trouteLocation() {\n\t\t\tconst route = this.resolvedRoute\n\t\t\treturn typeof route === 'string' ? { name: route } : route\n\t\t},\n\n\t\t/** The href of the click-through link. */\n\t\trouteHref() {\n\t\t\treturn resolveHref(this.routeLocation, this.$router)\n\t\t},\n\t},\n\n\twatch: {\n\t\tresolvedVisibleWhen: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.evaluateCondition()\n\t\t\t},\n\t\t},\n\n\t\tresolvedConditionOutcome() {\n\t\t\tthis.evaluateCondition()\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\t/**\n\t\t * Replace `{value}` in a text by the value the predicate read, once\n\t\t * one is known.\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {string} The text with the value filled in.\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tfillValue(text) {\n\t\t\tif (this.conditionValue === null || !text.includes('{value}')) {\n\t\t\t\treturn text\n\t\t\t}\n\t\t\treturn text.replaceAll('{value}', String(this.conditionValue))\n\t\t},\n\n\t\t/**\n\t\t * Run an attention-card action: route an in-app link, leave an\n\t\t * external link to the browser, and emit `action` for a button.\n\t\t *\n\t\t * @param {MouseEvent} event The click.\n\t\t * @param {object} action The resolved action.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tonActionClick(event, action) {\n\t\t\tif (action.target) {\n\t\t\t\tfollowLinkClick(event, action.target, this.$router)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!action.href) {\n\t\t\t\tthis.$emit('action', { id: action.id, label: action.label, index: action.index })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Resolve the `visibleWhen` verdict. A host-injected\n\t\t * `conditionOutcome` wins outright — the host already ran the\n\t\t * predicate, so re-fetching would double the request and flash the\n\t\t * banner hidden until its own copy resolved. Otherwise evaluate\n\t\t * through the shared util primitives (endpoint / OpenRegister-source\n\t\t * modes — the Wave-1 banner shape is the canonical one, extracted to\n\t\t * `utils/visibleWhen.js` in Wave 3 so manifest actions reuse it),\n\t\t * keeping the read VALUE for the `{value}` text placeholder.\n\t\t * Fail-safe: any fetch/shape error leaves the banner hidden. The\n\t\t * failure is kept (`conditionFailed`, with its reason) so an\n\t\t * attention card can say it could not check.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check\n\t\t */\n\t\tasync evaluateCondition() {\n\t\t\tconst outcome = this.resolvedConditionOutcome\n\t\t\tif (outcome) {\n\t\t\t\tthis.conditionMet = outcome.met === true\n\t\t\t\tthis.conditionValue = outcome.value !== undefined ? outcome.value : null\n\t\t\t\tthis.conditionFailed = outcome.failed === true\n\t\t\t\tthis.conditionFailureReason = outcome.failed === true && typeof outcome.reason === 'string' ? outcome.reason : ''\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst cond = this.resolvedVisibleWhen\n\t\t\tif (!cond) {\n\t\t\t\tthis.conditionMet = null\n\t\t\t\tthis.conditionValue = null\n\t\t\t\tthis.conditionFailed = false\n\t\t\t\tthis.conditionFailureReason = ''\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst value = await readVisibleWhenValue(cond)\n\t\t\t\tthis.conditionValue = value\n\t\t\t\tthis.conditionMet = compareVisibleWhen(value, cond.op || 'eq', cond.value)\n\t\t\t\tthis.conditionFailed = false\n\t\t\t\tthis.conditionFailureReason = ''\n\t\t\t} catch (error) {\n\t\t\t\t// Still hidden: a banner that cannot tell whether it applies\n\t\t\t\t// should not claim attention. But not silently, because a\n\t\t\t\t// failed count and \"nothing to report\" look the same on screen.\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn('[CnBannerWidget] visibleWhen could not be evaluated, banner hidden:', error?.message || error)\n\t\t\t\tthis.conditionMet = false\n\t\t\t\tthis.conditionValue = null\n\t\t\t\tthis.conditionFailed = true\n\t\t\t\tthis.conditionFailureReason = typeof error?.message === 'string' ? error.message : String(error ?? '')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Route a plain click on the link; a modified click opens the href\n\t\t * in the browser.\n\t\t *\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonClick(event) {\n\t\t\tif (!this.clickable) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfollowLinkClick(event, this.routeLocation, this.$router)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-banner-widget {\n\twidth: 100%;\n}\n\n.cn-banner-widget__card {\n\tmargin: 0;\n}\n\n.cn-banner-widget__text--clickable {\n\tcursor: pointer;\n\ttext-decoration: underline;\n\tcolor: inherit;\n}\n\n/* The check failed. Muted text, no fill, no severity colour: it is a note\n that something is not known, not a warning. */\n.cn-banner-widget--unchecked {\n\tbox-sizing: border-box;\n\tmargin: 0;\n\tpadding: 8px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tcursor: help;\n}\n\n/* The reason, for assistive technology; sighted readers get the tooltip. */\n.cn-banner-widget__unchecked-reason {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tmargin: -1px;\n\tpadding: 0;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\twhite-space: nowrap;\n\tborder: 0;\n}\n\n/* Attention card. The severity edge is an inset shadow, so it takes no width. */\n.cn-banner-widget--attention {\n\t--cn-banner-accent: var(--color-primary-element);\n\t--cn-banner-accent-text: var(--color-primary-element);\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 16px 24px;\n\tbox-sizing: border-box;\n\tpadding: 20px 24px 20px 28px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-container, var(--border-radius-large, 12px));\n\tbackground: var(--color-main-background);\n\tbox-shadow: inset 4px 0 0 0 var(--cn-banner-accent);\n}\n\n.cn-banner-widget--attention.cn-banner-widget--warning {\n\t--cn-banner-accent: var(--color-element-warning, var(--color-warning));\n\t--cn-banner-accent-text: var(--color-text-warning, var(--color-warning-text, var(--color-main-text)));\n}\n\n.cn-banner-widget--attention.cn-banner-widget--error {\n\t--cn-banner-accent: var(--color-element-error, var(--color-error));\n\t--cn-banner-accent-text: var(--color-text-error, var(--color-error-text));\n}\n\n.cn-banner-widget--attention.cn-banner-widget--success {\n\t--cn-banner-accent: var(--color-element-success, var(--color-success));\n\t--cn-banner-accent-text: var(--color-text-success, var(--color-success-text));\n}\n\n.cn-banner-widget__body {\n\tdisplay: flex;\n\tflex: 1 1 320px;\n\tflex-direction: column;\n\tgap: 6px;\n\tmin-width: 0;\n}\n\n.cn-banner-widget__kicker {\n\tmargin: 0;\n\tfont-size: 0.85em;\n\tfont-weight: 700;\n\tletter-spacing: 0.06em;\n\ttext-transform: uppercase;\n\tcolor: var(--cn-banner-accent-text);\n}\n\n.cn-banner-widget__title {\n\tmargin: 0;\n\tfont-size: 1.3em;\n\tfont-weight: 700;\n\tline-height: 1.3;\n\tcolor: var(--color-main-text);\n}\n\n.cn-banner-widget__reason {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-banner-widget__actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 10px;\n}\n\n.cn-banner-widget__action {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tbox-sizing: border-box;\n\tmin-height: var(--default-clickable-area, 40px);\n\tmargin: 0;\n\tpadding: 0 16px;\n\tborder: 1px solid var(--color-border-maxcontrast);\n\tborder-radius: var(--border-radius-element, var(--border-radius-large, 8px));\n\tfont: inherit;\n\tfont-weight: 600;\n\ttext-decoration: none;\n\tcursor: pointer;\n}\n\n.cn-banner-widget__action--secondary {\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-banner-widget__action--secondary:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-banner-widget__action--primary {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n}\n\n.cn-banner-widget__action--primary:hover {\n\tborder-color: var(--color-primary-element-hover);\n\tbackground: var(--color-primary-element-hover);\n}\n\n.cn-banner-widget__action:focus-visible {\n\toutline: 2px solid var(--color-main-text);\n\toutline-offset: 2px;\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createElementVNode","_toDisplayString","_openBlock","_Fragment","_renderList","_resolveDynamicComponent","_createTextVNode","_createVNode","_createBlock"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import script from './CnBrandBar.vue2.js';
|
|
2
|
+
import { render } from './CnBrandBar.vue3.js';
|
|
3
|
+
|
|
4
|
+
script.render = render;
|
|
5
|
+
script.__file = "src/components/CnBrandBar/CnBrandBar.vue";
|
|
6
|
+
const __sfc_main = script;
|
|
7
|
+
|
|
8
|
+
export { __sfc_main as default };
|
|
9
|
+
//# sourceMappingURL=CnBrandBar.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnBrandBar.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { markRaw } from 'vue';
|
|
2
|
+
|
|
3
|
+
const HOST_CLASS = 'cn-brand-bar-host';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The brand block in Nextcloud's header.
|
|
7
|
+
*
|
|
8
|
+
* @event unavailable Emitted when there is no `#header` to put the block in (or it disappears), so the host lets the navigation draw it.
|
|
9
|
+
*
|
|
10
|
+
* @spec openspec/changes/screens-brand-block-top-bar/specs/layout-components/spec.md#requirement-the-board-look-draws-the-brand-block-in-the-nextcloud-header
|
|
11
|
+
*/
|
|
12
|
+
var script = {
|
|
13
|
+
name: 'CnBrandBar',
|
|
14
|
+
|
|
15
|
+
props: {
|
|
16
|
+
/**
|
|
17
|
+
* The resolved brand: `{ logo, emblem, name, caption, alt }`. `emblem`
|
|
18
|
+
* is a URL, or `true` for the theme's `--nldesign-emblem-url`.
|
|
19
|
+
*
|
|
20
|
+
* @type {{logo?: string, emblem?: (string|boolean), name?: string, caption?: string, alt?: string}}
|
|
21
|
+
*/
|
|
22
|
+
brand: {
|
|
23
|
+
type: Object,
|
|
24
|
+
required: true,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
emits: ['unavailable'],
|
|
29
|
+
|
|
30
|
+
data() {
|
|
31
|
+
return { host: null, observer: null }
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
mounted() {
|
|
35
|
+
this.attach();
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
beforeUnmount() {
|
|
39
|
+
this.detach();
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
methods: {
|
|
43
|
+
/**
|
|
44
|
+
* Insert the host as the first child of `#header` and watch the header.
|
|
45
|
+
* Emits `unavailable` when there is no header.
|
|
46
|
+
*
|
|
47
|
+
* @return {void}
|
|
48
|
+
*/
|
|
49
|
+
attach() {
|
|
50
|
+
const header = document.getElementById('header');
|
|
51
|
+
if (!header) {
|
|
52
|
+
/**
|
|
53
|
+
* @event unavailable Emitted when there is no `#header` to put the block in.
|
|
54
|
+
*/
|
|
55
|
+
this.$emit('unavailable');
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
this.host = markRaw(document.createElement('div'));
|
|
59
|
+
this.host.className = HOST_CLASS;
|
|
60
|
+
header.insertBefore(this.host, header.firstChild);
|
|
61
|
+
if (typeof MutationObserver === 'function') {
|
|
62
|
+
this.observer = new MutationObserver(() => this.reattach());
|
|
63
|
+
this.observer.observe(header, { childList: true });
|
|
64
|
+
if (header.parentNode) {
|
|
65
|
+
this.observer.observe(header.parentNode, { childList: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Put the same host back when Nextcloud re-rendered the header and took
|
|
72
|
+
* it out. Never creates a second host; when the header is gone for good
|
|
73
|
+
* it hands the block back to the navigation.
|
|
74
|
+
*
|
|
75
|
+
* @return {void}
|
|
76
|
+
*/
|
|
77
|
+
reattach() {
|
|
78
|
+
if (!this.host) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
const header = document.getElementById('header');
|
|
82
|
+
if (!header) {
|
|
83
|
+
this.$emit('unavailable');
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
if (this.host.parentNode !== header) {
|
|
87
|
+
header.insertBefore(this.host, header.firstChild);
|
|
88
|
+
this.observer?.observe(header, { childList: true });
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Stop watching and remove only the host this component added.
|
|
94
|
+
*
|
|
95
|
+
* @return {void}
|
|
96
|
+
*/
|
|
97
|
+
detach() {
|
|
98
|
+
this.observer?.disconnect();
|
|
99
|
+
this.observer = null;
|
|
100
|
+
this.host?.remove();
|
|
101
|
+
this.host = null;
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export { script as default };
|
|
107
|
+
//# sourceMappingURL=CnBrandBar.vue2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnBrandBar.vue2.js","sources":["../../../../src/components/CnBrandBar/CnBrandBar.vue"],"sourcesContent":["<!--\n CnBrandBar — the brand block in Nextcloud's own header (screens-brand-block-top-bar).\n\n Draws the 237px brand block (emblem, organisation, app name, and a 1px\n divider at its end) at the START of `#header`, before the app menu, under the\n board look. CnAppRoot mounts it when a brand is declared and `#header` exists.\n\n `#header` is Nextcloud's and offers no extension point for apps, so this is\n the one place the library reaches into it, and it does so defensively:\n - it adds ONE node of its own (a host element) as the first child of the\n header, and teleports the block into it; it never moves, edits or removes\n a node Nextcloud drew;\n - it re-inserts its own host when Nextcloud re-renders the header (a\n MutationObserver on the header and its parent), never a second one;\n - with no `#header` it renders nothing and emits `unavailable`, so\n CnAppRoot lets the navigation draw the block instead.\n\n Sizes come from the board: `dossiq/DqKop`.\n-->\n<template>\n\t<Teleport v-if=\"host\" :to=\"host\">\n\t\t<div class=\"cn-brand-bar\" data-testid=\"cn-brand-bar\">\n\t\t\t<div class=\"cn-brand-bar__brand\" data-testid=\"cn-brand-bar-brand\">\n\t\t\t\t<!-- Decorative beside a name: the name already says whose app this is. -->\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"typeof brand.emblem === 'string' && brand.emblem !== ''\"\n\t\t\t\t\tclass=\"cn-brand-bar__emblem\"\n\t\t\t\t\tdata-testid=\"cn-brand-bar-emblem\"\n\t\t\t\t\t:src=\"brand.emblem\"\n\t\t\t\t\t:alt=\"brand.name ? '' : brand.alt\">\n\t\t\t\t<span\n\t\t\t\t\tv-else-if=\"brand.emblem === true\"\n\t\t\t\t\tclass=\"cn-brand-bar__emblem cn-brand-bar__emblem--theme\"\n\t\t\t\t\tdata-testid=\"cn-brand-bar-emblem\"\n\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t<img\n\t\t\t\t\tv-else-if=\"brand.logo\"\n\t\t\t\t\tclass=\"cn-brand-bar__emblem\"\n\t\t\t\t\t:src=\"brand.logo\"\n\t\t\t\t\t:alt=\"brand.name ? '' : brand.alt\">\n\t\t\t\t<span v-if=\"brand.name || brand.caption\" class=\"cn-brand-bar__text\">\n\t\t\t\t\t<span v-if=\"brand.caption\" class=\"cn-brand-bar__caption\">{{ brand.caption }}</span>\n\t\t\t\t\t<strong v-if=\"brand.name\" class=\"cn-brand-bar__name\">{{ brand.name }}</strong>\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t\t<span class=\"cn-brand-bar__divider\" aria-hidden=\"true\" />\n\t\t</div>\n\t</Teleport>\n</template>\n\n<script>\nimport { markRaw } from 'vue'\n\nconst HOST_CLASS = 'cn-brand-bar-host'\n\n/**\n * The brand block in Nextcloud's header.\n *\n * @event unavailable Emitted when there is no `#header` to put the block in (or it disappears), so the host lets the navigation draw it.\n *\n * @spec openspec/changes/screens-brand-block-top-bar/specs/layout-components/spec.md#requirement-the-board-look-draws-the-brand-block-in-the-nextcloud-header\n */\nexport default {\n\tname: 'CnBrandBar',\n\n\tprops: {\n\t\t/**\n\t\t * The resolved brand: `{ logo, emblem, name, caption, alt }`. `emblem`\n\t\t * is a URL, or `true` for the theme's `--nldesign-emblem-url`.\n\t\t *\n\t\t * @type {{logo?: string, emblem?: (string|boolean), name?: string, caption?: string, alt?: string}}\n\t\t */\n\t\tbrand: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\t},\n\n\temits: ['unavailable'],\n\n\tdata() {\n\t\treturn { host: null, observer: null }\n\t},\n\n\tmounted() {\n\t\tthis.attach()\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.detach()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Insert the host as the first child of `#header` and watch the header.\n\t\t * Emits `unavailable` when there is no header.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tattach() {\n\t\t\tconst header = document.getElementById('header')\n\t\t\tif (!header) {\n\t\t\t\t/**\n\t\t\t\t * @event unavailable Emitted when there is no `#header` to put the block in.\n\t\t\t\t */\n\t\t\t\tthis.$emit('unavailable')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.host = markRaw(document.createElement('div'))\n\t\t\tthis.host.className = HOST_CLASS\n\t\t\theader.insertBefore(this.host, header.firstChild)\n\t\t\tif (typeof MutationObserver === 'function') {\n\t\t\t\tthis.observer = new MutationObserver(() => this.reattach())\n\t\t\t\tthis.observer.observe(header, { childList: true })\n\t\t\t\tif (header.parentNode) {\n\t\t\t\t\tthis.observer.observe(header.parentNode, { childList: true })\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Put the same host back when Nextcloud re-rendered the header and took\n\t\t * it out. Never creates a second host; when the header is gone for good\n\t\t * it hands the block back to the navigation.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\treattach() {\n\t\t\tif (!this.host) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst header = document.getElementById('header')\n\t\t\tif (!header) {\n\t\t\t\tthis.$emit('unavailable')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.host.parentNode !== header) {\n\t\t\t\theader.insertBefore(this.host, header.firstChild)\n\t\t\t\tthis.observer?.observe(header, { childList: true })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Stop watching and remove only the host this component added.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tdetach() {\n\t\t\tthis.observer?.disconnect()\n\t\t\tthis.observer = null\n\t\t\tthis.host?.remove()\n\t\t\tthis.host = null\n\t\t},\n\t},\n}\n</script>\n\n<style>\n/* The host is one flex item at the start of Nextcloud's header. */\n.cn-brand-bar-host {\n\tflex: none;\n\tdisplay: flex;\n\talign-items: center;\n\theight: 100%;\n\tmargin-inline-end: 12px;\n}\n\n.cn-brand-bar {\n\tbox-sizing: border-box;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\twidth: var(--cn-board-brand-width, 237px);\n}\n\n.cn-brand-bar__brand {\n\tflex: 1 1 0;\n\tbox-sizing: border-box;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tmin-width: 0;\n\tmin-height: 48px;\n\tmax-height: 100%;\n\tpadding: 0 6px;\n\tborder-radius: 8px;\n\tcolor: var(--color-background-plain-text, var(--color-main-text));\n}\n\n.cn-brand-bar__emblem {\n\tflex: none;\n\theight: var(--cn-nav-emblem-size, 34px);\n\twidth: auto;\n\tobject-fit: contain;\n}\n\n.cn-brand-bar__emblem--theme {\n\tdisplay: inline-block;\n\twidth: var(--cn-nav-emblem-size, 34px);\n\tbackground: var(--nldesign-emblem-url) center / contain no-repeat;\n}\n\n.cn-brand-bar__text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tline-height: 1.15;\n\tmin-width: 0;\n}\n\n.cn-brand-bar__caption {\n\tfont-size: 13px;\n\twhite-space: nowrap;\n\topacity: 0.8;\n}\n\n.cn-brand-bar__name {\n\tfont-size: 18px;\n\tfont-weight: 700;\n\twhite-space: nowrap;\n}\n\n.cn-brand-bar__divider {\n\tflex: none;\n\twidth: 1px;\n\theight: 28px;\n\tbackground: currentColor;\n\topacity: 0.25;\n\tcolor: var(--color-background-plain-text, var(--color-main-text));\n}\n</style>\n"],"names":[],"mappings":";;AAqDA,MAAM,aAAa,oBAAkB;;AAErC;AACC;AACA;;AAEA;AACA;;AAED,aAAe;AACd,CAAA,IAAI,EAAE,YAAY;;AAElB,CAAA,KAAK,EAAE;AACN;;;AAGC;AACA;;AAED,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;GACZ,QAAQ,EAAE,IAAI;GACd;EACD;;AAED,CAAA,KAAK,EAAE,CAAC,aAAa,CAAC;;AAEtB,CAAA,IAAI,GAAG;AACN,EAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAK,EAAA;EACpC;;AAED,CAAA,OAAO,GAAG;EACT,IAAI,CAAC,MAAM,GAAC;EACZ;;AAED,CAAA,aAAa,GAAG;EACf,IAAI,CAAC,MAAM,GAAC;EACZ;;AAED,CAAA,OAAO,EAAE;AACR;;AAEC;AACA;;;AAGD,EAAA,MAAM,GAAG;AACR,GAAA,MAAM,SAAS,QAAQ,CAAC,cAAc,CAAC,QAAQ,EAAA;GAC/C,IAAI,CAAC,MAAM,EAAE;AACZ;AACC;;IAED,IAAI,CAAC,KAAK,CAAC,aAAa,EAAA;IACxB,MAAK;AACN,IAAA;GACA,IAAI,CAAC,OAAO,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,EAAA;GACjD,IAAI,CAAC,IAAI,CAAC,SAAU,GAAE,WAAS;GAC/B,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,EAAA;AAChD,GAAA,IAAI,OAAO,gBAAiB,KAAI,UAAU,EAAE;IAC3C,IAAI,CAAC,WAAW,IAAI,gBAAgB,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,EAAA;AAC1D,IAAA,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAA;AACjD,IAAA,IAAI,MAAM,CAAC,UAAU,EAAE;KACtB,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAG,EAAG,EAAA;AAC7D,KAAA;AACD,IAAA;GACA;;AAED;;;AAGC;AACA;;;EAGD,QAAQ,GAAG;AACV,GAAA,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;IACf,MAAK;AACN,IAAA;AACA,GAAA,MAAM,SAAS,QAAQ,CAAC,cAAc,CAAC,QAAQ,EAAA;GAC/C,IAAI,CAAC,MAAM,EAAE;IACZ,IAAI,CAAC,KAAK,CAAC,aAAa,EAAA;IACxB,MAAK;AACN,IAAA;AACA,GAAA,IAAI,IAAI,CAAC,IAAI,CAAC,eAAe,MAAM,EAAE;IACpC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,EAAA;IAChD,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAA;AACnD,IAAA;GACA;;AAED;;AAEC;;;AAGD,EAAA,MAAM,GAAG;GACR,IAAI,CAAC,QAAQ,EAAE,UAAU,GAAC;AAC1B,GAAA,IAAI,CAAC,QAAO,GAAI,KAAG;AACnB,GAAA,IAAI,CAAC,IAAI,EAAE,MAAM,GAAC;GAClB,IAAI,CAAC,IAAK,GAAE,KAAG;GACf;EACD;AACF;;;;"}
|