@conduction/nextcloud-vue 2.104.0 → 2.106.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +18 -0
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +18 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +93 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +6 -3
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +3 -3
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +112 -5
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js +9 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js.map +1 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js +24 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js.map +1 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js +49 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js.map +1 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +36 -18
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +17 -38
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- 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/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +2 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +32 -29
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +53 -2
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +119 -111
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue.js +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +62 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +73 -4
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +74 -27
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +208 -46
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +121 -98
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +24 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +20 -4
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +25 -6
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +112 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +100 -44
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +67 -4
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js +9 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js.map +1 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js +88 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js +39 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +295 -12
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +267 -144
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +4 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +3 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +93 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +322 -246
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue.js +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +122 -7
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +189 -93
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +1 -1
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +3 -3
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +16 -4
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +42 -0
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +38 -19
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js +9 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js.map +1 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js +258 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js +69 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +11 -0
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -4
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +16 -14
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +24 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +24 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +1369 -748
- package/dist/esm/utils/dueText.js +18 -3
- package/dist/esm/utils/dueText.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +2 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/nextcloudApps.js +45 -0
- package/dist/esm/utils/nextcloudApps.js.map +1 -0
- package/dist/esm/utils/scopeListLayout.js +9 -2
- package/dist/esm/utils/scopeListLayout.js.map +1 -1
- package/dist/esm/utils/sortOrder.js +11 -1
- package/dist/esm/utils/sortOrder.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/visibleWhen.js +12 -4
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +8965 -6497
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1369 -748
- package/l10n/en.json +24 -1
- package/l10n/nl.json +24 -1
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -1
- package/src/components/CnCardGrid/CnCardGrid.md +2 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +20 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +19 -1
- package/src/components/CnChartWidget/CnChartWidget.md +2 -0
- package/src/components/CnChartWidget/CnChartWidget.vue +93 -1
- package/src/components/CnDashboardPage/CnDashboardPage.vue +9 -6
- package/src/components/CnDetailPage/CnDetailPage.md +3 -2
- package/src/components/CnDetailPage/CnDetailPage.vue +112 -5
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue +60 -0
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +36 -29
- package/src/components/CnFactsWidget/CnFactsWidget.vue +4 -0
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -1
- package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +4 -1
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +70 -4
- package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +62 -1
- package/src/components/CnFlowsPage/CnFlowBoard.vue +132 -8
- package/src/components/CnIndexPage/CnIndexPage.md +8 -5
- package/src/components/CnIndexPage/CnIndexPage.vue +236 -54
- package/src/components/CnIndexPage/CnIndexPageWidgets.vue +36 -1
- package/src/components/CnIndexPage/useSelfFetchList.js +25 -6
- package/src/components/CnIndexSidebar/CnIndexSidebar.md +2 -0
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +148 -1
- package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +68 -5
- package/src/components/CnNoticeWidget/CnNoticeWidget.vue +153 -0
- package/src/components/CnNoticeWidget/index.js +12 -0
- package/src/components/CnObjectCard/CnObjectCard.md +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +354 -13
- package/src/components/CnObjectSidebar/CnFilesTab.vue +6 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +190 -4
- package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +209 -9
- package/src/components/CnSettingsField/CnSettingsField.vue +4 -1
- package/src/components/CnSetupWizard/CnSetupWizard.vue +9 -1
- package/src/components/CnTabs/CnTabs.vue +11 -0
- package/src/components/CnTabsWidget/CnTabsWidget.vue +91 -18
- package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.md +11 -0
- package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue +382 -0
- package/src/components/CnTeamWorkloadWidget/index.js +14 -0
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +15 -4
- package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
- package/src/components/index.js +2 -0
- package/src/css/dialog.css +20 -2
- package/src/css/index-page.css +14 -2
- package/src/css/look-board-detail.css +125 -8
- package/src/css/look-board-index.css +100 -4
- package/src/css/look-board.css +122 -14
- package/src/index.js +2 -1
- package/src/schemas/app-manifest-v2.schema.json +110 -8
- package/src/utils/dueText.js +18 -3
- package/src/utils/libraryWidgetKeys.js +2 -0
- package/src/utils/nextcloudApps.js +50 -0
- package/src/utils/scopeListLayout.js +9 -2
- package/src/utils/sortOrder.js +10 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +12 -4
|
@@ -30,7 +30,8 @@ import { parseDateValue } from './dateVariant.js';
|
|
|
30
30
|
* `upcomingWeeksLabel` `{ one, other }` (from `upcomingWeeksFrom` days left,
|
|
31
31
|
* default 35, it reads the weeks left, rounded). Texts go through `translate`
|
|
32
32
|
* before `{count}` is filled. Tones: `danger` (overdue), `today`, `upcoming`,
|
|
33
|
-
* `empty`.
|
|
33
|
+
* `empty`. With `soonWithin` (days), today and a date at most that many days
|
|
34
|
+
* ahead also carry `soon: true`, drawn in the warning colour.
|
|
34
35
|
*
|
|
35
36
|
* @param {unknown} value A Date, a timestamp or an ISO string.
|
|
36
37
|
* @param {object} [config] The `due` config.
|
|
@@ -55,9 +56,23 @@ function resolveDue(value, config, translate$1 = (text) => text, now = new Date(
|
|
|
55
56
|
}
|
|
56
57
|
if (days === 0) {
|
|
57
58
|
const label = typeof cfg.todayLabel === 'string' && cfg.todayLabel !== '' ? translate$1(cfg.todayLabel) : translate('nextcloud-vue', 'Due today');
|
|
58
|
-
return { text: label, tone: 'today' }
|
|
59
|
+
return withSoon({ text: label, tone: 'today' }, 0, cfg)
|
|
59
60
|
}
|
|
60
|
-
return { text: upcomingText(-days, cfg, translate$1) ?? formatRelativeDate(date, { now }), tone: 'upcoming' }
|
|
61
|
+
return withSoon({ text: upcomingText(-days, cfg, translate$1) ?? formatRelativeDate(date, { now }), tone: 'upcoming' }, -days, cfg)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Marks a due date within `soonWithin` days as soon.
|
|
66
|
+
*
|
|
67
|
+
* @param {{text: string, tone: string}} result The due text and tone.
|
|
68
|
+
* @param {number} left Whole days left, 0 for today.
|
|
69
|
+
* @param {object} cfg The `due` config.
|
|
70
|
+
* @return {{text: string, tone: string, soon?: boolean}} The result, with `soon: true` when it is due soon.
|
|
71
|
+
* @spec openspec/changes/due-text-soon/specs/due-text-soon/spec.md#requirement-a-due-date-within-a-few-days-reads-as-soon
|
|
72
|
+
*/
|
|
73
|
+
function withSoon(result, left, cfg) {
|
|
74
|
+
const within = Number(cfg.soonWithin);
|
|
75
|
+
return Number.isFinite(within) && within >= 0 && left <= within ? { ...result, soon: true } : result
|
|
61
76
|
}
|
|
62
77
|
|
|
63
78
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dueText.js","sources":["../../../src/utils/dueText.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * dueText: the text of a due date and the tone it is drawn in.\n *\n * \"3 dagen te laat\" in the danger colour, \"Vandaag\", the short date for a\n * day still to come (\"morgen\", \"20 okt\"), and a configured text when the\n * field is empty (\"Geen deadline\"). A day to come can read as a count instead\n * (\"nog 4 dagen\", \"nog 8 weken\"). One formatter for record-list rows and\n * table cells, built on the relative date (`formatRelativeDate`).\n *\n * @module utils/dueText\n * @spec openspec/changes/screens-due-text/specs/due-text/spec.md#requirement-a-due-date-reads-as-text-with-a-tone\n */\n\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { ageInDays, formatRelativeDate } from './boardDate.js'\nimport { parseDateValue } from './dateVariant.js'\n\n/**\n * The text and tone of a due date.\n *\n * Config (all optional): `todayLabel` (default \"Due today\"), `overdueLabel`\n * `{ one, other }` with `{count}` (default \"{count} day overdue\" / \"{count} days\n * overdue\"), `emptyText` (shown when the field is empty; without it an empty\n * field reads as nothing), `upcomingLabel` `{ one, other }` with `{count}`\n * (a day to come reads as the days left instead of the date), and\n * `upcomingWeeksLabel` `{ one, other }` (from `upcomingWeeksFrom` days left,\n * default 35, it reads the weeks left, rounded). Texts go through `translate`\n * before `{count}` is filled. Tones: `danger` (overdue), `today`, `upcoming`,\n * `empty
|
|
1
|
+
{"version":3,"file":"dueText.js","sources":["../../../src/utils/dueText.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * dueText: the text of a due date and the tone it is drawn in.\n *\n * \"3 dagen te laat\" in the danger colour, \"Vandaag\", the short date for a\n * day still to come (\"morgen\", \"20 okt\"), and a configured text when the\n * field is empty (\"Geen deadline\"). A day to come can read as a count instead\n * (\"nog 4 dagen\", \"nog 8 weken\"). One formatter for record-list rows and\n * table cells, built on the relative date (`formatRelativeDate`).\n *\n * @module utils/dueText\n * @spec openspec/changes/screens-due-text/specs/due-text/spec.md#requirement-a-due-date-reads-as-text-with-a-tone\n */\n\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { ageInDays, formatRelativeDate } from './boardDate.js'\nimport { parseDateValue } from './dateVariant.js'\n\n/**\n * The text and tone of a due date.\n *\n * Config (all optional): `todayLabel` (default \"Due today\"), `overdueLabel`\n * `{ one, other }` with `{count}` (default \"{count} day overdue\" / \"{count} days\n * overdue\"), `emptyText` (shown when the field is empty; without it an empty\n * field reads as nothing), `upcomingLabel` `{ one, other }` with `{count}`\n * (a day to come reads as the days left instead of the date), and\n * `upcomingWeeksLabel` `{ one, other }` (from `upcomingWeeksFrom` days left,\n * default 35, it reads the weeks left, rounded). Texts go through `translate`\n * before `{count}` is filled. Tones: `danger` (overdue), `today`, `upcoming`,\n * `empty`. With `soonWithin` (days), today and a date at most that many days\n * ahead also carry `soon: true`, drawn in the warning colour.\n *\n * @param {unknown} value A Date, a timestamp or an ISO string.\n * @param {object} [config] The `due` config.\n * @param {(text: string) => string} [translate] The host's translate.\n * @param {Date} [now] The reference moment.\n * @return {{text: string, tone: string}|null} The text and tone, or null when there is nothing to show.\n */\nexport function resolveDue(value, config, translate = (text) => text, now = new Date()) {\n\tconst cfg = config && typeof config === 'object' ? config : {}\n\tconst date = parseDateValue(value)\n\tif (date === null) {\n\t\tconst empty = typeof cfg.emptyText === 'string' && cfg.emptyText !== '' ? translate(cfg.emptyText) : ''\n\t\treturn empty === '' ? null : { text: empty, tone: 'empty' }\n\t}\n\tconst days = ageInDays(date, now)\n\tif (days > 0) {\n\t\tconst overdue = pluralLabel(cfg.overdueLabel, days, translate)\n\t\tif (overdue !== null) {\n\t\t\treturn { text: overdue, tone: 'danger' }\n\t\t}\n\t\treturn { text: n('nextcloud-vue', '{count} day overdue', '{count} days overdue', days, { count: days }), tone: 'danger' }\n\t}\n\tif (days === 0) {\n\t\tconst label = typeof cfg.todayLabel === 'string' && cfg.todayLabel !== '' ? translate(cfg.todayLabel) : t('nextcloud-vue', 'Due today')\n\t\treturn withSoon({ text: label, tone: 'today' }, 0, cfg)\n\t}\n\treturn withSoon({ text: upcomingText(-days, cfg, translate) ?? formatRelativeDate(date, { now }), tone: 'upcoming' }, -days, cfg)\n}\n\n/**\n * Marks a due date within `soonWithin` days as soon.\n *\n * @param {{text: string, tone: string}} result The due text and tone.\n * @param {number} left Whole days left, 0 for today.\n * @param {object} cfg The `due` config.\n * @return {{text: string, tone: string, soon?: boolean}} The result, with `soon: true` when it is due soon.\n * @spec openspec/changes/due-text-soon/specs/due-text-soon/spec.md#requirement-a-due-date-within-a-few-days-reads-as-soon\n */\nfunction withSoon(result, left, cfg) {\n\tconst within = Number(cfg.soonWithin)\n\treturn Number.isFinite(within) && within >= 0 && left <= within ? { ...result, soon: true } : result\n}\n\n/**\n * The days or weeks left before a date to come, or null without `upcomingLabel`.\n *\n * @param {number} left Whole days left, at least 1.\n * @param {object} cfg The `due` config.\n * @param {(text: string) => string} translate The host's translate.\n * @return {string|null} The text, or null.\n * @spec openspec/changes/due-text-upcoming-count/specs/due-text-upcoming-count/spec.md#requirement-a-due-date-to-come-can-read-as-the-time-left\n */\nfunction upcomingText(left, cfg, translate) {\n\tconst from = Number(cfg.upcomingWeeksFrom)\n\tconst weeksFrom = Number.isFinite(from) && from > 0 ? from : 35\n\tif (left >= weeksFrom) {\n\t\tconst weeks = pluralLabel(cfg.upcomingWeeksLabel, Math.round(left / 7), translate)\n\t\tif (weeks !== null) {\n\t\t\treturn weeks\n\t\t}\n\t}\n\treturn pluralLabel(cfg.upcomingLabel, left, translate)\n}\n\n/**\n * A `{ one, other }` label with `{count}` filled, or null when none is configured.\n *\n * @param {unknown} labels The configured label pair.\n * @param {number} count The count.\n * @param {(text: string) => string} translate The host's translate.\n * @return {string|null} The text, or null.\n */\nfunction pluralLabel(labels, count, translate) {\n\tif (!labels || typeof labels !== 'object' || !(labels.one || labels.other)) {\n\t\treturn null\n\t}\n\tconst template = translate((count === 1 ? labels.one : labels.other) || labels.other || labels.one)\n\treturn template.replace(/\\{count\\}/g, String(count))\n}\n"],"names":["translate","n","t"],"mappings":"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"}
|
|
@@ -59,6 +59,7 @@ const BUILT_IN_WIDGET_KEYS = Object.freeze([
|
|
|
59
59
|
'object-table',
|
|
60
60
|
'form-renderer',
|
|
61
61
|
'map-viewer',
|
|
62
|
+
'notice',
|
|
62
63
|
'object-geo',
|
|
63
64
|
'card-grid',
|
|
64
65
|
'nav-card-grid',
|
|
@@ -150,6 +151,7 @@ const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
|
|
|
150
151
|
'table',
|
|
151
152
|
'tabs',
|
|
152
153
|
'tasks',
|
|
154
|
+
'team-workload',
|
|
153
155
|
'text',
|
|
154
156
|
'tile',
|
|
155
157
|
'timeline',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"libraryWidgetKeys.js","sources":["../../../src/utils/libraryWidgetKeys.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * libraryWidgetKeys — the one vocabulary of widget keys the LIBRARY itself\n * renders, and the only list `validateManifestV2` is allowed to consult.\n *\n * WHY THIS MODULE EXISTS\n * ----------------------\n * The validator used to carry its own hand-written array of \"library built-in\n * widget keys\", with a comment instructing whoever added a built-in to append\n * it \"in the same PR\". That instruction was followed for five keys and missed\n * for six, so the validator knew 11 keys while the library actually rendered\n * 45. Nothing failed when the two disagreed, because the only consumer is an\n * exemption: a key the validator does not recognise is treated as a CUSTOM\n * registry component, which makes a legitimate single-widget library dashboard\n * fail the ADR-036 Decision 1 anti-pattern rule with advice (\"declare it as\n * type:'custom'\") that is wrong for a library widget.\n *\n * A comment cannot enforce anything, so the array moved here and\n * `tests/utils/libraryWidgetKeys.spec.js` now binds it to the two registries\n * that actually resolve a key at runtime:\n *\n * 1. `BUILT_IN_WIDGETS` (components/CnWidgetGrid/builtInWidgets.js) — the v2\n * manifest widget set `CnWidgetGrid` resolves a `widgetKey` against.\n * 2. The dashboard widget catalog (`dashboardWidgetRegistry`), populated by\n * `registerDashboardWidgets.js`. `CnWidgetGrid` falls back to it (third in\n * the chain, after the consumer registry and `BUILT_IN_WIDGETS`), so a\n * catalog key renders on a v2 page exactly like a `BUILT_IN_WIDGETS` one.\n *\n * The test asserts SET EQUALITY in both directions against the live registries,\n * so adding a widget to either registry without touching this file fails CI,\n * and deleting one without removing it here fails too. The lists below are the\n * validator's view; the registries are the truth the test holds them to.\n *\n * THE CATALOG IS NOT LEGACY. It is a live half of the same vocabulary, and the\n * old array already conceded the point by exempting `chart` and `stats-block`,\n * which are catalog-only keys that `BUILT_IN_WIDGETS` has never held. What it\n * got wrong was doing that for two keys and not the other thirty-seven.\n * `widgetDispatch.js` unified the two resolution paths for the same reason: a\n * widget authored against one vocabulary was invisible to the other and said\n * nothing about why (101 of 236 detail widgets on hrmq).\n *\n * @module utils/libraryWidgetKeys\n */\n\nimport { WIDGET_TYPE_ALIASES } from './widgetTypeAliases.js'\n\n/**\n * Keys of `BUILT_IN_WIDGETS` — the v2 manifest widget set.\n *\n * Kept in the registry's own declaration order so a diff against\n * `builtInWidgets.js` reads straight down.\n *\n * @type {string[]} Frozen.\n */\nexport const BUILT_IN_WIDGET_KEYS = Object.freeze([\n\t'object-table',\n\t'form-renderer',\n\t'map-viewer',\n\t'object-geo',\n\t'card-grid',\n\t'nav-card-grid',\n\t'data',\n\t'metadata',\n\t'related',\n\t'integration',\n\t'banner',\n\t'audit-trail',\n\t'timeline',\n\t'header',\n\t'text',\n\t'divider',\n\t'saved-view-alert',\n\t'exports',\n])\n\n/**\n * Widget types the LIBRARY registers into the dashboard catalog at import time\n * (`registerDashboardWidgets.js` and the widget `index.js` modules it pulls in).\n *\n * Deliberately a static list rather than a read of `dashboardWidgetRegistry`:\n * that object is mutable and consumer-extensible, so deriving the exemption\n * from it at call time would let an app register its own full-page component in\n * the catalog and walk straight through the ADR-036 Decision 1 rule the\n * exemption exists to enforce. Only keys the library itself ships are exempt.\n *\n * Sorted, because unlike `BUILT_IN_WIDGETS` these registrations are spread over\n * ~30 files and have no meaningful declaration order.\n *\n * @type {string[]} Frozen.\n */\nexport const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([\n\t'audit-trail',\n\t'banner',\n\t'calendar',\n\t'chart',\n\t'checklist-run',\n\t'chips-field',\n\t'choice-picker',\n\t'container',\n\t'conversation',\n\t'countdown',\n\t'data',\n\t'delta',\n\t'device-frames',\n\t'divider',\n\t'document-review',\n\t'facts',\n\t'field-list',\n\t'files',\n\t'flow-runs',\n\t'gauge',\n\t'grouped-cards',\n\t'header',\n\t'image',\n\t'interaction-form',\n\t'kb-search',\n\t'label',\n\t'link',\n\t'links',\n\t'map',\n\t'markdown',\n\t'menu',\n\t'nc-widget',\n\t'next-step',\n\t'notepad',\n\t// `news` was here until the widget moved to LaunchPad, which owns the feed\n\t// fetching. An app that renders one now gets it from LaunchPad's registry.\n\t'object-geo',\n\t'object-list',\n\t'object-table',\n\t'party-actions',\n\t'people',\n\t'quicklinks',\n\t'radio-cards',\n\t'record-list',\n\t'related',\n\t'saved-view',\n\t'saved-views',\n\t'sections',\n\t'settings-form',\n\t'spend-analytics',\n\t'stacked-bar',\n\t'stages',\n\t'stat',\n\t'stats-block',\n\t'switch-rows',\n\t'table',\n\t'tabs',\n\t'tasks',\n\t'text',\n\t'tile',\n\t'timeline',\n\t'toggle-table',\n\t'video',\n\t'week-strip',\n\t'working-calendar',\n\t'workspace-filter',\n])\n\n/**\n * Expand a key set over `WIDGET_TYPE_ALIASES` so both spellings of an aliased\n * widget are present whenever either one is.\n *\n * `CnWidgetGrid` resolves a key and its canonical form (`object-table` ⇄\n * `table`, `map-viewer` ⇄ `map`), so both spellings render and both must be\n * exempt. Today each registry happens to hold one side of each pair, which is\n * exactly the accident that would silently stop being true when the next alias\n * lands.\n *\n * @param {Set<string>} keys The key set to expand, mutated in place.\n * @return {Set<string>} The same set.\n */\nfunction expandAliases(keys) {\n\tfor (const [alias, canonical] of Object.entries(WIDGET_TYPE_ALIASES)) {\n\t\tif (keys.has(alias) || keys.has(canonical)) {\n\t\t\tkeys.add(alias)\n\t\t\tkeys.add(canonical)\n\t\t}\n\t}\n\treturn keys\n}\n\n/**\n * Every widget key the library renders itself: both registries, plus both\n * spellings of every alias.\n *\n * A `widgetKey` in this list is served by a library `Cn*` SFC and does NOT\n * count as a custom registry component for the single-12×12-widget dashboard\n * rule (ADR-036 Decision 1).\n *\n * @type {string[]} Frozen.\n */\nexport const LIBRARY_WIDGET_KEYS = Object.freeze([\n\t...expandAliases(new Set([\n\t\t...BUILT_IN_WIDGET_KEYS,\n\t\t...DASHBOARD_CATALOG_WIDGET_KEYS,\n\t])),\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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC;AAClD,CAAC,cAAc;AACf,CAAC,eAAe;AAChB,CAAC,YAAY;AACb,CAAC,YAAY;AACb,CAAC,WAAW;AACZ,CAAC,eAAe;AAChB,CAAC,MAAM;AACP,CAAC,UAAU;AACX,CAAC,SAAS;AACV,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,aAAa;AACd,CAAC,UAAU;AACX,CAAC,QAAQ;AACT,CAAC,MAAM;AACP,CAAC,SAAS;AACV,CAAC,kBAAkB;AACnB,CAAC,SAAS;AACV,CAAC,EAAC;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,6BAA6B,GAAG,MAAM,CAAC,MAAM,CAAC;AAC3D,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,UAAU;AACX,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,aAAa;AACd,CAAC,eAAe;AAChB,CAAC,WAAW;AACZ,CAAC,cAAc;AACf,CAAC,WAAW;AACZ,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,SAAS;AACV,CAAC,iBAAiB;AAClB,CAAC,OAAO;AACR,CAAC,YAAY;AACb,CAAC,OAAO;AACR,CAAC,WAAW;AACZ,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,QAAQ;AACT,CAAC,OAAO;AACR,CAAC,kBAAkB;AACnB,CAAC,WAAW;AACZ,CAAC,OAAO;AACR,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,KAAK;AACN,CAAC,UAAU;AACX,CAAC,MAAM;AACP,CAAC,WAAW;AACZ,CAAC,WAAW;AACZ,CAAC,SAAS;AACV;AACA;AACA,CAAC,YAAY;AACb,CAAC,aAAa;AACd,CAAC,cAAc;AACf,CAAC,eAAe;AAChB,CAAC,QAAQ;AACT,CAAC,YAAY;AACb,CAAC,aAAa;AACd,CAAC,aAAa;AACd,CAAC,SAAS;AACV,CAAC,YAAY;AACb,CAAC,aAAa;AACd,CAAC,UAAU;AACX,CAAC,eAAe;AAChB,CAAC,iBAAiB;AAClB,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,MAAM;AACP,CAAC,aAAa;AACd,CAAC,aAAa;AACd,CAAC,OAAO;AACR,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,MAAM;AACP,CAAC,MAAM;AACP,CAAC,UAAU;AACX,CAAC,cAAc;AACf,CAAC,OAAO;AACR,CAAC,YAAY;AACb,CAAC,kBAAkB;AACnB,CAAC,kBAAkB;AACnB,CAAC,EAAC;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,aAAa,CAAC,IAAI,EAAE;AAC7B,CAAC,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE;AACvE,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE;AAC9C,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAC;AAClB,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAC;AACtB,GAAG;AACH,EAAE;AACF,CAAC,OAAO,IAAI;AACZ,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,mBAAmB,GAAG,MAAM,CAAC,MAAM,CAAC;AACjD,CAAC,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC;AAC1B,EAAE,GAAG,oBAAoB;AACzB,EAAE,GAAG,6BAA6B;AAClC,EAAE,CAAC,CAAC;AACJ,CAAC;;;;"}
|
|
1
|
+
{"version":3,"file":"libraryWidgetKeys.js","sources":["../../../src/utils/libraryWidgetKeys.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * libraryWidgetKeys — the one vocabulary of widget keys the LIBRARY itself\n * renders, and the only list `validateManifestV2` is allowed to consult.\n *\n * WHY THIS MODULE EXISTS\n * ----------------------\n * The validator used to carry its own hand-written array of \"library built-in\n * widget keys\", with a comment instructing whoever added a built-in to append\n * it \"in the same PR\". That instruction was followed for five keys and missed\n * for six, so the validator knew 11 keys while the library actually rendered\n * 45. Nothing failed when the two disagreed, because the only consumer is an\n * exemption: a key the validator does not recognise is treated as a CUSTOM\n * registry component, which makes a legitimate single-widget library dashboard\n * fail the ADR-036 Decision 1 anti-pattern rule with advice (\"declare it as\n * type:'custom'\") that is wrong for a library widget.\n *\n * A comment cannot enforce anything, so the array moved here and\n * `tests/utils/libraryWidgetKeys.spec.js` now binds it to the two registries\n * that actually resolve a key at runtime:\n *\n * 1. `BUILT_IN_WIDGETS` (components/CnWidgetGrid/builtInWidgets.js) — the v2\n * manifest widget set `CnWidgetGrid` resolves a `widgetKey` against.\n * 2. The dashboard widget catalog (`dashboardWidgetRegistry`), populated by\n * `registerDashboardWidgets.js`. `CnWidgetGrid` falls back to it (third in\n * the chain, after the consumer registry and `BUILT_IN_WIDGETS`), so a\n * catalog key renders on a v2 page exactly like a `BUILT_IN_WIDGETS` one.\n *\n * The test asserts SET EQUALITY in both directions against the live registries,\n * so adding a widget to either registry without touching this file fails CI,\n * and deleting one without removing it here fails too. The lists below are the\n * validator's view; the registries are the truth the test holds them to.\n *\n * THE CATALOG IS NOT LEGACY. It is a live half of the same vocabulary, and the\n * old array already conceded the point by exempting `chart` and `stats-block`,\n * which are catalog-only keys that `BUILT_IN_WIDGETS` has never held. What it\n * got wrong was doing that for two keys and not the other thirty-seven.\n * `widgetDispatch.js` unified the two resolution paths for the same reason: a\n * widget authored against one vocabulary was invisible to the other and said\n * nothing about why (101 of 236 detail widgets on hrmq).\n *\n * @module utils/libraryWidgetKeys\n */\n\nimport { WIDGET_TYPE_ALIASES } from './widgetTypeAliases.js'\n\n/**\n * Keys of `BUILT_IN_WIDGETS` — the v2 manifest widget set.\n *\n * Kept in the registry's own declaration order so a diff against\n * `builtInWidgets.js` reads straight down.\n *\n * @type {string[]} Frozen.\n */\nexport const BUILT_IN_WIDGET_KEYS = Object.freeze([\n\t'object-table',\n\t'form-renderer',\n\t'map-viewer',\n\t'notice',\n\t'object-geo',\n\t'card-grid',\n\t'nav-card-grid',\n\t'data',\n\t'metadata',\n\t'related',\n\t'integration',\n\t'banner',\n\t'audit-trail',\n\t'timeline',\n\t'header',\n\t'text',\n\t'divider',\n\t'saved-view-alert',\n\t'exports',\n])\n\n/**\n * Widget types the LIBRARY registers into the dashboard catalog at import time\n * (`registerDashboardWidgets.js` and the widget `index.js` modules it pulls in).\n *\n * Deliberately a static list rather than a read of `dashboardWidgetRegistry`:\n * that object is mutable and consumer-extensible, so deriving the exemption\n * from it at call time would let an app register its own full-page component in\n * the catalog and walk straight through the ADR-036 Decision 1 rule the\n * exemption exists to enforce. Only keys the library itself ships are exempt.\n *\n * Sorted, because unlike `BUILT_IN_WIDGETS` these registrations are spread over\n * ~30 files and have no meaningful declaration order.\n *\n * @type {string[]} Frozen.\n */\nexport const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([\n\t'audit-trail',\n\t'banner',\n\t'calendar',\n\t'chart',\n\t'checklist-run',\n\t'chips-field',\n\t'choice-picker',\n\t'container',\n\t'conversation',\n\t'countdown',\n\t'data',\n\t'delta',\n\t'device-frames',\n\t'divider',\n\t'document-review',\n\t'facts',\n\t'field-list',\n\t'files',\n\t'flow-runs',\n\t'gauge',\n\t'grouped-cards',\n\t'header',\n\t'image',\n\t'interaction-form',\n\t'kb-search',\n\t'label',\n\t'link',\n\t'links',\n\t'map',\n\t'markdown',\n\t'menu',\n\t'nc-widget',\n\t'next-step',\n\t'notepad',\n\t// `news` was here until the widget moved to LaunchPad, which owns the feed\n\t// fetching. An app that renders one now gets it from LaunchPad's registry.\n\t'object-geo',\n\t'object-list',\n\t'object-table',\n\t'party-actions',\n\t'people',\n\t'quicklinks',\n\t'radio-cards',\n\t'record-list',\n\t'related',\n\t'saved-view',\n\t'saved-views',\n\t'sections',\n\t'settings-form',\n\t'spend-analytics',\n\t'stacked-bar',\n\t'stages',\n\t'stat',\n\t'stats-block',\n\t'switch-rows',\n\t'table',\n\t'tabs',\n\t'tasks',\n\t'team-workload',\n\t'text',\n\t'tile',\n\t'timeline',\n\t'toggle-table',\n\t'video',\n\t'week-strip',\n\t'working-calendar',\n\t'workspace-filter',\n])\n\n/**\n * Expand a key set over `WIDGET_TYPE_ALIASES` so both spellings of an aliased\n * widget are present whenever either one is.\n *\n * `CnWidgetGrid` resolves a key and its canonical form (`object-table` ⇄\n * `table`, `map-viewer` ⇄ `map`), so both spellings render and both must be\n * exempt. Today each registry happens to hold one side of each pair, which is\n * exactly the accident that would silently stop being true when the next alias\n * lands.\n *\n * @param {Set<string>} keys The key set to expand, mutated in place.\n * @return {Set<string>} The same set.\n */\nfunction expandAliases(keys) {\n\tfor (const [alias, canonical] of Object.entries(WIDGET_TYPE_ALIASES)) {\n\t\tif (keys.has(alias) || keys.has(canonical)) {\n\t\t\tkeys.add(alias)\n\t\t\tkeys.add(canonical)\n\t\t}\n\t}\n\treturn keys\n}\n\n/**\n * Every widget key the library renders itself: both registries, plus both\n * spellings of every alias.\n *\n * A `widgetKey` in this list is served by a library `Cn*` SFC and does NOT\n * count as a custom registry component for the single-12×12-widget dashboard\n * rule (ADR-036 Decision 1).\n *\n * @type {string[]} Frozen.\n */\nexport const LIBRARY_WIDGET_KEYS = Object.freeze([\n\t...expandAliases(new Set([\n\t\t...BUILT_IN_WIDGET_KEYS,\n\t\t...DASHBOARD_CATALOG_WIDGET_KEYS,\n\t])),\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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC;AAClD,CAAC,cAAc;AACf,CAAC,eAAe;AAChB,CAAC,YAAY;AACb,CAAC,QAAQ;AACT,CAAC,YAAY;AACb,CAAC,WAAW;AACZ,CAAC,eAAe;AAChB,CAAC,MAAM;AACP,CAAC,UAAU;AACX,CAAC,SAAS;AACV,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,aAAa;AACd,CAAC,UAAU;AACX,CAAC,QAAQ;AACT,CAAC,MAAM;AACP,CAAC,SAAS;AACV,CAAC,kBAAkB;AACnB,CAAC,SAAS;AACV,CAAC,EAAC;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,6BAA6B,GAAG,MAAM,CAAC,MAAM,CAAC;AAC3D,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,UAAU;AACX,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,aAAa;AACd,CAAC,eAAe;AAChB,CAAC,WAAW;AACZ,CAAC,cAAc;AACf,CAAC,WAAW;AACZ,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,SAAS;AACV,CAAC,iBAAiB;AAClB,CAAC,OAAO;AACR,CAAC,YAAY;AACb,CAAC,OAAO;AACR,CAAC,WAAW;AACZ,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,QAAQ;AACT,CAAC,OAAO;AACR,CAAC,kBAAkB;AACnB,CAAC,WAAW;AACZ,CAAC,OAAO;AACR,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,KAAK;AACN,CAAC,UAAU;AACX,CAAC,MAAM;AACP,CAAC,WAAW;AACZ,CAAC,WAAW;AACZ,CAAC,SAAS;AACV;AACA;AACA,CAAC,YAAY;AACb,CAAC,aAAa;AACd,CAAC,cAAc;AACf,CAAC,eAAe;AAChB,CAAC,QAAQ;AACT,CAAC,YAAY;AACb,CAAC,aAAa;AACd,CAAC,aAAa;AACd,CAAC,SAAS;AACV,CAAC,YAAY;AACb,CAAC,aAAa;AACd,CAAC,UAAU;AACX,CAAC,eAAe;AAChB,CAAC,iBAAiB;AAClB,CAAC,aAAa;AACd,CAAC,QAAQ;AACT,CAAC,MAAM;AACP,CAAC,aAAa;AACd,CAAC,aAAa;AACd,CAAC,OAAO;AACR,CAAC,MAAM;AACP,CAAC,OAAO;AACR,CAAC,eAAe;AAChB,CAAC,MAAM;AACP,CAAC,MAAM;AACP,CAAC,UAAU;AACX,CAAC,cAAc;AACf,CAAC,OAAO;AACR,CAAC,YAAY;AACb,CAAC,kBAAkB;AACnB,CAAC,kBAAkB;AACnB,CAAC,EAAC;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,aAAa,CAAC,IAAI,EAAE;AAC7B,CAAC,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE;AACvE,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE;AAC9C,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAC;AAClB,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAC;AACtB,GAAG;AACH,EAAE;AACF,CAAC,OAAO,IAAI;AACZ,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,mBAAmB,GAAG,MAAM,CAAC,MAAM,CAAC;AACjD,CAAC,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC;AAC1B,EAAE,GAAG,oBAAoB;AACzB,EAAE,GAAG,6BAA6B;AAClC,EAAE,CAAC,CAAC;AACJ,CAAC;;;;"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { generateOcsUrl } from '@nextcloud/router';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
5
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The display names of the apps in Nextcloud's navigation, by app id, read
|
|
11
|
+
* once per page load from the OCS navigation endpoint with the reader's
|
|
12
|
+
* session. An app outside the navigation, or a failed read, has no entry.
|
|
13
|
+
*
|
|
14
|
+
* @module utils/nextcloudApps
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
let pending = null;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The app display names by app id.
|
|
21
|
+
*
|
|
22
|
+
* @param {object} axios The axios instance.
|
|
23
|
+
* @return {Promise<Record<string, string>>} The names; empty when the read fails.
|
|
24
|
+
*/
|
|
25
|
+
function appDisplayNames(axios) {
|
|
26
|
+
if (!pending) {
|
|
27
|
+
pending = axios.get(generateOcsUrl('core/navigation/apps'), { params: { format: 'json' }, headers: { 'OCS-APIRequest': 'true' } })
|
|
28
|
+
.then((res) => {
|
|
29
|
+
const entries = (res && res.data && res.data.ocs && res.data.ocs.data) || [];
|
|
30
|
+
const names = {};
|
|
31
|
+
for (const entry of Array.isArray(entries) ? entries : Object.values(entries)) {
|
|
32
|
+
const id = entry && (entry.app || entry.id);
|
|
33
|
+
if (id && typeof entry.name === 'string' && entry.name !== '') {
|
|
34
|
+
names[String(id)] = entry.name;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return names
|
|
38
|
+
})
|
|
39
|
+
.catch(() => ({}));
|
|
40
|
+
}
|
|
41
|
+
return pending
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { appDisplayNames };
|
|
45
|
+
//# sourceMappingURL=nextcloudApps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nextcloudApps.js","sources":["../../../src/utils/nextcloudApps.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\nimport { generateOcsUrl } from '@nextcloud/router'\n\n/**\n * The display names of the apps in Nextcloud's navigation, by app id, read\n * once per page load from the OCS navigation endpoint with the reader's\n * session. An app outside the navigation, or a failed read, has no entry.\n *\n * @module utils/nextcloudApps\n */\n\nlet pending = null\n\n/**\n * The app display names by app id.\n *\n * @param {object} axios The axios instance.\n * @return {Promise<Record<string, string>>} The names; empty when the read fails.\n */\nexport function appDisplayNames(axios) {\n\tif (!pending) {\n\t\tpending = axios.get(generateOcsUrl('core/navigation/apps'), { params: { format: 'json' }, headers: { 'OCS-APIRequest': 'true' } })\n\t\t\t.then((res) => {\n\t\t\t\tconst entries = (res && res.data && res.data.ocs && res.data.ocs.data) || []\n\t\t\t\tconst names = {}\n\t\t\t\tfor (const entry of Array.isArray(entries) ? entries : Object.values(entries)) {\n\t\t\t\t\tconst id = entry && (entry.app || entry.id)\n\t\t\t\t\tif (id && typeof entry.name === 'string' && entry.name !== '') {\n\t\t\t\t\t\tnames[String(id)] = entry.name\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn names\n\t\t\t})\n\t\t\t.catch(() => ({}))\n\t}\n\treturn pending\n}\n\n/**\n * Forget the names read, so the next call reads them again.\n *\n * @return {void}\n */\nexport function clearAppDisplayNames() {\n\tpending = null\n}\n"],"names":[],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,OAAO,GAAG,KAAI;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,KAAK,EAAE;AACvC,CAAC,IAAI,CAAC,OAAO,EAAE;AACf,EAAE,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAE,CAAC;AACpI,IAAI,IAAI,CAAC,CAAC,GAAG,KAAK;AAClB,IAAI,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,GAAE;AAChF,IAAI,MAAM,KAAK,GAAG,GAAE;AACpB,IAAI,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE;AACnF,KAAK,MAAM,EAAE,GAAG,KAAK,KAAK,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,EAAE,EAAC;AAChD,KAAK,IAAI,EAAE,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,EAAE,EAAE;AACpE,MAAM,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,KAAI;AACpC,MAAM;AACN,KAAK;AACL,IAAI,OAAO,KAAK;AAChB,IAAI,CAAC;AACL,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,EAAC;AACrB,EAAE;AACF,CAAC,OAAO,OAAO;AACf;;;;"}
|
|
@@ -147,8 +147,15 @@ function resolveScopeLayout({ scope = null, row = null, pageColumns = [] } = {})
|
|
|
147
147
|
return
|
|
148
148
|
}
|
|
149
149
|
// The page's own entry is used, not the scope's, so the label and
|
|
150
|
-
// formatter a column was declared with survive being selected.
|
|
151
|
-
|
|
150
|
+
// formatter a column was declared with survive being selected. A
|
|
151
|
+
// column the page hides by default shows: the scope asked for it.
|
|
152
|
+
const own = byKey.get(key);
|
|
153
|
+
if (own && typeof own === 'object' && own.hidden === true) {
|
|
154
|
+
const { hidden, ...shown } = own;
|
|
155
|
+
columns.push(shown);
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
columns.push(own);
|
|
152
159
|
});
|
|
153
160
|
|
|
154
161
|
return { columns, sortKeys, searchFields }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scopeListLayout.js","sources":["../../../src/utils/scopeListLayout.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { QUERY_PRESET_KEYS } from './queryPresets.js'\n\n/**\n * The three list-layout keys a `folderSidebar` scope may carry.\n *\n * @type {string[]}\n */\nconst SCOPE_LAYOUT_KEYS = ['columns', 'defaultSort', 'searchFields']\n\n/**\n * The presentation keys a scope may carry beside its list layout: the keys a\n * query preset may set (`QUERY_PRESET_KEYS`), less the two the list layout\n * resolves itself (`columns`, `defaultSort`).\n *\n * @type {ReadonlyArray<string>}\n */\nconst SCOPE_PRESENTATION_KEYS = Object.freeze(QUERY_PRESET_KEYS.filter((key) => key !== 'columns' && key !== 'defaultSort'))\n\n/**\n * The key of a column entry, in either of the two shapes the manifest accepts:\n * a bare string (legacy shorthand) or a `{ key, label, ... }` object.\n *\n * @param {(string|object)} column A column entry.\n * @return {string} The column key, or '' when the entry carries none.\n */\nexport function columnKeyOf(column) {\n\tif (typeof column === 'string') {\n\t\treturn column\n\t}\n\tif (column && typeof column.key === 'string') {\n\t\treturn column.key\n\t}\n\treturn ''\n}\n\n/**\n * Normalise one sort entry to the `{ key, order }` shape the fetch uses.\n * Accepts `key` or `field` because the page prop spells it `field` and the\n * manifest spells it `key`; an entry naming neither is dropped rather than\n * sent as an empty sort key.\n *\n * @param {object} entry A sort entry.\n * @return {({key: string, order: string}|null)} The normalised entry, or null.\n */\nfunction normaliseSortEntry(entry) {\n\tif (!entry || typeof entry !== 'object') {\n\t\treturn null\n\t}\n\tconst key = typeof entry.key === 'string' && entry.key !== ''\n\t\t? entry.key\n\t\t: (typeof entry.field === 'string' ? entry.field : '')\n\tif (key === '') {\n\t\treturn null\n\t}\n\treturn { key, order: entry.order === 'desc' ? 'desc' : 'asc' }\n}\n\n/**\n * Normalise a `defaultSort` value, which may be a single entry or a list.\n *\n * @param {(object|object[]|undefined)} value The declared sort.\n * @return {Array<{key: string, order: string}>} The normalised sort keys.\n */\nfunction normaliseSort(value) {\n\tif (value === undefined || value === null) {\n\t\treturn []\n\t}\n\tconst list = Array.isArray(value) ? value : [value]\n\treturn list.map(normaliseSortEntry).filter(Boolean)\n}\n\n/**\n * Read one layout key from the scope entry, falling back to the schema row's\n * `x-index` block. A value declared on the folder entry WINS over the row's,\n * which is what makes the row a default rather than an override.\n *\n * @param {(object|null)} scope The folder entry for the active scope.\n * @param {(object|null)} row The schema row the folder derives from.\n * @param {string} key One of `columns`, `defaultSort`, `searchFields`.\n * @return {(Array|object|undefined)} The declared value, the row value, or undefined.\n */\nfunction declaredOrRow(scope, row, key) {\n\tif (scope && scope[key] !== undefined) {\n\t\treturn scope[key]\n\t}\n\tconst carried = row && row['x-index']\n\tif (carried && typeof carried === 'object' && carried[key] !== undefined) {\n\t\treturn carried[key]\n\t}\n\treturn undefined\n}\n\n/**\n * Resolve the list layout a `folderSidebar` scope asks for.\n *\n * Two declarations meet here and they do not decide the same thing:\n *\n * - The PAGE decides MEMBERSHIP. `config.columns` is the set of columns this\n * page has, and it carries each column's definition (label, formatter,\n * widget). A scope may reorder that set and narrow it; it cannot add to it.\n * A scope naming a column the page does not declare is DROPPED, so a scope\n * written before a column was removed cannot bring the column back.\n * - The SCOPE decides PRESENTATION: which of the page's columns are shown, in\n * what order, sorted by what, searched over what.\n *\n * The page declaring no columns at all is the one case where a scope's list is\n * taken as written: there is no declared set to measure it against, because the\n * columns are derived from the schema at runtime.\n *\n * @param {object} options Resolution inputs.\n * @param {(object|null)} [options.scope] The folder entry for the active scope.\n * @param {(object|null)} [options.row] The schema row the folder derives from.\n * @param {Array} [options.pageColumns] The page's declared `config.columns`.\n * @return {{columns: (Array|null), sortKeys: Array<{key: string, order: string}>, searchFields: string[]}}\n * `columns` is null when neither source declares any, meaning the page's own\n * list stands.\n */\nexport function resolveScopeLayout({ scope = null, row = null, pageColumns = [] } = {}) {\n\tconst declaredColumns = declaredOrRow(scope, row, 'columns')\n\tconst declaredSearch = declaredOrRow(scope, row, 'searchFields')\n\tconst sortKeys = normaliseSort(declaredOrRow(scope, row, 'defaultSort'))\n\n\tconst searchFields = Array.isArray(declaredSearch)\n\t\t? declaredSearch.filter((f) => typeof f === 'string' && f !== '')\n\t\t: []\n\n\tif (!Array.isArray(declaredColumns) || declaredColumns.length === 0) {\n\t\treturn { columns: null, sortKeys, searchFields }\n\t}\n\n\tconst page = Array.isArray(pageColumns) ? pageColumns : []\n\tif (page.length === 0) {\n\t\treturn { columns: [...declaredColumns], sortKeys, searchFields }\n\t}\n\n\tconst byKey = new Map()\n\tpage.forEach((col) => {\n\t\tconst key = columnKeyOf(col)\n\t\tif (key !== '') {\n\t\t\tbyKey.set(key, col)\n\t\t}\n\t})\n\n\tconst columns = []\n\tdeclaredColumns.forEach((col) => {\n\t\tconst key = columnKeyOf(col)\n\t\tif (key === '' || !byKey.has(key)) {\n\t\t\treturn\n\t\t}\n\t\t// The page's own entry is used, not the scope's, so the label and\n\t\t// formatter a column was declared with survive being selected.\n\t\tcolumns.push(byKey.get(key))\n\t})\n\n\treturn { columns, sortKeys, searchFields }\n}\n\n/**\n * The presentation a scope asks for: each of `SCOPE_PRESENTATION_KEYS` the\n * folder entry declares, else the one its schema row's `x-index` declares.\n * Only declared keys are in the result, so the page's own value stands for\n * every other key. These are the keys a query preset sets, under the same\n * names, and a scope's value wins over a preset's because it is applied on\n * top of the page config the preset produced.\n *\n * @param {object} options Resolution inputs.\n * @param {(object|null)} [options.scope] The folder entry for the active scope.\n * @param {(object|null)} [options.row] The schema row the folder derives from.\n * @return {object} The declared presentation keys.\n * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-a-preset-or-a-scope-sets-the-list-s-presentation\n */\nexport function resolveScopePresentation({ scope = null, row = null } = {}) {\n\tconst out = {}\n\tfor (const key of SCOPE_PRESENTATION_KEYS) {\n\t\tconst value = declaredOrRow(scope, row, key)\n\t\tif (value !== undefined) {\n\t\t\tout[key] = value\n\t\t}\n\t}\n\treturn out\n}\n\nexport { SCOPE_LAYOUT_KEYS, SCOPE_PRESENTATION_KEYS }\n"],"names":[],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACK,MAAC,uBAAuB,GAAG,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,aAAa,CAAC,EAAC;AAC5H;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,WAAW,CAAC,MAAM,EAAE;AACpC,CAAC,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;AACjC,EAAE,OAAO,MAAM;AACf,EAAE;AACF,CAAC,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,EAAE;AAC/C,EAAE,OAAO,MAAM,CAAC,GAAG;AACnB,EAAE;AACF,CAAC,OAAO,EAAE;AACV,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,kBAAkB,CAAC,KAAK,EAAE;AACnC,CAAC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAC1C,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,GAAG,GAAG,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,GAAG,KAAK,EAAE;AAC9D,IAAI,KAAK,CAAC,GAAG;AACb,KAAK,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,GAAG,KAAK,CAAC,KAAK,GAAG,EAAE,EAAC;AACxD,CAAC,IAAI,GAAG,KAAK,EAAE,EAAE;AACjB,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,KAAK,MAAM,GAAG,MAAM,GAAG,KAAK,EAAE;AAC/D,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,aAAa,CAAC,KAAK,EAAE;AAC9B,CAAC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE;AAC5C,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,KAAK,EAAC;AACpD,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;AACpD,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE;AACxC,CAAC,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE;AACxC,EAAE,OAAO,KAAK,CAAC,GAAG,CAAC;AACnB,EAAE;AACF,CAAC,MAAM,OAAO,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,EAAC;AACtC,CAAC,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE;AAC3E,EAAE,OAAO,OAAO,CAAC,GAAG,CAAC;AACrB,EAAE;AACF,CAAC,OAAO,SAAS;AACjB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,kBAAkB,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,GAAG,IAAI,EAAE,WAAW,GAAG,EAAE,EAAE,GAAG,EAAE,EAAE;AACxF,CAAC,MAAM,eAAe,GAAG,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAC;AAC7D,CAAC,MAAM,cAAc,GAAG,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,EAAC;AACjE,CAAC,MAAM,QAAQ,GAAG,aAAa,CAAC,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,aAAa,CAAC,EAAC;AACzE;AACA,CAAC,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC;AACnD,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC;AACnE,IAAI,GAAE;AACN;AACA,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE;AACtE,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE;AAClD,EAAE;AACF;AACA,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,GAAG,GAAE;AAC3D,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE;AACxB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,eAAe,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE;AAClE,EAAE;AACF;AACA,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,GAAE;AACxB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK;AACvB,EAAE,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAC;AAC9B,EAAE,IAAI,GAAG,KAAK,EAAE,EAAE;AAClB,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAC;AACtB,GAAG;AACH,EAAE,EAAC;AACH;AACA,CAAC,MAAM,OAAO,GAAG,GAAE;AACnB,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK;AAClC,EAAE,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAC;AAC9B,EAAE,IAAI,GAAG,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AACrC,GAAG,MAAM;AACT,GAAG;AACH;AACA;AACA,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAC;AAC9B,EAAE,EAAC;AACH;AACA,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE;AAC3C,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,wBAAwB,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,EAAE,EAAE;AAC5E,CAAC,MAAM,GAAG,GAAG,GAAE;AACf,CAAC,KAAK,MAAM,GAAG,IAAI,uBAAuB,EAAE;AAC5C,EAAE,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAC;AAC9C,EAAE,IAAI,KAAK,KAAK,SAAS,EAAE;AAC3B,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,MAAK;AACnB,GAAG;AACH,EAAE;AACF,CAAC,OAAO,GAAG;AACX;;;;"}
|
|
1
|
+
{"version":3,"file":"scopeListLayout.js","sources":["../../../src/utils/scopeListLayout.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { QUERY_PRESET_KEYS } from './queryPresets.js'\n\n/**\n * The three list-layout keys a `folderSidebar` scope may carry.\n *\n * @type {string[]}\n */\nconst SCOPE_LAYOUT_KEYS = ['columns', 'defaultSort', 'searchFields']\n\n/**\n * The presentation keys a scope may carry beside its list layout: the keys a\n * query preset may set (`QUERY_PRESET_KEYS`), less the two the list layout\n * resolves itself (`columns`, `defaultSort`).\n *\n * @type {ReadonlyArray<string>}\n */\nconst SCOPE_PRESENTATION_KEYS = Object.freeze(QUERY_PRESET_KEYS.filter((key) => key !== 'columns' && key !== 'defaultSort'))\n\n/**\n * The key of a column entry, in either of the two shapes the manifest accepts:\n * a bare string (legacy shorthand) or a `{ key, label, ... }` object.\n *\n * @param {(string|object)} column A column entry.\n * @return {string} The column key, or '' when the entry carries none.\n */\nexport function columnKeyOf(column) {\n\tif (typeof column === 'string') {\n\t\treturn column\n\t}\n\tif (column && typeof column.key === 'string') {\n\t\treturn column.key\n\t}\n\treturn ''\n}\n\n/**\n * Normalise one sort entry to the `{ key, order }` shape the fetch uses.\n * Accepts `key` or `field` because the page prop spells it `field` and the\n * manifest spells it `key`; an entry naming neither is dropped rather than\n * sent as an empty sort key.\n *\n * @param {object} entry A sort entry.\n * @return {({key: string, order: string}|null)} The normalised entry, or null.\n */\nfunction normaliseSortEntry(entry) {\n\tif (!entry || typeof entry !== 'object') {\n\t\treturn null\n\t}\n\tconst key = typeof entry.key === 'string' && entry.key !== ''\n\t\t? entry.key\n\t\t: (typeof entry.field === 'string' ? entry.field : '')\n\tif (key === '') {\n\t\treturn null\n\t}\n\treturn { key, order: entry.order === 'desc' ? 'desc' : 'asc' }\n}\n\n/**\n * Normalise a `defaultSort` value, which may be a single entry or a list.\n *\n * @param {(object|object[]|undefined)} value The declared sort.\n * @return {Array<{key: string, order: string}>} The normalised sort keys.\n */\nfunction normaliseSort(value) {\n\tif (value === undefined || value === null) {\n\t\treturn []\n\t}\n\tconst list = Array.isArray(value) ? value : [value]\n\treturn list.map(normaliseSortEntry).filter(Boolean)\n}\n\n/**\n * Read one layout key from the scope entry, falling back to the schema row's\n * `x-index` block. A value declared on the folder entry WINS over the row's,\n * which is what makes the row a default rather than an override.\n *\n * @param {(object|null)} scope The folder entry for the active scope.\n * @param {(object|null)} row The schema row the folder derives from.\n * @param {string} key One of `columns`, `defaultSort`, `searchFields`.\n * @return {(Array|object|undefined)} The declared value, the row value, or undefined.\n */\nfunction declaredOrRow(scope, row, key) {\n\tif (scope && scope[key] !== undefined) {\n\t\treturn scope[key]\n\t}\n\tconst carried = row && row['x-index']\n\tif (carried && typeof carried === 'object' && carried[key] !== undefined) {\n\t\treturn carried[key]\n\t}\n\treturn undefined\n}\n\n/**\n * Resolve the list layout a `folderSidebar` scope asks for.\n *\n * Two declarations meet here and they do not decide the same thing:\n *\n * - The PAGE decides MEMBERSHIP. `config.columns` is the set of columns this\n * page has, and it carries each column's definition (label, formatter,\n * widget). A scope may reorder that set and narrow it; it cannot add to it.\n * A scope naming a column the page does not declare is DROPPED, so a scope\n * written before a column was removed cannot bring the column back.\n * - The SCOPE decides PRESENTATION: which of the page's columns are shown, in\n * what order, sorted by what, searched over what.\n *\n * The page declaring no columns at all is the one case where a scope's list is\n * taken as written: there is no declared set to measure it against, because the\n * columns are derived from the schema at runtime.\n *\n * @param {object} options Resolution inputs.\n * @param {(object|null)} [options.scope] The folder entry for the active scope.\n * @param {(object|null)} [options.row] The schema row the folder derives from.\n * @param {Array} [options.pageColumns] The page's declared `config.columns`.\n * @return {{columns: (Array|null), sortKeys: Array<{key: string, order: string}>, searchFields: string[]}}\n * `columns` is null when neither source declares any, meaning the page's own\n * list stands.\n */\nexport function resolveScopeLayout({ scope = null, row = null, pageColumns = [] } = {}) {\n\tconst declaredColumns = declaredOrRow(scope, row, 'columns')\n\tconst declaredSearch = declaredOrRow(scope, row, 'searchFields')\n\tconst sortKeys = normaliseSort(declaredOrRow(scope, row, 'defaultSort'))\n\n\tconst searchFields = Array.isArray(declaredSearch)\n\t\t? declaredSearch.filter((f) => typeof f === 'string' && f !== '')\n\t\t: []\n\n\tif (!Array.isArray(declaredColumns) || declaredColumns.length === 0) {\n\t\treturn { columns: null, sortKeys, searchFields }\n\t}\n\n\tconst page = Array.isArray(pageColumns) ? pageColumns : []\n\tif (page.length === 0) {\n\t\treturn { columns: [...declaredColumns], sortKeys, searchFields }\n\t}\n\n\tconst byKey = new Map()\n\tpage.forEach((col) => {\n\t\tconst key = columnKeyOf(col)\n\t\tif (key !== '') {\n\t\t\tbyKey.set(key, col)\n\t\t}\n\t})\n\n\tconst columns = []\n\tdeclaredColumns.forEach((col) => {\n\t\tconst key = columnKeyOf(col)\n\t\tif (key === '' || !byKey.has(key)) {\n\t\t\treturn\n\t\t}\n\t\t// The page's own entry is used, not the scope's, so the label and\n\t\t// formatter a column was declared with survive being selected. A\n\t\t// column the page hides by default shows: the scope asked for it.\n\t\tconst own = byKey.get(key)\n\t\tif (own && typeof own === 'object' && own.hidden === true) {\n\t\t\tconst { hidden, ...shown } = own\n\t\t\tcolumns.push(shown)\n\t\t\treturn\n\t\t}\n\t\tcolumns.push(own)\n\t})\n\n\treturn { columns, sortKeys, searchFields }\n}\n\n/**\n * The presentation a scope asks for: each of `SCOPE_PRESENTATION_KEYS` the\n * folder entry declares, else the one its schema row's `x-index` declares.\n * Only declared keys are in the result, so the page's own value stands for\n * every other key. These are the keys a query preset sets, under the same\n * names, and a scope's value wins over a preset's because it is applied on\n * top of the page config the preset produced.\n *\n * @param {object} options Resolution inputs.\n * @param {(object|null)} [options.scope] The folder entry for the active scope.\n * @param {(object|null)} [options.row] The schema row the folder derives from.\n * @return {object} The declared presentation keys.\n * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-a-preset-or-a-scope-sets-the-list-s-presentation\n */\nexport function resolveScopePresentation({ scope = null, row = null } = {}) {\n\tconst out = {}\n\tfor (const key of SCOPE_PRESENTATION_KEYS) {\n\t\tconst value = declaredOrRow(scope, row, key)\n\t\tif (value !== undefined) {\n\t\t\tout[key] = value\n\t\t}\n\t}\n\treturn out\n}\n\nexport { SCOPE_LAYOUT_KEYS, SCOPE_PRESENTATION_KEYS }\n"],"names":[],"mappings":"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"}
|
|
@@ -57,5 +57,15 @@ function sameSortKeys(a, b) {
|
|
|
57
57
|
&& left.every((entry, index) => entry.key === right[index].key && (entry.order || 'asc') === (right[index].order || 'asc'))
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
/**
|
|
61
|
+
* The same keys, each direction turned round.
|
|
62
|
+
*
|
|
63
|
+
* @param {Array<{key: string, order?: string}>} keys The sort keys.
|
|
64
|
+
* @return {Array<{key: string, order: 'asc'|'desc'}>} The reversed keys.
|
|
65
|
+
*/
|
|
66
|
+
function reverseSortKeys(keys) {
|
|
67
|
+
return (Array.isArray(keys) ? keys : []).map((entry) => ({ key: entry.key, order: entry.order === 'desc' ? 'asc' : 'desc' }))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { orderToSortKeys, reverseSortKeys, sameSortKeys };
|
|
61
71
|
//# sourceMappingURL=sortOrder.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sortOrder.js","sources":["../../../src/utils/sortOrder.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\n/**\n * Read an order a manifest writes into the `[{ key, order }]` list a self-fetch\n * index page sorts by. Accepted forms:\n *\n * - `\"title\"` ascending, `\"-createdAt\"` descending, `\"priority:desc\"`;\n * - several of those, comma separated, in priority order (`\"-priority,title\"`);\n * - an OpenRegister `_order` map, `{ \"priority\": \"desc\", \"title\": \"asc\" }`;\n * - a `[{ key, order? }]` list.\n *\n * @param {string|object|Array|null|undefined} value The order.\n * @return {Array<{key: string, order: 'asc'|'desc'}>} The sort keys, possibly empty.\n */\nexport function orderToSortKeys(value) {\n\tconst direction = (order) => (String(order || '').toLowerCase() === 'desc' ? 'desc' : 'asc')\n\tif (Array.isArray(value)) {\n\t\treturn value\n\t\t\t.filter((entry) => entry && typeof entry.key === 'string' && entry.key !== '')\n\t\t\t.map((entry) => ({ key: entry.key, order: direction(entry.order) }))\n\t}\n\tif (value && typeof value === 'object') {\n\t\treturn Object.entries(value)\n\t\t\t.filter(([key]) => key !== '')\n\t\t\t.map(([key, order]) => ({ key, order: direction(order) }))\n\t}\n\tif (typeof value !== 'string') {\n\t\treturn []\n\t}\n\treturn value.split(',')\n\t\t.map((part) => part.trim())\n\t\t.filter(Boolean)\n\t\t.map((part) => {\n\t\t\tif (part.startsWith('-')) {\n\t\t\t\treturn { key: part.slice(1), order: 'desc' }\n\t\t\t}\n\t\t\tconst [key, order] = part.split(':')\n\t\t\treturn { key, order: direction(order) }\n\t\t})\n\t\t.filter((entry) => entry.key !== '')\n}\n\n/**\n * Whether two sort key lists order the same way.\n *\n * @param {Array<{key: string, order: string}>} a One list.\n * @param {Array<{key: string, order: string}>} b The other.\n * @return {boolean} True when they name the same keys, directions and priority.\n */\nexport function sameSortKeys(a, b) {\n\tconst left = Array.isArray(a) ? a : []\n\tconst right = Array.isArray(b) ? b : []\n\treturn left.length === right.length\n\t\t&& left.every((entry, index) => entry.key === right[index].key && (entry.order || 'asc') === (right[index].order || 'asc'))\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,KAAK,EAAE;AACvC,CAAC,MAAM,SAAS,GAAG,CAAC,KAAK,MAAM,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,KAAK,EAAC;AAC7F,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;AAC3B,EAAE,OAAO,KAAK;AACd,IAAI,MAAM,CAAC,CAAC,KAAK,KAAK,KAAK,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,GAAG,KAAK,EAAE,CAAC;AACjF,IAAI,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACvE,EAAE;AACF,CAAC,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AACzC,EAAE,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;AAC9B,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC;AACjC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAC7D,EAAE;AACF,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAChC,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;AACxB,GAAG,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;AAC7B,GAAG,MAAM,CAAC,OAAO,CAAC;AAClB,GAAG,GAAG,CAAC,CAAC,IAAI,KAAK;AACjB,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;AAC7B,IAAI,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE;AAChD,IAAI;AACJ,GAAG,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAC;AACvC,GAAG,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE;AAC1C,GAAG,CAAC;AACJ,GAAG,MAAM,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,GAAG,KAAK,EAAE,CAAC;AACtC,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE;AACnC,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,GAAE;AACvC,CAAC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,GAAE;AACxC,CAAC,OAAO,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;AACpC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC;AAC7H;;;;"}
|
|
1
|
+
{"version":3,"file":"sortOrder.js","sources":["../../../src/utils/sortOrder.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\n/**\n * Read an order a manifest writes into the `[{ key, order }]` list a self-fetch\n * index page sorts by. Accepted forms:\n *\n * - `\"title\"` ascending, `\"-createdAt\"` descending, `\"priority:desc\"`;\n * - several of those, comma separated, in priority order (`\"-priority,title\"`);\n * - an OpenRegister `_order` map, `{ \"priority\": \"desc\", \"title\": \"asc\" }`;\n * - a `[{ key, order? }]` list.\n *\n * @param {string|object|Array|null|undefined} value The order.\n * @return {Array<{key: string, order: 'asc'|'desc'}>} The sort keys, possibly empty.\n */\nexport function orderToSortKeys(value) {\n\tconst direction = (order) => (String(order || '').toLowerCase() === 'desc' ? 'desc' : 'asc')\n\tif (Array.isArray(value)) {\n\t\treturn value\n\t\t\t.filter((entry) => entry && typeof entry.key === 'string' && entry.key !== '')\n\t\t\t.map((entry) => ({ key: entry.key, order: direction(entry.order) }))\n\t}\n\tif (value && typeof value === 'object') {\n\t\treturn Object.entries(value)\n\t\t\t.filter(([key]) => key !== '')\n\t\t\t.map(([key, order]) => ({ key, order: direction(order) }))\n\t}\n\tif (typeof value !== 'string') {\n\t\treturn []\n\t}\n\treturn value.split(',')\n\t\t.map((part) => part.trim())\n\t\t.filter(Boolean)\n\t\t.map((part) => {\n\t\t\tif (part.startsWith('-')) {\n\t\t\t\treturn { key: part.slice(1), order: 'desc' }\n\t\t\t}\n\t\t\tconst [key, order] = part.split(':')\n\t\t\treturn { key, order: direction(order) }\n\t\t})\n\t\t.filter((entry) => entry.key !== '')\n}\n\n/**\n * Whether two sort key lists order the same way.\n *\n * @param {Array<{key: string, order: string}>} a One list.\n * @param {Array<{key: string, order: string}>} b The other.\n * @return {boolean} True when they name the same keys, directions and priority.\n */\nexport function sameSortKeys(a, b) {\n\tconst left = Array.isArray(a) ? a : []\n\tconst right = Array.isArray(b) ? b : []\n\treturn left.length === right.length\n\t\t&& left.every((entry, index) => entry.key === right[index].key && (entry.order || 'asc') === (right[index].order || 'asc'))\n}\n\n/**\n * The same keys, each direction turned round.\n *\n * @param {Array<{key: string, order?: string}>} keys The sort keys.\n * @return {Array<{key: string, order: 'asc'|'desc'}>} The reversed keys.\n */\nexport function reverseSortKeys(keys) {\n\treturn (Array.isArray(keys) ? keys : []).map((entry) => ({ key: entry.key, order: entry.order === 'desc' ? 'asc' : 'desc' }))\n}\n"],"names":[],"mappings":"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"}
|