@conduction/nextcloud-vue 2.56.0-beta.30 → 2.56.0-beta.31
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/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +3 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +80 -4
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +95 -91
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +85 -6
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +4 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +2 -2
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +40 -3
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +19 -11
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +43 -0
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js +7 -7
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +125 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +3 -2
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +302 -4
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +417 -244
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +101 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +141 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +60 -0
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +74 -49
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +29 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +48 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +7 -7
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +63 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +8 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +14 -0
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js +5 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +13 -6
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +84 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +85 -41
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +7 -2
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
- package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +197 -6
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +94 -50
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useListView.js +4 -4
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/index.js +10 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +8 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +54 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +198 -8
- package/dist/esm/utils/facets.js +75 -3
- package/dist/esm/utils/facets.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +7 -1
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/pageControls.js +81 -0
- package/dist/esm/utils/pageControls.js.map +1 -0
- package/dist/esm/utils/personalLenses.js +40 -3
- package/dist/esm/utils/personalLenses.js.map +1 -1
- package/dist/esm/utils/resolveFilterTokens.js +1 -1
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/resolveManifestTokens.js +2 -0
- package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +19 -1
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/esm/utils/userGroupsPeek.js +91 -0
- package/dist/esm/utils/userGroupsPeek.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/visibleWhen.js +57 -5
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/esm/utils/widgetVisibility.js +4 -57
- package/dist/esm/utils/widgetVisibility.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +4309 -2233
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +198 -8
- package/l10n/en.json +8 -1
- package/l10n/nl.json +54 -1
- package/package.json +2 -1
- package/scripts/check-docs-mdx.mjs +108 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +6 -1
- package/src/components/CnAppRoot/CnAppRoot.md +4 -2
- package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
- package/src/components/CnBannerWidget/CnBannerWidget.vue +112 -7
- package/src/components/CnCellRenderer/CnCellRenderer.vue +4 -1
- package/src/components/CnConversationThread/CnConversationWidget.vue +1 -1
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +2 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +40 -3
- package/src/components/CnDataTable/CnDataTable.vue +19 -11
- package/src/components/CnDetailPage/CnDetailPage.vue +44 -1
- package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
- package/src/components/CnFactsWidget/CnFactsWidget.vue +125 -0
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +3 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +588 -117
- package/src/components/CnFilesBrowser/filesBrowser.js +100 -0
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +140 -0
- package/src/components/CnFormPage/CnFormPage.vue +81 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +56 -3
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +7 -7
- package/src/components/CnNextStepCard/CnNextStepWidget.vue +1 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +1 -1
- package/src/components/CnObjectSidebar/CnFilesTab.vue +70 -1
- package/src/components/CnObjectSidebar/CnNotesTab.vue +16 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +13 -6
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +180 -3
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
- package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
- package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +281 -10
- package/src/components/index.js +8 -0
- package/src/composables/useListView.js +4 -4
- package/src/css/look-board-index.css +16 -8
- package/src/index.js +8 -0
- package/src/schemas/app-manifest-v2.schema.json +93 -9
- package/src/utils/facets.js +74 -2
- package/src/utils/fillTemplate.js +7 -1
- package/src/utils/pageControls.js +78 -0
- package/src/utils/personalLenses.js +39 -2
- package/src/utils/resolveFilterTokens.js +3 -1
- package/src/utils/resolveManifestTokens.js +2 -0
- package/src/utils/sentinelTokens.js +19 -1
- package/src/utils/userGroupsPeek.js +87 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +56 -4
- package/src/utils/widgetVisibility.js +7 -56
package/dist/esm/utils/facets.js
CHANGED
|
@@ -37,14 +37,29 @@ function normalizeFacetBucket(bucket) {
|
|
|
37
37
|
return option
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* The option id that stands for "this property holds no value".
|
|
42
|
+
*
|
|
43
|
+
* Chosen so no real bucket key can collide with it. Selecting it is sent to
|
|
44
|
+
* OpenRegister as `<property>_isnull=true` by {@link facetFilterParams}.
|
|
45
|
+
*
|
|
46
|
+
* @type {string}
|
|
47
|
+
*/
|
|
48
|
+
const MISSING_VALUE = '__cn_missing__';
|
|
49
|
+
|
|
40
50
|
/**
|
|
41
51
|
* Normalise the `facets` block of a collection response into sidebar format.
|
|
42
52
|
*
|
|
43
53
|
* Fields whose facet carries no bucket list are skipped entirely, so a caller
|
|
44
54
|
* can tell an unfaceted field from one that faceted to nothing.
|
|
45
55
|
*
|
|
56
|
+
* What OpenRegister puts beside the buckets is carried through rather than
|
|
57
|
+
* dropped: `missing` (how many objects in scope hold no value, sent as
|
|
58
|
+
* `{ results }` or `{ count }`), `type` and `title`. A missing count is only
|
|
59
|
+
* set when the backend sent one, for the same reason a bucket count is.
|
|
60
|
+
*
|
|
46
61
|
* @param {object} facets The `facets` block of an API response
|
|
47
|
-
* @return {object} `{ fieldName: { values: [{ value, count?, label? }] } }`
|
|
62
|
+
* @return {object} `{ fieldName: { values: [{ value, count?, label? }], missing?, type?, title? } }`
|
|
48
63
|
*/
|
|
49
64
|
function normalizeFacets(facets) {
|
|
50
65
|
const transformed = {};
|
|
@@ -54,12 +69,69 @@ function normalizeFacets(facets) {
|
|
|
54
69
|
if (!buckets) {
|
|
55
70
|
continue
|
|
56
71
|
}
|
|
57
|
-
|
|
72
|
+
const normalized = { values: buckets.map(normalizeFacetBucket) };
|
|
73
|
+
|
|
74
|
+
const missing = facet.missing?.results ?? facet.missing?.count;
|
|
75
|
+
if (typeof missing === 'number') {
|
|
76
|
+
normalized.missing = missing;
|
|
77
|
+
}
|
|
78
|
+
for (const name of ['type', 'title']) {
|
|
79
|
+
if (facet[name] !== undefined && facet[name] !== null) {
|
|
80
|
+
normalized[name] = facet[name];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
transformed[key] = normalized;
|
|
58
85
|
}
|
|
59
86
|
|
|
60
87
|
return transformed
|
|
61
88
|
}
|
|
62
89
|
|
|
90
|
+
/**
|
|
91
|
+
* The sidebar options for one normalised facet, with the missing-value option
|
|
92
|
+
* appended when some objects hold no value.
|
|
93
|
+
*
|
|
94
|
+
* @param {object|undefined} facet A normalised facet from {@link normalizeFacets}
|
|
95
|
+
* @param {function(number): string} missingLabel Renders the missing option's label from its count
|
|
96
|
+
* @return {Array<{id: unknown, label: string}>|null} The options, or null when the
|
|
97
|
+
* facet offers nothing, so the caller can fall back to its static options
|
|
98
|
+
*/
|
|
99
|
+
function facetOptions(facet, missingLabel) {
|
|
100
|
+
const options = (facet?.values || []).map((v) => ({
|
|
101
|
+
id: v.value,
|
|
102
|
+
label: facetOptionLabel(v),
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
if (typeof facet?.missing === 'number' && facet.missing > 0) {
|
|
106
|
+
options.push({ id: MISSING_VALUE, label: missingLabel(facet.missing) });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return options.length > 0 ? options : null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The query parameters one facet selection sends.
|
|
114
|
+
*
|
|
115
|
+
* An ordinary selection is the property itself, one value unwrapped and
|
|
116
|
+
* several as a list. The missing-value option is exclusive: no object both
|
|
117
|
+
* holds one of the chosen values and holds none, so combining them would
|
|
118
|
+
* always answer an empty list. It is sent as `<property>_isnull=true`.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} key The filtered property
|
|
121
|
+
* @param {Array} values The selected option ids
|
|
122
|
+
* @return {object} The parameters to merge into the request
|
|
123
|
+
*/
|
|
124
|
+
function facetFilterParams(key, values) {
|
|
125
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
126
|
+
return {}
|
|
127
|
+
}
|
|
128
|
+
if (values.includes(MISSING_VALUE)) {
|
|
129
|
+
return { [`${key}_isnull`]: 'true' }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { [key]: values.length === 1 ? values[0] : values }
|
|
133
|
+
}
|
|
134
|
+
|
|
63
135
|
/**
|
|
64
136
|
* Build the visible label for one faceted filter option.
|
|
65
137
|
*
|
|
@@ -82,5 +154,5 @@ function facetOptionLabel(bucket) {
|
|
|
82
154
|
return `${text} (${bucket.count})`
|
|
83
155
|
}
|
|
84
156
|
|
|
85
|
-
export { facetOptionLabel, normalizeFacetBucket, normalizeFacets };
|
|
157
|
+
export { MISSING_VALUE, facetFilterParams, facetOptionLabel, facetOptions, normalizeFacetBucket, normalizeFacets };
|
|
86
158
|
//# sourceMappingURL=facets.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facets.js","sources":["../../../src/utils/facets.js"],"sourcesContent":["/**\n * Facet helpers shared by the object store, the search plugin and the two\n * filter sidebars.\n *\n * The shape on the wire is not ours. OpenRegister emits a terms bucket as\n * `{ key, results, label }` from its object search, and `{ value, count,\n * label }` from its facetable-field discovery. Both are real, so both are\n * read here rather than one being declared correct.\n *\n * The rule these helpers exist to hold is narrower than the field names: a\n * number that was never sent must never reach the screen as a number. An\n * absent count normalises to an absent count and renders as nothing, because\n * `(0)` is a claim about the data that no response supports.\n */\n\n/**\n * Normalise one facet bucket into the sidebar's option shape.\n *\n * `count` and `label` are omitted when the bucket carries neither name for\n * them, so a caller can distinguish \"no count\" from \"a count of zero\".\n *\n * @param {object} bucket A raw facet bucket from the API\n * @return {{value: unknown, count?: unknown, label?: unknown}} The normalised option\n */\nexport function normalizeFacetBucket(bucket) {\n\tconst option = { value: bucket.key ?? bucket.value }\n\n\tconst count = bucket.results ?? bucket.count\n\tif (count !== undefined && count !== null) {\n\t\toption.count = count\n\t}\n\n\tif (bucket.label !== undefined && bucket.label !== null) {\n\t\toption.label = bucket.label\n\t}\n\n\treturn option\n}\n\n/**\n * Normalise the `facets` block of a collection response into sidebar format.\n *\n * Fields whose facet carries no bucket list are skipped entirely, so a caller\n * can tell an unfaceted field from one that faceted to nothing.\n *\n * @param {object} facets The `facets` block of an API response\n * @return {object} `{ fieldName: { values: [{ value, count?, label? }] } }`\n */\nexport function normalizeFacets(facets) {\n\tconst transformed = {}\n\n\tfor (const [key, facet] of Object.entries(facets || {})) {\n\t\tconst buckets = facet?.buckets || facet?.data?.buckets\n\t\tif (!buckets) {\n\t\t\tcontinue\n\t\t}\n\t\
|
|
1
|
+
{"version":3,"file":"facets.js","sources":["../../../src/utils/facets.js"],"sourcesContent":["/**\n * Facet helpers shared by the object store, the search plugin and the two\n * filter sidebars.\n *\n * The shape on the wire is not ours. OpenRegister emits a terms bucket as\n * `{ key, results, label }` from its object search, and `{ value, count,\n * label }` from its facetable-field discovery. Both are real, so both are\n * read here rather than one being declared correct.\n *\n * The rule these helpers exist to hold is narrower than the field names: a\n * number that was never sent must never reach the screen as a number. An\n * absent count normalises to an absent count and renders as nothing, because\n * `(0)` is a claim about the data that no response supports.\n */\n\n/**\n * Normalise one facet bucket into the sidebar's option shape.\n *\n * `count` and `label` are omitted when the bucket carries neither name for\n * them, so a caller can distinguish \"no count\" from \"a count of zero\".\n *\n * @param {object} bucket A raw facet bucket from the API\n * @return {{value: unknown, count?: unknown, label?: unknown}} The normalised option\n */\nexport function normalizeFacetBucket(bucket) {\n\tconst option = { value: bucket.key ?? bucket.value }\n\n\tconst count = bucket.results ?? bucket.count\n\tif (count !== undefined && count !== null) {\n\t\toption.count = count\n\t}\n\n\tif (bucket.label !== undefined && bucket.label !== null) {\n\t\toption.label = bucket.label\n\t}\n\n\treturn option\n}\n\n/**\n * The option id that stands for \"this property holds no value\".\n *\n * Chosen so no real bucket key can collide with it. Selecting it is sent to\n * OpenRegister as `<property>_isnull=true` by {@link facetFilterParams}.\n *\n * @type {string}\n */\nexport const MISSING_VALUE = '__cn_missing__'\n\n/**\n * Normalise the `facets` block of a collection response into sidebar format.\n *\n * Fields whose facet carries no bucket list are skipped entirely, so a caller\n * can tell an unfaceted field from one that faceted to nothing.\n *\n * What OpenRegister puts beside the buckets is carried through rather than\n * dropped: `missing` (how many objects in scope hold no value, sent as\n * `{ results }` or `{ count }`), `type` and `title`. A missing count is only\n * set when the backend sent one, for the same reason a bucket count is.\n *\n * @param {object} facets The `facets` block of an API response\n * @return {object} `{ fieldName: { values: [{ value, count?, label? }], missing?, type?, title? } }`\n */\nexport function normalizeFacets(facets) {\n\tconst transformed = {}\n\n\tfor (const [key, facet] of Object.entries(facets || {})) {\n\t\tconst buckets = facet?.buckets || facet?.data?.buckets\n\t\tif (!buckets) {\n\t\t\tcontinue\n\t\t}\n\t\tconst normalized = { values: buckets.map(normalizeFacetBucket) }\n\n\t\tconst missing = facet.missing?.results ?? facet.missing?.count\n\t\tif (typeof missing === 'number') {\n\t\t\tnormalized.missing = missing\n\t\t}\n\t\tfor (const name of ['type', 'title']) {\n\t\t\tif (facet[name] !== undefined && facet[name] !== null) {\n\t\t\t\tnormalized[name] = facet[name]\n\t\t\t}\n\t\t}\n\n\t\ttransformed[key] = normalized\n\t}\n\n\treturn transformed\n}\n\n/**\n * The sidebar options for one normalised facet, with the missing-value option\n * appended when some objects hold no value.\n *\n * @param {object|undefined} facet A normalised facet from {@link normalizeFacets}\n * @param {function(number): string} missingLabel Renders the missing option's label from its count\n * @return {Array<{id: unknown, label: string}>|null} The options, or null when the\n * facet offers nothing, so the caller can fall back to its static options\n */\nexport function facetOptions(facet, missingLabel) {\n\tconst options = (facet?.values || []).map((v) => ({\n\t\tid: v.value,\n\t\tlabel: facetOptionLabel(v),\n\t}))\n\n\tif (typeof facet?.missing === 'number' && facet.missing > 0) {\n\t\toptions.push({ id: MISSING_VALUE, label: missingLabel(facet.missing) })\n\t}\n\n\treturn options.length > 0 ? options : null\n}\n\n/**\n * The query parameters one facet selection sends.\n *\n * An ordinary selection is the property itself, one value unwrapped and\n * several as a list. The missing-value option is exclusive: no object both\n * holds one of the chosen values and holds none, so combining them would\n * always answer an empty list. It is sent as `<property>_isnull=true`.\n *\n * @param {string} key The filtered property\n * @param {Array} values The selected option ids\n * @return {object} The parameters to merge into the request\n */\nexport function facetFilterParams(key, values) {\n\tif (!Array.isArray(values) || values.length === 0) {\n\t\treturn {}\n\t}\n\tif (values.includes(MISSING_VALUE)) {\n\t\treturn { [`${key}_isnull`]: 'true' }\n\t}\n\n\treturn { [key]: values.length === 1 ? values[0] : values }\n}\n\n/**\n * Build the visible label for one faceted filter option.\n *\n * A bucket that carries a label renders the label: a facet over a `$ref` keys\n * its buckets by identifier, so without this the filter shows the user a\n * column of uuids. A bucket with no count renders no count at all.\n *\n * @param {object} bucket A normalised facet value: `{ value, count?, label? }`\n * @return {string} The label to render for this option\n */\nexport function facetOptionLabel(bucket) {\n\tconst text = (bucket.label !== undefined && bucket.label !== null)\n\t\t? String(bucket.label)\n\t\t: String(bucket.value)\n\n\tif (bucket.count === undefined || bucket.count === null) {\n\t\treturn text\n\t}\n\n\treturn `${text} (${bucket.count})`\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -50,6 +50,11 @@ function formatValue(value, format, now) {
|
|
|
50
50
|
if (format === 'datetime') {
|
|
51
51
|
return formatBoardDateTime(value, now)
|
|
52
52
|
}
|
|
53
|
+
if (format === 'days') {
|
|
54
|
+
// Whole days from the date to now (0 for today; negative for a future date).
|
|
55
|
+
const date = parseDateValue(value);
|
|
56
|
+
return date === null ? '' : String(Math.floor((now.getTime() - date.getTime()) / 86400000))
|
|
57
|
+
}
|
|
53
58
|
return String(value)
|
|
54
59
|
}
|
|
55
60
|
|
|
@@ -58,7 +63,8 @@ function formatValue(value, format, now) {
|
|
|
58
63
|
* `"Termijn {deadline|date}"` from an object.
|
|
59
64
|
*
|
|
60
65
|
* Every `{path}` token is a dot path into the object; `{path|date}` and
|
|
61
|
-
* `{path|datetime}` format it as the boards write dates
|
|
66
|
+
* `{path|datetime}` format it as the boards write dates, and `{path|days}` is the
|
|
67
|
+
* whole days since that date ("has waited {createdAt|days} days"). A token without a
|
|
62
68
|
* value empties the WHOLE template, because "Termijn " is worse than nothing.
|
|
63
69
|
* A template without tokens is returned as written (a constant label).
|
|
64
70
|
*
|
|
@@ -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 } from './boardDate.js'\nimport { parseDateValue } from './dateVariant.js'\nimport { readPath } from './readPath.js'\n\nconst TOKEN = /\\{([^{}\\s|]+)(?:\\|([a-z]+))?\\}/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 * Format one token value.\n *\n * @param {unknown} value The raw value.\n * @param {string} format `date`, `datetime` or '' (as is).\n * @param {Date} now The reference moment.\n * @return {string} The text, '' when nothing can be shown.\n */\nfunction formatValue(value, format, now) {\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\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. 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 * @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()) {\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) => {\n\t\tconst value = object && typeof object === 'object' ? readPath(object, path) : undefined\n\t\tconst shown = formatValue(value, format || '', now)\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 * @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()) {\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)\n\t\tif (text !== '') {\n\t\t\treturn text\n\t\t}\n\t}\n\treturn ''\n}\n"],"names":[],"mappings":"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"}
|
|
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 } from './boardDate.js'\nimport { parseDateValue } from './dateVariant.js'\nimport { readPath } from './readPath.js'\n\nconst TOKEN = /\\{([^{}\\s|]+)(?:\\|([a-z]+))?\\}/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 * Format one token value.\n *\n * @param {unknown} value The raw value.\n * @param {string} format `date`, `datetime` or '' (as is).\n * @param {Date} now The reference moment.\n * @return {string} The text, '' when nothing can be shown.\n */\nfunction formatValue(value, format, now) {\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 === '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, 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 * @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()) {\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) => {\n\t\tconst value = object && typeof object === 'object' ? readPath(object, path) : undefined\n\t\tconst shown = formatValue(value, format || '', now)\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 * @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()) {\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)\n\t\tif (text !== '') {\n\t\t\treturn text\n\t\t}\n\t}\n\treturn ''\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* pageControls: how a list widget listens to the page's controls.
|
|
6
|
+
*
|
|
7
|
+
* A page control (a `workspace-filter` select or switch) writes a value to the
|
|
8
|
+
* page workspace. A list widget declares which control it listens to and what
|
|
9
|
+
* the value means for it:
|
|
10
|
+
*
|
|
11
|
+
* pageControls: {
|
|
12
|
+
* sort: { key: "sort", map: { deadline: { field: "slaDeadline", dir: "asc" } } },
|
|
13
|
+
* toggle: { key: "showDone", removes: ["status"] }
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* `sort`: the workspace value at `key` picks an entry of `map` (the sort the
|
|
17
|
+
* list uses); a value with no entry leaves the list's own sort.
|
|
18
|
+
* `toggle`: while the workspace value at `key` is on (true, "1", "true"), the
|
|
19
|
+
* filter keys in `removes` are dropped, so a list that hides finished work
|
|
20
|
+
* shows it.
|
|
21
|
+
*
|
|
22
|
+
* @module utils/pageControls
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether a workspace value reads as on.
|
|
27
|
+
*
|
|
28
|
+
* @param {unknown} value The value.
|
|
29
|
+
* @return {boolean} True for true, 1, "1" and "true".
|
|
30
|
+
*/
|
|
31
|
+
function isOn(value) {
|
|
32
|
+
return value === true || value === 1 || value === '1' || value === 'true'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The sort a list uses once the page's sort control is read.
|
|
37
|
+
*
|
|
38
|
+
* @param {object|null} pageControls The widget's `pageControls`.
|
|
39
|
+
* @param {object} workspace The plain workspace bag.
|
|
40
|
+
* @param {{field: string, dir?: string}|null} own The list's own sort.
|
|
41
|
+
* @return {{field: string, dir?: string}|null} The sort to use.
|
|
42
|
+
* @spec openspec/changes/screens-page-controls/specs/page-controls/spec.md#requirement-a-list-listens-to-the-pages-controls
|
|
43
|
+
*/
|
|
44
|
+
function pageSort(pageControls, workspace, own) {
|
|
45
|
+
const listen = pageControls && typeof pageControls === 'object' ? pageControls.sort : null;
|
|
46
|
+
if (!listen || typeof listen.key !== 'string' || !listen.map || typeof listen.map !== 'object') {
|
|
47
|
+
return own
|
|
48
|
+
}
|
|
49
|
+
const chosen = workspace ? workspace[listen.key] : undefined;
|
|
50
|
+
const entry = chosen !== undefined && chosen !== null ? listen.map[String(chosen)] : null;
|
|
51
|
+
return entry && typeof entry === 'object' && typeof entry.field === 'string' && entry.field !== '' ? entry : own
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The filter a list uses once the page's toggle is read: with the toggle on,
|
|
56
|
+
* the keys it `removes` are gone.
|
|
57
|
+
*
|
|
58
|
+
* @param {object|null} pageControls The widget's `pageControls`.
|
|
59
|
+
* @param {object} workspace The plain workspace bag.
|
|
60
|
+
* @param {object} filter The list's own filter (before token resolution).
|
|
61
|
+
* @return {object} The filter to use (the same object when nothing is removed).
|
|
62
|
+
* @spec openspec/changes/screens-page-controls/specs/page-controls/spec.md#requirement-a-list-listens-to-the-pages-controls
|
|
63
|
+
*/
|
|
64
|
+
function pageFilter(pageControls, workspace, filter) {
|
|
65
|
+
const listen = pageControls && typeof pageControls === 'object' ? pageControls.toggle : null;
|
|
66
|
+
const base = filter && typeof filter === 'object' ? filter : {};
|
|
67
|
+
if (!listen || typeof listen.key !== 'string' || !Array.isArray(listen.removes)) {
|
|
68
|
+
return base
|
|
69
|
+
}
|
|
70
|
+
if (!isOn(workspace ? workspace[listen.key] : undefined)) {
|
|
71
|
+
return base
|
|
72
|
+
}
|
|
73
|
+
const out = { ...base };
|
|
74
|
+
for (const key of listen.removes) {
|
|
75
|
+
delete out[key];
|
|
76
|
+
}
|
|
77
|
+
return out
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { isOn, pageFilter, pageSort };
|
|
81
|
+
//# sourceMappingURL=pageControls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pageControls.js","sources":["../../../src/utils/pageControls.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * pageControls: how a list widget listens to the page's controls.\n *\n * A page control (a `workspace-filter` select or switch) writes a value to the\n * page workspace. A list widget declares which control it listens to and what\n * the value means for it:\n *\n * pageControls: {\n * sort: { key: \"sort\", map: { deadline: { field: \"slaDeadline\", dir: \"asc\" } } },\n * toggle: { key: \"showDone\", removes: [\"status\"] }\n * }\n *\n * `sort`: the workspace value at `key` picks an entry of `map` (the sort the\n * list uses); a value with no entry leaves the list's own sort.\n * `toggle`: while the workspace value at `key` is on (true, \"1\", \"true\"), the\n * filter keys in `removes` are dropped, so a list that hides finished work\n * shows it.\n *\n * @module utils/pageControls\n */\n\n/**\n * Whether a workspace value reads as on.\n *\n * @param {unknown} value The value.\n * @return {boolean} True for true, 1, \"1\" and \"true\".\n */\nexport function isOn(value) {\n\treturn value === true || value === 1 || value === '1' || value === 'true'\n}\n\n/**\n * The sort a list uses once the page's sort control is read.\n *\n * @param {object|null} pageControls The widget's `pageControls`.\n * @param {object} workspace The plain workspace bag.\n * @param {{field: string, dir?: string}|null} own The list's own sort.\n * @return {{field: string, dir?: string}|null} The sort to use.\n * @spec openspec/changes/screens-page-controls/specs/page-controls/spec.md#requirement-a-list-listens-to-the-pages-controls\n */\nexport function pageSort(pageControls, workspace, own) {\n\tconst listen = pageControls && typeof pageControls === 'object' ? pageControls.sort : null\n\tif (!listen || typeof listen.key !== 'string' || !listen.map || typeof listen.map !== 'object') {\n\t\treturn own\n\t}\n\tconst chosen = workspace ? workspace[listen.key] : undefined\n\tconst entry = chosen !== undefined && chosen !== null ? listen.map[String(chosen)] : null\n\treturn entry && typeof entry === 'object' && typeof entry.field === 'string' && entry.field !== '' ? entry : own\n}\n\n/**\n * The filter a list uses once the page's toggle is read: with the toggle on,\n * the keys it `removes` are gone.\n *\n * @param {object|null} pageControls The widget's `pageControls`.\n * @param {object} workspace The plain workspace bag.\n * @param {object} filter The list's own filter (before token resolution).\n * @return {object} The filter to use (the same object when nothing is removed).\n * @spec openspec/changes/screens-page-controls/specs/page-controls/spec.md#requirement-a-list-listens-to-the-pages-controls\n */\nexport function pageFilter(pageControls, workspace, filter) {\n\tconst listen = pageControls && typeof pageControls === 'object' ? pageControls.toggle : null\n\tconst base = filter && typeof filter === 'object' ? filter : {}\n\tif (!listen || typeof listen.key !== 'string' || !Array.isArray(listen.removes)) {\n\t\treturn base\n\t}\n\tif (!isOn(workspace ? workspace[listen.key] : undefined)) {\n\t\treturn base\n\t}\n\tconst out = { ...base }\n\tfor (const key of listen.removes) {\n\t\tdelete out[key]\n\t}\n\treturn out\n}\n"],"names":[],"mappings":"AAAA;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;AACA;AACA;AACO,SAAS,IAAI,CAAC,KAAK,EAAE;AAC5B,CAAC,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,MAAM;AAC1E,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,QAAQ,CAAC,YAAY,EAAE,SAAS,EAAE,GAAG,EAAE;AACvD,CAAC,MAAM,MAAM,GAAG,YAAY,IAAI,OAAO,YAAY,KAAK,QAAQ,GAAG,YAAY,CAAC,IAAI,GAAG,KAAI;AAC3F,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,EAAE;AACjG,EAAE,OAAO,GAAG;AACZ,EAAE;AACF,CAAC,MAAM,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,UAAS;AAC7D,CAAC,MAAM,KAAK,GAAG,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,KAAI;AAC1F,CAAC,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,KAAK,KAAK,EAAE,GAAG,KAAK,GAAG,GAAG;AACjH,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,UAAU,CAAC,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE;AAC5D,CAAC,MAAM,MAAM,GAAG,YAAY,IAAI,OAAO,YAAY,KAAK,QAAQ,GAAG,YAAY,CAAC,MAAM,GAAG,KAAI;AAC7F,CAAC,MAAM,IAAI,GAAG,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,GAAG,MAAM,GAAG,GAAE;AAChE,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE;AAClF,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,EAAE;AAC3D,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,GAAG,GAAG,EAAE,GAAG,IAAI,GAAE;AACxB,CAAC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE;AACnC,EAAE,OAAO,GAAG,CAAC,GAAG,EAAC;AACjB,EAAE;AACF,CAAC,OAAO,GAAG;AACX;;;;"}
|
|
@@ -38,6 +38,42 @@ function personalLensTabs(lenses) {
|
|
|
38
38
|
.map((id) => ({ label: labels[id], filter: { [LENS_FILTER_KEYS[id]]: true }, lens: id }))
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Turn the `empty` / `notEmpty` shorthand of quick filters into a filter.
|
|
43
|
+
*
|
|
44
|
+
* `{ label: "Not logged in yet", empty: "lastLoginAt" }` is the rows where the
|
|
45
|
+
* field has no value: the filter `{ lastLoginAt: { isnull: true } }`, which
|
|
46
|
+
* OpenRegister reads as `lastLoginAt[isnull]=true`. `notEmpty` is the rows
|
|
47
|
+
* where it has one. Either takes a field or a list of fields and merges with
|
|
48
|
+
* the entry's own `filter`. Returns the same array when no entry uses it.
|
|
49
|
+
*
|
|
50
|
+
* @param {Array<object>|null} quickFilters The page's quick filters.
|
|
51
|
+
* @return {Array<object>|null} The quick filters with the shorthand expanded.
|
|
52
|
+
*/
|
|
53
|
+
function expandEmptyFilters(quickFilters) {
|
|
54
|
+
if (!Array.isArray(quickFilters)) {
|
|
55
|
+
return quickFilters
|
|
56
|
+
}
|
|
57
|
+
const uses = (tab) => tab && typeof tab === 'object' && (tab.empty || tab.notEmpty);
|
|
58
|
+
if (!quickFilters.some(uses)) {
|
|
59
|
+
return quickFilters
|
|
60
|
+
}
|
|
61
|
+
const fieldsOf = (value) => (Array.isArray(value) ? value : [value]).filter((f) => typeof f === 'string' && f !== '');
|
|
62
|
+
return quickFilters.map((tab) => {
|
|
63
|
+
if (!uses(tab)) {
|
|
64
|
+
return tab
|
|
65
|
+
}
|
|
66
|
+
const filter = { ...(tab.filter && typeof tab.filter === 'object' ? tab.filter : {}) };
|
|
67
|
+
for (const field of fieldsOf(tab.empty)) {
|
|
68
|
+
filter[field] = { isnull: true };
|
|
69
|
+
}
|
|
70
|
+
for (const field of fieldsOf(tab.notEmpty)) {
|
|
71
|
+
filter[field] = { isnull: false };
|
|
72
|
+
}
|
|
73
|
+
return { ...tab, filter }
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
41
77
|
/**
|
|
42
78
|
* The page's quick filters with its personal lenses appended. A page with no
|
|
43
79
|
* quick filters of its own gets an "All" tab first, so a lens is not active
|
|
@@ -49,14 +85,15 @@ function personalLensTabs(lenses) {
|
|
|
49
85
|
*/
|
|
50
86
|
function withPersonalLenses(quickFilters, lenses) {
|
|
51
87
|
const tabs = personalLensTabs(lenses);
|
|
88
|
+
const expanded = expandEmptyFilters(quickFilters);
|
|
52
89
|
if (tabs.length === 0) {
|
|
53
|
-
return
|
|
90
|
+
return expanded
|
|
54
91
|
}
|
|
55
|
-
const own = Array.isArray(
|
|
92
|
+
const own = Array.isArray(expanded) ? expanded : [];
|
|
56
93
|
return own.length > 0
|
|
57
94
|
? [...own, ...tabs]
|
|
58
95
|
: [{ label: translate('nextcloud-vue', 'All'), filter: {}, default: true }, ...tabs]
|
|
59
96
|
}
|
|
60
97
|
|
|
61
|
-
export { LENS_FILTER_KEYS, personalLensTabs, withPersonalLenses };
|
|
98
|
+
export { LENS_FILTER_KEYS, expandEmptyFilters, personalLensTabs, withPersonalLenses };
|
|
62
99
|
//# sourceMappingURL=personalLenses.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"personalLenses.js","sources":["../../../src/utils/personalLenses.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * The personal lenses of an index page (Following, Recent, Unread) as quick\n * filters, appended after the page's own.\n *\n * There is no Favourites lens any more: a favourite is a follow with\n * notifications off (OpenRegister `merge-follow-and-favourites`), so\n * `favourite` is an alias of `watching` and asking for both gives one tab.\n *\n * @spec openspec/changes/one-follow-control/specs/record-follow/spec.md#requirement-index-pages-offer-one-following-lens-and-a-follow-column\n */\nimport { translate as t } from '@nextcloud/l10n'\n\n/** Lens id to the query key OpenRegister reads. */\nexport const LENS_FILTER_KEYS = { recent: '_recent', watching: '_watching', unread: '_unread' }\n\n/** Deprecated lens ids and the lens they now mean. */\nconst LENS_ALIASES = { favourite: 'watching' }\n\n/**\n * Quick filters for the requested lenses. Unknown ids are ignored.\n *\n * @param {string[]} lenses Any of `watching`, `recent`, `unread`, and the deprecated `favourite` (read as `watching`).\n * @return {Array<{label: string, filter: object, lens: string}>} One tab per lens, in the order asked.\n */\nexport function personalLensTabs(lenses) {\n\tconst labels = {\n\t\trecent: t('nextcloud-vue', 'Recent'),\n\t\twatching: t('nextcloud-vue', 'Following'),\n\t\tunread: t('nextcloud-vue', 'Unread'),\n\t}\n\treturn (Array.isArray(lenses) ? lenses : [])\n\t\t.map((id) => (Object.hasOwn(LENS_ALIASES, id) ? LENS_ALIASES[id] : id))\n\t\t.filter((id, i, all) => Object.hasOwn(LENS_FILTER_KEYS, id) && all.indexOf(id) === i)\n\t\t.map((id) => ({ label: labels[id], filter: { [LENS_FILTER_KEYS[id]]: true }, lens: id }))\n}\n\n/**\n * The page's quick filters with its personal lenses appended. A page with no\n * quick filters of its own gets an \"All\" tab first, so a lens is not active\n * before the person asks for it.\n *\n * @param {Array<object>|null} quickFilters The page's own quick filters.\n * @param {string[]} lenses The requested lenses.\n * @return {Array<object>|null} The combined list, or the page's own list unchanged when no lens is asked for.\n */\nexport function withPersonalLenses(quickFilters, lenses) {\n\tconst tabs = personalLensTabs(lenses)\n\tif (tabs.length === 0) {\n\t\treturn
|
|
1
|
+
{"version":3,"file":"personalLenses.js","sources":["../../../src/utils/personalLenses.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * The personal lenses of an index page (Following, Recent, Unread) as quick\n * filters, appended after the page's own.\n *\n * There is no Favourites lens any more: a favourite is a follow with\n * notifications off (OpenRegister `merge-follow-and-favourites`), so\n * `favourite` is an alias of `watching` and asking for both gives one tab.\n *\n * @spec openspec/changes/one-follow-control/specs/record-follow/spec.md#requirement-index-pages-offer-one-following-lens-and-a-follow-column\n */\nimport { translate as t } from '@nextcloud/l10n'\n\n/** Lens id to the query key OpenRegister reads. */\nexport const LENS_FILTER_KEYS = { recent: '_recent', watching: '_watching', unread: '_unread' }\n\n/** Deprecated lens ids and the lens they now mean. */\nconst LENS_ALIASES = { favourite: 'watching' }\n\n/**\n * Quick filters for the requested lenses. Unknown ids are ignored.\n *\n * @param {string[]} lenses Any of `watching`, `recent`, `unread`, and the deprecated `favourite` (read as `watching`).\n * @return {Array<{label: string, filter: object, lens: string}>} One tab per lens, in the order asked.\n */\nexport function personalLensTabs(lenses) {\n\tconst labels = {\n\t\trecent: t('nextcloud-vue', 'Recent'),\n\t\twatching: t('nextcloud-vue', 'Following'),\n\t\tunread: t('nextcloud-vue', 'Unread'),\n\t}\n\treturn (Array.isArray(lenses) ? lenses : [])\n\t\t.map((id) => (Object.hasOwn(LENS_ALIASES, id) ? LENS_ALIASES[id] : id))\n\t\t.filter((id, i, all) => Object.hasOwn(LENS_FILTER_KEYS, id) && all.indexOf(id) === i)\n\t\t.map((id) => ({ label: labels[id], filter: { [LENS_FILTER_KEYS[id]]: true }, lens: id }))\n}\n\n/**\n * Turn the `empty` / `notEmpty` shorthand of quick filters into a filter.\n *\n * `{ label: \"Not logged in yet\", empty: \"lastLoginAt\" }` is the rows where the\n * field has no value: the filter `{ lastLoginAt: { isnull: true } }`, which\n * OpenRegister reads as `lastLoginAt[isnull]=true`. `notEmpty` is the rows\n * where it has one. Either takes a field or a list of fields and merges with\n * the entry's own `filter`. Returns the same array when no entry uses it.\n *\n * @param {Array<object>|null} quickFilters The page's quick filters.\n * @return {Array<object>|null} The quick filters with the shorthand expanded.\n */\nexport function expandEmptyFilters(quickFilters) {\n\tif (!Array.isArray(quickFilters)) {\n\t\treturn quickFilters\n\t}\n\tconst uses = (tab) => tab && typeof tab === 'object' && (tab.empty || tab.notEmpty)\n\tif (!quickFilters.some(uses)) {\n\t\treturn quickFilters\n\t}\n\tconst fieldsOf = (value) => (Array.isArray(value) ? value : [value]).filter((f) => typeof f === 'string' && f !== '')\n\treturn quickFilters.map((tab) => {\n\t\tif (!uses(tab)) {\n\t\t\treturn tab\n\t\t}\n\t\tconst filter = { ...(tab.filter && typeof tab.filter === 'object' ? tab.filter : {}) }\n\t\tfor (const field of fieldsOf(tab.empty)) {\n\t\t\tfilter[field] = { isnull: true }\n\t\t}\n\t\tfor (const field of fieldsOf(tab.notEmpty)) {\n\t\t\tfilter[field] = { isnull: false }\n\t\t}\n\t\treturn { ...tab, filter }\n\t})\n}\n\n/**\n * The page's quick filters with its personal lenses appended. A page with no\n * quick filters of its own gets an \"All\" tab first, so a lens is not active\n * before the person asks for it.\n *\n * @param {Array<object>|null} quickFilters The page's own quick filters.\n * @param {string[]} lenses The requested lenses.\n * @return {Array<object>|null} The combined list, or the page's own list unchanged when no lens is asked for.\n */\nexport function withPersonalLenses(quickFilters, lenses) {\n\tconst tabs = personalLensTabs(lenses)\n\tconst expanded = expandEmptyFilters(quickFilters)\n\tif (tabs.length === 0) {\n\t\treturn expanded\n\t}\n\tconst own = Array.isArray(expanded) ? expanded : []\n\treturn own.length > 0\n\t\t? [...own, ...tabs]\n\t\t: [{ label: t('nextcloud-vue', 'All'), filter: {}, default: true }, ...tabs]\n}\n"],"names":["t"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getCurrentUser } from '@nextcloud/auth';
|
|
2
2
|
import { TODAY_DELTA_RE } from './sentinelTokens.js';
|
|
3
|
-
import { peekCurrentUserGroups } from './
|
|
3
|
+
import { peekCurrentUserGroups } from './userGroupsPeek.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveFilterTokens.js","sources":["../../../src/utils/resolveFilterTokens.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Resolve dynamic `@`-tokens inside a widget filter at FETCH time, so manifest\n * filters can express \"relative to now / the current user\" without baking a\n * fixed value. The widgets call this just before sending the filter to\n * OpenRegister.\n *\n * Supported tokens:\n * - `@me` → the current Nextcloud user id (`getCurrentUser()` from\n * `@nextcloud/auth`; `window.OC.currentUser` fallback)\n * - `@myGroups` → the ids of the Nextcloud groups the current user is\n * in, as an ARRAY (an IN filter). `ctx.myGroups` wins;\n * otherwise a reactive peek over `getCurrentUserGroups`.\n * Stays UNRESOLVED while loading and when the user is\n * in no group, so the caller waits instead of sending\n * an empty IN list (which OpenRegister reads as \"no\n * constraint\"). Spread in place inside an IN-list array.\n * - `@now` → the current instant, ISO-8601\n * - `@today` → today at 00:00, `YYYY-MM-DD` (date-prefix comparable)\n * - `@today±Nd` → relative-date arithmetic at day granularity: N days\n * from today (e.g. `@today-30d`, `@today+7d`)\n * - `@monthStart` → first day of the current month, `YYYY-MM-DD`\n * - `@monthEnd` → last day of the current month, `YYYY-MM-DD`; on a\n * date-time field use `lt: '@nextMonthStart'` instead,\n * since `lte` a date stops at that day's midnight\n * - `@nextMonthStart` → first day of the next month, `YYYY-MM-DD`\n * - `@quarterStart` → first day of the current quarter, `YYYY-MM-DD`\n * - `@yearStart` → first day of the current year, `YYYY-MM-DD`\n * - `@currentFiscalYear` → the current calendar year as a number string, e.g. `2026`\n * - `@range.from` / `@range.to` / `@range.preset` → the active date range a\n * widget passes in `ctx.range` (CnStatWidget: its own\n * picker, else the dashboard's). A trailing `?` marks it\n * optional, as for `@workspace.<key>?`.\n * - `@objectId` → the current detail-page object's id (needs `ctx`)\n * - `@object.<field>` → a field off the current detail-page object (needs `ctx`)\n * - `@workspace.<key>` → a value off the page-level workspace context (needs `ctx.workspace`)\n * - `@config.<key>` → a value off the page-level app config (needs `ctx.config`)\n *\n * `@objectId` / `@object.<field>` are OBJECT-CONTEXT tokens: they resolve only\n * when a `ctx` `{ objectId, object }` is supplied (a detail page provides it via\n * the `cnObjectContext` inject). `@workspace.<key>` is a WORKSPACE-CONTEXT token:\n * it resolves a key off `ctx.workspace` (the reactive `cnWorkspaceContext` a\n * dashboard/workspace page provides) so a list widget can react to page-level\n * state another widget writes (e.g. a selected client). Without the matching\n * context the tokens pass through unchanged — and an UNRESOLVED `@workspace.*`\n * token signals \"no selection yet\" to the caller (see {@link hasUnresolvedTokens}).\n *\n * `@config.<key>` is an APP-CONFIG token: it resolves a key off `ctx.config` (the\n * page-level app config a dashboard/detail page provides via `cnAppConfig` — e.g.\n * a reporting `currency` captured by the setup wizard). Like `@workspace.<key>`, a\n * trailing `?` marks it optional. An UNRESOLVED required `@config.<key>` passes\n * through unchanged; this lets `format: { currency: '@config.currency' }` fall\n * back to a literal default when the config is unset.\n *\n * IN-lists: a filter value MAY be an ARRAY (`{ status: ['open', 'pending'] }`,\n * or the explicit operator form `{ status: { in: [...] } }`). Tokens inside the\n * array are resolved item-by-item (`{ assignee: ['@me', 'shared'] }` works).\n * OpenRegister expects an IN-list as repeated bracket params\n * (`status[]=open&status[]=pending` — the same shape `buildQueryString` /\n * `useObjectStore` send), so widget fetchers serialize a resolved array as\n * repeated `field[]` params, never as a JSON blob or a single joined string.\n *\n * @module utils/resolveFilterTokens\n */\n\nimport { getCurrentUser } from '@nextcloud/auth'\nimport { TODAY_DELTA_RE } from './sentinelTokens.js'\nimport { peekCurrentUserGroups } from './widgetVisibility.js'\n\n/** The filter token for the current user's group ids. */\nexport const MY_GROUPS_TOKEN = '@myGroups'\n\n/**\n * Resolve `@myGroups`: the current user's group ids, or the token itself while\n * they are loading or when there are none (an empty IN list would match every\n * row, so \"no team\" must mean \"wait\", never \"everything\").\n *\n * @spec openspec/changes/nextcloud-group-surfaces/specs/schema-utilities/spec.md#requirement-mygroups-resolves-to-the-current-users-group-ids\n * @param {{myGroups?: string[]}} [ctx] Optional context; `ctx.myGroups` wins.\n * @return {string[]|string} The group ids, or `'@myGroups'` when unresolved.\n */\nfunction resolveMyGroups(ctx) {\n\tlet groups = ctx && Array.isArray(ctx.myGroups) ? ctx.myGroups : null\n\tif (groups === null) {\n\t\ttry {\n\t\t\tgroups = peekCurrentUserGroups()\n\t\t} catch {\n\t\t\tgroups = null\n\t\t}\n\t}\n\tconst ids = Array.isArray(groups)\n\t\t? groups.filter((g) => typeof g === 'string' && g !== '')\n\t\t: []\n\treturn ids.length > 0 ? [...ids] : MY_GROUPS_TOKEN\n}\n\n/**\n * Format a Date as `YYYY-MM-DD` (local).\n *\n * @param {Date} d The date.\n * @return {string} The date-only string.\n */\nfunction ymd(d) {\n\tconst m = String(d.getMonth() + 1).padStart(2, '0')\n\tconst day = String(d.getDate()).padStart(2, '0')\n\treturn `${d.getFullYear()}-${m}-${day}`\n}\n\n/**\n * Resolve a single filter value if it is a dynamic `@`-token, else pass through.\n *\n * @param {unknown} v The candidate value.\n * @param {{objectId?: (string|number), object?: object, workspace?: object, config?: object, range?: object, myGroups?: string[]}} [ctx] Optional\n * context for `@objectId` / `@object.<field>` (detail page),\n * `@workspace.<key>` (page-level workspace state), and `@config.<key>`\n * (page-level app config) tokens.\n * @return {unknown} The resolved value.\n */\nexport function resolveFilterValue(v, ctx) {\n\tif (typeof v !== 'string' || v.charAt(0) !== '@') {\n\t\treturn v\n\t}\n\tconst now = new Date()\n\tif (v === MY_GROUPS_TOKEN) {\n\t\treturn resolveMyGroups(ctx)\n\t}\n\tif (v === '@objectId') {\n\t\treturn (ctx && ctx.objectId !== undefined && ctx.objectId !== null) ? String(ctx.objectId) : v\n\t}\n\tif (v.startsWith('@object.')) {\n\t\tconst field = v.slice('@object.'.length)\n\t\tif (ctx && ctx.object && field && ctx.object[field] !== undefined) {\n\t\t\treturn ctx.object[field]\n\t\t}\n\t\treturn v\n\t}\n\tif (v.startsWith('@workspace.')) {\n\t\t// A trailing `?` marks the token OPTIONAL: when unset the caller drops the\n\t\t// filter key (show all) instead of waiting (see hasUnresolvedTokens /\n\t\t// CnObjectListWidget). The `?` is stripped from the key lookup.\n\t\tconst raw = v.slice('@workspace.'.length)\n\t\tconst key = raw.endsWith('?') ? raw.slice(0, -1) : raw\n\t\tif (ctx && ctx.workspace && key && ctx.workspace[key] !== undefined\n\t\t\t&& ctx.workspace[key] !== null && ctx.workspace[key] !== '') {\n\t\t\treturn ctx.workspace[key]\n\t\t}\n\t\treturn v\n\t}\n\tif (v.startsWith('@config.')) {\n\t\t// A trailing `?` marks the token OPTIONAL (same convention as\n\t\t// `@workspace.<key>?`): an unset value lets the caller drop the key. A\n\t\t// required `@config.<key>` that is unset passes through unchanged so a\n\t\t// downstream consumer can fall back to a literal default (e.g.\n\t\t// `format.currency` → `'EUR'`).\n\t\tconst raw = v.slice('@config.'.length)\n\t\tconst key = raw.endsWith('?') ? raw.slice(0, -1) : raw\n\t\tif (ctx && ctx.config && key && ctx.config[key] !== undefined\n\t\t\t&& ctx.config[key] !== null && ctx.config[key] !== '') {\n\t\t\treturn ctx.config[key]\n\t\t}\n\t\treturn v\n\t}\n\tif (v.startsWith('@range.')) {\n\t\t// `@range.from` / `@range.to` / `@range.preset`: the active date range the\n\t\t// caller put in `ctx.range`. CnStatWidget documented these tokens and put\n\t\t// `range` in its endpoint context, but nothing resolved them, so the\n\t\t// literal token stayed in the params, hasUnresolvedTokens blocked the\n\t\t// request, and a card that used its own period picker never loaded. A\n\t\t// trailing `?` marks the token OPTIONAL, the same convention as\n\t\t// `@workspace.<key>?`: unset, the caller drops the key.\n\t\tconst raw = v.slice('@range.'.length)\n\t\tconst key = raw.endsWith('?') ? raw.slice(0, -1) : raw\n\t\tif (ctx && ctx.range && key && ctx.range[key] !== undefined\n\t\t\t&& ctx.range[key] !== null && ctx.range[key] !== '') {\n\t\t\treturn ctx.range[key]\n\t\t}\n\t\treturn v\n\t}\n\tif (v === '@me.displayName' || v === '@me.email') {\n\t\t// The form's own context wins (`ctx.me`, e.g. an email fetched from the\n\t\t// profile); @nextcloud/auth only knows the uid and display name.\n\t\tconst key = v.slice('@me.'.length)\n\t\tif (ctx && ctx.me && ctx.me[key] !== undefined && ctx.me[key] !== null && ctx.me[key] !== '') {\n\t\t\treturn ctx.me[key]\n\t\t}\n\t\ttry {\n\t\t\tconst user = getCurrentUser()\n\t\t\tif (user && user[key]) {\n\t\t\t\treturn user[key]\n\t\t\t}\n\t\t} catch {\n\t\t\t// not signed in: leave the token unresolved\n\t\t}\n\t\treturn v\n\t}\n\tif (v === '@me') {\n\t\t// Canonical source is @nextcloud/auth; window.OC is the fallback for\n\t\t// environments where the auth package can't read the page state (jsdom).\n\t\tlet uid\n\t\ttry {\n\t\t\tconst user = getCurrentUser()\n\t\t\tuid = user && user.uid\n\t\t} catch {\n\t\t\tuid = null\n\t\t}\n\t\treturn uid || (typeof window !== 'undefined' && window.OC && window.OC.currentUser) || ''\n\t}\n\tif (v === '@now') {\n\t\treturn now.toISOString()\n\t}\n\tif (v === '@today') {\n\t\tconst d = new Date(now)\n\t\td.setHours(0, 0, 0, 0)\n\t\treturn ymd(d)\n\t}\n\tif (v === '@monthStart') {\n\t\treturn ymd(new Date(now.getFullYear(), now.getMonth(), 1))\n\t}\n\tif (v === '@monthEnd') {\n\t\t// Day 0 of the next month is the last day of this one.\n\t\treturn ymd(new Date(now.getFullYear(), now.getMonth() + 1, 0))\n\t}\n\tif (v === '@nextMonthStart') {\n\t\treturn ymd(new Date(now.getFullYear(), now.getMonth() + 1, 1))\n\t}\n\tif (v === '@quarterStart') {\n\t\treturn ymd(new Date(now.getFullYear(), Math.floor(now.getMonth() / 3) * 3, 1))\n\t}\n\tif (v === '@yearStart') {\n\t\treturn ymd(new Date(now.getFullYear(), 0, 1))\n\t}\n\tif (v === '@currentFiscalYear') {\n\t\treturn String(now.getFullYear())\n\t}\n\tconst m = v.match(TODAY_DELTA_RE)\n\tif (m) {\n\t\tconst d = new Date(now)\n\t\td.setHours(0, 0, 0, 0)\n\t\td.setDate(d.getDate() + Number(m[1]))\n\t\treturn ymd(d)\n\t}\n\treturn v\n}\n\n/**\n * Whether a value is an UNRESOLVED OPTIONAL token — a string still shaped like\n * `@workspace.<key>?` or `@config.<key>?` after token resolution (the `?` marks\n * it as \"drop me when unset\"). Callers strip such keys from the filter so the\n * list shows all rows rather than waiting for a selection / a config value.\n *\n * @param {unknown} v A (possibly resolved) filter value.\n * @return {boolean}\n */\nexport function isOptionalUnresolved(v) {\n\treturn typeof v === 'string'\n\t\t&& (v.startsWith('@workspace.') || v.startsWith('@config.') || v.startsWith('@range.'))\n\t\t&& v.endsWith('?')\n}\n\n/**\n * Return a copy of a resolved filter map with every UNRESOLVED OPTIONAL\n * workspace token (`@workspace.<key>?` left as-is) removed. Required tokens and\n * concrete values are kept untouched. Inside an IN-list array, unresolved\n * optional ITEMS are dropped; a key whose array empties out is dropped whole\n * (an empty IN-list means \"no constraint\", not \"match nothing\").\n *\n * @param {object} filter A filter map already passed through {@link resolveFilterTokens}.\n * @return {object} The filter without optional-unresolved keys.\n */\nexport function dropOptionalUnresolved(filter) {\n\tif (!filter || typeof filter !== 'object') {\n\t\treturn filter\n\t}\n\tconst out = {}\n\tfor (const [k, v] of Object.entries(filter)) {\n\t\tif (isOptionalUnresolved(v)) {\n\t\t\tcontinue\n\t\t}\n\t\tif (Array.isArray(v)) {\n\t\t\tconst items = v.filter((item) => !isOptionalUnresolved(item))\n\t\t\tif (items.length > 0) {\n\t\t\t\tout[k] = items\n\t\t\t}\n\t\t} else if (v && typeof v === 'object') {\n\t\t\tconst inner = {}\n\t\t\tfor (const [op, ov] of Object.entries(v)) {\n\t\t\t\tif (Array.isArray(ov)) {\n\t\t\t\t\tconst items = ov.filter((item) => !isOptionalUnresolved(item))\n\t\t\t\t\tif (items.length > 0) {\n\t\t\t\t\t\tinner[op] = items\n\t\t\t\t\t}\n\t\t\t\t} else if (!isOptionalUnresolved(ov)) {\n\t\t\t\t\tinner[op] = ov\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (Object.keys(inner).length > 0) {\n\t\t\t\tout[k] = inner\n\t\t\t}\n\t\t} else {\n\t\t\tout[k] = v\n\t\t}\n\t}\n\treturn out\n}\n\n/**\n * Whether a resolved filter map still carries an unresolved `@`-token. Used by\n * context-bound widgets to detect \"the page state this widget depends on isn't\n * set yet\" (e.g. a client-overview list whose `client` filter is still the raw\n * `@workspace.selectedClient` because no client is selected) and skip the query\n * / render a prompt instead of fetching the whole register.\n *\n * OPTIONAL tokens (`@workspace.<key>?`) are NOT counted — they are meant to be\n * dropped, not waited on (see {@link dropOptionalUnresolved}).\n *\n * @param {object} filter A filter map already passed through {@link resolveFilterTokens}.\n * @return {boolean} True when any value is still a (non-optional) string beginning with `@`.\n */\nexport function hasUnresolvedTokens(filter) {\n\tif (!filter || typeof filter !== 'object') {\n\t\treturn false\n\t}\n\tconst blocking = (x) => typeof x === 'string' && x.charAt(0) === '@' && !isOptionalUnresolved(x)\n\tconst anyBlocking = (v) => {\n\t\tif (blocking(v)) {\n\t\t\treturn true\n\t\t}\n\t\tif (Array.isArray(v)) {\n\t\t\treturn v.some(blocking)\n\t\t}\n\t\treturn false\n\t}\n\tfor (const v of Object.values(filter)) {\n\t\tif (anyBlocking(v)) {\n\t\t\treturn true\n\t\t}\n\t\tif (v && typeof v === 'object' && !Array.isArray(v)) {\n\t\t\tfor (const ov of Object.values(v)) {\n\t\t\t\tif (anyBlocking(ov)) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n/**\n * Resolve every dynamic `@`-token in a filter map (equality, operator, and\n * IN-list array shapes). Array values resolve item-by-item so a list can mix\n * tokens and literals (`{ assignee: ['@me', 'shared'] }`).\n *\n * @param {object} filter The filter map (`{ field: value | value[] | { op: value | value[] } }`).\n * @param {{objectId?: (string|number), object?: object, workspace?: object, config?: object, range?: object}} [ctx] Optional\n * context forwarded to {@link resolveFilterValue} for `@objectId` /\n * `@object.<field>` / `@workspace.<key>` / `@config.<key>` tokens.\n * @return {object} A new filter map with tokens resolved.\n */\nexport function resolveFilterTokens(filter, ctx) {\n\tif (!filter || typeof filter !== 'object') {\n\t\treturn filter\n\t}\n\t// An item that resolves to a list (`@myGroups`) is spread in place, so\n\t// `['@myGroups', 'archive']` stays one flat IN list.\n\tconst resolveOne = (v) => (Array.isArray(v)\n\t\t? v.flatMap((item) => {\n\t\t\t\tconst r = resolveFilterValue(item, ctx)\n\t\t\t\treturn Array.isArray(r) ? r : [r]\n\t\t\t})\n\t\t: resolveFilterValue(v, ctx))\n\tconst out = {}\n\tfor (const [k, v] of Object.entries(filter)) {\n\t\tif (v && typeof v === 'object' && !Array.isArray(v)) {\n\t\t\tconst inner = {}\n\t\t\tfor (const [op, ov] of Object.entries(v)) {\n\t\t\t\tinner[op] = resolveOne(ov)\n\t\t\t}\n\t\t\tout[k] = inner\n\t\t} else {\n\t\t\tout[k] = resolveOne(v)\n\t\t}\n\t}\n\treturn out\n}\n\n/**\n * Recursively resolve `@`-tokens inside an arbitrary JSON value — objects\n * and arrays at ANY depth, unlike {@link resolveFilterTokens} which only\n * understands the flat filter-map shape (equality / operator / IN-list, one\n * level of nesting). Used by the `api-call` action's `payload`: a\n * Filinq-style generation body like\n * `{ dataRefs: [{ register: 'crm', schema: 'lead', id: '@objectId' }] }`\n * needs the token resolved inside a nested array of objects, which the\n * shallow filter-map resolver can't reach.\n *\n * @param {unknown} value The value to resolve — object / array / primitive.\n * @param {{objectId?: (string|number), object?: object, workspace?: object, config?: object, range?: object}} [ctx] Optional\n * context forwarded to {@link resolveFilterValue}.\n * @return {unknown} A new value with every string leaf token-resolved (structure preserved).\n */\nexport function resolveDeepTokens(value, ctx) {\n\tif (Array.isArray(value)) {\n\t\treturn value.map((v) => resolveDeepTokens(v, ctx))\n\t}\n\tif (value && typeof value === 'object') {\n\t\tconst out = {}\n\t\tfor (const [k, v] of Object.entries(value)) {\n\t\t\tout[k] = resolveDeepTokens(v, ctx)\n\t\t}\n\t\treturn out\n\t}\n\treturn resolveFilterValue(value, ctx)\n}\n\n/**\n * Deep-tree counterpart to {@link hasUnresolvedTokens}: whether an\n * already-{@link resolveDeepTokens}-resolved value still carries an\n * unresolved, non-optional `@`-token anywhere inside it (any depth). Used to\n * BLOCK an `api-call` whose `payload` has a required token (typically\n * `@objectId` / `@object.<field>`) that failed to resolve, instead of\n * silently sending the literal token string to the server.\n *\n * @param {unknown} value The (already deep-resolved) value.\n * @return {boolean} True when a required token is still unresolved.\n */\nexport function hasUnresolvedDeepTokens(value) {\n\tif (Array.isArray(value)) {\n\t\treturn value.some(hasUnresolvedDeepTokens)\n\t}\n\tif (value && typeof value === 'object') {\n\t\treturn Object.values(value).some(hasUnresolvedDeepTokens)\n\t}\n\treturn typeof value === 'string' && value.charAt(0) === '@' && !isOptionalUnresolved(value)\n}\n\n/**\n * Deep-tree counterpart to {@link dropOptionalUnresolved}: strips unresolved\n * OPTIONAL tokens (`@workspace.<key>?` / `@config.<key>?` left as-is after\n * resolution) out of an arbitrary JSON value at any depth. An object key\n * whose value is an unresolved optional token is dropped entirely; an array\n * item that resolves unresolved-optional is filtered out.\n *\n * @param {unknown} value The (already {@link resolveDeepTokens}-resolved) value.\n * @return {unknown} The value with unresolved-optional leaves removed.\n */\nexport function dropOptionalUnresolvedDeep(value) {\n\tif (Array.isArray(value)) {\n\t\treturn value\n\t\t\t.filter((v) => !isOptionalUnresolved(v))\n\t\t\t.map((v) => dropOptionalUnresolvedDeep(v))\n\t}\n\tif (value && typeof value === 'object') {\n\t\tconst out = {}\n\t\tfor (const [k, v] of Object.entries(value)) {\n\t\t\tif (isOptionalUnresolved(v)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tout[k] = dropOptionalUnresolvedDeep(v)\n\t\t}\n\t\treturn out\n\t}\n\treturn value\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resolveFilterTokens.js","sources":["../../../src/utils/resolveFilterTokens.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Resolve dynamic `@`-tokens inside a widget filter at FETCH time, so manifest\n * filters can express \"relative to now / the current user\" without baking a\n * fixed value. The widgets call this just before sending the filter to\n * OpenRegister.\n *\n * Supported tokens:\n * - `@me` → the current Nextcloud user id (`getCurrentUser()` from\n * `@nextcloud/auth`; `window.OC.currentUser` fallback)\n * - `@myGroups` → the ids of the Nextcloud groups the current user is\n * in, as an ARRAY (an IN filter). `ctx.myGroups` wins;\n * otherwise a reactive peek over `getCurrentUserGroups`.\n * Stays UNRESOLVED while loading and when the user is\n * in no group, so the caller waits instead of sending\n * an empty IN list (which OpenRegister reads as \"no\n * constraint\"). Spread in place inside an IN-list array.\n * - `@now` → the current instant, ISO-8601\n * - `@today` → today at 00:00, `YYYY-MM-DD` (date-prefix comparable)\n * - `@today±Nd` → relative-date arithmetic at day granularity: N days\n * from today (e.g. `@today-30d`, `@today+7d`)\n * - `@monthStart` → first day of the current month, `YYYY-MM-DD`\n * - `@monthEnd` → last day of the current month, `YYYY-MM-DD`; on a\n * date-time field use `lt: '@nextMonthStart'` instead,\n * since `lte` a date stops at that day's midnight\n * - `@nextMonthStart` → first day of the next month, `YYYY-MM-DD`\n * - `@quarterStart` → first day of the current quarter, `YYYY-MM-DD`\n * - `@yearStart` → first day of the current year, `YYYY-MM-DD`\n * - `@currentFiscalYear` → the current calendar year as a number string, e.g. `2026`\n * - `@range.from` / `@range.to` / `@range.preset` → the active date range a\n * widget passes in `ctx.range` (CnStatWidget: its own\n * picker, else the dashboard's). A trailing `?` marks it\n * optional, as for `@workspace.<key>?`.\n * - `@objectId` → the current detail-page object's id (needs `ctx`)\n * - `@object.<field>` → a field off the current detail-page object (needs `ctx`)\n * - `@workspace.<key>` → a value off the page-level workspace context (needs `ctx.workspace`)\n * - `@config.<key>` → a value off the page-level app config (needs `ctx.config`)\n *\n * `@objectId` / `@object.<field>` are OBJECT-CONTEXT tokens: they resolve only\n * when a `ctx` `{ objectId, object }` is supplied (a detail page provides it via\n * the `cnObjectContext` inject). `@workspace.<key>` is a WORKSPACE-CONTEXT token:\n * it resolves a key off `ctx.workspace` (the reactive `cnWorkspaceContext` a\n * dashboard/workspace page provides) so a list widget can react to page-level\n * state another widget writes (e.g. a selected client). Without the matching\n * context the tokens pass through unchanged — and an UNRESOLVED `@workspace.*`\n * token signals \"no selection yet\" to the caller (see {@link hasUnresolvedTokens}).\n *\n * `@config.<key>` is an APP-CONFIG token: it resolves a key off `ctx.config` (the\n * page-level app config a dashboard/detail page provides via `cnAppConfig` — e.g.\n * a reporting `currency` captured by the setup wizard). Like `@workspace.<key>`, a\n * trailing `?` marks it optional. An UNRESOLVED required `@config.<key>` passes\n * through unchanged; this lets `format: { currency: '@config.currency' }` fall\n * back to a literal default when the config is unset.\n *\n * IN-lists: a filter value MAY be an ARRAY (`{ status: ['open', 'pending'] }`,\n * or the explicit operator form `{ status: { in: [...] } }`). Tokens inside the\n * array are resolved item-by-item (`{ assignee: ['@me', 'shared'] }` works).\n * OpenRegister expects an IN-list as repeated bracket params\n * (`status[]=open&status[]=pending` — the same shape `buildQueryString` /\n * `useObjectStore` send), so widget fetchers serialize a resolved array as\n * repeated `field[]` params, never as a JSON blob or a single joined string.\n *\n * @module utils/resolveFilterTokens\n */\n\nimport { getCurrentUser } from '@nextcloud/auth'\nimport { TODAY_DELTA_RE } from './sentinelTokens.js'\n// userGroupsPeek, not widgetVisibility: that one imports @nextcloud/axios, and\n// this resolver runs on public pages and in node (see userGroupsPeek.js).\nimport { peekCurrentUserGroups } from './userGroupsPeek.js'\n\n/** The filter token for the current user's group ids. */\nexport const MY_GROUPS_TOKEN = '@myGroups'\n\n/**\n * Resolve `@myGroups`: the current user's group ids, or the token itself while\n * they are loading or when there are none (an empty IN list would match every\n * row, so \"no team\" must mean \"wait\", never \"everything\").\n *\n * @spec openspec/changes/nextcloud-group-surfaces/specs/schema-utilities/spec.md#requirement-mygroups-resolves-to-the-current-users-group-ids\n * @param {{myGroups?: string[]}} [ctx] Optional context; `ctx.myGroups` wins.\n * @return {string[]|string} The group ids, or `'@myGroups'` when unresolved.\n */\nfunction resolveMyGroups(ctx) {\n\tlet groups = ctx && Array.isArray(ctx.myGroups) ? ctx.myGroups : null\n\tif (groups === null) {\n\t\ttry {\n\t\t\tgroups = peekCurrentUserGroups()\n\t\t} catch {\n\t\t\tgroups = null\n\t\t}\n\t}\n\tconst ids = Array.isArray(groups)\n\t\t? groups.filter((g) => typeof g === 'string' && g !== '')\n\t\t: []\n\treturn ids.length > 0 ? [...ids] : MY_GROUPS_TOKEN\n}\n\n/**\n * Format a Date as `YYYY-MM-DD` (local).\n *\n * @param {Date} d The date.\n * @return {string} The date-only string.\n */\nfunction ymd(d) {\n\tconst m = String(d.getMonth() + 1).padStart(2, '0')\n\tconst day = String(d.getDate()).padStart(2, '0')\n\treturn `${d.getFullYear()}-${m}-${day}`\n}\n\n/**\n * Resolve a single filter value if it is a dynamic `@`-token, else pass through.\n *\n * @param {unknown} v The candidate value.\n * @param {{objectId?: (string|number), object?: object, workspace?: object, config?: object, range?: object, myGroups?: string[]}} [ctx] Optional\n * context for `@objectId` / `@object.<field>` (detail page),\n * `@workspace.<key>` (page-level workspace state), and `@config.<key>`\n * (page-level app config) tokens.\n * @return {unknown} The resolved value.\n */\nexport function resolveFilterValue(v, ctx) {\n\tif (typeof v !== 'string' || v.charAt(0) !== '@') {\n\t\treturn v\n\t}\n\tconst now = new Date()\n\tif (v === MY_GROUPS_TOKEN) {\n\t\treturn resolveMyGroups(ctx)\n\t}\n\tif (v === '@objectId') {\n\t\treturn (ctx && ctx.objectId !== undefined && ctx.objectId !== null) ? String(ctx.objectId) : v\n\t}\n\tif (v.startsWith('@object.')) {\n\t\tconst field = v.slice('@object.'.length)\n\t\tif (ctx && ctx.object && field && ctx.object[field] !== undefined) {\n\t\t\treturn ctx.object[field]\n\t\t}\n\t\treturn v\n\t}\n\tif (v.startsWith('@workspace.')) {\n\t\t// A trailing `?` marks the token OPTIONAL: when unset the caller drops the\n\t\t// filter key (show all) instead of waiting (see hasUnresolvedTokens /\n\t\t// CnObjectListWidget). The `?` is stripped from the key lookup.\n\t\tconst raw = v.slice('@workspace.'.length)\n\t\tconst key = raw.endsWith('?') ? raw.slice(0, -1) : raw\n\t\tif (ctx && ctx.workspace && key && ctx.workspace[key] !== undefined\n\t\t\t&& ctx.workspace[key] !== null && ctx.workspace[key] !== '') {\n\t\t\treturn ctx.workspace[key]\n\t\t}\n\t\treturn v\n\t}\n\tif (v.startsWith('@config.')) {\n\t\t// A trailing `?` marks the token OPTIONAL (same convention as\n\t\t// `@workspace.<key>?`): an unset value lets the caller drop the key. A\n\t\t// required `@config.<key>` that is unset passes through unchanged so a\n\t\t// downstream consumer can fall back to a literal default (e.g.\n\t\t// `format.currency` → `'EUR'`).\n\t\tconst raw = v.slice('@config.'.length)\n\t\tconst key = raw.endsWith('?') ? raw.slice(0, -1) : raw\n\t\tif (ctx && ctx.config && key && ctx.config[key] !== undefined\n\t\t\t&& ctx.config[key] !== null && ctx.config[key] !== '') {\n\t\t\treturn ctx.config[key]\n\t\t}\n\t\treturn v\n\t}\n\tif (v.startsWith('@range.')) {\n\t\t// `@range.from` / `@range.to` / `@range.preset`: the active date range the\n\t\t// caller put in `ctx.range`. CnStatWidget documented these tokens and put\n\t\t// `range` in its endpoint context, but nothing resolved them, so the\n\t\t// literal token stayed in the params, hasUnresolvedTokens blocked the\n\t\t// request, and a card that used its own period picker never loaded. A\n\t\t// trailing `?` marks the token OPTIONAL, the same convention as\n\t\t// `@workspace.<key>?`: unset, the caller drops the key.\n\t\tconst raw = v.slice('@range.'.length)\n\t\tconst key = raw.endsWith('?') ? raw.slice(0, -1) : raw\n\t\tif (ctx && ctx.range && key && ctx.range[key] !== undefined\n\t\t\t&& ctx.range[key] !== null && ctx.range[key] !== '') {\n\t\t\treturn ctx.range[key]\n\t\t}\n\t\treturn v\n\t}\n\tif (v === '@me.displayName' || v === '@me.email') {\n\t\t// The form's own context wins (`ctx.me`, e.g. an email fetched from the\n\t\t// profile); @nextcloud/auth only knows the uid and display name.\n\t\tconst key = v.slice('@me.'.length)\n\t\tif (ctx && ctx.me && ctx.me[key] !== undefined && ctx.me[key] !== null && ctx.me[key] !== '') {\n\t\t\treturn ctx.me[key]\n\t\t}\n\t\ttry {\n\t\t\tconst user = getCurrentUser()\n\t\t\tif (user && user[key]) {\n\t\t\t\treturn user[key]\n\t\t\t}\n\t\t} catch {\n\t\t\t// not signed in: leave the token unresolved\n\t\t}\n\t\treturn v\n\t}\n\tif (v === '@me') {\n\t\t// Canonical source is @nextcloud/auth; window.OC is the fallback for\n\t\t// environments where the auth package can't read the page state (jsdom).\n\t\tlet uid\n\t\ttry {\n\t\t\tconst user = getCurrentUser()\n\t\t\tuid = user && user.uid\n\t\t} catch {\n\t\t\tuid = null\n\t\t}\n\t\treturn uid || (typeof window !== 'undefined' && window.OC && window.OC.currentUser) || ''\n\t}\n\tif (v === '@now') {\n\t\treturn now.toISOString()\n\t}\n\tif (v === '@today') {\n\t\tconst d = new Date(now)\n\t\td.setHours(0, 0, 0, 0)\n\t\treturn ymd(d)\n\t}\n\tif (v === '@monthStart') {\n\t\treturn ymd(new Date(now.getFullYear(), now.getMonth(), 1))\n\t}\n\tif (v === '@monthEnd') {\n\t\t// Day 0 of the next month is the last day of this one.\n\t\treturn ymd(new Date(now.getFullYear(), now.getMonth() + 1, 0))\n\t}\n\tif (v === '@nextMonthStart') {\n\t\treturn ymd(new Date(now.getFullYear(), now.getMonth() + 1, 1))\n\t}\n\tif (v === '@quarterStart') {\n\t\treturn ymd(new Date(now.getFullYear(), Math.floor(now.getMonth() / 3) * 3, 1))\n\t}\n\tif (v === '@yearStart') {\n\t\treturn ymd(new Date(now.getFullYear(), 0, 1))\n\t}\n\tif (v === '@currentFiscalYear') {\n\t\treturn String(now.getFullYear())\n\t}\n\tconst m = v.match(TODAY_DELTA_RE)\n\tif (m) {\n\t\tconst d = new Date(now)\n\t\td.setHours(0, 0, 0, 0)\n\t\td.setDate(d.getDate() + Number(m[1]))\n\t\treturn ymd(d)\n\t}\n\treturn v\n}\n\n/**\n * Whether a value is an UNRESOLVED OPTIONAL token — a string still shaped like\n * `@workspace.<key>?` or `@config.<key>?` after token resolution (the `?` marks\n * it as \"drop me when unset\"). Callers strip such keys from the filter so the\n * list shows all rows rather than waiting for a selection / a config value.\n *\n * @param {unknown} v A (possibly resolved) filter value.\n * @return {boolean}\n */\nexport function isOptionalUnresolved(v) {\n\treturn typeof v === 'string'\n\t\t&& (v.startsWith('@workspace.') || v.startsWith('@config.') || v.startsWith('@range.'))\n\t\t&& v.endsWith('?')\n}\n\n/**\n * Return a copy of a resolved filter map with every UNRESOLVED OPTIONAL\n * workspace token (`@workspace.<key>?` left as-is) removed. Required tokens and\n * concrete values are kept untouched. Inside an IN-list array, unresolved\n * optional ITEMS are dropped; a key whose array empties out is dropped whole\n * (an empty IN-list means \"no constraint\", not \"match nothing\").\n *\n * @param {object} filter A filter map already passed through {@link resolveFilterTokens}.\n * @return {object} The filter without optional-unresolved keys.\n */\nexport function dropOptionalUnresolved(filter) {\n\tif (!filter || typeof filter !== 'object') {\n\t\treturn filter\n\t}\n\tconst out = {}\n\tfor (const [k, v] of Object.entries(filter)) {\n\t\tif (isOptionalUnresolved(v)) {\n\t\t\tcontinue\n\t\t}\n\t\tif (Array.isArray(v)) {\n\t\t\tconst items = v.filter((item) => !isOptionalUnresolved(item))\n\t\t\tif (items.length > 0) {\n\t\t\t\tout[k] = items\n\t\t\t}\n\t\t} else if (v && typeof v === 'object') {\n\t\t\tconst inner = {}\n\t\t\tfor (const [op, ov] of Object.entries(v)) {\n\t\t\t\tif (Array.isArray(ov)) {\n\t\t\t\t\tconst items = ov.filter((item) => !isOptionalUnresolved(item))\n\t\t\t\t\tif (items.length > 0) {\n\t\t\t\t\t\tinner[op] = items\n\t\t\t\t\t}\n\t\t\t\t} else if (!isOptionalUnresolved(ov)) {\n\t\t\t\t\tinner[op] = ov\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (Object.keys(inner).length > 0) {\n\t\t\t\tout[k] = inner\n\t\t\t}\n\t\t} else {\n\t\t\tout[k] = v\n\t\t}\n\t}\n\treturn out\n}\n\n/**\n * Whether a resolved filter map still carries an unresolved `@`-token. Used by\n * context-bound widgets to detect \"the page state this widget depends on isn't\n * set yet\" (e.g. a client-overview list whose `client` filter is still the raw\n * `@workspace.selectedClient` because no client is selected) and skip the query\n * / render a prompt instead of fetching the whole register.\n *\n * OPTIONAL tokens (`@workspace.<key>?`) are NOT counted — they are meant to be\n * dropped, not waited on (see {@link dropOptionalUnresolved}).\n *\n * @param {object} filter A filter map already passed through {@link resolveFilterTokens}.\n * @return {boolean} True when any value is still a (non-optional) string beginning with `@`.\n */\nexport function hasUnresolvedTokens(filter) {\n\tif (!filter || typeof filter !== 'object') {\n\t\treturn false\n\t}\n\tconst blocking = (x) => typeof x === 'string' && x.charAt(0) === '@' && !isOptionalUnresolved(x)\n\tconst anyBlocking = (v) => {\n\t\tif (blocking(v)) {\n\t\t\treturn true\n\t\t}\n\t\tif (Array.isArray(v)) {\n\t\t\treturn v.some(blocking)\n\t\t}\n\t\treturn false\n\t}\n\tfor (const v of Object.values(filter)) {\n\t\tif (anyBlocking(v)) {\n\t\t\treturn true\n\t\t}\n\t\tif (v && typeof v === 'object' && !Array.isArray(v)) {\n\t\t\tfor (const ov of Object.values(v)) {\n\t\t\t\tif (anyBlocking(ov)) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n/**\n * Resolve every dynamic `@`-token in a filter map (equality, operator, and\n * IN-list array shapes). Array values resolve item-by-item so a list can mix\n * tokens and literals (`{ assignee: ['@me', 'shared'] }`).\n *\n * @param {object} filter The filter map (`{ field: value | value[] | { op: value | value[] } }`).\n * @param {{objectId?: (string|number), object?: object, workspace?: object, config?: object, range?: object}} [ctx] Optional\n * context forwarded to {@link resolveFilterValue} for `@objectId` /\n * `@object.<field>` / `@workspace.<key>` / `@config.<key>` tokens.\n * @return {object} A new filter map with tokens resolved.\n */\nexport function resolveFilterTokens(filter, ctx) {\n\tif (!filter || typeof filter !== 'object') {\n\t\treturn filter\n\t}\n\t// An item that resolves to a list (`@myGroups`) is spread in place, so\n\t// `['@myGroups', 'archive']` stays one flat IN list.\n\tconst resolveOne = (v) => (Array.isArray(v)\n\t\t? v.flatMap((item) => {\n\t\t\t\tconst r = resolveFilterValue(item, ctx)\n\t\t\t\treturn Array.isArray(r) ? r : [r]\n\t\t\t})\n\t\t: resolveFilterValue(v, ctx))\n\tconst out = {}\n\tfor (const [k, v] of Object.entries(filter)) {\n\t\tif (v && typeof v === 'object' && !Array.isArray(v)) {\n\t\t\tconst inner = {}\n\t\t\tfor (const [op, ov] of Object.entries(v)) {\n\t\t\t\tinner[op] = resolveOne(ov)\n\t\t\t}\n\t\t\tout[k] = inner\n\t\t} else {\n\t\t\tout[k] = resolveOne(v)\n\t\t}\n\t}\n\treturn out\n}\n\n/**\n * Recursively resolve `@`-tokens inside an arbitrary JSON value — objects\n * and arrays at ANY depth, unlike {@link resolveFilterTokens} which only\n * understands the flat filter-map shape (equality / operator / IN-list, one\n * level of nesting). Used by the `api-call` action's `payload`: a\n * Filinq-style generation body like\n * `{ dataRefs: [{ register: 'crm', schema: 'lead', id: '@objectId' }] }`\n * needs the token resolved inside a nested array of objects, which the\n * shallow filter-map resolver can't reach.\n *\n * @param {unknown} value The value to resolve — object / array / primitive.\n * @param {{objectId?: (string|number), object?: object, workspace?: object, config?: object, range?: object}} [ctx] Optional\n * context forwarded to {@link resolveFilterValue}.\n * @return {unknown} A new value with every string leaf token-resolved (structure preserved).\n */\nexport function resolveDeepTokens(value, ctx) {\n\tif (Array.isArray(value)) {\n\t\treturn value.map((v) => resolveDeepTokens(v, ctx))\n\t}\n\tif (value && typeof value === 'object') {\n\t\tconst out = {}\n\t\tfor (const [k, v] of Object.entries(value)) {\n\t\t\tout[k] = resolveDeepTokens(v, ctx)\n\t\t}\n\t\treturn out\n\t}\n\treturn resolveFilterValue(value, ctx)\n}\n\n/**\n * Deep-tree counterpart to {@link hasUnresolvedTokens}: whether an\n * already-{@link resolveDeepTokens}-resolved value still carries an\n * unresolved, non-optional `@`-token anywhere inside it (any depth). Used to\n * BLOCK an `api-call` whose `payload` has a required token (typically\n * `@objectId` / `@object.<field>`) that failed to resolve, instead of\n * silently sending the literal token string to the server.\n *\n * @param {unknown} value The (already deep-resolved) value.\n * @return {boolean} True when a required token is still unresolved.\n */\nexport function hasUnresolvedDeepTokens(value) {\n\tif (Array.isArray(value)) {\n\t\treturn value.some(hasUnresolvedDeepTokens)\n\t}\n\tif (value && typeof value === 'object') {\n\t\treturn Object.values(value).some(hasUnresolvedDeepTokens)\n\t}\n\treturn typeof value === 'string' && value.charAt(0) === '@' && !isOptionalUnresolved(value)\n}\n\n/**\n * Deep-tree counterpart to {@link dropOptionalUnresolved}: strips unresolved\n * OPTIONAL tokens (`@workspace.<key>?` / `@config.<key>?` left as-is after\n * resolution) out of an arbitrary JSON value at any depth. An object key\n * whose value is an unresolved optional token is dropped entirely; an array\n * item that resolves unresolved-optional is filtered out.\n *\n * @param {unknown} value The (already {@link resolveDeepTokens}-resolved) value.\n * @return {unknown} The value with unresolved-optional leaves removed.\n */\nexport function dropOptionalUnresolvedDeep(value) {\n\tif (Array.isArray(value)) {\n\t\treturn value\n\t\t\t.filter((v) => !isOptionalUnresolved(v))\n\t\t\t.map((v) => dropOptionalUnresolvedDeep(v))\n\t}\n\tif (value && typeof value === 'object') {\n\t\tconst out = {}\n\t\tfor (const [k, v] of Object.entries(value)) {\n\t\t\tif (isOptionalUnresolved(v)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tout[k] = dropOptionalUnresolvedDeep(v)\n\t\t}\n\t\treturn out\n\t}\n\treturn value\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -56,6 +56,8 @@ const SENTINEL_RESOLVERS = Object.freeze({
|
|
|
56
56
|
declarative: 'fetchAggregate (OpenRegister server-side)',
|
|
57
57
|
visibleWhen: 'evaluateVisibleWhen (utils/visibleWhen.js)',
|
|
58
58
|
answer: 'resolveAssignValue (utils/formAssign.js)',
|
|
59
|
+
row: 'CnFieldListWidget (rowActions request)',
|
|
60
|
+
subject: 'CnBannerWidget (action route)',
|
|
59
61
|
});
|
|
60
62
|
|
|
61
63
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveManifestTokens.js","sources":["../../../src/utils/resolveManifestTokens.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Single documented resolver-dispatch entrypoint for the closed sentinel-token\n * vocabulary. Before this module the four resolvers were invoked ad-hoc by\n * callers who had to know which resolver owned which token. This entrypoint\n * routes a token to its owning resolver BY CONTEXT (see\n * {@link module:utils/sentinelTokens}) so the vocabulary cannot drift between\n * the schema, the runtime, and the gate.\n *\n * It does NOT re-implement resolution: it composes the existing resolver\n * functions verbatim —\n *\n * - route context → {@link resolveRouteSentinels} (reference-preserving walk +\n * per-page unresolved warning);\n * - filter / object / workspace / config(@config.<key>) contexts →\n * {@link resolveFilterValue} per leaf (identical behaviour to\n * resolveFilterTokens, which is that function applied over a filter map);\n * - config `@resolve:<key>` context → handled at manifest-LOAD time by\n * `resolveManifestSentinels` (async IAppConfig), so a render-time\n * subtree pass leaves it untouched — matching today's split;\n * - declarative `@self`/`@ref`/`@aggregate` → resolved server-side by\n * OpenRegister, so they pass through the client walk untouched;\n * - visibleWhen `@total` (manifest-form-logic) → resolved by\n * {@link module:utils/visibleWhen}'s own `evaluateVisibleWhen` /\n * `evaluateVisibleWhenLocal`, not by this generic subtree walk, so it\n * likewise passes through untouched.\n *\n * On top it adds the migration-window behaviour the vocabulary requires: a\n * one-time `console.warn` naming the canonical replacement whenever a\n * DEPRECATED token is encountered.\n *\n * @module utils/resolveManifestTokens\n */\n\nimport { isOptionalUnresolved, resolveFilterValue } from './resolveFilterTokens.js'\nimport resolveRouteSentinels from './resolveRouteSentinels.js'\nimport { contextOf, looksLikeSentinel, matchDeprecation, SENTINEL_CONTEXTS } from './sentinelTokens.js'\n\n/**\n * Dispatch table: canonical context → the resolver that owns it. Documentation\n * + programmatic lookup; the actual routing in {@link resolveManifestSubtree}\n * uses the same source-of-truth classifier ({@link contextOf}).\n *\n * @type {Readonly<Record<string, string>>}\n */\nexport const SENTINEL_RESOLVERS = Object.freeze({\n\tfilter: 'resolveFilterTokens',\n\tconfig: 'resolveManifestSentinels / resolveFilterTokens',\n\tobject: 'resolveFilterTokens',\n\tworkspace: 'resolveFilterTokens',\n\troute: 'resolveRouteSentinels',\n\tdeclarative: 'fetchAggregate (OpenRegister server-side)',\n\tvisibleWhen: 'evaluateVisibleWhen (utils/visibleWhen.js)',\n\tanswer: 'resolveAssignValue (utils/formAssign.js)',\n})\n\n/**\n * Process-wide set of deprecated tokens already warned about, so the same\n * deprecated token referenced from many pages warns once. Cleared by\n * {@link clearDeprecationWarnings} (test-only).\n *\n * @type {Set<string>}\n */\nconst _warnedDeprecations = new Set()\n\n/**\n * Reset the one-time deprecation-warning dedup set. Test-only.\n *\n * @return {void}\n */\nexport function clearDeprecationWarnings() {\n\t_warnedDeprecations.clear()\n}\n\n/**\n * Emit a one-time deprecation warning for `token` if it is on the deprecation\n * map. Idempotent per token for the process lifetime.\n *\n * @param {string} token The `@`-prefixed token.\n * @param {(...args: unknown[]) => void} warn console.warn override (tests).\n * @return {boolean} True when the token is deprecated (regardless of whether a\n * warning was emitted this call).\n */\nexport function warnIfDeprecated(token, warn) {\n\tconst dep = matchDeprecation(token)\n\tif (!dep) {\n\t\treturn false\n\t}\n\tif (!_warnedDeprecations.has(token)) {\n\t\t_warnedDeprecations.add(token)\n\t\tconst target = dep.replacement\n\t\t\t? `use '${dep.replacement}' instead`\n\t\t\t: 'it will be removed with no direct replacement'\n\t\twarn(`[resolveManifestTokens] Deprecated sentinel token '${token}' — ${target} `\n\t\t\t+ `(removal ${dep.removal}). ${dep.note}`)\n\t}\n\treturn true\n}\n\n/**\n * Resolve every sentinel token in a manifest subtree through the single\n * dispatch, routing each by context to its existing resolver.\n *\n * Behaviour is the composition of the existing resolvers, so this is a\n * drop-in for a caller that previously ran `resolveRouteSentinels` then a\n * `resolveFilterTokens`-style pass by hand.\n *\n * @param {unknown} value The subtree (typically a `pages[].config` block). Not mutated.\n * @param {object} [opts] Resolution inputs.\n * @param {object} [opts.params] vue-router params for `@route.<param>`.\n * @param {{objectId?: (string|number), object?: object, workspace?: object, config?: object}} [opts.ctx]\n * Context forwarded to {@link resolveFilterValue} for filter / object /\n * workspace / `@config.<key>` tokens.\n * @param {string} [opts.pageId] Page id for route-resolver warning dedup.\n * @param {(...args: unknown[]) => void} [opts.warn] console.warn override (tests).\n * @return {{value: unknown, unresolved: string[]}} The resolved subtree plus the\n * list of tokens that stayed unresolved (excluding OPTIONAL `?` tokens, which\n * are meant to be dropped, not waited on).\n */\nexport function resolveManifestSubtree(value, opts = {}) {\n\tconst params = opts.params && typeof opts.params === 'object' ? opts.params : {}\n\tconst ctx = opts.ctx && typeof opts.ctx === 'object' ? opts.ctx : {}\n\tconst pageId = opts.pageId || '<unknown>'\n\tconst warn = opts.warn ?? ((...args) => {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.warn(...args)\n\t})\n\n\t// Pass 1: route context (reuses the reference-preserving route walker,\n\t// including its own per-page unresolved warning).\n\tconst afterRoute = resolveRouteSentinels(value, params, pageId)\n\n\t// Pass 2: filter / object / workspace / @config.<key> contexts, per leaf,\n\t// via the existing resolveFilterValue. Deprecated tokens warn once.\n\tconst unresolved = []\n\tconst walk = (node) => {\n\t\tif (typeof node === 'string') {\n\t\t\tif (!looksLikeSentinel(node)) {\n\t\t\t\treturn node\n\t\t\t}\n\t\t\twarnIfDeprecated(node, warn)\n\t\t\tconst resolved = resolveFilterValue(node, ctx)\n\t\t\tif (resolved === node && !isOptionalUnresolved(node)) {\n\t\t\t\t// Still a raw token: unresolved unless it is a load-time / server-side\n\t\t\t\t// context this render-pass deliberately leaves alone.\n\t\t\t\tconst c = contextOf(node)\n\t\t\t\tif (c !== 'config' && c !== 'declarative' && c !== 'visibleWhen') {\n\t\t\t\t\tunresolved.push(node)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn resolved\n\t\t}\n\t\tif (Array.isArray(node)) {\n\t\t\treturn node.map(walk)\n\t\t}\n\t\tif (node && typeof node === 'object') {\n\t\t\tconst out = {}\n\t\t\tfor (const [k, v] of Object.entries(node)) {\n\t\t\t\tout[k] = walk(v)\n\t\t\t}\n\t\t\treturn out\n\t\t}\n\t\treturn node\n\t}\n\n\treturn { value: walk(afterRoute), unresolved }\n}\n\nexport { SENTINEL_CONTEXTS }\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resolveManifestTokens.js","sources":["../../../src/utils/resolveManifestTokens.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Single documented resolver-dispatch entrypoint for the closed sentinel-token\n * vocabulary. Before this module the four resolvers were invoked ad-hoc by\n * callers who had to know which resolver owned which token. This entrypoint\n * routes a token to its owning resolver BY CONTEXT (see\n * {@link module:utils/sentinelTokens}) so the vocabulary cannot drift between\n * the schema, the runtime, and the gate.\n *\n * It does NOT re-implement resolution: it composes the existing resolver\n * functions verbatim —\n *\n * - route context → {@link resolveRouteSentinels} (reference-preserving walk +\n * per-page unresolved warning);\n * - filter / object / workspace / config(@config.<key>) contexts →\n * {@link resolveFilterValue} per leaf (identical behaviour to\n * resolveFilterTokens, which is that function applied over a filter map);\n * - config `@resolve:<key>` context → handled at manifest-LOAD time by\n * `resolveManifestSentinels` (async IAppConfig), so a render-time\n * subtree pass leaves it untouched — matching today's split;\n * - declarative `@self`/`@ref`/`@aggregate` → resolved server-side by\n * OpenRegister, so they pass through the client walk untouched;\n * - visibleWhen `@total` (manifest-form-logic) → resolved by\n * {@link module:utils/visibleWhen}'s own `evaluateVisibleWhen` /\n * `evaluateVisibleWhenLocal`, not by this generic subtree walk, so it\n * likewise passes through untouched.\n *\n * On top it adds the migration-window behaviour the vocabulary requires: a\n * one-time `console.warn` naming the canonical replacement whenever a\n * DEPRECATED token is encountered.\n *\n * @module utils/resolveManifestTokens\n */\n\nimport { isOptionalUnresolved, resolveFilterValue } from './resolveFilterTokens.js'\nimport resolveRouteSentinels from './resolveRouteSentinels.js'\nimport { contextOf, looksLikeSentinel, matchDeprecation, SENTINEL_CONTEXTS } from './sentinelTokens.js'\n\n/**\n * Dispatch table: canonical context → the resolver that owns it. Documentation\n * + programmatic lookup; the actual routing in {@link resolveManifestSubtree}\n * uses the same source-of-truth classifier ({@link contextOf}).\n *\n * @type {Readonly<Record<string, string>>}\n */\nexport const SENTINEL_RESOLVERS = Object.freeze({\n\tfilter: 'resolveFilterTokens',\n\tconfig: 'resolveManifestSentinels / resolveFilterTokens',\n\tobject: 'resolveFilterTokens',\n\tworkspace: 'resolveFilterTokens',\n\troute: 'resolveRouteSentinels',\n\tdeclarative: 'fetchAggregate (OpenRegister server-side)',\n\tvisibleWhen: 'evaluateVisibleWhen (utils/visibleWhen.js)',\n\tanswer: 'resolveAssignValue (utils/formAssign.js)',\n\trow: 'CnFieldListWidget (rowActions request)',\n\tsubject: 'CnBannerWidget (action route)',\n})\n\n/**\n * Process-wide set of deprecated tokens already warned about, so the same\n * deprecated token referenced from many pages warns once. Cleared by\n * {@link clearDeprecationWarnings} (test-only).\n *\n * @type {Set<string>}\n */\nconst _warnedDeprecations = new Set()\n\n/**\n * Reset the one-time deprecation-warning dedup set. Test-only.\n *\n * @return {void}\n */\nexport function clearDeprecationWarnings() {\n\t_warnedDeprecations.clear()\n}\n\n/**\n * Emit a one-time deprecation warning for `token` if it is on the deprecation\n * map. Idempotent per token for the process lifetime.\n *\n * @param {string} token The `@`-prefixed token.\n * @param {(...args: unknown[]) => void} warn console.warn override (tests).\n * @return {boolean} True when the token is deprecated (regardless of whether a\n * warning was emitted this call).\n */\nexport function warnIfDeprecated(token, warn) {\n\tconst dep = matchDeprecation(token)\n\tif (!dep) {\n\t\treturn false\n\t}\n\tif (!_warnedDeprecations.has(token)) {\n\t\t_warnedDeprecations.add(token)\n\t\tconst target = dep.replacement\n\t\t\t? `use '${dep.replacement}' instead`\n\t\t\t: 'it will be removed with no direct replacement'\n\t\twarn(`[resolveManifestTokens] Deprecated sentinel token '${token}' — ${target} `\n\t\t\t+ `(removal ${dep.removal}). ${dep.note}`)\n\t}\n\treturn true\n}\n\n/**\n * Resolve every sentinel token in a manifest subtree through the single\n * dispatch, routing each by context to its existing resolver.\n *\n * Behaviour is the composition of the existing resolvers, so this is a\n * drop-in for a caller that previously ran `resolveRouteSentinels` then a\n * `resolveFilterTokens`-style pass by hand.\n *\n * @param {unknown} value The subtree (typically a `pages[].config` block). Not mutated.\n * @param {object} [opts] Resolution inputs.\n * @param {object} [opts.params] vue-router params for `@route.<param>`.\n * @param {{objectId?: (string|number), object?: object, workspace?: object, config?: object}} [opts.ctx]\n * Context forwarded to {@link resolveFilterValue} for filter / object /\n * workspace / `@config.<key>` tokens.\n * @param {string} [opts.pageId] Page id for route-resolver warning dedup.\n * @param {(...args: unknown[]) => void} [opts.warn] console.warn override (tests).\n * @return {{value: unknown, unresolved: string[]}} The resolved subtree plus the\n * list of tokens that stayed unresolved (excluding OPTIONAL `?` tokens, which\n * are meant to be dropped, not waited on).\n */\nexport function resolveManifestSubtree(value, opts = {}) {\n\tconst params = opts.params && typeof opts.params === 'object' ? opts.params : {}\n\tconst ctx = opts.ctx && typeof opts.ctx === 'object' ? opts.ctx : {}\n\tconst pageId = opts.pageId || '<unknown>'\n\tconst warn = opts.warn ?? ((...args) => {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.warn(...args)\n\t})\n\n\t// Pass 1: route context (reuses the reference-preserving route walker,\n\t// including its own per-page unresolved warning).\n\tconst afterRoute = resolveRouteSentinels(value, params, pageId)\n\n\t// Pass 2: filter / object / workspace / @config.<key> contexts, per leaf,\n\t// via the existing resolveFilterValue. Deprecated tokens warn once.\n\tconst unresolved = []\n\tconst walk = (node) => {\n\t\tif (typeof node === 'string') {\n\t\t\tif (!looksLikeSentinel(node)) {\n\t\t\t\treturn node\n\t\t\t}\n\t\t\twarnIfDeprecated(node, warn)\n\t\t\tconst resolved = resolveFilterValue(node, ctx)\n\t\t\tif (resolved === node && !isOptionalUnresolved(node)) {\n\t\t\t\t// Still a raw token: unresolved unless it is a load-time / server-side\n\t\t\t\t// context this render-pass deliberately leaves alone.\n\t\t\t\tconst c = contextOf(node)\n\t\t\t\tif (c !== 'config' && c !== 'declarative' && c !== 'visibleWhen') {\n\t\t\t\t\tunresolved.push(node)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn resolved\n\t\t}\n\t\tif (Array.isArray(node)) {\n\t\t\treturn node.map(walk)\n\t\t}\n\t\tif (node && typeof node === 'object') {\n\t\t\tconst out = {}\n\t\t\tfor (const [k, v] of Object.entries(node)) {\n\t\t\t\tout[k] = walk(v)\n\t\t\t}\n\t\t\treturn out\n\t\t}\n\t\treturn node\n\t}\n\n\treturn { value: walk(afterRoute), unresolved }\n}\n\nexport { SENTINEL_CONTEXTS }\n"],"names":[],"mappings":"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"}
|