@conduction/nextcloud-vue 2.111.0 → 2.113.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/CnActionButtons/CnActionButtons.vue.js +1 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js +4 -6
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +3 -2
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +4 -2
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +3 -2
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +2 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +54 -5
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +16 -2
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +10 -0
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +2 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +22 -2
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +17 -3
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +18 -3
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +52 -13
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +31 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +48 -11
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +160 -97
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +2 -2
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +222 -15
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +24 -7
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js +1 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js +21 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js.map +1 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +36 -2
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +44 -5
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +1 -0
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +22 -2
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js +36 -16
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue.js +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +29 -12
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +3 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js.map +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js.map +1 -1
- package/dist/esm/composables/useRefLabels.js +26 -6
- package/dist/esm/composables/useRefLabels.js.map +1 -1
- package/dist/esm/integrations/registry.js +4 -0
- package/dist/esm/integrations/registry.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -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 +680 -449
- package/dist/esm/store/plugins/liveUpdates.js +3 -1
- package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
- package/dist/esm/store/plugins/logs.js +2 -1
- package/dist/esm/store/plugins/logs.js.map +1 -1
- package/dist/esm/utils/actionLink.js +104 -1
- package/dist/esm/utils/actionLink.js.map +1 -1
- package/dist/esm/utils/detailActionModel.js +28 -1
- package/dist/esm/utils/detailActionModel.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +13 -5
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/headerFieldChips.js +21 -2
- package/dist/esm/utils/headerFieldChips.js.map +1 -1
- package/dist/esm/utils/mapList.js +26 -11
- package/dist/esm/utils/mapList.js.map +1 -1
- package/dist/esm/utils/savedViewMenu.js +12 -5
- package/dist/esm/utils/savedViewMenu.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 +2800 -1933
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +680 -449
- package/l10n/en.json +3 -1
- package/l10n/nl.json +3 -1
- package/package.json +1 -1
- package/src/components/CnActionButtons/CnActionButtons.vue +4 -6
- package/src/components/CnActionDialog/CnActionDialog.vue +32 -9
- package/src/components/CnAssignDialog/CnAssignDialog.vue +4 -2
- package/src/components/CnBoardView/CnBoardView.vue +58 -5
- package/src/components/CnCardGrid/CnCardGrid.md +1 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +11 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +27 -3
- package/src/components/CnChartWidget/CnChartWidget.vue +6 -0
- package/src/components/CnChoiceCards/CnChoiceCards.vue +0 -3
- package/src/components/CnDashboardPage/CnDashboardPage.vue +20 -5
- package/src/components/CnDetailPage/CnDetailPage.vue +62 -14
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +100 -5
- package/src/components/CnFormDialog/CnFormDialog.vue +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +233 -18
- package/src/components/CnNcDashboardList/CnNcDashboardList.vue +21 -1
- package/src/components/CnObjectCard/CnObjectCard.md +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +51 -5
- package/src/components/CnPartyPicker/CnPartyPicker.vue +62 -16
- package/src/components/CnReportsPage/CnReportsPage.vue +31 -12
- package/src/components/CnSearchSelect/CnSearchSelect.vue +8 -0
- package/src/composables/useRefLabels.js +25 -5
- package/src/css/badge.css +15 -13
- package/src/css/board.css +0 -1
- package/src/css/dialog.css +53 -3
- package/src/css/index-page.css +26 -1
- package/src/css/look-board-detail.css +36 -15
- package/src/css/look-board-index.css +67 -15
- package/src/css/look-board.css +32 -18
- package/src/integrations/registry.js +4 -0
- package/src/schemas/app-manifest-v2.schema.json +61 -13
- package/src/store/plugins/liveUpdates.js +3 -1
- package/src/store/plugins/logs.js +2 -1
- package/src/utils/actionLink.js +103 -0
- package/src/utils/detailActionModel.js +27 -0
- package/src/utils/fillTemplate.js +13 -5
- package/src/utils/headerFieldChips.js +21 -2
- package/src/utils/mapList.js +26 -11
- package/src/utils/savedViewMenu.js +12 -5
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fillTemplate.js","sources":["../../../src/utils/fillTemplate.js"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 Conduction B.V.\n// SPDX-License-Identifier: EUPL-1.2\n\nimport { boardDateLocale, formatBoardDate, formatBoardWeekday, formatRelativeDate, formatTimeOfDay } from './boardDate.js'\nimport { parseDateValue } from './dateVariant.js'\nimport { formatMoney } from './formatMetric.js'\nimport { readPath } from './readPath.js'\nimport { userDisplayName } from './userDisplayName.js'\n\nconst TOKEN = /\\{([^{}\\s|]+)(?:\\|([a-z]+)(?::([^{}\\s]+))?)?\\}/g\n\n/**\n * A moment in the board's short form with the time: \"4 okt, 10:52\", or\n * \"14 feb 2024, 09:00\" outside the current year.\n *\n * @param {unknown} value A Date, a timestamp or an ISO string.\n * @param {Date} [now] The reference moment.\n * @return {string} The text, or '' when the value is not a date.\n * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record\n */\nexport function formatBoardDateTime(value, now = new Date()) {\n\tconst day = formatBoardDate(value, now)\n\tconst date = parseDateValue(value)\n\tif (day === '' || date === null) {\n\t\treturn ''\n\t}\n\tlet time\n\ttry {\n\t\ttime = new Intl.DateTimeFormat(boardDateLocale(), { hour: '2-digit', minute: '2-digit' }).format(date)\n\t} catch {\n\t\ttime = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit' }).format(date)\n\t}\n\treturn `${day}, ${time}`\n}\n\n/**\n * A date with the month written out: \"7 oktober\", or \"6 oktober 2026\"\n * outside the current year or when `withYear` asks for it.\n *\n * @param {unknown} value A Date, a timestamp or an ISO string.\n * @param {Date} now The reference moment.\n * @param {boolean} [withYear] Always write the year.\n * @return {string} The text, or '' when the value is not a date.\n * @spec openspec/changes/screens-header-meta-tokens/specs/detail-page-board-look/spec.md#requirement-the-header-meta-line-formats-users-long-dates-and-enum-labels\n */\nexport function formatLongDate(value, now = new Date(), withYear = false) {\n\tconst date = parseDateValue(value)\n\tif (date === null) {\n\t\treturn ''\n\t}\n\tconst options = { day: 'numeric', month: 'long' }\n\tif (withYear || date.getFullYear() !== now.getFullYear()) {\n\t\toptions.year = 'numeric'\n\t}\n\ttry {\n\t\treturn new Intl.DateTimeFormat(boardDateLocale(), options).format(date)\n\t} catch {\n\t\treturn new Intl.DateTimeFormat(undefined, options).format(date)\n\t}\n}\n\n/**\n * Format one token value.\n *\n * @param {unknown} value The raw value.\n * @param {string} format `date`, `datetime`, `weekday`, `longdate`, `time`, `user`, `currency`, `relative`, `relativetime`, `days` or '' (as is).\n * @param {Date} now The reference moment.\n * @param {string} [arg] The format's argument (`currency:EUR`, or a row path holding the code).\n * @param {object|null} [object] The row, for a currency read from a field.\n * @param {object|null} [config] The app config map, for the reporting currency.\n * @return {string} The text, '' when nothing can be shown.\n */\nfunction formatValue(value, format, now, arg = '', object = null, config = null) {\n\tif (value === null || value === undefined || value === '' || typeof value === 'object') {\n\t\treturn ''\n\t}\n\tif (format === 'date') {\n\t\treturn formatBoardDate(value, now)\n\t}\n\tif (format === 'datetime') {\n\t\treturn formatBoardDateTime(value, now)\n\t}\n\tif (format === 'weekday') {\n\t\treturn formatBoardWeekday(value, now, { today: arg === 'today' })\n\t}\n\tif (format === 'longdate') {\n\t\treturn formatLongDate(value, now, arg === 'year')\n\t}\n\tif (format === 'user') {\n\t\treturn userDisplayName(value)\n\t}\n\tif (format === 'time') {\n\t\tconst date = parseDateValue(value)\n\t\treturn date === null ? '' : formatTimeOfDay(date)\n\t}\n\tif (format === 'currency') {\n\t\t// `{amount|currency:EUR}` names the code, `{amount|currency:price.code}`\n\t\t// reads it from the row, `{amount|currency}` is the app's reporting\n\t\t// currency (EUR without one).\n\t\tconst isCode = /^[A-Za-z]{3}$/.test(arg)\n\t\treturn formatMoney(value, { currency: isCode ? arg : undefined, currencyField: !isCode && arg ? arg : undefined }, object, config)\n\t}\n\tif (format === 'relative') {\n\t\treturn formatRelativeDate(value, { now })\n\t}\n\tif (format === 'relativetime') {\n\t\treturn formatRelativeDate(value, { now, withTime: true })\n\t}\n\tif (format === 'days') {\n\t\t// Whole days from the date to now (0 for today; negative for a future date).\n\t\tconst date = parseDateValue(value)\n\t\treturn date === null ? '' : String(Math.floor((now.getTime() - date.getTime()) / 86400000))\n\t}\n\treturn String(value)\n}\n\n/**\n * Fill a row template such as `\"{identifier} · {caseTypeName}\"` or\n * `\"Termijn {deadline|date}\"` from an object.\n *\n * Every `{path}` token is a dot path into the object; `{path|date}` and\n * `{path|datetime}` format it as the boards write dates, `{path|weekday}` adds\n * the short weekday (\"wo 7 okt\"; `{path|weekday:today}` reads \"Vandaag, wo 7 okt\"\n * for today), `{path|longdate}` writes the month out (\"7 oktober\";\n * `{path|longdate:year}` always adds the year), `{path|time}` is the time\n * of day (\"08:45\"), `{path|user}` is a user id's display name (looked up\n * once, '' until it arrives), `{path|currency}`,\n * `{path|currency:EUR}` and `{path|currency:field}` write an amount in a\n * currency (a code, or the row field that holds it; without either, or when\n * the row field is empty, the app's reporting currency), `{path|relative}` and\n * `{path|relativetime}` say \"today\", \"yesterday\" and then the short date\n * (\"vandaag, 09.42\"), and `{path|days}` is the\n * whole days since that date (\"has waited {createdAt|days} days\"). A token without a\n * value empties the WHOLE template, because \"Termijn \" is worse than nothing.\n * A template without tokens is returned as written (a constant label).\n *\n * @param {string} template The template, already translated.\n * @param {object|null} object The object to read.\n * @param {Date} [now] The reference moment for dates.\n * @param {{config?: object}|null} [context] The app config map, for the reporting currency of a money token.\n * @return {string} The filled text, or ''.\n * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record\n */\nexport function fillTemplate(template, object, now = new Date(), context = null) {\n\tif (typeof template !== 'string' || template.trim() === '') {\n\t\treturn ''\n\t}\n\tlet missing = false\n\tconst text = template.replace(TOKEN, (_match, path, format, arg) => {\n\t\tconst value = object && typeof object === 'object' ? readPath(object, path) : undefined\n\t\tconst shown = formatValue(value, format || '', now, arg || '', object, context && context.config ? context.config : null)\n\t\tif (shown === '') {\n\t\t\tmissing = true\n\t\t}\n\t\treturn shown\n\t})\n\treturn missing ? '' : text.trim()\n}\n\n/**\n * The first of several templates that fills: `[\"Klaar {closedAt|date}\",\n * \"Termijn {deadline|date}\"]` reads \"Klaar ...\" once the case is closed and\n * \"Termijn ...\" while it is open. A single string is one candidate.\n *\n * @param {string|string[]} templates The candidates, in order.\n * @param {object|null} object The object to read.\n * @param {(key: string) => string} [translate] Applied to each template before filling.\n * @param {Date} [now] The reference moment for dates.\n * @param {{config?: object}|null} [context] Passed on to `fillTemplate`.\n * @return {string} The first filled text, or ''.\n * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record\n */\nexport function firstFilled(templates, object, translate = (key) => key, now = new Date(), context = null) {\n\tconst list = Array.isArray(templates) ? templates : [templates]\n\tfor (const template of list) {\n\t\tif (typeof template !== 'string') {\n\t\t\tcontinue\n\t\t}\n\t\tconst text = fillTemplate(translate(template), object, now, context)\n\t\tif (text !== '') {\n\t\t\treturn text\n\t\t}\n\t}\n\treturn ''\n}\n\n/**\n * Fill a template such as `\"{identifier} · {requester}\"` field by field\n * (a table column's `secondary` line, a detail page's meta line).\n *\n * A field without a value takes its separator with it: the text between two\n * fields belongs to the field after it, so an empty requester leaves\n * \"2026-0002\", not \"2026-0002 ·\", and an empty first field does not leave a\n * leading separator either. Text before the first field and after the last\n * one is kept as written. A template whose fields all have values fills\n * exactly as before.\n *\n * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-empty-field-takes-its-separator-with-it\n * @param {string} template The template, with `{field}` placeholders.\n * @param {(key: string) => string} valueOf The text of a field ('' when empty).\n * @return {string} The filled template.\n */\nexport function joinTemplateParts(template, valueOf) {\n\t// split() with a capture group alternates literal, field, literal, …\n\tconst parts = template.split(/\\{([^}]+)\\}/)\n\tconst lead = parts[0]\n\tconst tail = parts[parts.length - 1]\n\tlet body = ''\n\tfor (let i = 1; i < parts.length; i += 2) {\n\t\tconst value = valueOf(parts[i].trim())\n\t\tif (value === '') {\n\t\t\tcontinue\n\t\t}\n\t\t// The literal before this field: the separator, unless nothing has\n\t\t// been written yet (then there is nothing to separate from).\n\t\tconst separator = i === 1 ? '' : parts[i - 1]\n\t\tbody += (body === '' ? '' : separator) + value\n\t}\n\treturn lead + body + tail\n}\n"],"names":[],"mappings":";;;;;;AAAA;AACA;AACA;AAMA;AACA,MAAM,KAAK,GAAG,kDAAiD;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,mBAAmB,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,EAAE;AAC7D,CAAC,MAAM,GAAG,GAAG,eAAe,CAAC,KAAK,EAAE,GAAG,EAAC;AACxC,CAAC,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,EAAC;AACnC,CAAC,IAAI,GAAG,KAAK,EAAE,IAAI,IAAI,KAAK,IAAI,EAAE;AAClC,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,IAAI,KAAI;AACT,CAAC,IAAI;AACL,EAAE,IAAI,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,eAAe,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,EAAC;AACxG,EAAE,CAAC,MAAM;AACT,EAAE,IAAI,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,EAAC;AAChG,EAAE;AACF,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;AACzB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,cAAc,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,EAAE,QAAQ,GAAG,KAAK,EAAE;AAC1E,CAAC,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,EAAC;AACnC,CAAC,IAAI,IAAI,KAAK,IAAI,EAAE;AACpB,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,OAAO,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAE;AAClD,CAAC,IAAI,QAAQ,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,WAAW,EAAE,EAAE;AAC3D,EAAE,OAAO,CAAC,IAAI,GAAG,UAAS;AAC1B,EAAE;AACF,CAAC,IAAI;AACL,EAAE,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,eAAe,EAAE,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;AACzE,EAAE,CAAC,MAAM;AACT,EAAE,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;AACjE,EAAE;AACF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE;AACjF,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AACzF,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,IAAI,MAAM,KAAK,MAAM,EAAE;AACxB,EAAE,OAAO,eAAe,CAAC,KAAK,EAAE,GAAG,CAAC;AACpC,EAAE;AACF,CAAC,IAAI,MAAM,KAAK,UAAU,EAAE;AAC5B,EAAE,OAAO,mBAAmB,CAAC,KAAK,EAAE,GAAG,CAAC;AACxC,EAAE;AACF,CAAC,IAAI,MAAM,KAAK,SAAS,EAAE;AAC3B,EAAE,OAAO,kBAAkB,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,KAAK,OAAO,EAAE,CAAC;AACnE,EAAE;AACF,CAAC,IAAI,MAAM,KAAK,UAAU,EAAE;AAC5B,EAAE,OAAO,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,KAAK,MAAM,CAAC;AACnD,EAAE;AACF,CAAC,IAAI,MAAM,KAAK,MAAM,EAAE;AACxB,EAAE,OAAO,eAAe,CAAC,KAAK,CAAC;AAC/B,EAAE;AACF,CAAC,IAAI,MAAM,KAAK,MAAM,EAAE;AACxB,EAAE,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,EAAC;AACpC,EAAE,OAAO,IAAI,KAAK,IAAI,GAAG,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC;AACnD,EAAE;AACF,CAAC,IAAI,MAAM,KAAK,UAAU,EAAE;AAC5B;AACA;AACA;AACA,EAAE,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,EAAC;AAC1C,EAAE,OAAO,WAAW,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,EAAE,aAAa,EAAE,CAAC,MAAM,IAAI,GAAG,GAAG,GAAG,GAAG,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC;AACpI,EAAE;AACF,CAAC,IAAI,MAAM,KAAK,UAAU,EAAE;AAC5B,EAAE,OAAO,kBAAkB,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC;AAC3C,EAAE;AACF,CAAC,IAAI,MAAM,KAAK,cAAc,EAAE;AAChC,EAAE,OAAO,kBAAkB,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC3D,EAAE;AACF,CAAC,IAAI,MAAM,KAAK,MAAM,EAAE;AACxB;AACA,EAAE,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,EAAC;AACpC,EAAE,OAAO,IAAI,KAAK,IAAI,GAAG,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,QAAQ,CAAC,CAAC;AAC7F,EAAE;AACF,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC;AACrB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,EAAE,OAAO,GAAG,IAAI,EAAE;AACjF,CAAC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;AAC7D,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,IAAI,OAAO,GAAG,MAAK;AACpB,CAAC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK;AACrE,EAAE,MAAM,KAAK,GAAG,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,GAAG,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,UAAS;AACzF,EAAE,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,MAAM,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,EAAC;AAC3H,EAAE,IAAI,KAAK,KAAK,EAAE,EAAE;AACpB,GAAG,OAAO,GAAG,KAAI;AACjB,GAAG;AACH,EAAE,OAAO,KAAK;AACd,EAAE,EAAC;AACH,CAAC,OAAO,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE;AAClC,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,GAAG,CAAC,GAAG,KAAK,GAAG,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,EAAE,OAAO,GAAG,IAAI,EAAE;AAC3G,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,SAAS,GAAG,CAAC,SAAS,EAAC;AAChE,CAAC,KAAK,MAAM,QAAQ,IAAI,IAAI,EAAE;AAC9B,EAAE,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE;AACpC,GAAG,QAAQ;AACX,GAAG;AACH,EAAE,MAAM,IAAI,GAAG,YAAY,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAC;AACtE,EAAE,IAAI,IAAI,KAAK,EAAE,EAAE;AACnB,GAAG,OAAO,IAAI;AACd,GAAG;AACH,EAAE;AACF,CAAC,OAAO,EAAE;AACV,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE;AACrD;AACA,CAAC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,aAAa,EAAC;AAC5C,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,EAAC;AACtB,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAC;AACrC,CAAC,IAAI,IAAI,GAAG,GAAE;AACd,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE;AAC3C,EAAE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAC;AACxC,EAAE,IAAI,KAAK,KAAK,EAAE,EAAE;AACpB,GAAG,QAAQ;AACX,GAAG;AACH;AACA;AACA,EAAE,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,EAAC;AAC/C,EAAE,IAAI,IAAI,CAAC,IAAI,KAAK,EAAE,GAAG,EAAE,GAAG,SAAS,IAAI,MAAK;AAChD,EAAE;AACF,CAAC,OAAO,IAAI,GAAG,IAAI,GAAG,IAAI;AAC1B;;;;"}
|
|
1
|
+
{"version":3,"file":"fillTemplate.js","sources":["../../../src/utils/fillTemplate.js"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 Conduction B.V.\n// SPDX-License-Identifier: EUPL-1.2\n\nimport { boardDateLocale, formatBoardDate, formatBoardWeekday, formatRelativeDate, formatTimeOfDay } from './boardDate.js'\nimport { parseDateValue } from './dateVariant.js'\nimport { formatMoney } from './formatMetric.js'\nimport { readPath } from './readPath.js'\nimport { userDisplayName } from './userDisplayName.js'\n\nconst TOKEN = /\\{([^{}\\s|]+)(?:\\|([A-Za-z]+)(?::([^{}\\s]+))?)?\\}/g\n\n/**\n * A moment in the board's short form with the time: \"4 okt, 10:52\", or\n * \"14 feb 2024, 09:00\" outside the current year.\n *\n * @param {unknown} value A Date, a timestamp or an ISO string.\n * @param {Date} [now] The reference moment.\n * @return {string} The text, or '' when the value is not a date.\n * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record\n */\nexport function formatBoardDateTime(value, now = new Date()) {\n\tconst day = formatBoardDate(value, now)\n\tconst date = parseDateValue(value)\n\tif (day === '' || date === null) {\n\t\treturn ''\n\t}\n\tlet time\n\ttry {\n\t\ttime = new Intl.DateTimeFormat(boardDateLocale(), { hour: '2-digit', minute: '2-digit' }).format(date)\n\t} catch {\n\t\ttime = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit' }).format(date)\n\t}\n\treturn `${day}, ${time}`\n}\n\n/**\n * A date with the month written out: \"7 oktober\", or \"6 oktober 2026\"\n * outside the current year or when `withYear` asks for it.\n *\n * @param {unknown} value A Date, a timestamp or an ISO string.\n * @param {Date} now The reference moment.\n * @param {boolean} [withYear] Always write the year.\n * @return {string} The text, or '' when the value is not a date.\n * @spec openspec/changes/screens-header-meta-tokens/specs/detail-page-board-look/spec.md#requirement-the-header-meta-line-formats-users-long-dates-and-enum-labels\n */\nexport function formatLongDate(value, now = new Date(), withYear = false) {\n\tconst date = parseDateValue(value)\n\tif (date === null) {\n\t\treturn ''\n\t}\n\tconst options = { day: 'numeric', month: 'long' }\n\tif (withYear || date.getFullYear() !== now.getFullYear()) {\n\t\toptions.year = 'numeric'\n\t}\n\ttry {\n\t\treturn new Intl.DateTimeFormat(boardDateLocale(), options).format(date)\n\t} catch {\n\t\treturn new Intl.DateTimeFormat(undefined, options).format(date)\n\t}\n}\n\n/**\n * Format one token value.\n *\n * @param {unknown} value The raw value.\n * @param {string} format `date`, `datetime`, `weekday`, `longdate`, `time`, `user`, `currency`, `relative`, `relativetime`, `days` or '' (as is).\n * @param {Date} now The reference moment.\n * @param {string} [arg] The format's argument (`currency:EUR`, or a row path holding the code).\n * @param {object|null} [object] The row, for a currency read from a field.\n * @param {object|null} [config] The app config map, for the reporting currency.\n * @param {object|null} [formatters] The app's named formatters (`cnFormatters`), for any other format name.\n * @return {string} The text, '' when nothing can be shown.\n * @spec openspec/changes/map-list-formatters/specs/map-list-formatters/spec.md#requirement-a-map-list-reads-display-names-through-the-apps-formatters\n */\nfunction formatValue(value, format, now, arg = '', object = null, config = null, formatters = null) {\n\tif (value === null || value === undefined || value === '' || typeof value === 'object') {\n\t\treturn ''\n\t}\n\tif (format !== '' && formatters && typeof formatters[format] === 'function') {\n\t\tconst shown = formatters[format](value, object)\n\t\treturn shown === null || shown === undefined ? '' : String(shown)\n\t}\n\tif (format === 'date') {\n\t\treturn formatBoardDate(value, now)\n\t}\n\tif (format === 'datetime') {\n\t\treturn formatBoardDateTime(value, now)\n\t}\n\tif (format === 'weekday') {\n\t\treturn formatBoardWeekday(value, now, { today: arg === 'today' })\n\t}\n\tif (format === 'longdate') {\n\t\treturn formatLongDate(value, now, arg === 'year')\n\t}\n\tif (format === 'user') {\n\t\treturn userDisplayName(value)\n\t}\n\tif (format === 'time') {\n\t\tconst date = parseDateValue(value)\n\t\treturn date === null ? '' : formatTimeOfDay(date)\n\t}\n\tif (format === 'currency') {\n\t\t// `{amount|currency:EUR}` names the code, `{amount|currency:price.code}`\n\t\t// reads it from the row, `{amount|currency}` is the app's reporting\n\t\t// currency (EUR without one).\n\t\tconst isCode = /^[A-Za-z]{3}$/.test(arg)\n\t\treturn formatMoney(value, { currency: isCode ? arg : undefined, currencyField: !isCode && arg ? arg : undefined }, object, config)\n\t}\n\tif (format === 'relative') {\n\t\treturn formatRelativeDate(value, { now })\n\t}\n\tif (format === 'relativetime') {\n\t\treturn formatRelativeDate(value, { now, withTime: true })\n\t}\n\tif (format === 'days') {\n\t\t// Whole days from the date to now (0 for today; negative for a future date).\n\t\tconst date = parseDateValue(value)\n\t\treturn date === null ? '' : String(Math.floor((now.getTime() - date.getTime()) / 86400000))\n\t}\n\treturn String(value)\n}\n\n/**\n * Fill a row template such as `\"{identifier} · {caseTypeName}\"` or\n * `\"Termijn {deadline|date}\"` from an object.\n *\n * Every `{path}` token is a dot path into the object; `{path|date}` and\n * `{path|datetime}` format it as the boards write dates, `{path|weekday}` adds\n * the short weekday (\"wo 7 okt\"; `{path|weekday:today}` reads \"Vandaag, wo 7 okt\"\n * for today), `{path|longdate}` writes the month out (\"7 oktober\";\n * `{path|longdate:year}` always adds the year), `{path|time}` is the time\n * of day (\"08:45\"), `{path|user}` is a user id's display name (looked up\n * once, '' until it arrives), `{path|currency}`,\n * `{path|currency:EUR}` and `{path|currency:field}` write an amount in a\n * currency (a code, or the row field that holds it; without either, or when\n * the row field is empty, the app's reporting currency), `{path|relative}` and\n * `{path|relativetime}` say \"today\", \"yesterday\" and then the short date\n * (\"vandaag, 09.42\"), and `{path|days}` is the\n * whole days since that date (\"has waited {createdAt|days} days\"). Any other\n * name is one of the app's formatters, passed as `context.formatters`\n * (`{id|caseAddress}`). A token without a\n * value empties the WHOLE template, because \"Termijn \" is worse than nothing.\n * A template without tokens is returned as written (a constant label).\n *\n * @param {string} template The template, already translated.\n * @param {object|null} object The object to read.\n * @param {Date} [now] The reference moment for dates.\n * @param {{config?: object, formatters?: object}|null} [context] The app config map, for the reporting currency of a money token, and the app's named formatters.\n * @return {string} The filled text, or ''.\n * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record\n */\nexport function fillTemplate(template, object, now = new Date(), context = null) {\n\tif (typeof template !== 'string' || template.trim() === '') {\n\t\treturn ''\n\t}\n\tlet missing = false\n\tconst text = template.replace(TOKEN, (_match, path, format, arg) => {\n\t\tconst value = object && typeof object === 'object' ? readPath(object, path) : undefined\n\t\tconst shown = formatValue(value, format || '', now, arg || '', object, context && context.config ? context.config : null, context && context.formatters ? context.formatters : null)\n\t\tif (shown === '') {\n\t\t\tmissing = true\n\t\t}\n\t\treturn shown\n\t})\n\treturn missing ? '' : text.trim()\n}\n\n/**\n * The first of several templates that fills: `[\"Klaar {closedAt|date}\",\n * \"Termijn {deadline|date}\"]` reads \"Klaar ...\" once the case is closed and\n * \"Termijn ...\" while it is open. A single string is one candidate.\n *\n * @param {string|string[]} templates The candidates, in order.\n * @param {object|null} object The object to read.\n * @param {(key: string) => string} [translate] Applied to each template before filling.\n * @param {Date} [now] The reference moment for dates.\n * @param {{config?: object}|null} [context] Passed on to `fillTemplate`.\n * @return {string} The first filled text, or ''.\n * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record\n */\nexport function firstFilled(templates, object, translate = (key) => key, now = new Date(), context = null) {\n\tconst list = Array.isArray(templates) ? templates : [templates]\n\tfor (const template of list) {\n\t\tif (typeof template !== 'string') {\n\t\t\tcontinue\n\t\t}\n\t\tconst text = fillTemplate(translate(template), object, now, context)\n\t\tif (text !== '') {\n\t\t\treturn text\n\t\t}\n\t}\n\treturn ''\n}\n\n/**\n * Fill a template such as `\"{identifier} · {requester}\"` field by field\n * (a table column's `secondary` line, a detail page's meta line).\n *\n * A field without a value takes its separator with it: the text between two\n * fields belongs to the field after it, so an empty requester leaves\n * \"2026-0002\", not \"2026-0002 ·\", and an empty first field does not leave a\n * leading separator either. Text before the first field and after the last\n * one is kept as written. A template whose fields all have values fills\n * exactly as before.\n *\n * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-empty-field-takes-its-separator-with-it\n * @param {string} template The template, with `{field}` placeholders.\n * @param {(key: string) => string} valueOf The text of a field ('' when empty).\n * @return {string} The filled template.\n */\nexport function joinTemplateParts(template, valueOf) {\n\t// split() with a capture group alternates literal, field, literal, …\n\tconst parts = template.split(/\\{([^}]+)\\}/)\n\tconst lead = parts[0]\n\tconst tail = parts[parts.length - 1]\n\tlet body = ''\n\tfor (let i = 1; i < parts.length; i += 2) {\n\t\tconst value = valueOf(parts[i].trim())\n\t\tif (value === '') {\n\t\t\tcontinue\n\t\t}\n\t\t// The literal before this field: the separator, unless nothing has\n\t\t// been written yet (then there is nothing to separate from).\n\t\tconst separator = i === 1 ? '' : parts[i - 1]\n\t\tbody += (body === '' ? '' : separator) + value\n\t}\n\treturn lead + body + tail\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,12 @@
|
|
|
11
11
|
/** The renderers a header chip can use. */
|
|
12
12
|
const HEADER_CHIP_FORMATS = Object.freeze(['text', 'mono', 'badge', 'user', 'date']);
|
|
13
13
|
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* The variants a colour name maps onto; anything else is neutral. A name is a
|
|
16
|
+
* badge role (`success`, `warning`, ...) or an NL Design System hue as a
|
|
17
|
+
* register stores it (`red`, `blue-light`, ...): a hue and its light tint
|
|
18
|
+
* take the same tone.
|
|
19
|
+
*/
|
|
15
20
|
const COLOR_VARIANTS = Object.freeze({
|
|
16
21
|
success: 'success',
|
|
17
22
|
warning: 'warning',
|
|
@@ -19,6 +24,20 @@ const COLOR_VARIANTS = Object.freeze({
|
|
|
19
24
|
info: 'info',
|
|
20
25
|
primary: 'primary',
|
|
21
26
|
neutral: 'default',
|
|
27
|
+
blue: 'info',
|
|
28
|
+
'blue-light': 'info',
|
|
29
|
+
green: 'success',
|
|
30
|
+
'green-light': 'success',
|
|
31
|
+
orange: 'warning',
|
|
32
|
+
'orange-light': 'warning',
|
|
33
|
+
red: 'error',
|
|
34
|
+
'red-light': 'error',
|
|
35
|
+
purple: 'purple',
|
|
36
|
+
'purple-light': 'purple',
|
|
37
|
+
teal: 'teal',
|
|
38
|
+
'teal-light': 'teal',
|
|
39
|
+
grey: 'default',
|
|
40
|
+
'grey-light': 'default',
|
|
22
41
|
});
|
|
23
42
|
|
|
24
43
|
/**
|
|
@@ -69,7 +88,7 @@ function isEmptyChipValue(value) {
|
|
|
69
88
|
/**
|
|
70
89
|
* Map a colour name read off a referenced object onto a badge variant.
|
|
71
90
|
*
|
|
72
|
-
* @param {*} name The colour name (`success`, `warning`, `error`, `info`, `neutral`).
|
|
91
|
+
* @param {*} name The colour name: a badge role (`success`, `warning`, `error`, `info`, `neutral`) or an NL Design System hue (`red`, `blue-light`, ...).
|
|
73
92
|
* @return {string} A `CnStatusBadge` variant; `default` (neutral) when unknown.
|
|
74
93
|
*/
|
|
75
94
|
function variantForColor(name) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headerFieldChips.js","sources":["../../../src/utils/headerFieldChips.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Pure helpers for the detail-page header field chips\n * (manifest `config.headerFields`).\n *\n * @spec openspec/changes/detail-header-field-chips/specs/detail-page-header/spec.md\n */\n\n/** The renderers a header chip can use. */\nexport const HEADER_CHIP_FORMATS = Object.freeze(['text', 'mono', 'badge', 'user', 'date'])\n\n
|
|
1
|
+
{"version":3,"file":"headerFieldChips.js","sources":["../../../src/utils/headerFieldChips.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Pure helpers for the detail-page header field chips\n * (manifest `config.headerFields`).\n *\n * @spec openspec/changes/detail-header-field-chips/specs/detail-page-header/spec.md\n */\n\n/** The renderers a header chip can use. */\nexport const HEADER_CHIP_FORMATS = Object.freeze(['text', 'mono', 'badge', 'user', 'date'])\n\n/**\n * The variants a colour name maps onto; anything else is neutral. A name is a\n * badge role (`success`, `warning`, ...) or an NL Design System hue as a\n * register stores it (`red`, `blue-light`, ...): a hue and its light tint\n * take the same tone.\n */\nconst COLOR_VARIANTS = Object.freeze({\n\tsuccess: 'success',\n\twarning: 'warning',\n\terror: 'error',\n\tinfo: 'info',\n\tprimary: 'primary',\n\tneutral: 'default',\n\tblue: 'info',\n\t'blue-light': 'info',\n\tgreen: 'success',\n\t'green-light': 'success',\n\torange: 'warning',\n\t'orange-light': 'warning',\n\tred: 'error',\n\t'red-light': 'error',\n\tpurple: 'purple',\n\t'purple-light': 'purple',\n\tteal: 'teal',\n\t'teal-light': 'teal',\n\tgrey: 'default',\n\t'grey-light': 'default',\n})\n\n/**\n * Normalise `headerFields`: a string is `{ key }`, an object keeps its known\n * keys, and anything without a usable `key` or with an unknown `format` is\n * dropped. A repeated key keeps its first entry.\n *\n * @param {Array<string|object>|null|undefined} fields The declared entries.\n * @return {Array<{key: string, format: string, labelField: string, colorField: string, warnWhenPast: boolean}>} The usable entries, in order.\n */\nexport function normalizeHeaderFields(fields) {\n\tif (!Array.isArray(fields)) {\n\t\treturn []\n\t}\n\tconst seen = new Set()\n\tconst out = []\n\tfor (const entry of fields) {\n\t\tconst raw = typeof entry === 'string' ? { key: entry } : entry\n\t\tif (!raw || typeof raw !== 'object' || typeof raw.key !== 'string' || raw.key === '' || seen.has(raw.key)) {\n\t\t\tcontinue\n\t\t}\n\t\tconst format = raw.format === undefined ? 'text' : raw.format\n\t\tif (!HEADER_CHIP_FORMATS.includes(format)) {\n\t\t\tcontinue\n\t\t}\n\t\tseen.add(raw.key)\n\t\tout.push({\n\t\t\tkey: raw.key,\n\t\t\tformat,\n\t\t\tlabelField: typeof raw.labelField === 'string' ? raw.labelField : '',\n\t\t\tcolorField: typeof raw.colorField === 'string' ? raw.colorField : '',\n\t\t\twarnWhenPast: raw.warnWhenPast === true,\n\t\t})\n\t}\n\treturn out\n}\n\n/**\n * Whether a field value has nothing to show.\n *\n * @param {*} value The raw value.\n * @return {boolean} True for null, undefined, an empty string or an empty array.\n */\nexport function isEmptyChipValue(value) {\n\treturn value === null || value === undefined || value === '' || (Array.isArray(value) && value.length === 0)\n}\n\n/**\n * Map a colour name read off a referenced object onto a badge variant.\n *\n * @param {*} name The colour name: a badge role (`success`, `warning`, `error`, `info`, `neutral`) or an NL Design System hue (`red`, `blue-light`, ...).\n * @return {string} A `CnStatusBadge` variant; `default` (neutral) when unknown.\n */\nexport function variantForColor(name) {\n\tconst key = typeof name === 'string' ? name.trim().toLowerCase() : ''\n\treturn COLOR_VARIANTS[key] || 'default'\n}\n\n/**\n * The display label of a referenced object: `labelField`, then `title`, then\n * `name`, then `@self.name`. A per-language map collapses to its first value.\n *\n * @param {object|null} obj The referenced object.\n * @param {string} [labelField] The preferred property.\n * @return {string} The label, or '' when none is usable.\n */\nexport function pickRefLabel(obj, labelField = '') {\n\tif (!obj || typeof obj !== 'object') {\n\t\treturn ''\n\t}\n\tconst candidates = [labelField ? obj[labelField] : undefined, obj.title, obj.name, obj['@self'] && obj['@self'].name]\n\tfor (const raw of candidates) {\n\t\tif (typeof raw === 'string' && raw !== '') {\n\t\t\treturn raw\n\t\t}\n\t\tif (typeof raw === 'number') {\n\t\t\treturn String(raw)\n\t\t}\n\t\tif (raw && typeof raw === 'object' && !Array.isArray(raw)) {\n\t\t\tconst first = Object.values(raw).find((v) => typeof v === 'string' && v !== '')\n\t\t\tif (first) {\n\t\t\t\treturn first\n\t\t\t}\n\t\t}\n\t}\n\treturn ''\n}\n\n/**\n * Whether a date value lies before now.\n *\n * @param {*} value A date string, number or Date.\n * @param {number} [now] The current time in ms (injectable for tests).\n * @return {boolean} True when it parses and is in the past.\n */\nexport function isPastDate(value, now = Date.now()) {\n\tconst time = value instanceof Date ? value.getTime() : new Date(value).getTime()\n\treturn !Number.isNaN(time) && time < now\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isBadgeVariant } from './badgeVariants.js';
|
|
2
|
-
import {
|
|
2
|
+
import { pillLook } from './detailActionModel.js';
|
|
3
|
+
import { fillTemplate, firstFilled } from './fillTemplate.js';
|
|
3
4
|
import { readPath } from './readPath.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -13,10 +14,14 @@ import { readPath } from './readPath.js';
|
|
|
13
14
|
* pin and the number in the list are the same, and the rows without one.
|
|
14
15
|
*
|
|
15
16
|
* `spec` is `mapConfig.list`: `{ title, sub?, pills?: [{ path?, text?, map?,
|
|
16
|
-
* colorMap? }], due? }`. `title`, `sub` and `due` are
|
|
17
|
-
* (`"{title}"`, `"{address} · {identifier}"`,
|
|
18
|
-
* translated by the caller;
|
|
19
|
-
*
|
|
17
|
+
* formatter?, colorMap?, variant?, resolve? }], due? }`. `title`, `sub` and `due` are
|
|
18
|
+
* field templates (`"{title}"`, `"{address} · {identifier}"`,
|
|
19
|
+
* `"{deadline|date}"`), already translated by the caller; `sub` may list
|
|
20
|
+
* several, the first that fills wins. A pill reads `path` (or shows `text`),
|
|
21
|
+
* maps it through `map` or the app formatter `formatter` (a reference's
|
|
22
|
+
* display name), and takes its tone from `colorMap` by the raw value, or,
|
|
23
|
+
* with `resolve`, the tone and icon of the record the value points at
|
|
24
|
+
* (`options.looks`, read by CnIndexPage as for its other pills).
|
|
20
25
|
*
|
|
21
26
|
* @param {Array<object>} rows The rows, in display order.
|
|
22
27
|
* @param {object} spec The list declaration.
|
|
@@ -24,9 +29,14 @@ import { readPath } from './readPath.js';
|
|
|
24
29
|
* @param {string} options.rowKey The row's key field.
|
|
25
30
|
* @param {Function} options.located Whether a row has a place on the map.
|
|
26
31
|
* @param {Function} [options.translate] The host's translate, for pill labels.
|
|
32
|
+
* @param {object} [options.formatters] The app's named formatters, for pills and template tokens.
|
|
33
|
+
* @param {object} [options.looks] The referenced records' looks by field and id (`{ [path]: { [id]: { color, icon } } }`).
|
|
27
34
|
* @return {{items: Array<{key: string, n: number, row: object, title: string, sub: string, due: string, pills: Array<{label: string, variant: string}>}>, missing: Array<object>}}
|
|
35
|
+
* @spec openspec/changes/map-list-formatters/specs/map-list-formatters/spec.md#requirement-a-map-list-reads-display-names-through-the-apps-formatters
|
|
36
|
+
* @spec openspec/changes/map-list-formatters/specs/map-list-formatters/spec.md#requirement-a-map-list-pill-takes-its-tone-and-icon-from-the-referenced-record
|
|
28
37
|
*/
|
|
29
|
-
function mapListItems(rows, spec, { rowKey, located, translate = (text) => text }) {
|
|
38
|
+
function mapListItems(rows, spec, { rowKey, located, translate = (text) => text, formatters = null, looks = null }) {
|
|
39
|
+
const context = formatters ? { formatters } : null;
|
|
30
40
|
const items = [];
|
|
31
41
|
const missing = [];
|
|
32
42
|
const list = spec && typeof spec === 'object' ? spec : {};
|
|
@@ -41,7 +51,9 @@ function mapListItems(rows, spec, { rowKey, located, translate = (text) => text
|
|
|
41
51
|
return null
|
|
42
52
|
}
|
|
43
53
|
const raw = typeof pill.path === 'string' && pill.path !== '' ? readPath(row, pill.path) : undefined;
|
|
44
|
-
const
|
|
54
|
+
const formatter = typeof pill.formatter === 'string' && formatters ? formatters[pill.formatter] : null;
|
|
55
|
+
const formatted = typeof formatter === 'function' && (typeof raw === 'string' || typeof raw === 'number') && raw !== '' ? formatter(raw, row) : undefined;
|
|
56
|
+
const mapped = pill.map && (typeof raw === 'string' || typeof raw === 'number') && Object.hasOwn(pill.map, raw) ? pill.map[raw] : formatted;
|
|
45
57
|
const text = mapped ?? pill.text ?? raw;
|
|
46
58
|
if (typeof text !== 'string' && typeof text !== 'number') {
|
|
47
59
|
return null
|
|
@@ -51,16 +63,19 @@ function mapListItems(rows, spec, { rowKey, located, translate = (text) => text
|
|
|
51
63
|
return null
|
|
52
64
|
}
|
|
53
65
|
const tone = pill.colorMap && (typeof raw === 'string' || typeof raw === 'number') ? pill.colorMap[raw] : pill.variant;
|
|
54
|
-
|
|
66
|
+
const fieldLooks = pill.resolve && looks && typeof pill.path === 'string' ? looks[pill.path] : null;
|
|
67
|
+
const look = pillLook(fieldLooks && (typeof raw === 'string' || typeof raw === 'number') ? fieldLooks[String(raw)] : null);
|
|
68
|
+
const variant = look.variant || tone;
|
|
69
|
+
return { label, variant: isBadgeVariant(variant) ? variant : 'default', ...(look.icon ? { icon: look.icon } : {}) }
|
|
55
70
|
})
|
|
56
71
|
.filter(Boolean);
|
|
57
72
|
items.push({
|
|
58
73
|
key: String(row[rowKey]),
|
|
59
74
|
n: items.length + 1,
|
|
60
75
|
row,
|
|
61
|
-
title: fillTemplate(list.title || '', row),
|
|
62
|
-
sub:
|
|
63
|
-
due: fillTemplate(list.due || '', row),
|
|
76
|
+
title: fillTemplate(list.title || '', row, undefined, context),
|
|
77
|
+
sub: list.sub ? firstFilled(list.sub, row, undefined, undefined, context) : '',
|
|
78
|
+
due: fillTemplate(list.due || '', row, undefined, context),
|
|
64
79
|
pills,
|
|
65
80
|
});
|
|
66
81
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mapList.js","sources":["../../../src/utils/mapList.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\nimport { isBadgeVariant } from './badgeVariants.js'\nimport { fillTemplate } from './fillTemplate.js'\nimport { readPath } from './readPath.js'\n\n/**\n * The numbered list beside an index map (DqZakenKaart): one entry per row\n * that has a place on the map, numbered in list order so the number on the\n * pin and the number in the list are the same, and the rows without one.\n *\n * `spec` is `mapConfig.list`: `{ title, sub?, pills?: [{ path?, text?, map?,\n * colorMap? }], due? }`. `title`, `sub` and `due` are field templates
|
|
1
|
+
{"version":3,"file":"mapList.js","sources":["../../../src/utils/mapList.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\nimport { isBadgeVariant } from './badgeVariants.js'\nimport { pillLook } from './detailActionModel.js'\nimport { fillTemplate, firstFilled } from './fillTemplate.js'\nimport { readPath } from './readPath.js'\n\n/**\n * The numbered list beside an index map (DqZakenKaart): one entry per row\n * that has a place on the map, numbered in list order so the number on the\n * pin and the number in the list are the same, and the rows without one.\n *\n * `spec` is `mapConfig.list`: `{ title, sub?, pills?: [{ path?, text?, map?,\n * formatter?, colorMap?, variant?, resolve? }], due? }`. `title`, `sub` and `due` are\n * field templates (`\"{title}\"`, `\"{address} · {identifier}\"`,\n * `\"{deadline|date}\"`), already translated by the caller; `sub` may list\n * several, the first that fills wins. A pill reads `path` (or shows `text`),\n * maps it through `map` or the app formatter `formatter` (a reference's\n * display name), and takes its tone from `colorMap` by the raw value, or,\n * with `resolve`, the tone and icon of the record the value points at\n * (`options.looks`, read by CnIndexPage as for its other pills).\n *\n * @param {Array<object>} rows The rows, in display order.\n * @param {object} spec The list declaration.\n * @param {object} options How to read a row.\n * @param {string} options.rowKey The row's key field.\n * @param {Function} options.located Whether a row has a place on the map.\n * @param {Function} [options.translate] The host's translate, for pill labels.\n * @param {object} [options.formatters] The app's named formatters, for pills and template tokens.\n * @param {object} [options.looks] The referenced records' looks by field and id (`{ [path]: { [id]: { color, icon } } }`).\n * @return {{items: Array<{key: string, n: number, row: object, title: string, sub: string, due: string, pills: Array<{label: string, variant: string}>}>, missing: Array<object>}}\n * @spec openspec/changes/map-list-formatters/specs/map-list-formatters/spec.md#requirement-a-map-list-reads-display-names-through-the-apps-formatters\n * @spec openspec/changes/map-list-formatters/specs/map-list-formatters/spec.md#requirement-a-map-list-pill-takes-its-tone-and-icon-from-the-referenced-record\n */\nexport function mapListItems(rows, spec, { rowKey, located, translate = (text) => text, formatters = null, looks = null }) {\n\tconst context = formatters ? { formatters } : null\n\tconst items = []\n\tconst missing = []\n\tconst list = spec && typeof spec === 'object' ? spec : {}\n\tfor (const row of Array.isArray(rows) ? rows : []) {\n\t\tif (!located(row)) {\n\t\t\tmissing.push(row)\n\t\t\tcontinue\n\t\t}\n\t\tconst pills = (Array.isArray(list.pills) ? list.pills : [])\n\t\t\t.map((pill) => {\n\t\t\t\tif (!pill || typeof pill !== 'object') {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tconst raw = typeof pill.path === 'string' && pill.path !== '' ? readPath(row, pill.path) : undefined\n\t\t\t\tconst formatter = typeof pill.formatter === 'string' && formatters ? formatters[pill.formatter] : null\n\t\t\t\tconst formatted = typeof formatter === 'function' && (typeof raw === 'string' || typeof raw === 'number') && raw !== '' ? formatter(raw, row) : undefined\n\t\t\t\tconst mapped = pill.map && (typeof raw === 'string' || typeof raw === 'number') && Object.hasOwn(pill.map, raw) ? pill.map[raw] : formatted\n\t\t\t\tconst text = mapped ?? pill.text ?? raw\n\t\t\t\tif (typeof text !== 'string' && typeof text !== 'number') {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tconst label = translate(String(text))\n\t\t\t\tif (label === '') {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tconst tone = pill.colorMap && (typeof raw === 'string' || typeof raw === 'number') ? pill.colorMap[raw] : pill.variant\n\t\t\t\tconst fieldLooks = pill.resolve && looks && typeof pill.path === 'string' ? looks[pill.path] : null\n\t\t\t\tconst look = pillLook(fieldLooks && (typeof raw === 'string' || typeof raw === 'number') ? fieldLooks[String(raw)] : null)\n\t\t\t\tconst variant = look.variant || tone\n\t\t\t\treturn { label, variant: isBadgeVariant(variant) ? variant : 'default', ...(look.icon ? { icon: look.icon } : {}) }\n\t\t\t})\n\t\t\t.filter(Boolean)\n\t\titems.push({\n\t\t\tkey: String(row[rowKey]),\n\t\t\tn: items.length + 1,\n\t\t\trow,\n\t\t\ttitle: fillTemplate(list.title || '', row, undefined, context),\n\t\t\tsub: list.sub ? firstFilled(list.sub, row, undefined, undefined, context) : '',\n\t\t\tdue: fillTemplate(list.due || '', row, undefined, context),\n\t\t\tpills,\n\t\t})\n\t}\n\treturn { items, missing }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -65,6 +65,9 @@ function normalizeSavedViewMenu(entries) {
|
|
|
65
65
|
}
|
|
66
66
|
seen.add(`${page}|${id}`);
|
|
67
67
|
const entry = { id, page, name: e && typeof e.name === 'string' && e.name !== '' ? e.name : id };
|
|
68
|
+
if (typeof e.icon === 'string' && e.icon !== '') {
|
|
69
|
+
entry.icon = e.icon;
|
|
70
|
+
}
|
|
68
71
|
const badge = normalizeBadge(e.badge);
|
|
69
72
|
if (badge) {
|
|
70
73
|
entry.badge = badge;
|
|
@@ -88,12 +91,12 @@ function toggleSavedViewMenuEntry(entries, entry) {
|
|
|
88
91
|
if (present) {
|
|
89
92
|
return list.filter((e) => !(e.id === id && e.page === entry.page))
|
|
90
93
|
}
|
|
91
|
-
return normalizeSavedViewMenu([...list, { id, page: entry.page, name: entry.name, badge: entry.badge }])
|
|
94
|
+
return normalizeSavedViewMenu([...list, { id, page: entry.page, name: entry.name, icon: entry.icon, badge: entry.badge }])
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
/**
|
|
95
98
|
* Bring one page's entries in line with the views that page can show:
|
|
96
|
-
* current names and badges, and no entry for a view that was deleted or is
|
|
99
|
+
* current names, icons and badges, and no entry for a view that was deleted or is
|
|
97
100
|
* no longer shared with the user. Other pages' entries are left alone. A
|
|
98
101
|
* view's badge is read from its `menuBadge` (see `withSavedViewMenu`).
|
|
99
102
|
*
|
|
@@ -111,9 +114,10 @@ function syncSavedViewMenu(entries, page, views) {
|
|
|
111
114
|
return e
|
|
112
115
|
}
|
|
113
116
|
const view = byId.get(e.id);
|
|
114
|
-
const { badge, ...rest } = e;
|
|
117
|
+
const { badge, icon, ...rest } = e;
|
|
115
118
|
const next = normalizeBadge(view.menuBadge);
|
|
116
|
-
|
|
119
|
+
const nextIcon = typeof view.icon === 'string' && view.icon !== '' ? view.icon : icon;
|
|
120
|
+
return normalizeSavedViewMenu([{ ...rest, name: view.name || e.name, ...(nextIcon ? { icon: nextIcon } : {}), ...(next ? { badge: next } : {}) }])[0]
|
|
117
121
|
})
|
|
118
122
|
}
|
|
119
123
|
|
|
@@ -128,6 +132,9 @@ const SAVED_VIEW_MENU_EMPTY = 'saved-views-empty';
|
|
|
128
132
|
* same place (Ruben's design rule, 19 Oct); it replaces app-specific groups
|
|
129
133
|
* such as dossiq's "My case types".
|
|
130
134
|
*
|
|
135
|
+
* An entry shows its view's `icon` (a registered icon name; a case type's
|
|
136
|
+
* own icon, say), else the filter icon.
|
|
137
|
+
*
|
|
131
138
|
* An entry with a badge carries it as the menu entry's `count` (and
|
|
132
139
|
* `counterVariant`), which CnAppRoot fetches like any filtered menu count.
|
|
133
140
|
*
|
|
@@ -155,7 +162,7 @@ function withSavedViewMenu(menu, entries, options = {}) {
|
|
|
155
162
|
label: e.name,
|
|
156
163
|
route: e.page,
|
|
157
164
|
query: { savedView: e.id },
|
|
158
|
-
icon: 'FilterOutline',
|
|
165
|
+
icon: e.icon || 'FilterOutline',
|
|
159
166
|
...(e.badge || {}),
|
|
160
167
|
}))
|
|
161
168
|
: [{ id: SAVED_VIEW_MENU_EMPTY, type: 'hint', label: translate('nextcloud-vue', 'Add a view to the menu from Filters') }]),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"savedViewMenu.js","sources":["../../../src/utils/savedViewMenu.js"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 Conduction B.V.\n// SPDX-License-Identifier: EUPL-1.2\n\nimport { translate as t } from '@nextcloud/l10n'\n\n/**\n * Saved views a user added to the app menu.\n *\n * Per user, in the app's preferences (`cn_saved_view_menu`), as a list of\n * `{ id, page, name, badge? }`: the view id (or `default:<slug>`), the route\n * name of the index page it filters, the name to show, and the count the\n * entry shows when the view's `overrides.badge` asks for one. The view itself is not\n * touched, so a view shared with the user can be added like an own one, and\n * removing it from the menu never deletes it.\n *\n * A menu entry links to its page with `?savedView=<id>`; the page applies\n * that view. The entries form the \"My views\" group at the bottom of the menu.\n *\n * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-7\n */\n\n/** The preference key the list is stored under. */\nexport const SAVED_VIEW_MENU_KEY = 'cn_saved_view_menu'\n\n/** The id of the \"My views\" caption. */\nexport const SAVED_VIEW_MENU_CAPTION = 'saved-views-caption'\n\n/** The id prefix of a menu entry made from a saved view. */\nexport const SAVED_VIEW_MENU_PREFIX = 'saved-view-'\n\n/**\n * A menu badge as stored on an entry: `{ count, counterVariant? }`, where `count` is\n * a menu entry `count` (a number or `{ register, schema, filter? }`).\n *\n * @param {unknown} badge The stored value.\n * @return {{count: (number|object), counterVariant?: string}|null} The badge, or null.\n */\nfunction normalizeBadge(badge) {\n\tif (!badge || typeof badge !== 'object') {\n\t\treturn null\n\t}\n\tconst count = badge.count\n\tconst isSource = count && typeof count === 'object' && typeof count.register === 'string' && typeof count.schema === 'string'\n\tif (!isSource && !(typeof count === 'number' && count >= 0)) {\n\t\treturn null\n\t}\n\treturn badge.counterVariant === 'attention' ? { count, counterVariant: 'attention' } : { count }\n}\n\n/**\n * The stored list, cleaned: entries with an id and a page, no repeats.\n *\n * @param {unknown} entries The stored value.\n * @return {Array<{id: string, page: string, name: string, badge?: object}>} The list.\n */\nexport function normalizeSavedViewMenu(entries) {\n\tconst seen = new Set()\n\tconst out = []\n\tfor (const e of Array.isArray(entries) ? entries : []) {\n\t\tconst id = e && (typeof e.id === 'string' || typeof e.id === 'number') ? String(e.id) : ''\n\t\tconst page = e && typeof e.page === 'string' ? e.page : ''\n\t\tif (id === '' || page === '' || seen.has(`${page}|${id}`)) {\n\t\t\tcontinue\n\t\t}\n\t\tseen.add(`${page}|${id}`)\n\t\tconst entry = { id, page, name: e && typeof e.name === 'string' && e.name !== '' ? e.name : id }\n\t\tconst badge = normalizeBadge(e.badge)\n\t\tif (badge) {\n\t\t\tentry.badge = badge\n\t\t}\n\t\tout.push(entry)\n\t}\n\treturn out\n}\n\n/**\n * Add an entry, or remove it when it is already there.\n *\n * @param {Array<object>} entries The current list.\n * @param {{id: string, page: string, name: string, badge?: object}} entry The view.\n * @return {Array<{id: string, page: string, name: string, badge?: object}>} The new list.\n */\nexport function toggleSavedViewMenuEntry(entries, entry) {\n\tconst list = normalizeSavedViewMenu(entries)\n\tconst id = String(entry.id)\n\tconst present = list.some((e) => e.id === id && e.page === entry.page)\n\tif (present) {\n\t\treturn list.filter((e) => !(e.id === id && e.page === entry.page))\n\t}\n\treturn normalizeSavedViewMenu([...list, { id, page: entry.page, name: entry.name, badge: entry.badge }])\n}\n\n/**\n * Bring one page's entries in line with the views that page can show:\n * current names and badges, and no entry for a view that was deleted or is\n * no longer shared with the user. Other pages' entries are left alone. A\n * view's badge is read from its `menuBadge` (see `withSavedViewMenu`).\n *\n * @param {Array<object>} entries The current list.\n * @param {string} page The page's route name.\n * @param {Array<object>} views The views the page lists (defaults included).\n * @return {Array<{id: string, page: string, name: string}>} The new list.\n */\nexport function syncSavedViewMenu(entries, page, views) {\n\tconst byId = new Map((Array.isArray(views) ? views : []).map((v) => [String(v.id || v.slug), v]))\n\treturn normalizeSavedViewMenu(entries)\n\t\t.filter((e) => e.page !== page || byId.has(e.id))\n\t\t.map((e) => {\n\t\t\tif (e.page !== page) {\n\t\t\t\treturn e\n\t\t\t}\n\t\t\tconst view = byId.get(e.id)\n\t\t\tconst { badge, ...rest } = e\n\t\t\tconst next = normalizeBadge(view.menuBadge)\n\t\t\treturn normalizeSavedViewMenu([{ ...rest, name: view.name || e.name, ...(next ? { badge: next } : {}) }])[0]\n\t\t})\n}\n\n/** The id of the line the \"My views\" group shows while it is empty. */\nexport const SAVED_VIEW_MENU_EMPTY = 'saved-views-empty'\n\n/**\n * The manifest menu with the user's saved views in it, as the standard \"My\n * views\" group at the bottom of the menu: a caption and one entry per view,\n * after every main entry and so just above the footer entries (Help,\n * Advanced), which CnAppNav draws apart. Every app gets the same group in the\n * same place (Ruben's design rule, 19 Oct); it replaces app-specific groups\n * such as dossiq's \"My case types\".\n *\n * An entry with a badge carries it as the menu entry's `count` (and\n * `counterVariant`), which CnAppRoot fetches like any filtered menu count.\n *\n * With `showEmpty`, an app that offers saved views shows the group also while\n * the user pinned none, with one line saying how to add one, so the group can\n * be found.\n *\n * @param {Array<object>} menu The manifest's `menu`.\n * @param {Array<object>} entries The user's list.\n * @param {{showEmpty?: boolean}} [options] Options.\n * @return {Array<object>} A new menu, in manifest order; the given one when there is nothing to add.\n * @spec openspec/changes/my-views-menu-section/specs/app-nav/spec.md#requirement-every-app-menu-ends-with-my-views\n */\nexport function withSavedViewMenu(menu, entries, options = {}) {\n\tconst list = normalizeSavedViewMenu(entries)\n\tif (!Array.isArray(menu) || (list.length === 0 && options.showEmpty !== true)) {\n\t\treturn menu\n\t}\n\tconst sorted = menu.map((item, index) => ({ item, index })).sort((a, b) => byOrder(a.item, b.item) || a.index - b.index).map(({ item }) => item)\n\tconst group = [\n\t\t{ id: SAVED_VIEW_MENU_CAPTION, type: 'caption', label: t('nextcloud-vue', 'My views') },\n\t\t...(list.length > 0\n\t\t\t? list.map((e) => ({\n\t\t\t\t\tid: `${SAVED_VIEW_MENU_PREFIX}${e.id}`,\n\t\t\t\t\tlabel: e.name,\n\t\t\t\t\troute: e.page,\n\t\t\t\t\tquery: { savedView: e.id },\n\t\t\t\t\ticon: 'FilterOutline',\n\t\t\t\t\t...(e.badge || {}),\n\t\t\t\t}))\n\t\t\t: [{ id: SAVED_VIEW_MENU_EMPTY, type: 'hint', label: t('nextcloud-vue', 'Add a view to the menu from Filters') }]),\n\t]\n\t// Orders after the last ordered entry, so CnAppNav's own sort keeps the\n\t// group at the bottom.\n\tconst orders = sorted.map((item) => item && item.order).filter((order) => typeof order === 'number')\n\tif (orders.length > 0) {\n\t\tconst start = Math.max(...orders)\n\t\tgroup.forEach((item, k) => {\n\t\t\titem.order = start + k + 1\n\t\t})\n\t}\n\treturn [...sorted, ...group]\n}\n\n/**\n * Manifest menu order: entries with an `order` first, by it; the rest keep their place.\n *\n * @param {object} a First entry.\n * @param {object} b Second entry.\n * @return {number}\n */\nfunction byOrder(a, b) {\n\tconst aHas = typeof a?.order === 'number'\n\tconst bHas = typeof b?.order === 'number'\n\tif (aHas !== bHas) {\n\t\treturn aHas ? -1 : 1\n\t}\n\treturn aHas ? a.order - b.order : 0\n}\n"],"names":["t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"savedViewMenu.js","sources":["../../../src/utils/savedViewMenu.js"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 Conduction B.V.\n// SPDX-License-Identifier: EUPL-1.2\n\nimport { translate as t } from '@nextcloud/l10n'\n\n/**\n * Saved views a user added to the app menu.\n *\n * Per user, in the app's preferences (`cn_saved_view_menu`), as a list of\n * `{ id, page, name, badge? }`: the view id (or `default:<slug>`), the route\n * name of the index page it filters, the name to show, and the count the\n * entry shows when the view's `overrides.badge` asks for one. The view itself is not\n * touched, so a view shared with the user can be added like an own one, and\n * removing it from the menu never deletes it.\n *\n * A menu entry links to its page with `?savedView=<id>`; the page applies\n * that view. The entries form the \"My views\" group at the bottom of the menu.\n *\n * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-7\n */\n\n/** The preference key the list is stored under. */\nexport const SAVED_VIEW_MENU_KEY = 'cn_saved_view_menu'\n\n/** The id of the \"My views\" caption. */\nexport const SAVED_VIEW_MENU_CAPTION = 'saved-views-caption'\n\n/** The id prefix of a menu entry made from a saved view. */\nexport const SAVED_VIEW_MENU_PREFIX = 'saved-view-'\n\n/**\n * A menu badge as stored on an entry: `{ count, counterVariant? }`, where `count` is\n * a menu entry `count` (a number or `{ register, schema, filter? }`).\n *\n * @param {unknown} badge The stored value.\n * @return {{count: (number|object), counterVariant?: string}|null} The badge, or null.\n */\nfunction normalizeBadge(badge) {\n\tif (!badge || typeof badge !== 'object') {\n\t\treturn null\n\t}\n\tconst count = badge.count\n\tconst isSource = count && typeof count === 'object' && typeof count.register === 'string' && typeof count.schema === 'string'\n\tif (!isSource && !(typeof count === 'number' && count >= 0)) {\n\t\treturn null\n\t}\n\treturn badge.counterVariant === 'attention' ? { count, counterVariant: 'attention' } : { count }\n}\n\n/**\n * The stored list, cleaned: entries with an id and a page, no repeats.\n *\n * @param {unknown} entries The stored value.\n * @return {Array<{id: string, page: string, name: string, badge?: object}>} The list.\n */\nexport function normalizeSavedViewMenu(entries) {\n\tconst seen = new Set()\n\tconst out = []\n\tfor (const e of Array.isArray(entries) ? entries : []) {\n\t\tconst id = e && (typeof e.id === 'string' || typeof e.id === 'number') ? String(e.id) : ''\n\t\tconst page = e && typeof e.page === 'string' ? e.page : ''\n\t\tif (id === '' || page === '' || seen.has(`${page}|${id}`)) {\n\t\t\tcontinue\n\t\t}\n\t\tseen.add(`${page}|${id}`)\n\t\tconst entry = { id, page, name: e && typeof e.name === 'string' && e.name !== '' ? e.name : id }\n\t\tif (typeof e.icon === 'string' && e.icon !== '') {\n\t\t\tentry.icon = e.icon\n\t\t}\n\t\tconst badge = normalizeBadge(e.badge)\n\t\tif (badge) {\n\t\t\tentry.badge = badge\n\t\t}\n\t\tout.push(entry)\n\t}\n\treturn out\n}\n\n/**\n * Add an entry, or remove it when it is already there.\n *\n * @param {Array<object>} entries The current list.\n * @param {{id: string, page: string, name: string, badge?: object}} entry The view.\n * @return {Array<{id: string, page: string, name: string, badge?: object}>} The new list.\n */\nexport function toggleSavedViewMenuEntry(entries, entry) {\n\tconst list = normalizeSavedViewMenu(entries)\n\tconst id = String(entry.id)\n\tconst present = list.some((e) => e.id === id && e.page === entry.page)\n\tif (present) {\n\t\treturn list.filter((e) => !(e.id === id && e.page === entry.page))\n\t}\n\treturn normalizeSavedViewMenu([...list, { id, page: entry.page, name: entry.name, icon: entry.icon, badge: entry.badge }])\n}\n\n/**\n * Bring one page's entries in line with the views that page can show:\n * current names, icons and badges, and no entry for a view that was deleted or is\n * no longer shared with the user. Other pages' entries are left alone. A\n * view's badge is read from its `menuBadge` (see `withSavedViewMenu`).\n *\n * @param {Array<object>} entries The current list.\n * @param {string} page The page's route name.\n * @param {Array<object>} views The views the page lists (defaults included).\n * @return {Array<{id: string, page: string, name: string}>} The new list.\n */\nexport function syncSavedViewMenu(entries, page, views) {\n\tconst byId = new Map((Array.isArray(views) ? views : []).map((v) => [String(v.id || v.slug), v]))\n\treturn normalizeSavedViewMenu(entries)\n\t\t.filter((e) => e.page !== page || byId.has(e.id))\n\t\t.map((e) => {\n\t\t\tif (e.page !== page) {\n\t\t\t\treturn e\n\t\t\t}\n\t\t\tconst view = byId.get(e.id)\n\t\t\tconst { badge, icon, ...rest } = e\n\t\t\tconst next = normalizeBadge(view.menuBadge)\n\t\t\tconst nextIcon = typeof view.icon === 'string' && view.icon !== '' ? view.icon : icon\n\t\t\treturn normalizeSavedViewMenu([{ ...rest, name: view.name || e.name, ...(nextIcon ? { icon: nextIcon } : {}), ...(next ? { badge: next } : {}) }])[0]\n\t\t})\n}\n\n/** The id of the line the \"My views\" group shows while it is empty. */\nexport const SAVED_VIEW_MENU_EMPTY = 'saved-views-empty'\n\n/**\n * The manifest menu with the user's saved views in it, as the standard \"My\n * views\" group at the bottom of the menu: a caption and one entry per view,\n * after every main entry and so just above the footer entries (Help,\n * Advanced), which CnAppNav draws apart. Every app gets the same group in the\n * same place (Ruben's design rule, 19 Oct); it replaces app-specific groups\n * such as dossiq's \"My case types\".\n *\n * An entry shows its view's `icon` (a registered icon name; a case type's\n * own icon, say), else the filter icon.\n *\n * An entry with a badge carries it as the menu entry's `count` (and\n * `counterVariant`), which CnAppRoot fetches like any filtered menu count.\n *\n * With `showEmpty`, an app that offers saved views shows the group also while\n * the user pinned none, with one line saying how to add one, so the group can\n * be found.\n *\n * @param {Array<object>} menu The manifest's `menu`.\n * @param {Array<object>} entries The user's list.\n * @param {{showEmpty?: boolean}} [options] Options.\n * @return {Array<object>} A new menu, in manifest order; the given one when there is nothing to add.\n * @spec openspec/changes/my-views-menu-section/specs/app-nav/spec.md#requirement-every-app-menu-ends-with-my-views\n */\nexport function withSavedViewMenu(menu, entries, options = {}) {\n\tconst list = normalizeSavedViewMenu(entries)\n\tif (!Array.isArray(menu) || (list.length === 0 && options.showEmpty !== true)) {\n\t\treturn menu\n\t}\n\tconst sorted = menu.map((item, index) => ({ item, index })).sort((a, b) => byOrder(a.item, b.item) || a.index - b.index).map(({ item }) => item)\n\tconst group = [\n\t\t{ id: SAVED_VIEW_MENU_CAPTION, type: 'caption', label: t('nextcloud-vue', 'My views') },\n\t\t...(list.length > 0\n\t\t\t? list.map((e) => ({\n\t\t\t\t\tid: `${SAVED_VIEW_MENU_PREFIX}${e.id}`,\n\t\t\t\t\tlabel: e.name,\n\t\t\t\t\troute: e.page,\n\t\t\t\t\tquery: { savedView: e.id },\n\t\t\t\t\ticon: e.icon || 'FilterOutline',\n\t\t\t\t\t...(e.badge || {}),\n\t\t\t\t}))\n\t\t\t: [{ id: SAVED_VIEW_MENU_EMPTY, type: 'hint', label: t('nextcloud-vue', 'Add a view to the menu from Filters') }]),\n\t]\n\t// Orders after the last ordered entry, so CnAppNav's own sort keeps the\n\t// group at the bottom.\n\tconst orders = sorted.map((item) => item && item.order).filter((order) => typeof order === 'number')\n\tif (orders.length > 0) {\n\t\tconst start = Math.max(...orders)\n\t\tgroup.forEach((item, k) => {\n\t\t\titem.order = start + k + 1\n\t\t})\n\t}\n\treturn [...sorted, ...group]\n}\n\n/**\n * Manifest menu order: entries with an `order` first, by it; the rest keep their place.\n *\n * @param {object} a First entry.\n * @param {object} b Second entry.\n * @return {number}\n */\nfunction byOrder(a, b) {\n\tconst aHas = typeof a?.order === 'number'\n\tconst bHas = typeof b?.order === 'number'\n\tif (aHas !== bHas) {\n\t\treturn aHas ? -1 : 1\n\t}\n\treturn aHas ? a.order - b.order : 0\n}\n"],"names":["t"],"mappings":"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"}
|