@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
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
- Content sections via slots
|
|
22
22
|
-->
|
|
23
23
|
<template>
|
|
24
|
-
<div
|
|
24
|
+
<div
|
|
25
|
+
class="cn-detail-page"
|
|
26
|
+
:class="{ 'cn-detail-page--with-side': showsSideColumn }"
|
|
27
|
+
data-testid="cn-detail-page"
|
|
28
|
+
:style="{ maxWidth: maxWidth }">
|
|
25
29
|
<!-- Skip link to the page's primary action. Off screen until it takes
|
|
26
30
|
focus, so it costs a sighted mouse user nothing and saves a
|
|
27
31
|
keyboard user every widget on the page. Targets the action the
|
|
@@ -29,7 +33,7 @@
|
|
|
29
33
|
that lands somewhere arbitrary is worse than none, because it is
|
|
30
34
|
the one link a keyboard user is told to trust. -->
|
|
31
35
|
<a
|
|
32
|
-
v-if="
|
|
36
|
+
v-if="effectivePrimaryLabel && !objectNotFound"
|
|
33
37
|
:href="`#${primaryActionAnchorId}`"
|
|
34
38
|
class="cn-detail-page__skip-link"
|
|
35
39
|
data-testid="cn-detail-page-skip-link"
|
|
@@ -72,6 +76,23 @@
|
|
|
72
76
|
class="cn-detail-page__icon" />
|
|
73
77
|
</slot>
|
|
74
78
|
<div class="cn-detail-page__header-text">
|
|
79
|
+
<!-- Type and status pills (manifest `config.typePill` /
|
|
80
|
+
`config.statusPill`). What kind of record this is and
|
|
81
|
+
where it stands, readable before the title. Nothing
|
|
82
|
+
renders for a page that declares neither. -->
|
|
83
|
+
<div
|
|
84
|
+
v-if="headerPills.length > 0"
|
|
85
|
+
class="cn-detail-page__pills"
|
|
86
|
+
data-testid="cn-detail-page-pills">
|
|
87
|
+
<CnStatusBadge
|
|
88
|
+
v-for="pill in headerPills"
|
|
89
|
+
:key="pill.key"
|
|
90
|
+
:label="pill.label"
|
|
91
|
+
:colorKey="pill.colorKey"
|
|
92
|
+
:colorMap="pill.colorMap"
|
|
93
|
+
:variant="pill.variant"
|
|
94
|
+
:data-testid="`cn-detail-page-pill-${pill.key}`" />
|
|
95
|
+
</div>
|
|
75
96
|
<!-- Type name as a small eyebrow above the record name, so
|
|
76
97
|
the header reads "the record" not "the kind of record"
|
|
77
98
|
(ADR-062). Only shown once the object resolves to a
|
|
@@ -150,14 +171,38 @@
|
|
|
150
171
|
</div>
|
|
151
172
|
<!-- The action the page declares as its primary one, and the
|
|
152
173
|
target the skip link above lands on. -->
|
|
174
|
+
<!-- With a "what now" card on the page the button lives in that
|
|
175
|
+
card, next to the checklist that explains it, and not here
|
|
176
|
+
as well: one primary button per page. -->
|
|
153
177
|
<NcButton
|
|
154
|
-
v-if="
|
|
178
|
+
v-if="effectivePrimaryLabel && !showsNextStepCard"
|
|
155
179
|
:id="primaryActionAnchorId"
|
|
156
180
|
ref="primaryActionButton"
|
|
157
181
|
variant="primary"
|
|
182
|
+
:href="primaryActionHref || undefined"
|
|
183
|
+
:target="primaryActionHref ? primaryActionTarget : undefined"
|
|
184
|
+
:disabled="primaryActionDisabled"
|
|
158
185
|
data-testid="cn-detail-page-primary-action"
|
|
159
|
-
@click="
|
|
160
|
-
{{
|
|
186
|
+
@click="onPrimaryActionClick">
|
|
187
|
+
{{ effectivePrimaryLabel }}
|
|
188
|
+
</NcButton>
|
|
189
|
+
<!-- Quick actions (manifest `config.quickActions`): the few
|
|
190
|
+
actions a handler reaches for on every record, always
|
|
191
|
+
visible. At most three, so the row cannot grow back into
|
|
192
|
+
the button wall the Actions menu replaced. -->
|
|
193
|
+
<NcButton
|
|
194
|
+
v-for="entry in quickActionEntries"
|
|
195
|
+
:key="`quick-${entry.id}`"
|
|
196
|
+
variant="secondary"
|
|
197
|
+
:href="entry.href || undefined"
|
|
198
|
+
:target="entry.href ? (entry.linkTarget || undefined) : undefined"
|
|
199
|
+
:disabled="entry.disabled"
|
|
200
|
+
:data-testid="`cn-detail-page-quick-${entry.id}`"
|
|
201
|
+
@click="onPinnedActionClick(entry)">
|
|
202
|
+
<template v-if="entry.iconName" #icon>
|
|
203
|
+
<CnIcon :name="entry.iconName" :size="20" />
|
|
204
|
+
</template>
|
|
205
|
+
{{ entry.label }}
|
|
161
206
|
</NcButton>
|
|
162
207
|
<!-- Declarative lifecycle transitions (manifest
|
|
163
208
|
`config.lifecycleActions`). Status-gated; driven by the
|
|
@@ -245,11 +290,13 @@
|
|
|
245
290
|
<!-- In-app edit button (ADR-041): icon-only, self-wires from CnAppRoot. -->
|
|
246
291
|
<CnBuildiqEditButton />
|
|
247
292
|
<CnActionsMenu
|
|
248
|
-
:showRefresh="
|
|
293
|
+
:showRefresh="menuShowsRefresh"
|
|
249
294
|
:refreshing="effectiveRefreshing"
|
|
250
|
-
:showRequestFeature="showRequestFeature"
|
|
251
|
-
:showReportBug="showReportBug"
|
|
252
|
-
:showDocumentation="showDocumentation"
|
|
295
|
+
:showRequestFeature="showRequestFeature && menuShowsHelpLinks"
|
|
296
|
+
:showReportBug="showReportBug && menuShowsHelpLinks"
|
|
297
|
+
:showDocumentation="showDocumentation && menuShowsHelpLinks"
|
|
298
|
+
:actionsMenuLabel="actionsMenuName"
|
|
299
|
+
:hasPrimaryItems="headerMenuGroups.length > 0"
|
|
253
300
|
:documentationUrl="documentationUrl"
|
|
254
301
|
:docsAnchor="resolvedPageId"
|
|
255
302
|
:documentationLabel="documentationLabel || undefined"
|
|
@@ -260,40 +307,56 @@
|
|
|
260
307
|
testidBase="cn-detail-page"
|
|
261
308
|
@refresh="onHeaderRefresh"
|
|
262
309
|
@requestFeature="onHeaderRequestFeature">
|
|
263
|
-
<template v-if="
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
:
|
|
274
|
-
:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
</
|
|
293
|
-
|
|
294
|
-
|
|
310
|
+
<template v-if="headerMenuGroups.length" #primary-items>
|
|
311
|
+
<!-- One block per group. A page that groups nothing has
|
|
312
|
+
a single captionless group, and renders exactly the
|
|
313
|
+
flat list it always did. -->
|
|
314
|
+
<template v-for="(group, groupIndex) in headerMenuGroups">
|
|
315
|
+
<NcActionSeparator
|
|
316
|
+
v-if="groupIndex > 0"
|
|
317
|
+
:key="`${group.key}-separator`" />
|
|
318
|
+
<NcActionCaption
|
|
319
|
+
v-if="group.label"
|
|
320
|
+
:key="`${group.key}-caption`"
|
|
321
|
+
:name="group.label"
|
|
322
|
+
:data-testid="`cn-detail-page-action-group-${group.key}`" />
|
|
323
|
+
<template v-for="entry in group.entries">
|
|
324
|
+
<!-- An action that goes to a URL is a LINK. The browser
|
|
325
|
+
then supplies middle-click, "open in new tab" and the
|
|
326
|
+
semantics assistive tech announces, none of which a
|
|
327
|
+
click handler can. -->
|
|
328
|
+
<NcActionLink
|
|
329
|
+
v-if="entry.href"
|
|
330
|
+
:key="`${entry.id}-link`"
|
|
331
|
+
:href="entry.href"
|
|
332
|
+
:target="entry.linkTarget || undefined"
|
|
333
|
+
:data-testid="entry.testid">
|
|
334
|
+
<template v-if="entry.iconName || entry.iconClass" #icon>
|
|
335
|
+
<CnIcon v-if="entry.iconName" :name="entry.iconName" :size="20" />
|
|
336
|
+
<span v-else :class="entry.iconClass" />
|
|
337
|
+
</template>
|
|
338
|
+
{{ entry.label }}
|
|
339
|
+
</NcActionLink>
|
|
340
|
+
<NcActionButton
|
|
341
|
+
v-else
|
|
342
|
+
:key="entry.id"
|
|
343
|
+
:data-testid="entry.testid"
|
|
344
|
+
:disabled="entry.disabled"
|
|
345
|
+
:aria-pressed="entry.pressed === null ? null : String(entry.pressed)"
|
|
346
|
+
:closeAfterClick="true"
|
|
347
|
+
@click="entry.run()">
|
|
348
|
+
<template v-if="entry.iconName || entry.iconClass" #icon>
|
|
349
|
+
<CnIcon v-if="entry.iconName" :name="entry.iconName" :size="20" />
|
|
350
|
+
<span v-else :class="entry.iconClass" />
|
|
351
|
+
</template>
|
|
352
|
+
{{ entry.label }}
|
|
353
|
+
</NcActionButton>
|
|
354
|
+
</template>
|
|
295
355
|
</template>
|
|
296
|
-
|
|
356
|
+
<!-- Divides the page's own actions from the help links
|
|
357
|
+
below. With the help links switched off there is
|
|
358
|
+
nothing below to divide from. -->
|
|
359
|
+
<NcActionSeparator v-if="menuShowsHelpLinks" />
|
|
297
360
|
</template>
|
|
298
361
|
</CnActionsMenu>
|
|
299
362
|
</div>
|
|
@@ -326,6 +389,33 @@
|
|
|
326
389
|
{{ editLockRefusal }}
|
|
327
390
|
</p>
|
|
328
391
|
|
|
392
|
+
<!-- The "what now" card (manifest `config.nextStep`): what this stage
|
|
393
|
+
still needs, next to the button that takes the step. Above the
|
|
394
|
+
body and the side column, at the full width of both. Hidden for a
|
|
395
|
+
stage that declares no checklist. -->
|
|
396
|
+
<CnNextStepCard
|
|
397
|
+
v-if="showsNextStepCard"
|
|
398
|
+
class="cn-detail-page__next-step"
|
|
399
|
+
:title="nextStepCard.title"
|
|
400
|
+
titleTag="h3"
|
|
401
|
+
:items="nextStepCard.items"
|
|
402
|
+
:after="nextStepCard.after"
|
|
403
|
+
data-testid="cn-detail-page-next-step">
|
|
404
|
+
<template v-if="effectivePrimaryLabel" #action>
|
|
405
|
+
<NcButton
|
|
406
|
+
:id="primaryActionAnchorId"
|
|
407
|
+
ref="primaryActionButton"
|
|
408
|
+
variant="primary"
|
|
409
|
+
:href="primaryActionHref || undefined"
|
|
410
|
+
:target="primaryActionHref ? primaryActionTarget : undefined"
|
|
411
|
+
:disabled="primaryActionDisabled"
|
|
412
|
+
data-testid="cn-detail-page-primary-action"
|
|
413
|
+
@click="onPrimaryActionClick">
|
|
414
|
+
{{ effectivePrimaryLabel }}
|
|
415
|
+
</NcButton>
|
|
416
|
+
</template>
|
|
417
|
+
</CnNextStepCard>
|
|
418
|
+
|
|
329
419
|
<!-- Not-found state: the record behind the address does not exist. -->
|
|
330
420
|
<div v-if="objectNotFound" class="cn-detail-page__not-found" data-testid="cn-detail-page-not-found">
|
|
331
421
|
<!--
|
|
@@ -709,6 +799,68 @@
|
|
|
709
799
|
</div>
|
|
710
800
|
</div>
|
|
711
801
|
|
|
802
|
+
<!-- Side column (manifest `config.sideColumn`): the record's facts as
|
|
803
|
+
a column of cards beside the body. A sibling of the body, after it
|
|
804
|
+
in the document, so it reads after the body and stacks under it
|
|
805
|
+
when the page is too narrow for both. -->
|
|
806
|
+
<aside
|
|
807
|
+
v-if="showsSideColumn"
|
|
808
|
+
class="cn-detail-page__side"
|
|
809
|
+
:aria-label="sideColumnLabel"
|
|
810
|
+
data-testid="cn-detail-page-side">
|
|
811
|
+
<div
|
|
812
|
+
v-for="widget in sideColumnWidgets"
|
|
813
|
+
:key="`side-${widget.id}`"
|
|
814
|
+
class="cn-detail-page__side-item"
|
|
815
|
+
:data-testid="`cn-detail-page-side-${widget.id}`">
|
|
816
|
+
<!--
|
|
817
|
+
@slot `widget-${widget.id}`
|
|
818
|
+
@description The same per-widget slot the body grid offers,
|
|
819
|
+
for a widget placed in the side column. Same name and same
|
|
820
|
+
bindings, so a custom widget placed here needs nothing it
|
|
821
|
+
would not need in the grid. Default content renders the
|
|
822
|
+
widget through `CnDetailWidgetHost` as a card.
|
|
823
|
+
@binding {object} item `{ id, widgetId }`, for parity with the grid slot.
|
|
824
|
+
@binding {object} widget The widget definition.
|
|
825
|
+
@binding {string|number} objectId This record's id.
|
|
826
|
+
@binding {object} object The loaded record, or null while it is still being fetched.
|
|
827
|
+
@binding {object} objectData Alias of `object`.
|
|
828
|
+
@binding {string} objectType Resolved object type slug.
|
|
829
|
+
@binding {string} register Register slug of this page.
|
|
830
|
+
@binding {string} schema Schema slug of this page.
|
|
831
|
+
-->
|
|
832
|
+
<slot
|
|
833
|
+
:name="`widget-${widget.id}`"
|
|
834
|
+
:item="{ id: widget.id, widgetId: widget.id }"
|
|
835
|
+
:widget="widget"
|
|
836
|
+
:objectId="objectId"
|
|
837
|
+
:object="resolvedObject"
|
|
838
|
+
:objectData="resolvedObject"
|
|
839
|
+
:objectType="resolvedObjectType"
|
|
840
|
+
:register="register"
|
|
841
|
+
:schema="schema">
|
|
842
|
+
<CnDetailWidgetHost
|
|
843
|
+
:widget="widget"
|
|
844
|
+
chrome="card"
|
|
845
|
+
:showCardTitle="true"
|
|
846
|
+
:objectId="objectId"
|
|
847
|
+
:object="currentObject"
|
|
848
|
+
:objectType="resolvedObjectType"
|
|
849
|
+
:schemaObject="currentSchema"
|
|
850
|
+
:register="register"
|
|
851
|
+
:schema="schema"
|
|
852
|
+
:store="effectiveObjectStore"
|
|
853
|
+
:surface="surface"
|
|
854
|
+
:integrationContext="effectiveIntegrationContext"
|
|
855
|
+
:hideEmpty="hideEmpty"
|
|
856
|
+
:cnRegistry="cnRegistry"
|
|
857
|
+
:availableWidgets="widgets"
|
|
858
|
+
@geoSaved="onGeoSaved"
|
|
859
|
+
@openIntegration="onAutoBodyOpenIntegration" />
|
|
860
|
+
</slot>
|
|
861
|
+
</div>
|
|
862
|
+
</aside>
|
|
863
|
+
|
|
712
864
|
<!-- Per-widget style/config editor (ADR-041). Opened by the per-widget
|
|
713
865
|
configure cog while in Buildiq edit mode; the modal's own Delete
|
|
714
866
|
button removes the widget from the grid. -->
|
|
@@ -810,9 +962,10 @@
|
|
|
810
962
|
</template>
|
|
811
963
|
|
|
812
964
|
<script>
|
|
965
|
+
import { getCurrentUser } from '@nextcloud/auth'
|
|
813
966
|
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
|
|
814
967
|
import { translate as t } from '@nextcloud/l10n'
|
|
815
|
-
import { NcActionButton, NcActionLink, NcActionSeparator, NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
|
|
968
|
+
import { NcActionButton, NcActionCaption, NcActionLink, NcActionSeparator, NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
|
|
816
969
|
import { provide, ref, watch } from 'vue'
|
|
817
970
|
import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
|
|
818
971
|
import ArrowLeft from 'vue-material-design-icons/ArrowLeft.vue'
|
|
@@ -834,6 +987,7 @@ import CnDetailWidgetHost from '../CnDetailWidgetHost/CnDetailWidgetHost.vue'
|
|
|
834
987
|
import CnFormDialog from '../CnFormDialog/CnFormDialog.vue'
|
|
835
988
|
import CnLifecycleActions from '../CnLifecycleActions/CnLifecycleActions.vue'
|
|
836
989
|
import CnLockedBanner from '../CnLockedBanner/CnLockedBanner.vue'
|
|
990
|
+
import CnNextStepCard from '../CnNextStepCard/CnNextStepCard.vue'
|
|
837
991
|
import CnRelatedCollections from '../CnRelatedCollections/CnRelatedCollections.vue'
|
|
838
992
|
import CnSummaryAggregates from '../CnSummaryAggregates/CnSummaryAggregates.vue'
|
|
839
993
|
import CnTranslatedBadge from '../CnTranslatedBadge/CnTranslatedBadge.vue'
|
|
@@ -846,6 +1000,15 @@ import { useObjectStore } from '../../store/index.js'
|
|
|
846
1000
|
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
847
1001
|
import { compactLayoutRows } from '../../utils/dashboardPlacement.js'
|
|
848
1002
|
import { defaultDetailGrid } from '../../utils/defaultDetailGrid.js'
|
|
1003
|
+
import {
|
|
1004
|
+
groupMenuEntries,
|
|
1005
|
+
MAX_QUICK_ACTIONS,
|
|
1006
|
+
normalisePinnedAction,
|
|
1007
|
+
resolveNextStep,
|
|
1008
|
+
resolvePill,
|
|
1009
|
+
stageEntry,
|
|
1010
|
+
stageOf,
|
|
1011
|
+
} from '../../utils/detailActionModel.js'
|
|
849
1012
|
import { cnGridCellStyle, hasGridRow } from '../../utils/grid.js'
|
|
850
1013
|
import { slotRenders } from '../../utils/slotContent.js'
|
|
851
1014
|
import {
|
|
@@ -860,6 +1023,7 @@ import {
|
|
|
860
1023
|
} from '../../utils/widgetDispatch.js'
|
|
861
1024
|
import { CnActionButtons } from '../CnActionButtons/index.js'
|
|
862
1025
|
import { CnIcon } from '../CnIcon/index.js'
|
|
1026
|
+
import { CnStatusBadge } from '../CnStatusBadge/index.js'
|
|
863
1027
|
import { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js'
|
|
864
1028
|
|
|
865
1029
|
import '../CnWidgetGrid/registerDashboardWidgets.js'
|
|
@@ -984,6 +1148,7 @@ export default {
|
|
|
984
1148
|
ChevronLeft,
|
|
985
1149
|
ChevronRight,
|
|
986
1150
|
NcActionButton,
|
|
1151
|
+
NcActionCaption,
|
|
987
1152
|
NcActionLink,
|
|
988
1153
|
NcActionSeparator,
|
|
989
1154
|
NcButton,
|
|
@@ -998,11 +1163,13 @@ export default {
|
|
|
998
1163
|
CnActionsMenu,
|
|
999
1164
|
CnBuildiqEditButton,
|
|
1000
1165
|
CnLockedBanner,
|
|
1166
|
+
CnNextStepCard,
|
|
1001
1167
|
CnDetailWidgetHost,
|
|
1002
1168
|
CnFormDialog,
|
|
1003
1169
|
CnDashboardGrid,
|
|
1004
1170
|
CnLifecycleActions,
|
|
1005
1171
|
CnActionButtons,
|
|
1172
|
+
CnStatusBadge,
|
|
1006
1173
|
CnSummaryAggregates,
|
|
1007
1174
|
CnRelatedCollections,
|
|
1008
1175
|
CnBodySections,
|
|
@@ -1811,6 +1978,130 @@ export default {
|
|
|
1811
1978
|
type: Boolean,
|
|
1812
1979
|
default: false,
|
|
1813
1980
|
},
|
|
1981
|
+
|
|
1982
|
+
// ── The action model (detail-action-model-and-case-surfaces) ────
|
|
1983
|
+
|
|
1984
|
+
/**
|
|
1985
|
+
* The primary action per stage (manifest `config.primaryActionByStage`):
|
|
1986
|
+
* a map from a stage value to an action. When the record's stage has
|
|
1987
|
+
* an entry, that action is the page's primary button, so the button
|
|
1988
|
+
* names the next step: "Take on" on a new case, "Publish" on a
|
|
1989
|
+
* decided one. An entry is an action object in the `headerActions`
|
|
1990
|
+
* shape, or the id of a declared header action. A stage without an
|
|
1991
|
+
* entry falls back to `primaryAction`.
|
|
1992
|
+
*
|
|
1993
|
+
* @type {Record<string, string|object>|null}
|
|
1994
|
+
*/
|
|
1995
|
+
primaryActionByStage: {
|
|
1996
|
+
type: Object,
|
|
1997
|
+
default: null,
|
|
1998
|
+
},
|
|
1999
|
+
|
|
2000
|
+
/**
|
|
2001
|
+
* Dot-path to the field that holds the record's stage. Read by
|
|
2002
|
+
* `primaryActionByStage` and `nextStep`.
|
|
2003
|
+
*
|
|
2004
|
+
* @type {string}
|
|
2005
|
+
*/
|
|
2006
|
+
stageField: {
|
|
2007
|
+
type: String,
|
|
2008
|
+
default: 'status',
|
|
2009
|
+
},
|
|
2010
|
+
|
|
2011
|
+
/**
|
|
2012
|
+
* Always visible buttons in the header (manifest `config.quickActions`)
|
|
2013
|
+
* for the few actions a handler uses on every record. At most three
|
|
2014
|
+
* render; later entries are dropped. An entry is an action object in
|
|
2015
|
+
* the `headerActions` shape, or the id of a declared header action,
|
|
2016
|
+
* which then leaves the Actions menu.
|
|
2017
|
+
*
|
|
2018
|
+
* @type {Array<string|object>}
|
|
2019
|
+
*/
|
|
2020
|
+
quickActions: {
|
|
2021
|
+
type: Array,
|
|
2022
|
+
default: () => [],
|
|
2023
|
+
},
|
|
2024
|
+
|
|
2025
|
+
/**
|
|
2026
|
+
* What the Actions menu itself shows (manifest `config.actionsMenu`):
|
|
2027
|
+
* `{ showRefresh?, showHelpLinks?, label? }`. `showRefresh: false`
|
|
2028
|
+
* removes Refresh. `showHelpLinks: false` removes Request a feature,
|
|
2029
|
+
* Report a bug and Documentation, for an app that offers them in its
|
|
2030
|
+
* own help menu. `label` names the menu, for example "More". `null`
|
|
2031
|
+
* (the default) changes nothing.
|
|
2032
|
+
*
|
|
2033
|
+
* @type {{showRefresh?: boolean, showHelpLinks?: boolean, label?: string}|null}
|
|
2034
|
+
*/
|
|
2035
|
+
actionsMenu: {
|
|
2036
|
+
type: Object,
|
|
2037
|
+
default: null,
|
|
2038
|
+
},
|
|
2039
|
+
|
|
2040
|
+
/**
|
|
2041
|
+
* The "what now" card (manifest `config.nextStep`):
|
|
2042
|
+
* `{ field?, stages: { <stage>: { title?, checklist, after? } } }`.
|
|
2043
|
+
* Each checklist item is `{ label, doneField?, doneWhen?, hint? }`.
|
|
2044
|
+
* Rendered above the body for a stage that declares a checklist, with
|
|
2045
|
+
* the primary button inside it. See `CnNextStepCard`.
|
|
2046
|
+
*
|
|
2047
|
+
* @type {object|null}
|
|
2048
|
+
*/
|
|
2049
|
+
nextStep: {
|
|
2050
|
+
type: Object,
|
|
2051
|
+
default: null,
|
|
2052
|
+
},
|
|
2053
|
+
|
|
2054
|
+
/**
|
|
2055
|
+
* A pill above the title saying what kind of record this is
|
|
2056
|
+
* (manifest `config.typePill`): `{ field, colorMap?, labels?, variant? }`.
|
|
2057
|
+
* Rendered through `CnStatusBadge`; `colorMap` is keyed on the field's
|
|
2058
|
+
* raw value, `labels` maps a raw value to the text to show.
|
|
2059
|
+
*
|
|
2060
|
+
* @type {{field: string, colorMap?: object, labels?: object, variant?: string}|null}
|
|
2061
|
+
*/
|
|
2062
|
+
typePill: {
|
|
2063
|
+
type: Object,
|
|
2064
|
+
default: null,
|
|
2065
|
+
},
|
|
2066
|
+
|
|
2067
|
+
/**
|
|
2068
|
+
* A pill above the title saying where the record stands (manifest
|
|
2069
|
+
* `config.statusPill`). Same shape as `typePill`.
|
|
2070
|
+
*
|
|
2071
|
+
* @type {{field: string, colorMap?: object, labels?: object, variant?: string}|null}
|
|
2072
|
+
*/
|
|
2073
|
+
statusPill: {
|
|
2074
|
+
type: Object,
|
|
2075
|
+
default: null,
|
|
2076
|
+
},
|
|
2077
|
+
|
|
2078
|
+
/**
|
|
2079
|
+
* A column of cards beside the body (manifest `config.sideColumn`),
|
|
2080
|
+
* for the record's facts: deadline, requester, handler, links. Each
|
|
2081
|
+
* entry is a widget definition (`{ id?, type, title?, content? }`) or
|
|
2082
|
+
* the id of a widget declared in `widgets`. The column stacks under
|
|
2083
|
+
* the body on a narrow page. Empty (the default) leaves the layout as
|
|
2084
|
+
* it is.
|
|
2085
|
+
*
|
|
2086
|
+
* @type {Array<string|object>}
|
|
2087
|
+
*/
|
|
2088
|
+
sideColumn: {
|
|
2089
|
+
type: Array,
|
|
2090
|
+
default: () => [],
|
|
2091
|
+
},
|
|
2092
|
+
|
|
2093
|
+
/**
|
|
2094
|
+
* Whether the current user administers this instance. Shows the
|
|
2095
|
+
* `adminOnly` header actions. `null` (the default) reads it from
|
|
2096
|
+
* `getCurrentUser()`. Visibility only, never an authorization
|
|
2097
|
+
* decision: the endpoint behind an action decides who may call it.
|
|
2098
|
+
*
|
|
2099
|
+
* @type {boolean|null}
|
|
2100
|
+
*/
|
|
2101
|
+
isAdmin: {
|
|
2102
|
+
type: Boolean,
|
|
2103
|
+
default: null,
|
|
2104
|
+
},
|
|
1814
2105
|
},
|
|
1815
2106
|
|
|
1816
2107
|
emits: [
|
|
@@ -2078,7 +2369,7 @@ export default {
|
|
|
2078
2369
|
* @spec openspec/changes/case-page-and-list-as-a-place/specs/index-page/spec.md
|
|
2079
2370
|
*/
|
|
2080
2371
|
primaryActionAnchorId() {
|
|
2081
|
-
const scope = this.primaryAction?.id || this.pageId || this.objectId || 'record'
|
|
2372
|
+
const scope = this.stagePrimaryEntry?.id || this.primaryAction?.id || this.pageId || this.objectId || 'record'
|
|
2082
2373
|
return `cn-primary-action-${String(scope).replace(/[^A-Za-z0-9_-]/g, '-')}`
|
|
2083
2374
|
},
|
|
2084
2375
|
|
|
@@ -2093,7 +2384,291 @@ export default {
|
|
|
2093
2384
|
* @spec openspec/changes/case-page-and-list-as-a-place/specs/index-page/spec.md
|
|
2094
2385
|
*/
|
|
2095
2386
|
skipLinkLabel() {
|
|
2096
|
-
return `${this.effectiveTranslate('Skip to')} ${this.
|
|
2387
|
+
return `${this.effectiveTranslate('Skip to')} ${this.effectivePrimaryLabel}`
|
|
2388
|
+
},
|
|
2389
|
+
|
|
2390
|
+
// ── The action model (detail-action-model-and-case-surfaces) ────
|
|
2391
|
+
|
|
2392
|
+
/**
|
|
2393
|
+
* The record's stage, as `primaryActionByStage` and `nextStep` key it.
|
|
2394
|
+
*
|
|
2395
|
+
* @return {string} The stage, or '' while the record has none.
|
|
2396
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage
|
|
2397
|
+
*/
|
|
2398
|
+
recordStage() {
|
|
2399
|
+
return stageOf(this.currentObject, this.stageField)
|
|
2400
|
+
},
|
|
2401
|
+
|
|
2402
|
+
/**
|
|
2403
|
+
* The stage's primary action as an id, plus its definition when the
|
|
2404
|
+
* map carries one inline.
|
|
2405
|
+
*
|
|
2406
|
+
* @return {{id: string, inline: object|null}|null} The pinned action, or null when this stage declares none.
|
|
2407
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage
|
|
2408
|
+
*/
|
|
2409
|
+
stagePrimary() {
|
|
2410
|
+
return normalisePinnedAction(stageEntry(this.primaryActionByStage, this.recordStage), 'cn-stage-primary')
|
|
2411
|
+
},
|
|
2412
|
+
|
|
2413
|
+
/**
|
|
2414
|
+
* The quick actions as ids, plus definitions for the inline ones.
|
|
2415
|
+
* Capped at three, and never the action that is already the primary
|
|
2416
|
+
* button.
|
|
2417
|
+
*
|
|
2418
|
+
* @return {Array<{id: string, inline: object|null}>} The pinned actions.
|
|
2419
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-quick-actions
|
|
2420
|
+
*/
|
|
2421
|
+
pinnedQuickActions() {
|
|
2422
|
+
const primaryId = this.stagePrimary?.id
|
|
2423
|
+
return (Array.isArray(this.quickActions) ? this.quickActions : [])
|
|
2424
|
+
.map((entry, index) => normalisePinnedAction(entry, `cn-quick-${index}`))
|
|
2425
|
+
.filter((pinned) => pinned && pinned.id !== primaryId)
|
|
2426
|
+
.slice(0, MAX_QUICK_ACTIONS)
|
|
2427
|
+
},
|
|
2428
|
+
|
|
2429
|
+
/**
|
|
2430
|
+
* The stage's primary action in dispatchable form: the menu entry
|
|
2431
|
+
* CnActionButtons built for it, with its `run()`. Null while the
|
|
2432
|
+
* action is hidden by its own `visibleWhen`, in which case the page
|
|
2433
|
+
* falls back to `primaryAction`.
|
|
2434
|
+
*
|
|
2435
|
+
* @return {object|null} The entry, or null.
|
|
2436
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage
|
|
2437
|
+
*/
|
|
2438
|
+
stagePrimaryEntry() {
|
|
2439
|
+
const id = this.stagePrimary?.id
|
|
2440
|
+
return (id && this.menuHeaderActions.find((entry) => entry.id === id)) || null
|
|
2441
|
+
},
|
|
2442
|
+
|
|
2443
|
+
/**
|
|
2444
|
+
* The quick actions in dispatchable form, in declaration order.
|
|
2445
|
+
*
|
|
2446
|
+
* @return {Array<object>} The entries that are visible.
|
|
2447
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-quick-actions
|
|
2448
|
+
*/
|
|
2449
|
+
quickActionEntries() {
|
|
2450
|
+
return this.pinnedQuickActions
|
|
2451
|
+
.map((pinned) => this.menuHeaderActions.find((entry) => entry.id === pinned.id))
|
|
2452
|
+
.filter(Boolean)
|
|
2453
|
+
},
|
|
2454
|
+
|
|
2455
|
+
/**
|
|
2456
|
+
* What the primary button says: the stage's action when the stage
|
|
2457
|
+
* declares one, the page's `primaryAction` otherwise. Empty gates
|
|
2458
|
+
* both the button and the skip link.
|
|
2459
|
+
*
|
|
2460
|
+
* @return {string} The label, or an empty string.
|
|
2461
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage
|
|
2462
|
+
*/
|
|
2463
|
+
effectivePrimaryLabel() {
|
|
2464
|
+
return this.stagePrimaryEntry ? this.stagePrimaryEntry.label : this.primaryActionLabel
|
|
2465
|
+
},
|
|
2466
|
+
|
|
2467
|
+
/**
|
|
2468
|
+
* The URL the primary button links to, when its action is a link.
|
|
2469
|
+
*
|
|
2470
|
+
* @return {string} The href, or '' for an action that is dispatched.
|
|
2471
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage
|
|
2472
|
+
*/
|
|
2473
|
+
primaryActionHref() {
|
|
2474
|
+
return this.stagePrimaryEntry?.href || ''
|
|
2475
|
+
},
|
|
2476
|
+
|
|
2477
|
+
/**
|
|
2478
|
+
* The link target that goes with `primaryActionHref`.
|
|
2479
|
+
*
|
|
2480
|
+
* @return {string|undefined} The target, or undefined.
|
|
2481
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage
|
|
2482
|
+
*/
|
|
2483
|
+
primaryActionTarget() {
|
|
2484
|
+
return this.stagePrimaryEntry?.linkTarget || undefined
|
|
2485
|
+
},
|
|
2486
|
+
|
|
2487
|
+
/**
|
|
2488
|
+
* Whether the primary button waits for its own request.
|
|
2489
|
+
*
|
|
2490
|
+
* @return {boolean} True while the stage's action is in flight.
|
|
2491
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage
|
|
2492
|
+
*/
|
|
2493
|
+
primaryActionDisabled() {
|
|
2494
|
+
return Boolean(this.stagePrimaryEntry?.disabled)
|
|
2495
|
+
},
|
|
2496
|
+
|
|
2497
|
+
/**
|
|
2498
|
+
* Whether the viewer administers this instance, for `adminOnly`
|
|
2499
|
+
* actions. The prop wins; otherwise Nextcloud says.
|
|
2500
|
+
*
|
|
2501
|
+
* @return {boolean} True for an instance admin.
|
|
2502
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-grouped-and-admin-only-menu-actions
|
|
2503
|
+
*/
|
|
2504
|
+
viewerIsAdmin() {
|
|
2505
|
+
if (this.isAdmin !== null) {
|
|
2506
|
+
return this.isAdmin
|
|
2507
|
+
}
|
|
2508
|
+
try {
|
|
2509
|
+
return getCurrentUser()?.isAdmin === true
|
|
2510
|
+
} catch {
|
|
2511
|
+
return false
|
|
2512
|
+
}
|
|
2513
|
+
},
|
|
2514
|
+
|
|
2515
|
+
/**
|
|
2516
|
+
* The declared actions by id, for the `group` and `adminOnly` a menu
|
|
2517
|
+
* entry does not carry itself.
|
|
2518
|
+
*
|
|
2519
|
+
* @return {Record<string, object>} The lookup.
|
|
2520
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-grouped-and-admin-only-menu-actions
|
|
2521
|
+
*/
|
|
2522
|
+
headerActionsById() {
|
|
2523
|
+
return Object.fromEntries(this.effectiveHeaderActions
|
|
2524
|
+
.filter((action) => action && action.id)
|
|
2525
|
+
.map((action) => [action.id, action]))
|
|
2526
|
+
},
|
|
2527
|
+
|
|
2528
|
+
/**
|
|
2529
|
+
* The menu entries in the groups the menu draws: ungrouped first,
|
|
2530
|
+
* named groups in first appearance order, admin actions last and for
|
|
2531
|
+
* admins only. A page that groups nothing gets one captionless group.
|
|
2532
|
+
*
|
|
2533
|
+
* @return {Array<{key: string, label: string, entries: Array<object>}>} The groups.
|
|
2534
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-grouped-and-admin-only-menu-actions
|
|
2535
|
+
*/
|
|
2536
|
+
headerMenuGroups() {
|
|
2537
|
+
const groups = groupMenuEntries(this.headerMenuEntries, this.headerActionsById, {
|
|
2538
|
+
isAdmin: this.viewerIsAdmin,
|
|
2539
|
+
adminLabel: t('nextcloud-vue', 'Administration'),
|
|
2540
|
+
})
|
|
2541
|
+
return groups.map((group) => ({ ...group, label: group.label ? this.effectiveTranslate(group.label) : '' }))
|
|
2542
|
+
},
|
|
2543
|
+
|
|
2544
|
+
/**
|
|
2545
|
+
* Whether Refresh renders in the Actions menu.
|
|
2546
|
+
*
|
|
2547
|
+
* @return {boolean} The existing rule, unless `actionsMenu.showRefresh` is false.
|
|
2548
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-actions-menu-built-ins-are-optional
|
|
2549
|
+
*/
|
|
2550
|
+
menuShowsRefresh() {
|
|
2551
|
+
return this.actionsMenu?.showRefresh === false ? false : this.effectiveHeaderShowRefresh
|
|
2552
|
+
},
|
|
2553
|
+
|
|
2554
|
+
/**
|
|
2555
|
+
* Whether the help links render in the Actions menu.
|
|
2556
|
+
*
|
|
2557
|
+
* @return {boolean} True unless `actionsMenu.showHelpLinks` is false.
|
|
2558
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-actions-menu-built-ins-are-optional
|
|
2559
|
+
*/
|
|
2560
|
+
menuShowsHelpLinks() {
|
|
2561
|
+
return this.actionsMenu?.showHelpLinks !== false
|
|
2562
|
+
},
|
|
2563
|
+
|
|
2564
|
+
/**
|
|
2565
|
+
* The Actions menu's name.
|
|
2566
|
+
*
|
|
2567
|
+
* @return {string|undefined} `actionsMenu.label` translated, or undefined for the default "Actions".
|
|
2568
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-actions-menu-built-ins-are-optional
|
|
2569
|
+
*/
|
|
2570
|
+
actionsMenuName() {
|
|
2571
|
+
const label = this.actionsMenu?.label
|
|
2572
|
+
return typeof label === 'string' && label !== '' ? this.effectiveTranslate(label) : undefined
|
|
2573
|
+
},
|
|
2574
|
+
|
|
2575
|
+
/**
|
|
2576
|
+
* Whether the page is showing its body: not the not-found, loading,
|
|
2577
|
+
* error or empty state. The "what now" card and the side column are
|
|
2578
|
+
* siblings of the body and follow it.
|
|
2579
|
+
*
|
|
2580
|
+
* @return {boolean} True when the body renders.
|
|
2581
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
|
|
2582
|
+
*/
|
|
2583
|
+
showsBody() {
|
|
2584
|
+
return !this.objectNotFound && !this.showLoadingState && !this.error && !this.empty
|
|
2585
|
+
},
|
|
2586
|
+
|
|
2587
|
+
/**
|
|
2588
|
+
* The "what now" card for the record's stage, labels translated.
|
|
2589
|
+
*
|
|
2590
|
+
* @return {{stage: string, title: string, items: Array<object>, after: string}|null} The card, or null when the stage declares no checklist.
|
|
2591
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
|
|
2592
|
+
*/
|
|
2593
|
+
nextStepCard() {
|
|
2594
|
+
const card = resolveNextStep(this.nextStep, this.currentObject, this.stageField)
|
|
2595
|
+
if (!card) {
|
|
2596
|
+
return null
|
|
2597
|
+
}
|
|
2598
|
+
const tr = (text) => (text ? this.effectiveTranslate(text) : '')
|
|
2599
|
+
return {
|
|
2600
|
+
...card,
|
|
2601
|
+
title: tr(card.title),
|
|
2602
|
+
after: tr(card.after),
|
|
2603
|
+
items: card.items.map((item) => ({ ...item, label: tr(item.label), hint: tr(item.hint) })),
|
|
2604
|
+
}
|
|
2605
|
+
},
|
|
2606
|
+
|
|
2607
|
+
/**
|
|
2608
|
+
* Whether the "what now" card renders.
|
|
2609
|
+
*
|
|
2610
|
+
* @return {boolean} True when the stage has a checklist and the body is showing.
|
|
2611
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
|
|
2612
|
+
*/
|
|
2613
|
+
showsNextStepCard() {
|
|
2614
|
+
return Boolean(this.nextStepCard) && this.showsBody
|
|
2615
|
+
},
|
|
2616
|
+
|
|
2617
|
+
/**
|
|
2618
|
+
* The pills above the title: type first, then status.
|
|
2619
|
+
*
|
|
2620
|
+
* @return {Array<{key: string, label: string, colorKey: string, colorMap: object|null, variant: string}>} The pills whose field has a value.
|
|
2621
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-header-pills
|
|
2622
|
+
*/
|
|
2623
|
+
headerPills() {
|
|
2624
|
+
return [['type', this.typePill], ['status', this.statusPill]]
|
|
2625
|
+
.map(([key, config]) => {
|
|
2626
|
+
const pill = resolvePill(config, this.resolvedObject)
|
|
2627
|
+
return pill ? { ...pill, key, label: this.effectiveTranslate(pill.label) } : null
|
|
2628
|
+
})
|
|
2629
|
+
.filter(Boolean)
|
|
2630
|
+
},
|
|
2631
|
+
|
|
2632
|
+
/**
|
|
2633
|
+
* The side column's widget definitions. A string names a widget
|
|
2634
|
+
* declared in `widgets`; an object is a definition of its own.
|
|
2635
|
+
*
|
|
2636
|
+
* @return {Array<object>} The definitions that resolve, each with an `id`.
|
|
2637
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-side-column
|
|
2638
|
+
*/
|
|
2639
|
+
sideColumnWidgets() {
|
|
2640
|
+
const declared = Array.isArray(this.widgets) ? this.widgets : []
|
|
2641
|
+
return (Array.isArray(this.sideColumn) ? this.sideColumn : [])
|
|
2642
|
+
.map((entry, index) => {
|
|
2643
|
+
if (typeof entry === 'string') {
|
|
2644
|
+
return declared.find((widget) => widget && widget.id === entry) || null
|
|
2645
|
+
}
|
|
2646
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
2647
|
+
return null
|
|
2648
|
+
}
|
|
2649
|
+
return entry.id ? entry : { ...entry, id: `cn-side-${index}` }
|
|
2650
|
+
})
|
|
2651
|
+
.filter(Boolean)
|
|
2652
|
+
},
|
|
2653
|
+
|
|
2654
|
+
/**
|
|
2655
|
+
* Whether the side column renders.
|
|
2656
|
+
*
|
|
2657
|
+
* @return {boolean} True when it has widgets and the body is showing.
|
|
2658
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-side-column
|
|
2659
|
+
*/
|
|
2660
|
+
showsSideColumn() {
|
|
2661
|
+
return this.sideColumnWidgets.length > 0 && this.showsBody
|
|
2662
|
+
},
|
|
2663
|
+
|
|
2664
|
+
/**
|
|
2665
|
+
* Accessible name of the side column.
|
|
2666
|
+
*
|
|
2667
|
+
* @return {string} The label.
|
|
2668
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-side-column
|
|
2669
|
+
*/
|
|
2670
|
+
sideColumnLabel() {
|
|
2671
|
+
return t('nextcloud-vue', 'Details')
|
|
2097
2672
|
},
|
|
2098
2673
|
|
|
2099
2674
|
/**
|
|
@@ -2297,7 +2872,16 @@ export default {
|
|
|
2297
2872
|
*/
|
|
2298
2873
|
headerMenuEntries() {
|
|
2299
2874
|
const lifecycle = this.showsLifecycleActions ? this.lifecycleMenuEntries : []
|
|
2300
|
-
|
|
2875
|
+
// An action drawn as a button, the stage's primary one or a quick
|
|
2876
|
+
// action, is not listed a second time in the menu.
|
|
2877
|
+
const pinned = new Set([
|
|
2878
|
+
...(this.stagePrimaryEntry ? [this.stagePrimaryEntry.id] : []),
|
|
2879
|
+
...this.quickActionEntries.map((entry) => entry.id),
|
|
2880
|
+
])
|
|
2881
|
+
const inMenu = pinned.size > 0
|
|
2882
|
+
? this.menuHeaderActions.filter((entry) => !pinned.has(entry.id))
|
|
2883
|
+
: this.menuHeaderActions
|
|
2884
|
+
return [...lifecycle, ...inMenu]
|
|
2301
2885
|
},
|
|
2302
2886
|
|
|
2303
2887
|
/**
|
|
@@ -2319,7 +2903,17 @@ export default {
|
|
|
2319
2903
|
* @return {Array<object>} The action descriptors.
|
|
2320
2904
|
*/
|
|
2321
2905
|
effectiveHeaderActions() {
|
|
2322
|
-
const
|
|
2906
|
+
const authored = Array.isArray(this.headerActions) ? this.headerActions : []
|
|
2907
|
+
// Inline pinned actions (a stage's primary action, a quick action
|
|
2908
|
+
// written out in place) join the list CnActionButtons dispatches,
|
|
2909
|
+
// so they get the same dialogs, toasts and `visibleWhen` gating as
|
|
2910
|
+
// any header action. An id that is already declared is not added
|
|
2911
|
+
// twice. With nothing pinned inline this is `headerActions` itself.
|
|
2912
|
+
const known = new Set(authored.map((action) => action && action.id))
|
|
2913
|
+
const inline = [this.stagePrimary, ...this.pinnedQuickActions]
|
|
2914
|
+
.filter((pinned) => pinned && pinned.inline && !known.has(pinned.id))
|
|
2915
|
+
.map((pinned) => pinned.inline)
|
|
2916
|
+
const declared = inline.length > 0 ? [...authored, ...inline] : authored
|
|
2323
2917
|
if (!(this.foldsEditIntoActions && this.canEditRecord)) {
|
|
2324
2918
|
return declared
|
|
2325
2919
|
}
|
|
@@ -3132,7 +3726,11 @@ export default {
|
|
|
3132
3726
|
* @spec openspec/changes/case-page-and-list-as-a-place/specs/index-page/spec.md
|
|
3133
3727
|
*/
|
|
3134
3728
|
onSkipToPrimaryAction(event) {
|
|
3135
|
-
|
|
3729
|
+
// The ref covers the header button; the id also finds the button
|
|
3730
|
+
// when it sits inside the "what now" card.
|
|
3731
|
+
const target = this.$refs.primaryActionButton?.$el
|
|
3732
|
+
?? this.$refs.primaryActionButton
|
|
3733
|
+
?? this.$el?.querySelector?.(`[id="${this.primaryActionAnchorId}"]`)
|
|
3136
3734
|
const focusable = target?.matches?.('button, a') ? target : target?.querySelector?.('button, a')
|
|
3137
3735
|
if (focusable?.focus) {
|
|
3138
3736
|
event.preventDefault()
|
|
@@ -3140,6 +3738,43 @@ export default {
|
|
|
3140
3738
|
}
|
|
3141
3739
|
},
|
|
3142
3740
|
|
|
3741
|
+
/**
|
|
3742
|
+
* The primary button was pressed.
|
|
3743
|
+
*
|
|
3744
|
+
* A stage's action is dispatched the way a header action is, through
|
|
3745
|
+
* the `run()` CnActionButtons bound for it, and announced to the host
|
|
3746
|
+
* with its declaration. A link action is left to the browser. Without
|
|
3747
|
+
* a stage action this is the page's `primaryAction`, announced as
|
|
3748
|
+
* before.
|
|
3749
|
+
*
|
|
3750
|
+
* @return {void}
|
|
3751
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage
|
|
3752
|
+
*/
|
|
3753
|
+
onPrimaryActionClick() {
|
|
3754
|
+
const entry = this.stagePrimaryEntry
|
|
3755
|
+
if (!entry) {
|
|
3756
|
+
this.$emit('primary-action', this.primaryAction)
|
|
3757
|
+
return
|
|
3758
|
+
}
|
|
3759
|
+
this.$emit('primary-action', this.headerActionsById[entry.id] || { id: entry.id, label: entry.label })
|
|
3760
|
+
this.onPinnedActionClick(entry)
|
|
3761
|
+
},
|
|
3762
|
+
|
|
3763
|
+
/**
|
|
3764
|
+
* A pinned action's button was pressed: run it, unless it is a link
|
|
3765
|
+
* the browser is already following.
|
|
3766
|
+
*
|
|
3767
|
+
* @param {object} entry The action's menu entry.
|
|
3768
|
+
* @return {void}
|
|
3769
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-quick-actions
|
|
3770
|
+
*/
|
|
3771
|
+
onPinnedActionClick(entry) {
|
|
3772
|
+
if (!entry || entry.href || typeof entry.run !== 'function') {
|
|
3773
|
+
return
|
|
3774
|
+
}
|
|
3775
|
+
entry.run()
|
|
3776
|
+
},
|
|
3777
|
+
|
|
3143
3778
|
/**
|
|
3144
3779
|
* The active tab changed: tell the host, and put it in the address
|
|
3145
3780
|
* when the page declares that its tab is part of its address.
|