@conduction/nextcloud-vue 2.93.0 → 2.95.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/CnActionsMenu/CnActionsMenu.vue2.js +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +2 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +28 -8
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +37 -2
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +46 -28
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +17 -10
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue2.js.map +1 -1
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue3.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +139 -3
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js +60 -12
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +91 -36
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +115 -72
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +32 -3
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +7 -7
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +56 -16
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue.js +9 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue.js.map +1 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue2.js +240 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue3.js +65 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnDeviceFramesWidget/index.js +30 -0
- package/dist/esm/components/CnDeviceFramesWidget/index.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +17 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +30 -31
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue.js +6 -0
- package/dist/esm/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue.js.map +1 -0
- package/dist/esm/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue2.js +90 -0
- package/dist/esm/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue2.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +141 -7
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue.js +8 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js +114 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js +45 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue2.js +38 -2
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue2.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue3.js +12 -2
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue3.js.map +1 -1
- package/dist/esm/components/CnPagination/CnPagination.vue2.js.map +1 -1
- package/dist/esm/components/CnPagination/CnPagination.vue3.js +56 -51
- package/dist/esm/components/CnPagination/CnPagination.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +51 -4
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +20 -2
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +230 -25
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +447 -221
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +24 -0
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js +3 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js +39 -2
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +85 -8
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +37 -9
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -3
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +20 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +31 -20
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +9 -0
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +18 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +18 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +712 -330
- package/dist/esm/utils/dueText.js +49 -8
- package/dist/esm/utils/dueText.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +8275 -6302
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +712 -330
- package/l10n/en.json +18 -1
- package/l10n/nl.json +18 -1
- package/package.json +1 -1
- package/src/components/CnActionsMenu/CnActionsMenu.vue +3 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +39 -1
- package/src/components/CnAppRoot/CnAppRoot.vue +20 -0
- package/src/components/CnBrandBar/CnBrandBar.vue +5 -4
- package/src/components/CnCountdownWidget/CnCountdownWidget.vue +222 -6
- package/src/components/CnDashboardPage/CnDashboardPage.vue +96 -2
- package/src/components/CnDataTable/CnDataTable.md +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +39 -10
- package/src/components/CnDetailPage/CnDetailPage.vue +55 -15
- package/src/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue +360 -0
- package/src/components/CnDeviceFramesWidget/index.js +29 -0
- package/src/components/CnFactsWidget/CnFactsWidget.vue +46 -2
- package/src/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue +107 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +144 -7
- package/src/components/CnPageHeader/CnHeaderTrail.vue +140 -0
- package/src/components/CnPageHeader/CnPageHeader.md +11 -0
- package/src/components/CnPageHeader/CnPageHeader.vue +42 -0
- package/src/components/CnPagination/CnPagination.vue +10 -2
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +84 -4
- package/src/components/CnRegisterMapping/CnRegisterMapping.md +10 -0
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +511 -28
- package/src/components/CnSettingsPage/CnSettingsPage.vue +26 -0
- package/src/components/CnTabs/CnTabs.vue +39 -2
- package/src/components/CnTabsWidget/CnTabsWidget.vue +106 -13
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +4 -3
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +8 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +31 -2
- package/src/components/index.js +1 -0
- package/src/composables/indexSources.js +9 -0
- package/src/css/dialog.css +14 -0
- package/src/css/look-board-index.css +27 -1
- package/src/css/look-board.css +27 -0
- package/src/css/page-header.css +16 -0
- package/src/index.js +1 -0
- package/src/schemas/app-manifest-v2.schema.json +27 -15
- package/src/utils/dueText.js +49 -8
- package/src/utils/libraryWidgetKeys.js +1 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -1 +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
|
|
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 brand block (a 237px brand link with the emblem, organisation and\n app name, then a 1px divider 12px on) 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}\n\n/* The brand link is 237px; the divider follows it, 12px on. */\n.cn-brand-bar__brand {\n\tflex: none;\n\twidth: var(--cn-board-brand-width, 237px);\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;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnBrandBar.vue3.js","sources":["../../../../src/components/CnBrandBar/CnBrandBar.vue?vue&type=template&id=
|
|
1
|
+
{"version":3,"file":"CnBrandBar.vue3.js","sources":["../../../../src/components/CnBrandBar/CnBrandBar.vue?vue&type=template&id=639a1bad&lang.js"],"sourcesContent":["<!--\n CnBrandBar — the brand block in Nextcloud's own header (screens-brand-block-top-bar).\n\n Draws the brand block (a 237px brand link with the emblem, organisation and\n app name, then a 1px divider 12px on) 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}\n\n/* The brand link is 237px; the divider follows it, 12px on. */\n.cn-brand-bar__brand {\n\tflex: none;\n\twidth: var(--cn-board-brand-width, 237px);\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":["_createBlock","_createElementVNode","_createElementBlock"],"mappings":";;;AAwCO,EAAA,KAAK,EAAC,cAAc;AAAC,EAAA,aAAW,EAAC,cAAc;;;AAC9C,EAAA,KAAK,EAAC,qBAAqB;AAAC,EAAA,aAAW,EAAC,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;UAFnD,KAAK,CAAA,IAAA;oBAAjBA;;AAAmB,QAAA,EAAC,EAAI,KAAG,CAAA,IAAA;;AAC5B,QAAAC,kBAAA,QAAA;AACC,UAAAA,kBAAA,QAAA;AAGG,YAAA,CAAA,OAAA,MAAA,CAAA,KAAA,CAAA,MAAA,KAAA,QAAA,IAAA,MAAA,CAAA,KAAA,CAAA,MAAA,KAAA,EAAA;8BADJC;;AAEC,kBAAA,KAAK,EAAC,sBAAsB;AAC5B,kBAAA,aAAE,EAAA,qBAAA;kBACD,GAAG,EAAE,MAAA,CAAA,KAAA,CAAA,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -2,7 +2,7 @@ import script from './CnCountdownWidget.vue2.js';
|
|
|
2
2
|
import { render } from './CnCountdownWidget.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-22ccd275";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -2,7 +2,10 @@ import { translate } from '@nextcloud/l10n';
|
|
|
2
2
|
import { inject } from 'vue';
|
|
3
3
|
import __sfc_main from '../CnWidgetGrid/CnWidgetIcon.vue.js';
|
|
4
4
|
import { getByPath } from '../../composables/useEndpointSource.js';
|
|
5
|
+
import { normalizeLook } from '../../composables/useLook.js';
|
|
6
|
+
import { dispatchAction } from '../../utils/actionsDispatcher.js';
|
|
5
7
|
import { resolveObjectTokenContext } from '../../utils/detailObjectContext.js';
|
|
8
|
+
import { resolveDeepTokens } from '../../utils/resolveFilterTokens.js';
|
|
6
9
|
import { userLocale } from '../../utils/userLocale.js';
|
|
7
10
|
|
|
8
11
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
@@ -80,8 +83,15 @@ var script = {
|
|
|
80
83
|
* - `showDate` — render the absolute date underneath (default true).
|
|
81
84
|
* - `settledField` — property on the record that is truthy once the work is done; the tile then reads `settledLabel` and never counts overdue.
|
|
82
85
|
* - `settledLabel` — the headline of a settled record, run through the host's translate function (default "Closed").
|
|
86
|
+
* - `daysLabel` — `{ one, other }` with `{count}`, the headline while days remain ("27 dagen").
|
|
87
|
+
* - `caption` — the line under the headline instead of the date, with `{date}` (the long date) and `{count}` (days left).
|
|
88
|
+
* - `startField` — property holding the start of the term; a bar then shows how much of it has passed.
|
|
89
|
+
* - `action` — one manifest action under the card (`{ label, type, variant?, ... }`, the headerActions shape; `variant: "primary"` draws it in the primary tint).
|
|
90
|
+
* - `emptyLabel` — the headline while the date is unset (default a dash).
|
|
91
|
+
* - `emptyCaption` — the line under it while the date is unset, with `{date}` read from `emptyDateField`.
|
|
92
|
+
* - `facts` — `[{ label, field }]` rows under the caption.
|
|
83
93
|
*
|
|
84
|
-
* @type {{ field?: string, label?: string, icon?: string, thresholds?: {warn?: number, danger?: number}, showDate?: boolean, settledField?: string, settledLabel?: string }}
|
|
94
|
+
* @type {{ field?: string, label?: string, icon?: string, thresholds?: {warn?: number, danger?: number}, showDate?: boolean, settledField?: string, settledLabel?: string, daysLabel?: {one?: string, other?: string}, caption?: string, startField?: string, action?: object, emptyLabel?: string, emptyCaption?: string, emptyDateField?: string, facts?: Array<{label: string, field: string}> }}
|
|
85
95
|
*/
|
|
86
96
|
content: {
|
|
87
97
|
type: Object,
|
|
@@ -105,6 +115,8 @@ var script = {
|
|
|
105
115
|
cnObjectContextInjected: inject('cnObjectContext', null),
|
|
106
116
|
cnDetailObjectContextInjected: inject('cnDetailObjectContext', null),
|
|
107
117
|
cnTranslate: inject('cnTranslate', null),
|
|
118
|
+
cnLook: inject('cnLook', 'nextcloud'),
|
|
119
|
+
cnDispatchAction: inject('cnDispatchAction', null),
|
|
108
120
|
}
|
|
109
121
|
},
|
|
110
122
|
|
|
@@ -196,7 +208,7 @@ var script = {
|
|
|
196
208
|
}
|
|
197
209
|
const days = this.daysRemaining;
|
|
198
210
|
if (days === null) {
|
|
199
|
-
return '—'
|
|
211
|
+
return this.content?.emptyLabel ? this.translate(this.content.emptyLabel) : '—'
|
|
200
212
|
}
|
|
201
213
|
if (days < 0) {
|
|
202
214
|
const overdue = Math.abs(days);
|
|
@@ -207,12 +219,89 @@ var script = {
|
|
|
207
219
|
if (days === 0) {
|
|
208
220
|
return translate('nextcloud-vue', 'Due today')
|
|
209
221
|
}
|
|
222
|
+
const labels = this.content?.daysLabel;
|
|
223
|
+
if (labels && typeof labels === 'object' && (labels.one || labels.other)) {
|
|
224
|
+
const template = this.translate((days === 1 ? labels.one : labels.other) || labels.other || labels.one);
|
|
225
|
+
return template.replace(/\{count\}/g, String(days))
|
|
226
|
+
}
|
|
210
227
|
if (days === 1) {
|
|
211
228
|
return translate('nextcloud-vue', '1 day left')
|
|
212
229
|
}
|
|
213
230
|
return translate('nextcloud-vue', '{count} days left', { count: days })
|
|
214
231
|
},
|
|
215
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Whether the app takes the board look: no icon circle.
|
|
235
|
+
*
|
|
236
|
+
* @return {boolean} True in the board look.
|
|
237
|
+
* @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card
|
|
238
|
+
*/
|
|
239
|
+
isBoardLook() {
|
|
240
|
+
return normalizeLook(this.cnLook) === 'board'
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* How much of the term has passed, from `startField` to the date, in
|
|
245
|
+
* whole days and as a percentage (0 to 100), or null without a usable
|
|
246
|
+
* start.
|
|
247
|
+
*
|
|
248
|
+
* @return {number|null} The share passed.
|
|
249
|
+
* @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card
|
|
250
|
+
*/
|
|
251
|
+
progress() {
|
|
252
|
+
const field = this.content?.startField;
|
|
253
|
+
if (!field || !this.record || !this.targetDate || this.isSettled) {
|
|
254
|
+
return null
|
|
255
|
+
}
|
|
256
|
+
const raw = getByPath(this.record, field);
|
|
257
|
+
const start = raw === null || raw === undefined || raw === '' ? null : new Date(raw);
|
|
258
|
+
if (!start || Number.isNaN(start.getTime())) {
|
|
259
|
+
return null
|
|
260
|
+
}
|
|
261
|
+
const startOfDay = (d) => Date.UTC(d.getFullYear(), d.getMonth(), d.getDate());
|
|
262
|
+
const span = startOfDay(this.targetDate) - startOfDay(start);
|
|
263
|
+
if (span <= 0) {
|
|
264
|
+
return null
|
|
265
|
+
}
|
|
266
|
+
return Math.min(100, Math.max(0, ((startOfDay(new Date()) - startOfDay(start)) / span) * 100))
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The `facts` rows under the caption (`[{ label, field }]`), with
|
|
271
|
+
* their values read off the record; a row without a value is left out.
|
|
272
|
+
*
|
|
273
|
+
* @return {Array<{label: string, value: string}>} The rows.
|
|
274
|
+
* @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card
|
|
275
|
+
*/
|
|
276
|
+
facts() {
|
|
277
|
+
const rows = Array.isArray(this.content?.facts) ? this.content.facts : [];
|
|
278
|
+
return rows
|
|
279
|
+
.filter((row) => row && typeof row.label === 'string' && typeof row.field === 'string')
|
|
280
|
+
.map((row) => {
|
|
281
|
+
const value = this.record ? getByPath(this.record, row.field) : null;
|
|
282
|
+
return { label: this.translate(row.label), value: value === null || value === undefined ? '' : this.translate(String(value)) }
|
|
283
|
+
})
|
|
284
|
+
.filter((row) => row.value !== '')
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
/** @return {string} The accessible name of the progress bar. */
|
|
288
|
+
progressLabel() {
|
|
289
|
+
return translate('nextcloud-vue', 'Time passed')
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* The label of `content.action`, translated, or ''.
|
|
294
|
+
*
|
|
295
|
+
* @return {string} The label.
|
|
296
|
+
* @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card
|
|
297
|
+
*/
|
|
298
|
+
actionLabel() {
|
|
299
|
+
const action = this.content?.action;
|
|
300
|
+
return action && typeof action === 'object' && typeof action.label === 'string' && action.label !== ''
|
|
301
|
+
? this.translate(action.label)
|
|
302
|
+
: ''
|
|
303
|
+
},
|
|
304
|
+
|
|
216
305
|
/**
|
|
217
306
|
* The absolute date under the headline, so the tile answers "how long"
|
|
218
307
|
* and "when" at once.
|
|
@@ -220,10 +309,21 @@ var script = {
|
|
|
220
309
|
* @return {string} The formatted date, or ''.
|
|
221
310
|
*/
|
|
222
311
|
subLabel() {
|
|
312
|
+
if (!this.targetDate && typeof this.content?.emptyCaption === 'string' && this.content.emptyCaption !== '') {
|
|
313
|
+
const raw = this.content.emptyDateField && this.record ? getByPath(this.record, this.content.emptyDateField) : null;
|
|
314
|
+
const when = raw ? new Date(raw) : null;
|
|
315
|
+
const date = when && !Number.isNaN(when.getTime()) ? when.toLocaleDateString(userLocale(), { day: 'numeric', month: 'long', year: 'numeric' }) : '';
|
|
316
|
+
return this.translate(this.content.emptyCaption).replace(/\{date\}/g, date)
|
|
317
|
+
}
|
|
223
318
|
if (!this.targetDate || this.content?.showDate === false) {
|
|
224
319
|
return ''
|
|
225
320
|
}
|
|
226
|
-
|
|
321
|
+
const date = this.targetDate.toLocaleDateString(userLocale(), { day: 'numeric', month: 'long', year: 'numeric' });
|
|
322
|
+
const caption = this.content?.caption;
|
|
323
|
+
if (typeof caption === 'string' && caption !== '') {
|
|
324
|
+
return this.translate(caption).replace(/\{date\}/g, date).replace(/\{count\}/g, String(this.daysRemaining ?? ''))
|
|
325
|
+
}
|
|
326
|
+
return date
|
|
227
327
|
},
|
|
228
328
|
|
|
229
329
|
/**
|
|
@@ -252,6 +352,9 @@ var script = {
|
|
|
252
352
|
|
|
253
353
|
/** @return {string} The value colour for the current band. */
|
|
254
354
|
valueColor() {
|
|
355
|
+
if (this.isBoardLook && this.variant === 'default') {
|
|
356
|
+
return 'var(--color-main-text)'
|
|
357
|
+
}
|
|
255
358
|
return VARIANT_COLORS[this.variant] || VARIANT_COLORS.default
|
|
256
359
|
},
|
|
257
360
|
|
|
@@ -270,6 +373,39 @@ var script = {
|
|
|
270
373
|
return { color: this.valueColor }
|
|
271
374
|
},
|
|
272
375
|
},
|
|
376
|
+
|
|
377
|
+
methods: {
|
|
378
|
+
/**
|
|
379
|
+
* The host's translate, or the text unchanged.
|
|
380
|
+
*
|
|
381
|
+
* @param {string} text The text.
|
|
382
|
+
* @return {string} The translation.
|
|
383
|
+
*/
|
|
384
|
+
translate(text) {
|
|
385
|
+
return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Dispatch `content.action` with the record's tokens resolved, through
|
|
390
|
+
* the page's dispatcher when there is one.
|
|
391
|
+
*
|
|
392
|
+
* @return {unknown}
|
|
393
|
+
* @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card
|
|
394
|
+
*/
|
|
395
|
+
runAction() {
|
|
396
|
+
const action = this.content?.action;
|
|
397
|
+
if (!action || typeof action !== 'object') {
|
|
398
|
+
return undefined
|
|
399
|
+
}
|
|
400
|
+
const ctx = resolveObjectTokenContext(this.cnObjectContextInjected, this.cnDetailObjectContextInjected) || {};
|
|
401
|
+
const tokenCtx = { ...ctx, object: this.record || ctx.object || null, objectId: this.record?.id ?? ctx.objectId ?? null };
|
|
402
|
+
const resolved = resolveDeepTokens(action, tokenCtx);
|
|
403
|
+
if (typeof this.cnDispatchAction === 'function') {
|
|
404
|
+
return this.cnDispatchAction(resolved)
|
|
405
|
+
}
|
|
406
|
+
return dispatchAction(resolved, { router: this.$router || null, tokenCtx })
|
|
407
|
+
},
|
|
408
|
+
},
|
|
273
409
|
};
|
|
274
410
|
|
|
275
411
|
export { script as default };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnCountdownWidget.vue2.js","sources":["../../../../src/components/CnCountdownWidget/CnCountdownWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-kpi-card cn-countdown-widget\" :class=\"`cn-countdown-widget--${variant}`\">\n\t\t<div class=\"cn-kpi-card__icon cn-countdown-widget__icon\" :style=\"iconCircleStyle\">\n\t\t\t<CnWidgetIcon :name=\"resolvedIcon\" :size=\"24\" />\n\t\t</div>\n\n\t\t<div class=\"cn-kpi-card__body\">\n\t\t\t<div v-if=\"resolvedLabel\" class=\"cn-kpi-card__title\">\n\t\t\t\t{{ resolvedLabel }}\n\t\t\t</div>\n\t\t\t<div class=\"cn-kpi-card__value cn-countdown-widget__value\" :style=\"{ color: valueColor }\">\n\t\t\t\t{{ headline }}\n\t\t\t</div>\n\t\t\t<div v-if=\"subLabel\" class=\"cn-countdown-widget__sub\">\n\t\t\t\t{{ subLabel }}\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { inject } from 'vue'\nimport CnWidgetIcon from '../CnWidgetGrid/CnWidgetIcon.vue'\nimport { getByPath } from '../../composables/useEndpointSource.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\nconst VARIANT_COLORS = Object.freeze({\n\tdefault: 'var(--color-primary-element)',\n\tsuccess: 'var(--color-success)',\n\twarning: 'var(--color-warning)',\n\terror: 'var(--color-error)',\n})\n\n/**\n * CnCountdownWidget — a KPI tile for how long is left until a date on the\n * bound record.\n *\n * Every other tile in the catalog answers \"how many\" by aggregating or calling\n * an endpoint. This one answers \"how long\", and it needs neither: the record is\n * already loaded by the detail page, so the tile reads the date straight off it\n * and does the arithmetic. No request, and nothing to go stale.\n *\n * It headlines the remaining time and carries the date itself underneath,\n * because \"12 days left\" and \"5 October\" answer different questions and a\n * handler asks both.\n *\n * ## Thresholds\n *\n * `thresholds` recolours the tile as the date approaches, in days:\n *\n * ```js\n * content: {\n * label: 'Time left',\n * field: 'deadline',\n * icon: 'ClockAlertOutline',\n * thresholds: { warn: 14, danger: 5 },\n * }\n * ```\n *\n * A date in the past reads \"overdue by N days\" in the error colour, never a\n * negative number: \"-3 days left\" is a puzzle, and an overdue case is the one\n * state a handler must not have to decode.\n *\n * ## A settled record\n *\n * `settledField` names a property on the record that is truthy once the work\n * the date belongs to is done, such as a case in its final status. A settled\n * record reads `settledLabel` (default \"Closed\") in no threshold colour, never\n * \"overdue\": a closed case missed nothing that can still be acted on, and a red\n * count of days on it sends a handler after a case that needs nobody.\n *\n * ```js\n * content: { field: 'deadline', settledField: 'isFinalStatus', settledLabel: 'Handled' }\n * ```\n *\n * ## An unset date\n *\n * Renders a dash and no threshold colour. A case with no deadline is not urgent\n * and is not on time; it simply has no deadline, and colouring it green would\n * claim otherwise.\n */\nexport default {\n\tname: 'CnCountdownWidget',\n\n\tcomponents: {\n\t\tCnWidgetIcon,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The tile's config.\n\t\t *\n\t\t * - `field` — property on the bound record holding the target date.\n\t\t * - `label` — the tile's title.\n\t\t * - `icon` — MDI icon name.\n\t\t * - `thresholds` — `{ warn, danger }` in days remaining.\n\t\t * - `showDate` — render the absolute date underneath (default true).\n\t\t * - `settledField` — property on the record that is truthy once the work is done; the tile then reads `settledLabel` and never counts overdue.\n\t\t * - `settledLabel` — the headline of a settled record, run through the host's translate function (default \"Closed\").\n\t\t *\n\t\t * @type {{ field?: string, label?: string, icon?: string, thresholds?: {warn?: number, danger?: number}, showDate?: boolean, settledField?: string, settledLabel?: string }}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * The bound record, when the surface passes it explicitly. Falls back to\n\t\t * the injected `cnObjectContext` a detail page provides.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup() {\n\t\treturn {\n\t\t\tcnObjectContextInjected: inject('cnObjectContext', null),\n\t\t\tcnDetailObjectContextInjected: inject('cnDetailObjectContext', null),\n\t\t\tcnTranslate: inject('cnTranslate', null),\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The record to read the date from.\n\t\t *\n\t\t * @return {object|null} The bound record, or null.\n\t\t */\n\t\trecord() {\n\t\t\tif (this.objectData) {\n\t\t\t\treturn this.objectData\n\t\t\t}\n\t\t\tconst ctx = resolveObjectTokenContext(this.cnObjectContextInjected, this.cnDetailObjectContextInjected)\n\t\t\treturn ctx?.object || null\n\t\t},\n\n\t\t/**\n\t\t * The target date, or null when unset or unparseable.\n\t\t *\n\t\t * @return {Date|null} The parsed date.\n\t\t */\n\t\ttargetDate() {\n\t\t\tconst field = this.content?.field\n\t\t\tif (!this.record || !field) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst raw = getByPath(this.record, field)\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst parsed = new Date(raw)\n\t\t\treturn Number.isNaN(parsed.getTime()) ? null : parsed\n\t\t},\n\n\t\t/**\n\t\t * Whole days from today until the target. Negative once it has passed.\n\t\t *\n\t\t * Both ends are floored to midnight first, so \"tomorrow\" is 1 day away\n\t\t * all day today rather than flipping to 0 at lunchtime.\n\t\t *\n\t\t * @return {number|null} Days remaining, or null.\n\t\t */\n\t\tdaysRemaining() {\n\t\t\tif (!this.targetDate) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst startOfDay = (d) => Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())\n\t\t\treturn Math.round((startOfDay(this.targetDate) - startOfDay(new Date())) / MS_PER_DAY)\n\t\t},\n\n\t\t/**\n\t\t * Whether the record says its work is done (`settledField` is truthy).\n\t\t *\n\t\t * @return {boolean} True for a settled record.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\tisSettled() {\n\t\t\tconst field = this.content?.settledField\n\t\t\tif (!this.record || !field) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn Boolean(getByPath(this.record, field))\n\t\t},\n\n\t\t/**\n\t\t * Whether the date has passed on a record still open.\n\t\t *\n\t\t * @return {boolean} True when overdue.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\tisOverdue() {\n\t\t\treturn !this.isSettled && this.daysRemaining !== null && this.daysRemaining < 0\n\t\t},\n\n\t\t/**\n\t\t * The headline: the remaining time in words.\n\t\t *\n\t\t * @return {string} The headline text.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\theadline() {\n\t\t\tif (this.isSettled) {\n\t\t\t\tconst label = this.content?.settledLabel\n\t\t\t\tif (label) {\n\t\t\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(label) : label\n\t\t\t\t}\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tconst days = this.daysRemaining\n\t\t\tif (days === null) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\tif (days < 0) {\n\t\t\t\tconst overdue = Math.abs(days)\n\t\t\t\treturn overdue === 1\n\t\t\t\t\t? t('nextcloud-vue', '1 day overdue')\n\t\t\t\t\t: t('nextcloud-vue', '{count} days overdue', { count: overdue })\n\t\t\t}\n\t\t\tif (days === 0) {\n\t\t\t\treturn t('nextcloud-vue', 'Due today')\n\t\t\t}\n\t\t\tif (days === 1) {\n\t\t\t\treturn t('nextcloud-vue', '1 day left')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{count} days left', { count: days })\n\t\t},\n\n\t\t/**\n\t\t * The absolute date under the headline, so the tile answers \"how long\"\n\t\t * and \"when\" at once.\n\t\t *\n\t\t * @return {string} The formatted date, or ''.\n\t\t */\n\t\tsubLabel() {\n\t\t\tif (!this.targetDate || this.content?.showDate === false) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.targetDate.toLocaleDateString(userLocale(), { day: 'numeric', month: 'long', year: 'numeric' })\n\t\t},\n\n\t\t/**\n\t\t * The threshold band this date falls in.\n\t\t *\n\t\t * @return {string} One of default / success / warning / error.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\tvariant() {\n\t\t\tconst days = this.daysRemaining\n\t\t\tif (days === null || this.isSettled) {\n\t\t\t\treturn 'default'\n\t\t\t}\n\t\t\tif (days < 0) {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\tconst thresholds = this.content?.thresholds || {}\n\t\t\tif (Number.isFinite(thresholds.danger) && days <= thresholds.danger) {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\tif (Number.isFinite(thresholds.warn) && days <= thresholds.warn) {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\treturn 'default'\n\t\t},\n\n\t\t/** @return {string} The value colour for the current band. */\n\t\tvalueColor() {\n\t\t\treturn VARIANT_COLORS[this.variant] || VARIANT_COLORS.default\n\t\t},\n\n\t\t/** @return {string} The tile label. */\n\t\tresolvedLabel() {\n\t\t\treturn this.content?.label || ''\n\t\t},\n\n\t\t/** @return {string} The MDI icon name. */\n\t\tresolvedIcon() {\n\t\t\treturn this.content?.icon || 'ClockOutline'\n\t\t},\n\n\t\t/** @return {object} Inline style for the icon circle. */\n\t\ticonCircleStyle() {\n\t\t\treturn { color: this.valueColor }\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-countdown-widget__value {\n\twhite-space: nowrap;\n}\n\n.cn-countdown-widget__sub {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n</style>\n"],"names":["CnWidgetIcon","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnCountdownWidget.vue2.js","sources":["../../../../src/components/CnCountdownWidget/CnCountdownWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div\n\t\tclass=\"cn-kpi-card cn-countdown-widget\"\n\t\t:class=\"[`cn-countdown-widget--${variant}`, { 'cn-kpi-card--board cn-countdown-widget--board': isBoardLook }]\">\n\t\t<div v-if=\"!isBoardLook\" class=\"cn-kpi-card__icon cn-countdown-widget__icon\" :style=\"iconCircleStyle\">\n\t\t\t<CnWidgetIcon :name=\"resolvedIcon\" :size=\"24\" />\n\t\t</div>\n\n\t\t<div class=\"cn-kpi-card__body\">\n\t\t\t<div v-if=\"resolvedLabel\" class=\"cn-kpi-card__title\">\n\t\t\t\t{{ resolvedLabel }}\n\t\t\t</div>\n\t\t\t<div class=\"cn-kpi-card__value cn-countdown-widget__value\" :style=\"{ color: valueColor }\">\n\t\t\t\t{{ headline }}\n\t\t\t</div>\n\t\t\t<div v-if=\"subLabel\" class=\"cn-countdown-widget__sub\" data-testid=\"cn-countdown-sub\">\n\t\t\t\t{{ subLabel }}\n\t\t\t</div>\n\t\t\t<dl v-if=\"facts.length\" class=\"cn-countdown-widget__facts\" data-testid=\"cn-countdown-facts\">\n\t\t\t\t<template v-for=\"fact in facts\" :key=\"fact.label\">\n\t\t\t\t\t<dt>{{ fact.label }}</dt>\n\t\t\t\t\t<dd>{{ fact.value }}</dd>\n\t\t\t\t</template>\n\t\t\t</dl>\n\t\t\t<span\n\t\t\t\tv-if=\"progress !== null\"\n\t\t\t\tclass=\"cn-countdown-widget__progress\"\n\t\t\t\trole=\"meter\"\n\t\t\t\t:aria-label=\"progressLabel\"\n\t\t\t\t:aria-valuenow=\"Math.round(progress)\"\n\t\t\t\taria-valuemin=\"0\"\n\t\t\t\taria-valuemax=\"100\"\n\t\t\t\tdata-testid=\"cn-countdown-progress\">\n\t\t\t\t<span class=\"cn-countdown-widget__progress-fill\" :style=\"{ width: `${progress}%` }\" />\n\t\t\t</span>\n\t\t\t<button\n\t\t\t\tv-if=\"actionLabel\"\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-countdown-widget__action\"\n\t\t\t\t:class=\"{ 'cn-countdown-widget__action--primary': content.action && content.action.variant === 'primary' }\"\n\t\t\t\tdata-testid=\"cn-countdown-action\"\n\t\t\t\t@click=\"runAction\">\n\t\t\t\t{{ actionLabel }}\n\t\t\t</button>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { inject } from 'vue'\nimport CnWidgetIcon from '../CnWidgetGrid/CnWidgetIcon.vue'\nimport { getByPath } from '../../composables/useEndpointSource.js'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport { dispatchAction } from '../../utils/actionsDispatcher.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { resolveDeepTokens } from '../../utils/resolveFilterTokens.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\nconst VARIANT_COLORS = Object.freeze({\n\tdefault: 'var(--color-primary-element)',\n\tsuccess: 'var(--color-success)',\n\twarning: 'var(--color-warning)',\n\terror: 'var(--color-error)',\n})\n\n/**\n * CnCountdownWidget — a KPI tile for how long is left until a date on the\n * bound record.\n *\n * Every other tile in the catalog answers \"how many\" by aggregating or calling\n * an endpoint. This one answers \"how long\", and it needs neither: the record is\n * already loaded by the detail page, so the tile reads the date straight off it\n * and does the arithmetic. No request, and nothing to go stale.\n *\n * It headlines the remaining time and carries the date itself underneath,\n * because \"12 days left\" and \"5 October\" answer different questions and a\n * handler asks both.\n *\n * ## Thresholds\n *\n * `thresholds` recolours the tile as the date approaches, in days:\n *\n * ```js\n * content: {\n * label: 'Time left',\n * field: 'deadline',\n * icon: 'ClockAlertOutline',\n * thresholds: { warn: 14, danger: 5 },\n * }\n * ```\n *\n * A date in the past reads \"overdue by N days\" in the error colour, never a\n * negative number: \"-3 days left\" is a puzzle, and an overdue case is the one\n * state a handler must not have to decode.\n *\n * ## A settled record\n *\n * `settledField` names a property on the record that is truthy once the work\n * the date belongs to is done, such as a case in its final status. A settled\n * record reads `settledLabel` (default \"Closed\") in no threshold colour, never\n * \"overdue\": a closed case missed nothing that can still be acted on, and a red\n * count of days on it sends a handler after a case that needs nobody.\n *\n * ```js\n * content: { field: 'deadline', settledField: 'isFinalStatus', settledLabel: 'Handled' }\n * ```\n *\n * ## An unset date\n *\n * Renders a dash and no threshold colour. A case with no deadline is not urgent\n * and is not on time; it simply has no deadline, and colouring it green would\n * claim otherwise.\n */\nexport default {\n\tname: 'CnCountdownWidget',\n\n\tcomponents: {\n\t\tCnWidgetIcon,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The tile's config.\n\t\t *\n\t\t * - `field` — property on the bound record holding the target date.\n\t\t * - `label` — the tile's title.\n\t\t * - `icon` — MDI icon name.\n\t\t * - `thresholds` — `{ warn, danger }` in days remaining.\n\t\t * - `showDate` — render the absolute date underneath (default true).\n\t\t * - `settledField` — property on the record that is truthy once the work is done; the tile then reads `settledLabel` and never counts overdue.\n\t\t * - `settledLabel` — the headline of a settled record, run through the host's translate function (default \"Closed\").\n\t\t * - `daysLabel` — `{ one, other }` with `{count}`, the headline while days remain (\"27 dagen\").\n\t\t * - `caption` — the line under the headline instead of the date, with `{date}` (the long date) and `{count}` (days left).\n\t\t * - `startField` — property holding the start of the term; a bar then shows how much of it has passed.\n\t\t * - `action` — one manifest action under the card (`{ label, type, variant?, ... }`, the headerActions shape; `variant: \"primary\"` draws it in the primary tint).\n\t\t * - `emptyLabel` — the headline while the date is unset (default a dash).\n\t\t * - `emptyCaption` — the line under it while the date is unset, with `{date}` read from `emptyDateField`.\n\t\t * - `facts` — `[{ label, field }]` rows under the caption.\n\t\t *\n\t\t * @type {{ field?: string, label?: string, icon?: string, thresholds?: {warn?: number, danger?: number}, showDate?: boolean, settledField?: string, settledLabel?: string, daysLabel?: {one?: string, other?: string}, caption?: string, startField?: string, action?: object, emptyLabel?: string, emptyCaption?: string, emptyDateField?: string, facts?: Array<{label: string, field: string}> }}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * The bound record, when the surface passes it explicitly. Falls back to\n\t\t * the injected `cnObjectContext` a detail page provides.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup() {\n\t\treturn {\n\t\t\tcnObjectContextInjected: inject('cnObjectContext', null),\n\t\t\tcnDetailObjectContextInjected: inject('cnDetailObjectContext', null),\n\t\t\tcnTranslate: inject('cnTranslate', null),\n\t\t\tcnLook: inject('cnLook', 'nextcloud'),\n\t\t\tcnDispatchAction: inject('cnDispatchAction', null),\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The record to read the date from.\n\t\t *\n\t\t * @return {object|null} The bound record, or null.\n\t\t */\n\t\trecord() {\n\t\t\tif (this.objectData) {\n\t\t\t\treturn this.objectData\n\t\t\t}\n\t\t\tconst ctx = resolveObjectTokenContext(this.cnObjectContextInjected, this.cnDetailObjectContextInjected)\n\t\t\treturn ctx?.object || null\n\t\t},\n\n\t\t/**\n\t\t * The target date, or null when unset or unparseable.\n\t\t *\n\t\t * @return {Date|null} The parsed date.\n\t\t */\n\t\ttargetDate() {\n\t\t\tconst field = this.content?.field\n\t\t\tif (!this.record || !field) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst raw = getByPath(this.record, field)\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst parsed = new Date(raw)\n\t\t\treturn Number.isNaN(parsed.getTime()) ? null : parsed\n\t\t},\n\n\t\t/**\n\t\t * Whole days from today until the target. Negative once it has passed.\n\t\t *\n\t\t * Both ends are floored to midnight first, so \"tomorrow\" is 1 day away\n\t\t * all day today rather than flipping to 0 at lunchtime.\n\t\t *\n\t\t * @return {number|null} Days remaining, or null.\n\t\t */\n\t\tdaysRemaining() {\n\t\t\tif (!this.targetDate) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst startOfDay = (d) => Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())\n\t\t\treturn Math.round((startOfDay(this.targetDate) - startOfDay(new Date())) / MS_PER_DAY)\n\t\t},\n\n\t\t/**\n\t\t * Whether the record says its work is done (`settledField` is truthy).\n\t\t *\n\t\t * @return {boolean} True for a settled record.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\tisSettled() {\n\t\t\tconst field = this.content?.settledField\n\t\t\tif (!this.record || !field) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn Boolean(getByPath(this.record, field))\n\t\t},\n\n\t\t/**\n\t\t * Whether the date has passed on a record still open.\n\t\t *\n\t\t * @return {boolean} True when overdue.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\tisOverdue() {\n\t\t\treturn !this.isSettled && this.daysRemaining !== null && this.daysRemaining < 0\n\t\t},\n\n\t\t/**\n\t\t * The headline: the remaining time in words.\n\t\t *\n\t\t * @return {string} The headline text.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\theadline() {\n\t\t\tif (this.isSettled) {\n\t\t\t\tconst label = this.content?.settledLabel\n\t\t\t\tif (label) {\n\t\t\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(label) : label\n\t\t\t\t}\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tconst days = this.daysRemaining\n\t\t\tif (days === null) {\n\t\t\t\treturn this.content?.emptyLabel ? this.translate(this.content.emptyLabel) : '—'\n\t\t\t}\n\t\t\tif (days < 0) {\n\t\t\t\tconst overdue = Math.abs(days)\n\t\t\t\treturn overdue === 1\n\t\t\t\t\t? t('nextcloud-vue', '1 day overdue')\n\t\t\t\t\t: t('nextcloud-vue', '{count} days overdue', { count: overdue })\n\t\t\t}\n\t\t\tif (days === 0) {\n\t\t\t\treturn t('nextcloud-vue', 'Due today')\n\t\t\t}\n\t\t\tconst labels = this.content?.daysLabel\n\t\t\tif (labels && typeof labels === 'object' && (labels.one || labels.other)) {\n\t\t\t\tconst template = this.translate((days === 1 ? labels.one : labels.other) || labels.other || labels.one)\n\t\t\t\treturn template.replace(/\\{count\\}/g, String(days))\n\t\t\t}\n\t\t\tif (days === 1) {\n\t\t\t\treturn t('nextcloud-vue', '1 day left')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{count} days left', { count: days })\n\t\t},\n\n\t\t/**\n\t\t * Whether the app takes the board look: no icon circle.\n\t\t *\n\t\t * @return {boolean} True in the board look.\n\t\t * @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * How much of the term has passed, from `startField` to the date, in\n\t\t * whole days and as a percentage (0 to 100), or null without a usable\n\t\t * start.\n\t\t *\n\t\t * @return {number|null} The share passed.\n\t\t * @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card\n\t\t */\n\t\tprogress() {\n\t\t\tconst field = this.content?.startField\n\t\t\tif (!field || !this.record || !this.targetDate || this.isSettled) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst raw = getByPath(this.record, field)\n\t\t\tconst start = raw === null || raw === undefined || raw === '' ? null : new Date(raw)\n\t\t\tif (!start || Number.isNaN(start.getTime())) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst startOfDay = (d) => Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())\n\t\t\tconst span = startOfDay(this.targetDate) - startOfDay(start)\n\t\t\tif (span <= 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn Math.min(100, Math.max(0, ((startOfDay(new Date()) - startOfDay(start)) / span) * 100))\n\t\t},\n\n\t\t/**\n\t\t * The `facts` rows under the caption (`[{ label, field }]`), with\n\t\t * their values read off the record; a row without a value is left out.\n\t\t *\n\t\t * @return {Array<{label: string, value: string}>} The rows.\n\t\t * @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card\n\t\t */\n\t\tfacts() {\n\t\t\tconst rows = Array.isArray(this.content?.facts) ? this.content.facts : []\n\t\t\treturn rows\n\t\t\t\t.filter((row) => row && typeof row.label === 'string' && typeof row.field === 'string')\n\t\t\t\t.map((row) => {\n\t\t\t\t\tconst value = this.record ? getByPath(this.record, row.field) : null\n\t\t\t\t\treturn { label: this.translate(row.label), value: value === null || value === undefined ? '' : this.translate(String(value)) }\n\t\t\t\t})\n\t\t\t\t.filter((row) => row.value !== '')\n\t\t},\n\n\t\t/** @return {string} The accessible name of the progress bar. */\n\t\tprogressLabel() {\n\t\t\treturn t('nextcloud-vue', 'Time passed')\n\t\t},\n\n\t\t/**\n\t\t * The label of `content.action`, translated, or ''.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card\n\t\t */\n\t\tactionLabel() {\n\t\t\tconst action = this.content?.action\n\t\t\treturn action && typeof action === 'object' && typeof action.label === 'string' && action.label !== ''\n\t\t\t\t? this.translate(action.label)\n\t\t\t\t: ''\n\t\t},\n\n\t\t/**\n\t\t * The absolute date under the headline, so the tile answers \"how long\"\n\t\t * and \"when\" at once.\n\t\t *\n\t\t * @return {string} The formatted date, or ''.\n\t\t */\n\t\tsubLabel() {\n\t\t\tif (!this.targetDate && typeof this.content?.emptyCaption === 'string' && this.content.emptyCaption !== '') {\n\t\t\t\tconst raw = this.content.emptyDateField && this.record ? getByPath(this.record, this.content.emptyDateField) : null\n\t\t\t\tconst when = raw ? new Date(raw) : null\n\t\t\t\tconst date = when && !Number.isNaN(when.getTime()) ? when.toLocaleDateString(userLocale(), { day: 'numeric', month: 'long', year: 'numeric' }) : ''\n\t\t\t\treturn this.translate(this.content.emptyCaption).replace(/\\{date\\}/g, date)\n\t\t\t}\n\t\t\tif (!this.targetDate || this.content?.showDate === false) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst date = this.targetDate.toLocaleDateString(userLocale(), { day: 'numeric', month: 'long', year: 'numeric' })\n\t\t\tconst caption = this.content?.caption\n\t\t\tif (typeof caption === 'string' && caption !== '') {\n\t\t\t\treturn this.translate(caption).replace(/\\{date\\}/g, date).replace(/\\{count\\}/g, String(this.daysRemaining ?? ''))\n\t\t\t}\n\t\t\treturn date\n\t\t},\n\n\t\t/**\n\t\t * The threshold band this date falls in.\n\t\t *\n\t\t * @return {string} One of default / success / warning / error.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\tvariant() {\n\t\t\tconst days = this.daysRemaining\n\t\t\tif (days === null || this.isSettled) {\n\t\t\t\treturn 'default'\n\t\t\t}\n\t\t\tif (days < 0) {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\tconst thresholds = this.content?.thresholds || {}\n\t\t\tif (Number.isFinite(thresholds.danger) && days <= thresholds.danger) {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\tif (Number.isFinite(thresholds.warn) && days <= thresholds.warn) {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\treturn 'default'\n\t\t},\n\n\t\t/** @return {string} The value colour for the current band. */\n\t\tvalueColor() {\n\t\t\tif (this.isBoardLook && this.variant === 'default') {\n\t\t\t\treturn 'var(--color-main-text)'\n\t\t\t}\n\t\t\treturn VARIANT_COLORS[this.variant] || VARIANT_COLORS.default\n\t\t},\n\n\t\t/** @return {string} The tile label. */\n\t\tresolvedLabel() {\n\t\t\treturn this.content?.label || ''\n\t\t},\n\n\t\t/** @return {string} The MDI icon name. */\n\t\tresolvedIcon() {\n\t\t\treturn this.content?.icon || 'ClockOutline'\n\t\t},\n\n\t\t/** @return {object} Inline style for the icon circle. */\n\t\ticonCircleStyle() {\n\t\t\treturn { color: this.valueColor }\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The host's translate, or the text unchanged.\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {string} The translation.\n\t\t */\n\t\ttranslate(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\t/**\n\t\t * Dispatch `content.action` with the record's tokens resolved, through\n\t\t * the page's dispatcher when there is one.\n\t\t *\n\t\t * @return {unknown}\n\t\t * @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card\n\t\t */\n\t\trunAction() {\n\t\t\tconst action = this.content?.action\n\t\t\tif (!action || typeof action !== 'object') {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tconst ctx = resolveObjectTokenContext(this.cnObjectContextInjected, this.cnDetailObjectContextInjected) || {}\n\t\t\tconst tokenCtx = { ...ctx, object: this.record || ctx.object || null, objectId: this.record?.id ?? ctx.objectId ?? null }\n\t\t\tconst resolved = resolveDeepTokens(action, tokenCtx)\n\t\t\tif (typeof this.cnDispatchAction === 'function') {\n\t\t\t\treturn this.cnDispatchAction(resolved)\n\t\t\t}\n\t\t\treturn dispatchAction(resolved, { router: this.$router || null, tokenCtx })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-countdown-widget__value {\n\twhite-space: nowrap;\n}\n\n.cn-countdown-widget__progress {\n\tbackground: var(--color-border);\n\tborder-radius: 3px;\n\tdisplay: block;\n\theight: 6px;\n\tmargin-block-start: 10px;\n\toverflow: hidden;\n}\n\n.cn-countdown-widget__progress-fill {\n\tbackground: var(--color-primary-element);\n\tdisplay: block;\n\theight: 100%;\n}\n\n.cn-countdown-widget__action {\n\talign-self: flex-start;\n\tbackground: var(--color-background-dark);\n\tborder: 0;\n\tborder-radius: var(--border-radius-pill, 17px);\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n\tmargin-block-start: 10px;\n\tmin-height: 34px;\n\tpadding: 0 12px;\n}\n\n.cn-countdown-widget__action--primary {\n\tbackground: var(--color-primary-element-light);\n\tcolor: var(--color-primary-element-light-text);\n}\n\n.cn-countdown-widget__facts {\n\tdisplay: grid;\n\tgap: 8px 16px;\n\tgrid-template-columns: max-content 1fr;\n\tmargin: 10px 0 0;\n}\n\n.cn-countdown-widget__facts dt {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-countdown-widget__facts dd {\n\tmargin: 0;\n}\n\n.cn-countdown-widget__action:hover,\n.cn-countdown-widget__action:focus-visible {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-countdown-widget__sub {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n</style>\n"],"names":["CnWidgetIcon","t"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveComponent, openBlock, createElementBlock, normalizeClass,
|
|
1
|
+
import { resolveComponent, openBlock, createElementBlock, normalizeClass, normalizeStyle, createVNode, createCommentVNode, createElementVNode, toDisplayString, Fragment, renderList } from 'vue';
|
|
2
2
|
|
|
3
3
|
const _hoisted_1 = { class: "cn-kpi-card__body" };
|
|
4
4
|
const _hoisted_2 = {
|
|
@@ -7,24 +7,34 @@ const _hoisted_2 = {
|
|
|
7
7
|
};
|
|
8
8
|
const _hoisted_3 = {
|
|
9
9
|
key: 1,
|
|
10
|
-
class: "cn-countdown-widget__sub"
|
|
10
|
+
class: "cn-countdown-widget__sub",
|
|
11
|
+
"data-testid": "cn-countdown-sub"
|
|
11
12
|
};
|
|
13
|
+
const _hoisted_4 = {
|
|
14
|
+
key: 2,
|
|
15
|
+
class: "cn-countdown-widget__facts",
|
|
16
|
+
"data-testid": "cn-countdown-facts"
|
|
17
|
+
};
|
|
18
|
+
const _hoisted_5 = ["aria-label", "aria-valuenow"];
|
|
12
19
|
|
|
13
20
|
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
14
21
|
const _component_CnWidgetIcon = resolveComponent("CnWidgetIcon");
|
|
15
22
|
|
|
16
23
|
return (openBlock(), createElementBlock("div", {
|
|
17
|
-
class: normalizeClass(["cn-kpi-card cn-countdown-widget", `cn-countdown-widget--${$options.variant}
|
|
24
|
+
class: normalizeClass(["cn-kpi-card cn-countdown-widget", [`cn-countdown-widget--${$options.variant}`, { 'cn-kpi-card--board cn-countdown-widget--board': $options.isBoardLook }]])
|
|
18
25
|
}, [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
(!$options.isBoardLook)
|
|
27
|
+
? (openBlock(), createElementBlock("div", {
|
|
28
|
+
key: 0,
|
|
29
|
+
class: "cn-kpi-card__icon cn-countdown-widget__icon",
|
|
30
|
+
style: normalizeStyle($options.iconCircleStyle)
|
|
31
|
+
}, [
|
|
32
|
+
createVNode(_component_CnWidgetIcon, {
|
|
33
|
+
name: $options.resolvedIcon,
|
|
34
|
+
size: 24
|
|
35
|
+
}, null, 8 /* PROPS */, ["name"])
|
|
36
|
+
], 4 /* STYLE */))
|
|
37
|
+
: createCommentVNode("v-if", true),
|
|
28
38
|
createElementVNode("div", _hoisted_1, [
|
|
29
39
|
($options.resolvedLabel)
|
|
30
40
|
? (openBlock(), createElementBlock("div", _hoisted_2, toDisplayString($options.resolvedLabel), 1 /* TEXT */))
|
|
@@ -35,6 +45,44 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
35
45
|
}, toDisplayString($options.headline), 5 /* TEXT, STYLE */),
|
|
36
46
|
($options.subLabel)
|
|
37
47
|
? (openBlock(), createElementBlock("div", _hoisted_3, toDisplayString($options.subLabel), 1 /* TEXT */))
|
|
48
|
+
: createCommentVNode("v-if", true),
|
|
49
|
+
($options.facts.length)
|
|
50
|
+
? (openBlock(), createElementBlock("dl", _hoisted_4, [
|
|
51
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList($options.facts, (fact) => {
|
|
52
|
+
return (openBlock(), createElementBlock(Fragment, {
|
|
53
|
+
key: fact.label
|
|
54
|
+
}, [
|
|
55
|
+
createElementVNode("dt", null, toDisplayString(fact.label), 1 /* TEXT */),
|
|
56
|
+
createElementVNode("dd", null, toDisplayString(fact.value), 1 /* TEXT */)
|
|
57
|
+
], 64 /* STABLE_FRAGMENT */))
|
|
58
|
+
}), 128 /* KEYED_FRAGMENT */))
|
|
59
|
+
]))
|
|
60
|
+
: createCommentVNode("v-if", true),
|
|
61
|
+
($options.progress !== null)
|
|
62
|
+
? (openBlock(), createElementBlock("span", {
|
|
63
|
+
key: 3,
|
|
64
|
+
class: "cn-countdown-widget__progress",
|
|
65
|
+
role: "meter",
|
|
66
|
+
"aria-label": $options.progressLabel,
|
|
67
|
+
"aria-valuenow": Math.round($options.progress),
|
|
68
|
+
"aria-valuemin": "0",
|
|
69
|
+
"aria-valuemax": "100",
|
|
70
|
+
"data-testid": "cn-countdown-progress"
|
|
71
|
+
}, [
|
|
72
|
+
createElementVNode("span", {
|
|
73
|
+
class: "cn-countdown-widget__progress-fill",
|
|
74
|
+
style: normalizeStyle({ width: `${$options.progress}%` })
|
|
75
|
+
}, null, 4 /* STYLE */)
|
|
76
|
+
], 8 /* PROPS */, _hoisted_5))
|
|
77
|
+
: createCommentVNode("v-if", true),
|
|
78
|
+
($options.actionLabel)
|
|
79
|
+
? (openBlock(), createElementBlock("button", {
|
|
80
|
+
key: 4,
|
|
81
|
+
type: "button",
|
|
82
|
+
class: normalizeClass(["cn-countdown-widget__action", { 'cn-countdown-widget__action--primary': $props.content.action && $props.content.action.variant === 'primary' }]),
|
|
83
|
+
"data-testid": "cn-countdown-action",
|
|
84
|
+
onClick: _cache[0] || (_cache[0] = (...args) => ($options.runAction && $options.runAction(...args)))
|
|
85
|
+
}, toDisplayString($options.actionLabel), 3 /* TEXT, CLASS */))
|
|
38
86
|
: createCommentVNode("v-if", true)
|
|
39
87
|
])
|
|
40
88
|
], 2 /* CLASS */))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnCountdownWidget.vue3.js","sources":["../../../../src/components/CnCountdownWidget/CnCountdownWidget.vue?vue&type=template&id=d1b330c0&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-kpi-card cn-countdown-widget\" :class=\"`cn-countdown-widget--${variant}`\">\n\t\t<div class=\"cn-kpi-card__icon cn-countdown-widget__icon\" :style=\"iconCircleStyle\">\n\t\t\t<CnWidgetIcon :name=\"resolvedIcon\" :size=\"24\" />\n\t\t</div>\n\n\t\t<div class=\"cn-kpi-card__body\">\n\t\t\t<div v-if=\"resolvedLabel\" class=\"cn-kpi-card__title\">\n\t\t\t\t{{ resolvedLabel }}\n\t\t\t</div>\n\t\t\t<div class=\"cn-kpi-card__value cn-countdown-widget__value\" :style=\"{ color: valueColor }\">\n\t\t\t\t{{ headline }}\n\t\t\t</div>\n\t\t\t<div v-if=\"subLabel\" class=\"cn-countdown-widget__sub\">\n\t\t\t\t{{ subLabel }}\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { inject } from 'vue'\nimport CnWidgetIcon from '../CnWidgetGrid/CnWidgetIcon.vue'\nimport { getByPath } from '../../composables/useEndpointSource.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\nconst VARIANT_COLORS = Object.freeze({\n\tdefault: 'var(--color-primary-element)',\n\tsuccess: 'var(--color-success)',\n\twarning: 'var(--color-warning)',\n\terror: 'var(--color-error)',\n})\n\n/**\n * CnCountdownWidget — a KPI tile for how long is left until a date on the\n * bound record.\n *\n * Every other tile in the catalog answers \"how many\" by aggregating or calling\n * an endpoint. This one answers \"how long\", and it needs neither: the record is\n * already loaded by the detail page, so the tile reads the date straight off it\n * and does the arithmetic. No request, and nothing to go stale.\n *\n * It headlines the remaining time and carries the date itself underneath,\n * because \"12 days left\" and \"5 October\" answer different questions and a\n * handler asks both.\n *\n * ## Thresholds\n *\n * `thresholds` recolours the tile as the date approaches, in days:\n *\n * ```js\n * content: {\n * label: 'Time left',\n * field: 'deadline',\n * icon: 'ClockAlertOutline',\n * thresholds: { warn: 14, danger: 5 },\n * }\n * ```\n *\n * A date in the past reads \"overdue by N days\" in the error colour, never a\n * negative number: \"-3 days left\" is a puzzle, and an overdue case is the one\n * state a handler must not have to decode.\n *\n * ## A settled record\n *\n * `settledField` names a property on the record that is truthy once the work\n * the date belongs to is done, such as a case in its final status. A settled\n * record reads `settledLabel` (default \"Closed\") in no threshold colour, never\n * \"overdue\": a closed case missed nothing that can still be acted on, and a red\n * count of days on it sends a handler after a case that needs nobody.\n *\n * ```js\n * content: { field: 'deadline', settledField: 'isFinalStatus', settledLabel: 'Handled' }\n * ```\n *\n * ## An unset date\n *\n * Renders a dash and no threshold colour. A case with no deadline is not urgent\n * and is not on time; it simply has no deadline, and colouring it green would\n * claim otherwise.\n */\nexport default {\n\tname: 'CnCountdownWidget',\n\n\tcomponents: {\n\t\tCnWidgetIcon,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The tile's config.\n\t\t *\n\t\t * - `field` — property on the bound record holding the target date.\n\t\t * - `label` — the tile's title.\n\t\t * - `icon` — MDI icon name.\n\t\t * - `thresholds` — `{ warn, danger }` in days remaining.\n\t\t * - `showDate` — render the absolute date underneath (default true).\n\t\t * - `settledField` — property on the record that is truthy once the work is done; the tile then reads `settledLabel` and never counts overdue.\n\t\t * - `settledLabel` — the headline of a settled record, run through the host's translate function (default \"Closed\").\n\t\t *\n\t\t * @type {{ field?: string, label?: string, icon?: string, thresholds?: {warn?: number, danger?: number}, showDate?: boolean, settledField?: string, settledLabel?: string }}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * The bound record, when the surface passes it explicitly. Falls back to\n\t\t * the injected `cnObjectContext` a detail page provides.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup() {\n\t\treturn {\n\t\t\tcnObjectContextInjected: inject('cnObjectContext', null),\n\t\t\tcnDetailObjectContextInjected: inject('cnDetailObjectContext', null),\n\t\t\tcnTranslate: inject('cnTranslate', null),\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The record to read the date from.\n\t\t *\n\t\t * @return {object|null} The bound record, or null.\n\t\t */\n\t\trecord() {\n\t\t\tif (this.objectData) {\n\t\t\t\treturn this.objectData\n\t\t\t}\n\t\t\tconst ctx = resolveObjectTokenContext(this.cnObjectContextInjected, this.cnDetailObjectContextInjected)\n\t\t\treturn ctx?.object || null\n\t\t},\n\n\t\t/**\n\t\t * The target date, or null when unset or unparseable.\n\t\t *\n\t\t * @return {Date|null} The parsed date.\n\t\t */\n\t\ttargetDate() {\n\t\t\tconst field = this.content?.field\n\t\t\tif (!this.record || !field) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst raw = getByPath(this.record, field)\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst parsed = new Date(raw)\n\t\t\treturn Number.isNaN(parsed.getTime()) ? null : parsed\n\t\t},\n\n\t\t/**\n\t\t * Whole days from today until the target. Negative once it has passed.\n\t\t *\n\t\t * Both ends are floored to midnight first, so \"tomorrow\" is 1 day away\n\t\t * all day today rather than flipping to 0 at lunchtime.\n\t\t *\n\t\t * @return {number|null} Days remaining, or null.\n\t\t */\n\t\tdaysRemaining() {\n\t\t\tif (!this.targetDate) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst startOfDay = (d) => Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())\n\t\t\treturn Math.round((startOfDay(this.targetDate) - startOfDay(new Date())) / MS_PER_DAY)\n\t\t},\n\n\t\t/**\n\t\t * Whether the record says its work is done (`settledField` is truthy).\n\t\t *\n\t\t * @return {boolean} True for a settled record.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\tisSettled() {\n\t\t\tconst field = this.content?.settledField\n\t\t\tif (!this.record || !field) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn Boolean(getByPath(this.record, field))\n\t\t},\n\n\t\t/**\n\t\t * Whether the date has passed on a record still open.\n\t\t *\n\t\t * @return {boolean} True when overdue.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\tisOverdue() {\n\t\t\treturn !this.isSettled && this.daysRemaining !== null && this.daysRemaining < 0\n\t\t},\n\n\t\t/**\n\t\t * The headline: the remaining time in words.\n\t\t *\n\t\t * @return {string} The headline text.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\theadline() {\n\t\t\tif (this.isSettled) {\n\t\t\t\tconst label = this.content?.settledLabel\n\t\t\t\tif (label) {\n\t\t\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(label) : label\n\t\t\t\t}\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tconst days = this.daysRemaining\n\t\t\tif (days === null) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\tif (days < 0) {\n\t\t\t\tconst overdue = Math.abs(days)\n\t\t\t\treturn overdue === 1\n\t\t\t\t\t? t('nextcloud-vue', '1 day overdue')\n\t\t\t\t\t: t('nextcloud-vue', '{count} days overdue', { count: overdue })\n\t\t\t}\n\t\t\tif (days === 0) {\n\t\t\t\treturn t('nextcloud-vue', 'Due today')\n\t\t\t}\n\t\t\tif (days === 1) {\n\t\t\t\treturn t('nextcloud-vue', '1 day left')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{count} days left', { count: days })\n\t\t},\n\n\t\t/**\n\t\t * The absolute date under the headline, so the tile answers \"how long\"\n\t\t * and \"when\" at once.\n\t\t *\n\t\t * @return {string} The formatted date, or ''.\n\t\t */\n\t\tsubLabel() {\n\t\t\tif (!this.targetDate || this.content?.showDate === false) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.targetDate.toLocaleDateString(userLocale(), { day: 'numeric', month: 'long', year: 'numeric' })\n\t\t},\n\n\t\t/**\n\t\t * The threshold band this date falls in.\n\t\t *\n\t\t * @return {string} One of default / success / warning / error.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\tvariant() {\n\t\t\tconst days = this.daysRemaining\n\t\t\tif (days === null || this.isSettled) {\n\t\t\t\treturn 'default'\n\t\t\t}\n\t\t\tif (days < 0) {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\tconst thresholds = this.content?.thresholds || {}\n\t\t\tif (Number.isFinite(thresholds.danger) && days <= thresholds.danger) {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\tif (Number.isFinite(thresholds.warn) && days <= thresholds.warn) {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\treturn 'default'\n\t\t},\n\n\t\t/** @return {string} The value colour for the current band. */\n\t\tvalueColor() {\n\t\t\treturn VARIANT_COLORS[this.variant] || VARIANT_COLORS.default\n\t\t},\n\n\t\t/** @return {string} The tile label. */\n\t\tresolvedLabel() {\n\t\t\treturn this.content?.label || ''\n\t\t},\n\n\t\t/** @return {string} The MDI icon name. */\n\t\tresolvedIcon() {\n\t\t\treturn this.content?.icon || 'ClockOutline'\n\t\t},\n\n\t\t/** @return {object} Inline style for the icon circle. */\n\t\ticonCircleStyle() {\n\t\t\treturn { color: this.valueColor }\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-countdown-widget__value {\n\twhite-space: nowrap;\n}\n\n.cn-countdown-widget__sub {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n</style>\n"],"names":["_normalizeClass","_createVNode","_createElementBlock","_toDisplayString","_createElementVNode"],"mappings":";;AAgBO,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,mBAAI,GAAA;;;AACT,EAAA,KAAA,EAAA,oBAAA;;;;;;;;;;;IANH,KAAM,EAAAA,cAAA,CAAA,CAAC,iCAAoB,EAAA,CAAA,qBAAA,EAAA,QAAA,CAAA,OAAA,CAAA,CAAA,CAAA,CAAA;;sBAGxB,CAAA,KAAA,EAAA;AAFA,MAAA,KAAI,EAAC,6CAA0C;AAAA,MAAA,KAAA,iBAAA,QAAA,CAAA,eAAA,CAAA;;MACnDC,WAAkB,CAAA,uBAAA,EAAA;AAAJ,QAAA,IAAI,EAAA,QAAA,CAAA,YAAA;AAAA,QAAA,IAAA,EAAA,EAAA;;;;OAId,QAAA,CAAA,aAAA;wBAALC,kBAEM,CAAA,KAAA,EAFN,UAEM,EAAAC,eAAA,CADD,QAAY,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;;MAEjBC,kBAEG,CAAA,KAAA,EAAA;AAFE,QAAA,KAAA,EAAA,+CAAA;AAAA,QAAA,KAAA,0BAAA,QAAA,CAAA,UAAA,EAAA,CAAA;yBACD,QAAA,CAAA,QAAA,CAAA,EAAA,CAAA,mBAAA;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnCountdownWidget.vue3.js","sources":["../../../../src/components/CnCountdownWidget/CnCountdownWidget.vue?vue&type=template&id=22ccd275&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div\n\t\tclass=\"cn-kpi-card cn-countdown-widget\"\n\t\t:class=\"[`cn-countdown-widget--${variant}`, { 'cn-kpi-card--board cn-countdown-widget--board': isBoardLook }]\">\n\t\t<div v-if=\"!isBoardLook\" class=\"cn-kpi-card__icon cn-countdown-widget__icon\" :style=\"iconCircleStyle\">\n\t\t\t<CnWidgetIcon :name=\"resolvedIcon\" :size=\"24\" />\n\t\t</div>\n\n\t\t<div class=\"cn-kpi-card__body\">\n\t\t\t<div v-if=\"resolvedLabel\" class=\"cn-kpi-card__title\">\n\t\t\t\t{{ resolvedLabel }}\n\t\t\t</div>\n\t\t\t<div class=\"cn-kpi-card__value cn-countdown-widget__value\" :style=\"{ color: valueColor }\">\n\t\t\t\t{{ headline }}\n\t\t\t</div>\n\t\t\t<div v-if=\"subLabel\" class=\"cn-countdown-widget__sub\" data-testid=\"cn-countdown-sub\">\n\t\t\t\t{{ subLabel }}\n\t\t\t</div>\n\t\t\t<dl v-if=\"facts.length\" class=\"cn-countdown-widget__facts\" data-testid=\"cn-countdown-facts\">\n\t\t\t\t<template v-for=\"fact in facts\" :key=\"fact.label\">\n\t\t\t\t\t<dt>{{ fact.label }}</dt>\n\t\t\t\t\t<dd>{{ fact.value }}</dd>\n\t\t\t\t</template>\n\t\t\t</dl>\n\t\t\t<span\n\t\t\t\tv-if=\"progress !== null\"\n\t\t\t\tclass=\"cn-countdown-widget__progress\"\n\t\t\t\trole=\"meter\"\n\t\t\t\t:aria-label=\"progressLabel\"\n\t\t\t\t:aria-valuenow=\"Math.round(progress)\"\n\t\t\t\taria-valuemin=\"0\"\n\t\t\t\taria-valuemax=\"100\"\n\t\t\t\tdata-testid=\"cn-countdown-progress\">\n\t\t\t\t<span class=\"cn-countdown-widget__progress-fill\" :style=\"{ width: `${progress}%` }\" />\n\t\t\t</span>\n\t\t\t<button\n\t\t\t\tv-if=\"actionLabel\"\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-countdown-widget__action\"\n\t\t\t\t:class=\"{ 'cn-countdown-widget__action--primary': content.action && content.action.variant === 'primary' }\"\n\t\t\t\tdata-testid=\"cn-countdown-action\"\n\t\t\t\t@click=\"runAction\">\n\t\t\t\t{{ actionLabel }}\n\t\t\t</button>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { inject } from 'vue'\nimport CnWidgetIcon from '../CnWidgetGrid/CnWidgetIcon.vue'\nimport { getByPath } from '../../composables/useEndpointSource.js'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport { dispatchAction } from '../../utils/actionsDispatcher.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { resolveDeepTokens } from '../../utils/resolveFilterTokens.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\nconst VARIANT_COLORS = Object.freeze({\n\tdefault: 'var(--color-primary-element)',\n\tsuccess: 'var(--color-success)',\n\twarning: 'var(--color-warning)',\n\terror: 'var(--color-error)',\n})\n\n/**\n * CnCountdownWidget — a KPI tile for how long is left until a date on the\n * bound record.\n *\n * Every other tile in the catalog answers \"how many\" by aggregating or calling\n * an endpoint. This one answers \"how long\", and it needs neither: the record is\n * already loaded by the detail page, so the tile reads the date straight off it\n * and does the arithmetic. No request, and nothing to go stale.\n *\n * It headlines the remaining time and carries the date itself underneath,\n * because \"12 days left\" and \"5 October\" answer different questions and a\n * handler asks both.\n *\n * ## Thresholds\n *\n * `thresholds` recolours the tile as the date approaches, in days:\n *\n * ```js\n * content: {\n * label: 'Time left',\n * field: 'deadline',\n * icon: 'ClockAlertOutline',\n * thresholds: { warn: 14, danger: 5 },\n * }\n * ```\n *\n * A date in the past reads \"overdue by N days\" in the error colour, never a\n * negative number: \"-3 days left\" is a puzzle, and an overdue case is the one\n * state a handler must not have to decode.\n *\n * ## A settled record\n *\n * `settledField` names a property on the record that is truthy once the work\n * the date belongs to is done, such as a case in its final status. A settled\n * record reads `settledLabel` (default \"Closed\") in no threshold colour, never\n * \"overdue\": a closed case missed nothing that can still be acted on, and a red\n * count of days on it sends a handler after a case that needs nobody.\n *\n * ```js\n * content: { field: 'deadline', settledField: 'isFinalStatus', settledLabel: 'Handled' }\n * ```\n *\n * ## An unset date\n *\n * Renders a dash and no threshold colour. A case with no deadline is not urgent\n * and is not on time; it simply has no deadline, and colouring it green would\n * claim otherwise.\n */\nexport default {\n\tname: 'CnCountdownWidget',\n\n\tcomponents: {\n\t\tCnWidgetIcon,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The tile's config.\n\t\t *\n\t\t * - `field` — property on the bound record holding the target date.\n\t\t * - `label` — the tile's title.\n\t\t * - `icon` — MDI icon name.\n\t\t * - `thresholds` — `{ warn, danger }` in days remaining.\n\t\t * - `showDate` — render the absolute date underneath (default true).\n\t\t * - `settledField` — property on the record that is truthy once the work is done; the tile then reads `settledLabel` and never counts overdue.\n\t\t * - `settledLabel` — the headline of a settled record, run through the host's translate function (default \"Closed\").\n\t\t * - `daysLabel` — `{ one, other }` with `{count}`, the headline while days remain (\"27 dagen\").\n\t\t * - `caption` — the line under the headline instead of the date, with `{date}` (the long date) and `{count}` (days left).\n\t\t * - `startField` — property holding the start of the term; a bar then shows how much of it has passed.\n\t\t * - `action` — one manifest action under the card (`{ label, type, variant?, ... }`, the headerActions shape; `variant: \"primary\"` draws it in the primary tint).\n\t\t * - `emptyLabel` — the headline while the date is unset (default a dash).\n\t\t * - `emptyCaption` — the line under it while the date is unset, with `{date}` read from `emptyDateField`.\n\t\t * - `facts` — `[{ label, field }]` rows under the caption.\n\t\t *\n\t\t * @type {{ field?: string, label?: string, icon?: string, thresholds?: {warn?: number, danger?: number}, showDate?: boolean, settledField?: string, settledLabel?: string, daysLabel?: {one?: string, other?: string}, caption?: string, startField?: string, action?: object, emptyLabel?: string, emptyCaption?: string, emptyDateField?: string, facts?: Array<{label: string, field: string}> }}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * The bound record, when the surface passes it explicitly. Falls back to\n\t\t * the injected `cnObjectContext` a detail page provides.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup() {\n\t\treturn {\n\t\t\tcnObjectContextInjected: inject('cnObjectContext', null),\n\t\t\tcnDetailObjectContextInjected: inject('cnDetailObjectContext', null),\n\t\t\tcnTranslate: inject('cnTranslate', null),\n\t\t\tcnLook: inject('cnLook', 'nextcloud'),\n\t\t\tcnDispatchAction: inject('cnDispatchAction', null),\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The record to read the date from.\n\t\t *\n\t\t * @return {object|null} The bound record, or null.\n\t\t */\n\t\trecord() {\n\t\t\tif (this.objectData) {\n\t\t\t\treturn this.objectData\n\t\t\t}\n\t\t\tconst ctx = resolveObjectTokenContext(this.cnObjectContextInjected, this.cnDetailObjectContextInjected)\n\t\t\treturn ctx?.object || null\n\t\t},\n\n\t\t/**\n\t\t * The target date, or null when unset or unparseable.\n\t\t *\n\t\t * @return {Date|null} The parsed date.\n\t\t */\n\t\ttargetDate() {\n\t\t\tconst field = this.content?.field\n\t\t\tif (!this.record || !field) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst raw = getByPath(this.record, field)\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst parsed = new Date(raw)\n\t\t\treturn Number.isNaN(parsed.getTime()) ? null : parsed\n\t\t},\n\n\t\t/**\n\t\t * Whole days from today until the target. Negative once it has passed.\n\t\t *\n\t\t * Both ends are floored to midnight first, so \"tomorrow\" is 1 day away\n\t\t * all day today rather than flipping to 0 at lunchtime.\n\t\t *\n\t\t * @return {number|null} Days remaining, or null.\n\t\t */\n\t\tdaysRemaining() {\n\t\t\tif (!this.targetDate) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst startOfDay = (d) => Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())\n\t\t\treturn Math.round((startOfDay(this.targetDate) - startOfDay(new Date())) / MS_PER_DAY)\n\t\t},\n\n\t\t/**\n\t\t * Whether the record says its work is done (`settledField` is truthy).\n\t\t *\n\t\t * @return {boolean} True for a settled record.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\tisSettled() {\n\t\t\tconst field = this.content?.settledField\n\t\t\tif (!this.record || !field) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn Boolean(getByPath(this.record, field))\n\t\t},\n\n\t\t/**\n\t\t * Whether the date has passed on a record still open.\n\t\t *\n\t\t * @return {boolean} True when overdue.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\tisOverdue() {\n\t\t\treturn !this.isSettled && this.daysRemaining !== null && this.daysRemaining < 0\n\t\t},\n\n\t\t/**\n\t\t * The headline: the remaining time in words.\n\t\t *\n\t\t * @return {string} The headline text.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\theadline() {\n\t\t\tif (this.isSettled) {\n\t\t\t\tconst label = this.content?.settledLabel\n\t\t\t\tif (label) {\n\t\t\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(label) : label\n\t\t\t\t}\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tconst days = this.daysRemaining\n\t\t\tif (days === null) {\n\t\t\t\treturn this.content?.emptyLabel ? this.translate(this.content.emptyLabel) : '—'\n\t\t\t}\n\t\t\tif (days < 0) {\n\t\t\t\tconst overdue = Math.abs(days)\n\t\t\t\treturn overdue === 1\n\t\t\t\t\t? t('nextcloud-vue', '1 day overdue')\n\t\t\t\t\t: t('nextcloud-vue', '{count} days overdue', { count: overdue })\n\t\t\t}\n\t\t\tif (days === 0) {\n\t\t\t\treturn t('nextcloud-vue', 'Due today')\n\t\t\t}\n\t\t\tconst labels = this.content?.daysLabel\n\t\t\tif (labels && typeof labels === 'object' && (labels.one || labels.other)) {\n\t\t\t\tconst template = this.translate((days === 1 ? labels.one : labels.other) || labels.other || labels.one)\n\t\t\t\treturn template.replace(/\\{count\\}/g, String(days))\n\t\t\t}\n\t\t\tif (days === 1) {\n\t\t\t\treturn t('nextcloud-vue', '1 day left')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{count} days left', { count: days })\n\t\t},\n\n\t\t/**\n\t\t * Whether the app takes the board look: no icon circle.\n\t\t *\n\t\t * @return {boolean} True in the board look.\n\t\t * @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * How much of the term has passed, from `startField` to the date, in\n\t\t * whole days and as a percentage (0 to 100), or null without a usable\n\t\t * start.\n\t\t *\n\t\t * @return {number|null} The share passed.\n\t\t * @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card\n\t\t */\n\t\tprogress() {\n\t\t\tconst field = this.content?.startField\n\t\t\tif (!field || !this.record || !this.targetDate || this.isSettled) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst raw = getByPath(this.record, field)\n\t\t\tconst start = raw === null || raw === undefined || raw === '' ? null : new Date(raw)\n\t\t\tif (!start || Number.isNaN(start.getTime())) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst startOfDay = (d) => Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())\n\t\t\tconst span = startOfDay(this.targetDate) - startOfDay(start)\n\t\t\tif (span <= 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn Math.min(100, Math.max(0, ((startOfDay(new Date()) - startOfDay(start)) / span) * 100))\n\t\t},\n\n\t\t/**\n\t\t * The `facts` rows under the caption (`[{ label, field }]`), with\n\t\t * their values read off the record; a row without a value is left out.\n\t\t *\n\t\t * @return {Array<{label: string, value: string}>} The rows.\n\t\t * @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card\n\t\t */\n\t\tfacts() {\n\t\t\tconst rows = Array.isArray(this.content?.facts) ? this.content.facts : []\n\t\t\treturn rows\n\t\t\t\t.filter((row) => row && typeof row.label === 'string' && typeof row.field === 'string')\n\t\t\t\t.map((row) => {\n\t\t\t\t\tconst value = this.record ? getByPath(this.record, row.field) : null\n\t\t\t\t\treturn { label: this.translate(row.label), value: value === null || value === undefined ? '' : this.translate(String(value)) }\n\t\t\t\t})\n\t\t\t\t.filter((row) => row.value !== '')\n\t\t},\n\n\t\t/** @return {string} The accessible name of the progress bar. */\n\t\tprogressLabel() {\n\t\t\treturn t('nextcloud-vue', 'Time passed')\n\t\t},\n\n\t\t/**\n\t\t * The label of `content.action`, translated, or ''.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card\n\t\t */\n\t\tactionLabel() {\n\t\t\tconst action = this.content?.action\n\t\t\treturn action && typeof action === 'object' && typeof action.label === 'string' && action.label !== ''\n\t\t\t\t? this.translate(action.label)\n\t\t\t\t: ''\n\t\t},\n\n\t\t/**\n\t\t * The absolute date under the headline, so the tile answers \"how long\"\n\t\t * and \"when\" at once.\n\t\t *\n\t\t * @return {string} The formatted date, or ''.\n\t\t */\n\t\tsubLabel() {\n\t\t\tif (!this.targetDate && typeof this.content?.emptyCaption === 'string' && this.content.emptyCaption !== '') {\n\t\t\t\tconst raw = this.content.emptyDateField && this.record ? getByPath(this.record, this.content.emptyDateField) : null\n\t\t\t\tconst when = raw ? new Date(raw) : null\n\t\t\t\tconst date = when && !Number.isNaN(when.getTime()) ? when.toLocaleDateString(userLocale(), { day: 'numeric', month: 'long', year: 'numeric' }) : ''\n\t\t\t\treturn this.translate(this.content.emptyCaption).replace(/\\{date\\}/g, date)\n\t\t\t}\n\t\t\tif (!this.targetDate || this.content?.showDate === false) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst date = this.targetDate.toLocaleDateString(userLocale(), { day: 'numeric', month: 'long', year: 'numeric' })\n\t\t\tconst caption = this.content?.caption\n\t\t\tif (typeof caption === 'string' && caption !== '') {\n\t\t\t\treturn this.translate(caption).replace(/\\{date\\}/g, date).replace(/\\{count\\}/g, String(this.daysRemaining ?? ''))\n\t\t\t}\n\t\t\treturn date\n\t\t},\n\n\t\t/**\n\t\t * The threshold band this date falls in.\n\t\t *\n\t\t * @return {string} One of default / success / warning / error.\n\t\t * @spec openspec/changes/countdown-settled-record/specs/countdown-settled-record/spec.md#requirement-a-settled-record-never-reads-as-overdue\n\t\t */\n\t\tvariant() {\n\t\t\tconst days = this.daysRemaining\n\t\t\tif (days === null || this.isSettled) {\n\t\t\t\treturn 'default'\n\t\t\t}\n\t\t\tif (days < 0) {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\tconst thresholds = this.content?.thresholds || {}\n\t\t\tif (Number.isFinite(thresholds.danger) && days <= thresholds.danger) {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\tif (Number.isFinite(thresholds.warn) && days <= thresholds.warn) {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\treturn 'default'\n\t\t},\n\n\t\t/** @return {string} The value colour for the current band. */\n\t\tvalueColor() {\n\t\t\tif (this.isBoardLook && this.variant === 'default') {\n\t\t\t\treturn 'var(--color-main-text)'\n\t\t\t}\n\t\t\treturn VARIANT_COLORS[this.variant] || VARIANT_COLORS.default\n\t\t},\n\n\t\t/** @return {string} The tile label. */\n\t\tresolvedLabel() {\n\t\t\treturn this.content?.label || ''\n\t\t},\n\n\t\t/** @return {string} The MDI icon name. */\n\t\tresolvedIcon() {\n\t\t\treturn this.content?.icon || 'ClockOutline'\n\t\t},\n\n\t\t/** @return {object} Inline style for the icon circle. */\n\t\ticonCircleStyle() {\n\t\t\treturn { color: this.valueColor }\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The host's translate, or the text unchanged.\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {string} The translation.\n\t\t */\n\t\ttranslate(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\t/**\n\t\t * Dispatch `content.action` with the record's tokens resolved, through\n\t\t * the page's dispatcher when there is one.\n\t\t *\n\t\t * @return {unknown}\n\t\t * @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card\n\t\t */\n\t\trunAction() {\n\t\t\tconst action = this.content?.action\n\t\t\tif (!action || typeof action !== 'object') {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tconst ctx = resolveObjectTokenContext(this.cnObjectContextInjected, this.cnDetailObjectContextInjected) || {}\n\t\t\tconst tokenCtx = { ...ctx, object: this.record || ctx.object || null, objectId: this.record?.id ?? ctx.objectId ?? null }\n\t\t\tconst resolved = resolveDeepTokens(action, tokenCtx)\n\t\t\tif (typeof this.cnDispatchAction === 'function') {\n\t\t\t\treturn this.cnDispatchAction(resolved)\n\t\t\t}\n\t\t\treturn dispatchAction(resolved, { router: this.$router || null, tokenCtx })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-countdown-widget__value {\n\twhite-space: nowrap;\n}\n\n.cn-countdown-widget__progress {\n\tbackground: var(--color-border);\n\tborder-radius: 3px;\n\tdisplay: block;\n\theight: 6px;\n\tmargin-block-start: 10px;\n\toverflow: hidden;\n}\n\n.cn-countdown-widget__progress-fill {\n\tbackground: var(--color-primary-element);\n\tdisplay: block;\n\theight: 100%;\n}\n\n.cn-countdown-widget__action {\n\talign-self: flex-start;\n\tbackground: var(--color-background-dark);\n\tborder: 0;\n\tborder-radius: var(--border-radius-pill, 17px);\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n\tmargin-block-start: 10px;\n\tmin-height: 34px;\n\tpadding: 0 12px;\n}\n\n.cn-countdown-widget__action--primary {\n\tbackground: var(--color-primary-element-light);\n\tcolor: var(--color-primary-element-light-text);\n}\n\n.cn-countdown-widget__facts {\n\tdisplay: grid;\n\tgap: 8px 16px;\n\tgrid-template-columns: max-content 1fr;\n\tmargin: 10px 0 0;\n}\n\n.cn-countdown-widget__facts dt {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-countdown-widget__facts dd {\n\tmargin: 0;\n}\n\n.cn-countdown-widget__action:hover,\n.cn-countdown-widget__action:focus-visible {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-countdown-widget__sub {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n</style>\n"],"names":["_createVNode","_createElementBlock","_toDisplayString","_createElementVNode","_openBlock","_Fragment","_renderList","_normalizeClass"],"mappings":";;AAkBO,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,mBAAI,GAAA;;;AACT,EAAA,KAAA,EAAA,oBAAA;;;;AAMgB,EAAA,KAAK,EAAA,0BAAA;AAAA,EAAA,aAAA,EAAA,kBAAA;;;;AAGtB,EAAA,KAAA,EAAA,4BAAA;AAAA,EAAA,aAAA,EAAA,oBAAA;;;;;;;;8DAfyB,CAAA,CAAA,qBAAA,EAAA,QAAA,CAAA,OAAA,uDAAA,QAAA,CAAA,WAAA,EAAA,CAAA,CAAA,CAAA;;MAClB,QAAW,CAAA,WAAA;wCAEjB,CAAA,KAAA,EAAA;;AAFmB,UAAA,KAAK,EAAC,6CAAsB;AAAA,UAAA,KAAA,iBAAA,QAAA,CAAA,eAAA,CAAA;;UACnDA,WAAkB,CAAA,uBAAA,EAAA;AAAJ,YAAA,IAAI,EAAA,QAAA,CAAA,YAAA;AAAA,YAAA,IAAA,EAAA,EAAA;;;;;OAId,QAAA,CAAA,aAAA;wBAALC,kBAEM,CAAA,KAAA,EAFN,UAEM,EAAAC,eAAA,CADH,QAAc,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;;MAEjBC,kBAEM,CAAA,KAAA,EAAA;AAFD,QAAA,KAAA,EAAA,+CAAA;AAAA,QAAA,KAAA,0BAAA,QAAA,CAAA,UAAA,EAAA,CAAA;yBACD,QAAQ,CAAA,QAAA,CAAA,EAAA,CAAA,mBAAA;OAED,QAAS,CAAA,QAAA;0CAEd,CAAA,KAAA,YAAA,EAAAD,eAAA,CADF,QAAQ,CAAA,QAAA,CAAA,EAAA,CAAA,YAAA;;AAER,MAAA,CAAA,QAAA,CAAA,KAAA,CAAA,MAAA;AAAJ,WAAAE,SAAA,EAAA,EAAAH,kBAAA,CAKK,MALL,UAKK,EAAA;8BAJJA,kBAGW,CAAAI,QAAA,EAAA,IAAA,EAAAC,UAAA,CAHR,QAAA,CAAA,KAAA,EAAA,CAAA,IAAA,KAAA;;AAAA,gBAAA,GAAA,EAAA,IAAA,CAAA,KAAA;;gBACFH,kBAAsB,CAAA,IAAA,EAAA,IAAA,EAAAD,eAAA,CAAf,IAAI,CAAC;gBACZC,kBAAyB,CAAA,IAAA,EAAA,IAAA,EAAAD,eAAA,CAAlB,IAAI,CAAC,KAAK,CAAA,EAAA,CAAA,YAAA;;;;;OAIZ,QAAQ,CAAA,QAAA,KAAA,IAAA;wBADdD,kBAUM,CAAA,MAAA,EAAA;;AARN,YAAA,KAAK,EAAC,+BAAY;AAClB,YAAA,IAAI,EAAC,OAAO;AACX,YAAA,YAAU,EAAE,QAAa,CAAA,aAAA;AACzB,YAAA,eAAI,EAAA,IAAA,CAAA,KAAA,CAAA,QAAA,CAAA,QAAA,CAAA;AACL,YAAA,eAAK,EAAA,GAAA;AACL,YAAA,eAAa,EAAC,KAAG;AACjB,YAAA,aAAW,EAAC,uBAAA;;YACZE,kBAAkC,CAAA,MAAA,EAAA;AAA5B,cAAA,KAAK,EAAC,oCAAsB;AAAA,cAAA,KAAA,6BAAA,QAAA,CAAA,QAAA,CAAA,CAAA,CAAA,EAAA,CAAA;;;;OAG5B,QAAW,CAAA,WAAA;wBADjBF;;AAEA,YAAA,IAAI,EAAC,QAAQ;YACb,KAAK,EAAAM,cAAA,CAAA,CAAC,6BAAC,EAAA,EAAA,sCAAA,EACJ,MAAA,CAAA,OAAA,CAAA,MAAA,IAAA,MAAA,CAAA,OAAA,CAAA,MAAA,CAAA,OAAA,KAAA,SAAA,EAAA,CAAA,CAAA;AACH,YAAA,aAAE,EAAA,qBAAA;;;;;;;;;;"}
|
|
@@ -2,7 +2,7 @@ import script from './CnDashboardPage.vue2.js';
|
|
|
2
2
|
import { render } from './CnDashboardPage.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-4f9a30ca";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|