@conduction/nextcloud-vue 2.96.0 → 2.98.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/CnAppRoot/CnAppRoot.vue2.js +17 -0
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +383 -359
- 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 +106 -2
- 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/CnChecklistRunWidget/CnChecklistRunWidget.vue.js +9 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js +964 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js +282 -0
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChecklistRunWidget/index.js +28 -0
- package/dist/esm/components/CnChecklistRunWidget/index.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +17 -3
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +61 -43
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +159 -18
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +9 -7
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +1 -0
- 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 +26 -8
- 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 +188 -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/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +48 -3
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +46 -12
- 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 +3 -3
- 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 +27 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +16 -2
- 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 +144 -0
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +488 -399
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +344 -21
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +29 -7
- 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 +2 -2
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +2 -2
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +13 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +5 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +9 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js.map +1 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +220 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +121 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +83 -9
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +80 -11
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/composables/indexSources.js +420 -5
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/composables/useOrganisationStore.js +115 -0
- package/dist/esm/composables/useOrganisationStore.js.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +21 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +36 -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 +725 -383
- package/dist/esm/utils/builtInFormatters.js +34 -1
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/matrixCells.js +158 -0
- package/dist/esm/utils/matrixCells.js.map +1 -0
- package/dist/esm/utils/nextcloudDirectory.js +121 -0
- package/dist/esm/utils/nextcloudDirectory.js.map +1 -0
- package/dist/esm/utils/queryPresets.js +13 -2
- package/dist/esm/utils/queryPresets.js.map +1 -1
- package/dist/esm/utils/scopeListLayout.js +37 -1
- package/dist/esm/utils/scopeListLayout.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +14443 -10519
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +725 -383
- package/l10n/en.json +21 -1
- package/l10n/nl.json +36 -1
- package/package.json +2 -2
- package/scripts/merge-l10n.js +196 -0
- package/src/components/CnAppRoot/CnAppRoot.md +1 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +17 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +57 -1
- package/src/components/CnChartWidget/CnChartWidget.md +2 -1
- package/src/components/CnChartWidget/CnChartWidget.vue +106 -2
- package/src/components/CnChecklistRunWidget/CnChecklistRunWidget.vue +1269 -0
- package/src/components/CnChecklistRunWidget/index.js +27 -0
- package/src/components/CnConversationThread/CnConversationThread.md +12 -0
- package/src/components/CnConversationThread/CnConversationThread.vue +30 -4
- package/src/components/CnConversationThread/CnConversationWidget.vue +167 -24
- package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +26 -8
- package/src/components/CnDetailPage/CnDetailPage.md +48 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +188 -5
- package/src/components/CnFactsWidget/CnFactsWidget.vue +114 -3
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +3 -3
- package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +38 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +508 -236
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
- package/src/components/CnIndexPage/CnIndexPage.md +28 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +367 -27
- package/src/components/CnIndexPage/CnIndexPageWidgets.vue +13 -4
- package/src/components/CnIndexPage/useNamedSource.js +1 -1
- package/src/components/CnObjectSidebar/CnFilesTab.vue +17 -0
- package/src/components/CnOrderedListField/CnOrderedListField.vue +386 -0
- package/src/components/CnOrderedListField/index.js +7 -0
- package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +139 -10
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/index.js +2 -0
- package/src/composables/indexSources.js +419 -4
- package/src/composables/useOrganisationStore.js +111 -0
- package/src/index.js +2 -0
- package/src/schemas/app-manifest-v2.schema.json +42 -5
- package/src/utils/builtInFormatters.js +33 -0
- package/src/utils/libraryWidgetKeys.js +1 -0
- package/src/utils/matrixCells.js +154 -0
- package/src/utils/nextcloudDirectory.js +117 -0
- package/src/utils/queryPresets.js +13 -2
- package/src/utils/scopeListLayout.js +37 -1
- package/src/utils/validateManifestV2.compiled.js +3 -3
package/l10n/en.json
CHANGED
|
@@ -1145,6 +1145,8 @@
|
|
|
1145
1145
|
"Due tomorrow": "Due tomorrow",
|
|
1146
1146
|
"Due in {days} days": "Due in {days} days",
|
|
1147
1147
|
"Available": "Available",
|
|
1148
|
+
"{value} of {limit}": "{value} of {limit}",
|
|
1149
|
+
"{value} of {limit}, over the limit": "{value} of {limit}, over the limit",
|
|
1148
1150
|
"Ready to claim": "Ready to claim",
|
|
1149
1151
|
"In progress": "In progress",
|
|
1150
1152
|
"Completed": "Completed",
|
|
@@ -1584,7 +1586,25 @@
|
|
|
1584
1586
|
"{s} s": "{s} s",
|
|
1585
1587
|
"About this {app} installation.": "About this {app} installation.",
|
|
1586
1588
|
"{version}, up to date": "{version}, up to date",
|
|
1587
|
-
"for an SLA": "for an SLA"
|
|
1589
|
+
"for an SLA": "for an SLA",
|
|
1590
|
+
"About {name}": "About {name}",
|
|
1591
|
+
"Sent message": "Sent message",
|
|
1592
|
+
"Received message": "Received message",
|
|
1593
|
+
"Loading messages…": "Loading messages…",
|
|
1594
|
+
"Move {item} up": "Move {item} up",
|
|
1595
|
+
"Move {item} down": "Move {item} down",
|
|
1596
|
+
"Remove {item}": "Remove {item}",
|
|
1597
|
+
"Nothing added yet.": "Nothing added yet.",
|
|
1598
|
+
"Ordered list": "Ordered list",
|
|
1599
|
+
"{item} moved to position {position} of {total}": "{item} moved to position {position} of {total}",
|
|
1600
|
+
"{item} removed": "{item} removed",
|
|
1601
|
+
"{item} added": "{item} added",
|
|
1602
|
+
"Organisations": "Organisations",
|
|
1603
|
+
"Provisioning": "Provisioning",
|
|
1604
|
+
"Suspended": "Suspended",
|
|
1605
|
+
"Deprovisioning": "Deprovisioning",
|
|
1606
|
+
"Archived": "Archived",
|
|
1607
|
+
"Retained": "Retained"
|
|
1588
1608
|
},
|
|
1589
1609
|
"plurals": {
|
|
1590
1610
|
"{count} selected": [
|
package/l10n/nl.json
CHANGED
|
@@ -1171,6 +1171,8 @@
|
|
|
1171
1171
|
"Due tomorrow": "Morgen",
|
|
1172
1172
|
"Due in {days} days": "Over {days} dagen",
|
|
1173
1173
|
"Available": "Beschikbaar",
|
|
1174
|
+
"{value} of {limit}": "{value} van {limit}",
|
|
1175
|
+
"{value} of {limit}, over the limit": "{value} van {limit}, boven de limiet",
|
|
1174
1176
|
"Ready to claim": "Klaar om op te pakken",
|
|
1175
1177
|
"In progress": "In behandeling",
|
|
1176
1178
|
"Completed": "Afgerond",
|
|
@@ -1770,7 +1772,40 @@
|
|
|
1770
1772
|
"{s} s": "{s} s",
|
|
1771
1773
|
"About this {app} installation.": "Over deze {app}-installatie.",
|
|
1772
1774
|
"{version}, up to date": "{version}, bijgewerkt",
|
|
1773
|
-
"for an SLA": "voor een SLA"
|
|
1775
|
+
"for an SLA": "voor een SLA",
|
|
1776
|
+
"About {name}": "Over {name}",
|
|
1777
|
+
"Sent message": "Verstuurd bericht",
|
|
1778
|
+
"Received message": "Ontvangen bericht",
|
|
1779
|
+
"Loading messages…": "Berichten laden…",
|
|
1780
|
+
"Move {item} up": "{item} omhoog",
|
|
1781
|
+
"Move {item} down": "{item} omlaag",
|
|
1782
|
+
"Remove {item}": "{item} verwijderen",
|
|
1783
|
+
"Nothing added yet.": "Nog niets toegevoegd.",
|
|
1784
|
+
"Ordered list": "Lijst op volgorde",
|
|
1785
|
+
"{item} moved to position {position} of {total}": "{item} staat nu op plaats {position} van {total}",
|
|
1786
|
+
"{item} removed": "{item} verwijderd",
|
|
1787
|
+
"{item} added": "{item} toegevoegd",
|
|
1788
|
+
"Question {n} of {total}": "Vraag {n} van {total}",
|
|
1789
|
+
"Questions answered": "Beantwoorde vragen",
|
|
1790
|
+
"(required)": "(verplicht)",
|
|
1791
|
+
"Answer this question to go on.": "Beantwoord deze vraag om verder te gaan.",
|
|
1792
|
+
"Finish": "Afronden",
|
|
1793
|
+
"Take or add a photo": "Foto maken of toevoegen",
|
|
1794
|
+
"Another photo": "Nog een foto",
|
|
1795
|
+
"Photo for question {n}": "Foto bij vraag {n}",
|
|
1796
|
+
"This checklist has no questions.": "Deze checklist heeft geen vragen.",
|
|
1797
|
+
"The checklist could not be loaded.": "De checklist kon niet worden geladen.",
|
|
1798
|
+
"The answer could not be saved. Try again.": "Het antwoord kon niet worden opgeslagen. Probeer het opnieuw.",
|
|
1799
|
+
"Checklist finished.": "Checklist afgerond.",
|
|
1800
|
+
"Not applicable": "Niet van toepassing",
|
|
1801
|
+
"Answer with a photo.": "Beantwoord met een foto.",
|
|
1802
|
+
"This answer needs a photo. Add one to go on.": "Bij dit antwoord hoort een foto. Voeg er een toe om verder te gaan.",
|
|
1803
|
+
"Organisations": "Organisaties",
|
|
1804
|
+
"Provisioning": "Wordt ingericht",
|
|
1805
|
+
"Suspended": "Opgeschort",
|
|
1806
|
+
"Deprovisioning": "Wordt opgeheven",
|
|
1807
|
+
"Archived": "Gearchiveerd",
|
|
1808
|
+
"Retained": "Bewaard"
|
|
1774
1809
|
},
|
|
1775
1810
|
"plurals": {
|
|
1776
1811
|
"{count} selected": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.98.0",
|
|
4
4
|
"description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
|
|
5
5
|
"homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"jsdoc-baselines:update": "node scripts/check-jsdoc.js --update",
|
|
87
87
|
"lint": "eslint src/ eslint/ testing/ tests/ e2e/",
|
|
88
88
|
"lint-fix": "npm run lint -- --fix",
|
|
89
|
-
"prepare": "husky || true",
|
|
89
|
+
"prepare": "husky || true; git config merge.l10n.name 'l10n catalogue key-wise merge' || true; git config merge.l10n.driver 'node scripts/merge-l10n.js %O %A %B %P' || true",
|
|
90
90
|
"prepublishOnly": "npm run build",
|
|
91
91
|
"smoke-baseline:update": "node scripts/update-smoke-baseline.js",
|
|
92
92
|
"styleguide": "cd styleguide && npm run server",
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
5
|
+
*
|
|
6
|
+
* Git merge driver for the l10n catalogues (`l10n/*.json`).
|
|
7
|
+
*
|
|
8
|
+
* Every branch that adds a library string appends to the end of `translations`
|
|
9
|
+
* (and sometimes `plurals`) in `l10n/en.json` and `l10n/nl.json`, so two
|
|
10
|
+
* branches always edit the same last lines and git calls it a conflict on
|
|
11
|
+
* every merge of development. A catalogue is a set of keys, not a sequence of
|
|
12
|
+
* lines, so this driver merges the three versions key by key: a key one side
|
|
13
|
+
* added is kept, a key one side removed is dropped, a key only one side
|
|
14
|
+
* changed takes that side's text, and only a key BOTH sides changed to
|
|
15
|
+
* different texts is a conflict.
|
|
16
|
+
*
|
|
17
|
+
* A conflict is written with `<<<<<<<` / `=======` / `>>>>>>>` markers around
|
|
18
|
+
* the two one-line entries and exit code 1, so git marks the path unmerged and
|
|
19
|
+
* the file does not parse until a person picks one. A side that does not
|
|
20
|
+
* parse is not guessed at: exit 1, our version untouched.
|
|
21
|
+
*
|
|
22
|
+
* Order: our keys in our order, then the keys only their side added, in
|
|
23
|
+
* theirs. Output is `JSON.stringify(doc, null, 2)` plus a newline, which is the
|
|
24
|
+
* shape every catalogue in this repo already has byte for byte.
|
|
25
|
+
*
|
|
26
|
+
* `merge=union` is not used on purpose: it concatenates both hunks, which on
|
|
27
|
+
* JSON drops a comma or doubles a brace, and a catalogue that does not parse
|
|
28
|
+
* turns every string back into its English key.
|
|
29
|
+
*
|
|
30
|
+
* Registration is local git config (git never runs a command a repository
|
|
31
|
+
* ships); `npm install` sets it through `prepare`, an existing clone runs:
|
|
32
|
+
*
|
|
33
|
+
* git config merge.l10n.driver 'node scripts/merge-l10n.js %O %A %B %P'
|
|
34
|
+
*
|
|
35
|
+
* Without it git falls back to its ordinary merge, as before.
|
|
36
|
+
*
|
|
37
|
+
* Usage (what git invokes): node scripts/merge-l10n.js %O %A %B %P
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
'use strict'
|
|
41
|
+
|
|
42
|
+
const fs = require('fs')
|
|
43
|
+
|
|
44
|
+
const MARKER_INDENT = ' '
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether two catalogue values are the same (a plural is an array of forms).
|
|
48
|
+
*
|
|
49
|
+
* @param {unknown} a One value, undefined for absent.
|
|
50
|
+
* @param {unknown} b The other.
|
|
51
|
+
* @return {boolean}
|
|
52
|
+
*/
|
|
53
|
+
function same(a, b) {
|
|
54
|
+
return a === b || (a !== undefined && b !== undefined && JSON.stringify(a) === JSON.stringify(b))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Three-way merge of one object of keys (a catalogue block).
|
|
59
|
+
*
|
|
60
|
+
* @param {object} base The ancestor's block.
|
|
61
|
+
* @param {object} ours Our block.
|
|
62
|
+
* @param {object} theirs Their block.
|
|
63
|
+
* @return {{merged: object, conflicts: Array<{key: string, ours: unknown, theirs: unknown}>}}
|
|
64
|
+
*/
|
|
65
|
+
function mergeBlock(base, ours, theirs) {
|
|
66
|
+
const merged = {}
|
|
67
|
+
const conflicts = []
|
|
68
|
+
const keys = [...Object.keys(ours), ...Object.keys(theirs).filter((key) => !Object.hasOwn(ours, key))]
|
|
69
|
+
for (const key of keys) {
|
|
70
|
+
const b = base[key]
|
|
71
|
+
const o = ours[key]
|
|
72
|
+
const t = theirs[key]
|
|
73
|
+
let value
|
|
74
|
+
if (same(o, t)) {
|
|
75
|
+
value = o
|
|
76
|
+
} else if (same(o, b)) {
|
|
77
|
+
value = t
|
|
78
|
+
} else if (same(t, b)) {
|
|
79
|
+
value = o
|
|
80
|
+
} else {
|
|
81
|
+
conflicts.push({ key, ours: o, theirs: t })
|
|
82
|
+
value = o === undefined ? t : o
|
|
83
|
+
}
|
|
84
|
+
if (value !== undefined) {
|
|
85
|
+
merged[key] = value
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { merged, conflicts }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Whether a value is a plain object (a catalogue block).
|
|
93
|
+
*
|
|
94
|
+
* @param {unknown} value The value.
|
|
95
|
+
* @return {boolean}
|
|
96
|
+
*/
|
|
97
|
+
function isBlock(value) {
|
|
98
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Merge three catalogue texts.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} baseText The ancestor ('' when both sides added the file).
|
|
105
|
+
* @param {string} oursText Our version.
|
|
106
|
+
* @param {string} theirsText Their version.
|
|
107
|
+
* @return {{text: string|null, conflicts: number}} The merged text (null when a side does not parse) and the number of conflicting keys.
|
|
108
|
+
*/
|
|
109
|
+
function mergeCatalogues(baseText, oursText, theirsText) {
|
|
110
|
+
let base
|
|
111
|
+
let ours
|
|
112
|
+
let theirs
|
|
113
|
+
try {
|
|
114
|
+
base = baseText.trim() === '' ? {} : JSON.parse(baseText)
|
|
115
|
+
ours = JSON.parse(oursText)
|
|
116
|
+
theirs = JSON.parse(theirsText)
|
|
117
|
+
} catch {
|
|
118
|
+
return { text: null, conflicts: 0 }
|
|
119
|
+
}
|
|
120
|
+
if (!isBlock(base) || !isBlock(ours) || !isBlock(theirs)) {
|
|
121
|
+
return { text: null, conflicts: 0 }
|
|
122
|
+
}
|
|
123
|
+
const doc = {}
|
|
124
|
+
const conflicts = []
|
|
125
|
+
const topKeys = [...Object.keys(ours), ...Object.keys(theirs).filter((key) => !Object.hasOwn(ours, key))]
|
|
126
|
+
for (const key of topKeys) {
|
|
127
|
+
const [b, o, t] = [base[key], ours[key], theirs[key]]
|
|
128
|
+
if (isBlock(o) || isBlock(t)) {
|
|
129
|
+
const result = mergeBlock(isBlock(b) ? b : {}, isBlock(o) ? o : {}, isBlock(t) ? t : {})
|
|
130
|
+
doc[key] = result.merged
|
|
131
|
+
result.conflicts.forEach((conflict) => conflicts.push({ block: key, ...conflict }))
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
const result = mergeBlock({ [key]: b }, o === undefined ? {} : { [key]: o }, t === undefined ? {} : { [key]: t })
|
|
135
|
+
if (Object.hasOwn(result.merged, key)) {
|
|
136
|
+
doc[key] = result.merged[key]
|
|
137
|
+
}
|
|
138
|
+
result.conflicts.forEach((conflict) => conflicts.push({ block: null, ...conflict }))
|
|
139
|
+
}
|
|
140
|
+
let text = JSON.stringify(doc, null, 2) + '\n'
|
|
141
|
+
for (const conflict of conflicts) {
|
|
142
|
+
text = markConflict(text, conflict)
|
|
143
|
+
}
|
|
144
|
+
return { text, conflicts: conflicts.length }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Put conflict markers around one key's entry, both sides on one line each.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} text The serialised merge.
|
|
151
|
+
* @param {{block: string|null, key: string, ours: unknown, theirs: unknown}} conflict The conflict.
|
|
152
|
+
* @return {string} The text with markers.
|
|
153
|
+
*/
|
|
154
|
+
function markConflict(text, conflict) {
|
|
155
|
+
const indent = conflict.block ? MARKER_INDENT : ' '
|
|
156
|
+
const entry = (value) => (value === undefined ? `${indent}(removed)` : `${indent}${JSON.stringify(conflict.key)}: ${JSON.stringify(value)},`)
|
|
157
|
+
const lines = text.split('\n')
|
|
158
|
+
const start = lines.findIndex((line) => line.startsWith(`${indent}${JSON.stringify(conflict.key)}: `))
|
|
159
|
+
if (start < 0) {
|
|
160
|
+
return text
|
|
161
|
+
}
|
|
162
|
+
let end = start
|
|
163
|
+
if (/[[{]$/.test(lines[start])) {
|
|
164
|
+
const close = indent + (lines[start].endsWith('[') ? ']' : '}')
|
|
165
|
+
while (end < lines.length - 1 && !lines[end].startsWith(close)) {
|
|
166
|
+
end += 1
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const block = ['<<<<<<< ours', entry(conflict.ours), '=======', entry(conflict.theirs), '>>>>>>> theirs']
|
|
170
|
+
lines.splice(start, end - start + 1, ...block)
|
|
171
|
+
return lines.join('\n')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = { mergeCatalogues }
|
|
175
|
+
|
|
176
|
+
if (require.main === module) {
|
|
177
|
+
const [basePath, oursPath, theirsPath, label] = process.argv.slice(2)
|
|
178
|
+
const read = (file) => {
|
|
179
|
+
try {
|
|
180
|
+
return fs.readFileSync(file, 'utf8')
|
|
181
|
+
} catch {
|
|
182
|
+
return ''
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const { text, conflicts } = mergeCatalogues(read(basePath), read(oursPath), read(theirsPath))
|
|
186
|
+
if (text === null) {
|
|
187
|
+
process.stderr.write(`merge-l10n: ${label || oursPath} does not parse on one side; resolve it by hand.\n`)
|
|
188
|
+
process.exit(1)
|
|
189
|
+
}
|
|
190
|
+
fs.writeFileSync(oursPath, text)
|
|
191
|
+
if (conflicts > 0) {
|
|
192
|
+
process.stderr.write(`merge-l10n: ${conflicts} key(s) in ${label || oursPath} were translated differently on both sides.\n`)
|
|
193
|
+
process.exit(1)
|
|
194
|
+
}
|
|
195
|
+
process.exit(0)
|
|
196
|
+
}
|
|
@@ -87,6 +87,7 @@ With loading state, custom components, translate, permissions, and custom page t
|
|
|
87
87
|
| `userSettingsTitle` | String | No | `''` | Title shown at the top of the hosted `NcAppSettingsDialog`. Empty (the default) resolves to "User settings" from the library catalogue ("Persoonlijke instellingen" in Dutch), then the app's `translate`. Override per app to brand the modal (e.g. `'Decidiq preferences'`). |
|
|
88
88
|
| `userSettingsEyebrow` | String | No | `''` | Board look: the line above the user-settings title. Empty uses "Your account" ("Uw account"). Goes through the app's `translate`. |
|
|
89
89
|
| `userSettingsSubtitle` | String | No | `''` | Board look: the sentence under the user-settings title. Empty uses "Only for you. Colleagues keep their own settings." Goes through the app's `translate`. |
|
|
90
|
+
| `userSettingsNextcloudLinks` | Array | No | `[]` | Board look: Nextcloud's own personal settings, listed first in the user-settings navigation as links, `[{ label, section? \| href?, icon? }]`. With any, the app's sections sit under one entry named after the app. |
|
|
90
91
|
| `userSettingsSections` | Array | No | `null` | The user-settings sections to draw, in order: `app` (the app's slot content), `notifications`, `credentials`, `walkthrough`, `integrations`. A key left out is not drawn. `null` keeps the defaults: every section after `#user-settings-sections` content, or only the `#user-settings` content and the integrations section. |
|
|
91
92
|
| `adminSettingsTitle` | String | No | `''` | Title shown at the top of the admin-settings `NcAppSettingsDialog`. Empty (the default) resolves to `translate('Administration')`. Override per app (e.g. `'Pipelinq administration'`). |
|
|
92
93
|
| `requiresApps` | Array | No | `['openregister']` | App ids that MUST be installed for the host app to function. Checked against the OCS capabilities API on mount. When any required app is missing, CnAppRoot renders the empty state from the `or-missing` slot (default: `<NcEmptyContent>`) instead of the renderer. Pass `[]` to opt out (e.g. launchpad, the docs/styleguide app) — useful for any consumer that does not depend on OpenRegister. |
|
|
@@ -1687,6 +1687,20 @@ export default {
|
|
|
1687
1687
|
default: '',
|
|
1688
1688
|
},
|
|
1689
1689
|
|
|
1690
|
+
/**
|
|
1691
|
+
* Board look: Nextcloud's own personal settings, listed first in the
|
|
1692
|
+
* user-settings navigation as links, `[{ label, section? | href?, icon? }]`
|
|
1693
|
+
* (`section` is a personal settings section id). With any, the app's
|
|
1694
|
+
* sections sit under one entry named after the app. Labels go through
|
|
1695
|
+
* the app's `translate`.
|
|
1696
|
+
*
|
|
1697
|
+
* @type {Array<object>}
|
|
1698
|
+
*/
|
|
1699
|
+
userSettingsNextcloudLinks: {
|
|
1700
|
+
type: Array,
|
|
1701
|
+
default: () => [],
|
|
1702
|
+
},
|
|
1703
|
+
|
|
1690
1704
|
/**
|
|
1691
1705
|
* The sections of the user-settings modal, in the order they are
|
|
1692
1706
|
* drawn. Keys: `app` (the content of the `#user-settings-sections`
|
|
@@ -3143,6 +3157,9 @@ export default {
|
|
|
3143
3157
|
if (this.userSettingsSubtitle) {
|
|
3144
3158
|
props.subtitle = this.translate(this.userSettingsSubtitle)
|
|
3145
3159
|
}
|
|
3160
|
+
if (Array.isArray(this.userSettingsNextcloudLinks) && this.userSettingsNextcloudLinks.length > 0) {
|
|
3161
|
+
props.nextcloudLinks = this.userSettingsNextcloudLinks.map((link) => ({ ...link, label: this.translate(link?.label || '') }))
|
|
3162
|
+
}
|
|
3146
3163
|
return props
|
|
3147
3164
|
},
|
|
3148
3165
|
|
|
@@ -24,6 +24,24 @@
|
|
|
24
24
|
</span>
|
|
25
25
|
</template>
|
|
26
26
|
|
|
27
|
+
<!-- Built-in "actions" widget: row actions as inline buttons ("Aan zaak
|
|
28
|
+
koppelen", "Nieuwe zaak"). The page resolves the buttons
|
|
29
|
+
(widgetProps.items) and runs a click (widgetProps.run). -->
|
|
30
|
+
<template v-else-if="widget === 'actions'">
|
|
31
|
+
<span class="cn-cell-renderer__actions" data-testid="cn-cell-actions">
|
|
32
|
+
<NcButton
|
|
33
|
+
v-for="item in actionItems"
|
|
34
|
+
:key="item.key"
|
|
35
|
+
:variant="item.variant"
|
|
36
|
+
size="small"
|
|
37
|
+
:disabled="item.disabled"
|
|
38
|
+
:data-testid="`cn-cell-action-${item.key}`"
|
|
39
|
+
@click.stop="runAction(item)">
|
|
40
|
+
{{ item.label }}
|
|
41
|
+
</NcButton>
|
|
42
|
+
</span>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
27
45
|
<!-- Built-in "badge" widget: renders the value as a status pill. An enum
|
|
28
46
|
value shows its label (x-enum-labels, translated) unless a formatter
|
|
29
47
|
shapes it; the colour stays keyed on the raw value. -->
|
|
@@ -350,7 +368,7 @@
|
|
|
350
368
|
|
|
351
369
|
<script>
|
|
352
370
|
import { getCanonicalLocale, translate as t } from '@nextcloud/l10n'
|
|
353
|
-
import { NcAvatar, NcCheckboxRadioSwitch, NcDateTime } from '@nextcloud/vue'
|
|
371
|
+
import { NcAvatar, NcButton, NcCheckboxRadioSwitch, NcDateTime } from '@nextcloud/vue'
|
|
354
372
|
import CheckBold from 'vue-material-design-icons/CheckBold.vue'
|
|
355
373
|
import CnFkResolveCell from '../CnFkResolveCell/CnFkResolveCell.vue'
|
|
356
374
|
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
@@ -398,6 +416,7 @@ export default {
|
|
|
398
416
|
CnGroupNameCell,
|
|
399
417
|
CheckBold,
|
|
400
418
|
NcAvatar,
|
|
419
|
+
NcButton,
|
|
401
420
|
NcDateTime,
|
|
402
421
|
},
|
|
403
422
|
|
|
@@ -586,6 +605,24 @@ export default {
|
|
|
586
605
|
},
|
|
587
606
|
|
|
588
607
|
computed: {
|
|
608
|
+
/**
|
|
609
|
+
* The `actions` widget's buttons for this row: the page's resolved
|
|
610
|
+
* `widgetProps.items` (a list, or a function of the row returning
|
|
611
|
+
* one), each `{ key, label, variant?, disabled? }`.
|
|
612
|
+
*
|
|
613
|
+
* @return {Array<{key: string, label: string, variant: string, disabled: boolean}>}
|
|
614
|
+
* @spec openspec/changes/table-actions-column/specs/index-page/spec.md#requirement-a-column-can-show-row-actions-as-buttons
|
|
615
|
+
*/
|
|
616
|
+
actionItems() {
|
|
617
|
+
const raw = typeof this.widgetProps?.items === 'function' ? this.widgetProps.items(this.row) : this.widgetProps?.items
|
|
618
|
+
const items = Array.isArray(raw) ? raw : []
|
|
619
|
+
return items.map((item) => ({
|
|
620
|
+
...item,
|
|
621
|
+
variant: item.variant || 'secondary',
|
|
622
|
+
disabled: item.disabled === true,
|
|
623
|
+
}))
|
|
624
|
+
},
|
|
625
|
+
|
|
589
626
|
/**
|
|
590
627
|
* Whether the "switch" widget is on: the pending state while a toggle
|
|
591
628
|
* is in flight, else `widgetProps.checked(row)`, else the value.
|
|
@@ -1419,6 +1456,19 @@ export default {
|
|
|
1419
1456
|
},
|
|
1420
1457
|
|
|
1421
1458
|
methods: {
|
|
1459
|
+
/**
|
|
1460
|
+
* Run an `actions` widget button for this row through the page.
|
|
1461
|
+
*
|
|
1462
|
+
* @param {{key: string}} item The button.
|
|
1463
|
+
* @return {void}
|
|
1464
|
+
* @spec openspec/changes/table-actions-column/specs/index-page/spec.md#requirement-a-column-can-show-row-actions-as-buttons
|
|
1465
|
+
*/
|
|
1466
|
+
runAction(item) {
|
|
1467
|
+
if (typeof this.widgetProps?.run === 'function') {
|
|
1468
|
+
this.widgetProps.run(item.key, this.row)
|
|
1469
|
+
}
|
|
1470
|
+
},
|
|
1471
|
+
|
|
1422
1472
|
/**
|
|
1423
1473
|
* The "switch" widget was flipped: show the new state and hand it to
|
|
1424
1474
|
* `widgetProps.onToggle(row, on)`. A rejection puts the switch back.
|
|
@@ -1555,6 +1605,12 @@ export default {
|
|
|
1555
1605
|
</script>
|
|
1556
1606
|
|
|
1557
1607
|
<style scoped>
|
|
1608
|
+
.cn-cell-renderer__actions {
|
|
1609
|
+
display: inline-flex;
|
|
1610
|
+
flex-wrap: wrap;
|
|
1611
|
+
gap: 8px;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1558
1614
|
.cn-cell-renderer__icon-label {
|
|
1559
1615
|
display: inline-flex;
|
|
1560
1616
|
align-items: center;
|
|
@@ -18,7 +18,7 @@ When `CnDashboardPage` resolves a widget definition with `type: "chart"`, it mou
|
|
|
18
18
|
}
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Supported `props` keys forwarded by the dispatcher: `chartKind` (→ `type`), `series`, `categories`, `labels`, `options`, `colors`, `toolbar`, `legend`, `height`, `width`, `unavailableLabel`, plus the display passthrough `horizontal`, `legendPosition`, `valueAxisBaseline`, `valueFormat`, `colorMap`, `emptyLabel`, `views`, `endpointSource`, `barMax`, `footnote`, `highlight`. The `dataSource` field resolves declaratively: `aggregate: 'count'` / raw `graphql` via GraphQL, `bucket` / `groupBy` / the OBJECT-form `aggregate` (Wave 3: group-by + topN/Other + labelResolve) via OpenRegister's REST aggregation facets, with an optional sibling `drilldown: { route, filterParam }` that navigates on segment/bar click with the raw category key in the query.
|
|
21
|
+
Supported `props` keys forwarded by the dispatcher: `chartKind` (→ `type`), `series`, `categories`, `labels`, `options`, `colors`, `toolbar`, `legend`, `height`, `width`, `unavailableLabel`, plus the display passthrough `horizontal`, `legendPosition`, `valueAxisBaseline`, `valueFormat`, `colorMap`, `emptyLabel`, `views`, `endpointSource`, `barMax`, `footnote`, `highlight`, `cellSource`. The `dataSource` field resolves declaratively: `aggregate: 'count'` / raw `graphql` via GraphQL, `bucket` / `groupBy` / the OBJECT-form `aggregate` (Wave 3: group-by + topN/Other + labelResolve) via OpenRegister's REST aggregation facets, with an optional sibling `drilldown: { route, filterParam }` that navigates on segment/bar click with the raw category key in the query.
|
|
22
22
|
|
|
23
23
|
Area chart — time series data:
|
|
24
24
|
|
|
@@ -109,6 +109,7 @@ Fallback slot — shown when ApexCharts is not available:
|
|
|
109
109
|
| `barMax` | Number | `null` | `bar-list`: the value a full track stands for (100 for percentages); null takes the largest value |
|
|
110
110
|
| `footnote` | String | `''` | `bar-list` / `matrix`: a line under the rows, through the host's translate |
|
|
111
111
|
| `highlight` | Object | `null` | `matrix`: the cell to outline, `{ row, column, columnKeys? }`, tokens such as `@object.behaviour` resolved |
|
|
112
|
+
| `cellSource` | Object | `null` | `matrix`: read the cells from records that each hold one cell (`{ register, schema, filter?, rowField, columnField, valueField, … }`), or with `cellsPath` from the cells inside one record, see [Bar list and matrix](#bar-list-and-matrix) |
|
|
112
113
|
| `valueAxisBaseline` | String | `'auto'` | How the value axis picks its baseline, so a small difference cannot read as a dramatic one. `'auto'` anchors magnitude marks (bar/area) at zero and stops line charts zooming past a quarter of the data's magnitude; `'zero'` always anchors at zero; `'fit'` restores plain ApexCharts autoscaling — use it for series that live far from zero (a percentage hovering 95–99), where a zero baseline flattens the signal. Pie-family charts, negative series, empty data and stacked charts (`options.chart.stacked` — where the visible height is the per-category sum, not the largest datapoint) always autoscale; an explicit `options.yaxis.min`/`max` still wins. The ceiling rounds up to a nice number (7 → 8, 62 → 80). |
|
|
113
114
|
| `valueFormat` | String\|Object | `null` | Named value formatter for the value axis + tooltip: `'currency'`, `'currency-compact'`, `'percent'`, or `{ name, currency?, decimals? }` |
|
|
114
115
|
| `colorMap` | Object | `null` | Per-category colour map (`{ categoryLabel: cssColor }`) for pie-family slices and (distributed) bar categories |
|
|
@@ -138,9 +138,11 @@ import { useDataSource } from '../../composables/useDataSource.js'
|
|
|
138
138
|
import { getByPath, useEndpointSource } from '../../composables/useEndpointSource.js'
|
|
139
139
|
import { useObjectStore } from '../../store/useObjectStore.js'
|
|
140
140
|
import { resolveObjectOpType } from '../../utils/actionsDispatcher.js'
|
|
141
|
+
import { fetchReferenceObjects } from '../../utils/boardReferenceColumns.js'
|
|
141
142
|
import { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'
|
|
142
143
|
import { safeCurrencyCode } from '../../utils/formatMetric.js'
|
|
143
|
-
import {
|
|
144
|
+
import { cellsFromRecord, pivotCells } from '../../utils/matrixCells.js'
|
|
145
|
+
import { hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
|
|
144
146
|
import { userLocale } from '../../utils/userLocale.js'
|
|
145
147
|
|
|
146
148
|
/**
|
|
@@ -388,6 +390,26 @@ export default {
|
|
|
388
390
|
default: null,
|
|
389
391
|
},
|
|
390
392
|
|
|
393
|
+
/**
|
|
394
|
+
* `matrix`: read the cells from records that each hold one cell, in
|
|
395
|
+
* a register: `{ register, schema, filter?, rowField, columnField,
|
|
396
|
+
* valueField, rowLabelField?, columnLabelField?, rowLabels?,
|
|
397
|
+
* columnLabels?, valueLabels?, rowOrder?, columnOrder?, rowOrderField?,
|
|
398
|
+
* columnOrderField?, limit? }`. `filter` takes tokens such as
|
|
399
|
+
* `@object.matrixVersion`; the list waits while one is unresolved.
|
|
400
|
+
* With `cellsPath` the matrix sits inside ONE record instead: the
|
|
401
|
+
* first record `filter` finds (else `fallbackFilter`), its array at
|
|
402
|
+
* `cellsPath` narrowed by `cellFilter` (tokens allowed), the axis
|
|
403
|
+
* order from `rowOrderPath` / `columnOrderPath` on that record.
|
|
404
|
+
* Wins over `series` / `categories`.
|
|
405
|
+
*
|
|
406
|
+
* @spec openspec/changes/matrix-cell-records/specs/matrix-cell-records/spec.md#requirement-a-matrix-can-read-its-cells-from-records
|
|
407
|
+
*/
|
|
408
|
+
cellSource: {
|
|
409
|
+
type: Object,
|
|
410
|
+
default: null,
|
|
411
|
+
},
|
|
412
|
+
|
|
391
413
|
/**
|
|
392
414
|
* Chart data series. Format depends on chart type.
|
|
393
415
|
* For line/area/bar: [{ name: string, data: number[] }]
|
|
@@ -800,6 +822,8 @@ export default {
|
|
|
800
822
|
// `drilldown` click navigates with the filterable value, not the
|
|
801
823
|
// display label.
|
|
802
824
|
aggregateData: null,
|
|
825
|
+
// The `cellSource` matrix, laid out (`{ series, categories, columnKeys }`), or null.
|
|
826
|
+
cellData: null,
|
|
803
827
|
// Active `views[]` switcher key (null = the first view). Local
|
|
804
828
|
// UI state only — never persisted.
|
|
805
829
|
activeViewKey: null,
|
|
@@ -900,6 +924,9 @@ export default {
|
|
|
900
924
|
* and `labels`.
|
|
901
925
|
*/
|
|
902
926
|
resolvedSeries() {
|
|
927
|
+
if (this.cellData) {
|
|
928
|
+
return this.cellData.series
|
|
929
|
+
}
|
|
903
930
|
const ep = this.endpointChartData
|
|
904
931
|
if (ep) {
|
|
905
932
|
return ep.series
|
|
@@ -913,6 +940,9 @@ export default {
|
|
|
913
940
|
},
|
|
914
941
|
|
|
915
942
|
resolvedCategories() {
|
|
943
|
+
if (this.cellData) {
|
|
944
|
+
return this.cellData.categories
|
|
945
|
+
}
|
|
916
946
|
const ep = this.endpointChartData
|
|
917
947
|
if (ep) {
|
|
918
948
|
return ep.categories
|
|
@@ -926,6 +956,9 @@ export default {
|
|
|
926
956
|
},
|
|
927
957
|
|
|
928
958
|
resolvedLabels() {
|
|
959
|
+
if (this.cellData) {
|
|
960
|
+
return this.cellData.categories
|
|
961
|
+
}
|
|
929
962
|
const ep = this.endpointChartData
|
|
930
963
|
if (ep) {
|
|
931
964
|
return ep.labels
|
|
@@ -1316,6 +1349,30 @@ export default {
|
|
|
1316
1349
|
return this.emptyLabel !== '' && !this.hasChartData
|
|
1317
1350
|
},
|
|
1318
1351
|
|
|
1352
|
+
/**
|
|
1353
|
+
* The `cellSource` list as it would be asked, tokens resolved, or null
|
|
1354
|
+
* while there is none or a filter token is unresolved. Refetches when
|
|
1355
|
+
* it changes.
|
|
1356
|
+
*
|
|
1357
|
+
* @return {string|null} The key.
|
|
1358
|
+
* @spec openspec/changes/matrix-cell-records/specs/matrix-cell-records/spec.md#requirement-a-matrix-can-read-its-cells-from-records
|
|
1359
|
+
*/
|
|
1360
|
+
cellSourceKey() {
|
|
1361
|
+
const src = this.cellSource
|
|
1362
|
+
if (!src || typeof src !== 'object' || !src.register || !src.schema || !src.rowField || !src.columnField || !src.valueField) {
|
|
1363
|
+
return null
|
|
1364
|
+
}
|
|
1365
|
+
const ctx = this.chartTokenCtx()
|
|
1366
|
+
const resolve = (value) => resolveFilterTokens(value && typeof value === 'object' ? value : {}, ctx)
|
|
1367
|
+
const filter = resolve(src.filter)
|
|
1368
|
+
const cellFilter = resolve(src.cellFilter)
|
|
1369
|
+
if (hasUnresolvedTokens(filter) || hasUnresolvedTokens(cellFilter)) {
|
|
1370
|
+
return null
|
|
1371
|
+
}
|
|
1372
|
+
const fallbackFilter = src.fallbackFilter ? resolve(src.fallbackFilter) : null
|
|
1373
|
+
return JSON.stringify({ register: src.register, schema: src.schema, filter, fallbackFilter, cellFilter, limit: src.cellsPath ? 1 : (src.limit || 500) })
|
|
1374
|
+
},
|
|
1375
|
+
|
|
1319
1376
|
/**
|
|
1320
1377
|
* The rows of a `bar-list`: the categories of the first displayed
|
|
1321
1378
|
* series, each with its value's share of `barMax` (or the largest
|
|
@@ -1370,6 +1427,13 @@ export default {
|
|
|
1370
1427
|
cells: columns.map((column, index) => {
|
|
1371
1428
|
const raw = Array.isArray(entry.data) ? entry.data[index] : undefined
|
|
1372
1429
|
const isCurrent = current !== null && current.row === rowIndex && current.column === index
|
|
1430
|
+
const valueLabels = this.cellSource && typeof this.cellSource.valueLabels === 'object' ? this.cellSource.valueLabels : null
|
|
1431
|
+
if (valueLabels && raw !== null && raw !== undefined && typeof valueLabels[String(raw)] === 'string') {
|
|
1432
|
+
const label = valueLabels[String(raw)]
|
|
1433
|
+
const tone = map[String(raw)] || map[label]
|
|
1434
|
+
const level = TONE_LEVELS[tone] ?? 0
|
|
1435
|
+
return { text: this.hostTranslate(label), level: level || 'tone', color: level ? '' : (tone || ''), current: isCurrent }
|
|
1436
|
+
}
|
|
1373
1437
|
if (typeof raw === 'string') {
|
|
1374
1438
|
const tone = map[raw]
|
|
1375
1439
|
const level = TONE_LEVELS[tone] ?? 0
|
|
@@ -1409,7 +1473,8 @@ export default {
|
|
|
1409
1473
|
const series = Array.isArray(this.plottedSeries) ? this.plottedSeries : []
|
|
1410
1474
|
const rowIndex = series.findIndex((entry, i) => entry && (String(entry.key ?? '') === row || String(entry.name ?? '') === row || String(i + 1) === row))
|
|
1411
1475
|
const columns = Array.isArray(this.resolvedCategories) ? this.resolvedCategories.map((c) => String(c)) : []
|
|
1412
|
-
const
|
|
1476
|
+
const declared = Array.isArray(def.columnKeys) ? def.columnKeys : (this.cellData ? this.cellData.columnKeys : [])
|
|
1477
|
+
const keys = declared.map((k) => String(k))
|
|
1413
1478
|
let columnIndex = keys.indexOf(column)
|
|
1414
1479
|
if (columnIndex < 0) {
|
|
1415
1480
|
columnIndex = columns.findIndex((c, i) => c === column || String(i + 1) === column)
|
|
@@ -1586,6 +1651,10 @@ export default {
|
|
|
1586
1651
|
aggregateKey() {
|
|
1587
1652
|
this.fetchAggregateSource()
|
|
1588
1653
|
},
|
|
1654
|
+
|
|
1655
|
+
cellSourceKey() {
|
|
1656
|
+
this.fetchCells()
|
|
1657
|
+
},
|
|
1589
1658
|
},
|
|
1590
1659
|
|
|
1591
1660
|
created() {
|
|
@@ -1597,6 +1666,7 @@ export default {
|
|
|
1597
1666
|
this.fetchGroupBy()
|
|
1598
1667
|
this.fetchTimeBucket()
|
|
1599
1668
|
this.fetchAggregateSource()
|
|
1669
|
+
this.fetchCells()
|
|
1600
1670
|
// Subscribe to the widget Refresh bus (B3 event-bus opt-in mode)
|
|
1601
1671
|
// FIRST, before the ResizeObserver early-return — environments
|
|
1602
1672
|
// without ResizeObserver (jsdom) must still get the subscription.
|
|
@@ -1753,6 +1823,7 @@ export default {
|
|
|
1753
1823
|
this.fetchGroupBy(),
|
|
1754
1824
|
this.fetchTimeBucket(),
|
|
1755
1825
|
this.fetchAggregateSource(),
|
|
1826
|
+
this.fetchCells(),
|
|
1756
1827
|
])
|
|
1757
1828
|
},
|
|
1758
1829
|
|
|
@@ -1973,6 +2044,39 @@ export default {
|
|
|
1973
2044
|
}
|
|
1974
2045
|
},
|
|
1975
2046
|
|
|
2047
|
+
/**
|
|
2048
|
+
* Read the `cellSource` records and lay them out as the matrix: one
|
|
2049
|
+
* record per cell, or with `cellsPath` the cells inside one record. A
|
|
2050
|
+
* failed read leaves the matrix empty rather than showing stale
|
|
2051
|
+
* cells; an answer to an older request is dropped.
|
|
2052
|
+
*
|
|
2053
|
+
* @return {Promise<void>}
|
|
2054
|
+
* @spec openspec/changes/matrix-cell-records/specs/matrix-cell-records/spec.md#requirement-a-matrix-can-read-its-cells-from-records
|
|
2055
|
+
* @spec openspec/changes/matrix-cell-records/specs/matrix-cell-records/spec.md#requirement-a-matrix-can-read-its-cells-from-one-record
|
|
2056
|
+
*/
|
|
2057
|
+
async fetchCells() {
|
|
2058
|
+
const key = this.cellSourceKey
|
|
2059
|
+
if (key === null) {
|
|
2060
|
+
this.cellData = null
|
|
2061
|
+
return
|
|
2062
|
+
}
|
|
2063
|
+
const { register, schema, filter, fallbackFilter, cellFilter, limit } = JSON.parse(key)
|
|
2064
|
+
this._cellRequest = key
|
|
2065
|
+
let records = await fetchReferenceObjects({ register, schema, filter, limit }).catch(() => [])
|
|
2066
|
+
if (this.cellSource.cellsPath && records.length === 0 && fallbackFilter) {
|
|
2067
|
+
records = await fetchReferenceObjects({ register, schema, filter: fallbackFilter, limit }).catch(() => [])
|
|
2068
|
+
}
|
|
2069
|
+
if (this._cellRequest !== key) {
|
|
2070
|
+
return
|
|
2071
|
+
}
|
|
2072
|
+
if (this.cellSource.cellsPath) {
|
|
2073
|
+
const { cells, config } = cellsFromRecord(records[0] || null, this.cellSource, cellFilter)
|
|
2074
|
+
this.cellData = pivotCells(cells, config)
|
|
2075
|
+
return
|
|
2076
|
+
}
|
|
2077
|
+
this.cellData = pivotCells(records, this.cellSource)
|
|
2078
|
+
},
|
|
2079
|
+
|
|
1976
2080
|
/**
|
|
1977
2081
|
* Resolve the OBJECT-form `dataSource.aggregate` (Wave 3, #91) into
|
|
1978
2082
|
* chart data: a categorical group-by over the schema's objects with
|