@conduction/nextcloud-vue 2.56.0-beta.64 → 2.56.0-beta.66
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/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/CnAppRoot/CnAppRoot.vue2.js +2 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.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/CnDetailPage/CnDetailPage.vue2.js +52 -8
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +98 -63
- 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 +203 -14
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +19 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.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/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 +630 -303
- 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/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/savedViewHelpers.js +40 -3
- package/dist/esm/utils/savedViewHelpers.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 +2760 -1995
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +630 -303
- package/l10n/en.json +3 -1
- package/l10n/nl.json +3 -1
- package/package.json +1 -1
- package/src/components/CnActionDialog/CnActionDialog.vue +32 -9
- package/src/components/CnAppRoot/CnAppRoot.vue +2 -1
- 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/CnDetailPage/CnDetailPage.vue +77 -20
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +100 -5
- package/src/components/CnFormDialog/CnFormDialog.vue +1 -1
- package/src/components/CnIndexPage/CnIndexPage.md +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +211 -16
- package/src/components/CnIndexSidebar/CnIndexSidebar.md +1 -1
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +2 -2
- 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/CnSearchSelect/CnSearchSelect.vue +8 -0
- package/src/composables/useRefLabels.js +25 -5
- package/src/css/board.css +5 -2
- package/src/css/dialog.css +53 -3
- package/src/css/index-page.css +26 -1
- package/src/css/look-board-detail.css +55 -22
- package/src/css/look-board-index.css +82 -12
- package/src/css/look-board-reports.css +1 -1
- package/src/css/look-board.css +84 -11
- package/src/integrations/registry.js +4 -0
- package/src/schemas/app-manifest-v2.schema.json +11 -5
- package/src/store/plugins/liveUpdates.js +3 -1
- package/src/store/plugins/logs.js +2 -1
- 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/savedViewHelpers.js +39 -2
- package/src/utils/savedViewMenu.js +12 -5
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -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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"}
|
|
@@ -258,15 +258,18 @@ function schemaSlug(schema) {
|
|
|
258
258
|
* it: three case indexes share their views, the organisations index does
|
|
259
259
|
* not. `savedViewsScope` on a page overrides that, for pages that should
|
|
260
260
|
* share views although they read different sources, or keep them apart
|
|
261
|
-
* although they read the same one.
|
|
261
|
+
* although they read the same one. A page over a named source
|
|
262
|
+
* (`entitySource`) and no schema shares its views with the pages over that
|
|
263
|
+
* source.
|
|
262
264
|
*
|
|
263
265
|
* @param {object} page The page.
|
|
264
266
|
* @param {string} [page.scope] The explicit `savedViewsScope`, when set.
|
|
265
267
|
* @param {string} [page.register] The page's register.
|
|
266
268
|
* @param {object|string} [page.schema] The page's schema, slug or object.
|
|
269
|
+
* @param {string} [page.source] The page's `entitySource`, when it reads one.
|
|
267
270
|
* @return {string} The scope key, or the empty string for a page with no source.
|
|
268
271
|
*/
|
|
269
|
-
function savedViewScope({ scope, register, schema } = {}) {
|
|
272
|
+
function savedViewScope({ scope, register, schema, source } = {}) {
|
|
270
273
|
if (typeof scope === 'string' && scope !== '') {
|
|
271
274
|
return scope
|
|
272
275
|
}
|
|
@@ -274,9 +277,43 @@ function savedViewScope({ scope, register, schema } = {}) {
|
|
|
274
277
|
if (typeof register === 'string' && register !== '' && slug !== '') {
|
|
275
278
|
return `${register}/${slug}`
|
|
276
279
|
}
|
|
280
|
+
if (typeof source === 'string' && source !== '') {
|
|
281
|
+
return `source:${source}`
|
|
282
|
+
}
|
|
277
283
|
return ''
|
|
278
284
|
}
|
|
279
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Whether an index page offers saved views in its sidebar.
|
|
288
|
+
*
|
|
289
|
+
* `allowSavedViews` decides when it is set. Unset, every index page that has
|
|
290
|
+
* a sidebar and a source a view can be scoped to (a register and schema, a
|
|
291
|
+
* named source, or `savedViewsScope`) offers them, so managing views is
|
|
292
|
+
* there on every list without each page opting in.
|
|
293
|
+
*
|
|
294
|
+
* @param {object} config The page's index config, or CnIndexPage's props.
|
|
295
|
+
* @param {boolean|null} [config.allowSavedViews] The explicit choice, when made.
|
|
296
|
+
* @param {object} [config.sidebar] The sidebar config.
|
|
297
|
+
* @param {string} [config.savedViewsScope] The explicit scope.
|
|
298
|
+
* @param {string} [config.register] The register.
|
|
299
|
+
* @param {object|string} [config.schema] The schema.
|
|
300
|
+
* @param {string} [config.entitySource] The named source.
|
|
301
|
+
* @return {boolean}
|
|
302
|
+
*/
|
|
303
|
+
function pageOffersSavedViews(config) {
|
|
304
|
+
if (!config || typeof config !== 'object') {
|
|
305
|
+
return false
|
|
306
|
+
}
|
|
307
|
+
if (typeof config.allowSavedViews === 'boolean') {
|
|
308
|
+
return config.allowSavedViews
|
|
309
|
+
}
|
|
310
|
+
const sidebar = config.sidebar;
|
|
311
|
+
if (!sidebar || typeof sidebar !== 'object' || sidebar.enabled !== true || sidebar.show === false) {
|
|
312
|
+
return false
|
|
313
|
+
}
|
|
314
|
+
return savedViewScope({ scope: config.savedViewsScope, register: config.register, schema: config.schema, source: config.entitySource }) !== ''
|
|
315
|
+
}
|
|
316
|
+
|
|
280
317
|
/**
|
|
281
318
|
* Whether a saved view belongs on a page.
|
|
282
319
|
*
|
|
@@ -410,5 +447,5 @@ function defaultFilterViews(defaults, page = {}) {
|
|
|
410
447
|
})
|
|
411
448
|
}
|
|
412
449
|
|
|
413
|
-
export { buildRouteQueryFromViewState, buildViewCreatePayload, defaultFilterViews, extractViewState, extractViewStateFromRouteQuery, isOwnView, normalizeSharedWith, savedViewScope, schemaSlug, viewAccess, viewMatchesScope };
|
|
450
|
+
export { buildRouteQueryFromViewState, buildViewCreatePayload, defaultFilterViews, extractViewState, extractViewStateFromRouteQuery, isOwnView, normalizeSharedWith, pageOffersSavedViews, savedViewScope, schemaSlug, viewAccess, viewMatchesScope };
|
|
414
451
|
//# sourceMappingURL=savedViewHelpers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"savedViewHelpers.js","sources":["../../../src/utils/savedViewHelpers.js"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 Conduction B.V.\n// SPDX-License-Identifier: EUPL-1.2\n\n/**\n * Pure helpers for CnIndexPage's saved-views feature (saved-views-ui).\n *\n * OpenRegister ships a saved-search Views backend\n * (`/apps/openregister/api/views`, ViewsController + ViewService); these\n * helpers translate between the three state shapes involved:\n *\n * 1. **Route query** — CnIndexPage's deep-link contract: every `$route.query`\n * key NOT starting with `_` is a fetch filter (see\n * `useSelfFetchList.resolveQueryFilters`); `_`-prefixed keys are reserved\n * list params. Search is `_search` and sort is `_order`, the JSON-encoded\n * ordered array `[{ key, order }, …]` — the one spelling CnIndexPage,\n * listNavigation, CnPageRenderer and OpenRegister's object API all agree\n * on. This module writes NO spelling of its own.\n * 2. **View state** — the normalized internal object\n * `{ filters, search, sortKeys, sortKey, sortOrder }`, where `sortKeys` is\n * the truth and the last two mirror its first entry, exactly as\n * `useListView` holds the same state.\n * 3. **OR View payload** — the `POST/PUT /api/views` body whose opaque\n * `query` field round-trips through OpenRegister unchanged.\n *\n * All functions are pure and defensive: malformed input degrades to the\n * empty/default state, never throws.\n *\n * @module utils/savedViewHelpers\n */\n\nimport { parseSortKeys } from './routeFilters.js'\n\n/** The default (empty) view state. @return {object} A fresh empty state. */\nfunction emptyState() {\n\treturn { filters: {}, search: '', sortKeys: [], sortKey: null, sortOrder: 'asc' }\n}\n\n/**\n * Coerce anything into an ordered sort-key list.\n *\n * The single defensive entry point, and the whole of the stored-shape\n * compatibility: a view saved before sorts could be chained holds `sort` as one\n * `{ key, order }` object, and that must keep opening exactly as it does today.\n *\n * @param {unknown} value An array, a single `{ key, order }`, or anything else.\n * @return {Array<{key: string, order: 'asc'|'desc'}>} The normalized list.\n */\nfunction normalizeSortKeys(value) {\n\tconst list = Array.isArray(value) ? value : [value]\n\tconst seen = new Set()\n\tconst keys = []\n\tfor (const entry of list) {\n\t\tif (!entry || typeof entry !== 'object' || typeof entry.key !== 'string' || entry.key === '') {\n\t\t\tcontinue\n\t\t}\n\t\tif (seen.has(entry.key)) {\n\t\t\tcontinue\n\t\t}\n\t\tseen.add(entry.key)\n\t\tkeys.push({ key: entry.key, order: entry.order === 'desc' ? 'desc' : 'asc' })\n\t}\n\treturn keys\n}\n\n/**\n * Write a sort onto a state, keeping the single-key mirror in step.\n *\n * @param {object} state The state to write onto (mutated and returned).\n * @param {Array<{key: string, order: 'asc'|'desc'}>} keys The normalized sort.\n * @return {object} The same state.\n */\nfunction withSort(state, keys) {\n\tstate.sortKeys = keys\n\tstate.sortKey = keys.length > 0 ? keys[0].key : null\n\tstate.sortOrder = keys.length > 0 ? keys[0].order : 'asc'\n\treturn state\n}\n\n/**\n * The sort a caller handed in, whichever of the two shapes they hold.\n *\n * @param {object} src A view state, possibly carrying only `sortKey`/`sortOrder`.\n * @return {Array<{key: string, order: 'asc'|'desc'}>} The normalized sort.\n */\nfunction sortKeysOf(src) {\n\tif (src.sortKeys !== undefined && src.sortKeys !== null) {\n\t\treturn normalizeSortKeys(src.sortKeys)\n\t}\n\treturn src.sortKey ? normalizeSortKeys({ key: src.sortKey, order: src.sortOrder }) : []\n}\n\n/**\n * Extract the current view state from a `$route.query`-shaped object.\n *\n * Non-underscore-prefixed keys become `filters` entries (values passed\n * through as-is, including arrays from `?status[]=a&status[]=b` deep\n * links). Reserved keys map onto the dedicated state fields: `_search` and\n * `_order`. Other reserved keys (`_page`, `_limit`, …) are ignored —\n * pagination is never part of a saved view.\n *\n * @param {object|null|undefined} query The `$route.query` object.\n * @return {{ filters: object, search: string, sortKeys: Array<{key: string, order: string}>, sortKey: ?string, sortOrder: string }} The normalized view state.\n */\nexport function extractViewStateFromRouteQuery(query) {\n\tconst state = emptyState()\n\tif (!query || typeof query !== 'object' || Array.isArray(query)) {\n\t\treturn state\n\t}\n\tfor (const [key, value] of Object.entries(query)) {\n\t\tif (key.startsWith('_')) {\n\t\t\tcontinue\n\t\t}\n\t\tif (value === undefined || value === null || value === '') {\n\t\t\tcontinue\n\t\t}\n\t\tstate.filters[key] = value\n\t}\n\tif (typeof query._search === 'string' && query._search !== '') {\n\t\tstate.search = query._search\n\t}\n\treturn withSort(state, parseSortKeys(query._order))\n}\n\n/**\n * Build a fresh route-query object that APPLIES a view state.\n *\n * Spreads `state.filters` (skipping null/undefined/empty-string/empty-array\n * values, matching what `CnIndexPage.persistViewStateToRoute` skips — the two\n * write the same query and must agree on what an absent filter looks like),\n * then sets the reserved keys only when meaningful: `_search` for a non-empty\n * search term, `_order` for an active sort. Deliberately omits `_page` —\n * applying a view is a full state replace that implicitly resets pagination.\n *\n * @param {{ filters?: object, search?: string, sortKeys?: Array<{key: string, order: string}>, sortKey?: ?string, sortOrder?: string }} state The view state to serialize.\n * @return {object} A plain object suitable for `$router.replace({ query })`.\n */\nexport function buildRouteQueryFromViewState(state) {\n\tconst query = {}\n\tconst src = (state && typeof state === 'object') ? state : {}\n\tconst filters = (src.filters && typeof src.filters === 'object' && !Array.isArray(src.filters)) ? src.filters : {}\n\tfor (const [key, value] of Object.entries(filters)) {\n\t\tif (value === undefined || value === null || value === '' || (Array.isArray(value) && value.length === 0)) {\n\t\t\tcontinue\n\t\t}\n\t\tquery[key] = value\n\t}\n\tif (typeof src.search === 'string' && src.search !== '') {\n\t\tquery._search = src.search\n\t}\n\tconst keys = sortKeysOf(src)\n\tif (keys.length > 0) {\n\t\tquery._order = JSON.stringify(keys)\n\t}\n\treturn query\n}\n\n/**\n * Build the exact `POST /apps/openregister/api/views` (and PUT) request\n * body for persisting a view. The `query` field is OpenRegister's opaque\n * JSON blob — ViewsController stores `$data['query']` verbatim when it is\n * an array/object, so the shape below round-trips unchanged and\n * {@link extractViewState} is its inverse.\n *\n * @param {object} options Payload options.\n * @param {string} options.name Required view name.\n * @param {string} [options.description] Optional description.\n * @param {boolean} [options.isPublic] Share the view with other users.\n * @param {boolean} [options.isDefault] Mark as the user's default view.\n * @param {{ filters?: object, search?: string, sortKeys?: Array<{key: string, order: string}>, sortKey?: ?string, sortOrder?: string }} options.state The view state to persist.\n * @param {string} [options.scope] The pages this view belongs to, from {@link savedViewScope}; omitted when empty.\n * @param {string} [options.register] The page's register, written as OpenRegister's own `registers` list.\n * @param {object|string} [options.schema] The page's schema (slug or object), written as OpenRegister's own `schemas` list.\n * @param {Array<{group: string, mode: string}>} [options.sharedWith] Groups to share with; omitted from the body when empty.\n * @param {object} [options.presentation] How the view shows (OpenRegister's shape); omitted from the body for a table, which is the default.\n * @return {object} The request body for the OR views API.\n */\nexport function buildViewCreatePayload({ name, description, isPublic, isDefault, state, scope, register, schema, sharedWith, presentation } = {}) {\n\tconst src = (state && typeof state === 'object') ? state : {}\n\tconst keys = sortKeysOf(src)\n\tconst query = {\n\t\tfilters: (src.filters && typeof src.filters === 'object' && !Array.isArray(src.filters)) ? src.filters : {},\n\t\tsearch: (typeof src.search === 'string') ? src.search : '',\n\t\t// `null` rather than `[]` for no sort: every view stored before sorts\n\t\t// could be chained says `null`, and one spelling for \"no sort\" is worth\n\t\t// more than the symmetry.\n\t\tsort: keys.length > 0 ? keys : null,\n\t}\n\tif (typeof scope === 'string' && scope !== '') {\n\t\tquery.scope = scope\n\t}\n\tif (typeof register === 'string' && register !== '') {\n\t\tquery.registers = [register]\n\t}\n\tconst slug = schemaSlug(schema)\n\tif (slug !== '') {\n\t\tquery.schemas = [slug]\n\t}\n\tconst body = {\n\t\tname,\n\t\tdescription: description || '',\n\t\tisPublic: !!isPublic,\n\t\tisDefault: !!isDefault,\n\t\tquery,\n\t}\n\tif (presentation && typeof presentation === 'object' && (presentation.viewType === 'kanban' || presentation.viewType === 'calendar')) {\n\t\tbody.presentation = presentation\n\t}\n\tconst audience = normalizeSharedWith(sharedWith)\n\tif (audience.length > 0) {\n\t\tbody.sharedWith = audience\n\t}\n\treturn body\n}\n\n/**\n * Clean an audience list: keep `{ group, mode }` and `{ user, mode }` entries\n * that name exactly one target, drop repeats, and coerce any mode but `write`\n * to `read`.\n *\n * @param {unknown} sharedWith The audience as given.\n * @return {Array<{group?: string, user?: string, mode: 'read'|'write'}>} The cleaned list.\n * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-5\n */\nexport function normalizeSharedWith(sharedWith) {\n\tconst seen = new Set()\n\tconst out = []\n\tfor (const entry of Array.isArray(sharedWith) ? sharedWith : []) {\n\t\tconst group = entry && typeof entry.group === 'string' ? entry.group : ''\n\t\tconst user = entry && typeof entry.user === 'string' ? entry.user : ''\n\t\tif ((group === '') === (user === '')) {\n\t\t\tcontinue\n\t\t}\n\t\tconst key = user !== '' ? `user:${user}` : `group:${group}`\n\t\tif (seen.has(key)) {\n\t\t\tcontinue\n\t\t}\n\t\tseen.add(key)\n\t\tconst mode = entry.mode === 'write' ? 'write' : 'read'\n\t\tout.push(user !== '' ? { user, mode } : { group, mode })\n\t}\n\treturn out\n}\n\n/**\n * What the current user may do with a view: `owner`, `write` or `read`.\n *\n * The server decides: `@self.access` wins. A view without it (an older\n * OpenRegister) counts as `owner` when its `owner` is the current user and as\n * `read` otherwise.\n *\n * @param {object|null|undefined} view The View API object.\n * @param {string|null|undefined} currentUserId The signed-in NC user id.\n * @return {'owner'|'write'|'read'} The access.\n */\nexport function viewAccess(view, currentUserId) {\n\tconst access = view && view['@self'] && view['@self'].access\n\tif (access === 'owner' || access === 'write' || access === 'read') {\n\t\treturn access\n\t}\n\treturn isOwnView(view, currentUserId) ? 'owner' : 'read'\n}\n\n/**\n * The slug of a schema given as a slug or as a schema object.\n *\n * @param {object|string|null|undefined} schema The page's `schema` prop.\n * @return {string} The slug, or the empty string.\n */\nexport function schemaSlug(schema) {\n\tif (typeof schema === 'string') {\n\t\treturn schema\n\t}\n\tif (schema && typeof schema === 'object' && typeof schema.slug === 'string') {\n\t\treturn schema.slug\n\t}\n\treturn ''\n}\n\n/**\n * The key naming which pages share a view.\n *\n * A view is a set of filters over one schema's fields, so by default every\n * page over the same register and schema shares it and no other page sees\n * it: three case indexes share their views, the organisations index does\n * not. `savedViewsScope` on a page overrides that, for pages that should\n * share views although they read different sources, or keep them apart\n * although they read the same one.\n *\n * @param {object} page The page.\n * @param {string} [page.scope] The explicit `savedViewsScope`, when set.\n * @param {string} [page.register] The page's register.\n * @param {object|string} [page.schema] The page's schema, slug or object.\n * @return {string} The scope key, or the empty string for a page with no source.\n */\nexport function savedViewScope({ scope, register, schema } = {}) {\n\tif (typeof scope === 'string' && scope !== '') {\n\t\treturn scope\n\t}\n\tconst slug = schemaSlug(schema)\n\tif (typeof register === 'string' && register !== '' && slug !== '') {\n\t\treturn `${register}/${slug}`\n\t}\n\treturn ''\n}\n\n/**\n * Whether a saved view belongs on a page.\n *\n * A view saved with a `scope` is shown where the scope matches. A view that\n * names `schemas` (OpenRegister's own search UI writes those) is shown on a\n * page over one of them. A view that says neither, saved before views were\n * scoped, is shown everywhere as it always was: hiding it would lose the\n * view, and nothing can tell which page it was made on.\n *\n * @param {object|null|undefined} view The View API object.\n * @param {object} page The page, as {@link savedViewScope} takes it.\n * @return {boolean} True when the view is shown on this page.\n */\nexport function viewMatchesScope(view, page = {}) {\n\tconst query = view && typeof view === 'object' ? view.query : null\n\tif (!query || typeof query !== 'object' || Array.isArray(query)) {\n\t\treturn true\n\t}\n\tif (typeof query.scope === 'string' && query.scope !== '') {\n\t\treturn query.scope === savedViewScope(page)\n\t}\n\tif (Array.isArray(query.schemas) && query.schemas.length > 0) {\n\t\tconst slug = schemaSlug(page.schema)\n\t\tif (slug === '' || !query.schemas.map(String).includes(slug)) {\n\t\t\treturn false\n\t\t}\n\t\tif (Array.isArray(query.registers) && query.registers.length > 0 && page.register) {\n\t\t\treturn query.registers.map(String).includes(String(page.register))\n\t\t}\n\t\treturn true\n\t}\n\treturn true\n}\n\n/**\n * Normalize a View API object (as returned by `GET /api/views`) back into\n * the internal view state. Accepts either a full view object (reads its\n * `query` property) or a raw query blob directly. Every field degrades\n * defensively: missing/null/malformed `query`, non-object `filters`,\n * malformed `sort` all yield the empty/default state — never throws.\n *\n * @param {object|null|undefined} view The View API object (or its raw `query` blob).\n * @return {{ filters: object, search: string, sortKeys: Array<{key: string, order: string}>, sortKey: ?string, sortOrder: string }} The normalized view state.\n */\nexport function extractViewState(view) {\n\tconst state = emptyState()\n\tif (!view || typeof view !== 'object' || Array.isArray(view)) {\n\t\treturn state\n\t}\n\t// Full view object (has a `query` key) or a raw query blob itself.\n\tconst query = (Object.hasOwn(view, 'query')) ? view.query : view\n\tif (!query || typeof query !== 'object' || Array.isArray(query)) {\n\t\treturn state\n\t}\n\tif (query.filters && typeof query.filters === 'object' && !Array.isArray(query.filters)) {\n\t\tfor (const [key, value] of Object.entries(query.filters)) {\n\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tstate.filters[key] = value\n\t\t}\n\t}\n\tif (typeof query.search === 'string') {\n\t\tstate.search = query.search\n\t}\n\treturn withSort(state, normalizeSortKeys(query.sort))\n}\n\n/**\n * Whether the given view is owned by the given user. Drives the delete\n * affordance in CnSavedViewsControl — OpenRegister scopes destroy() to the\n * owner server-side (a foreign id 404s), so this only hides the button for\n * views the API would refuse to delete anyway.\n *\n * @param {object|null|undefined} view The View API object.\n * @param {string|null|undefined} currentUserId The signed-in NC user id.\n * @return {boolean} True when the view belongs to the current user.\n */\nexport function isOwnView(view, currentUserId) {\n\treturn !!view && !!currentUserId && view.owner === currentUserId\n}\n\n/**\n * The default views an index page declares (`config.defaultSavedViews`), as\n * read-only views the saved-views control lists. They are never stored: the\n * manifest installs them, so every user has the same set and none can delete\n * one. `seeded: true` puts them in the control's default group; the query\n * keeps its `@`-tokens, which resolve when a default is applied.\n *\n * A default's `overrides` (page config laid over the page while it is\n * active, see `utils/savedViewOverrides.js`) ride in `query.overrides`, so a\n * copy of the default keeps them.\n *\n * @param {Array<object>|null|undefined} defaults The declared entries `{ slug, name, filters, search?, sort?, label?, overrides? }`.\n * @param {{ scope?: string, register?: string, schema?: object|string|null }} [page] The page, as `savedViewScope` takes it.\n * @return {Array<object>} The views, in declaration order; entries without a slug or name are left out.\n * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-4\n */\nexport function defaultFilterViews(defaults, page = {}) {\n\tif (!Array.isArray(defaults)) {\n\t\treturn []\n\t}\n\treturn defaults\n\t\t.filter((entry) => entry && typeof entry.slug === 'string' && entry.slug !== '' && typeof entry.name === 'string' && entry.name !== '')\n\t\t.map((entry) => {\n\t\t\tconst { query } = buildViewCreatePayload({\n\t\t\t\tname: entry.name,\n\t\t\t\tstate: {\n\t\t\t\t\tfilters: (entry.filters && typeof entry.filters === 'object' && !Array.isArray(entry.filters)) ? entry.filters : {},\n\t\t\t\t\tsearch: typeof entry.search === 'string' ? entry.search : '',\n\t\t\t\t\tsortKeys: Array.isArray(entry.sort) ? entry.sort : [],\n\t\t\t\t},\n\t\t\t\tscope: savedViewScope(page),\n\t\t\t\tregister: page.register,\n\t\t\t\tschema: page.schema,\n\t\t\t})\n\t\t\tif (entry.overrides && typeof entry.overrides === 'object' && !Array.isArray(entry.overrides)) {\n\t\t\t\tquery.overrides = entry.overrides\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tid: `default:${entry.slug}`,\n\t\t\t\tslug: entry.slug,\n\t\t\t\tname: entry.name,\n\t\t\t\towner: '',\n\t\t\t\tseeded: true,\n\t\t\t\tisPublic: false,\n\t\t\t\tlabels: typeof entry.label === 'string' && entry.label !== '' ? [entry.label] : [],\n\t\t\t\t'@self': { access: 'read' },\n\t\t\t\tquery,\n\t\t\t}\n\t\t})\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"savedViewHelpers.js","sources":["../../../src/utils/savedViewHelpers.js"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 Conduction B.V.\n// SPDX-License-Identifier: EUPL-1.2\n\n/**\n * Pure helpers for CnIndexPage's saved-views feature (saved-views-ui).\n *\n * OpenRegister ships a saved-search Views backend\n * (`/apps/openregister/api/views`, ViewsController + ViewService); these\n * helpers translate between the three state shapes involved:\n *\n * 1. **Route query** — CnIndexPage's deep-link contract: every `$route.query`\n * key NOT starting with `_` is a fetch filter (see\n * `useSelfFetchList.resolveQueryFilters`); `_`-prefixed keys are reserved\n * list params. Search is `_search` and sort is `_order`, the JSON-encoded\n * ordered array `[{ key, order }, …]` — the one spelling CnIndexPage,\n * listNavigation, CnPageRenderer and OpenRegister's object API all agree\n * on. This module writes NO spelling of its own.\n * 2. **View state** — the normalized internal object\n * `{ filters, search, sortKeys, sortKey, sortOrder }`, where `sortKeys` is\n * the truth and the last two mirror its first entry, exactly as\n * `useListView` holds the same state.\n * 3. **OR View payload** — the `POST/PUT /api/views` body whose opaque\n * `query` field round-trips through OpenRegister unchanged.\n *\n * All functions are pure and defensive: malformed input degrades to the\n * empty/default state, never throws.\n *\n * @module utils/savedViewHelpers\n */\n\nimport { parseSortKeys } from './routeFilters.js'\n\n/** The default (empty) view state. @return {object} A fresh empty state. */\nfunction emptyState() {\n\treturn { filters: {}, search: '', sortKeys: [], sortKey: null, sortOrder: 'asc' }\n}\n\n/**\n * Coerce anything into an ordered sort-key list.\n *\n * The single defensive entry point, and the whole of the stored-shape\n * compatibility: a view saved before sorts could be chained holds `sort` as one\n * `{ key, order }` object, and that must keep opening exactly as it does today.\n *\n * @param {unknown} value An array, a single `{ key, order }`, or anything else.\n * @return {Array<{key: string, order: 'asc'|'desc'}>} The normalized list.\n */\nfunction normalizeSortKeys(value) {\n\tconst list = Array.isArray(value) ? value : [value]\n\tconst seen = new Set()\n\tconst keys = []\n\tfor (const entry of list) {\n\t\tif (!entry || typeof entry !== 'object' || typeof entry.key !== 'string' || entry.key === '') {\n\t\t\tcontinue\n\t\t}\n\t\tif (seen.has(entry.key)) {\n\t\t\tcontinue\n\t\t}\n\t\tseen.add(entry.key)\n\t\tkeys.push({ key: entry.key, order: entry.order === 'desc' ? 'desc' : 'asc' })\n\t}\n\treturn keys\n}\n\n/**\n * Write a sort onto a state, keeping the single-key mirror in step.\n *\n * @param {object} state The state to write onto (mutated and returned).\n * @param {Array<{key: string, order: 'asc'|'desc'}>} keys The normalized sort.\n * @return {object} The same state.\n */\nfunction withSort(state, keys) {\n\tstate.sortKeys = keys\n\tstate.sortKey = keys.length > 0 ? keys[0].key : null\n\tstate.sortOrder = keys.length > 0 ? keys[0].order : 'asc'\n\treturn state\n}\n\n/**\n * The sort a caller handed in, whichever of the two shapes they hold.\n *\n * @param {object} src A view state, possibly carrying only `sortKey`/`sortOrder`.\n * @return {Array<{key: string, order: 'asc'|'desc'}>} The normalized sort.\n */\nfunction sortKeysOf(src) {\n\tif (src.sortKeys !== undefined && src.sortKeys !== null) {\n\t\treturn normalizeSortKeys(src.sortKeys)\n\t}\n\treturn src.sortKey ? normalizeSortKeys({ key: src.sortKey, order: src.sortOrder }) : []\n}\n\n/**\n * Extract the current view state from a `$route.query`-shaped object.\n *\n * Non-underscore-prefixed keys become `filters` entries (values passed\n * through as-is, including arrays from `?status[]=a&status[]=b` deep\n * links). Reserved keys map onto the dedicated state fields: `_search` and\n * `_order`. Other reserved keys (`_page`, `_limit`, …) are ignored —\n * pagination is never part of a saved view.\n *\n * @param {object|null|undefined} query The `$route.query` object.\n * @return {{ filters: object, search: string, sortKeys: Array<{key: string, order: string}>, sortKey: ?string, sortOrder: string }} The normalized view state.\n */\nexport function extractViewStateFromRouteQuery(query) {\n\tconst state = emptyState()\n\tif (!query || typeof query !== 'object' || Array.isArray(query)) {\n\t\treturn state\n\t}\n\tfor (const [key, value] of Object.entries(query)) {\n\t\tif (key.startsWith('_')) {\n\t\t\tcontinue\n\t\t}\n\t\tif (value === undefined || value === null || value === '') {\n\t\t\tcontinue\n\t\t}\n\t\tstate.filters[key] = value\n\t}\n\tif (typeof query._search === 'string' && query._search !== '') {\n\t\tstate.search = query._search\n\t}\n\treturn withSort(state, parseSortKeys(query._order))\n}\n\n/**\n * Build a fresh route-query object that APPLIES a view state.\n *\n * Spreads `state.filters` (skipping null/undefined/empty-string/empty-array\n * values, matching what `CnIndexPage.persistViewStateToRoute` skips — the two\n * write the same query and must agree on what an absent filter looks like),\n * then sets the reserved keys only when meaningful: `_search` for a non-empty\n * search term, `_order` for an active sort. Deliberately omits `_page` —\n * applying a view is a full state replace that implicitly resets pagination.\n *\n * @param {{ filters?: object, search?: string, sortKeys?: Array<{key: string, order: string}>, sortKey?: ?string, sortOrder?: string }} state The view state to serialize.\n * @return {object} A plain object suitable for `$router.replace({ query })`.\n */\nexport function buildRouteQueryFromViewState(state) {\n\tconst query = {}\n\tconst src = (state && typeof state === 'object') ? state : {}\n\tconst filters = (src.filters && typeof src.filters === 'object' && !Array.isArray(src.filters)) ? src.filters : {}\n\tfor (const [key, value] of Object.entries(filters)) {\n\t\tif (value === undefined || value === null || value === '' || (Array.isArray(value) && value.length === 0)) {\n\t\t\tcontinue\n\t\t}\n\t\tquery[key] = value\n\t}\n\tif (typeof src.search === 'string' && src.search !== '') {\n\t\tquery._search = src.search\n\t}\n\tconst keys = sortKeysOf(src)\n\tif (keys.length > 0) {\n\t\tquery._order = JSON.stringify(keys)\n\t}\n\treturn query\n}\n\n/**\n * Build the exact `POST /apps/openregister/api/views` (and PUT) request\n * body for persisting a view. The `query` field is OpenRegister's opaque\n * JSON blob — ViewsController stores `$data['query']` verbatim when it is\n * an array/object, so the shape below round-trips unchanged and\n * {@link extractViewState} is its inverse.\n *\n * @param {object} options Payload options.\n * @param {string} options.name Required view name.\n * @param {string} [options.description] Optional description.\n * @param {boolean} [options.isPublic] Share the view with other users.\n * @param {boolean} [options.isDefault] Mark as the user's default view.\n * @param {{ filters?: object, search?: string, sortKeys?: Array<{key: string, order: string}>, sortKey?: ?string, sortOrder?: string }} options.state The view state to persist.\n * @param {string} [options.scope] The pages this view belongs to, from {@link savedViewScope}; omitted when empty.\n * @param {string} [options.register] The page's register, written as OpenRegister's own `registers` list.\n * @param {object|string} [options.schema] The page's schema (slug or object), written as OpenRegister's own `schemas` list.\n * @param {Array<{group: string, mode: string}>} [options.sharedWith] Groups to share with; omitted from the body when empty.\n * @param {object} [options.presentation] How the view shows (OpenRegister's shape); omitted from the body for a table, which is the default.\n * @return {object} The request body for the OR views API.\n */\nexport function buildViewCreatePayload({ name, description, isPublic, isDefault, state, scope, register, schema, sharedWith, presentation } = {}) {\n\tconst src = (state && typeof state === 'object') ? state : {}\n\tconst keys = sortKeysOf(src)\n\tconst query = {\n\t\tfilters: (src.filters && typeof src.filters === 'object' && !Array.isArray(src.filters)) ? src.filters : {},\n\t\tsearch: (typeof src.search === 'string') ? src.search : '',\n\t\t// `null` rather than `[]` for no sort: every view stored before sorts\n\t\t// could be chained says `null`, and one spelling for \"no sort\" is worth\n\t\t// more than the symmetry.\n\t\tsort: keys.length > 0 ? keys : null,\n\t}\n\tif (typeof scope === 'string' && scope !== '') {\n\t\tquery.scope = scope\n\t}\n\tif (typeof register === 'string' && register !== '') {\n\t\tquery.registers = [register]\n\t}\n\tconst slug = schemaSlug(schema)\n\tif (slug !== '') {\n\t\tquery.schemas = [slug]\n\t}\n\tconst body = {\n\t\tname,\n\t\tdescription: description || '',\n\t\tisPublic: !!isPublic,\n\t\tisDefault: !!isDefault,\n\t\tquery,\n\t}\n\tif (presentation && typeof presentation === 'object' && (presentation.viewType === 'kanban' || presentation.viewType === 'calendar')) {\n\t\tbody.presentation = presentation\n\t}\n\tconst audience = normalizeSharedWith(sharedWith)\n\tif (audience.length > 0) {\n\t\tbody.sharedWith = audience\n\t}\n\treturn body\n}\n\n/**\n * Clean an audience list: keep `{ group, mode }` and `{ user, mode }` entries\n * that name exactly one target, drop repeats, and coerce any mode but `write`\n * to `read`.\n *\n * @param {unknown} sharedWith The audience as given.\n * @return {Array<{group?: string, user?: string, mode: 'read'|'write'}>} The cleaned list.\n * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-5\n */\nexport function normalizeSharedWith(sharedWith) {\n\tconst seen = new Set()\n\tconst out = []\n\tfor (const entry of Array.isArray(sharedWith) ? sharedWith : []) {\n\t\tconst group = entry && typeof entry.group === 'string' ? entry.group : ''\n\t\tconst user = entry && typeof entry.user === 'string' ? entry.user : ''\n\t\tif ((group === '') === (user === '')) {\n\t\t\tcontinue\n\t\t}\n\t\tconst key = user !== '' ? `user:${user}` : `group:${group}`\n\t\tif (seen.has(key)) {\n\t\t\tcontinue\n\t\t}\n\t\tseen.add(key)\n\t\tconst mode = entry.mode === 'write' ? 'write' : 'read'\n\t\tout.push(user !== '' ? { user, mode } : { group, mode })\n\t}\n\treturn out\n}\n\n/**\n * What the current user may do with a view: `owner`, `write` or `read`.\n *\n * The server decides: `@self.access` wins. A view without it (an older\n * OpenRegister) counts as `owner` when its `owner` is the current user and as\n * `read` otherwise.\n *\n * @param {object|null|undefined} view The View API object.\n * @param {string|null|undefined} currentUserId The signed-in NC user id.\n * @return {'owner'|'write'|'read'} The access.\n */\nexport function viewAccess(view, currentUserId) {\n\tconst access = view && view['@self'] && view['@self'].access\n\tif (access === 'owner' || access === 'write' || access === 'read') {\n\t\treturn access\n\t}\n\treturn isOwnView(view, currentUserId) ? 'owner' : 'read'\n}\n\n/**\n * The slug of a schema given as a slug or as a schema object.\n *\n * @param {object|string|null|undefined} schema The page's `schema` prop.\n * @return {string} The slug, or the empty string.\n */\nexport function schemaSlug(schema) {\n\tif (typeof schema === 'string') {\n\t\treturn schema\n\t}\n\tif (schema && typeof schema === 'object' && typeof schema.slug === 'string') {\n\t\treturn schema.slug\n\t}\n\treturn ''\n}\n\n/**\n * The key naming which pages share a view.\n *\n * A view is a set of filters over one schema's fields, so by default every\n * page over the same register and schema shares it and no other page sees\n * it: three case indexes share their views, the organisations index does\n * not. `savedViewsScope` on a page overrides that, for pages that should\n * share views although they read different sources, or keep them apart\n * although they read the same one. A page over a named source\n * (`entitySource`) and no schema shares its views with the pages over that\n * source.\n *\n * @param {object} page The page.\n * @param {string} [page.scope] The explicit `savedViewsScope`, when set.\n * @param {string} [page.register] The page's register.\n * @param {object|string} [page.schema] The page's schema, slug or object.\n * @param {string} [page.source] The page's `entitySource`, when it reads one.\n * @return {string} The scope key, or the empty string for a page with no source.\n */\nexport function savedViewScope({ scope, register, schema, source } = {}) {\n\tif (typeof scope === 'string' && scope !== '') {\n\t\treturn scope\n\t}\n\tconst slug = schemaSlug(schema)\n\tif (typeof register === 'string' && register !== '' && slug !== '') {\n\t\treturn `${register}/${slug}`\n\t}\n\tif (typeof source === 'string' && source !== '') {\n\t\treturn `source:${source}`\n\t}\n\treturn ''\n}\n\n/**\n * Whether an index page offers saved views in its sidebar.\n *\n * `allowSavedViews` decides when it is set. Unset, every index page that has\n * a sidebar and a source a view can be scoped to (a register and schema, a\n * named source, or `savedViewsScope`) offers them, so managing views is\n * there on every list without each page opting in.\n *\n * @param {object} config The page's index config, or CnIndexPage's props.\n * @param {boolean|null} [config.allowSavedViews] The explicit choice, when made.\n * @param {object} [config.sidebar] The sidebar config.\n * @param {string} [config.savedViewsScope] The explicit scope.\n * @param {string} [config.register] The register.\n * @param {object|string} [config.schema] The schema.\n * @param {string} [config.entitySource] The named source.\n * @return {boolean}\n */\nexport function pageOffersSavedViews(config) {\n\tif (!config || typeof config !== 'object') {\n\t\treturn false\n\t}\n\tif (typeof config.allowSavedViews === 'boolean') {\n\t\treturn config.allowSavedViews\n\t}\n\tconst sidebar = config.sidebar\n\tif (!sidebar || typeof sidebar !== 'object' || sidebar.enabled !== true || sidebar.show === false) {\n\t\treturn false\n\t}\n\treturn savedViewScope({ scope: config.savedViewsScope, register: config.register, schema: config.schema, source: config.entitySource }) !== ''\n}\n\n/**\n * Whether a saved view belongs on a page.\n *\n * A view saved with a `scope` is shown where the scope matches. A view that\n * names `schemas` (OpenRegister's own search UI writes those) is shown on a\n * page over one of them. A view that says neither, saved before views were\n * scoped, is shown everywhere as it always was: hiding it would lose the\n * view, and nothing can tell which page it was made on.\n *\n * @param {object|null|undefined} view The View API object.\n * @param {object} page The page, as {@link savedViewScope} takes it.\n * @return {boolean} True when the view is shown on this page.\n */\nexport function viewMatchesScope(view, page = {}) {\n\tconst query = view && typeof view === 'object' ? view.query : null\n\tif (!query || typeof query !== 'object' || Array.isArray(query)) {\n\t\treturn true\n\t}\n\tif (typeof query.scope === 'string' && query.scope !== '') {\n\t\treturn query.scope === savedViewScope(page)\n\t}\n\tif (Array.isArray(query.schemas) && query.schemas.length > 0) {\n\t\tconst slug = schemaSlug(page.schema)\n\t\tif (slug === '' || !query.schemas.map(String).includes(slug)) {\n\t\t\treturn false\n\t\t}\n\t\tif (Array.isArray(query.registers) && query.registers.length > 0 && page.register) {\n\t\t\treturn query.registers.map(String).includes(String(page.register))\n\t\t}\n\t\treturn true\n\t}\n\treturn true\n}\n\n/**\n * Normalize a View API object (as returned by `GET /api/views`) back into\n * the internal view state. Accepts either a full view object (reads its\n * `query` property) or a raw query blob directly. Every field degrades\n * defensively: missing/null/malformed `query`, non-object `filters`,\n * malformed `sort` all yield the empty/default state — never throws.\n *\n * @param {object|null|undefined} view The View API object (or its raw `query` blob).\n * @return {{ filters: object, search: string, sortKeys: Array<{key: string, order: string}>, sortKey: ?string, sortOrder: string }} The normalized view state.\n */\nexport function extractViewState(view) {\n\tconst state = emptyState()\n\tif (!view || typeof view !== 'object' || Array.isArray(view)) {\n\t\treturn state\n\t}\n\t// Full view object (has a `query` key) or a raw query blob itself.\n\tconst query = (Object.hasOwn(view, 'query')) ? view.query : view\n\tif (!query || typeof query !== 'object' || Array.isArray(query)) {\n\t\treturn state\n\t}\n\tif (query.filters && typeof query.filters === 'object' && !Array.isArray(query.filters)) {\n\t\tfor (const [key, value] of Object.entries(query.filters)) {\n\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tstate.filters[key] = value\n\t\t}\n\t}\n\tif (typeof query.search === 'string') {\n\t\tstate.search = query.search\n\t}\n\treturn withSort(state, normalizeSortKeys(query.sort))\n}\n\n/**\n * Whether the given view is owned by the given user. Drives the delete\n * affordance in CnSavedViewsControl — OpenRegister scopes destroy() to the\n * owner server-side (a foreign id 404s), so this only hides the button for\n * views the API would refuse to delete anyway.\n *\n * @param {object|null|undefined} view The View API object.\n * @param {string|null|undefined} currentUserId The signed-in NC user id.\n * @return {boolean} True when the view belongs to the current user.\n */\nexport function isOwnView(view, currentUserId) {\n\treturn !!view && !!currentUserId && view.owner === currentUserId\n}\n\n/**\n * The default views an index page declares (`config.defaultSavedViews`), as\n * read-only views the saved-views control lists. They are never stored: the\n * manifest installs them, so every user has the same set and none can delete\n * one. `seeded: true` puts them in the control's default group; the query\n * keeps its `@`-tokens, which resolve when a default is applied.\n *\n * A default's `overrides` (page config laid over the page while it is\n * active, see `utils/savedViewOverrides.js`) ride in `query.overrides`, so a\n * copy of the default keeps them.\n *\n * @param {Array<object>|null|undefined} defaults The declared entries `{ slug, name, filters, search?, sort?, label?, overrides? }`.\n * @param {{ scope?: string, register?: string, schema?: object|string|null }} [page] The page, as `savedViewScope` takes it.\n * @return {Array<object>} The views, in declaration order; entries without a slug or name are left out.\n * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-4\n */\nexport function defaultFilterViews(defaults, page = {}) {\n\tif (!Array.isArray(defaults)) {\n\t\treturn []\n\t}\n\treturn defaults\n\t\t.filter((entry) => entry && typeof entry.slug === 'string' && entry.slug !== '' && typeof entry.name === 'string' && entry.name !== '')\n\t\t.map((entry) => {\n\t\t\tconst { query } = buildViewCreatePayload({\n\t\t\t\tname: entry.name,\n\t\t\t\tstate: {\n\t\t\t\t\tfilters: (entry.filters && typeof entry.filters === 'object' && !Array.isArray(entry.filters)) ? entry.filters : {},\n\t\t\t\t\tsearch: typeof entry.search === 'string' ? entry.search : '',\n\t\t\t\t\tsortKeys: Array.isArray(entry.sort) ? entry.sort : [],\n\t\t\t\t},\n\t\t\t\tscope: savedViewScope(page),\n\t\t\t\tregister: page.register,\n\t\t\t\tschema: page.schema,\n\t\t\t})\n\t\t\tif (entry.overrides && typeof entry.overrides === 'object' && !Array.isArray(entry.overrides)) {\n\t\t\t\tquery.overrides = entry.overrides\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tid: `default:${entry.slug}`,\n\t\t\t\tslug: entry.slug,\n\t\t\t\tname: entry.name,\n\t\t\t\towner: '',\n\t\t\t\tseeded: true,\n\t\t\t\tisPublic: false,\n\t\t\t\tlabels: typeof entry.label === 'string' && entry.label !== '' ? [entry.label] : [],\n\t\t\t\t'@self': { access: 'read' },\n\t\t\t\tquery,\n\t\t\t}\n\t\t})\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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"}
|