@conduction/nextcloud-vue 2.62.0 → 2.63.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/CnAdminActionCard/CnAdminActionCard.vue.js +10 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js.map +1 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js +189 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js.map +1 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js +72 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js.map +1 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +271 -32
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +99 -9
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +39 -18
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +53 -37
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js +10 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +293 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +189 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +216 -8
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +118 -50
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js +3 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +234 -9
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +96 -57
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +51 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/selfModeActions.js +3 -3
- package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +36 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js +121 -12
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js.map +1 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js +3 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +326 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +112 -26
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +10 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +337 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +102 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +34 -0
- package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -0
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +131 -8
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js +40 -38
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +8 -6
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.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/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
- package/dist/esm/composables/useDependencyCheck.js +65 -0
- package/dist/esm/composables/useDependencyCheck.js.map +1 -0
- package/dist/esm/composables/useWalkthrough.js +218 -6
- package/dist/esm/composables/useWalkthrough.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 +43 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +43 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +449 -0
- package/dist/esm/store/useObjectStore.js +8 -0
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/columnFilters.js +311 -0
- package/dist/esm/utils/columnFilters.js.map +1 -0
- package/dist/esm/utils/groupAutocomplete.js +123 -0
- package/dist/esm/utils/groupAutocomplete.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +2 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/pickerOptions.js +198 -0
- package/dist/esm/utils/pickerOptions.js.map +1 -0
- package/dist/esm/utils/schema.js +83 -7
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/timelineEvents.js +174 -0
- package/dist/esm/utils/timelineEvents.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/walkthroughSignals.js +88 -0
- package/dist/esm/utils/walkthroughSignals.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +9369 -5193
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +449 -0
- package/l10n/en.json +43 -0
- package/l10n/nl.json +43 -0
- package/package.json +1 -1
- package/src/components/CnAdminActionCard/CnAdminActionCard.vue +234 -0
- package/src/components/CnAdminActionCard/index.js +4 -0
- package/src/components/CnAppNav/CnAppNav.vue +271 -32
- package/src/components/CnAppRoot/CnAppRoot.vue +129 -15
- package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
- package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -2
- package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
- package/src/components/CnDataTable/CnDataTable.md +3 -0
- package/src/components/CnDataTable/CnDataTable.vue +260 -4
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
- package/src/components/CnFieldHelper/CnFieldHelper.vue +4 -2
- package/src/components/CnFormDialog/CnFormDialog.vue +290 -31
- package/src/components/CnIndexPage/CnIndexPage.vue +55 -2
- package/src/components/CnIndexPage/selfModeActions.js +3 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +39 -1
- package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
- package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +477 -0
- package/src/components/CnTimelineWidget/dashboardRegistration.js +32 -0
- package/src/components/CnTimelineWidget/index.js +4 -0
- package/src/components/CnWalkthrough/CnWalkthrough.vue +143 -10
- package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
- package/src/components/index.js +2 -0
- package/src/composables/useDependencyCheck.js +61 -0
- package/src/composables/useWalkthrough.js +217 -5
- package/src/css/dashboard.css +49 -0
- package/src/css/detail-page.css +19 -0
- package/src/css/table.css +81 -0
- package/src/index.js +2 -0
- package/src/schemas/app-manifest-v2.schema.json +23 -1
- package/src/store/useObjectStore.js +8 -0
- package/src/utils/columnFilters.js +324 -0
- package/src/utils/groupAutocomplete.js +119 -0
- package/src/utils/libraryWidgetKeys.js +2 -0
- package/src/utils/pickerOptions.js +194 -0
- package/src/utils/schema.js +83 -7
- package/src/utils/timelineEvents.js +171 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
- package/src/utils/walkthroughSignals.js +94 -0
|
@@ -45,6 +45,44 @@ function byManifestOrder(a, b) {
|
|
|
45
45
|
return a.order - b.order
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/** A `:name` page-path segment: name, optional `(regexp)`, optional modifier. */
|
|
49
|
+
const PARAM_SEGMENT = /^:(\w+)(\([^)]*\))?([?*+])?$/;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Whether a `PARAM_SEGMENT` match may be left out (`?` or `*`).
|
|
53
|
+
*
|
|
54
|
+
* @param {Array<string>} param The match.
|
|
55
|
+
* @return {boolean} True for an optional segment.
|
|
56
|
+
*/
|
|
57
|
+
function isOptionalParam(param) {
|
|
58
|
+
return param[3] === '?' || param[3] === '*'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Whether a route param value fills its segment.
|
|
63
|
+
*
|
|
64
|
+
* @param {unknown} value The value.
|
|
65
|
+
* @return {boolean} False for undefined, null and the empty string.
|
|
66
|
+
*/
|
|
67
|
+
function isFilled(value) {
|
|
68
|
+
return value !== undefined && value !== null && value !== ''
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Decode one percent-encoded path segment, or keep it as is when it is not
|
|
73
|
+
* valid percent-encoding.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} segment The segment.
|
|
76
|
+
* @return {string} The decoded segment.
|
|
77
|
+
*/
|
|
78
|
+
function decodeSegment(segment) {
|
|
79
|
+
try {
|
|
80
|
+
return decodeURIComponent(segment)
|
|
81
|
+
} catch {
|
|
82
|
+
return segment
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
48
86
|
var script = {
|
|
49
87
|
name: 'CnAppNav',
|
|
50
88
|
|
|
@@ -575,7 +613,9 @@ var script = {
|
|
|
575
613
|
* `/expenses/:id`) light up — and auto-expand — their index entry
|
|
576
614
|
* even though the detail route name is not in the menu. Longest
|
|
577
615
|
* prefix disambiguates nested namespaces (e.g. `/pos` vs
|
|
578
|
-
* `/pos/refunds`).
|
|
616
|
+
* `/pos/refunds`). An entry with `params` matches by its page path
|
|
617
|
+
* with those params filled in (see `pageMatchDepth`). Returns null
|
|
618
|
+
* when nothing matches.
|
|
579
619
|
*
|
|
580
620
|
* @return {string|null}
|
|
581
621
|
*/
|
|
@@ -596,20 +636,15 @@ var script = {
|
|
|
596
636
|
return routeName ?? null
|
|
597
637
|
}
|
|
598
638
|
let best = null;
|
|
599
|
-
let
|
|
639
|
+
let bestDepth = 0;
|
|
600
640
|
for (const it of flat) {
|
|
601
641
|
if (!it.route) {
|
|
602
642
|
continue
|
|
603
643
|
}
|
|
604
|
-
const
|
|
605
|
-
if (
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
if (path === pagePath || path.startsWith(pagePath + '/')) {
|
|
609
|
-
if (pagePath.length > bestLen) {
|
|
610
|
-
best = it.route;
|
|
611
|
-
bestLen = pagePath.length;
|
|
612
|
-
}
|
|
644
|
+
const depth = this.pageMatchDepth(it, path);
|
|
645
|
+
if (depth > bestDepth) {
|
|
646
|
+
best = it.route;
|
|
647
|
+
bestDepth = depth;
|
|
613
648
|
}
|
|
614
649
|
}
|
|
615
650
|
return best ?? routeName ?? null
|
|
@@ -658,7 +693,9 @@ var script = {
|
|
|
658
693
|
* that route, else the first in menu order.
|
|
659
694
|
*
|
|
660
695
|
* Null whenever the existing rule already marks an entry, so a list
|
|
661
|
-
* with an entry without a `query` behaves as it did.
|
|
696
|
+
* with an entry without a `query` behaves as it did. An entry with
|
|
697
|
+
* `params` is a candidate only when the current path sits below its
|
|
698
|
+
* own filled-in page path, so another param value is never marked.
|
|
662
699
|
*
|
|
663
700
|
* @return {object|null} The entry to mark, or null.
|
|
664
701
|
* @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-a-page-below-a-list-marks-one-menu-entry
|
|
@@ -668,7 +705,9 @@ var script = {
|
|
|
668
705
|
if (!active || this.$route?.name === active) {
|
|
669
706
|
return null
|
|
670
707
|
}
|
|
671
|
-
const
|
|
708
|
+
const path = this.$route?.path;
|
|
709
|
+
const entries = this.flatEntries.filter((entry) => !entry.href && entry.route === active
|
|
710
|
+
&& (!this.hasParams(entry) || this.pageMatchDepth(entry, path) > 0));
|
|
672
711
|
if (entries.length === 0 || entries.some((entry) => this.isActiveByRule(entry))) {
|
|
673
712
|
return null
|
|
674
713
|
}
|
|
@@ -718,6 +757,8 @@ var script = {
|
|
|
718
757
|
// stays out of `data()`: a reactive Set mutated inside render would
|
|
719
758
|
// re-trigger the render effect.
|
|
720
759
|
this._autoCountWarned = new Set();
|
|
760
|
+
// Same latch, for warnUnfilledRouteParam() (`itemTo()` runs during render).
|
|
761
|
+
this._unfilledParamWarned = new Set();
|
|
721
762
|
},
|
|
722
763
|
|
|
723
764
|
methods: {
|
|
@@ -926,7 +967,19 @@ var script = {
|
|
|
926
967
|
.sort(byManifestOrder)
|
|
927
968
|
},
|
|
928
969
|
|
|
970
|
+
/**
|
|
971
|
+
* The label an entry shows: run through the translate function, or
|
|
972
|
+
* as written when the entry sets `translateLabel: false` (a
|
|
973
|
+
* user-authored title that may equal a translation key). Every
|
|
974
|
+
* place the nav shows an entry's label reads it from here.
|
|
975
|
+
*
|
|
976
|
+
* @param {object} item Menu entry.
|
|
977
|
+
* @return {string} The label to render as text.
|
|
978
|
+
*/
|
|
929
979
|
resolveLabel(item) {
|
|
980
|
+
if (item.translateLabel === false) {
|
|
981
|
+
return item.label
|
|
982
|
+
}
|
|
930
983
|
return this.effectiveTranslate(item.label)
|
|
931
984
|
},
|
|
932
985
|
|
|
@@ -937,7 +990,10 @@ var script = {
|
|
|
937
990
|
* work" and "Queue" on one list). The route name alone lit both. An
|
|
938
991
|
* entry with a `query` is active only when the address carries that
|
|
939
992
|
* query; an entry without one steps back when a sibling's query
|
|
940
|
-
* matches, so exactly one of them is marked.
|
|
993
|
+
* matches, so exactly one of them is marked. `params` work the
|
|
994
|
+
* same way against the route's params, so of several entries on a
|
|
995
|
+
* `/items/:slug` route only the one whose `slug` is in the address
|
|
996
|
+
* is marked.
|
|
941
997
|
*
|
|
942
998
|
* On a page below a list where that rule marks nothing, the one
|
|
943
999
|
* entry `subRouteParent` picks is active instead.
|
|
@@ -956,9 +1012,9 @@ var script = {
|
|
|
956
1012
|
},
|
|
957
1013
|
|
|
958
1014
|
/**
|
|
959
|
-
* The route and
|
|
960
|
-
* page below a list. `subRouteParent` asks this to see whether
|
|
961
|
-
* rule already marks an entry.
|
|
1015
|
+
* The route, query and params rule on its own, without the fallback
|
|
1016
|
+
* for a page below a list. `subRouteParent` asks this to see whether
|
|
1017
|
+
* the rule already marks an entry.
|
|
962
1018
|
*
|
|
963
1019
|
* @param {object} item Menu entry.
|
|
964
1020
|
* @return {boolean} True when the rule marks the entry.
|
|
@@ -972,17 +1028,155 @@ var script = {
|
|
|
972
1028
|
if (item.route !== this.activeRouteName) {
|
|
973
1029
|
return false
|
|
974
1030
|
}
|
|
975
|
-
if (
|
|
976
|
-
return this.
|
|
1031
|
+
if (this.isNarrowed(item)) {
|
|
1032
|
+
return this.narrowingMatches(item)
|
|
977
1033
|
}
|
|
978
|
-
|
|
979
|
-
for (const entry of this.visibleItems) {
|
|
980
|
-
siblings.push(entry, ...this.visibleChildren(entry));
|
|
981
|
-
}
|
|
982
|
-
return !siblings.some((entry) => entry !== item
|
|
1034
|
+
return !this.flatEntries.some((entry) => entry !== item
|
|
983
1035
|
&& entry.route === item.route
|
|
984
|
-
&&
|
|
985
|
-
&& this.
|
|
1036
|
+
&& this.isNarrowed(entry)
|
|
1037
|
+
&& this.narrowingMatches(entry))
|
|
1038
|
+
},
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* Whether an entry narrows its route with a `query` or `params`.
|
|
1042
|
+
*
|
|
1043
|
+
* @param {object} item Menu entry.
|
|
1044
|
+
* @return {boolean} True when the entry declares either.
|
|
1045
|
+
*/
|
|
1046
|
+
isNarrowed(item) {
|
|
1047
|
+
return Boolean(item.query && typeof item.query === 'object') || this.hasParams(item)
|
|
1048
|
+
},
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* Whether the address carries every `query` key and every `params`
|
|
1052
|
+
* key the entry declares.
|
|
1053
|
+
*
|
|
1054
|
+
* @param {object} item Menu entry that narrows its route.
|
|
1055
|
+
* @return {boolean} True when everything it declares matches.
|
|
1056
|
+
*/
|
|
1057
|
+
narrowingMatches(item) {
|
|
1058
|
+
const queryOk = !(item.query && typeof item.query === 'object') || this.queryMatches(item);
|
|
1059
|
+
const paramsOk = !this.hasParams(item) || this.paramsMatch(item);
|
|
1060
|
+
return queryOk && paramsOk
|
|
1061
|
+
},
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* Whether an entry declares a non-empty `params` object.
|
|
1065
|
+
*
|
|
1066
|
+
* @param {object} item Menu entry.
|
|
1067
|
+
* @return {boolean} True when it has route params.
|
|
1068
|
+
*/
|
|
1069
|
+
hasParams(item) {
|
|
1070
|
+
return Boolean(item?.params) && typeof item.params === 'object' && Object.keys(item.params).length > 0
|
|
1071
|
+
},
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Whether the route's params carry every key of an entry's
|
|
1075
|
+
* `params`. Values are compared as strings.
|
|
1076
|
+
*
|
|
1077
|
+
* @param {object} item Menu entry with `params`.
|
|
1078
|
+
* @return {boolean} True when every declared key matches.
|
|
1079
|
+
*/
|
|
1080
|
+
paramsMatch(item) {
|
|
1081
|
+
const current = this.$route?.params ?? {};
|
|
1082
|
+
return Object.keys(item.params).every((key) => String(current[key] ?? '') === String(item.params[key]))
|
|
1083
|
+
},
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* The segments of an entry's page path with its `params` filled in,
|
|
1087
|
+
* as plain values. A missing optional `:name?` segment is dropped;
|
|
1088
|
+
* null when a required one has no param, or without a page.
|
|
1089
|
+
*
|
|
1090
|
+
* @param {object} item Menu entry with `params`.
|
|
1091
|
+
* @return {Array<string>|null} The segments, or null.
|
|
1092
|
+
*/
|
|
1093
|
+
filledPageSegments(item) {
|
|
1094
|
+
const pagePath = this.pageForItem(item)?.route;
|
|
1095
|
+
if (!pagePath) {
|
|
1096
|
+
return null
|
|
1097
|
+
}
|
|
1098
|
+
const segments = [];
|
|
1099
|
+
for (const part of pagePath.split('/').filter(Boolean)) {
|
|
1100
|
+
const param = PARAM_SEGMENT.exec(part);
|
|
1101
|
+
if (!param) {
|
|
1102
|
+
segments.push(part);
|
|
1103
|
+
continue
|
|
1104
|
+
}
|
|
1105
|
+
const value = item.params?.[param[1]];
|
|
1106
|
+
if (isFilled(value)) {
|
|
1107
|
+
segments.push(String(value));
|
|
1108
|
+
} else if (!isOptionalParam(param)) {
|
|
1109
|
+
return null
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return segments
|
|
1113
|
+
},
|
|
1114
|
+
|
|
1115
|
+
/**
|
|
1116
|
+
* The required route param the router cannot fill when it resolves
|
|
1117
|
+
* an entry's link on its manifest page. vue-router fills a named
|
|
1118
|
+
* link's required params from the current route, then overlays every
|
|
1119
|
+
* key the entry declares, empty or null included. So a declared key
|
|
1120
|
+
* decides on its own value, and only an undeclared one falls back
|
|
1121
|
+
* to the current route. Null without a known page, or when every
|
|
1122
|
+
* required segment is filled. Reads `$route`, so it follows
|
|
1123
|
+
* navigation.
|
|
1124
|
+
*
|
|
1125
|
+
* @param {object} item Menu entry.
|
|
1126
|
+
* @return {string|null} The param name, or null.
|
|
1127
|
+
*/
|
|
1128
|
+
unfilledRouteParam(item) {
|
|
1129
|
+
const pagePath = this.pageForItem(item)?.route;
|
|
1130
|
+
if (!pagePath) {
|
|
1131
|
+
return null
|
|
1132
|
+
}
|
|
1133
|
+
const declared = item.params && typeof item.params === 'object' ? item.params : {};
|
|
1134
|
+
const current = this.$route?.params ?? {};
|
|
1135
|
+
for (const part of pagePath.split('/')) {
|
|
1136
|
+
const param = PARAM_SEGMENT.exec(part);
|
|
1137
|
+
if (!param || isOptionalParam(param)) {
|
|
1138
|
+
continue
|
|
1139
|
+
}
|
|
1140
|
+
const value = param[1] in declared ? declared[param[1]] : current[param[1]];
|
|
1141
|
+
if (!isFilled(value)) {
|
|
1142
|
+
return param[1]
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return null
|
|
1146
|
+
},
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* How deep a path sits on an entry's page path: the page path's
|
|
1150
|
+
* segment count when the path is that page or below it, else 0.
|
|
1151
|
+
* A `/` page path, or one with an unfilled `:name`, never matches.
|
|
1152
|
+
*
|
|
1153
|
+
* Without `params` the page path is compared as written. With
|
|
1154
|
+
* `params`, segments are compared decoded, because the router
|
|
1155
|
+
* percent-encodes param values in the path and the entry holds them
|
|
1156
|
+
* plain.
|
|
1157
|
+
*
|
|
1158
|
+
* @param {object} item Menu entry.
|
|
1159
|
+
* @param {string|undefined} path The path to test.
|
|
1160
|
+
* @return {number} The depth of the match, or 0.
|
|
1161
|
+
*/
|
|
1162
|
+
pageMatchDepth(item, path) {
|
|
1163
|
+
if (!path) {
|
|
1164
|
+
return 0
|
|
1165
|
+
}
|
|
1166
|
+
if (!this.hasParams(item)) {
|
|
1167
|
+
const pagePath = this.pageForItem(item)?.route;
|
|
1168
|
+
if (!pagePath || pagePath === '/' || pagePath.includes(':')) {
|
|
1169
|
+
return 0
|
|
1170
|
+
}
|
|
1171
|
+
const below = path === pagePath || path.startsWith(pagePath + '/');
|
|
1172
|
+
return below ? pagePath.split('/').filter(Boolean).length : 0
|
|
1173
|
+
}
|
|
1174
|
+
const segments = this.filledPageSegments(item);
|
|
1175
|
+
if (!segments || segments.length === 0) {
|
|
1176
|
+
return 0
|
|
1177
|
+
}
|
|
1178
|
+
const current = path.split('/').filter(Boolean).map(decodeSegment);
|
|
1179
|
+
return segments.every((segment, index) => current[index] === segment) ? segments.length : 0
|
|
986
1180
|
},
|
|
987
1181
|
|
|
988
1182
|
/**
|
|
@@ -1105,6 +1299,27 @@ var script = {
|
|
|
1105
1299
|
console.warn(`[CnAppNav] Menu entry "${item.id}" declares count: "auto" but has no resolvable index-type page with register + schema config — no badge will render.`);
|
|
1106
1300
|
},
|
|
1107
1301
|
|
|
1302
|
+
/**
|
|
1303
|
+
* One-shot `console.warn` per menu entry whose link cannot fill a
|
|
1304
|
+
* required route param. Keyed by `id`, else route and label, so
|
|
1305
|
+
* entries without an id each get their own warning.
|
|
1306
|
+
*
|
|
1307
|
+
* @param {{ id?: string, route: string, label?: string }} item Menu entry descriptor.
|
|
1308
|
+
* @param {string} param The unfilled param name.
|
|
1309
|
+
* @return {void}
|
|
1310
|
+
* @private
|
|
1311
|
+
*/
|
|
1312
|
+
warnUnfilledRouteParam(item, param) {
|
|
1313
|
+
const key = item.id ?? `${item.route}|${item.label ?? ''}`;
|
|
1314
|
+
if (this._unfilledParamWarned.has(key)) {
|
|
1315
|
+
return
|
|
1316
|
+
}
|
|
1317
|
+
this._unfilledParamWarned.add(key);
|
|
1318
|
+
const name = item.id ?? item.label ?? item.route;
|
|
1319
|
+
// eslint-disable-next-line no-console
|
|
1320
|
+
console.warn(`[CnAppNav] Menu entry "${name}" cannot fill the required route param "${param}" of page "${item.route}" from its params or the current route — the entry renders without a link.`);
|
|
1321
|
+
},
|
|
1322
|
+
|
|
1108
1323
|
/**
|
|
1109
1324
|
* Look up an item's resolved page (`pages[]` entry whose `id`
|
|
1110
1325
|
* matches the menu item's `route`) — used to decide whether the
|
|
@@ -1127,11 +1342,17 @@ var script = {
|
|
|
1127
1342
|
* items (`action: "user-settings"`) and `href` items return
|
|
1128
1343
|
* `null` so the entry is NOT a vue-router link: action items
|
|
1129
1344
|
* fall through to the click handler, `href` items render a real
|
|
1130
|
-
* anchor via `itemHref`. Route items return a named route
|
|
1345
|
+
* anchor via `itemHref`. Route items return a named route that
|
|
1346
|
+
* carries the item's `params` and `query` when it declares them.
|
|
1347
|
+
*
|
|
1348
|
+
* An item whose manifest page has a required segment that neither
|
|
1349
|
+
* its `params` nor the current route fill also returns null, with a
|
|
1350
|
+
* one-time warning: the router throws resolving such a link, which
|
|
1351
|
+
* would take the whole navigation down rather than that one entry.
|
|
1131
1352
|
*
|
|
1132
1353
|
* @param {object} item Menu item being rendered.
|
|
1133
|
-
* @return {object|null} A `{ name }` route object,
|
|
1134
|
-
* action / href / route-less items.
|
|
1354
|
+
* @return {object|null} A `{ name, params?, query? }` route object,
|
|
1355
|
+
* or null for action / href / route-less / unfillable items.
|
|
1135
1356
|
*/
|
|
1136
1357
|
itemTo(item) {
|
|
1137
1358
|
if (item.action) {
|
|
@@ -1143,9 +1364,22 @@ var script = {
|
|
|
1143
1364
|
if (!item.route) {
|
|
1144
1365
|
return null
|
|
1145
1366
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1367
|
+
const unfilled = this.unfilledRouteParam(item);
|
|
1368
|
+
if (unfilled) {
|
|
1369
|
+
this.warnUnfilledRouteParam(item, unfilled);
|
|
1370
|
+
return null
|
|
1371
|
+
}
|
|
1372
|
+
const to = { name: item.route };
|
|
1373
|
+
// Params fill a parameterised route (one entry per catalog →
|
|
1374
|
+
// /catalogs/:slug); query deep-links to a pre-filtered index page
|
|
1375
|
+
// (one entry per case type → Cases?caseType=…).
|
|
1376
|
+
if (this.hasParams(item)) {
|
|
1377
|
+
to.params = item.params;
|
|
1378
|
+
}
|
|
1379
|
+
if (item.query) {
|
|
1380
|
+
to.query = item.query;
|
|
1381
|
+
}
|
|
1382
|
+
return to
|
|
1149
1383
|
},
|
|
1150
1384
|
|
|
1151
1385
|
/**
|
|
@@ -1297,6 +1531,11 @@ var script = {
|
|
|
1297
1531
|
this.cnReplayWalkthrough(item.tourId);
|
|
1298
1532
|
return
|
|
1299
1533
|
}
|
|
1534
|
+
// An entry left without a link (see itemTo) renders a `#` anchor;
|
|
1535
|
+
// following it would change the address, so the click goes nowhere.
|
|
1536
|
+
if (!item.href && this.unfilledRouteParam(item) && event && typeof event.preventDefault === 'function') {
|
|
1537
|
+
event.preventDefault();
|
|
1538
|
+
}
|
|
1300
1539
|
if (this.visibleChildren(item).length === 0) {
|
|
1301
1540
|
return
|
|
1302
1541
|
}
|