@conduction/nextcloud-vue 2.62.0 → 2.64.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 +425 -39
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +119 -42
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +186 -9
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +65 -42
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +61 -16
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js +20 -0
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +3 -2
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -2
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/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 +29 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +152 -135
- 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 +253 -9
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +126 -50
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +66 -14
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +23 -14
- package/dist/esm/components/CnDetailPage/CnDetailPage.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/CnHeaderWidget/CnHeaderWidget.vue2.js +81 -4
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +22 -5
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +81 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +7 -3
- 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 +41 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +8 -3
- 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/CnStagesWidget/CnStagesWidget.vue2.js +63 -2
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +96 -37
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/stagesModel.js +5 -2
- package/dist/esm/components/CnStagesWidget/stagesModel.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +52 -2
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -6
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +14 -2
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +2 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.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/CnWidgetWrapper/CnWidgetWrapper.vue2.js +73 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +14 -2
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.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 +45 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +45 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +780 -81
- 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/schemaRefSlug.js +19 -3
- package/dist/esm/utils/schemaRefSlug.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 +13088 -7937
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +780 -81
- package/l10n/en.json +45 -0
- package/l10n/nl.json +45 -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 +568 -43
- package/src/components/CnAppRoot/CnAppRoot.md +4 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +223 -16
- package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +61 -16
- package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +22 -1
- package/src/components/CnCellRenderer/CnCellRenderer.vue +33 -2
- package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
- package/src/components/CnDashboardPage/CnDashboardPage.vue +45 -4
- package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
- package/src/components/CnDataTable/CnDataTable.md +3 -0
- package/src/components/CnDataTable/CnDataTable.vue +305 -5
- package/src/components/CnDetailPage/CnDetailPage.md +4 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +60 -1
- 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/CnHeaderWidget/CnHeaderWidget.vue +116 -6
- package/src/components/CnIndexPage/CnIndexPage.vue +87 -4
- package/src/components/CnIndexPage/selfModeActions.js +3 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +46 -2
- package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
- package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
- package/src/components/CnStagesWidget/CnStagesWidget.vue +180 -2
- package/src/components/CnStagesWidget/stagesModel.js +5 -2
- package/src/components/CnStatWidget/CnStatWidget.vue +57 -3
- package/src/components/CnTabsWidget/CnTabsWidget.vue +15 -2
- 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/CnWidgetWrapper/CnWidgetWrapper.md +4 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +107 -1
- 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 +29 -4
- package/src/css/kpi-card.css +24 -1
- package/src/css/table.css +99 -6
- package/src/index.js +2 -0
- package/src/schemas/app-manifest-v2.schema.json +135 -3
- 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/schemaRefSlug.js +19 -3
- package/src/utils/timelineEvents.js +171 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
- package/src/utils/walkthroughSignals.js +94 -0
|
@@ -10,10 +10,11 @@ import OpenInNew from 'vue-material-design-icons/OpenInNew.vue';
|
|
|
10
10
|
import Plus from 'vue-material-design-icons/Plus.vue';
|
|
11
11
|
import ShieldAccountOutline from 'vue-material-design-icons/ShieldAccountOutline.vue';
|
|
12
12
|
import '../CnIcon/CnIcon.vue.js';
|
|
13
|
-
import __sfc_main from '../CnMenuWidget/CnMenuItemIcon.vue.js';
|
|
13
|
+
import __sfc_main$1 from '../CnMenuWidget/CnMenuItemIcon.vue.js';
|
|
14
14
|
import { isAppInstalled } from '../../utils/appInstalled.js';
|
|
15
15
|
import { isSvgPath } from '../../utils/iconUtils.js';
|
|
16
16
|
import { passesContextPredicates } from '../../utils/visibleIfContext.js';
|
|
17
|
+
import __sfc_main from '../CnActionButtons/CnActionButtons.vue.js';
|
|
17
18
|
import { bridgedMdiForCssIcon } from '../CnIcon/cssIconBridge.js';
|
|
18
19
|
import { getSemanticIconComponent } from '../CnIcon/semanticIcons.js';
|
|
19
20
|
import { isCustomIconUrl, hasRegistryIcon } from '../CnWidgetGrid/widgetIcons.js';
|
|
@@ -45,6 +46,44 @@ function byManifestOrder(a, b) {
|
|
|
45
46
|
return a.order - b.order
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
/** A `:name` page-path segment: name, optional `(regexp)`, optional modifier. */
|
|
50
|
+
const PARAM_SEGMENT = /^:(\w+)(\([^)]*\))?([?*+])?$/;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Whether a `PARAM_SEGMENT` match may be left out (`?` or `*`).
|
|
54
|
+
*
|
|
55
|
+
* @param {Array<string>} param The match.
|
|
56
|
+
* @return {boolean} True for an optional segment.
|
|
57
|
+
*/
|
|
58
|
+
function isOptionalParam(param) {
|
|
59
|
+
return param[3] === '?' || param[3] === '*'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether a route param value fills its segment.
|
|
64
|
+
*
|
|
65
|
+
* @param {unknown} value The value.
|
|
66
|
+
* @return {boolean} False for undefined, null and the empty string.
|
|
67
|
+
*/
|
|
68
|
+
function isFilled(value) {
|
|
69
|
+
return value !== undefined && value !== null && value !== ''
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Decode one percent-encoded path segment, or keep it as is when it is not
|
|
74
|
+
* valid percent-encoding.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} segment The segment.
|
|
77
|
+
* @return {string} The decoded segment.
|
|
78
|
+
*/
|
|
79
|
+
function decodeSegment(segment) {
|
|
80
|
+
try {
|
|
81
|
+
return decodeURIComponent(segment)
|
|
82
|
+
} catch {
|
|
83
|
+
return segment
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
48
87
|
var script = {
|
|
49
88
|
name: 'CnAppNav',
|
|
50
89
|
|
|
@@ -57,7 +96,8 @@ var script = {
|
|
|
57
96
|
NcAppNavigationSettings,
|
|
58
97
|
NcButton,
|
|
59
98
|
NcCounterBubble,
|
|
60
|
-
|
|
99
|
+
CnActionButtons: __sfc_main,
|
|
100
|
+
CnMenuItemIcon: __sfc_main$1,
|
|
61
101
|
Cog,
|
|
62
102
|
HelpCircleOutline,
|
|
63
103
|
ShieldAccountOutline,
|
|
@@ -103,6 +143,16 @@ var script = {
|
|
|
103
143
|
* @type {{ [register: string]: { [schema: string]: number } }}
|
|
104
144
|
*/
|
|
105
145
|
cnMenuCounts: { default: () => ({}) },
|
|
146
|
+
/**
|
|
147
|
+
* Provided by CnAppRoot — reactive `{ [menuItemId]: number }` totals
|
|
148
|
+
* for every menu entry whose `count` is an object
|
|
149
|
+
* (`{ register, schema, filter? }`): the total of that filtered
|
|
150
|
+
* list, fetched under the entry's own key so an index page's
|
|
151
|
+
* whole-schema total is left alone. Empty outside a CnAppRoot.
|
|
152
|
+
*
|
|
153
|
+
* @type {{ [itemId: string]: number }}
|
|
154
|
+
*/
|
|
155
|
+
cnMenuItemCounts: { default: () => ({}) },
|
|
106
156
|
},
|
|
107
157
|
|
|
108
158
|
props: {
|
|
@@ -226,7 +276,7 @@ var script = {
|
|
|
226
276
|
},
|
|
227
277
|
},
|
|
228
278
|
|
|
229
|
-
emits: ['primary-action', 'primary-action-click'],
|
|
279
|
+
emits: ['primary-action', 'primary-action-click', 'primary-action-created', 'card-action'],
|
|
230
280
|
|
|
231
281
|
/**
|
|
232
282
|
* The navigation's own state: which groups are open, and which entry
|
|
@@ -287,11 +337,70 @@ var script = {
|
|
|
287
337
|
const text = (value) => (typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : '');
|
|
288
338
|
const brand = {
|
|
289
339
|
logo: typeof declared.logo === 'string' ? declared.logo : '',
|
|
340
|
+
// A URL, or `true` for the theme's emblem (--nldesign-emblem-url).
|
|
341
|
+
emblem: declared.emblem === true ? true : (typeof declared.emblem === 'string' ? declared.emblem : ''),
|
|
290
342
|
name: text(declared.name),
|
|
291
343
|
caption: text(declared.caption),
|
|
292
344
|
alt: text(declared.alt),
|
|
293
345
|
};
|
|
294
|
-
return (brand.logo || brand.name || brand.caption) ? brand : null
|
|
346
|
+
return (brand.logo || brand.emblem || brand.name || brand.caption) ? brand : null
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* The card above the footer entries (`nav.card`): `{ title, text?,
|
|
351
|
+
* link?: { label, route?, params?, href?, action? } }`. Title, text
|
|
352
|
+
* and link label go through the translate function. The link is a
|
|
353
|
+
* router link for a `route` (with a router), an anchor for an
|
|
354
|
+
* `href`, and a button emitting `card-action` with the `action` id
|
|
355
|
+
* otherwise. Null without a title.
|
|
356
|
+
*
|
|
357
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-navigation-carries-a-card-and-a-help-entry
|
|
358
|
+
* @return {object|null}
|
|
359
|
+
*/
|
|
360
|
+
resolvedCard() {
|
|
361
|
+
const declared = this.effectiveManifest?.nav?.card;
|
|
362
|
+
if (!declared || typeof declared !== 'object' || typeof declared.title !== 'string' || declared.title === '') {
|
|
363
|
+
return null
|
|
364
|
+
}
|
|
365
|
+
const text = (value) => (typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : '');
|
|
366
|
+
const card = { title: text(declared.title), text: text(declared.text), linkTag: '', linkAttrs: {}, linkLabel: '', action: '' };
|
|
367
|
+
const link = declared.link;
|
|
368
|
+
if (link && typeof link === 'object' && typeof link.label === 'string' && link.label !== '') {
|
|
369
|
+
card.linkLabel = text(link.label);
|
|
370
|
+
if (link.route && this.$router) {
|
|
371
|
+
card.linkTag = 'router-link';
|
|
372
|
+
card.linkAttrs = { to: { name: link.route, ...(link.params && typeof link.params === 'object' ? { params: link.params } : {}) } };
|
|
373
|
+
} else if (typeof link.href === 'string' && link.href !== '') {
|
|
374
|
+
card.linkTag = 'a';
|
|
375
|
+
card.linkAttrs = { href: link.href };
|
|
376
|
+
} else if (typeof link.action === 'string' && link.action !== '') {
|
|
377
|
+
card.linkTag = 'button';
|
|
378
|
+
card.linkAttrs = { type: 'button' };
|
|
379
|
+
card.action = link.action;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return card
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* The help entry (`nav.help`): `{ label, route?, href? }` rendered as
|
|
387
|
+
* a footer entry with a help icon. Null without a label or a target.
|
|
388
|
+
*
|
|
389
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-navigation-carries-a-card-and-a-help-entry
|
|
390
|
+
* @return {{label: string, to?: object, href?: string}|null}
|
|
391
|
+
*/
|
|
392
|
+
resolvedHelp() {
|
|
393
|
+
const declared = this.effectiveManifest?.nav?.help;
|
|
394
|
+
if (!declared || typeof declared !== 'object' || typeof declared.label !== 'string' || declared.label === '') {
|
|
395
|
+
return null
|
|
396
|
+
}
|
|
397
|
+
if (declared.route && this.$router) {
|
|
398
|
+
return { label: this.effectiveTranslate(declared.label), to: { name: declared.route }, href: undefined }
|
|
399
|
+
}
|
|
400
|
+
if (typeof declared.href === 'string' && declared.href !== '') {
|
|
401
|
+
return { label: this.effectiveTranslate(declared.label), to: undefined, href: declared.href }
|
|
402
|
+
}
|
|
403
|
+
return null
|
|
295
404
|
},
|
|
296
405
|
|
|
297
406
|
/**
|
|
@@ -312,20 +421,30 @@ var script = {
|
|
|
312
421
|
* 2. `manifest.nav.primaryAction` as app-wide default
|
|
313
422
|
* 3. `null` — no primary-action button renders
|
|
314
423
|
*
|
|
315
|
-
* Page-scoped declarations always win over the nav-root default.
|
|
424
|
+
* Page-scoped declarations always win over the nav-root default. An
|
|
425
|
+
* action declaring `permission` or `visibleIf` is gated like a menu
|
|
426
|
+
* entry.
|
|
316
427
|
*
|
|
428
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-navigation-primary-action-runs-a-page-action
|
|
317
429
|
* @return {object|null}
|
|
318
430
|
*/
|
|
319
431
|
activePrimaryAction() {
|
|
320
432
|
const pages = this.effectiveManifest?.pages ?? [];
|
|
321
433
|
const routeName = this.$route?.name;
|
|
434
|
+
// The same `permission` / `visibleIf` gate a menu entry has: an
|
|
435
|
+
// action the reader may not take, or that a condition hides, is
|
|
436
|
+
// not drawn. A gated-out page action falls back to the nav one.
|
|
437
|
+
const gated = (action) => (action && this.passesPermission(action) && this.passesVisibleIf(action) ? action : null);
|
|
322
438
|
if (routeName) {
|
|
323
439
|
const page = pages.find((p) => p.id === routeName);
|
|
324
440
|
if (page && page.primaryAction) {
|
|
325
|
-
|
|
441
|
+
const pageAction = gated(page.primaryAction);
|
|
442
|
+
if (pageAction) {
|
|
443
|
+
return pageAction
|
|
444
|
+
}
|
|
326
445
|
}
|
|
327
446
|
}
|
|
328
|
-
return this.effectiveManifest?.nav?.primaryAction ?? null
|
|
447
|
+
return gated(this.effectiveManifest?.nav?.primaryAction ?? null)
|
|
329
448
|
},
|
|
330
449
|
|
|
331
450
|
/**
|
|
@@ -339,6 +458,31 @@ var script = {
|
|
|
339
458
|
return this.mdiIconComponent(this.activePrimaryAction) ?? Plus
|
|
340
459
|
},
|
|
341
460
|
|
|
461
|
+
/**
|
|
462
|
+
* The CnActionButtons entry for a primary action that carries an
|
|
463
|
+
* `action` (`$defs/action`: open-form, navigate, api-call, ...):
|
|
464
|
+
* the action with the primary action's `id`, `label` and `icon`
|
|
465
|
+
* folded in and `variant: "primary"`. Null for a plain primary
|
|
466
|
+
* action, which keeps its link or button.
|
|
467
|
+
*
|
|
468
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-navigation-primary-action-runs-a-page-action
|
|
469
|
+
* @return {object|null}
|
|
470
|
+
*/
|
|
471
|
+
primaryDispatchEntry() {
|
|
472
|
+
const action = this.activePrimaryAction;
|
|
473
|
+
const inner = action && action.action;
|
|
474
|
+
if (!inner || typeof inner !== 'object' || typeof inner.type !== 'string') {
|
|
475
|
+
return null
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
icon: action.icon || 'Plus',
|
|
479
|
+
...inner,
|
|
480
|
+
id: inner.id || action.id || 'primary-action',
|
|
481
|
+
label: action.label,
|
|
482
|
+
variant: 'primary',
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
|
|
342
486
|
/**
|
|
343
487
|
* Link props for the primary action's button: an `href` opens in a
|
|
344
488
|
* new tab, a `route` is a router link. Null when the action is
|
|
@@ -575,7 +719,9 @@ var script = {
|
|
|
575
719
|
* `/expenses/:id`) light up — and auto-expand — their index entry
|
|
576
720
|
* even though the detail route name is not in the menu. Longest
|
|
577
721
|
* prefix disambiguates nested namespaces (e.g. `/pos` vs
|
|
578
|
-
* `/pos/refunds`).
|
|
722
|
+
* `/pos/refunds`). An entry with `params` matches by its page path
|
|
723
|
+
* with those params filled in (see `pageMatchDepth`). Returns null
|
|
724
|
+
* when nothing matches.
|
|
579
725
|
*
|
|
580
726
|
* @return {string|null}
|
|
581
727
|
*/
|
|
@@ -596,20 +742,15 @@ var script = {
|
|
|
596
742
|
return routeName ?? null
|
|
597
743
|
}
|
|
598
744
|
let best = null;
|
|
599
|
-
let
|
|
745
|
+
let bestDepth = 0;
|
|
600
746
|
for (const it of flat) {
|
|
601
747
|
if (!it.route) {
|
|
602
748
|
continue
|
|
603
749
|
}
|
|
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
|
-
}
|
|
750
|
+
const depth = this.pageMatchDepth(it, path);
|
|
751
|
+
if (depth > bestDepth) {
|
|
752
|
+
best = it.route;
|
|
753
|
+
bestDepth = depth;
|
|
613
754
|
}
|
|
614
755
|
}
|
|
615
756
|
return best ?? routeName ?? null
|
|
@@ -658,7 +799,9 @@ var script = {
|
|
|
658
799
|
* that route, else the first in menu order.
|
|
659
800
|
*
|
|
660
801
|
* Null whenever the existing rule already marks an entry, so a list
|
|
661
|
-
* with an entry without a `query` behaves as it did.
|
|
802
|
+
* with an entry without a `query` behaves as it did. An entry with
|
|
803
|
+
* `params` is a candidate only when the current path sits below its
|
|
804
|
+
* own filled-in page path, so another param value is never marked.
|
|
662
805
|
*
|
|
663
806
|
* @return {object|null} The entry to mark, or null.
|
|
664
807
|
* @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 +811,9 @@ var script = {
|
|
|
668
811
|
if (!active || this.$route?.name === active) {
|
|
669
812
|
return null
|
|
670
813
|
}
|
|
671
|
-
const
|
|
814
|
+
const path = this.$route?.path;
|
|
815
|
+
const entries = this.flatEntries.filter((entry) => !entry.href && entry.route === active
|
|
816
|
+
&& (!this.hasParams(entry) || this.pageMatchDepth(entry, path) > 0));
|
|
672
817
|
if (entries.length === 0 || entries.some((entry) => this.isActiveByRule(entry))) {
|
|
673
818
|
return null
|
|
674
819
|
}
|
|
@@ -718,6 +863,8 @@ var script = {
|
|
|
718
863
|
// stays out of `data()`: a reactive Set mutated inside render would
|
|
719
864
|
// re-trigger the render effect.
|
|
720
865
|
this._autoCountWarned = new Set();
|
|
866
|
+
// Same latch, for warnUnfilledRouteParam() (`itemTo()` runs during render).
|
|
867
|
+
this._unfilledParamWarned = new Set();
|
|
721
868
|
},
|
|
722
869
|
|
|
723
870
|
methods: {
|
|
@@ -926,7 +1073,19 @@ var script = {
|
|
|
926
1073
|
.sort(byManifestOrder)
|
|
927
1074
|
},
|
|
928
1075
|
|
|
1076
|
+
/**
|
|
1077
|
+
* The label an entry shows: run through the translate function, or
|
|
1078
|
+
* as written when the entry sets `translateLabel: false` (a
|
|
1079
|
+
* user-authored title that may equal a translation key). Every
|
|
1080
|
+
* place the nav shows an entry's label reads it from here.
|
|
1081
|
+
*
|
|
1082
|
+
* @param {object} item Menu entry.
|
|
1083
|
+
* @return {string} The label to render as text.
|
|
1084
|
+
*/
|
|
929
1085
|
resolveLabel(item) {
|
|
1086
|
+
if (item.translateLabel === false) {
|
|
1087
|
+
return item.label
|
|
1088
|
+
}
|
|
930
1089
|
return this.effectiveTranslate(item.label)
|
|
931
1090
|
},
|
|
932
1091
|
|
|
@@ -937,7 +1096,10 @@ var script = {
|
|
|
937
1096
|
* work" and "Queue" on one list). The route name alone lit both. An
|
|
938
1097
|
* entry with a `query` is active only when the address carries that
|
|
939
1098
|
* query; an entry without one steps back when a sibling's query
|
|
940
|
-
* matches, so exactly one of them is marked.
|
|
1099
|
+
* matches, so exactly one of them is marked. `params` work the
|
|
1100
|
+
* same way against the route's params, so of several entries on a
|
|
1101
|
+
* `/items/:slug` route only the one whose `slug` is in the address
|
|
1102
|
+
* is marked.
|
|
941
1103
|
*
|
|
942
1104
|
* On a page below a list where that rule marks nothing, the one
|
|
943
1105
|
* entry `subRouteParent` picks is active instead.
|
|
@@ -956,9 +1118,9 @@ var script = {
|
|
|
956
1118
|
},
|
|
957
1119
|
|
|
958
1120
|
/**
|
|
959
|
-
* The route and
|
|
960
|
-
* page below a list. `subRouteParent` asks this to see whether
|
|
961
|
-
* rule already marks an entry.
|
|
1121
|
+
* The route, query and params rule on its own, without the fallback
|
|
1122
|
+
* for a page below a list. `subRouteParent` asks this to see whether
|
|
1123
|
+
* the rule already marks an entry.
|
|
962
1124
|
*
|
|
963
1125
|
* @param {object} item Menu entry.
|
|
964
1126
|
* @return {boolean} True when the rule marks the entry.
|
|
@@ -972,17 +1134,155 @@ var script = {
|
|
|
972
1134
|
if (item.route !== this.activeRouteName) {
|
|
973
1135
|
return false
|
|
974
1136
|
}
|
|
975
|
-
if (
|
|
976
|
-
return this.
|
|
977
|
-
}
|
|
978
|
-
const siblings = [];
|
|
979
|
-
for (const entry of this.visibleItems) {
|
|
980
|
-
siblings.push(entry, ...this.visibleChildren(entry));
|
|
1137
|
+
if (this.isNarrowed(item)) {
|
|
1138
|
+
return this.narrowingMatches(item)
|
|
981
1139
|
}
|
|
982
|
-
return !
|
|
1140
|
+
return !this.flatEntries.some((entry) => entry !== item
|
|
983
1141
|
&& entry.route === item.route
|
|
984
|
-
&&
|
|
985
|
-
&& this.
|
|
1142
|
+
&& this.isNarrowed(entry)
|
|
1143
|
+
&& this.narrowingMatches(entry))
|
|
1144
|
+
},
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Whether an entry narrows its route with a `query` or `params`.
|
|
1148
|
+
*
|
|
1149
|
+
* @param {object} item Menu entry.
|
|
1150
|
+
* @return {boolean} True when the entry declares either.
|
|
1151
|
+
*/
|
|
1152
|
+
isNarrowed(item) {
|
|
1153
|
+
return Boolean(item.query && typeof item.query === 'object') || this.hasParams(item)
|
|
1154
|
+
},
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* Whether the address carries every `query` key and every `params`
|
|
1158
|
+
* key the entry declares.
|
|
1159
|
+
*
|
|
1160
|
+
* @param {object} item Menu entry that narrows its route.
|
|
1161
|
+
* @return {boolean} True when everything it declares matches.
|
|
1162
|
+
*/
|
|
1163
|
+
narrowingMatches(item) {
|
|
1164
|
+
const queryOk = !(item.query && typeof item.query === 'object') || this.queryMatches(item);
|
|
1165
|
+
const paramsOk = !this.hasParams(item) || this.paramsMatch(item);
|
|
1166
|
+
return queryOk && paramsOk
|
|
1167
|
+
},
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* Whether an entry declares a non-empty `params` object.
|
|
1171
|
+
*
|
|
1172
|
+
* @param {object} item Menu entry.
|
|
1173
|
+
* @return {boolean} True when it has route params.
|
|
1174
|
+
*/
|
|
1175
|
+
hasParams(item) {
|
|
1176
|
+
return Boolean(item?.params) && typeof item.params === 'object' && Object.keys(item.params).length > 0
|
|
1177
|
+
},
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* Whether the route's params carry every key of an entry's
|
|
1181
|
+
* `params`. Values are compared as strings.
|
|
1182
|
+
*
|
|
1183
|
+
* @param {object} item Menu entry with `params`.
|
|
1184
|
+
* @return {boolean} True when every declared key matches.
|
|
1185
|
+
*/
|
|
1186
|
+
paramsMatch(item) {
|
|
1187
|
+
const current = this.$route?.params ?? {};
|
|
1188
|
+
return Object.keys(item.params).every((key) => String(current[key] ?? '') === String(item.params[key]))
|
|
1189
|
+
},
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* The segments of an entry's page path with its `params` filled in,
|
|
1193
|
+
* as plain values. A missing optional `:name?` segment is dropped;
|
|
1194
|
+
* null when a required one has no param, or without a page.
|
|
1195
|
+
*
|
|
1196
|
+
* @param {object} item Menu entry with `params`.
|
|
1197
|
+
* @return {Array<string>|null} The segments, or null.
|
|
1198
|
+
*/
|
|
1199
|
+
filledPageSegments(item) {
|
|
1200
|
+
const pagePath = this.pageForItem(item)?.route;
|
|
1201
|
+
if (!pagePath) {
|
|
1202
|
+
return null
|
|
1203
|
+
}
|
|
1204
|
+
const segments = [];
|
|
1205
|
+
for (const part of pagePath.split('/').filter(Boolean)) {
|
|
1206
|
+
const param = PARAM_SEGMENT.exec(part);
|
|
1207
|
+
if (!param) {
|
|
1208
|
+
segments.push(part);
|
|
1209
|
+
continue
|
|
1210
|
+
}
|
|
1211
|
+
const value = item.params?.[param[1]];
|
|
1212
|
+
if (isFilled(value)) {
|
|
1213
|
+
segments.push(String(value));
|
|
1214
|
+
} else if (!isOptionalParam(param)) {
|
|
1215
|
+
return null
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return segments
|
|
1219
|
+
},
|
|
1220
|
+
|
|
1221
|
+
/**
|
|
1222
|
+
* The required route param the router cannot fill when it resolves
|
|
1223
|
+
* an entry's link on its manifest page. vue-router fills a named
|
|
1224
|
+
* link's required params from the current route, then overlays every
|
|
1225
|
+
* key the entry declares, empty or null included. So a declared key
|
|
1226
|
+
* decides on its own value, and only an undeclared one falls back
|
|
1227
|
+
* to the current route. Null without a known page, or when every
|
|
1228
|
+
* required segment is filled. Reads `$route`, so it follows
|
|
1229
|
+
* navigation.
|
|
1230
|
+
*
|
|
1231
|
+
* @param {object} item Menu entry.
|
|
1232
|
+
* @return {string|null} The param name, or null.
|
|
1233
|
+
*/
|
|
1234
|
+
unfilledRouteParam(item) {
|
|
1235
|
+
const pagePath = this.pageForItem(item)?.route;
|
|
1236
|
+
if (!pagePath) {
|
|
1237
|
+
return null
|
|
1238
|
+
}
|
|
1239
|
+
const declared = item.params && typeof item.params === 'object' ? item.params : {};
|
|
1240
|
+
const current = this.$route?.params ?? {};
|
|
1241
|
+
for (const part of pagePath.split('/')) {
|
|
1242
|
+
const param = PARAM_SEGMENT.exec(part);
|
|
1243
|
+
if (!param || isOptionalParam(param)) {
|
|
1244
|
+
continue
|
|
1245
|
+
}
|
|
1246
|
+
const value = param[1] in declared ? declared[param[1]] : current[param[1]];
|
|
1247
|
+
if (!isFilled(value)) {
|
|
1248
|
+
return param[1]
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return null
|
|
1252
|
+
},
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* How deep a path sits on an entry's page path: the page path's
|
|
1256
|
+
* segment count when the path is that page or below it, else 0.
|
|
1257
|
+
* A `/` page path, or one with an unfilled `:name`, never matches.
|
|
1258
|
+
*
|
|
1259
|
+
* Without `params` the page path is compared as written. With
|
|
1260
|
+
* `params`, segments are compared decoded, because the router
|
|
1261
|
+
* percent-encodes param values in the path and the entry holds them
|
|
1262
|
+
* plain.
|
|
1263
|
+
*
|
|
1264
|
+
* @param {object} item Menu entry.
|
|
1265
|
+
* @param {string|undefined} path The path to test.
|
|
1266
|
+
* @return {number} The depth of the match, or 0.
|
|
1267
|
+
*/
|
|
1268
|
+
pageMatchDepth(item, path) {
|
|
1269
|
+
if (!path) {
|
|
1270
|
+
return 0
|
|
1271
|
+
}
|
|
1272
|
+
if (!this.hasParams(item)) {
|
|
1273
|
+
const pagePath = this.pageForItem(item)?.route;
|
|
1274
|
+
if (!pagePath || pagePath === '/' || pagePath.includes(':')) {
|
|
1275
|
+
return 0
|
|
1276
|
+
}
|
|
1277
|
+
const below = path === pagePath || path.startsWith(pagePath + '/');
|
|
1278
|
+
return below ? pagePath.split('/').filter(Boolean).length : 0
|
|
1279
|
+
}
|
|
1280
|
+
const segments = this.filledPageSegments(item);
|
|
1281
|
+
if (!segments || segments.length === 0) {
|
|
1282
|
+
return 0
|
|
1283
|
+
}
|
|
1284
|
+
const current = path.split('/').filter(Boolean).map(decodeSegment);
|
|
1285
|
+
return segments.every((segment, index) => current[index] === segment) ? segments.length : 0
|
|
986
1286
|
},
|
|
987
1287
|
|
|
988
1288
|
/**
|
|
@@ -1047,6 +1347,9 @@ var script = {
|
|
|
1047
1347
|
* `NcAppNavigationItem`).
|
|
1048
1348
|
*
|
|
1049
1349
|
* - Literal positive integer in `item.count` → return as-is.
|
|
1350
|
+
* - `item.count` is an object (`{ register, schema, filter? }`) →
|
|
1351
|
+
* `cnMenuItemCounts[item.id] ?? null`, the filtered total CnAppRoot
|
|
1352
|
+
* fetched for this entry.
|
|
1050
1353
|
* - `item.count === "auto"` → look up the entry's resolved page;
|
|
1051
1354
|
* when that page is `type: "index"` with a `register`/`schema`
|
|
1052
1355
|
* in its `config`, return
|
|
@@ -1070,6 +1373,11 @@ var script = {
|
|
|
1070
1373
|
if (typeof raw === 'number') {
|
|
1071
1374
|
return raw > 0 ? raw : null
|
|
1072
1375
|
}
|
|
1376
|
+
if (raw && typeof raw === 'object') {
|
|
1377
|
+
// A filtered count: CnAppRoot fetched it per entry id.
|
|
1378
|
+
const value = this.cnMenuItemCounts?.[item.id];
|
|
1379
|
+
return (typeof value === 'number' && value > 0) ? value : null
|
|
1380
|
+
}
|
|
1073
1381
|
if (raw !== 'auto') {
|
|
1074
1382
|
return null
|
|
1075
1383
|
}
|
|
@@ -1105,6 +1413,27 @@ var script = {
|
|
|
1105
1413
|
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
1414
|
},
|
|
1107
1415
|
|
|
1416
|
+
/**
|
|
1417
|
+
* One-shot `console.warn` per menu entry whose link cannot fill a
|
|
1418
|
+
* required route param. Keyed by `id`, else route and label, so
|
|
1419
|
+
* entries without an id each get their own warning.
|
|
1420
|
+
*
|
|
1421
|
+
* @param {{ id?: string, route: string, label?: string }} item Menu entry descriptor.
|
|
1422
|
+
* @param {string} param The unfilled param name.
|
|
1423
|
+
* @return {void}
|
|
1424
|
+
* @private
|
|
1425
|
+
*/
|
|
1426
|
+
warnUnfilledRouteParam(item, param) {
|
|
1427
|
+
const key = item.id ?? `${item.route}|${item.label ?? ''}`;
|
|
1428
|
+
if (this._unfilledParamWarned.has(key)) {
|
|
1429
|
+
return
|
|
1430
|
+
}
|
|
1431
|
+
this._unfilledParamWarned.add(key);
|
|
1432
|
+
const name = item.id ?? item.label ?? item.route;
|
|
1433
|
+
// eslint-disable-next-line no-console
|
|
1434
|
+
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.`);
|
|
1435
|
+
},
|
|
1436
|
+
|
|
1108
1437
|
/**
|
|
1109
1438
|
* Look up an item's resolved page (`pages[]` entry whose `id`
|
|
1110
1439
|
* matches the menu item's `route`) — used to decide whether the
|
|
@@ -1127,11 +1456,17 @@ var script = {
|
|
|
1127
1456
|
* items (`action: "user-settings"`) and `href` items return
|
|
1128
1457
|
* `null` so the entry is NOT a vue-router link: action items
|
|
1129
1458
|
* fall through to the click handler, `href` items render a real
|
|
1130
|
-
* anchor via `itemHref`. Route items return a named route
|
|
1459
|
+
* anchor via `itemHref`. Route items return a named route that
|
|
1460
|
+
* carries the item's `params` and `query` when it declares them.
|
|
1461
|
+
*
|
|
1462
|
+
* An item whose manifest page has a required segment that neither
|
|
1463
|
+
* its `params` nor the current route fill also returns null, with a
|
|
1464
|
+
* one-time warning: the router throws resolving such a link, which
|
|
1465
|
+
* would take the whole navigation down rather than that one entry.
|
|
1131
1466
|
*
|
|
1132
1467
|
* @param {object} item Menu item being rendered.
|
|
1133
|
-
* @return {object|null} A `{ name }` route object,
|
|
1134
|
-
* action / href / route-less items.
|
|
1468
|
+
* @return {object|null} A `{ name, params?, query? }` route object,
|
|
1469
|
+
* or null for action / href / route-less / unfillable items.
|
|
1135
1470
|
*/
|
|
1136
1471
|
itemTo(item) {
|
|
1137
1472
|
if (item.action) {
|
|
@@ -1143,9 +1478,22 @@ var script = {
|
|
|
1143
1478
|
if (!item.route) {
|
|
1144
1479
|
return null
|
|
1145
1480
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1481
|
+
const unfilled = this.unfilledRouteParam(item);
|
|
1482
|
+
if (unfilled) {
|
|
1483
|
+
this.warnUnfilledRouteParam(item, unfilled);
|
|
1484
|
+
return null
|
|
1485
|
+
}
|
|
1486
|
+
const to = { name: item.route };
|
|
1487
|
+
// Params fill a parameterised route (one entry per catalog →
|
|
1488
|
+
// /catalogs/:slug); query deep-links to a pre-filtered index page
|
|
1489
|
+
// (one entry per case type → Cases?caseType=…).
|
|
1490
|
+
if (this.hasParams(item)) {
|
|
1491
|
+
to.params = item.params;
|
|
1492
|
+
}
|
|
1493
|
+
if (item.query) {
|
|
1494
|
+
to.query = item.query;
|
|
1495
|
+
}
|
|
1496
|
+
return to
|
|
1149
1497
|
},
|
|
1150
1498
|
|
|
1151
1499
|
/**
|
|
@@ -1297,6 +1645,11 @@ var script = {
|
|
|
1297
1645
|
this.cnReplayWalkthrough(item.tourId);
|
|
1298
1646
|
return
|
|
1299
1647
|
}
|
|
1648
|
+
// An entry left without a link (see itemTo) renders a `#` anchor;
|
|
1649
|
+
// following it would change the address, so the click goes nowhere.
|
|
1650
|
+
if (!item.href && this.unfilledRouteParam(item) && event && typeof event.preventDefault === 'function') {
|
|
1651
|
+
event.preventDefault();
|
|
1652
|
+
}
|
|
1300
1653
|
if (this.visibleChildren(item).length === 0) {
|
|
1301
1654
|
return
|
|
1302
1655
|
}
|
|
@@ -1335,6 +1688,39 @@ var script = {
|
|
|
1335
1688
|
*
|
|
1336
1689
|
* @return {void}
|
|
1337
1690
|
*/
|
|
1691
|
+
/**
|
|
1692
|
+
* Re-emit the object an `open-form` primary action created, so the
|
|
1693
|
+
* host can refresh a list or open the record.
|
|
1694
|
+
*
|
|
1695
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-navigation-primary-action-runs-a-page-action
|
|
1696
|
+
* @param {object} created The created object.
|
|
1697
|
+
* @return {void}
|
|
1698
|
+
*/
|
|
1699
|
+
onPrimaryActionCreated(created) {
|
|
1700
|
+
/**
|
|
1701
|
+
* @event primary-action-created Emitted after an `open-form` primary action saves. Payload: the created object.
|
|
1702
|
+
*/
|
|
1703
|
+
this.$emit('primary-action-created', created);
|
|
1704
|
+
},
|
|
1705
|
+
|
|
1706
|
+
/**
|
|
1707
|
+
* A card link that is a button (an `action` id) emits `card-action`
|
|
1708
|
+
* with that id for the host to run; links and anchors navigate on
|
|
1709
|
+
* their own.
|
|
1710
|
+
*
|
|
1711
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-navigation-carries-a-card-and-a-help-entry
|
|
1712
|
+
* @return {void}
|
|
1713
|
+
*/
|
|
1714
|
+
onCardLinkClick() {
|
|
1715
|
+
const card = this.resolvedCard;
|
|
1716
|
+
if (card && card.action) {
|
|
1717
|
+
/**
|
|
1718
|
+
* @event card-action Emitted when the nav card's link is an action button. Payload: the action id.
|
|
1719
|
+
*/
|
|
1720
|
+
this.$emit('card-action', card.action);
|
|
1721
|
+
}
|
|
1722
|
+
},
|
|
1723
|
+
|
|
1338
1724
|
onPrimaryActionClick() {
|
|
1339
1725
|
const action = this.activePrimaryAction;
|
|
1340
1726
|
if (!action) {
|