@conduction/nextcloud-vue 2.56.0-beta.5 → 2.56.0-beta.7
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/CnActionsMenu/CnActionsMenu.vue2.js +14 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +39 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +46 -4
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +224 -9
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +79 -25
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js +42 -3
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js +39 -6
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +46 -0
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +11 -4
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js +10 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js.map +1 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +39 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +16 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +117 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +147 -89
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +10 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +259 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +131 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +10 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +116 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +24 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +503 -28
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +300 -142
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js +10 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js +237 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js +120 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js +10 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +77 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js +27 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +169 -12
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +15 -7
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js +37 -3
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js +44 -16
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +132 -3
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -3
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js +10 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js +150 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js +106 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js +9 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +47 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js +17 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +49 -0
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js +6 -0
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js +46 -0
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js +15 -3
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +37 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +29 -13
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +100 -3
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +50 -3
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js +10 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +186 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +46 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +11 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +298 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +77 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js +10 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js +169 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js +48 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js.map +1 -0
- package/dist/esm/components/CnTabs/CnTab.vue2.js +35 -0
- package/dist/esm/components/CnTabs/CnTab.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTab.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js +120 -7
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js +57 -5
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +59 -4
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +6 -2
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +10 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +484 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +115 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js +10 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js +189 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js +74 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js.map +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +82 -8
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +54 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +54 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +931 -4
- package/dist/esm/utils/dateVariant.js +124 -0
- package/dist/esm/utils/dateVariant.js.map +1 -0
- package/dist/esm/utils/detailActionModel.js +260 -0
- package/dist/esm/utils/detailActionModel.js.map +1 -0
- package/dist/esm/utils/dueRule.js +84 -0
- package/dist/esm/utils/dueRule.js.map +1 -0
- package/dist/esm/utils/fetchAggregate.js +37 -1
- package/dist/esm/utils/fetchAggregate.js.map +1 -1
- package/dist/esm/utils/fetchFilterCounts.js +186 -0
- package/dist/esm/utils/fetchFilterCounts.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +5 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/readPath.js +30 -0
- package/dist/esm/utils/readPath.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/nextcloud-vue.cjs.js +15070 -8994
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +931 -4
- package/l10n/en.json +54 -1
- package/l10n/nl.json +54 -1
- package/package.json +1 -1
- package/scripts/.jsdoc-baselines.json +3 -0
- package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
- package/src/components/CnAppNav/CnAppNav.md +26 -0
- package/src/components/CnAppNav/CnAppNav.vue +104 -1
- package/src/components/CnBannerWidget/CnBannerWidget.vue +365 -10
- package/src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue +65 -3
- package/src/components/CnBoardView/CnBoardView.vue +77 -0
- package/src/components/CnBrandStripe/CnBrandStripe.md +22 -0
- package/src/components/CnBrandStripe/CnBrandStripe.vue +100 -0
- package/src/components/CnBrandStripe/index.js +4 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +207 -1
- package/src/components/CnConversationThread/CnConversationThread.md +51 -0
- package/src/components/CnConversationThread/CnConversationThread.vue +457 -0
- package/src/components/CnConversationThread/CnConversationWidget.vue +144 -0
- package/src/components/CnConversationThread/index.js +4 -0
- package/src/components/CnDetailPage/CnDetailPage.md +16 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +683 -48
- package/src/components/CnDocumentReviewList/CnDocumentReviewList.md +55 -0
- package/src/components/CnDocumentReviewList/CnDocumentReviewList.vue +384 -0
- package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +108 -0
- package/src/components/CnDocumentReviewList/index.js +4 -0
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +189 -14
- package/src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue +53 -3
- package/src/components/CnIndexPage/CnIndexPage.vue +135 -3
- package/src/components/CnNextStepCard/CnNextStepCard.md +40 -0
- package/src/components/CnNextStepCard/CnNextStepCard.vue +319 -0
- package/src/components/CnNextStepCard/CnNextStepWidget.vue +59 -0
- package/src/components/CnNextStepCard/index.js +4 -0
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +49 -0
- package/src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue +6 -0
- package/src/components/CnObjectKanban/CnObjectKanban.vue +86 -2
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +3 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +60 -1
- package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +100 -3
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +50 -3
- package/src/components/CnSegmentedControl/CnSegmentedControl.md +46 -0
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +291 -0
- package/src/components/CnSegmentedControl/index.js +4 -0
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.md +15 -0
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +414 -0
- package/src/components/CnStackedBarWidget/index.js +14 -0
- package/src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue +224 -0
- package/src/components/CnStackedBarWidgetForm/index.js +4 -0
- package/src/components/CnTabs/CnTab.vue +35 -0
- package/src/components/CnTabs/CnTabs.vue +244 -10
- package/src/components/CnTabsWidget/CnTabsWidget.vue +63 -4
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.md +31 -0
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +668 -0
- package/src/components/CnWeekStripWidget/index.js +14 -0
- package/src/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue +265 -0
- package/src/components/CnWeekStripWidgetForm/index.js +4 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +74 -0
- package/src/components/index.js +11 -0
- package/src/css/detail-page.css +47 -0
- package/src/index.js +9 -0
- package/src/schemas/app-manifest-v2.schema.json +389 -1
- package/src/schemas/app-manifest.schema.json +2 -1
- package/src/utils/dateVariant.js +120 -0
- package/src/utils/detailActionModel.js +256 -0
- package/src/utils/dueRule.js +94 -0
- package/src/utils/fetchAggregate.js +36 -0
- package/src/utils/fetchFilterCounts.js +182 -0
- package/src/utils/index.js +1 -0
- package/src/utils/libraryWidgetKeys.js +5 -0
- package/src/utils/readPath.js +27 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnAppNav.vue3.js","sources":["../../../../src/components/CnAppNav/CnAppNav.vue?vue&type=template&id=2b18f46f&lang.js"],"sourcesContent":["<!--\n CnAppNav — manifest-driven app navigation.\n\n Renders the manifest's `menu[]` array as a Nextcloud app navigation\n (NcAppNavigation + NcAppNavigationItem). One level of nested\n `children[]` is supported. Items are sorted by `order` at BOTH levels —\n top-level entries and a group's children — and items without an order\n render last. Items with a `permission` are filtered against\n the `permissions` prop — when the prop is omitted, all items render.\n\n An optional primary action renders above the main list as an\n NcAppNavigationNew button (e.g. a \"new\" button or an active-context\n switcher). Hosts supply it via the `#primary-action` slot (full\n control over dynamic content + click handling); a static\n `nav.primaryAction` ({ label, icon?, route?, href? }) manifest field\n is the declarative fallback. The slot wins when both are present;\n nothing renders when neither is.\n\n Items split into three RENDERED groups by `section`, plus one section\n this component deliberately renders nowhere:\n - `section: \"main\"` (default) — top of the navigation, scrollable.\n - `section: \"footer\"` — rendered in NcAppNavigation's `#footer` slot,\n OUTSIDE the scrollable list and directly above the settings foldout,\n so they stay visible regardless of how long the main menu is. (The\n earlier `pinned`-prop approach kept them inside the scroll container:\n `margin-top: auto` only bottom-pins while the list does not overflow,\n so apps with long menus showed them mid-scroll.) For always-visible,\n non-settings links: Documentation, Features & Roadmap, About.\n - `section: \"settings\"` — rendered INSIDE an NcAppNavigationSettings\n foldout (the NC-native gear-icon button that slides a panel open).\n A \"Personal settings\" entry is auto-prepended at the top of the\n foldout (opens the host app's NcAppSettingsDialog via\n cnOpenUserSettings); opt out with `nav.includePersonalSettings:\n false`. Right below it, an \"Admin settings\" entry is auto-prepended\n for INSTANCE ADMINS only — gated on the `isAdmin` prop (computed by\n CnAppRoot from `getCurrentUser()?.isAdmin`). It is a LINK to\n `/settings/admin/<appId>` that opens in a NEW TAB and carries an\n open-in-new marker (the destination is outside the app — the marker\n says so up front, the new tab keeps the app open), not a modal: per\n ADR-079 app-level\n configuration lives in Nextcloud's own settings framework, which\n authorizes it server-side. `isAdmin` gates VISIBILITY only and is\n never an authorization decision; it is also NOT interchangeable with\n `isOwner`, which means \"owns this app\" rather than \"administers this\n instance\" and keeps its own separate uses. The\n foldout mounts whenever there are settings items OR personal\n settings is enabled — so every app shows a Settings gear with at\n least Personal settings. It is only fully suppressed when there are\n no settings items AND `nav.includePersonalSettings: false`.\n - `section: \"integrations\"` — rendered by this component NOWHERE. Per\n ADR-110 these are the links that LEAVE this app for another one, and\n they belong in the Integrations section at the bottom of the per-user\n settings modal, which CnAppRoot owns. A link to another app cannot be\n the active route and carries no counter, so putting it in the nav\n makes another app's capability read as this app's feature. The one\n deep link that stays in the navigation is the auto-prepended Admin\n settings above — apps never declare that one themselves.\n\n Manifest and translate are injected from CnAppRoot by default but can\n also be passed as props for standalone use without CnAppRoot. Props\n win over inject when both are present.\n\n Items can opt out of routing in favour of a built-in action by\n setting `action` on the manifest entry. Supported keywords:\n `\"user-settings\"` invokes the `cnOpenUserSettings` provide-injected\n by CnAppRoot, which opens the host app's NcAppSettingsDialog modal;\n `\"admin-settings\"` opens `/settings/admin/<appId>` in a new tab (ADR-079);\n `\"replay-walkthrough\"` invokes `cnReplayWalkthrough` (optionally\n with the item's `tourId`) to re-run the product walkthrough from\n the first step (ADR-043). Both `route` and `href` are ignored when\n `action` is set. The injects default to a no-op so CnAppNav stays\n usable standalone (without a CnAppRoot ancestor).\n\n See REQ-JMR-004 of the json-manifest-renderer specification.\n-->\n<template>\n\t<NcAppNavigation :aria-label=\"ariaLabel\" data-testid=\"cn-nav\">\n\t\t<template v-if=\"$slots.search || $slots.search\" #search>\n\t\t\t<!--\n\t\t\t\t@slot search\n\t\t\t\t@description Forwarded into NcAppNavigation's #search slot.\n\t\t\t\tHosts mount NcAppNavigationSearch here. When unset, no search\n\t\t\t\tinput renders inside the navigation.\n\t\t\t-->\n\t\t\t<slot name=\"search\" />\n\t\t</template>\n\t\t<!-- @slot primary-action Optional primary action rendered above the\n\t\t main list as NcAppNavigation's top region (e.g. an app's \"new\"\n\t\t button or an active-context switcher). When provided, it takes\n\t\t precedence over the manifest-declared primaryAction (page-scoped\n\t\t OR nav root). Omitted entirely when neither the slot nor any\n\t\t resolvable primaryAction is present. -->\n\t\t<slot name=\"primary-action\">\n\t\t\t<!-- A primary action with an href or route is a real link.\n\t\t\t NcAppNavigationNew cannot render one, so this mirrors its\n\t\t\t wrapper and button. -->\n\t\t\t<div\n\t\t\t\tv-if=\"activePrimaryAction && primaryActionLink\"\n\t\t\t\tclass=\"app-navigation-new cn-app-nav__primary-action\"\n\t\t\t\tdata-testid=\"cn-nav-primary-action\">\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\twide\n\t\t\t\t\tv-bind=\"primaryActionLink\"\n\t\t\t\t\t@click=\"onPrimaryActionClick\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<component :is=\"primaryActionIconComponent\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ resolveLabel(activePrimaryAction) }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t\t<NcAppNavigationNew\n\t\t\t\tv-else-if=\"activePrimaryAction\"\n\t\t\t\t:text=\"resolveLabel(activePrimaryAction)\"\n\t\t\t\tdata-testid=\"cn-nav-primary-action\"\n\t\t\t\t@click=\"onPrimaryActionClick\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<component :is=\"primaryActionIconComponent\" :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t</NcAppNavigationNew>\n\t\t</slot>\n\t\t<template #list>\n\t\t\t<template v-for=\"item in mainItems\">\n\t\t\t\t<NcAppNavigationCaption\n\t\t\t\t\tv-if=\"isCaption(item)\"\n\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\t:name=\"resolveLabel(item)\"\n\t\t\t\t\t:data-testid=\"`cn-nav-caption-${item.id}`\" />\n\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\tv-else\n\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\t:name=\"resolveLabel(item)\"\n\t\t\t\t\t:to=\"itemTo(item)\"\n\t\t\t\t\t:href=\"itemHref(item)\"\n\t\t\t\t\t:icon=\"cssIconClass(item)\"\n\t\t\t\t\t:active=\"isActive(item)\"\n\t\t\t\t\t:pinned=\"Boolean(item.pinned)\"\n\t\t\t\t\t:allowCollapse=\"visibleChildren(item).length > 0\"\n\t\t\t\t\t:open=\"isItemOpen(item)\"\n\t\t\t\t\t:data-testid=\"`cn-nav-entry-${item.id}`\"\n\t\t\t\t\t:data-cn-route=\"item.route\"\n\t\t\t\t\t@update:open=\"setItemOpen(item, $event)\"\n\t\t\t\t\t@click=\"onItemClick(item, $event)\">\n\t\t\t\t\t<template v-if=\"mdiIconComponent(item) || isRichIcon(item) || isRegistryIcon(item) || isUnresolvedIcon(item)\" #icon>\n\t\t\t\t\t\t<component :is=\"mdiIconComponent(item)\" v-if=\"mdiIconComponent(item)\" :size=\"20\" />\n\t\t\t\t\t\t<HelpCircleOutline v-else-if=\"isUnresolvedIcon(item)\" :size=\"20\" />\n\t\t\t\t\t\t<CnMenuItemIcon v-else :icon=\"item.icon\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<template v-if=\"resolveCount(item)\" #counter>\n\t\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\t\t:count=\"resolveCount(item)\"\n\t\t\t\t\t\t\t:active=\"isActive(item)\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<template v-if=\"hasItemActionsSlot(item)\" #actions>\n\t\t\t\t\t\t<!--\n\t\t\t\t\t\t\t@slot `item-${item.id}-actions`\n\t\t\t\t\t\t\t@description Per-item scoped slot whose content lands inside\n\t\t\t\t\t\t\tthe NcAppNavigationItem's #actions slot for the menu entry\n\t\t\t\t\t\t\twith that id. Use it for inline NcActions menus (e.g. an\n\t\t\t\t\t\t\titem-level \"Pin\" button). Scope receives the menu item.\n\t\t\t\t\t\t\t@binding {object} item The menu item descriptor.\n\t\t\t\t\t\t-->\n\t\t\t\t\t\t<slot :name=\"`item-${item.id}-actions`\" :item=\"item\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\t\tv-for=\"child in visibleChildren(item)\"\n\t\t\t\t\t\t:key=\"child.id\"\n\t\t\t\t\t\t:name=\"resolveLabel(child)\"\n\t\t\t\t\t\t:to=\"itemTo(child)\"\n\t\t\t\t\t\t:href=\"itemHref(child)\"\n\t\t\t\t\t\t:icon=\"cssIconClass(child)\"\n\t\t\t\t\t\t:active=\"isActive(child)\"\n\t\t\t\t\t\t:pinned=\"Boolean(child.pinned)\"\n\t\t\t\t\t\t:data-testid=\"`cn-nav-entry-${child.id}`\"\n\t\t\t\t\t\t:data-cn-route=\"child.route\"\n\t\t\t\t\t\t@click=\"onItemClick(child, $event)\">\n\t\t\t\t\t\t<template v-if=\"mdiIconComponent(child) || isRichIcon(child) || isRegistryIcon(child) || isUnresolvedIcon(child)\" #icon>\n\t\t\t\t\t\t\t<component :is=\"mdiIconComponent(child)\" v-if=\"mdiIconComponent(child)\" :size=\"20\" />\n\t\t\t\t\t\t\t<HelpCircleOutline v-else-if=\"isUnresolvedIcon(child)\" :size=\"20\" />\n\t\t\t\t\t\t\t<CnMenuItemIcon v-else :icon=\"child.icon\" :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template v-if=\"resolveCount(child)\" #counter>\n\t\t\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\t\t\t:count=\"resolveCount(child)\"\n\t\t\t\t\t\t\t\t:active=\"isActive(child)\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcAppNavigationItem>\n\t\t\t\t</NcAppNavigationItem>\n\t\t\t</template>\n\t\t</template>\n\t\t<template v-if=\"footerItems.length > 0 || showSettingsFoldout\" #footer>\n\t\t\t<!-- Footer-section entries (Documentation, Features & Roadmap,\n\t\t\t About) live in NcAppNavigation's #footer slot — OUTSIDE the\n\t\t\t scrollable list — so they stay visible above the settings\n\t\t\t foldout no matter how long the main menu is. The pinned-prop\n\t\t\t approach only bottom-pinned while the list did not overflow.\n\n\t\t\t NcAppNavigationList, not a bare <ul>: its padding, gap and\n\t\t\t hover highlight are scoped to itself, so they survive being\n\t\t\t slotted. The main list's inset comes from NcAppNavigation's\n\t\t\t scope, which slot content does not carry. -->\n\t\t\t<NcAppNavigationList v-if=\"footerItems.length > 0\" class=\"cn-app-nav__footer-list\">\n\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\tv-for=\"item in footerItems\"\n\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\t:name=\"resolveLabel(item)\"\n\t\t\t\t\t:to=\"itemTo(item)\"\n\t\t\t\t\t:href=\"itemHref(item)\"\n\t\t\t\t\t:icon=\"cssIconClass(item)\"\n\t\t\t\t\t:active=\"isActive(item)\"\n\t\t\t\t\t:data-testid=\"`cn-nav-entry-${item.id}`\"\n\t\t\t\t\t:data-cn-route=\"item.route\"\n\t\t\t\t\t@click=\"onItemClick(item, $event)\">\n\t\t\t\t\t<template v-if=\"mdiIconComponent(item) || isRichIcon(item) || isRegistryIcon(item) || isUnresolvedIcon(item)\" #icon>\n\t\t\t\t\t\t<component :is=\"mdiIconComponent(item)\" v-if=\"mdiIconComponent(item)\" :size=\"20\" />\n\t\t\t\t\t\t<HelpCircleOutline v-else-if=\"isUnresolvedIcon(item)\" :size=\"20\" />\n\t\t\t\t\t\t<CnMenuItemIcon v-else :icon=\"item.icon\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<template v-if=\"resolveCount(item)\" #counter>\n\t\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\t\t:count=\"resolveCount(item)\"\n\t\t\t\t\t\t\t:active=\"isActive(item)\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcAppNavigationItem>\n\t\t\t</NcAppNavigationList>\n\t\t\t<!-- Settings foldout (section: \"settings\" items). NC-native\n\t\t\t gear-icon button that slides open a panel; the first entry\n\t\t\t is an auto-prepended \"Personal settings\" that opens the\n\t\t\t host app's NcAppSettingsDialog via cnOpenUserSettings. -->\n\t\t\t<NcAppNavigationSettings\n\t\t\t\tv-if=\"showSettingsFoldout\"\n\t\t\t\t:name=\"settingsFoldoutLabel\"\n\t\t\t\tdata-testid=\"cn-nav-settings\">\n\t\t\t\t<ul class=\"cn-app-nav__settings-list\">\n\t\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\t\tv-if=\"includePersonalSettings\"\n\t\t\t\t\t\t:name=\"personalSettingsLabel\"\n\t\t\t\t\t\tdata-testid=\"cn-nav-personal-settings\"\n\t\t\t\t\t\t@click=\"onPersonalSettingsClick\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Cog :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcAppNavigationItem>\n\t\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\t\tv-if=\"roadmapEntry\"\n\t\t\t\t\t\t:name=\"roadmapEntry.label\"\n\t\t\t\t\t\t:to=\"roadmapEntry.to\"\n\t\t\t\t\t\t:href=\"roadmapEntry.href\"\n\t\t\t\t\t\tdata-testid=\"cn-nav-roadmap\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<MapMarkerPath :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcAppNavigationItem>\n\t\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\t\tv-if=\"documentationEntry\"\n\t\t\t\t\t\t:name=\"documentationEntry.label\"\n\t\t\t\t\t\t:href=\"documentationEntry.href\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\tdata-testid=\"cn-nav-documentation\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<BookOpenVariant :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcAppNavigationItem>\n\t\t\t\t\t<!-- The target is Nextcloud's OWN settings area, not an\n\t\t\t\t\t in-app route: a new tab keeps the app open, and the\n\t\t\t\t\t trailing open-in-new marker tells the user they are\n\t\t\t\t\t leaving the app before they click. The new tab comes\n\t\t\t\t\t from the href being ABSOLUTE (see adminSettingsHref) —\n\t\t\t\t\t NcAppNavigationItem ignores a `target` attr and only\n\t\t\t\t\t sets target=\"_blank\" for scheme-prefixed hrefs. -->\n\t\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\t\tv-if=\"showAdminSettingsLink\"\n\t\t\t\t\t\t:name=\"adminSettingsLabel\"\n\t\t\t\t\t\t:href=\"adminSettingsHref\"\n\t\t\t\t\t\tdata-testid=\"cn-nav-admin-settings\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<ShieldAccountOutline :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #counter>\n\t\t\t\t\t\t\t<OpenInNew :size=\"16\" :title=\"opensInNewTabHint\" data-testid=\"cn-nav-admin-settings-external\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcAppNavigationItem>\n\t\t\t\t\t<template v-for=\"item in settingsItems\">\n\t\t\t\t\t\t<NcAppNavigationCaption\n\t\t\t\t\t\t\tv-if=\"isCaption(item)\"\n\t\t\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\t\t\t:name=\"resolveLabel(item)\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-nav-caption-${item.id}`\" />\n\t\t\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\t\t\t:name=\"resolveLabel(item)\"\n\t\t\t\t\t\t\t:to=\"itemTo(item)\"\n\t\t\t\t\t\t\t:href=\"itemHref(item)\"\n\t\t\t\t\t\t\t:icon=\"cssIconClass(item)\"\n\t\t\t\t\t\t\t:active=\"isActive(item)\"\n\t\t\t\t\t\t\t:data-cn-route=\"item.route\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-nav-entry-${item.id}`\"\n\t\t\t\t\t\t\t@click=\"onItemClick(item, $event)\">\n\t\t\t\t\t\t\t<template v-if=\"mdiIconComponent(item) || isRichIcon(item) || isRegistryIcon(item) || isUnresolvedIcon(item)\" #icon>\n\t\t\t\t\t\t\t\t<component :is=\"mdiIconComponent(item)\" v-if=\"mdiIconComponent(item)\" :size=\"20\" />\n\t\t\t\t\t\t\t\t<HelpCircleOutline v-else-if=\"isUnresolvedIcon(item)\" :size=\"20\" />\n\t\t\t\t\t\t\t\t<CnMenuItemIcon v-else :icon=\"item.icon\" :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t<template v-if=\"resolveCount(item)\" #counter>\n\t\t\t\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\t\t\t\t:count=\"resolveCount(item)\"\n\t\t\t\t\t\t\t\t\t:active=\"isActive(item)\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t</NcAppNavigationItem>\n\t\t\t\t\t</template>\n\t\t\t\t</ul>\n\t\t\t</NcAppNavigationSettings>\n\t\t</template>\n\t</NcAppNavigation>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcAppNavigation, NcAppNavigationCaption, NcAppNavigationItem, NcAppNavigationList, NcAppNavigationNew, NcAppNavigationSettings, NcButton, NcCounterBubble } from '@nextcloud/vue'\nimport BookOpenVariant from 'vue-material-design-icons/BookOpenVariant.vue'\nimport Cog from 'vue-material-design-icons/Cog.vue'\n// ADR-077 rule 4: visible fallback for an unresolvable icon name.\nimport HelpCircleOutline from 'vue-material-design-icons/HelpCircleOutline.vue'\nimport MapMarkerPath from 'vue-material-design-icons/MapMarkerPath.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport ShieldAccountOutline from 'vue-material-design-icons/ShieldAccountOutline.vue'\nimport { ICON_MAP } from '../CnIcon/CnIcon.vue'\nimport CnMenuItemIcon from '../CnMenuWidget/CnMenuItemIcon.vue'\nimport { isAppInstalled } from '../../utils/appInstalled.js'\nimport { isSvgPath } from '../../utils/iconUtils.js'\nimport { passesContextPredicates } from '../../utils/visibleIfContext.js'\n// The legacy `icon-*` → MDI bridge lives beside CnIcon now, so the menu EDITOR\n// resolves a seeded `icon-comment` the same way this nav does. It used to be a\n// map local to this file, which is why the nav drew a proper glyph and the\n// editor drew a help-circle \"?\" for the very same manifest value.\nimport { bridgedMdiForCssIcon } from '../CnIcon/cssIconBridge.js'\nimport { getSemanticIconComponent } from '../CnIcon/semanticIcons.js'\nimport { hasRegistryIcon, isCustomIconUrl } from '../CnWidgetGrid/widgetIcons.js'\n\n/**\n * Order two menu entries by their manifest `order`.\n *\n * Entries carrying an `order` come first, ascending; entries without one render\n * last and keep their relative order. Used at BOTH nav levels so a group's\n * children obey the same rule as the top-level list.\n *\n * @param {object} a First entry.\n * @param {object} b Second entry.\n * @return {number} Comparator result.\n */\nfunction byManifestOrder(a, b) {\n\tconst aHas = typeof a.order === 'number'\n\tconst bHas = typeof b.order === 'number'\n\tif (aHas && !bHas) {\n\t\treturn -1\n\t}\n\tif (!aHas && bHas) {\n\t\treturn 1\n\t}\n\tif (!aHas && !bHas) {\n\t\treturn 0\n\t}\n\treturn a.order - b.order\n}\n\nexport default {\n\tname: 'CnAppNav',\n\n\tcomponents: {\n\t\tNcAppNavigation,\n\t\tNcAppNavigationCaption,\n\t\tNcAppNavigationItem,\n\t\tNcAppNavigationList,\n\t\tNcAppNavigationNew,\n\t\tNcAppNavigationSettings,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tCnMenuItemIcon,\n\t\tCog,\n\t\tHelpCircleOutline,\n\t\tShieldAccountOutline,\n\t\tMapMarkerPath,\n\t\tBookOpenVariant,\n\t\tOpenInNew,\n\t},\n\n\tinject: {\n\t\tcnManifest: { default: null },\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/**\n\t\t * Provided by CnAppRoot — opens the host app's\n\t\t * NcAppSettingsDialog. Defaults to a no-op so CnAppNav is\n\t\t * still usable when mounted outside a CnAppRoot ancestor;\n\t\t * the click silently does nothing in that case rather than\n\t\t * throwing.\n\t\t */\n\t\tcnOpenUserSettings: { default: () => () => {} },\n\t\t/**\n\t\t * Provided by CnAppRoot — the consuming app's slug, used to build\n\t\t * the \"Admin settings\" link target `/settings/admin/<appId>`.\n\t\t * Overridden by the explicit `appId` prop when both are present.\n\t\t * Null outside a CnAppRoot ancestor, in which case the link is\n\t\t * suppressed (ADR-079 §2).\n\t\t */\n\t\tcnAppId: { default: null },\n\t\t/**\n\t\t * Provided by CnAppRoot — restarts the product walkthrough (ADR-043)\n\t\t * from the first step. Bound to menu entries declaring\n\t\t * `action: \"replay-walkthrough\"` (optionally with a `tourId`).\n\t\t * Defaults to a no-op so CnAppNav stays usable standalone.\n\t\t */\n\t\tcnReplayWalkthrough: { default: () => () => {} },\n\t\t/**\n\t\t * Provided by CnAppRoot — reactive `{ [register]: { [schema]: number } }`\n\t\t * map populated from `useObjectStore().totals` for every\n\t\t * `count: \"auto\"` menu entry whose resolved page is `type: \"index\"`\n\t\t * with `register + schema` in its config. Defaults to an empty\n\t\t * object so `resolveCount` returns `null` (no badge) when CnAppNav\n\t\t * is mounted outside a CnAppRoot ancestor.\n\t\t *\n\t\t * @type {{ [register: string]: { [schema: string]: number } }}\n\t\t */\n\t\tcnMenuCounts: { default: () => ({}) },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Manifest object. Falls back to injected `cnManifest`. Provide\n\t\t * explicitly when mounting CnAppNav outside of CnAppRoot.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tmanifest: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to injected `cnTranslate`,\n\t\t * which itself defaults to an identity function.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * List of permission strings the current user holds. Items\n\t\t * declaring a `permission` only render when their permission\n\t\t * appears in this list. When the prop is omitted (or empty),\n\t\t * all items are visible regardless of their permission field.\n\t\t *\n\t\t * @type {Array<string>}\n\t\t */\n\t\tpermissions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Whether the current user is an OWNER of this app\n\t\t * (admin-settings-owner-gating capability). Computed by CnAppRoot\n\t\t * from `currentUserGroups` ∩ `permissions.owners` and/or a manifest\n\t\t * `runtime.user` owner signal — deliberately NOT `OC.isUserAdmin()`.\n\t\t * Read by nothing in this component: the auto-included \"Admin\n\t\t * settings\" entry is gated on `isAdmin` alone (see the file header\n\t\t * and `showAdminSettingsLink`). The prop stays because CnAppRoot\n\t\t * binds it as published API and owner-gating is its own question,\n\t\t * not the instance-admin one.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tisOwner: { // eslint-disable-line vue/no-unused-properties -- published prop bound by CnAppRoot; owner-gating is decided upstream, not here\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Whether the current user administers this Nextcloud instance.\n\t\t * Computed by CnAppRoot from `getCurrentUser()?.isAdmin`\n\t\t * (`@nextcloud/auth`) — NOT the legacy `OC.isUserAdmin()` global,\n\t\t * and NOT interchangeable with `isOwner`, which is about owning a\n\t\t * given app rather than administering the instance (ADR-079 §3).\n\t\t *\n\t\t * Gates VISIBILITY of the \"Admin settings\" link only. It is not an\n\t\t * authorization decision and MUST NOT be used as one: the boundary\n\t\t * is Nextcloud's settings framework, which refuses\n\t\t * `/settings/admin/<app>` server-side for non-admins. Defaults to\n\t\t * `false` so CnAppNav mounted standalone never shows the link.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tisAdmin: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * App id used to build the \"Admin settings\" link target\n\t\t * (`/settings/admin/<appId>`). Falls back to the injected\n\t\t * `cnAppId` provided by CnAppRoot. With neither available the\n\t\t * link is suppressed rather than pointing at a broken URL.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Accessible name for the navigation landmark, forwarded to\n\t\t * `NcAppNavigation`'s `aria-label`.\n\t\t *\n\t\t * `@nextcloud/vue` 9 requires `ariaLabel` or `ariaLabelledby` and warns\n\t\t * when neither is set: a `<nav>` with no accessible name is announced as\n\t\t * an unlabelled landmark, so a screen-reader user tabbing the landmark\n\t\t * list cannot tell it from any other nav on the page (WCAG 2.4.6 /\n\t\t * 1.3.1). The default covers every consumer without a code change; pass\n\t\t * your own when an app shows more than one navigation, since the point\n\t\t * of the name is to tell them apart.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Main navigation'),\n\t\t},\n\t},\n\n\temits: ['primary-action', 'primary-action-click'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/**\n\t\t\t * Per-item expand/collapse state for menu groups, keyed by\n\t\t\t * item id. Seeded lazily from the manifest's `item.open` by\n\t\t\t * `isItemOpen`; written by the collapse chevron (via\n\t\t\t * `@update:open`), by title clicks on route-less group items\n\t\t\t * (which TOGGLE open/closed), and by title clicks on a group\n\t\t\t * that ALSO carries a route or href (which force it OPEN,\n\t\t\t * alongside the navigation — see `onItemClick`).\n\t\t\t */\n\t\t\topenState: {},\n\n\t\t}\n\t},\n\n\tcomputed: {\n\t\teffectiveManifest() {\n\t\t\treturn this.manifest ?? this.cnManifest\n\t\t},\n\n\t\teffectiveTranslate() {\n\t\t\treturn this.translate ?? this.cnTranslate\n\t\t},\n\n\t\t/**\n\t\t * Manifest-declared root-level primary action (`nav.primaryAction`)\n\t\t * — retained for backwards compatibility with code that reads this\n\t\t * computed. New code SHOULD read `activePrimaryAction` instead,\n\t\t * which also handles the page-scoped override.\n\t\t *\n\t\t * @return {object|null} `{ label, icon?, route?, href?, id?, payload? }` or null.\n\t\t */\n\t\tprimaryAction() {\n\t\t\treturn this.effectiveManifest?.nav?.primaryAction ?? null\n\t\t},\n\n\t\t/**\n\t\t * Resolved primary action for the current route. Resolution order:\n\t\t * 1. The current route's matching `pages[].primaryAction`\n\t\t * 2. `manifest.nav.primaryAction` as app-wide default\n\t\t * 3. `null` — no primary-action button renders\n\t\t *\n\t\t * Page-scoped declarations always win over the nav-root default.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tactivePrimaryAction() {\n\t\t\tconst pages = this.effectiveManifest?.pages ?? []\n\t\t\tconst routeName = this.$route?.name\n\t\t\tif (routeName) {\n\t\t\t\tconst page = pages.find((p) => p.id === routeName)\n\t\t\t\tif (page && page.primaryAction) {\n\t\t\t\t\treturn page.primaryAction\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn this.effectiveManifest?.nav?.primaryAction ?? null\n\t\t},\n\n\t\t/**\n\t\t * MDI icon component for the resolved primary action. Honors the\n\t\t * action's `icon` field when set; falls back to the canonical\n\t\t * `Plus` icon to match NC's NcAppNavigationNew default styling.\n\t\t *\n\t\t * @return {import('vue').Component}\n\t\t */\n\t\tprimaryActionIconComponent() {\n\t\t\treturn this.mdiIconComponent(this.activePrimaryAction) ?? Plus\n\t\t},\n\n\t\t/**\n\t\t * Link props for the primary action's button: an `href` opens in a\n\t\t * new tab, a `route` is a router link. Null when the action is\n\t\t * neither (or there is no router), so it stays a plain button.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tprimaryActionLink() {\n\t\t\tconst action = this.activePrimaryAction\n\t\t\tif (!action) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (action.href) {\n\t\t\t\treturn { href: action.href, target: '_blank' }\n\t\t\t}\n\t\t\tif (action.route && this.$router) {\n\t\t\t\treturn { to: { name: action.route } }\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/**\n\t\t * All visible items (filtered by permission and visibleIf conditions,\n\t\t * sorted by order). Retained for backwards-compat with the previous\n\t\t * public API and tests that read this computed; new code should use\n\t\t * `mainItems` / `settingsItems` instead.\n\t\t */\n\t\tvisibleItems() {\n\t\t\treturn (this.effectiveManifest?.menu ?? [])\n\t\t\t\t.filter((item) => this.passesPermission(item) && this.passesVisibleIf(item))\n\t\t\t\t.slice()\n\t\t\t\t.sort(byManifestOrder)\n\t\t},\n\n\t\t/**\n\t\t * Items that render in the top list (default placement).\n\t\t *\n\t\t * Note the `=== 'main'` rather than a negated list: an entry whose\n\t\t * section this component does not render must fall through to\n\t\t * NOTHING, not to the main list. `section: \"integrations\"` is\n\t\t * exactly that case — those entries render in CnAppRoot's\n\t\t * per-user settings modal, not in the navigation (ADR-110) — and a\n\t\t * `!== 'footer' && !== 'settings'` test would have quietly put\n\t\t * every one of them back in the nav this contract removes them\n\t\t * from.\n\t\t */\n\t\tmainItems() {\n\t\t\treturn this.visibleItems.filter((item) => (item.section ?? 'main') === 'main')\n\t\t},\n\n\t\t/**\n\t\t * Items pinned to the bottom of the navigation (section:\n\t\t * \"footer\") — rendered as flat NcAppNavigationItems above the\n\t\t * settings foldout. For always-visible, non-settings entries:\n\t\t * Documentation, Features & Roadmap, About.\n\t\t */\n\t\tfooterItems() {\n\t\t\treturn this.visibleItems.filter((item) => item.section === 'footer')\n\t\t},\n\n\t\t/**\n\t\t * Items that render INSIDE the NcAppNavigationSettings foldout\n\t\t * (section: \"settings\"). The foldout is the NC-native gear-icon\n\t\t * button that slides a panel open; these entries are app-level\n\t\t * configuration pages (Forms, Pipelines, Automations, …).\n\t\t */\n\t\tsettingsItems() {\n\t\t\treturn this.visibleItems.filter((item) => item.section === 'settings')\n\t\t},\n\n\t\t/**\n\t\t * Whether the settings foldout mounts. Mounts when there is at\n\t\t * least one `section: \"settings\"` item OR personal settings is\n\t\t * enabled — so every app shows a Settings gear with at least the\n\t\t * auto-prepended \"Personal settings\" entry. Only fully suppressed\n\t\t * when there are no settings items AND `nav.includePersonalSettings`\n\t\t * is explicitly `false`.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tshowSettingsFoldout() {\n\t\t\treturn this.settingsItems.length > 0 || this.includePersonalSettings\n\t\t\t\t|| this.roadmapEntry !== null || this.documentationEntry !== null\n\t\t},\n\n\t\t/**\n\t\t * Whether to auto-prepend the \"Personal settings\" entry at the top\n\t\t * of the foldout. On by default; opt out with\n\t\t * `manifest.nav.includePersonalSettings: false` (e.g. when the app\n\t\t * has no per-user NcAppSettingsDialog wired through\n\t\t * cnOpenUserSettings).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tincludePersonalSettings() {\n\t\t\treturn this.effectiveManifest?.nav?.includePersonalSettings !== false\n\t\t},\n\n\t\t/**\n\t\t * Label for the foldout's gear button. Manifest override:\n\t\t * `nav.settingsLabel`; defaults to \"Settings\".\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsettingsFoldoutLabel() {\n\t\t\tconst custom = this.effectiveManifest?.nav?.settingsLabel\n\t\t\tif (typeof custom === 'string' && custom.length > 0) {\n\t\t\t\treturn this.effectiveTranslate(custom)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Settings')\n\t\t},\n\n\t\t/**\n\t\t * Label for the auto-prepended Personal-settings entry.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tpersonalSettingsLabel() {\n\t\t\treturn t('nextcloud-vue', 'Personal settings')\n\t\t},\n\n\t\t/**\n\t\t * Optional \"Features & roadmap\" foldout entry. Enabled via\n\t\t * `nav.includeRoadmap`; `nav.roadmapUrl` is treated as an external link\n\t\t * when it looks like a URL, otherwise as an in-app router target.\n\t\t *\n\t\t * @return {{label: string, to: (string|null), href: (string|null)}|null}\n\t\t */\n\t\troadmapEntry() {\n\t\t\tconst nav = this.effectiveManifest?.nav\n\t\t\tif (!nav || nav.includeRoadmap !== true) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst label = (typeof nav.roadmapLabel === 'string' && nav.roadmapLabel)\n\t\t\t\t? this.effectiveTranslate(nav.roadmapLabel)\n\t\t\t\t: t('nextcloud-vue', 'Features & roadmap')\n\t\t\tconst target = typeof nav.roadmapUrl === 'string' ? nav.roadmapUrl.trim() : ''\n\t\t\tconst external = /^(https?:)?\\/\\//.test(target)\n\t\t\treturn { label, to: (target && !external) ? target : null, href: external ? target : null }\n\t\t},\n\n\t\t/**\n\t\t * Optional \"Documentation\" foldout entry. Enabled via\n\t\t * `nav.includeDocumentation`; always an external link (`nav.documentationUrl`).\n\t\t *\n\t\t * @return {{label: string, href: string}|null}\n\t\t */\n\t\tdocumentationEntry() {\n\t\t\tconst nav = this.effectiveManifest?.nav\n\t\t\tif (!nav || nav.includeDocumentation !== true) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst target = typeof nav.documentationUrl === 'string' ? nav.documentationUrl.trim() : ''\n\t\t\tif (!target) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst label = (typeof nav.documentationLabel === 'string' && nav.documentationLabel)\n\t\t\t\t? this.effectiveTranslate(nav.documentationLabel)\n\t\t\t\t: t('nextcloud-vue', 'Documentation')\n\t\t\treturn { label, href: target }\n\t\t},\n\n\t\t/**\n\t\t * Label for the auto-prepended Admin-settings entry.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tadminSettingsLabel() {\n\t\t\treturn t('nextcloud-vue', 'Admin settings')\n\t\t},\n\n\t\t/**\n\t\t * Accessible name (and hover tooltip) of the open-in-new marker on\n\t\t * the Admin-settings entry — the visual cue that the link leaves\n\t\t * the app for Nextcloud's own settings area.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\topensInNewTabHint() {\n\t\t\treturn t('nextcloud-vue', 'Opens in a new tab')\n\t\t},\n\n\t\t/**\n\t\t * Resolved app id for the Admin-settings link — the explicit\n\t\t * `appId` prop, else the `cnAppId` provided by CnAppRoot.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\teffectiveAppId() {\n\t\t\tconst id = this.appId || this.cnAppId\n\t\t\treturn (typeof id === 'string' && id.trim()) ? id.trim() : ''\n\t\t},\n\n\t\t/**\n\t\t * Target of the Admin-settings entry: the app's section in\n\t\t * Nextcloud's own admin settings (ADR-079 §1). This is a LINK, not\n\t\t * a modal — the app never re-implements the settings surface, and\n\t\t * the destination is authorized server-side by the settings\n\t\t * framework.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tadminSettingsHref() {\n\t\t\tif (!this.effectiveAppId) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// ABSOLUTE (same-origin) on purpose: NcAppNavigationItem renders\n\t\t\t// target=\"_blank\" only for hrefs its isExternal() deems external\n\t\t\t// (scheme-prefixed) and ignores a passed `target` — a relative\n\t\t\t// path therefore always opens in the SAME tab. The absolute form\n\t\t\t// gets the new tab while keeping native anchor semantics\n\t\t\t// (middle-click, copy link).\n\t\t\tconst path = generateUrl('/settings/admin/{appId}', { appId: this.effectiveAppId })\n\t\t\tconst origin = (typeof window !== 'undefined' && window.location && window.location.origin) || ''\n\t\t\treturn origin + path\n\t\t},\n\n\t\t/**\n\t\t * Whether to show the Admin-settings link: the user administers the\n\t\t * instance AND we can build a target. Visibility only — see the\n\t\t * `isAdmin` prop docs; the access check lives in Nextcloud.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tshowAdminSettingsLink() {\n\t\t\treturn this.isAdmin === true && this.adminSettingsHref !== ''\n\t\t},\n\n\t\t/**\n\t\t * Route name of the menu item that best matches the current route.\n\t\t * A direct match (current route name IS a menu target) wins;\n\t\t * otherwise the current path is matched against each item's page\n\t\t * path and the LONGEST prefix wins, so detail / nested routes (e.g.\n\t\t * `/expenses/:id`) light up — and auto-expand — their index entry\n\t\t * even though the detail route name is not in the menu. Longest\n\t\t * prefix disambiguates nested namespaces (e.g. `/pos` vs\n\t\t * `/pos/refunds`). Returns null when nothing matches.\n\t\t *\n\t\t * @return {string|null}\n\t\t */\n\t\tactiveRouteName() {\n\t\t\tconst routeName = this.$route?.name\n\t\t\tconst path = this.$route?.path\n\t\t\tconst flat = []\n\t\t\tfor (const item of this.visibleItems) {\n\t\t\t\tflat.push(item)\n\t\t\t\tfor (const child of this.visibleChildren(item)) {\n\t\t\t\t\tflat.push(child)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (routeName && flat.some((it) => it.route === routeName)) {\n\t\t\t\treturn routeName\n\t\t\t}\n\t\t\tif (!path) {\n\t\t\t\treturn routeName ?? null\n\t\t\t}\n\t\t\tlet best = null\n\t\t\tlet bestLen = -1\n\t\t\tfor (const it of flat) {\n\t\t\t\tif (!it.route) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst pagePath = this.pageForItem(it)?.route\n\t\t\t\tif (!pagePath || pagePath === '/' || pagePath.includes(':')) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (path === pagePath || path.startsWith(pagePath + '/')) {\n\t\t\t\t\tif (pagePath.length > bestLen) {\n\t\t\t\t\t\tbest = it.route\n\t\t\t\t\t\tbestLen = pagePath.length\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn best ?? routeName ?? null\n\t\t},\n\t},\n\n\twatch: {\n\t\tactiveRouteName: {\n\t\t\timmediate: true,\n\t\t\thandler(_current, previous) {\n\t\t\t\tthis.pinGroupsEntered(previous)\n\t\t\t},\n\t\t},\n\n\t\t// The manifest can arrive after the route did.\n\t\tvisibleItems() {\n\t\t\tthis.pinGroupsEntered(this.activeRouteName)\n\t\t},\n\t},\n\n\tcreated() {\n\t\t// Non-reactive one-shot latch for warnAutoCountMisconfigured(). It has\n\t\t// to be seeded here rather than lazily on first use: `resolveCount()`\n\t\t// runs DURING RENDER, and Vue 3's instance proxy emits\n\t\t// \"Property \"_autoCountWarned\" was accessed during render but is not\n\t\t// defined on instance\" for the first read of an unset instance field\n\t\t// (Vue 2 read it back as plain `undefined`, silently). It deliberately\n\t\t// stays out of `data()`: a reactive Set mutated inside render would\n\t\t// re-trigger the render effect.\n\t\tthis._autoCountWarned = new Set()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve a menu item's `icon` string to an MDI Vue component. MDI names\n\t\t * resolve via the per-app `registerIcons()` registry; legacy Nextcloud\n\t\t * `icon-*` class names resolve via the shared `cssIconBridge` so they\n\t\t * render monochrome (fill:currentColor) like every other glyph.\n\t\t * Returns `null` (→ the `:icon=\"cssIconClass(item)\"` CSS-class fallback)\n\t\t * for unbridged `icon-*` names and unknown MDI names.\n\t\t *\n\t\t * @param {{ icon?: string }} item Menu item descriptor.\n\t\t * @return {import('vue').Component|null}\n\t\t */\n\t\tmdiIconComponent(item) {\n\t\t\tconst icon = item?.icon\n\t\t\tif (typeof icon !== 'string' || icon.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (icon.startsWith('icon-')) {\n\t\t\t\treturn bridgedMdiForCssIcon(icon) || null\n\t\t\t}\n\t\t\t// ADR-077: the app's own registerIcons() entries win (so an app can\n\t\t\t// override), then the shared semantic vocabulary — which resolves\n\t\t\t// WITHOUT the app having registered anything. Before the vocabulary\n\t\t\t// existed, a name the app forgot to register resolved to null here\n\t\t\t// and the entry rendered with no icon at all.\n\t\t\treturn ICON_MAP[icon] || getSemanticIconComponent(icon) || null\n\t\t},\n\n\t\t/**\n\t\t * Whether the item declares an icon that NOTHING can resolve — not the\n\t\t * app registry, not the semantic vocabulary, not the widget registry, and\n\t\t * not a URL / SVG path.\n\t\t *\n\t\t * ADR-077 rule 4: such an icon MUST degrade to a visible fallback glyph.\n\t\t * Historically it rendered as empty space, which is indistinguishable\n\t\t * from a deliberate no-icon design choice — 29 of hrmq's 72 nav entries\n\t\t * were silently blank this way. A visible \"?\" is discoverable; blank is\n\t\t * not.\n\t\t *\n\t\t * `icon-*` values are excluded: those have their own CSS-class fallback\n\t\t * path via the `cssIconClass` method.\n\t\t *\n\t\t * @param {{ icon?: string }} item Menu item descriptor.\n\t\t * @return {boolean} true when the declared icon resolves to nothing.\n\t\t */\n\t\tisUnresolvedIcon(item) {\n\t\t\tconst icon = item?.icon\n\t\t\tif (typeof icon !== 'string' || icon.length === 0) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (icon.startsWith('icon-')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn !this.mdiIconComponent(item)\n\t\t\t\t&& !this.isRichIcon(item)\n\t\t\t\t&& !this.isRegistryIcon(item)\n\t\t},\n\n\t\t/**\n\t\t * Whether the item's icon is a raw SVG path or an image URL (incl. the\n\t\t * `data:` URIs the bundled NL-government sets emit) — neither of which is a\n\t\t * component, so `ICON_MAP` can't resolve it and the `#icon` slot must\n\t\t * render it through CnMenuItemIcon instead.\n\t\t *\n\t\t * Without this, an icon picked from CnIconBrowser's Gemeente / Den Haag /\n\t\t * RVO tabs would simply not appear in the navigation.\n\t\t *\n\t\t * @param {{ icon?: string }} item Menu item descriptor.\n\t\t * @return {boolean} true for path/URL icons.\n\t\t */\n\t\tisRichIcon(item) {\n\t\t\tconst icon = item?.icon\n\t\t\tif (typeof icon !== 'string' || icon.length === 0 || icon.startsWith('icon-')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn isCustomIconUrl(icon) || isSvgPath(icon)\n\t\t},\n\n\t\t/**\n\t\t * Whether the item's icon is a plain MDI *name* that the shared widget-icon\n\t\t * registry can render (e.g. \"Heart\", \"Home\" — what CnIconBrowser emits from\n\t\t * its component-name sources).\n\t\t *\n\t\t * `ICON_MAP` only holds what the consuming app passed to `registerIcons()`,\n\t\t * and an app rendering USER-AUTHORED manifests cannot pre-register whatever\n\t\t * icon a user might pick — Buildiq registers none at all. So a picked name\n\t\t * failed `mdiIconComponent` (not registered) AND `isRichIcon` (not a URL or\n\t\t * SVG path), the `#icon` slot was skipped entirely, and the menu item rendered\n\t\t * with NO icon — even though CnMenuItemIcon → CnWidgetIcon could resolve it.\n\t\t *\n\t\t * Gate on the registry actually HAVING the name: `getIconComponent` answers\n\t\t * the default icon for anything unknown, so gating on it would render a wrong\n\t\t * (but plausible) icon for a typo instead of none.\n\t\t *\n\t\t * @param {{ icon?: string }} item Menu item descriptor.\n\t\t * @return {boolean} true when the widget-icon registry can render this name.\n\t\t */\n\t\tisRegistryIcon(item) {\n\t\t\treturn hasRegistryIcon(item?.icon)\n\t\t},\n\n\t\t/**\n\t\t * Pass-through for the `:icon` prop on NcAppNavigationItem when\n\t\t * the manifest declares a Nextcloud CSS-class icon (`icon-*`).\n\t\t * Returns an empty string when the icon is an MDI name OR a bridged\n\t\t * `icon-*` (rendered via the `#icon` slot instead), so NcAppNavigationItem\n\t\t * doesn't also paint a CSS-class background-image.\n\t\t *\n\t\t * @param {{ icon?: string }} item Menu item descriptor.\n\t\t * @return {string}\n\t\t */\n\t\tcssIconClass(item) {\n\t\t\tconst icon = item?.icon\n\t\t\tif (typeof icon !== 'string' || icon.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (!icon.startsWith('icon-')) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn bridgedMdiForCssIcon(icon) ? '' : icon\n\t\t},\n\n\t\tpassesPermission(item) {\n\t\t\tif (!item.permission) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (!this.permissions || this.permissions.length === 0) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.permissions.includes(item.permission)\n\t\t},\n\n\t\t/**\n\t\t * Evaluate a menu item's `visibleIf` condition block.\n\t\t *\n\t\t * Returns `true` (visible) when:\n\t\t * - No `visibleIf` is declared (backwards-compatible default).\n\t\t * - `visibleIf.appInstalled` is set AND the named app is\n\t\t * installed / enabled (checked via `OC.appswebroots` then the\n\t\t * capabilities fallback, cached per page load by `isAppInstalled`).\n\t\t * - Context-path predicates (any key that is a dot-separated path\n\t\t * into `manifest.runtime`) all pass against the current runtime\n\t\t * data. Predicates are evaluated by `passesContextPredicates`.\n\t\t * Example: `{ \"user.primaryRole\": { \"in\": [\"hr\", \"compliance\"] } }`\n\t\t * hides the entry unless `manifest.runtime.user.primaryRole` is\n\t\t * `\"hr\"` or `\"compliance\"`. When the runtime block is absent the\n\t\t * entry is hidden (fail-safe: never show role-gated items to\n\t\t * unidentified users).\n\t\t *\n\t\t * All conditions are combined with implicit AND — every condition\n\t\t * must pass for the item to render. Returns `false` when any fails.\n\t\t *\n\t\t * @param {object} item Menu item (or child) to evaluate.\n\t\t * @return {boolean} Whether the item should render.\n\t\t */\n\t\tpassesVisibleIf(item) {\n\t\t\tconst condition = item.visibleIf\n\t\t\tif (!condition || typeof condition !== 'object') {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\t// Specialised condition: appInstalled.\n\t\t\tif (condition.appInstalled) {\n\t\t\t\tif (!isAppInstalled(condition.appInstalled)) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Context-path predicates: any non-reserved key is a dot-path\n\t\t\t// into manifest.runtime evaluated by passesContextPredicates.\n\t\t\tconst runtime = this.effectiveManifest?.runtime ?? null\n\t\t\tif (!passesContextPredicates(condition, runtime)) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\treturn true\n\t\t},\n\n\t\t/**\n\t\t * A group's visible children, in `order`.\n\t\t *\n\t\t * The SORT is the fix for a silent no-op: this used to filter only, so\n\t\t * `order` on a child was accepted, documented and ignored. It bites\n\t\t * hardest on relocated leaves, because `applyMenuRelocations()` walks\n\t\t * the menu backwards and appends each move, which lands a group's\n\t\t * children in REVERSE manifest order — an order nobody wrote down and\n\t\t * no app can influence except by reordering `manifest.json` itself.\n\t\t * Pipelinq's Customer Support group read \"Projecten, My Work, Queue,\n\t\t * Tasks, All tickets\" against declared orders of 55, 20, 10, 60, 50.\n\t\t *\n\t\t * Same comparator as the top level, so one rule governs both: items\n\t\t * without an `order` render last, in their existing relative order\n\t\t * (Array.prototype.sort is stable).\n\t\t *\n\t\t * @param {object} item The group whose children to render.\n\t\t * @return {Array<object>} Visible children, ordered.\n\t\t */\n\t\tvisibleChildren(item) {\n\t\t\tif (!Array.isArray(item.children)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn item.children\n\t\t\t\t.filter((c) => this.passesPermission(c) && this.passesVisibleIf(c))\n\t\t\t\t.sort(byManifestOrder)\n\t\t},\n\n\t\tresolveLabel(item) {\n\t\t\treturn this.effectiveTranslate(item.label)\n\t\t},\n\n\t\tisActive(item) {\n\t\t\tif (item.href || !item.route) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn item.route === this.activeRouteName\n\t\t},\n\n\t\t/**\n\t\t * Whether a menu entry renders as a `NcAppNavigationCaption`\n\t\t * (`type: \"caption\"`) rather than a clickable\n\t\t * `NcAppNavigationItem`. Caption entries ignore `route`, `href`,\n\t\t * `action`, `icon`, `count`, `children`, and `pinned`.\n\t\t *\n\t\t * @param {{ type?: string }} item Menu entry descriptor.\n\t\t * @return {boolean}\n\t\t */\n\t\tisCaption(item) {\n\t\t\treturn item?.type === 'caption'\n\t\t},\n\n\t\t/**\n\t\t * Whether the host has registered a scoped slot named\n\t\t * `item-${id}-actions` for this menu item. Used to gate template\n\t\t * rendering of the per-item `#actions` slot pass-through so items\n\t\t * without a matching slot don't render an empty `<template #actions>`.\n\t\t *\n\t\t * @param {{ id: string }} item Menu entry descriptor.\n\t\t * @return {boolean}\n\t\t */\n\t\thasItemActionsSlot(item) {\n\t\t\tif (!item?.id) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst name = `item-${item.id}-actions`\n\t\t\treturn Boolean((this.$slots && this.$slots[name])\n\t\t\t\t|| (this.$slots && this.$slots[name]))\n\t\t},\n\n\t\t/**\n\t\t * Resolve the count value to render in this entry's\n\t\t * `NcCounterBubble` (in the `#counter` slot of\n\t\t * `NcAppNavigationItem`).\n\t\t *\n\t\t * - Literal positive integer in `item.count` → return as-is.\n\t\t * - `item.count === \"auto\"` → look up the entry's resolved page;\n\t\t * when that page is `type: \"index\"` with a `register`/`schema`\n\t\t * in its `config`, return\n\t\t * `cnMenuCounts[register][schema] ?? null`.\n\t\t *\n\t\t * Returns `null` (no badge) when:\n\t\t * - `item.count` is unset or falsy\n\t\t * - The literal value is `0`\n\t\t * - `count === \"auto\"` but no matching index page resolves\n\t\t * (also emits a one-shot `console.warn`)\n\t\t * - The store has no entry for the resolved `(register, schema)`\n\t\t *\n\t\t * @param {object} item Menu entry descriptor.\n\t\t * @return {number|null} Count to render, or null for no badge.\n\t\t */\n\t\tresolveCount(item) {\n\t\t\tconst raw = item?.count\n\t\t\tif (raw === undefined || raw === null) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\treturn raw > 0 ? raw : null\n\t\t\t}\n\t\t\tif (raw !== 'auto') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst page = this.pageForItem(item)\n\t\t\tconst register = page?.config?.register\n\t\t\tconst schema = page?.config?.schema\n\t\t\tif (page?.type !== 'index' || !register || !schema) {\n\t\t\t\tthis.warnAutoCountMisconfigured(item)\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst value = this.cnMenuCounts?.[register]?.[schema]\n\t\t\tif (typeof value !== 'number' || value <= 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/**\n\t\t * One-shot `console.warn` per menu-item id for misconfigured\n\t\t * `count: \"auto\"` entries (no resolvable index page). Keeps the\n\t\t * console quiet on re-renders.\n\t\t *\n\t\t * @param {{ id: string }} item Menu entry descriptor.\n\t\t * @return {void}\n\t\t * @private\n\t\t */\n\t\twarnAutoCountMisconfigured(item) {\n\t\t\tif (this._autoCountWarned.has(item.id)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis._autoCountWarned.add(item.id)\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.warn(`[CnAppNav] Menu entry \"${item.id}\" declares count: \"auto\" but has no resolvable index-type page with register + schema config — no badge will render.`)\n\t\t},\n\n\t\t/**\n\t\t * Look up an item's resolved page (`pages[]` entry whose `id`\n\t\t * matches the menu item's `route`) — used to decide whether the\n\t\t * NcAppNavigationItem should match its router-link `exact`.\n\t\t *\n\t\t * @param {object} item Menu item to resolve.\n\t\t * @return {object|null} Matching page entry, or null when the item\n\t\t * has no `route` or no page matches.\n\t\t */\n\t\tpageForItem(item) {\n\t\t\tif (!item.route) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst pages = this.effectiveManifest?.pages ?? []\n\t\t\treturn pages.find((p) => p.id === item.route) ?? null\n\t\t},\n\n\t\t/**\n\t\t * Build the `:to` value for an `NcAppNavigationItem`. Action\n\t\t * items (`action: \"user-settings\"`) and `href` items return\n\t\t * `null` so the entry is NOT a vue-router link: action items\n\t\t * fall through to the click handler, `href` items render a real\n\t\t * anchor via `itemHref`. Route items return a named route.\n\t\t *\n\t\t * @param {object} item Menu item being rendered.\n\t\t * @return {object|null} A `{ name }` route object, or null for\n\t\t * action / href / route-less items.\n\t\t */\n\t\titemTo(item) {\n\t\t\tif (item.action) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (item.href) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (!item.route) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Carry optional query params so a nav entry can deep-link to a\n\t\t\t// pre-filtered index page (e.g. one entry per case type → Cases?caseType=…).\n\t\t\treturn item.query ? { name: item.route, query: item.query } : { name: item.route }\n\t\t},\n\n\t\t/**\n\t\t * Build the `:href` value for an `NcAppNavigationItem`. Returns\n\t\t * the item's `href` so the entry renders as a real anchor whose\n\t\t * destination is visible on hover and which gets the native link\n\t\t * cursor. `NcAppNavigationItem` adds `target=\"_blank\"` itself for\n\t\t * external (`scheme://`) URLs, so those open in a new tab while\n\t\t * internal app paths (e.g. `/index.php/apps/foo/`) navigate in the\n\t\t * same tab — no `window.open` interception. The `admin-settings`\n\t\t * action links to `adminSettingsHref` (absolute, so it opens in a\n\t\t * new tab); other action items carry no href. Returns `null` for\n\t\t * non-href items so the entry stays a router-link / button.\n\t\t *\n\t\t * @param {object} item Menu item being rendered.\n\t\t * @return {string|null} The destination URL, or null.\n\t\t */\n\t\titemHref(item) {\n\t\t\tif (item.action === 'admin-settings') {\n\t\t\t\treturn this.adminSettingsHref || null\n\t\t\t}\n\t\t\tif (item.action) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn item.href || null\n\t\t},\n\n\t\t/**\n\t\t * Whether a menu group renders expanded. Local `openState` (set\n\t\t * by the chevron, a title click, or `pinGroupsEntered`) wins;\n\t\t * otherwise the group auto-expands when it contains the active\n\t\t * route — so deep-linking to a child page reveals which group it\n\t\t * lives in — and finally falls back to the manifest's `item.open`\n\t\t * for the initial render.\n\t\t *\n\t\t * @param {{ id: string, open?: boolean }} item Menu entry descriptor.\n\t\t * @return {boolean}\n\t\t */\n\t\tisItemOpen(item) {\n\t\t\tconst local = this.openState[item.id]\n\t\t\tif (local !== undefined) {\n\t\t\t\treturn local\n\t\t\t}\n\t\t\tif (this.hasActiveChild(item)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn Boolean(item.open)\n\t\t},\n\n\t\t/**\n\t\t * Whether any of a group's visible children is the active route.\n\t\t * Drives auto-expansion of the parent group on page load.\n\t\t *\n\t\t * @param {object} item Menu entry descriptor.\n\t\t * @return {boolean}\n\t\t */\n\t\thasActiveChild(item) {\n\t\t\treturn this.visibleChildren(item).some((child) => this.isActive(child))\n\t\t},\n\n\t\t/**\n\t\t * Record a group's expand/collapse state. Bound to\n\t\t * NcAppNavigationItem's `@update:open` (chevron clicks) and\n\t\t * called from `onItemClick` for title clicks on route-less\n\t\t * group items.\n\t\t *\n\t\t * @param {{ id: string }} item Menu entry descriptor.\n\t\t * @param {boolean} value New open state.\n\t\t */\n\t\tsetItemOpen(item, value) {\n\t\t\tthis.openState[item.id] = value\n\t\t},\n\n\t\t/**\n\t\t * Pin open every group the active route just entered.\n\t\t *\n\t\t * Auto-expansion used to be read off the route alone, so a group\n\t\t * opened for a deep link collapsed the moment the route left it. A\n\t\t * group opened for you stays open until you close it. Only a group\n\t\t * the route ENTERS is pinned: one you closed while on one of its\n\t\t * children stays closed while you move between them.\n\t\t *\n\t\t * @param {string|null} previousRoute The active route before this change, or undefined at first render.\n\t\t */\n\t\tpinGroupsEntered(previousRoute) {\n\t\t\tfor (const item of this.visibleItems) {\n\t\t\t\tconst children = this.visibleChildren(item)\n\t\t\t\tif (children.length === 0 || !this.hasActiveChild(item)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst wasInside = children.some((child) => child.route === previousRoute)\n\t\t\t\tif (!wasInside || this.openState[item.id] === undefined) {\n\t\t\t\t\tthis.openState[item.id] = true\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Click handler. Dispatch order: action keyword → group\n\t\t * open/toggle. For `action: \"user-settings\"` invokes the injected\n\t\t * `cnOpenUserSettings` (provided by CnAppRoot) and prevents\n\t\t * default; `action: \"admin-settings\"` is a real link to\n\t\t * `/settings/admin/<appId>` (see `itemHref`) and is left to the\n\t\t * browser; for `action:\n\t\t * \"replay-walkthrough\"` invokes the injected\n\t\t * `cnReplayWalkthrough(item.tourId)` and prevents default. `href`\n\t\t * items with no children are NOT handled here — they render a real\n\t\t * anchor via `itemHref`, so the browser navigates natively\n\t\t * (external URLs open in a new tab, internal app paths in the same\n\t\t * tab).\n\t\t *\n\t\t * A group's children visibility on click depends on whether the\n\t\t * group ITSELF has a destination:\n\t\t * - Route-less, href-less: a pure group header, so its anchor is\n\t\t * a dead `#` link and the click's only job is to TOGGLE the\n\t\t * children open/closed (same effect as the collapse chevron).\n\t\t * - Carries a `route` or `href`: the click is a real navigation\n\t\t * (handled natively by `:to` / the anchor, not prevented here),\n\t\t * and this additionally forces the group OPEN — never closed —\n\t\t * so the reader lands on the group's own page without a second\n\t\t * click to see what else it holds. The collapse chevron is the\n\t\t * only control that can close such a group again.\n\t\t *\n\t\t * @param {object} item Menu item being clicked.\n\t\t * @param {Event} [event] Native click event (used to call\n\t\t * preventDefault for action / group links).\n\t\t */\n\t\tonItemClick(item, event) {\n\t\t\tif (item.action === 'user-settings') {\n\t\t\t\tif (event && typeof event.preventDefault === 'function') {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t}\n\t\t\t\tthis.cnOpenUserSettings()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (item.action === 'admin-settings') {\n\t\t\t\t// ADR-079: a real link to Nextcloud's own settings (see\n\t\t\t\t// itemHref), followed natively in a new tab. Without an app\n\t\t\t\t// id there is no href, and the click does nothing.\n\t\t\t\tif (!this.adminSettingsHref && event && typeof event.preventDefault === 'function') {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (item.action === 'replay-walkthrough') {\n\t\t\t\tif (event && typeof event.preventDefault === 'function') {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t}\n\t\t\t\tthis.cnReplayWalkthrough(item.tourId)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.visibleChildren(item).length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!item.route && !item.href) {\n\t\t\t\tif (event && typeof event.preventDefault === 'function') {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t}\n\t\t\t\tthis.setItemOpen(item, !this.isItemOpen(item))\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// The item is ALSO a real destination: let the click navigate\n\t\t\t// natively and reveal the children alongside it.\n\t\t\tthis.setItemOpen(item, true)\n\t\t},\n\n\t\t/**\n\t\t * Click handler for the auto-prepended Personal-settings entry in\n\t\t * the settings foldout. Invokes the injected `cnOpenUserSettings`\n\t\t * (provided by CnAppRoot → opens the host's NcAppSettingsDialog).\n\t\t * No-op inject when mounted standalone.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonPersonalSettingsClick() {\n\t\t\tthis.cnOpenUserSettings()\n\t\t},\n\n\t\t/**\n\t\t * Click handler for the manifest-declared primary action. Emits\n\t\t * `@primary-action` AND the back-compat `@primary-action-click`\n\t\t * event for host-side handling. Navigation is the link's own: an\n\t\t * `href` or `route` action renders as a real link (see\n\t\t * `primaryActionLink`), so the browser or router follows it after\n\t\t * these events. Not invoked when the host overrides the\n\t\t * `#primary-action` slot (it provides its own handler).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonPrimaryActionClick() {\n\t\t\tconst action = this.activePrimaryAction\n\t\t\tif (!action) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst payload = {\n\t\t\t\tid: action.id,\n\t\t\t\tlabel: action.label,\n\t\t\t\ticon: action.icon,\n\t\t\t\troute: action.route,\n\t\t\t\thref: action.href,\n\t\t\t\tpayload: action.payload,\n\t\t\t\tpage: this.$route?.name,\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event primary-action Emitted when the resolved primary-action button is clicked. Payload includes the action descriptor (`{ id, label, icon, route, href, payload }`) plus the current `page` (route name) for host dispatchers.\n\t\t\t * @type {{ id?: string, label: string, icon?: string, route?: string, href?: string, payload?: unknown, page?: string }}\n\t\t\t */\n\t\t\tthis.$emit('primary-action', payload)\n\t\t\t/**\n\t\t\t * @event primary-action-click Back-compat alias for `@primary-action`. Payload is the resolved primary action object as declared in the manifest.\n\t\t\t */\n\t\t\tthis.$emit('primary-action-click', action)\n\t\t},\n\t},\n}\n</script>\n\n<style>\n/*\n * The legacy `icon-*` classes in Nextcloud render a background-image\n * with a hardcoded dark fill, so they stay grey when an entry becomes\n * active (text turns white against the primary-element background).\n * Force the icon to white in the active state to match the label.\n * Only applies when the icon is provided via the `icon` prop (CSS\n * class) — items using a `<template #icon>` MDI component already\n * inherit `currentColor`.\n */\n.app-navigation-entry.active .app-navigation-entry-icon[class*=\"icon-\"] {\n\tfilter: brightness(0) invert(1);\n}\n\n/* The link form of the primary action, matching NcAppNavigationNew's scoped layout. */\n.cn-app-nav__primary-action {\n\tdisplay: block;\n\tpadding: calc(var(--default-grid-baseline, 4px) * 2);\n}\n\n/*\n * Footer-section items (section: \"footer\") render in NcAppNavigation's\n * #footer slot, outside the scroll container, so they stay visible above\n * the settings foldout regardless of menu length. (The interim\n * `pinned`-prop approach kept them inside the scrollable list, where\n * `margin-top: auto` only bottom-pins while the list does not overflow —\n * long menus showed Documentation / Features & roadmap mid-scroll.)\n *\n * No `padding` here on purpose: it comes from NcAppNavigationList's own\n * `var(--app-navigation-padding)`, so it stays in step with the main list.\n *\n * As a direct `> ul` child of `.app-navigation__content`, NC makes it a\n * shrinkable, scrollable flex item, and NcAppNavigationList adds\n * `overflow-y: auto` besides. That let a two-entry list be squeezed below its\n * content and grow a scrollbar on a short menu, so opt out of both.\n */\n.cn-app-nav__footer-list {\n\tlist-style: none;\n\tmargin: 0;\n\tflex-shrink: 0 !important;\n\toverflow: visible !important;\n}\n\n/*\n * Align the settings foldout's items with the main/footer list items,\n * whose icons sit at a 16px inset. The foldout's items live in a bare\n * <ul> inside NcAppNavigationSettings' panel (`#app-settings`, padding\n * 3px), so without this they start ~5px further left and the icon\n * column looks ragged. (The native settings-toggle button sits ~1px\n * left of the 16px baseline, but NC owns that with an !important\n * shorthand — a sub-pixel difference we leave to the native component.)\n */\n.cn-app-nav__settings-list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding-inline-start: 5px;\n}\n</style>\n"],"names":["_Fragment","_renderList","_openBlock","_createElementBlock","_createBlock","_resolveDynamicComponent","_createVNode","_renderSlot","_mergeProps"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnAppNav.vue3.js","sources":["../../../../src/components/CnAppNav/CnAppNav.vue?vue&type=template&id=2b18f46f&lang.js"],"sourcesContent":["<!--\n CnAppNav — manifest-driven app navigation.\n\n Renders the manifest's `menu[]` array as a Nextcloud app navigation\n (NcAppNavigation + NcAppNavigationItem). One level of nested\n `children[]` is supported. Items are sorted by `order` at BOTH levels —\n top-level entries and a group's children — and items without an order\n render last. Items with a `permission` are filtered against\n the `permissions` prop — when the prop is omitted, all items render.\n\n An optional primary action renders above the main list as an\n NcAppNavigationNew button (e.g. a \"new\" button or an active-context\n switcher). Hosts supply it via the `#primary-action` slot (full\n control over dynamic content + click handling); a static\n `nav.primaryAction` ({ label, icon?, route?, href? }) manifest field\n is the declarative fallback. The slot wins when both are present;\n nothing renders when neither is.\n\n Items split into three RENDERED groups by `section`, plus one section\n this component deliberately renders nowhere:\n - `section: \"main\"` (default) — top of the navigation, scrollable.\n - `section: \"footer\"` — rendered in NcAppNavigation's `#footer` slot,\n OUTSIDE the scrollable list and directly above the settings foldout,\n so they stay visible regardless of how long the main menu is. (The\n earlier `pinned`-prop approach kept them inside the scroll container:\n `margin-top: auto` only bottom-pins while the list does not overflow,\n so apps with long menus showed them mid-scroll.) For always-visible,\n non-settings links: Documentation, Features & Roadmap, About.\n - `section: \"settings\"` — rendered INSIDE an NcAppNavigationSettings\n foldout (the NC-native gear-icon button that slides a panel open).\n A \"Personal settings\" entry is auto-prepended at the top of the\n foldout (opens the host app's NcAppSettingsDialog via\n cnOpenUserSettings); opt out with `nav.includePersonalSettings:\n false`. Right below it, an \"Admin settings\" entry is auto-prepended\n for INSTANCE ADMINS only — gated on the `isAdmin` prop (computed by\n CnAppRoot from `getCurrentUser()?.isAdmin`). It is a LINK to\n `/settings/admin/<appId>` that opens in a NEW TAB and carries an\n open-in-new marker (the destination is outside the app — the marker\n says so up front, the new tab keeps the app open), not a modal: per\n ADR-079 app-level\n configuration lives in Nextcloud's own settings framework, which\n authorizes it server-side. `isAdmin` gates VISIBILITY only and is\n never an authorization decision; it is also NOT interchangeable with\n `isOwner`, which means \"owns this app\" rather than \"administers this\n instance\" and keeps its own separate uses. The\n foldout mounts whenever there are settings items OR personal\n settings is enabled — so every app shows a Settings gear with at\n least Personal settings. It is only fully suppressed when there are\n no settings items AND `nav.includePersonalSettings: false`.\n - `section: \"integrations\"` — rendered by this component NOWHERE. Per\n ADR-110 these are the links that LEAVE this app for another one, and\n they belong in the Integrations section at the bottom of the per-user\n settings modal, which CnAppRoot owns. A link to another app cannot be\n the active route and carries no counter, so putting it in the nav\n makes another app's capability read as this app's feature. The one\n deep link that stays in the navigation is the auto-prepended Admin\n settings above — apps never declare that one themselves.\n\n Manifest and translate are injected from CnAppRoot by default but can\n also be passed as props for standalone use without CnAppRoot. Props\n win over inject when both are present.\n\n Items can opt out of routing in favour of a built-in action by\n setting `action` on the manifest entry. Supported keywords:\n `\"user-settings\"` invokes the `cnOpenUserSettings` provide-injected\n by CnAppRoot, which opens the host app's NcAppSettingsDialog modal;\n `\"admin-settings\"` opens `/settings/admin/<appId>` in a new tab (ADR-079);\n `\"replay-walkthrough\"` invokes `cnReplayWalkthrough` (optionally\n with the item's `tourId`) to re-run the product walkthrough from\n the first step (ADR-043). Both `route` and `href` are ignored when\n `action` is set. The injects default to a no-op so CnAppNav stays\n usable standalone (without a CnAppRoot ancestor).\n\n See REQ-JMR-004 of the json-manifest-renderer specification.\n-->\n<template>\n\t<NcAppNavigation :aria-label=\"ariaLabel\" data-testid=\"cn-nav\">\n\t\t<template v-if=\"$slots.search || $slots.brand || resolvedBrand\" #search>\n\t\t\t<!--\n\t\t\t\t@slot brand\n\t\t\t\t@description Replace the brand block at the very top of the\n\t\t\t\tnavigation. Default content: the logo, name and caption from\n\t\t\t\tthe `brand` prop or the manifest's `nav.brand`. Renders\n\t\t\t\tnothing when neither is set.\n\t\t\t\t@binding {object|null} brand The resolved brand (`{ logo, name, caption, alt }`), or null.\n\t\t\t-->\n\t\t\t<slot name=\"brand\" :brand=\"resolvedBrand\">\n\t\t\t\t<div\n\t\t\t\t\tv-if=\"resolvedBrand\"\n\t\t\t\t\tclass=\"cn-app-nav__brand\"\n\t\t\t\t\tdata-testid=\"cn-nav-brand\">\n\t\t\t\t\t<!-- Decorative beside a name: the name already says whose\n\t\t\t\t\t app this is, and an alt text would say it twice. A logo\n\t\t\t\t\t on its own carries the name as its alt text. -->\n\t\t\t\t\t<img\n\t\t\t\t\t\tv-if=\"resolvedBrand.logo\"\n\t\t\t\t\t\tclass=\"cn-app-nav__brand-logo\"\n\t\t\t\t\t\t:src=\"resolvedBrand.logo\"\n\t\t\t\t\t\t:alt=\"resolvedBrand.name ? '' : resolvedBrand.alt\">\n\t\t\t\t\t<span v-if=\"resolvedBrand.name || resolvedBrand.caption\" class=\"cn-app-nav__brand-text\">\n\t\t\t\t\t\t<strong v-if=\"resolvedBrand.name\" class=\"cn-app-nav__brand-name\">{{ resolvedBrand.name }}</strong>\n\t\t\t\t\t\t<span v-if=\"resolvedBrand.caption\" class=\"cn-app-nav__brand-caption\">{{ resolvedBrand.caption }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</slot>\n\t\t\t<!--\n\t\t\t\t@slot search\n\t\t\t\t@description Forwarded into NcAppNavigation's #search slot.\n\t\t\t\tHosts mount NcAppNavigationSearch here. When unset, no search\n\t\t\t\tinput renders inside the navigation.\n\t\t\t-->\n\t\t\t<slot name=\"search\" />\n\t\t</template>\n\t\t<!-- @slot primary-action Optional primary action rendered above the\n\t\t main list as NcAppNavigation's top region (e.g. an app's \"new\"\n\t\t button or an active-context switcher). When provided, it takes\n\t\t precedence over the manifest-declared primaryAction (page-scoped\n\t\t OR nav root). Omitted entirely when neither the slot nor any\n\t\t resolvable primaryAction is present. -->\n\t\t<slot name=\"primary-action\">\n\t\t\t<!-- A primary action with an href or route is a real link.\n\t\t\t NcAppNavigationNew cannot render one, so this mirrors its\n\t\t\t wrapper and button. -->\n\t\t\t<div\n\t\t\t\tv-if=\"activePrimaryAction && primaryActionLink\"\n\t\t\t\tclass=\"app-navigation-new cn-app-nav__primary-action\"\n\t\t\t\tdata-testid=\"cn-nav-primary-action\">\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\twide\n\t\t\t\t\tv-bind=\"primaryActionLink\"\n\t\t\t\t\t@click=\"onPrimaryActionClick\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<component :is=\"primaryActionIconComponent\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ resolveLabel(activePrimaryAction) }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t\t<NcAppNavigationNew\n\t\t\t\tv-else-if=\"activePrimaryAction\"\n\t\t\t\t:text=\"resolveLabel(activePrimaryAction)\"\n\t\t\t\tdata-testid=\"cn-nav-primary-action\"\n\t\t\t\t@click=\"onPrimaryActionClick\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<component :is=\"primaryActionIconComponent\" :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t</NcAppNavigationNew>\n\t\t</slot>\n\t\t<template #list>\n\t\t\t<template v-for=\"item in mainItems\">\n\t\t\t\t<NcAppNavigationCaption\n\t\t\t\t\tv-if=\"isCaption(item)\"\n\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\t:name=\"resolveLabel(item)\"\n\t\t\t\t\t:data-testid=\"`cn-nav-caption-${item.id}`\" />\n\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\tv-else\n\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\t:name=\"resolveLabel(item)\"\n\t\t\t\t\t:to=\"itemTo(item)\"\n\t\t\t\t\t:href=\"itemHref(item)\"\n\t\t\t\t\t:icon=\"cssIconClass(item)\"\n\t\t\t\t\t:active=\"isActive(item)\"\n\t\t\t\t\t:pinned=\"Boolean(item.pinned)\"\n\t\t\t\t\t:allowCollapse=\"visibleChildren(item).length > 0\"\n\t\t\t\t\t:open=\"isItemOpen(item)\"\n\t\t\t\t\t:data-testid=\"`cn-nav-entry-${item.id}`\"\n\t\t\t\t\t:data-cn-route=\"item.route\"\n\t\t\t\t\t@update:open=\"setItemOpen(item, $event)\"\n\t\t\t\t\t@click=\"onItemClick(item, $event)\">\n\t\t\t\t\t<template v-if=\"mdiIconComponent(item) || isRichIcon(item) || isRegistryIcon(item) || isUnresolvedIcon(item)\" #icon>\n\t\t\t\t\t\t<component :is=\"mdiIconComponent(item)\" v-if=\"mdiIconComponent(item)\" :size=\"20\" />\n\t\t\t\t\t\t<HelpCircleOutline v-else-if=\"isUnresolvedIcon(item)\" :size=\"20\" />\n\t\t\t\t\t\t<CnMenuItemIcon v-else :icon=\"item.icon\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<template v-if=\"resolveCount(item)\" #counter>\n\t\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\t\t:count=\"resolveCount(item)\"\n\t\t\t\t\t\t\t:active=\"isActive(item)\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<template v-if=\"hasItemActionsSlot(item)\" #actions>\n\t\t\t\t\t\t<!--\n\t\t\t\t\t\t\t@slot `item-${item.id}-actions`\n\t\t\t\t\t\t\t@description Per-item scoped slot whose content lands inside\n\t\t\t\t\t\t\tthe NcAppNavigationItem's #actions slot for the menu entry\n\t\t\t\t\t\t\twith that id. Use it for inline NcActions menus (e.g. an\n\t\t\t\t\t\t\titem-level \"Pin\" button). Scope receives the menu item.\n\t\t\t\t\t\t\t@binding {object} item The menu item descriptor.\n\t\t\t\t\t\t-->\n\t\t\t\t\t\t<slot :name=\"`item-${item.id}-actions`\" :item=\"item\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\t\tv-for=\"child in visibleChildren(item)\"\n\t\t\t\t\t\t:key=\"child.id\"\n\t\t\t\t\t\t:name=\"resolveLabel(child)\"\n\t\t\t\t\t\t:to=\"itemTo(child)\"\n\t\t\t\t\t\t:href=\"itemHref(child)\"\n\t\t\t\t\t\t:icon=\"cssIconClass(child)\"\n\t\t\t\t\t\t:active=\"isActive(child)\"\n\t\t\t\t\t\t:pinned=\"Boolean(child.pinned)\"\n\t\t\t\t\t\t:data-testid=\"`cn-nav-entry-${child.id}`\"\n\t\t\t\t\t\t:data-cn-route=\"child.route\"\n\t\t\t\t\t\t@click=\"onItemClick(child, $event)\">\n\t\t\t\t\t\t<template v-if=\"mdiIconComponent(child) || isRichIcon(child) || isRegistryIcon(child) || isUnresolvedIcon(child)\" #icon>\n\t\t\t\t\t\t\t<component :is=\"mdiIconComponent(child)\" v-if=\"mdiIconComponent(child)\" :size=\"20\" />\n\t\t\t\t\t\t\t<HelpCircleOutline v-else-if=\"isUnresolvedIcon(child)\" :size=\"20\" />\n\t\t\t\t\t\t\t<CnMenuItemIcon v-else :icon=\"child.icon\" :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template v-if=\"resolveCount(child)\" #counter>\n\t\t\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\t\t\t:count=\"resolveCount(child)\"\n\t\t\t\t\t\t\t\t:active=\"isActive(child)\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcAppNavigationItem>\n\t\t\t\t</NcAppNavigationItem>\n\t\t\t</template>\n\t\t</template>\n\t\t<template v-if=\"footerItems.length > 0 || showSettingsFoldout\" #footer>\n\t\t\t<!-- Footer-section entries (Documentation, Features & Roadmap,\n\t\t\t About) live in NcAppNavigation's #footer slot — OUTSIDE the\n\t\t\t scrollable list — so they stay visible above the settings\n\t\t\t foldout no matter how long the main menu is. The pinned-prop\n\t\t\t approach only bottom-pinned while the list did not overflow.\n\n\t\t\t NcAppNavigationList, not a bare <ul>: its padding, gap and\n\t\t\t hover highlight are scoped to itself, so they survive being\n\t\t\t slotted. The main list's inset comes from NcAppNavigation's\n\t\t\t scope, which slot content does not carry. -->\n\t\t\t<NcAppNavigationList v-if=\"footerItems.length > 0\" class=\"cn-app-nav__footer-list\">\n\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\tv-for=\"item in footerItems\"\n\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\t:name=\"resolveLabel(item)\"\n\t\t\t\t\t:to=\"itemTo(item)\"\n\t\t\t\t\t:href=\"itemHref(item)\"\n\t\t\t\t\t:icon=\"cssIconClass(item)\"\n\t\t\t\t\t:active=\"isActive(item)\"\n\t\t\t\t\t:data-testid=\"`cn-nav-entry-${item.id}`\"\n\t\t\t\t\t:data-cn-route=\"item.route\"\n\t\t\t\t\t@click=\"onItemClick(item, $event)\">\n\t\t\t\t\t<template v-if=\"mdiIconComponent(item) || isRichIcon(item) || isRegistryIcon(item) || isUnresolvedIcon(item)\" #icon>\n\t\t\t\t\t\t<component :is=\"mdiIconComponent(item)\" v-if=\"mdiIconComponent(item)\" :size=\"20\" />\n\t\t\t\t\t\t<HelpCircleOutline v-else-if=\"isUnresolvedIcon(item)\" :size=\"20\" />\n\t\t\t\t\t\t<CnMenuItemIcon v-else :icon=\"item.icon\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<template v-if=\"resolveCount(item)\" #counter>\n\t\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\t\t:count=\"resolveCount(item)\"\n\t\t\t\t\t\t\t:active=\"isActive(item)\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcAppNavigationItem>\n\t\t\t</NcAppNavigationList>\n\t\t\t<!-- Settings foldout (section: \"settings\" items). NC-native\n\t\t\t gear-icon button that slides open a panel; the first entry\n\t\t\t is an auto-prepended \"Personal settings\" that opens the\n\t\t\t host app's NcAppSettingsDialog via cnOpenUserSettings. -->\n\t\t\t<NcAppNavigationSettings\n\t\t\t\tv-if=\"showSettingsFoldout\"\n\t\t\t\t:name=\"settingsFoldoutLabel\"\n\t\t\t\tdata-testid=\"cn-nav-settings\">\n\t\t\t\t<ul class=\"cn-app-nav__settings-list\">\n\t\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\t\tv-if=\"includePersonalSettings\"\n\t\t\t\t\t\t:name=\"personalSettingsLabel\"\n\t\t\t\t\t\tdata-testid=\"cn-nav-personal-settings\"\n\t\t\t\t\t\t@click=\"onPersonalSettingsClick\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Cog :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcAppNavigationItem>\n\t\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\t\tv-if=\"roadmapEntry\"\n\t\t\t\t\t\t:name=\"roadmapEntry.label\"\n\t\t\t\t\t\t:to=\"roadmapEntry.to\"\n\t\t\t\t\t\t:href=\"roadmapEntry.href\"\n\t\t\t\t\t\tdata-testid=\"cn-nav-roadmap\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<MapMarkerPath :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcAppNavigationItem>\n\t\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\t\tv-if=\"documentationEntry\"\n\t\t\t\t\t\t:name=\"documentationEntry.label\"\n\t\t\t\t\t\t:href=\"documentationEntry.href\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\tdata-testid=\"cn-nav-documentation\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<BookOpenVariant :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcAppNavigationItem>\n\t\t\t\t\t<!-- The target is Nextcloud's OWN settings area, not an\n\t\t\t\t\t in-app route: a new tab keeps the app open, and the\n\t\t\t\t\t trailing open-in-new marker tells the user they are\n\t\t\t\t\t leaving the app before they click. The new tab comes\n\t\t\t\t\t from the href being ABSOLUTE (see adminSettingsHref) —\n\t\t\t\t\t NcAppNavigationItem ignores a `target` attr and only\n\t\t\t\t\t sets target=\"_blank\" for scheme-prefixed hrefs. -->\n\t\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\t\tv-if=\"showAdminSettingsLink\"\n\t\t\t\t\t\t:name=\"adminSettingsLabel\"\n\t\t\t\t\t\t:href=\"adminSettingsHref\"\n\t\t\t\t\t\tdata-testid=\"cn-nav-admin-settings\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<ShieldAccountOutline :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #counter>\n\t\t\t\t\t\t\t<OpenInNew :size=\"16\" :title=\"opensInNewTabHint\" data-testid=\"cn-nav-admin-settings-external\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcAppNavigationItem>\n\t\t\t\t\t<template v-for=\"item in settingsItems\">\n\t\t\t\t\t\t<NcAppNavigationCaption\n\t\t\t\t\t\t\tv-if=\"isCaption(item)\"\n\t\t\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\t\t\t:name=\"resolveLabel(item)\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-nav-caption-${item.id}`\" />\n\t\t\t\t\t\t<NcAppNavigationItem\n\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\t\t\t:name=\"resolveLabel(item)\"\n\t\t\t\t\t\t\t:to=\"itemTo(item)\"\n\t\t\t\t\t\t\t:href=\"itemHref(item)\"\n\t\t\t\t\t\t\t:icon=\"cssIconClass(item)\"\n\t\t\t\t\t\t\t:active=\"isActive(item)\"\n\t\t\t\t\t\t\t:data-cn-route=\"item.route\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-nav-entry-${item.id}`\"\n\t\t\t\t\t\t\t@click=\"onItemClick(item, $event)\">\n\t\t\t\t\t\t\t<template v-if=\"mdiIconComponent(item) || isRichIcon(item) || isRegistryIcon(item) || isUnresolvedIcon(item)\" #icon>\n\t\t\t\t\t\t\t\t<component :is=\"mdiIconComponent(item)\" v-if=\"mdiIconComponent(item)\" :size=\"20\" />\n\t\t\t\t\t\t\t\t<HelpCircleOutline v-else-if=\"isUnresolvedIcon(item)\" :size=\"20\" />\n\t\t\t\t\t\t\t\t<CnMenuItemIcon v-else :icon=\"item.icon\" :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t<template v-if=\"resolveCount(item)\" #counter>\n\t\t\t\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\t\t\t\t:count=\"resolveCount(item)\"\n\t\t\t\t\t\t\t\t\t:active=\"isActive(item)\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t</NcAppNavigationItem>\n\t\t\t\t\t</template>\n\t\t\t\t</ul>\n\t\t\t</NcAppNavigationSettings>\n\t\t</template>\n\t</NcAppNavigation>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcAppNavigation, NcAppNavigationCaption, NcAppNavigationItem, NcAppNavigationList, NcAppNavigationNew, NcAppNavigationSettings, NcButton, NcCounterBubble } from '@nextcloud/vue'\nimport BookOpenVariant from 'vue-material-design-icons/BookOpenVariant.vue'\nimport Cog from 'vue-material-design-icons/Cog.vue'\n// ADR-077 rule 4: visible fallback for an unresolvable icon name.\nimport HelpCircleOutline from 'vue-material-design-icons/HelpCircleOutline.vue'\nimport MapMarkerPath from 'vue-material-design-icons/MapMarkerPath.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport ShieldAccountOutline from 'vue-material-design-icons/ShieldAccountOutline.vue'\nimport { ICON_MAP } from '../CnIcon/CnIcon.vue'\nimport CnMenuItemIcon from '../CnMenuWidget/CnMenuItemIcon.vue'\nimport { isAppInstalled } from '../../utils/appInstalled.js'\nimport { isSvgPath } from '../../utils/iconUtils.js'\nimport { passesContextPredicates } from '../../utils/visibleIfContext.js'\n// The legacy `icon-*` → MDI bridge lives beside CnIcon now, so the menu EDITOR\n// resolves a seeded `icon-comment` the same way this nav does. It used to be a\n// map local to this file, which is why the nav drew a proper glyph and the\n// editor drew a help-circle \"?\" for the very same manifest value.\nimport { bridgedMdiForCssIcon } from '../CnIcon/cssIconBridge.js'\nimport { getSemanticIconComponent } from '../CnIcon/semanticIcons.js'\nimport { hasRegistryIcon, isCustomIconUrl } from '../CnWidgetGrid/widgetIcons.js'\n\n/**\n * Order two menu entries by their manifest `order`.\n *\n * Entries carrying an `order` come first, ascending; entries without one render\n * last and keep their relative order. Used at BOTH nav levels so a group's\n * children obey the same rule as the top-level list.\n *\n * @param {object} a First entry.\n * @param {object} b Second entry.\n * @return {number} Comparator result.\n */\nfunction byManifestOrder(a, b) {\n\tconst aHas = typeof a.order === 'number'\n\tconst bHas = typeof b.order === 'number'\n\tif (aHas && !bHas) {\n\t\treturn -1\n\t}\n\tif (!aHas && bHas) {\n\t\treturn 1\n\t}\n\tif (!aHas && !bHas) {\n\t\treturn 0\n\t}\n\treturn a.order - b.order\n}\n\nexport default {\n\tname: 'CnAppNav',\n\n\tcomponents: {\n\t\tNcAppNavigation,\n\t\tNcAppNavigationCaption,\n\t\tNcAppNavigationItem,\n\t\tNcAppNavigationList,\n\t\tNcAppNavigationNew,\n\t\tNcAppNavigationSettings,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tCnMenuItemIcon,\n\t\tCog,\n\t\tHelpCircleOutline,\n\t\tShieldAccountOutline,\n\t\tMapMarkerPath,\n\t\tBookOpenVariant,\n\t\tOpenInNew,\n\t},\n\n\tinject: {\n\t\tcnManifest: { default: null },\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/**\n\t\t * Provided by CnAppRoot — opens the host app's\n\t\t * NcAppSettingsDialog. Defaults to a no-op so CnAppNav is\n\t\t * still usable when mounted outside a CnAppRoot ancestor;\n\t\t * the click silently does nothing in that case rather than\n\t\t * throwing.\n\t\t */\n\t\tcnOpenUserSettings: { default: () => () => {} },\n\t\t/**\n\t\t * Provided by CnAppRoot — the consuming app's slug, used to build\n\t\t * the \"Admin settings\" link target `/settings/admin/<appId>`.\n\t\t * Overridden by the explicit `appId` prop when both are present.\n\t\t * Null outside a CnAppRoot ancestor, in which case the link is\n\t\t * suppressed (ADR-079 §2).\n\t\t */\n\t\tcnAppId: { default: null },\n\t\t/**\n\t\t * Provided by CnAppRoot — restarts the product walkthrough (ADR-043)\n\t\t * from the first step. Bound to menu entries declaring\n\t\t * `action: \"replay-walkthrough\"` (optionally with a `tourId`).\n\t\t * Defaults to a no-op so CnAppNav stays usable standalone.\n\t\t */\n\t\tcnReplayWalkthrough: { default: () => () => {} },\n\t\t/**\n\t\t * Provided by CnAppRoot — reactive `{ [register]: { [schema]: number } }`\n\t\t * map populated from `useObjectStore().totals` for every\n\t\t * `count: \"auto\"` menu entry whose resolved page is `type: \"index\"`\n\t\t * with `register + schema` in its config. Defaults to an empty\n\t\t * object so `resolveCount` returns `null` (no badge) when CnAppNav\n\t\t * is mounted outside a CnAppRoot ancestor.\n\t\t *\n\t\t * @type {{ [register: string]: { [schema: string]: number } }}\n\t\t */\n\t\tcnMenuCounts: { default: () => ({}) },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Manifest object. Falls back to injected `cnManifest`. Provide\n\t\t * explicitly when mounting CnAppNav outside of CnAppRoot.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tmanifest: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to injected `cnTranslate`,\n\t\t * which itself defaults to an identity function.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * List of permission strings the current user holds. Items\n\t\t * declaring a `permission` only render when their permission\n\t\t * appears in this list. When the prop is omitted (or empty),\n\t\t * all items are visible regardless of their permission field.\n\t\t *\n\t\t * @type {Array<string>}\n\t\t */\n\t\tpermissions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Whether the current user is an OWNER of this app\n\t\t * (admin-settings-owner-gating capability). Computed by CnAppRoot\n\t\t * from `currentUserGroups` ∩ `permissions.owners` and/or a manifest\n\t\t * `runtime.user` owner signal — deliberately NOT `OC.isUserAdmin()`.\n\t\t * Read by nothing in this component: the auto-included \"Admin\n\t\t * settings\" entry is gated on `isAdmin` alone (see the file header\n\t\t * and `showAdminSettingsLink`). The prop stays because CnAppRoot\n\t\t * binds it as published API and owner-gating is its own question,\n\t\t * not the instance-admin one.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tisOwner: { // eslint-disable-line vue/no-unused-properties -- published prop bound by CnAppRoot; owner-gating is decided upstream, not here\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Whether the current user administers this Nextcloud instance.\n\t\t * Computed by CnAppRoot from `getCurrentUser()?.isAdmin`\n\t\t * (`@nextcloud/auth`) — NOT the legacy `OC.isUserAdmin()` global,\n\t\t * and NOT interchangeable with `isOwner`, which is about owning a\n\t\t * given app rather than administering the instance (ADR-079 §3).\n\t\t *\n\t\t * Gates VISIBILITY of the \"Admin settings\" link only. It is not an\n\t\t * authorization decision and MUST NOT be used as one: the boundary\n\t\t * is Nextcloud's settings framework, which refuses\n\t\t * `/settings/admin/<app>` server-side for non-admins. Defaults to\n\t\t * `false` so CnAppNav mounted standalone never shows the link.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tisAdmin: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * App id used to build the \"Admin settings\" link target\n\t\t * (`/settings/admin/<appId>`). Falls back to the injected\n\t\t * `cnAppId` provided by CnAppRoot. With neither available the\n\t\t * link is suppressed rather than pointing at a broken URL.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Accessible name for the navigation landmark, forwarded to\n\t\t * `NcAppNavigation`'s `aria-label`.\n\t\t *\n\t\t * `@nextcloud/vue` 9 requires `ariaLabel` or `ariaLabelledby` and warns\n\t\t * when neither is set: a `<nav>` with no accessible name is announced as\n\t\t * an unlabelled landmark, so a screen-reader user tabbing the landmark\n\t\t * list cannot tell it from any other nav on the page (WCAG 2.4.6 /\n\t\t * 1.3.1). The default covers every consumer without a code change; pass\n\t\t * your own when an app shows more than one navigation, since the point\n\t\t * of the name is to tell them apart.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Main navigation'),\n\t\t},\n\n\t\t/**\n\t\t * The brand block at the top of the navigation:\n\t\t * `{ logo?, name?, caption?, alt? }`. `logo` is an image URL, `name`\n\t\t * the app or organisation name, `caption` a line under it. Falls back\n\t\t * to the manifest's `nav.brand`. `null` (the default) with no manifest\n\t\t * brand renders nothing.\n\t\t *\n\t\t * @type {{logo?: string, name?: string, caption?: string, alt?: string}|null}\n\t\t */\n\t\tbrand: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['primary-action', 'primary-action-click'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/**\n\t\t\t * Per-item expand/collapse state for menu groups, keyed by\n\t\t\t * item id. Seeded lazily from the manifest's `item.open` by\n\t\t\t * `isItemOpen`; written by the collapse chevron (via\n\t\t\t * `@update:open`), by title clicks on route-less group items\n\t\t\t * (which TOGGLE open/closed), and by title clicks on a group\n\t\t\t * that ALSO carries a route or href (which force it OPEN,\n\t\t\t * alongside the navigation — see `onItemClick`).\n\t\t\t */\n\t\t\topenState: {},\n\n\t\t}\n\t},\n\n\tcomputed: {\n\t\teffectiveManifest() {\n\t\t\treturn this.manifest ?? this.cnManifest\n\t\t},\n\n\t\teffectiveTranslate() {\n\t\t\treturn this.translate ?? this.cnTranslate\n\t\t},\n\n\t\t/**\n\t\t * The brand to draw: the `brand` prop, else the manifest's\n\t\t * `nav.brand`. Name and caption go through the translate function,\n\t\t * like every other manifest label. Null when there is nothing to\n\t\t * show, which is what keeps the block out of a navigation that\n\t\t * declares no brand.\n\t\t *\n\t\t * @return {{logo: string, name: string, caption: string, alt: string}|null} The brand, or null.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-navigation-brand\n\t\t */\n\t\tresolvedBrand() {\n\t\t\tconst declared = this.brand ?? this.effectiveManifest?.nav?.brand\n\t\t\tif (!declared || typeof declared !== 'object') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst text = (value) => (typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : '')\n\t\t\tconst brand = {\n\t\t\t\tlogo: typeof declared.logo === 'string' ? declared.logo : '',\n\t\t\t\tname: text(declared.name),\n\t\t\t\tcaption: text(declared.caption),\n\t\t\t\talt: text(declared.alt),\n\t\t\t}\n\t\t\treturn (brand.logo || brand.name || brand.caption) ? brand : null\n\t\t},\n\n\t\t/**\n\t\t * Manifest-declared root-level primary action (`nav.primaryAction`)\n\t\t * — retained for backwards compatibility with code that reads this\n\t\t * computed. New code SHOULD read `activePrimaryAction` instead,\n\t\t * which also handles the page-scoped override.\n\t\t *\n\t\t * @return {object|null} `{ label, icon?, route?, href?, id?, payload? }` or null.\n\t\t */\n\t\tprimaryAction() {\n\t\t\treturn this.effectiveManifest?.nav?.primaryAction ?? null\n\t\t},\n\n\t\t/**\n\t\t * Resolved primary action for the current route. Resolution order:\n\t\t * 1. The current route's matching `pages[].primaryAction`\n\t\t * 2. `manifest.nav.primaryAction` as app-wide default\n\t\t * 3. `null` — no primary-action button renders\n\t\t *\n\t\t * Page-scoped declarations always win over the nav-root default.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tactivePrimaryAction() {\n\t\t\tconst pages = this.effectiveManifest?.pages ?? []\n\t\t\tconst routeName = this.$route?.name\n\t\t\tif (routeName) {\n\t\t\t\tconst page = pages.find((p) => p.id === routeName)\n\t\t\t\tif (page && page.primaryAction) {\n\t\t\t\t\treturn page.primaryAction\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn this.effectiveManifest?.nav?.primaryAction ?? null\n\t\t},\n\n\t\t/**\n\t\t * MDI icon component for the resolved primary action. Honors the\n\t\t * action's `icon` field when set; falls back to the canonical\n\t\t * `Plus` icon to match NC's NcAppNavigationNew default styling.\n\t\t *\n\t\t * @return {import('vue').Component}\n\t\t */\n\t\tprimaryActionIconComponent() {\n\t\t\treturn this.mdiIconComponent(this.activePrimaryAction) ?? Plus\n\t\t},\n\n\t\t/**\n\t\t * Link props for the primary action's button: an `href` opens in a\n\t\t * new tab, a `route` is a router link. Null when the action is\n\t\t * neither (or there is no router), so it stays a plain button.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tprimaryActionLink() {\n\t\t\tconst action = this.activePrimaryAction\n\t\t\tif (!action) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (action.href) {\n\t\t\t\treturn { href: action.href, target: '_blank' }\n\t\t\t}\n\t\t\tif (action.route && this.$router) {\n\t\t\t\treturn { to: { name: action.route } }\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/**\n\t\t * All visible items (filtered by permission and visibleIf conditions,\n\t\t * sorted by order). Retained for backwards-compat with the previous\n\t\t * public API and tests that read this computed; new code should use\n\t\t * `mainItems` / `settingsItems` instead.\n\t\t */\n\t\tvisibleItems() {\n\t\t\treturn (this.effectiveManifest?.menu ?? [])\n\t\t\t\t.filter((item) => this.passesPermission(item) && this.passesVisibleIf(item))\n\t\t\t\t.slice()\n\t\t\t\t.sort(byManifestOrder)\n\t\t},\n\n\t\t/**\n\t\t * Items that render in the top list (default placement).\n\t\t *\n\t\t * Note the `=== 'main'` rather than a negated list: an entry whose\n\t\t * section this component does not render must fall through to\n\t\t * NOTHING, not to the main list. `section: \"integrations\"` is\n\t\t * exactly that case — those entries render in CnAppRoot's\n\t\t * per-user settings modal, not in the navigation (ADR-110) — and a\n\t\t * `!== 'footer' && !== 'settings'` test would have quietly put\n\t\t * every one of them back in the nav this contract removes them\n\t\t * from.\n\t\t */\n\t\tmainItems() {\n\t\t\treturn this.visibleItems.filter((item) => (item.section ?? 'main') === 'main')\n\t\t},\n\n\t\t/**\n\t\t * Items pinned to the bottom of the navigation (section:\n\t\t * \"footer\") — rendered as flat NcAppNavigationItems above the\n\t\t * settings foldout. For always-visible, non-settings entries:\n\t\t * Documentation, Features & Roadmap, About.\n\t\t */\n\t\tfooterItems() {\n\t\t\treturn this.visibleItems.filter((item) => item.section === 'footer')\n\t\t},\n\n\t\t/**\n\t\t * Items that render INSIDE the NcAppNavigationSettings foldout\n\t\t * (section: \"settings\"). The foldout is the NC-native gear-icon\n\t\t * button that slides a panel open; these entries are app-level\n\t\t * configuration pages (Forms, Pipelines, Automations, …).\n\t\t */\n\t\tsettingsItems() {\n\t\t\treturn this.visibleItems.filter((item) => item.section === 'settings')\n\t\t},\n\n\t\t/**\n\t\t * Whether the settings foldout mounts. Mounts when there is at\n\t\t * least one `section: \"settings\"` item OR personal settings is\n\t\t * enabled — so every app shows a Settings gear with at least the\n\t\t * auto-prepended \"Personal settings\" entry. Only fully suppressed\n\t\t * when there are no settings items AND `nav.includePersonalSettings`\n\t\t * is explicitly `false`.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tshowSettingsFoldout() {\n\t\t\treturn this.settingsItems.length > 0 || this.includePersonalSettings\n\t\t\t\t|| this.roadmapEntry !== null || this.documentationEntry !== null\n\t\t},\n\n\t\t/**\n\t\t * Whether to auto-prepend the \"Personal settings\" entry at the top\n\t\t * of the foldout. On by default; opt out with\n\t\t * `manifest.nav.includePersonalSettings: false` (e.g. when the app\n\t\t * has no per-user NcAppSettingsDialog wired through\n\t\t * cnOpenUserSettings).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tincludePersonalSettings() {\n\t\t\treturn this.effectiveManifest?.nav?.includePersonalSettings !== false\n\t\t},\n\n\t\t/**\n\t\t * Label for the foldout's gear button. Manifest override:\n\t\t * `nav.settingsLabel`; defaults to \"Settings\".\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsettingsFoldoutLabel() {\n\t\t\tconst custom = this.effectiveManifest?.nav?.settingsLabel\n\t\t\tif (typeof custom === 'string' && custom.length > 0) {\n\t\t\t\treturn this.effectiveTranslate(custom)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Settings')\n\t\t},\n\n\t\t/**\n\t\t * Label for the auto-prepended Personal-settings entry.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tpersonalSettingsLabel() {\n\t\t\treturn t('nextcloud-vue', 'Personal settings')\n\t\t},\n\n\t\t/**\n\t\t * Optional \"Features & roadmap\" foldout entry. Enabled via\n\t\t * `nav.includeRoadmap`; `nav.roadmapUrl` is treated as an external link\n\t\t * when it looks like a URL, otherwise as an in-app router target.\n\t\t *\n\t\t * @return {{label: string, to: (string|null), href: (string|null)}|null}\n\t\t */\n\t\troadmapEntry() {\n\t\t\tconst nav = this.effectiveManifest?.nav\n\t\t\tif (!nav || nav.includeRoadmap !== true) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst label = (typeof nav.roadmapLabel === 'string' && nav.roadmapLabel)\n\t\t\t\t? this.effectiveTranslate(nav.roadmapLabel)\n\t\t\t\t: t('nextcloud-vue', 'Features & roadmap')\n\t\t\tconst target = typeof nav.roadmapUrl === 'string' ? nav.roadmapUrl.trim() : ''\n\t\t\tconst external = /^(https?:)?\\/\\//.test(target)\n\t\t\treturn { label, to: (target && !external) ? target : null, href: external ? target : null }\n\t\t},\n\n\t\t/**\n\t\t * Optional \"Documentation\" foldout entry. Enabled via\n\t\t * `nav.includeDocumentation`; always an external link (`nav.documentationUrl`).\n\t\t *\n\t\t * @return {{label: string, href: string}|null}\n\t\t */\n\t\tdocumentationEntry() {\n\t\t\tconst nav = this.effectiveManifest?.nav\n\t\t\tif (!nav || nav.includeDocumentation !== true) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst target = typeof nav.documentationUrl === 'string' ? nav.documentationUrl.trim() : ''\n\t\t\tif (!target) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst label = (typeof nav.documentationLabel === 'string' && nav.documentationLabel)\n\t\t\t\t? this.effectiveTranslate(nav.documentationLabel)\n\t\t\t\t: t('nextcloud-vue', 'Documentation')\n\t\t\treturn { label, href: target }\n\t\t},\n\n\t\t/**\n\t\t * Label for the auto-prepended Admin-settings entry.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tadminSettingsLabel() {\n\t\t\treturn t('nextcloud-vue', 'Admin settings')\n\t\t},\n\n\t\t/**\n\t\t * Accessible name (and hover tooltip) of the open-in-new marker on\n\t\t * the Admin-settings entry — the visual cue that the link leaves\n\t\t * the app for Nextcloud's own settings area.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\topensInNewTabHint() {\n\t\t\treturn t('nextcloud-vue', 'Opens in a new tab')\n\t\t},\n\n\t\t/**\n\t\t * Resolved app id for the Admin-settings link — the explicit\n\t\t * `appId` prop, else the `cnAppId` provided by CnAppRoot.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\teffectiveAppId() {\n\t\t\tconst id = this.appId || this.cnAppId\n\t\t\treturn (typeof id === 'string' && id.trim()) ? id.trim() : ''\n\t\t},\n\n\t\t/**\n\t\t * Target of the Admin-settings entry: the app's section in\n\t\t * Nextcloud's own admin settings (ADR-079 §1). This is a LINK, not\n\t\t * a modal — the app never re-implements the settings surface, and\n\t\t * the destination is authorized server-side by the settings\n\t\t * framework.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tadminSettingsHref() {\n\t\t\tif (!this.effectiveAppId) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// ABSOLUTE (same-origin) on purpose: NcAppNavigationItem renders\n\t\t\t// target=\"_blank\" only for hrefs its isExternal() deems external\n\t\t\t// (scheme-prefixed) and ignores a passed `target` — a relative\n\t\t\t// path therefore always opens in the SAME tab. The absolute form\n\t\t\t// gets the new tab while keeping native anchor semantics\n\t\t\t// (middle-click, copy link).\n\t\t\tconst path = generateUrl('/settings/admin/{appId}', { appId: this.effectiveAppId })\n\t\t\tconst origin = (typeof window !== 'undefined' && window.location && window.location.origin) || ''\n\t\t\treturn origin + path\n\t\t},\n\n\t\t/**\n\t\t * Whether to show the Admin-settings link: the user administers the\n\t\t * instance AND we can build a target. Visibility only — see the\n\t\t * `isAdmin` prop docs; the access check lives in Nextcloud.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tshowAdminSettingsLink() {\n\t\t\treturn this.isAdmin === true && this.adminSettingsHref !== ''\n\t\t},\n\n\t\t/**\n\t\t * Route name of the menu item that best matches the current route.\n\t\t * A direct match (current route name IS a menu target) wins;\n\t\t * otherwise the current path is matched against each item's page\n\t\t * path and the LONGEST prefix wins, so detail / nested routes (e.g.\n\t\t * `/expenses/:id`) light up — and auto-expand — their index entry\n\t\t * even though the detail route name is not in the menu. Longest\n\t\t * prefix disambiguates nested namespaces (e.g. `/pos` vs\n\t\t * `/pos/refunds`). Returns null when nothing matches.\n\t\t *\n\t\t * @return {string|null}\n\t\t */\n\t\tactiveRouteName() {\n\t\t\tconst routeName = this.$route?.name\n\t\t\tconst path = this.$route?.path\n\t\t\tconst flat = []\n\t\t\tfor (const item of this.visibleItems) {\n\t\t\t\tflat.push(item)\n\t\t\t\tfor (const child of this.visibleChildren(item)) {\n\t\t\t\t\tflat.push(child)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (routeName && flat.some((it) => it.route === routeName)) {\n\t\t\t\treturn routeName\n\t\t\t}\n\t\t\tif (!path) {\n\t\t\t\treturn routeName ?? null\n\t\t\t}\n\t\t\tlet best = null\n\t\t\tlet bestLen = -1\n\t\t\tfor (const it of flat) {\n\t\t\t\tif (!it.route) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst pagePath = this.pageForItem(it)?.route\n\t\t\t\tif (!pagePath || pagePath === '/' || pagePath.includes(':')) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (path === pagePath || path.startsWith(pagePath + '/')) {\n\t\t\t\t\tif (pagePath.length > bestLen) {\n\t\t\t\t\t\tbest = it.route\n\t\t\t\t\t\tbestLen = pagePath.length\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn best ?? routeName ?? null\n\t\t},\n\t},\n\n\twatch: {\n\t\tactiveRouteName: {\n\t\t\timmediate: true,\n\t\t\thandler(_current, previous) {\n\t\t\t\tthis.pinGroupsEntered(previous)\n\t\t\t},\n\t\t},\n\n\t\t// The manifest can arrive after the route did.\n\t\tvisibleItems() {\n\t\t\tthis.pinGroupsEntered(this.activeRouteName)\n\t\t},\n\t},\n\n\tcreated() {\n\t\t// Non-reactive one-shot latch for warnAutoCountMisconfigured(). It has\n\t\t// to be seeded here rather than lazily on first use: `resolveCount()`\n\t\t// runs DURING RENDER, and Vue 3's instance proxy emits\n\t\t// \"Property \"_autoCountWarned\" was accessed during render but is not\n\t\t// defined on instance\" for the first read of an unset instance field\n\t\t// (Vue 2 read it back as plain `undefined`, silently). It deliberately\n\t\t// stays out of `data()`: a reactive Set mutated inside render would\n\t\t// re-trigger the render effect.\n\t\tthis._autoCountWarned = new Set()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve a menu item's `icon` string to an MDI Vue component. MDI names\n\t\t * resolve via the per-app `registerIcons()` registry; legacy Nextcloud\n\t\t * `icon-*` class names resolve via the shared `cssIconBridge` so they\n\t\t * render monochrome (fill:currentColor) like every other glyph.\n\t\t * Returns `null` (→ the `:icon=\"cssIconClass(item)\"` CSS-class fallback)\n\t\t * for unbridged `icon-*` names and unknown MDI names.\n\t\t *\n\t\t * @param {{ icon?: string }} item Menu item descriptor.\n\t\t * @return {import('vue').Component|null}\n\t\t */\n\t\tmdiIconComponent(item) {\n\t\t\tconst icon = item?.icon\n\t\t\tif (typeof icon !== 'string' || icon.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (icon.startsWith('icon-')) {\n\t\t\t\treturn bridgedMdiForCssIcon(icon) || null\n\t\t\t}\n\t\t\t// ADR-077: the app's own registerIcons() entries win (so an app can\n\t\t\t// override), then the shared semantic vocabulary — which resolves\n\t\t\t// WITHOUT the app having registered anything. Before the vocabulary\n\t\t\t// existed, a name the app forgot to register resolved to null here\n\t\t\t// and the entry rendered with no icon at all.\n\t\t\treturn ICON_MAP[icon] || getSemanticIconComponent(icon) || null\n\t\t},\n\n\t\t/**\n\t\t * Whether the item declares an icon that NOTHING can resolve — not the\n\t\t * app registry, not the semantic vocabulary, not the widget registry, and\n\t\t * not a URL / SVG path.\n\t\t *\n\t\t * ADR-077 rule 4: such an icon MUST degrade to a visible fallback glyph.\n\t\t * Historically it rendered as empty space, which is indistinguishable\n\t\t * from a deliberate no-icon design choice — 29 of hrmq's 72 nav entries\n\t\t * were silently blank this way. A visible \"?\" is discoverable; blank is\n\t\t * not.\n\t\t *\n\t\t * `icon-*` values are excluded: those have their own CSS-class fallback\n\t\t * path via the `cssIconClass` method.\n\t\t *\n\t\t * @param {{ icon?: string }} item Menu item descriptor.\n\t\t * @return {boolean} true when the declared icon resolves to nothing.\n\t\t */\n\t\tisUnresolvedIcon(item) {\n\t\t\tconst icon = item?.icon\n\t\t\tif (typeof icon !== 'string' || icon.length === 0) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (icon.startsWith('icon-')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn !this.mdiIconComponent(item)\n\t\t\t\t&& !this.isRichIcon(item)\n\t\t\t\t&& !this.isRegistryIcon(item)\n\t\t},\n\n\t\t/**\n\t\t * Whether the item's icon is a raw SVG path or an image URL (incl. the\n\t\t * `data:` URIs the bundled NL-government sets emit) — neither of which is a\n\t\t * component, so `ICON_MAP` can't resolve it and the `#icon` slot must\n\t\t * render it through CnMenuItemIcon instead.\n\t\t *\n\t\t * Without this, an icon picked from CnIconBrowser's Gemeente / Den Haag /\n\t\t * RVO tabs would simply not appear in the navigation.\n\t\t *\n\t\t * @param {{ icon?: string }} item Menu item descriptor.\n\t\t * @return {boolean} true for path/URL icons.\n\t\t */\n\t\tisRichIcon(item) {\n\t\t\tconst icon = item?.icon\n\t\t\tif (typeof icon !== 'string' || icon.length === 0 || icon.startsWith('icon-')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn isCustomIconUrl(icon) || isSvgPath(icon)\n\t\t},\n\n\t\t/**\n\t\t * Whether the item's icon is a plain MDI *name* that the shared widget-icon\n\t\t * registry can render (e.g. \"Heart\", \"Home\" — what CnIconBrowser emits from\n\t\t * its component-name sources).\n\t\t *\n\t\t * `ICON_MAP` only holds what the consuming app passed to `registerIcons()`,\n\t\t * and an app rendering USER-AUTHORED manifests cannot pre-register whatever\n\t\t * icon a user might pick — Buildiq registers none at all. So a picked name\n\t\t * failed `mdiIconComponent` (not registered) AND `isRichIcon` (not a URL or\n\t\t * SVG path), the `#icon` slot was skipped entirely, and the menu item rendered\n\t\t * with NO icon — even though CnMenuItemIcon → CnWidgetIcon could resolve it.\n\t\t *\n\t\t * Gate on the registry actually HAVING the name: `getIconComponent` answers\n\t\t * the default icon for anything unknown, so gating on it would render a wrong\n\t\t * (but plausible) icon for a typo instead of none.\n\t\t *\n\t\t * @param {{ icon?: string }} item Menu item descriptor.\n\t\t * @return {boolean} true when the widget-icon registry can render this name.\n\t\t */\n\t\tisRegistryIcon(item) {\n\t\t\treturn hasRegistryIcon(item?.icon)\n\t\t},\n\n\t\t/**\n\t\t * Pass-through for the `:icon` prop on NcAppNavigationItem when\n\t\t * the manifest declares a Nextcloud CSS-class icon (`icon-*`).\n\t\t * Returns an empty string when the icon is an MDI name OR a bridged\n\t\t * `icon-*` (rendered via the `#icon` slot instead), so NcAppNavigationItem\n\t\t * doesn't also paint a CSS-class background-image.\n\t\t *\n\t\t * @param {{ icon?: string }} item Menu item descriptor.\n\t\t * @return {string}\n\t\t */\n\t\tcssIconClass(item) {\n\t\t\tconst icon = item?.icon\n\t\t\tif (typeof icon !== 'string' || icon.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (!icon.startsWith('icon-')) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn bridgedMdiForCssIcon(icon) ? '' : icon\n\t\t},\n\n\t\tpassesPermission(item) {\n\t\t\tif (!item.permission) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (!this.permissions || this.permissions.length === 0) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.permissions.includes(item.permission)\n\t\t},\n\n\t\t/**\n\t\t * Evaluate a menu item's `visibleIf` condition block.\n\t\t *\n\t\t * Returns `true` (visible) when:\n\t\t * - No `visibleIf` is declared (backwards-compatible default).\n\t\t * - `visibleIf.appInstalled` is set AND the named app is\n\t\t * installed / enabled (checked via `OC.appswebroots` then the\n\t\t * capabilities fallback, cached per page load by `isAppInstalled`).\n\t\t * - Context-path predicates (any key that is a dot-separated path\n\t\t * into `manifest.runtime`) all pass against the current runtime\n\t\t * data. Predicates are evaluated by `passesContextPredicates`.\n\t\t * Example: `{ \"user.primaryRole\": { \"in\": [\"hr\", \"compliance\"] } }`\n\t\t * hides the entry unless `manifest.runtime.user.primaryRole` is\n\t\t * `\"hr\"` or `\"compliance\"`. When the runtime block is absent the\n\t\t * entry is hidden (fail-safe: never show role-gated items to\n\t\t * unidentified users).\n\t\t *\n\t\t * All conditions are combined with implicit AND — every condition\n\t\t * must pass for the item to render. Returns `false` when any fails.\n\t\t *\n\t\t * @param {object} item Menu item (or child) to evaluate.\n\t\t * @return {boolean} Whether the item should render.\n\t\t */\n\t\tpassesVisibleIf(item) {\n\t\t\tconst condition = item.visibleIf\n\t\t\tif (!condition || typeof condition !== 'object') {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\t// Specialised condition: appInstalled.\n\t\t\tif (condition.appInstalled) {\n\t\t\t\tif (!isAppInstalled(condition.appInstalled)) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Context-path predicates: any non-reserved key is a dot-path\n\t\t\t// into manifest.runtime evaluated by passesContextPredicates.\n\t\t\tconst runtime = this.effectiveManifest?.runtime ?? null\n\t\t\tif (!passesContextPredicates(condition, runtime)) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\treturn true\n\t\t},\n\n\t\t/**\n\t\t * A group's visible children, in `order`.\n\t\t *\n\t\t * The SORT is the fix for a silent no-op: this used to filter only, so\n\t\t * `order` on a child was accepted, documented and ignored. It bites\n\t\t * hardest on relocated leaves, because `applyMenuRelocations()` walks\n\t\t * the menu backwards and appends each move, which lands a group's\n\t\t * children in REVERSE manifest order — an order nobody wrote down and\n\t\t * no app can influence except by reordering `manifest.json` itself.\n\t\t * Pipelinq's Customer Support group read \"Projecten, My Work, Queue,\n\t\t * Tasks, All tickets\" against declared orders of 55, 20, 10, 60, 50.\n\t\t *\n\t\t * Same comparator as the top level, so one rule governs both: items\n\t\t * without an `order` render last, in their existing relative order\n\t\t * (Array.prototype.sort is stable).\n\t\t *\n\t\t * @param {object} item The group whose children to render.\n\t\t * @return {Array<object>} Visible children, ordered.\n\t\t */\n\t\tvisibleChildren(item) {\n\t\t\tif (!Array.isArray(item.children)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn item.children\n\t\t\t\t.filter((c) => this.passesPermission(c) && this.passesVisibleIf(c))\n\t\t\t\t.sort(byManifestOrder)\n\t\t},\n\n\t\tresolveLabel(item) {\n\t\t\treturn this.effectiveTranslate(item.label)\n\t\t},\n\n\t\tisActive(item) {\n\t\t\tif (item.href || !item.route) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn item.route === this.activeRouteName\n\t\t},\n\n\t\t/**\n\t\t * Whether a menu entry renders as a `NcAppNavigationCaption`\n\t\t * (`type: \"caption\"`) rather than a clickable\n\t\t * `NcAppNavigationItem`. Caption entries ignore `route`, `href`,\n\t\t * `action`, `icon`, `count`, `children`, and `pinned`.\n\t\t *\n\t\t * @param {{ type?: string }} item Menu entry descriptor.\n\t\t * @return {boolean}\n\t\t */\n\t\tisCaption(item) {\n\t\t\treturn item?.type === 'caption'\n\t\t},\n\n\t\t/**\n\t\t * Whether the host has registered a scoped slot named\n\t\t * `item-${id}-actions` for this menu item. Used to gate template\n\t\t * rendering of the per-item `#actions` slot pass-through so items\n\t\t * without a matching slot don't render an empty `<template #actions>`.\n\t\t *\n\t\t * @param {{ id: string }} item Menu entry descriptor.\n\t\t * @return {boolean}\n\t\t */\n\t\thasItemActionsSlot(item) {\n\t\t\tif (!item?.id) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst name = `item-${item.id}-actions`\n\t\t\treturn Boolean((this.$slots && this.$slots[name])\n\t\t\t\t|| (this.$slots && this.$slots[name]))\n\t\t},\n\n\t\t/**\n\t\t * Resolve the count value to render in this entry's\n\t\t * `NcCounterBubble` (in the `#counter` slot of\n\t\t * `NcAppNavigationItem`).\n\t\t *\n\t\t * - Literal positive integer in `item.count` → return as-is.\n\t\t * - `item.count === \"auto\"` → look up the entry's resolved page;\n\t\t * when that page is `type: \"index\"` with a `register`/`schema`\n\t\t * in its `config`, return\n\t\t * `cnMenuCounts[register][schema] ?? null`.\n\t\t *\n\t\t * Returns `null` (no badge) when:\n\t\t * - `item.count` is unset or falsy\n\t\t * - The literal value is `0`\n\t\t * - `count === \"auto\"` but no matching index page resolves\n\t\t * (also emits a one-shot `console.warn`)\n\t\t * - The store has no entry for the resolved `(register, schema)`\n\t\t *\n\t\t * @param {object} item Menu entry descriptor.\n\t\t * @return {number|null} Count to render, or null for no badge.\n\t\t */\n\t\tresolveCount(item) {\n\t\t\tconst raw = item?.count\n\t\t\tif (raw === undefined || raw === null) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\treturn raw > 0 ? raw : null\n\t\t\t}\n\t\t\tif (raw !== 'auto') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst page = this.pageForItem(item)\n\t\t\tconst register = page?.config?.register\n\t\t\tconst schema = page?.config?.schema\n\t\t\tif (page?.type !== 'index' || !register || !schema) {\n\t\t\t\tthis.warnAutoCountMisconfigured(item)\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst value = this.cnMenuCounts?.[register]?.[schema]\n\t\t\tif (typeof value !== 'number' || value <= 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/**\n\t\t * One-shot `console.warn` per menu-item id for misconfigured\n\t\t * `count: \"auto\"` entries (no resolvable index page). Keeps the\n\t\t * console quiet on re-renders.\n\t\t *\n\t\t * @param {{ id: string }} item Menu entry descriptor.\n\t\t * @return {void}\n\t\t * @private\n\t\t */\n\t\twarnAutoCountMisconfigured(item) {\n\t\t\tif (this._autoCountWarned.has(item.id)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis._autoCountWarned.add(item.id)\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.warn(`[CnAppNav] Menu entry \"${item.id}\" declares count: \"auto\" but has no resolvable index-type page with register + schema config — no badge will render.`)\n\t\t},\n\n\t\t/**\n\t\t * Look up an item's resolved page (`pages[]` entry whose `id`\n\t\t * matches the menu item's `route`) — used to decide whether the\n\t\t * NcAppNavigationItem should match its router-link `exact`.\n\t\t *\n\t\t * @param {object} item Menu item to resolve.\n\t\t * @return {object|null} Matching page entry, or null when the item\n\t\t * has no `route` or no page matches.\n\t\t */\n\t\tpageForItem(item) {\n\t\t\tif (!item.route) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst pages = this.effectiveManifest?.pages ?? []\n\t\t\treturn pages.find((p) => p.id === item.route) ?? null\n\t\t},\n\n\t\t/**\n\t\t * Build the `:to` value for an `NcAppNavigationItem`. Action\n\t\t * items (`action: \"user-settings\"`) and `href` items return\n\t\t * `null` so the entry is NOT a vue-router link: action items\n\t\t * fall through to the click handler, `href` items render a real\n\t\t * anchor via `itemHref`. Route items return a named route.\n\t\t *\n\t\t * @param {object} item Menu item being rendered.\n\t\t * @return {object|null} A `{ name }` route object, or null for\n\t\t * action / href / route-less items.\n\t\t */\n\t\titemTo(item) {\n\t\t\tif (item.action) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (item.href) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (!item.route) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Carry optional query params so a nav entry can deep-link to a\n\t\t\t// pre-filtered index page (e.g. one entry per case type → Cases?caseType=…).\n\t\t\treturn item.query ? { name: item.route, query: item.query } : { name: item.route }\n\t\t},\n\n\t\t/**\n\t\t * Build the `:href` value for an `NcAppNavigationItem`. Returns\n\t\t * the item's `href` so the entry renders as a real anchor whose\n\t\t * destination is visible on hover and which gets the native link\n\t\t * cursor. `NcAppNavigationItem` adds `target=\"_blank\"` itself for\n\t\t * external (`scheme://`) URLs, so those open in a new tab while\n\t\t * internal app paths (e.g. `/index.php/apps/foo/`) navigate in the\n\t\t * same tab — no `window.open` interception. The `admin-settings`\n\t\t * action links to `adminSettingsHref` (absolute, so it opens in a\n\t\t * new tab); other action items carry no href. Returns `null` for\n\t\t * non-href items so the entry stays a router-link / button.\n\t\t *\n\t\t * @param {object} item Menu item being rendered.\n\t\t * @return {string|null} The destination URL, or null.\n\t\t */\n\t\titemHref(item) {\n\t\t\tif (item.action === 'admin-settings') {\n\t\t\t\treturn this.adminSettingsHref || null\n\t\t\t}\n\t\t\tif (item.action) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn item.href || null\n\t\t},\n\n\t\t/**\n\t\t * Whether a menu group renders expanded. Local `openState` (set\n\t\t * by the chevron, a title click, or `pinGroupsEntered`) wins;\n\t\t * otherwise the group auto-expands when it contains the active\n\t\t * route — so deep-linking to a child page reveals which group it\n\t\t * lives in — and finally falls back to the manifest's `item.open`\n\t\t * for the initial render.\n\t\t *\n\t\t * @param {{ id: string, open?: boolean }} item Menu entry descriptor.\n\t\t * @return {boolean}\n\t\t */\n\t\tisItemOpen(item) {\n\t\t\tconst local = this.openState[item.id]\n\t\t\tif (local !== undefined) {\n\t\t\t\treturn local\n\t\t\t}\n\t\t\tif (this.hasActiveChild(item)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn Boolean(item.open)\n\t\t},\n\n\t\t/**\n\t\t * Whether any of a group's visible children is the active route.\n\t\t * Drives auto-expansion of the parent group on page load.\n\t\t *\n\t\t * @param {object} item Menu entry descriptor.\n\t\t * @return {boolean}\n\t\t */\n\t\thasActiveChild(item) {\n\t\t\treturn this.visibleChildren(item).some((child) => this.isActive(child))\n\t\t},\n\n\t\t/**\n\t\t * Record a group's expand/collapse state. Bound to\n\t\t * NcAppNavigationItem's `@update:open` (chevron clicks) and\n\t\t * called from `onItemClick` for title clicks on route-less\n\t\t * group items.\n\t\t *\n\t\t * @param {{ id: string }} item Menu entry descriptor.\n\t\t * @param {boolean} value New open state.\n\t\t */\n\t\tsetItemOpen(item, value) {\n\t\t\tthis.openState[item.id] = value\n\t\t},\n\n\t\t/**\n\t\t * Pin open every group the active route just entered.\n\t\t *\n\t\t * Auto-expansion used to be read off the route alone, so a group\n\t\t * opened for a deep link collapsed the moment the route left it. A\n\t\t * group opened for you stays open until you close it. Only a group\n\t\t * the route ENTERS is pinned: one you closed while on one of its\n\t\t * children stays closed while you move between them.\n\t\t *\n\t\t * @param {string|null} previousRoute The active route before this change, or undefined at first render.\n\t\t */\n\t\tpinGroupsEntered(previousRoute) {\n\t\t\tfor (const item of this.visibleItems) {\n\t\t\t\tconst children = this.visibleChildren(item)\n\t\t\t\tif (children.length === 0 || !this.hasActiveChild(item)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst wasInside = children.some((child) => child.route === previousRoute)\n\t\t\t\tif (!wasInside || this.openState[item.id] === undefined) {\n\t\t\t\t\tthis.openState[item.id] = true\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Click handler. Dispatch order: action keyword → group\n\t\t * open/toggle. For `action: \"user-settings\"` invokes the injected\n\t\t * `cnOpenUserSettings` (provided by CnAppRoot) and prevents\n\t\t * default; `action: \"admin-settings\"` is a real link to\n\t\t * `/settings/admin/<appId>` (see `itemHref`) and is left to the\n\t\t * browser; for `action:\n\t\t * \"replay-walkthrough\"` invokes the injected\n\t\t * `cnReplayWalkthrough(item.tourId)` and prevents default. `href`\n\t\t * items with no children are NOT handled here — they render a real\n\t\t * anchor via `itemHref`, so the browser navigates natively\n\t\t * (external URLs open in a new tab, internal app paths in the same\n\t\t * tab).\n\t\t *\n\t\t * A group's children visibility on click depends on whether the\n\t\t * group ITSELF has a destination:\n\t\t * - Route-less, href-less: a pure group header, so its anchor is\n\t\t * a dead `#` link and the click's only job is to TOGGLE the\n\t\t * children open/closed (same effect as the collapse chevron).\n\t\t * - Carries a `route` or `href`: the click is a real navigation\n\t\t * (handled natively by `:to` / the anchor, not prevented here),\n\t\t * and this additionally forces the group OPEN — never closed —\n\t\t * so the reader lands on the group's own page without a second\n\t\t * click to see what else it holds. The collapse chevron is the\n\t\t * only control that can close such a group again.\n\t\t *\n\t\t * @param {object} item Menu item being clicked.\n\t\t * @param {Event} [event] Native click event (used to call\n\t\t * preventDefault for action / group links).\n\t\t */\n\t\tonItemClick(item, event) {\n\t\t\tif (item.action === 'user-settings') {\n\t\t\t\tif (event && typeof event.preventDefault === 'function') {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t}\n\t\t\t\tthis.cnOpenUserSettings()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (item.action === 'admin-settings') {\n\t\t\t\t// ADR-079: a real link to Nextcloud's own settings (see\n\t\t\t\t// itemHref), followed natively in a new tab. Without an app\n\t\t\t\t// id there is no href, and the click does nothing.\n\t\t\t\tif (!this.adminSettingsHref && event && typeof event.preventDefault === 'function') {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (item.action === 'replay-walkthrough') {\n\t\t\t\tif (event && typeof event.preventDefault === 'function') {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t}\n\t\t\t\tthis.cnReplayWalkthrough(item.tourId)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.visibleChildren(item).length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!item.route && !item.href) {\n\t\t\t\tif (event && typeof event.preventDefault === 'function') {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t}\n\t\t\t\tthis.setItemOpen(item, !this.isItemOpen(item))\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// The item is ALSO a real destination: let the click navigate\n\t\t\t// natively and reveal the children alongside it.\n\t\t\tthis.setItemOpen(item, true)\n\t\t},\n\n\t\t/**\n\t\t * Click handler for the auto-prepended Personal-settings entry in\n\t\t * the settings foldout. Invokes the injected `cnOpenUserSettings`\n\t\t * (provided by CnAppRoot → opens the host's NcAppSettingsDialog).\n\t\t * No-op inject when mounted standalone.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonPersonalSettingsClick() {\n\t\t\tthis.cnOpenUserSettings()\n\t\t},\n\n\t\t/**\n\t\t * Click handler for the manifest-declared primary action. Emits\n\t\t * `@primary-action` AND the back-compat `@primary-action-click`\n\t\t * event for host-side handling. Navigation is the link's own: an\n\t\t * `href` or `route` action renders as a real link (see\n\t\t * `primaryActionLink`), so the browser or router follows it after\n\t\t * these events. Not invoked when the host overrides the\n\t\t * `#primary-action` slot (it provides its own handler).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonPrimaryActionClick() {\n\t\t\tconst action = this.activePrimaryAction\n\t\t\tif (!action) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst payload = {\n\t\t\t\tid: action.id,\n\t\t\t\tlabel: action.label,\n\t\t\t\ticon: action.icon,\n\t\t\t\troute: action.route,\n\t\t\t\thref: action.href,\n\t\t\t\tpayload: action.payload,\n\t\t\t\tpage: this.$route?.name,\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event primary-action Emitted when the resolved primary-action button is clicked. Payload includes the action descriptor (`{ id, label, icon, route, href, payload }`) plus the current `page` (route name) for host dispatchers.\n\t\t\t * @type {{ id?: string, label: string, icon?: string, route?: string, href?: string, payload?: unknown, page?: string }}\n\t\t\t */\n\t\t\tthis.$emit('primary-action', payload)\n\t\t\t/**\n\t\t\t * @event primary-action-click Back-compat alias for `@primary-action`. Payload is the resolved primary action object as declared in the manifest.\n\t\t\t */\n\t\t\tthis.$emit('primary-action-click', action)\n\t\t},\n\t},\n}\n</script>\n\n<style>\n/* The brand block at the top of the navigation. */\n.cn-app-nav__brand {\n\talign-items: center;\n\tdisplay: flex;\n\tgap: calc(2.5 * var(--default-grid-baseline));\n\tmin-width: 0;\n\tpadding: calc(2 * var(--default-grid-baseline)) calc(2 * var(--default-grid-baseline)) calc(3 * var(--default-grid-baseline));\n}\n\n.cn-app-nav__brand-logo {\n\tflex: none;\n\theight: 34px;\n\tmax-width: 50%;\n\tobject-fit: contain;\n\twidth: auto;\n}\n\n.cn-app-nav__brand-text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tline-height: 1.15;\n\tmin-width: 0;\n}\n\n.cn-app-nav__brand-name {\n\tcolor: var(--color-main-text);\n\tfont-size: 1.2em;\n\tfont-weight: 700;\n\toverflow-wrap: anywhere;\n}\n\n.cn-app-nav__brand-caption {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\toverflow-wrap: anywhere;\n}\n\n/*\n * The legacy `icon-*` classes in Nextcloud render a background-image\n * with a hardcoded dark fill, so they stay grey when an entry becomes\n * active (text turns white against the primary-element background).\n * Force the icon to white in the active state to match the label.\n * Only applies when the icon is provided via the `icon` prop (CSS\n * class) — items using a `<template #icon>` MDI component already\n * inherit `currentColor`.\n */\n.app-navigation-entry.active .app-navigation-entry-icon[class*=\"icon-\"] {\n\tfilter: brightness(0) invert(1);\n}\n\n/* The link form of the primary action, matching NcAppNavigationNew's scoped layout. */\n.cn-app-nav__primary-action {\n\tdisplay: block;\n\tpadding: calc(var(--default-grid-baseline, 4px) * 2);\n}\n\n/*\n * Footer-section items (section: \"footer\") render in NcAppNavigation's\n * #footer slot, outside the scroll container, so they stay visible above\n * the settings foldout regardless of menu length. (The interim\n * `pinned`-prop approach kept them inside the scrollable list, where\n * `margin-top: auto` only bottom-pins while the list does not overflow —\n * long menus showed Documentation / Features & roadmap mid-scroll.)\n *\n * No `padding` here on purpose: it comes from NcAppNavigationList's own\n * `var(--app-navigation-padding)`, so it stays in step with the main list.\n *\n * As a direct `> ul` child of `.app-navigation__content`, NC makes it a\n * shrinkable, scrollable flex item, and NcAppNavigationList adds\n * `overflow-y: auto` besides. That let a two-entry list be squeezed below its\n * content and grow a scrollbar on a short menu, so opt out of both.\n */\n.cn-app-nav__footer-list {\n\tlist-style: none;\n\tmargin: 0;\n\tflex-shrink: 0 !important;\n\toverflow: visible !important;\n}\n\n/*\n * Align the settings foldout's items with the main/footer list items,\n * whose icons sit at a 16px inset. The foldout's items live in a bare\n * <ul> inside NcAppNavigationSettings' panel (`#app-settings`, padding\n * 3px), so without this they start ~5px further left and the icon\n * column looks ragged. (The native settings-toggle button sits ~1px\n * left of the 16px baseline, but NC owns that with an !important\n * shorthand — a sub-pixel difference we leave to the native component.)\n */\n.cn-app-nav__settings-list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding-inline-start: 5px;\n}\n</style>\n"],"names":["_createBlock","_Fragment","_renderList","_openBlock","_createElementBlock","_resolveDynamicComponent","_createVNode","_renderSlot","_mergeProps","_toDisplayString"],"mappings":"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"}
|