@conduction/nextcloud-vue 2.80.0 → 2.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +3 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +80 -4
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +95 -91
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +140 -10
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +52 -16
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +17 -3
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +63 -52
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js +10 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js +252 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js +140 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/index.js +26 -0
- package/dist/esm/components/CnChipsFieldWidget/index.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js +10 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js +354 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js +172 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/index.js +27 -0
- package/dist/esm/components/CnChoicePickerWidget/index.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +2 -2
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +65 -5
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +19 -11
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +3 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +72 -0
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +21 -4
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js +7 -7
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +131 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +80 -5
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +282 -69
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +302 -4
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +417 -244
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +101 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +141 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +60 -0
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +74 -49
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +29 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +48 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +7 -7
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +65 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +63 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +8 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +14 -0
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js +5 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +10 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +174 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +75 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/index.js +28 -0
- package/dist/esm/components/CnRadioCardsWidget/index.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +30 -6
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +34 -25
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js +10 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js +222 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js +64 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/index.js +30 -0
- package/dist/esm/components/CnSavedViewsWidget/index.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +10 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +347 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +184 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/index.js +27 -0
- package/dist/esm/components/CnSettingsFormWidget/index.js.map +1 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +87 -6
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +84 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +85 -41
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +10 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +159 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js +60 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/index.js +26 -0
- package/dist/esm/components/CnSwitchRowsWidget/index.js.map +1 -0
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +47 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +16 -14
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +80 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +75 -32
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +10 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +269 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js +106 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/index.js +27 -0
- package/dist/esm/components/CnToggleTableWidget/index.js.map +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +7 -2
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
- package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +197 -6
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +96 -50
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useListView.js +4 -4
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/index.js +17 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +9 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +55 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +1041 -12
- package/dist/esm/utils/contentHeaderLink.js +51 -0
- package/dist/esm/utils/contentHeaderLink.js.map +1 -0
- package/dist/esm/utils/facets.js +75 -3
- package/dist/esm/utils/facets.js.map +1 -1
- package/dist/esm/utils/fetchFilterCounts.js +40 -5
- package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +7 -1
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/initials.js +32 -0
- package/dist/esm/utils/initials.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +7 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/objectWrite.js +59 -0
- package/dist/esm/utils/objectWrite.js.map +1 -0
- package/dist/esm/utils/pageControls.js +81 -0
- package/dist/esm/utils/pageControls.js.map +1 -0
- package/dist/esm/utils/personalLenses.js +40 -3
- package/dist/esm/utils/personalLenses.js.map +1 -1
- package/dist/esm/utils/resolveFilterTokens.js +1 -1
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/resolveManifestTokens.js +2 -0
- package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +19 -1
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/esm/utils/userGroupsPeek.js +91 -0
- package/dist/esm/utils/userGroupsPeek.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/visibleWhen.js +57 -5
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/esm/utils/widgetDispatch.js +1 -1
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/esm/utils/widgetVisibility.js +4 -57
- package/dist/esm/utils/widgetVisibility.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +13589 -7833
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1041 -12
- package/l10n/en.json +9 -1
- package/l10n/nl.json +55 -1
- package/package.json +2 -1
- package/scripts/check-docs-mdx.mjs +108 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +6 -1
- package/src/components/CnAppRoot/CnAppRoot.md +4 -2
- package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
- package/src/components/CnBannerWidget/CnBannerWidget.vue +205 -20
- package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +47 -4
- package/src/components/CnChipsFieldWidget/CnChipsFieldWidget.vue +433 -0
- package/src/components/CnChipsFieldWidget/index.js +25 -0
- package/src/components/CnChoicePickerWidget/CnChoicePickerWidget.vue +556 -0
- package/src/components/CnChoicePickerWidget/index.js +26 -0
- package/src/components/CnConversationThread/CnConversationWidget.vue +1 -1
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +2 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +78 -5
- package/src/components/CnDataTable/CnDataTable.vue +21 -12
- package/src/components/CnDetailPage/CnDetailPage.vue +89 -3
- package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
- package/src/components/CnFactsWidget/CnFactsWidget.vue +142 -2
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +268 -6
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +588 -117
- package/src/components/CnFilesBrowser/filesBrowser.js +100 -0
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +140 -0
- package/src/components/CnFormPage/CnFormPage.vue +81 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +56 -4
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +7 -7
- package/src/components/CnNextStepCard/CnNextStepWidget.vue +1 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +69 -2
- package/src/components/CnObjectSidebar/CnFilesTab.vue +70 -1
- package/src/components/CnObjectSidebar/CnNotesTab.vue +16 -0
- package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
- package/src/components/CnRadioCardsWidget/index.js +27 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +55 -6
- package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
- package/src/components/CnSavedViewsWidget/index.js +29 -0
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +13 -0
- package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +555 -0
- package/src/components/CnSettingsFormWidget/index.js +26 -0
- package/src/components/CnStatWidget/CnStatWidget.vue +87 -6
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +180 -3
- package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +226 -0
- package/src/components/CnSwitchRowsWidget/index.js +25 -0
- package/src/components/CnTabsWidget/CnTabsWidget.vue +82 -3
- package/src/components/CnTasksWidget/CnTasksWidget.vue +175 -15
- package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +386 -0
- package/src/components/CnToggleTableWidget/index.js +26 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
- package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
- package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +309 -11
- package/src/components/index.js +15 -0
- package/src/composables/useListView.js +4 -4
- package/src/css/look-board-detail.css +36 -2
- package/src/css/look-board-index.css +39 -8
- package/src/css/look-board.css +72 -0
- package/src/index.js +15 -0
- package/src/schemas/app-manifest-v2.schema.json +93 -9
- package/src/utils/contentHeaderLink.js +48 -0
- package/src/utils/facets.js +74 -2
- package/src/utils/fetchFilterCounts.js +39 -4
- package/src/utils/fillTemplate.js +7 -1
- package/src/utils/initials.js +29 -0
- package/src/utils/libraryWidgetKeys.js +7 -0
- package/src/utils/objectWrite.js +55 -0
- package/src/utils/pageControls.js +78 -0
- package/src/utils/personalLenses.js +39 -2
- package/src/utils/resolveFilterTokens.js +3 -1
- package/src/utils/resolveManifestTokens.js +2 -0
- package/src/utils/sentinelTokens.js +19 -1
- package/src/utils/userGroupsPeek.js +87 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +56 -4
- package/src/utils/widgetDispatch.js +1 -1
- package/src/utils/widgetVisibility.js +7 -56
|
@@ -452,6 +452,7 @@
|
|
|
452
452
|
:layout="section.layout"
|
|
453
453
|
:editable="section.editable"
|
|
454
454
|
:columns="columns"
|
|
455
|
+
:columnOpts="gridColumnOpts"
|
|
455
456
|
:cellHeight="cellHeight"
|
|
456
457
|
:margin="gridMargin"
|
|
457
458
|
:float="gridFloat"
|
|
@@ -933,7 +934,8 @@ import { normalizeLook } from '../../composables/useLook.js'
|
|
|
933
934
|
import { pageViews } from '../../mixins/pageViews.js'
|
|
934
935
|
import { mergeUserLayout, resolveUserLayoutApi } from '../../store/plugins/dashboardLayouts.js'
|
|
935
936
|
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
936
|
-
import {
|
|
937
|
+
import { contentHeaderLink } from '../../utils/contentHeaderLink.js'
|
|
938
|
+
import { compareVisibleWhen, evaluateWorkspaceCondition, isWorkspaceCondition, readVisibleWhenValue } from '../../utils/visibleWhen.js'
|
|
937
939
|
import { canonicalWidgetType } from '../../utils/widgetTypeAliases.js'
|
|
938
940
|
import { CnActionButtons } from '../CnActionButtons/index.js'
|
|
939
941
|
import { CnActionsMenu } from '../CnActionsMenu/index.js'
|
|
@@ -2082,6 +2084,22 @@ export default {
|
|
|
2082
2084
|
return this.compactDisplayLayout(visible)
|
|
2083
2085
|
},
|
|
2084
2086
|
|
|
2087
|
+
/**
|
|
2088
|
+
* The responsive columns of the grid. Under the board look a phone gets
|
|
2089
|
+
* one column: below 640px the widgets stack in reading order (top to
|
|
2090
|
+
* bottom, then left to right) instead of squeezing six columns into
|
|
2091
|
+
* 150px. Without the board look the grid keeps its fixed columns.
|
|
2092
|
+
*
|
|
2093
|
+
* @return {object|null} GridStack `columnOpts`, or null.
|
|
2094
|
+
* @spec openspec/changes/screens-dashboard-phone/specs/dashboard-phone/spec.md#requirement-a-board-dashboard-stacks-on-a-phone
|
|
2095
|
+
*/
|
|
2096
|
+
gridColumnOpts() {
|
|
2097
|
+
if (!this.isBoardLook) {
|
|
2098
|
+
return null
|
|
2099
|
+
}
|
|
2100
|
+
return { columnMax: this.columns, breakpoints: [{ w: 640, c: 1 }], layout: 'list', breakpointForWindow: true }
|
|
2101
|
+
},
|
|
2102
|
+
|
|
2085
2103
|
/**
|
|
2086
2104
|
* The layout this page renders: the user's arrangement when they have
|
|
2087
2105
|
* one, the manifest otherwise.
|
|
@@ -2256,6 +2274,10 @@ export default {
|
|
|
2256
2274
|
const items = []
|
|
2257
2275
|
for (const id of ids) {
|
|
2258
2276
|
if (this.getWidgetDef(id) || this.hasWidgetSlot(id)) {
|
|
2277
|
+
// A tile a page control hides surrenders its cell.
|
|
2278
|
+
if (!this.gridEditable && this.isHiddenByWorkspace(id)) {
|
|
2279
|
+
continue
|
|
2280
|
+
}
|
|
2259
2281
|
items.push({ widgetId: id })
|
|
2260
2282
|
} else if (process.env.NODE_ENV !== 'production') {
|
|
2261
2283
|
// eslint-disable-next-line no-console
|
|
@@ -3401,7 +3423,8 @@ export default {
|
|
|
3401
3423
|
const layout = content.layout || props.layout || ''
|
|
3402
3424
|
const title = layout === 'attention' ? (content.title || props.title || '') : ''
|
|
3403
3425
|
return {
|
|
3404
|
-
|
|
3426
|
+
// A banner with only a bold `lead` has words to show too.
|
|
3427
|
+
text: content.text || props.text || content.lead || props.lead || title || '',
|
|
3405
3428
|
visibleWhen: (def && def.visibleWhen) || content.visibleWhen || props.visibleWhen || null,
|
|
3406
3429
|
layout,
|
|
3407
3430
|
}
|
|
@@ -3435,6 +3458,11 @@ export default {
|
|
|
3435
3458
|
if (!visibleWhen) {
|
|
3436
3459
|
return false
|
|
3437
3460
|
}
|
|
3461
|
+
// A workspace condition (a page control's choice) is answered at
|
|
3462
|
+
// once from the reactive bag; nothing is fetched for it.
|
|
3463
|
+
if (isWorkspaceCondition(visibleWhen)) {
|
|
3464
|
+
return !evaluateWorkspaceCondition(visibleWhen, this.workspaceContext)
|
|
3465
|
+
}
|
|
3438
3466
|
const outcome = this.widgetConditionOutcome[item.widgetId]
|
|
3439
3467
|
if (outcome && outcome.failed === true && layout === 'attention' && this.isBannerDef(def)) {
|
|
3440
3468
|
return false
|
|
@@ -3442,6 +3470,23 @@ export default {
|
|
|
3442
3470
|
return !outcome || outcome.met !== true
|
|
3443
3471
|
},
|
|
3444
3472
|
|
|
3473
|
+
/**
|
|
3474
|
+
* Whether a page control (a workspace condition on `visibleWhen`)
|
|
3475
|
+
* hides this widget right now. Other conditions are not looked at.
|
|
3476
|
+
*
|
|
3477
|
+
* @param {string} widgetId The widget id.
|
|
3478
|
+
* @return {boolean} True when the widget must not draw.
|
|
3479
|
+
* @spec openspec/changes/screens-page-lenses/specs/page-lenses/spec.md#requirement-a-widget-can-be-shown-by-a-workspace-value
|
|
3480
|
+
*/
|
|
3481
|
+
isHiddenByWorkspace(widgetId) {
|
|
3482
|
+
const def = this.getWidgetDef(widgetId)
|
|
3483
|
+
if (!def || !def.type) {
|
|
3484
|
+
return false
|
|
3485
|
+
}
|
|
3486
|
+
const { visibleWhen } = this.widgetDisplayConfig(def)
|
|
3487
|
+
return isWorkspaceCondition(visibleWhen) && !evaluateWorkspaceCondition(visibleWhen, this.workspaceContext)
|
|
3488
|
+
},
|
|
3489
|
+
|
|
3445
3490
|
/**
|
|
3446
3491
|
* Evaluate every conditional widget's `visibleWhen` into
|
|
3447
3492
|
* `widgetConditionOutcome` — the verdict AND the raw field value,
|
|
@@ -3464,7 +3509,8 @@ export default {
|
|
|
3464
3509
|
async evaluateWidgetConditions() {
|
|
3465
3510
|
const defs = [...(this.widgets || []), ...this.normalizedViews.flatMap((view) => view.widgets)]
|
|
3466
3511
|
const conditional = defs.filter((def) => def && def.id && def.type
|
|
3467
|
-
&& this.widgetDisplayConfig(def).visibleWhen
|
|
3512
|
+
&& this.widgetDisplayConfig(def).visibleWhen
|
|
3513
|
+
&& !isWorkspaceCondition(this.widgetDisplayConfig(def).visibleWhen))
|
|
3468
3514
|
// Skip when the CONDITIONAL SET is unchanged: the deep `widgets`
|
|
3469
3515
|
// watch fires on any def edit, but only an added/removed/edited
|
|
3470
3516
|
// predicate warrants refetching every conditional endpoint.
|
|
@@ -3484,8 +3530,13 @@ export default {
|
|
|
3484
3530
|
const cond = this.widgetDisplayConfig(def).visibleWhen
|
|
3485
3531
|
let outcome
|
|
3486
3532
|
try {
|
|
3487
|
-
|
|
3533
|
+
// `sink` keeps the first row of a source query, for a banner that names it.
|
|
3534
|
+
const sink = {}
|
|
3535
|
+
const value = await readVisibleWhenValue(cond, undefined, sink)
|
|
3488
3536
|
outcome = { met: compareVisibleWhen(value, cond.op || 'eq', cond.value), value }
|
|
3537
|
+
if (sink.row) {
|
|
3538
|
+
outcome.row = sink.row
|
|
3539
|
+
}
|
|
3489
3540
|
} catch (error) {
|
|
3490
3541
|
// Fail-safe: not met. But the failure is kept, and said
|
|
3491
3542
|
// out loud: a failed count and "nothing to report" look
|
|
@@ -3985,6 +4036,11 @@ export default {
|
|
|
3985
4036
|
const def = this.getWidgetDef(item.widgetId)
|
|
3986
4037
|
const value = item.showTitle !== undefined ? item.showTitle : def?.showTitle
|
|
3987
4038
|
if (value === undefined || value === null) {
|
|
4039
|
+
// A banner that names no title draws no title row: it used to show
|
|
4040
|
+
// its widget id ("attention-first") as the card title.
|
|
4041
|
+
if (this.isBannerDef(def) && !item.customTitle && !def?.customTitle && !def?.title) {
|
|
4042
|
+
return false
|
|
4043
|
+
}
|
|
3988
4044
|
return !this.isCardWidget(item)
|
|
3989
4045
|
}
|
|
3990
4046
|
return value !== false
|
|
@@ -4093,7 +4149,12 @@ export default {
|
|
|
4093
4149
|
return item.headerLink
|
|
4094
4150
|
}
|
|
4095
4151
|
const def = this.getWidgetDef(item.widgetId)
|
|
4096
|
-
|
|
4152
|
+
if (def?.headerLink && typeof def.headerLink === 'object') {
|
|
4153
|
+
return def.headerLink
|
|
4154
|
+
}
|
|
4155
|
+
// Last, what the widget's own content asks for (`headerLink`, or the
|
|
4156
|
+
// `viewAllRoute` / `viewAllLabel` of the tasks widget).
|
|
4157
|
+
return contentHeaderLink(item?.content) || contentHeaderLink(def?.content)
|
|
4097
4158
|
},
|
|
4098
4159
|
|
|
4099
4160
|
/**
|
|
@@ -4842,6 +4903,18 @@ export default {
|
|
|
4842
4903
|
.cn-dashboard-page__content-item {
|
|
4843
4904
|
width: 100%;
|
|
4844
4905
|
}
|
|
4906
|
+
/* On a phone (screens-dashboard-phone) the touch targets of a board dashboard
|
|
4907
|
+
are 44px: the widget Actions menu and the KPI tiles. */
|
|
4908
|
+
@media (max-width: 640px) {
|
|
4909
|
+
.cn-dashboard-page--board :deep(.cn-widget-wrapper__actions .action-item__menutoggle),
|
|
4910
|
+
.cn-dashboard-page--board :deep(.cn-widget-wrapper__actions .button-vue) {
|
|
4911
|
+
min-height: 44px;
|
|
4912
|
+
}
|
|
4913
|
+
|
|
4914
|
+
.cn-dashboard-page--board .cn-dashboard-page__kpi-cell {
|
|
4915
|
+
min-height: 44px;
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4845
4918
|
</style>
|
|
4846
4919
|
|
|
4847
4920
|
<!-- Unscoped, anchored on the stable `data-testid` NcActions renders on both
|
|
@@ -321,6 +321,7 @@
|
|
|
321
321
|
:formatterOptions="col.formatterOptions || null"
|
|
322
322
|
:widget="col.widget || null"
|
|
323
323
|
:widgetProps="col.widgetProps || undefined"
|
|
324
|
+
:secondary="col.widget === 'avatar' ? secondaryValue(row, col) : ''"
|
|
324
325
|
:format="columnFormat(col)"
|
|
325
326
|
:row="row"
|
|
326
327
|
:rowKey="rowKey" />
|
|
@@ -330,7 +331,7 @@
|
|
|
330
331
|
of the row. Lets one column read "title" over "number ·
|
|
331
332
|
requester" without a custom cell. -->
|
|
332
333
|
<span
|
|
333
|
-
v-if="secondaryValue(row, col)"
|
|
334
|
+
v-if="col.widget !== 'avatar' && secondaryValue(row, col)"
|
|
334
335
|
class="cn-table-cell__secondary"
|
|
335
336
|
data-testid="cn-cell-secondary">{{ secondaryValue(row, col) }}</span>
|
|
336
337
|
<!-- A row that entered a file-content search through an attached
|
|
@@ -1765,17 +1766,6 @@ export default {
|
|
|
1765
1766
|
return (col && typeof col.format === 'object') ? col.format : null
|
|
1766
1767
|
},
|
|
1767
1768
|
|
|
1768
|
-
/**
|
|
1769
|
-
* Value to render in a cell. For a column that declares `aggregate`
|
|
1770
|
-
* (a count of related objects), returns the cached count once
|
|
1771
|
-
* `loadAggregates()` has resolved it (or `'…'` while pending, `'—'`
|
|
1772
|
-
* if it failed / there's nothing to count). Otherwise the row's
|
|
1773
|
-
* property value via `getCellValue`.
|
|
1774
|
-
*
|
|
1775
|
-
* @param {object} row The row data.
|
|
1776
|
-
* @param {object} col The column definition.
|
|
1777
|
-
* @return {unknown} The value handed to the slot / CnCellRenderer.
|
|
1778
|
-
*/
|
|
1779
1769
|
/**
|
|
1780
1770
|
* The secondary line of a cell, from `col.secondary`: a function is
|
|
1781
1771
|
* called with the row; a string with `{field}` placeholders is a
|
|
@@ -1806,6 +1796,21 @@ export default {
|
|
|
1806
1796
|
return asText(this.getCellValue(row, spec))
|
|
1807
1797
|
},
|
|
1808
1798
|
|
|
1799
|
+
/**
|
|
1800
|
+
* Value to render in a cell. For a column that declares `aggregate`
|
|
1801
|
+
* (a count of related objects), returns the cached count once
|
|
1802
|
+
* `loadAggregates()` has resolved it (or `'…'` while pending, `'—'`
|
|
1803
|
+
* if it failed / there's nothing to count). A count of zero reads as
|
|
1804
|
+
* `aggregate.zeroLabel` when the column sets one ("None", which the
|
|
1805
|
+
* app's `cnTranslate` turns into "Geen" as the boards draw it); without
|
|
1806
|
+
* it the cell shows `0` as before. Otherwise the row's property value
|
|
1807
|
+
* via `getCellValue`.
|
|
1808
|
+
*
|
|
1809
|
+
* @spec openspec/changes/aggregate-zero-label/specs/aggregate-zero-label/spec.md#requirement-a-count-column-can-name-zero
|
|
1810
|
+
* @param {object} row The row data.
|
|
1811
|
+
* @param {object} col The column definition.
|
|
1812
|
+
* @return {unknown} The value handed to the slot / CnCellRenderer.
|
|
1813
|
+
*/
|
|
1809
1814
|
cellValue(row, col) {
|
|
1810
1815
|
if (col && col.aggregate) {
|
|
1811
1816
|
const cached = this.aggregateValues[String(row[this.rowKey])]
|
|
@@ -1813,6 +1818,10 @@ export default {
|
|
|
1813
1818
|
if (v === undefined) {
|
|
1814
1819
|
return this.aggregateLoading ? '…' : '—'
|
|
1815
1820
|
}
|
|
1821
|
+
const zeroLabel = col.aggregate.zeroLabel
|
|
1822
|
+
if ((v === 0 || v === '0') && typeof zeroLabel === 'string' && zeroLabel !== '') {
|
|
1823
|
+
return typeof this.cnTranslate === 'function' ? this.cnTranslate(zeroLabel) : zeroLabel
|
|
1824
|
+
}
|
|
1816
1825
|
return v
|
|
1817
1826
|
}
|
|
1818
1827
|
return this.getCellValue(row, col.key)
|
|
@@ -379,7 +379,7 @@
|
|
|
379
379
|
:showDocumentation="showDocumentation && menuShowsHelpLinks"
|
|
380
380
|
:actionsMenuLabel="boardMenuLabel"
|
|
381
381
|
:variant="isBoard ? 'secondary' : undefined"
|
|
382
|
-
:hasPrimaryItems="headerMenuGroups.length > 0 || showMarkUnread"
|
|
382
|
+
:hasPrimaryItems="headerMenuGroups.length > 0 || showMarkUnread || showOpenSidebar"
|
|
383
383
|
:documentationUrl="documentationUrl"
|
|
384
384
|
:docsAnchor="resolvedPageId"
|
|
385
385
|
:documentationLabel="documentationLabel || undefined"
|
|
@@ -390,7 +390,7 @@
|
|
|
390
390
|
testidBase="cn-detail-page"
|
|
391
391
|
@refresh="onHeaderRefresh"
|
|
392
392
|
@requestFeature="onHeaderRequestFeature">
|
|
393
|
-
<template v-if="headerMenuGroups.length || showMarkUnread" #primary-items>
|
|
393
|
+
<template v-if="headerMenuGroups.length || showMarkUnread || showOpenSidebar" #primary-items>
|
|
394
394
|
<!-- One block per group. A page that groups nothing has
|
|
395
395
|
a single captionless group, and renders exactly the
|
|
396
396
|
flat list it always did. -->
|
|
@@ -449,6 +449,20 @@
|
|
|
449
449
|
</template>
|
|
450
450
|
{{ markUnreadLabel }}
|
|
451
451
|
</NcActionButton>
|
|
452
|
+
<!-- Board look: the sidebar's own open toggle is hidden (it sat
|
|
453
|
+
over the header's last button, where the board draws none), so
|
|
454
|
+
the menu opens it. -->
|
|
455
|
+
<NcActionButton
|
|
456
|
+
v-if="showOpenSidebar"
|
|
457
|
+
key="open-sidebar"
|
|
458
|
+
data-testid="cn-detail-page-open-sidebar"
|
|
459
|
+
:closeAfterClick="true"
|
|
460
|
+
@click="openSidebar">
|
|
461
|
+
<template #icon>
|
|
462
|
+
<DockRight :size="20" />
|
|
463
|
+
</template>
|
|
464
|
+
{{ openSidebarLabel }}
|
|
465
|
+
</NcActionButton>
|
|
452
466
|
<!-- Divides the page's own actions from the help links
|
|
453
467
|
below. With the help links switched off there is
|
|
454
468
|
nothing below to divide from. -->
|
|
@@ -745,7 +759,7 @@
|
|
|
745
759
|
:layout="section.layout"
|
|
746
760
|
:editable="section.editable"
|
|
747
761
|
:columns="12"
|
|
748
|
-
:columnOpts="
|
|
762
|
+
:columnOpts="bodyColumnOpts"
|
|
749
763
|
class="cn-detail-page__grid"
|
|
750
764
|
@layoutChange="section.view ? onViewLayoutChange($event) : onBodyLayoutChange($event)">
|
|
751
765
|
<template #widget="{ item }">
|
|
@@ -1191,6 +1205,7 @@ import Check from 'vue-material-design-icons/Check.vue'
|
|
|
1191
1205
|
import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'
|
|
1192
1206
|
import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
|
|
1193
1207
|
import Cog from 'vue-material-design-icons/Cog.vue'
|
|
1208
|
+
import DockRight from 'vue-material-design-icons/DockRight.vue'
|
|
1194
1209
|
import EyeOffOutline from 'vue-material-design-icons/EyeOffOutline.vue'
|
|
1195
1210
|
import FileQuestionOutline from 'vue-material-design-icons/FileQuestionOutline.vue'
|
|
1196
1211
|
import InformationOutline from 'vue-material-design-icons/InformationOutline.vue'
|
|
@@ -1295,6 +1310,35 @@ const DEFAULT_DETAIL_COLUMN_OPTS = Object.freeze({
|
|
|
1295
1310
|
breakpointForWindow: false,
|
|
1296
1311
|
})
|
|
1297
1312
|
|
|
1313
|
+
/**
|
|
1314
|
+
* Where the default table stacks when a side column sits beside the body grid.
|
|
1315
|
+
*
|
|
1316
|
+
* The side column takes about 320px, so the grid it leaves is far narrower than
|
|
1317
|
+
* the page: 790px at a 1100px window. Stacking that at 1000px made a row of
|
|
1318
|
+
* small tiles (a KPI row of 5 or 6 stat tiles) impossible beside a side column.
|
|
1319
|
+
* 1000px exists to keep a `gridWidth: 3` side PANEL inside the grid readable; a
|
|
1320
|
+
* page with a real side column has no such panel, and 12 columns at 600px is
|
|
1321
|
+
* still 50px a column, enough for a tile. Below 600px it stacks as before.
|
|
1322
|
+
*/
|
|
1323
|
+
const SIDE_COLUMN_STACK_WIDTH = 600
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* Whether `opts` is the untouched default table, so a page that passed its own
|
|
1327
|
+
* `columnOpts` keeps exactly what it asked for.
|
|
1328
|
+
*
|
|
1329
|
+
* @param {object|null} opts The `columnOpts` value.
|
|
1330
|
+
* @return {boolean}
|
|
1331
|
+
*/
|
|
1332
|
+
function isDefaultColumnOpts(opts) {
|
|
1333
|
+
const breakpoints = opts && opts.breakpoints
|
|
1334
|
+
return !!opts
|
|
1335
|
+
&& opts.columnMax === DEFAULT_DETAIL_COLUMN_OPTS.columnMax
|
|
1336
|
+
&& Array.isArray(breakpoints)
|
|
1337
|
+
&& breakpoints.length === 1
|
|
1338
|
+
&& breakpoints[0].w === DEFAULT_DETAIL_COLUMN_OPTS.breakpoints[0].w
|
|
1339
|
+
&& breakpoints[0].c === 1
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1298
1342
|
/**
|
|
1299
1343
|
* Event-bus channel a page-level refresh is announced on. The page's own
|
|
1300
1344
|
* Actions menu emits it, the manifest `refresh` / `api-call` actions bump it,
|
|
@@ -1411,6 +1455,7 @@ export default {
|
|
|
1411
1455
|
CnSummaryAggregates,
|
|
1412
1456
|
CnDetailHeaderChips,
|
|
1413
1457
|
CnFollowToggle,
|
|
1458
|
+
DockRight,
|
|
1414
1459
|
EyeOffOutline,
|
|
1415
1460
|
CnRelatedCollections,
|
|
1416
1461
|
CnBodySections,
|
|
@@ -3335,6 +3380,20 @@ export default {
|
|
|
3335
3380
|
.map(({ widget }) => widget)
|
|
3336
3381
|
},
|
|
3337
3382
|
|
|
3383
|
+
/**
|
|
3384
|
+
* The `columnOpts` the body grid gets: the default table stacks at 1000px
|
|
3385
|
+
* of grid width, or at 600px when a side column narrows the grid. An
|
|
3386
|
+
* explicit `columnOpts` prop is passed through untouched.
|
|
3387
|
+
*
|
|
3388
|
+
* @return {object|null} The column options.
|
|
3389
|
+
*/
|
|
3390
|
+
bodyColumnOpts() {
|
|
3391
|
+
if (!this.showsSideColumn || !isDefaultColumnOpts(this.columnOpts)) {
|
|
3392
|
+
return this.columnOpts
|
|
3393
|
+
}
|
|
3394
|
+
return { ...this.columnOpts, breakpoints: [{ w: SIDE_COLUMN_STACK_WIDTH, c: 1 }] }
|
|
3395
|
+
},
|
|
3396
|
+
|
|
3338
3397
|
/**
|
|
3339
3398
|
* Whether the side column renders.
|
|
3340
3399
|
*
|
|
@@ -3866,6 +3925,22 @@ export default {
|
|
|
3866
3925
|
&& this.register !== '' && this.schema !== '' && this.objectId !== ''
|
|
3867
3926
|
},
|
|
3868
3927
|
|
|
3928
|
+
/**
|
|
3929
|
+
* Whether the header menu offers "Open sidebar": the board look, where the
|
|
3930
|
+
* sidebar's own toggle is hidden, on a page whose sidebar is active and
|
|
3931
|
+
* closed.
|
|
3932
|
+
*
|
|
3933
|
+
* @return {boolean}
|
|
3934
|
+
*/
|
|
3935
|
+
showOpenSidebar() {
|
|
3936
|
+
return this.isBoard && this.hasExternalSidebar && this.sidebarActive && this.objectSidebarState.open !== true
|
|
3937
|
+
},
|
|
3938
|
+
|
|
3939
|
+
/** @return {string} Label of the Open sidebar entry. */
|
|
3940
|
+
openSidebarLabel() {
|
|
3941
|
+
return t('nextcloud-vue', 'Open sidebar')
|
|
3942
|
+
},
|
|
3943
|
+
|
|
3869
3944
|
/** @return {string} Label of the Mark as unread entry. */
|
|
3870
3945
|
markUnreadLabel() {
|
|
3871
3946
|
return t('nextcloud-vue', 'Mark as unread')
|
|
@@ -4576,6 +4651,17 @@ export default {
|
|
|
4576
4651
|
},
|
|
4577
4652
|
|
|
4578
4653
|
methods: {
|
|
4654
|
+
/**
|
|
4655
|
+
* Open the object sidebar through the shared channel.
|
|
4656
|
+
*
|
|
4657
|
+
* @return {void}
|
|
4658
|
+
*/
|
|
4659
|
+
openSidebar() {
|
|
4660
|
+
if (this.objectSidebarState) {
|
|
4661
|
+
this.objectSidebarState.open = true
|
|
4662
|
+
}
|
|
4663
|
+
},
|
|
4664
|
+
|
|
4579
4665
|
/**
|
|
4580
4666
|
* Start or finish arranging the chosen view. Finishing stores the
|
|
4581
4667
|
* arrangement of every view the user moved, once.
|
|
@@ -31,7 +31,7 @@ import { readPath } from '../../utils/readPath.js'
|
|
|
31
31
|
* Renders CnDocumentReviewList from a list on the bound record:
|
|
32
32
|
* `content: { field, title?, statusField?, statuses?, rowAction?: { label, route } }`.
|
|
33
33
|
* A row action with a `route` opens that route with the row's key as `id`.
|
|
34
|
-
* Resolved by its registry key,
|
|
34
|
+
* Resolved by its registry key, and exported for an app that renders it itself.
|
|
35
35
|
*/
|
|
36
36
|
export default {
|
|
37
37
|
name: 'CnDocumentReviewWidget',
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
<script>
|
|
63
63
|
import { translate as t } from '@nextcloud/l10n'
|
|
64
64
|
import { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextField } from '@nextcloud/vue'
|
|
65
|
-
import {
|
|
65
|
+
import { facetOptions } from '../../utils/facets.js'
|
|
66
66
|
import { filtersFromSchema } from '../../utils/schema.js'
|
|
67
67
|
|
|
68
68
|
/**
|
|
@@ -175,12 +175,12 @@ export default {
|
|
|
175
175
|
|
|
176
176
|
getFilterOptions(filter) {
|
|
177
177
|
// Use facet data if available (live values with counts)
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
178
|
+
const live = facetOptions(
|
|
179
|
+
this.facetData[filter.key],
|
|
180
|
+
(count) => t('nextcloud-vue', 'No value ({count})', { count }),
|
|
181
|
+
)
|
|
182
|
+
if (live !== null) {
|
|
183
|
+
return live
|
|
184
184
|
}
|
|
185
185
|
// Fall back to static enum options from schema
|
|
186
186
|
return filter.options || []
|
|
@@ -140,6 +140,7 @@ import EyeOutline from 'vue-material-design-icons/EyeOutline.vue'
|
|
|
140
140
|
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
141
141
|
import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
|
|
142
142
|
import { resolveEndpointRequest } from '../../composables/useEndpointSource.js'
|
|
143
|
+
import { useRefLabels } from '../../composables/useRefLabels.js'
|
|
143
144
|
import { dispatchAction } from '../../utils/actionsDispatcher.js'
|
|
144
145
|
import { isBadgeVariant } from '../../utils/badgeVariants.js'
|
|
145
146
|
import { formatBoardDate } from '../../utils/boardDate.js'
|
|
@@ -150,6 +151,35 @@ import { hasUnresolvedDeepTokens, resolveDeepTokens } from '../../utils/resolveF
|
|
|
150
151
|
/** Kinds of entry a `fields[]` item can be. A labelled item without `as` is a row. */
|
|
151
152
|
const KINDS = Object.freeze(['row', 'title', 'line', 'pill', 'lead', 'text'])
|
|
152
153
|
|
|
154
|
+
/**
|
|
155
|
+
* The label resolver every facts widget on the page shares, made on first use
|
|
156
|
+
* (the object store needs an active pinia). One batched request per register and
|
|
157
|
+
* schema for the ids it has not seen; a label it has read stays until the page
|
|
158
|
+
* reloads, and an object the store already holds is not fetched at all.
|
|
159
|
+
*
|
|
160
|
+
* @type {{resolve: Function}|null}
|
|
161
|
+
*/
|
|
162
|
+
let refLabels = null
|
|
163
|
+
|
|
164
|
+
/** The page-wide reference label resolver. */
|
|
165
|
+
function sharedRefLabels() {
|
|
166
|
+
if (!refLabels) {
|
|
167
|
+
refLabels = useRefLabels()
|
|
168
|
+
}
|
|
169
|
+
return refLabels
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Key of a resolved reference in the widget's `labels` data.
|
|
174
|
+
*
|
|
175
|
+
* @param {{register: string, schema: string, labelField?: string}} resolve The entry's `resolve` config.
|
|
176
|
+
* @param {string} id The referenced object's id.
|
|
177
|
+
* @return {string} The key.
|
|
178
|
+
*/
|
|
179
|
+
function refKey(resolve, id) {
|
|
180
|
+
return `${resolve.register}/${resolve.schema}/${resolve.labelField || ''}/${id}`
|
|
181
|
+
}
|
|
182
|
+
|
|
153
183
|
/** Event-bus channel a reveal of a masked value is announced on, so an app can log it. */
|
|
154
184
|
export const FACTS_REVEAL_CHANNEL = 'cn:facts:reveal'
|
|
155
185
|
|
|
@@ -213,6 +243,7 @@ export default {
|
|
|
213
243
|
* `as` is `row` (default when `label` or `icon` is set), `title`, `line`, `text` (a line in the normal ink), `lead` (an icon tile or initials beside `title` and `subtitle`) or `pill`.
|
|
214
244
|
* A row's `format` may also be `email`, `phone` or `url` (a link) or `masked` (the last four characters; `reveal: true` adds Show). `copy: true` adds a copy button; `icon` replaces the label.
|
|
215
245
|
* A part may be `{ text }`, a constant; a `line` or `text` entry without `path` or `parts` shows its `text`.
|
|
246
|
+
* `resolve: { register, schema, labelField? }` on an entry or a part turns a value that is a reference (an id, or a list of ids) into the label of the referenced object (`labelField`, else `title`, then `name`): the case type or status of a task shows its name, not its uuid. The ids are fetched once per register and schema through the object store and shared by every facts widget on the page; the entry stays empty until its label is known, and when no label can be read.
|
|
216
247
|
* - `action` — an action drawn as a button under the facts; `headerAction` — one drawn in the card header (reported through the `header` event).
|
|
217
248
|
* `format` is `text` (default), `date` (board short date), `date-long` (always with the year) or `list` (an array joined by ", ", each item read at `itemPath`).
|
|
218
249
|
* `map` turns a raw value into a label (translated). `parts` (on a `line`) lists paths, or `{ path, label?, format? }`, joined by `separator` (default " · "); empty parts are dropped.
|
|
@@ -255,6 +286,8 @@ export default {
|
|
|
255
286
|
revealed: {},
|
|
256
287
|
/** Key of the row whose value was just copied. */
|
|
257
288
|
copied: '',
|
|
289
|
+
/** Labels of the referenced objects read so far, by `refKey`. */
|
|
290
|
+
labels: {},
|
|
258
291
|
}
|
|
259
292
|
},
|
|
260
293
|
|
|
@@ -430,6 +463,13 @@ export default {
|
|
|
430
463
|
},
|
|
431
464
|
},
|
|
432
465
|
|
|
466
|
+
record: {
|
|
467
|
+
immediate: true,
|
|
468
|
+
handler() {
|
|
469
|
+
this.loadLabels()
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
|
|
433
473
|
headerAction: {
|
|
434
474
|
immediate: true,
|
|
435
475
|
handler() {
|
|
@@ -668,6 +708,94 @@ export default {
|
|
|
668
708
|
})
|
|
669
709
|
},
|
|
670
710
|
|
|
711
|
+
/**
|
|
712
|
+
* The text of a reference: the labels of the objects its id(s) name, joined
|
|
713
|
+
* by ", ". An id whose label is not known yet (or cannot be read) adds nothing.
|
|
714
|
+
*
|
|
715
|
+
* @param {unknown} raw The id, or a list of ids (or of `{ id }` objects).
|
|
716
|
+
* @param {object} spec `{ resolve: { register, schema, labelField? }, itemPath? }`.
|
|
717
|
+
* @return {string}
|
|
718
|
+
*/
|
|
719
|
+
refText(raw, spec) {
|
|
720
|
+
return this.refIds(raw, spec)
|
|
721
|
+
.map((id) => this.labels[refKey(spec.resolve, id)])
|
|
722
|
+
.filter((label) => typeof label === 'string' && label !== '')
|
|
723
|
+
.join(', ')
|
|
724
|
+
},
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* The ids a reference value holds.
|
|
728
|
+
*
|
|
729
|
+
* @param {unknown} raw The value.
|
|
730
|
+
* @param {object} spec `{ itemPath? }`.
|
|
731
|
+
* @return {string[]}
|
|
732
|
+
*/
|
|
733
|
+
refIds(raw, spec) {
|
|
734
|
+
return (Array.isArray(raw) ? raw : [raw])
|
|
735
|
+
.map((item) => (item && typeof item === 'object' ? (spec.itemPath ? readPath(item, spec.itemPath) : (item.id ?? item.uuid)) : item))
|
|
736
|
+
.filter((id) => id !== null && id !== undefined && id !== '' && typeof id !== 'object')
|
|
737
|
+
.map(String)
|
|
738
|
+
},
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Every entry or part that carries `resolve`, with the record's value at its path.
|
|
742
|
+
*
|
|
743
|
+
* @param {object} record The object.
|
|
744
|
+
* @return {Array<{spec: object, raw: unknown}>}
|
|
745
|
+
*/
|
|
746
|
+
refSpecs(record) {
|
|
747
|
+
const fields = Array.isArray(this.content?.fields) ? this.content.fields : []
|
|
748
|
+
const specs = []
|
|
749
|
+
const visit = (spec) => {
|
|
750
|
+
if (!spec || typeof spec !== 'object') {
|
|
751
|
+
return
|
|
752
|
+
}
|
|
753
|
+
if (spec.resolve && typeof spec.resolve === 'object' && spec.path) {
|
|
754
|
+
specs.push({ spec, raw: readPath(record, spec.path) })
|
|
755
|
+
}
|
|
756
|
+
const nested = [...(Array.isArray(spec.parts) ? spec.parts : []), ...(Array.isArray(spec.title) ? spec.title : [spec.title]), ...(Array.isArray(spec.subtitle) ? spec.subtitle : [spec.subtitle])]
|
|
757
|
+
nested.forEach(visit)
|
|
758
|
+
}
|
|
759
|
+
fields.forEach(visit)
|
|
760
|
+
return specs
|
|
761
|
+
},
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Read the labels of the referenced objects, one batched request per
|
|
765
|
+
* register and schema, and keep them in `labels`.
|
|
766
|
+
*
|
|
767
|
+
* @param {object|null} record The object shown (defaults to the current record).
|
|
768
|
+
* @return {Promise<void>}
|
|
769
|
+
*/
|
|
770
|
+
async loadLabels(record = this.record) {
|
|
771
|
+
if (!record) {
|
|
772
|
+
return
|
|
773
|
+
}
|
|
774
|
+
const groups = new Map()
|
|
775
|
+
for (const { spec, raw } of this.refSpecs(record)) {
|
|
776
|
+
const { register, schema, labelField } = spec.resolve
|
|
777
|
+
if (!register || !schema) {
|
|
778
|
+
continue
|
|
779
|
+
}
|
|
780
|
+
const groupKey = `${register}/${schema}/${labelField || ''}`
|
|
781
|
+
const group = groups.get(groupKey) || { resolve: spec.resolve, ids: new Set() }
|
|
782
|
+
this.refIds(raw, spec).forEach((id) => group.ids.add(id))
|
|
783
|
+
groups.set(groupKey, group)
|
|
784
|
+
}
|
|
785
|
+
if (groups.size === 0) {
|
|
786
|
+
return
|
|
787
|
+
}
|
|
788
|
+
const resolver = sharedRefLabels()
|
|
789
|
+
const next = { ...this.labels }
|
|
790
|
+
await Promise.all([...groups.values()].map(async ({ resolve, ids }) => {
|
|
791
|
+
const found = await resolver.resolve(resolve.register, resolve.schema, [...ids], resolve.labelField)
|
|
792
|
+
Object.entries(found).forEach(([id, label]) => {
|
|
793
|
+
next[refKey(resolve, id)] = label
|
|
794
|
+
})
|
|
795
|
+
}))
|
|
796
|
+
this.labels = next
|
|
797
|
+
},
|
|
798
|
+
|
|
671
799
|
/**
|
|
672
800
|
* Read the source object.
|
|
673
801
|
*
|
|
@@ -774,6 +902,9 @@ export default {
|
|
|
774
902
|
if (raw === null || raw === undefined || raw === '') {
|
|
775
903
|
return ''
|
|
776
904
|
}
|
|
905
|
+
if (spec.resolve && typeof spec.resolve === 'object') {
|
|
906
|
+
return this.refText(raw, spec)
|
|
907
|
+
}
|
|
777
908
|
const format = spec.format || 'text'
|
|
778
909
|
if (format === 'list' || Array.isArray(raw)) {
|
|
779
910
|
const items = (Array.isArray(raw) ? raw : [raw])
|
|
@@ -887,12 +1018,21 @@ export default {
|
|
|
887
1018
|
display: contents;
|
|
888
1019
|
}
|
|
889
1020
|
|
|
890
|
-
|
|
1021
|
+
/* Nextcloud core styles `dt` and `dd` (text-align end, 12px padding), which
|
|
1022
|
+
pushed the label to the right of its column and added 12px around both. The
|
|
1023
|
+
class is doubled so these resets outrank that rule, and a dossiq-style
|
|
1024
|
+
`:deep()` override in an app is not needed. */
|
|
1025
|
+
.cn-facts-widget__label.cn-facts-widget__label {
|
|
1026
|
+
margin: 0;
|
|
1027
|
+
padding: 0;
|
|
1028
|
+
text-align: start;
|
|
891
1029
|
color: var(--color-text-maxcontrast);
|
|
892
1030
|
}
|
|
893
1031
|
|
|
894
|
-
.cn-facts-widget__value {
|
|
1032
|
+
.cn-facts-widget__value.cn-facts-widget__value {
|
|
895
1033
|
margin: 0;
|
|
1034
|
+
padding: 0;
|
|
1035
|
+
text-align: start;
|
|
896
1036
|
font-weight: 500;
|
|
897
1037
|
min-width: 0;
|
|
898
1038
|
overflow-wrap: anywhere;
|