@conduction/nextcloud-vue 2.56.0-beta.30 → 2.56.0-beta.32
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
package/src/css/look-board.css
CHANGED
|
@@ -555,3 +555,75 @@
|
|
|
555
555
|
.cn-look-board .cn-cell-renderer__age:not(.cn-look-nextcloud *) {
|
|
556
556
|
font-weight: 600;
|
|
557
557
|
}
|
|
558
|
+
|
|
559
|
+
/* ---- Record list on a dashboard card (PtDashboard) ---------------------- */
|
|
560
|
+
/* The dashboard's card body is flush, so the rows carry their own inset: the
|
|
561
|
+
board's card pads 24px at the sides and each row 14px, a soft hairline
|
|
562
|
+
between rows, the title 15px weight 600 and the meta 13px muted. A row is a
|
|
563
|
+
link, but only the hover says so: no underline on any text run, however the
|
|
564
|
+
host styles its links (`!important`, because a host rule such as
|
|
565
|
+
`#content a` outranks any class). The pill and the date sit at the right. */
|
|
566
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list {
|
|
567
|
+
padding: 0 24px 10px;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list--rows .cn-record-list__item + .cn-record-list__item {
|
|
571
|
+
border-top: 1px solid var(--color-border-dark, var(--color-border));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row {
|
|
575
|
+
padding: 14px 0;
|
|
576
|
+
flex-wrap: wrap;
|
|
577
|
+
gap: 8px 16px;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row,
|
|
581
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row:hover,
|
|
582
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row:focus-visible,
|
|
583
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row * {
|
|
584
|
+
color: inherit;
|
|
585
|
+
text-decoration: none !important;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__title {
|
|
589
|
+
color: var(--color-main-text);
|
|
590
|
+
font-size: 15px;
|
|
591
|
+
font-weight: 600;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__meta {
|
|
595
|
+
color: var(--color-text-maxcontrast);
|
|
596
|
+
font-size: 13px;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__trailing {
|
|
600
|
+
color: var(--color-text-maxcontrast);
|
|
601
|
+
font-size: 13px;
|
|
602
|
+
font-weight: 600;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/* Hover: the whole row takes the hover ground, 8px beyond the text so the
|
|
606
|
+
text does not move. */
|
|
607
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link {
|
|
608
|
+
margin-inline: -8px;
|
|
609
|
+
padding-inline: 8px;
|
|
610
|
+
border-radius: 8px;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link:hover,
|
|
614
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link:focus-visible {
|
|
615
|
+
background: var(--color-background-hover);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link:hover .cn-record-list__title {
|
|
619
|
+
text-decoration: none !important;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__aside {
|
|
623
|
+
margin-inline-start: auto;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/* The board draws no chevron after the pill and date. */
|
|
627
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__chevron {
|
|
628
|
+
display: none;
|
|
629
|
+
}
|
package/src/index.js
CHANGED
|
@@ -43,6 +43,8 @@ export {
|
|
|
43
43
|
CnAppNav,
|
|
44
44
|
CnAppRoot,
|
|
45
45
|
CnAuditTrailCard,
|
|
46
|
+
CnAuditTrailWidget,
|
|
47
|
+
CnBannerWidget,
|
|
46
48
|
CnBoardView,
|
|
47
49
|
CnBodySections,
|
|
48
50
|
CnBreadcrumbs,
|
|
@@ -58,7 +60,9 @@ export {
|
|
|
58
60
|
CnChartWidgetForm,
|
|
59
61
|
CnChatPage,
|
|
60
62
|
CnChildRecordsField,
|
|
63
|
+
CnChipsFieldWidget,
|
|
61
64
|
CnChoiceCards,
|
|
65
|
+
CnChoicePickerWidget,
|
|
62
66
|
CnColorPicker,
|
|
63
67
|
CnCommandPalette,
|
|
64
68
|
CnConfigurationCard,
|
|
@@ -70,6 +74,7 @@ export {
|
|
|
70
74
|
CnContainerWidgetForm,
|
|
71
75
|
CnContextMenu,
|
|
72
76
|
CnConversationThread,
|
|
77
|
+
CnConversationWidget,
|
|
73
78
|
CnCopyDialog,
|
|
74
79
|
CnCountdownWidget,
|
|
75
80
|
CnCountdownWidgetForm,
|
|
@@ -97,6 +102,7 @@ export {
|
|
|
97
102
|
CnDividerWidget,
|
|
98
103
|
CnDividerWidgetForm,
|
|
99
104
|
CnDocumentReviewList,
|
|
105
|
+
CnDocumentReviewWidget,
|
|
100
106
|
CnDurationField,
|
|
101
107
|
CnEditActionsModal,
|
|
102
108
|
CnEditDataModal,
|
|
@@ -164,11 +170,13 @@ export {
|
|
|
164
170
|
CnIntegrationTab,
|
|
165
171
|
CnIntegrationWidget,
|
|
166
172
|
CnIntegrationWidgetGrid,
|
|
173
|
+
CnInteractionFormWidget,
|
|
167
174
|
CnItemCard,
|
|
168
175
|
CnJourney,
|
|
169
176
|
CnJourneyDialog,
|
|
170
177
|
CnJourneyReviewList,
|
|
171
178
|
CnJsonViewer,
|
|
179
|
+
CnKbSearchWidget,
|
|
172
180
|
CnKpiGrid,
|
|
173
181
|
CnLabelWidget,
|
|
174
182
|
CnLabelWidgetForm,
|
|
@@ -200,6 +208,7 @@ export {
|
|
|
200
208
|
CnNcWidgetGridPicker,
|
|
201
209
|
CnNcWidgetWidget,
|
|
202
210
|
CnNextStepCard,
|
|
211
|
+
CnNextStepWidget,
|
|
203
212
|
CnNoteBody,
|
|
204
213
|
CnNoteCard,
|
|
205
214
|
CnNoteComposer,
|
|
@@ -238,6 +247,7 @@ export {
|
|
|
238
247
|
CnQuickFilterBar,
|
|
239
248
|
CnQuicklinksWidget,
|
|
240
249
|
CnQuicklinksWidgetForm,
|
|
250
|
+
CnRadioCardsWidget,
|
|
241
251
|
CnRecordListWidget,
|
|
242
252
|
CnReferencePreview,
|
|
243
253
|
CnRegisterMapping,
|
|
@@ -256,6 +266,7 @@ export {
|
|
|
256
266
|
CnSavedViewsControl,
|
|
257
267
|
CnSavedViewShareDialog,
|
|
258
268
|
CnSavedViewShareFields,
|
|
269
|
+
CnSavedViewsWidget,
|
|
259
270
|
CnSavedViewWidget,
|
|
260
271
|
CnSavedViewWidgetForm,
|
|
261
272
|
CnSaveViewDialog,
|
|
@@ -263,6 +274,7 @@ export {
|
|
|
263
274
|
CnSearchPage,
|
|
264
275
|
CnSectionsWidget,
|
|
265
276
|
CnSettingsCard,
|
|
277
|
+
CnSettingsFormWidget,
|
|
266
278
|
CnSettingsPage,
|
|
267
279
|
CnSettingsSection,
|
|
268
280
|
CnSetupWizard,
|
|
@@ -282,6 +294,7 @@ export {
|
|
|
282
294
|
CnSubObjectsField,
|
|
283
295
|
CnSummaryAggregates,
|
|
284
296
|
CnSupportDialog,
|
|
297
|
+
CnSwitchRowsWidget,
|
|
285
298
|
CnTab,
|
|
286
299
|
CnTabbedFormDialog,
|
|
287
300
|
CnTableWidget,
|
|
@@ -302,6 +315,7 @@ export {
|
|
|
302
315
|
CnTimelineStages,
|
|
303
316
|
CnTimelineView,
|
|
304
317
|
CnTimelineWidget,
|
|
318
|
+
CnToggleTableWidget,
|
|
305
319
|
CnTranslatedBadge,
|
|
306
320
|
CnTreeView,
|
|
307
321
|
CnUnreadMarker,
|
|
@@ -324,6 +338,7 @@ export {
|
|
|
324
338
|
CnWidgetVisibilityRulesModal,
|
|
325
339
|
CnWidgetWrapper,
|
|
326
340
|
CnWizardDialog,
|
|
341
|
+
CnWorkspaceFilterWidget,
|
|
327
342
|
DEFAULT_DATE_RANGE_PRESETS,
|
|
328
343
|
defaultPageTypes,
|
|
329
344
|
fetchWebdavFolderTree,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"$id": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json",
|
|
4
4
|
"title": "Conduction App Manifest v2",
|
|
5
5
|
"description": "v2 schema for the JSON-driven page and navigation manifest consumed by @conduction/nextcloud-vue. Introduces a uniform widgets[] array on every page type with a per-slot grid coordinate system, a typed actions[] discriminator, and the required $schema field for version detection. v1 manifests continue to validate against app-manifest.schema.json (unchanged).",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.89.0",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"required": ["$schema", "version"],
|
|
9
9
|
"allOf": [
|
|
@@ -803,6 +803,16 @@
|
|
|
803
803
|
"pattern": "^@answer\\.[A-Za-z][A-Za-z0-9_]*$",
|
|
804
804
|
"description": "form-live-values: another answer in the same form (@answer.<field>), read by a form field's assign rule and resolved client-side by CnFormPage / CnFormDialog. Mirrors SENTINEL_TOKEN_PATTERNS.answer."
|
|
805
805
|
},
|
|
806
|
+
"sentinelRowToken": {
|
|
807
|
+
"type": "string",
|
|
808
|
+
"pattern": "^@row\\.[A-Za-z][A-Za-z0-9_.@]*$",
|
|
809
|
+
"description": "Row context: a field of the row a row action runs on (@row.<field>, dot path allowed), read by the field-list widget's rowActions request url and payload (payload values resolve at any depth). Mirrors SENTINEL_TOKEN_PATTERNS.row."
|
|
810
|
+
},
|
|
811
|
+
"sentinelSubjectToken": {
|
|
812
|
+
"type": "string",
|
|
813
|
+
"pattern": "^@subject\\.[A-Za-z][A-Za-z0-9_.]*$",
|
|
814
|
+
"description": "Subject context: a field of the record an attention card names (the first row of its ordered `visibleWhen` source query), read by the banner widget's action route params and query (@subject.<field>, dot path allowed). Mirrors SENTINEL_TOKEN_PATTERNS.subject."
|
|
815
|
+
},
|
|
806
816
|
"sentinelTokenAny": {
|
|
807
817
|
"$comment": "Union of every canonical context plus the deprecated-during-window overlay. Single source the shared resolver + gate import.",
|
|
808
818
|
"anyOf": [
|
|
@@ -814,6 +824,8 @@
|
|
|
814
824
|
{ "$ref": "#/$defs/sentinelDeclarativeToken" },
|
|
815
825
|
{ "$ref": "#/$defs/sentinelVisibleWhenToken" },
|
|
816
826
|
{ "$ref": "#/$defs/sentinelAnswerToken" },
|
|
827
|
+
{ "$ref": "#/$defs/sentinelRowToken" },
|
|
828
|
+
{ "$ref": "#/$defs/sentinelSubjectToken" },
|
|
817
829
|
{ "$ref": "#/$defs/sentinelDeprecatedToken" }
|
|
818
830
|
]
|
|
819
831
|
},
|
|
@@ -1691,7 +1703,7 @@
|
|
|
1691
1703
|
"sizeToContent": {
|
|
1692
1704
|
"type": ["boolean", "integer"],
|
|
1693
1705
|
"minimum": 1,
|
|
1694
|
-
"description": "Opt-in: hand this placement's height to whatever it renders, so a widget that decides AT RUNTIME it has nothing to show costs no row. `true` sizes purely to content; an integer
|
|
1706
|
+
"description": "Opt-in: hand this placement's height to whatever it renders, so a widget that decides AT RUNTIME it has nothing to show costs no row. `true` sizes purely to content; an integer sizes to content too but caps the height at that many rows (GridStack's maximum, NOT a minimum: content shorter than N rows is as short as it is, content taller is clipped at N). It makes gridHeight advisory for this widget — right for anything whose height is whatever it turned out to be (a banner whose condition is false, a list that came back empty), wrong for anything with a deliberate size. Maps to CnDashboardGrid's per-item `sizeToContent`."
|
|
1695
1707
|
},
|
|
1696
1708
|
"props": {
|
|
1697
1709
|
"type": "object",
|
|
@@ -1898,11 +1910,16 @@
|
|
|
1898
1910
|
"properties": {
|
|
1899
1911
|
"register": { "type": "string" },
|
|
1900
1912
|
"schema": { "type": "string" },
|
|
1901
|
-
"filter": { "type": "object", "additionalProperties": true }
|
|
1913
|
+
"filter": { "type": "object", "additionalProperties": true },
|
|
1914
|
+
"order": {
|
|
1915
|
+
"type": "object",
|
|
1916
|
+
"additionalProperties": { "type": "string", "enum": ["asc", "desc"] },
|
|
1917
|
+
"description": "Order of the query (`{ field: \"asc\" | \"desc\" }`), so the first result is the first of an ordered list. With it an attention card names that row: `{subject.<field>}` in its title, reason and kicker and `@subject.<field>` in an action route read it (screens-start-here-banner, 2.89.0)."
|
|
1918
|
+
}
|
|
1902
1919
|
}
|
|
1903
1920
|
},
|
|
1904
1921
|
"field": { "type": "string", "description": "Dot-path into the response ('@total' or omitted = collection total for a source)." },
|
|
1905
|
-
"op": { "type": "string", "enum": ["eq", "neq", "gt", "gte", "lt", "lte"], "default": "eq", "description": "Comparison operator." },
|
|
1922
|
+
"op": { "type": "string", "enum": ["eq", "neq", "gt", "gte", "lt", "lte", "empty", "notEmpty"], "default": "eq", "description": "Comparison operator. `notEmpty` on `id` is the \"there is a first row\" test of a card that names one." },
|
|
1906
1923
|
"value": { "description": "Literal right-hand side of the comparison." }
|
|
1907
1924
|
}
|
|
1908
1925
|
},
|
|
@@ -1919,6 +1936,11 @@
|
|
|
1919
1936
|
"default": "banner",
|
|
1920
1937
|
"description": "How the banner renders. `banner` (default) is the note card. `attention` is a card with a severity edge, a kicker, a title, a reason line and up to two actions."
|
|
1921
1938
|
},
|
|
1939
|
+
"stacked": {
|
|
1940
|
+
"type": "boolean",
|
|
1941
|
+
"default": false,
|
|
1942
|
+
"description": "Attention card: put the actions under the text instead of beside it, with a 16px title (PqMijnWerk \"Begin hier\"; screens-start-here-banner, 2.89.0)."
|
|
1943
|
+
},
|
|
1922
1944
|
"kicker": {
|
|
1923
1945
|
"type": "string",
|
|
1924
1946
|
"description": "Attention card: the small label above the title."
|
|
@@ -2184,9 +2206,27 @@
|
|
|
2184
2206
|
},
|
|
2185
2207
|
"style": {
|
|
2186
2208
|
"type": "string",
|
|
2187
|
-
"enum": ["radio", "select"],
|
|
2209
|
+
"enum": ["radio", "select", "chips", "toggle"],
|
|
2188
2210
|
"default": "radio",
|
|
2189
|
-
"description": "Choice style: 'radio' (default) list
|
|
2211
|
+
"description": "Choice style: 'radio' (default) list, 'select' dropdown, 'chips' (one compact segmented group with a count after each label; screens-page-lenses, 2.87.0) or 'toggle' (a switch writing `onValue` / `offValue`; screens-page-controls, 2.88.0)."
|
|
2212
|
+
},
|
|
2213
|
+
"layout": {
|
|
2214
|
+
"type": "string",
|
|
2215
|
+
"enum": ["start", "bar"],
|
|
2216
|
+
"description": "'start' draws the start card; 'bar' draws `controls` side by side in one row (screens-page-controls, 2.88.0)."
|
|
2217
|
+
},
|
|
2218
|
+
"controls": {
|
|
2219
|
+
"type": "array",
|
|
2220
|
+
"items": { "type": "object", "additionalProperties": true },
|
|
2221
|
+
"description": "With `layout: \"bar\"`: the bar's controls, each a workspace-filter content (chips, a toggle, a select) with its own `writes`. `align: \"end\"` pushes a control to the right edge (2.88.0)."
|
|
2222
|
+
},
|
|
2223
|
+
"onValue": { "description": "style 'toggle': the value written while the switch is on. Default true (2.88.0)." },
|
|
2224
|
+
"offValue": { "description": "style 'toggle': the value written while the switch is off. Default false (2.88.0)." },
|
|
2225
|
+
"align": { "type": "string", "enum": ["start", "end"], "description": "In a bar: 'end' pushes this control to the right edge (2.88.0)." },
|
|
2226
|
+
"urlKey": {
|
|
2227
|
+
"type": "string",
|
|
2228
|
+
"minLength": 1,
|
|
2229
|
+
"description": "Keep the choice in the address as `?<urlKey>=<value>` (replacing the history entry, keeping other query keys). The address wins over `default` on load, so a chip is linkable (screens-page-lenses, 2.87.0)."
|
|
2190
2230
|
},
|
|
2191
2231
|
"allLabel": {
|
|
2192
2232
|
"type": "string",
|
|
@@ -2199,7 +2239,7 @@
|
|
|
2199
2239
|
},
|
|
2200
2240
|
"options": {
|
|
2201
2241
|
"type": "array",
|
|
2202
|
-
"description": "Static options — each { value, label, count? } (or { id, name } / bare strings, normalised).",
|
|
2242
|
+
"description": "Static options — each { value, label, count?, countFrom? } (or { id, name } / bare strings, normalised). `countFrom` is `{ register, schema, filter? }`, or a list of those that are added up; the option shows that list's total (one list request each; none when it fails). screens-page-lenses, 2.87.0.",
|
|
2203
2243
|
"items": { "type": ["object", "string", "number"] }
|
|
2204
2244
|
},
|
|
2205
2245
|
"source": {
|
|
@@ -2925,15 +2965,20 @@
|
|
|
2925
2965
|
"filter": { "type": "object", "additionalProperties": true }
|
|
2926
2966
|
}
|
|
2927
2967
|
},
|
|
2968
|
+
"workspace": {
|
|
2969
|
+
"type": "string",
|
|
2970
|
+
"minLength": 1,
|
|
2971
|
+
"description": "WORKSPACE mode (screens-page-lenses, 2.87.0): the key of the page workspace context to compare, read at once with no request. A page control (a `workspace-filter` with `writes`) sets the key, and a dashboard widget carrying this condition appears and disappears as it changes. Honoured on a dashboard widget or KPI-row tile only, and as a top-level condition (not inside `all` / `any`)."
|
|
2972
|
+
},
|
|
2928
2973
|
"field": {
|
|
2929
2974
|
"type": "string",
|
|
2930
2975
|
"description": "Dot-path to the compared value — into the endpoint body, the source's first result, or (LOCAL mode) the caller's object context."
|
|
2931
2976
|
},
|
|
2932
2977
|
"op": {
|
|
2933
2978
|
"type": "string",
|
|
2934
|
-
"enum": ["eq", "neq", "gt", "gte", "lt", "lte", "empty", "notEmpty"],
|
|
2979
|
+
"enum": ["eq", "neq", "gt", "gte", "lt", "lte", "empty", "notEmpty", "in"],
|
|
2935
2980
|
"default": "eq",
|
|
2936
|
-
"description": "Comparison operator (default eq). eq/neq compare loosely-normalised primitives; ordering operators coerce both sides to Number. `empty` / `notEmpty` take no `value` and treat undefined, null, '' and an empty array alike — the one question eq cannot ask, because an unset field arrives in a shape that depends on the store and String(null) is the word 'null' rather than blank."
|
|
2981
|
+
"description": "Comparison operator (default eq). eq/neq compare loosely-normalised primitives; ordering operators coerce both sides to Number. `in` holds when the value is one of the listed `value` array (2.87.0). `empty` / `notEmpty` take no `value` and treat undefined, null, '' and an empty array alike — the one question eq cannot ask, because an unset field arrives in a shape that depends on the store and String(null) is the word 'null' rather than blank."
|
|
2937
2982
|
},
|
|
2938
2983
|
"value": {
|
|
2939
2984
|
"description": "The right-hand side of the comparison, resolved through the shared @-token grammar so `@me` names the signed-in reader rather than a literal only the server knows. Omitted for `empty` / `notEmpty`."
|
|
@@ -2985,6 +3030,45 @@
|
|
|
2985
3030
|
"type": "array",
|
|
2986
3031
|
"items": { "type": "string" },
|
|
2987
3032
|
"description": "OpenRegister `_extend[]` values forwarded on the fetch (e.g. [\"calculations\"]) so read-time virtual/derived fields are hydrated and available as columns. Each entry is passed through verbatim."
|
|
3033
|
+
},
|
|
3034
|
+
"pageControls": { "$ref": "#/$defs/pageControls" }
|
|
3035
|
+
}
|
|
3036
|
+
},
|
|
3037
|
+
"pageControls": {
|
|
3038
|
+
"type": "object",
|
|
3039
|
+
"additionalProperties": false,
|
|
3040
|
+
"description": "screens-page-controls (2.88.0): which page controls a list widget (`record-list`, `object-table`) listens to, and what they mean for it. A page control is a `workspace-filter` select or switch that writes a workspace key. `sort` picks the list's sort from the key's value; `toggle` drops filter keys while the key is on. Without it the list ignores the page's controls.",
|
|
3041
|
+
"properties": {
|
|
3042
|
+
"sort": {
|
|
3043
|
+
"type": "object",
|
|
3044
|
+
"required": ["key", "map"],
|
|
3045
|
+
"additionalProperties": false,
|
|
3046
|
+
"description": "The workspace key a sort select writes, and what each of its values sorts by. A value with no entry leaves the list's own sort.",
|
|
3047
|
+
"properties": {
|
|
3048
|
+
"key": { "type": "string", "minLength": 1 },
|
|
3049
|
+
"map": {
|
|
3050
|
+
"type": "object",
|
|
3051
|
+
"additionalProperties": {
|
|
3052
|
+
"type": "object",
|
|
3053
|
+
"required": ["field"],
|
|
3054
|
+
"additionalProperties": false,
|
|
3055
|
+
"properties": {
|
|
3056
|
+
"field": { "type": "string", "minLength": 1 },
|
|
3057
|
+
"dir": { "type": "string", "enum": ["asc", "desc"], "default": "asc" }
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
},
|
|
3063
|
+
"toggle": {
|
|
3064
|
+
"type": "object",
|
|
3065
|
+
"required": ["key", "removes"],
|
|
3066
|
+
"additionalProperties": false,
|
|
3067
|
+
"description": "The workspace key a switch writes. While it is on (true, 1, \"1\", \"true\") the filter keys in `removes` are dropped, so a list that hides finished work shows it.",
|
|
3068
|
+
"properties": {
|
|
3069
|
+
"key": { "type": "string", "minLength": 1 },
|
|
3070
|
+
"removes": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }
|
|
3071
|
+
}
|
|
2988
3072
|
}
|
|
2989
3073
|
}
|
|
2990
3074
|
},
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* contentHeaderLink — the text link a widget's content asks its card header
|
|
6
|
+
* to carry: `content.headerLink`, or the shorter `viewAllRoute` (with an
|
|
7
|
+
* optional `viewAllLabel`) that an inbox-style widget uses for its "View all".
|
|
8
|
+
*
|
|
9
|
+
* @module utils/contentHeaderLink
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The header link a widget's `content` declares, in the shape
|
|
14
|
+
* CnWidgetWrapper's `headerLink` takes, or null when it declares none.
|
|
15
|
+
* The label is left as written: the wrapper translates it.
|
|
16
|
+
*
|
|
17
|
+
* `viewAllRoute` is a route name, or a location `{ name, params?, query? }`.
|
|
18
|
+
*
|
|
19
|
+
* @param {object|null|undefined} content The widget's content.
|
|
20
|
+
* @return {{label: string, route?: string, params?: object, query?: object, href?: string}|null}
|
|
21
|
+
*/
|
|
22
|
+
export function contentHeaderLink(content) {
|
|
23
|
+
if (!content || typeof content !== 'object') {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
const declared = content.headerLink
|
|
27
|
+
if (declared && typeof declared === 'object' && typeof declared.label === 'string' && declared.label !== '') {
|
|
28
|
+
return declared
|
|
29
|
+
}
|
|
30
|
+
const view = content.viewAllRoute
|
|
31
|
+
const name = typeof view === 'string' ? view : (view && typeof view === 'object' ? view.name : '')
|
|
32
|
+
if (typeof name !== 'string' || name === '') {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
const link = {
|
|
36
|
+
label: typeof content.viewAllLabel === 'string' && content.viewAllLabel !== '' ? content.viewAllLabel : 'View all',
|
|
37
|
+
route: name,
|
|
38
|
+
}
|
|
39
|
+
if (view && typeof view === 'object') {
|
|
40
|
+
if (view.params && typeof view.params === 'object') {
|
|
41
|
+
link.params = view.params
|
|
42
|
+
}
|
|
43
|
+
if (view.query && typeof view.query === 'object') {
|
|
44
|
+
link.query = view.query
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return link
|
|
48
|
+
}
|
package/src/utils/facets.js
CHANGED
|
@@ -37,14 +37,29 @@ export function normalizeFacetBucket(bucket) {
|
|
|
37
37
|
return option
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* The option id that stands for "this property holds no value".
|
|
42
|
+
*
|
|
43
|
+
* Chosen so no real bucket key can collide with it. Selecting it is sent to
|
|
44
|
+
* OpenRegister as `<property>_isnull=true` by {@link facetFilterParams}.
|
|
45
|
+
*
|
|
46
|
+
* @type {string}
|
|
47
|
+
*/
|
|
48
|
+
export const MISSING_VALUE = '__cn_missing__'
|
|
49
|
+
|
|
40
50
|
/**
|
|
41
51
|
* Normalise the `facets` block of a collection response into sidebar format.
|
|
42
52
|
*
|
|
43
53
|
* Fields whose facet carries no bucket list are skipped entirely, so a caller
|
|
44
54
|
* can tell an unfaceted field from one that faceted to nothing.
|
|
45
55
|
*
|
|
56
|
+
* What OpenRegister puts beside the buckets is carried through rather than
|
|
57
|
+
* dropped: `missing` (how many objects in scope hold no value, sent as
|
|
58
|
+
* `{ results }` or `{ count }`), `type` and `title`. A missing count is only
|
|
59
|
+
* set when the backend sent one, for the same reason a bucket count is.
|
|
60
|
+
*
|
|
46
61
|
* @param {object} facets The `facets` block of an API response
|
|
47
|
-
* @return {object} `{ fieldName: { values: [{ value, count?, label? }] } }`
|
|
62
|
+
* @return {object} `{ fieldName: { values: [{ value, count?, label? }], missing?, type?, title? } }`
|
|
48
63
|
*/
|
|
49
64
|
export function normalizeFacets(facets) {
|
|
50
65
|
const transformed = {}
|
|
@@ -54,12 +69,69 @@ export function normalizeFacets(facets) {
|
|
|
54
69
|
if (!buckets) {
|
|
55
70
|
continue
|
|
56
71
|
}
|
|
57
|
-
|
|
72
|
+
const normalized = { values: buckets.map(normalizeFacetBucket) }
|
|
73
|
+
|
|
74
|
+
const missing = facet.missing?.results ?? facet.missing?.count
|
|
75
|
+
if (typeof missing === 'number') {
|
|
76
|
+
normalized.missing = missing
|
|
77
|
+
}
|
|
78
|
+
for (const name of ['type', 'title']) {
|
|
79
|
+
if (facet[name] !== undefined && facet[name] !== null) {
|
|
80
|
+
normalized[name] = facet[name]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
transformed[key] = normalized
|
|
58
85
|
}
|
|
59
86
|
|
|
60
87
|
return transformed
|
|
61
88
|
}
|
|
62
89
|
|
|
90
|
+
/**
|
|
91
|
+
* The sidebar options for one normalised facet, with the missing-value option
|
|
92
|
+
* appended when some objects hold no value.
|
|
93
|
+
*
|
|
94
|
+
* @param {object|undefined} facet A normalised facet from {@link normalizeFacets}
|
|
95
|
+
* @param {function(number): string} missingLabel Renders the missing option's label from its count
|
|
96
|
+
* @return {Array<{id: unknown, label: string}>|null} The options, or null when the
|
|
97
|
+
* facet offers nothing, so the caller can fall back to its static options
|
|
98
|
+
*/
|
|
99
|
+
export function facetOptions(facet, missingLabel) {
|
|
100
|
+
const options = (facet?.values || []).map((v) => ({
|
|
101
|
+
id: v.value,
|
|
102
|
+
label: facetOptionLabel(v),
|
|
103
|
+
}))
|
|
104
|
+
|
|
105
|
+
if (typeof facet?.missing === 'number' && facet.missing > 0) {
|
|
106
|
+
options.push({ id: MISSING_VALUE, label: missingLabel(facet.missing) })
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return options.length > 0 ? options : null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The query parameters one facet selection sends.
|
|
114
|
+
*
|
|
115
|
+
* An ordinary selection is the property itself, one value unwrapped and
|
|
116
|
+
* several as a list. The missing-value option is exclusive: no object both
|
|
117
|
+
* holds one of the chosen values and holds none, so combining them would
|
|
118
|
+
* always answer an empty list. It is sent as `<property>_isnull=true`.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} key The filtered property
|
|
121
|
+
* @param {Array} values The selected option ids
|
|
122
|
+
* @return {object} The parameters to merge into the request
|
|
123
|
+
*/
|
|
124
|
+
export function facetFilterParams(key, values) {
|
|
125
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
126
|
+
return {}
|
|
127
|
+
}
|
|
128
|
+
if (values.includes(MISSING_VALUE)) {
|
|
129
|
+
return { [`${key}_isnull`]: 'true' }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { [key]: values.length === 1 ? values[0] : values }
|
|
133
|
+
}
|
|
134
|
+
|
|
63
135
|
/**
|
|
64
136
|
* Build the visible label for one faceted filter option.
|
|
65
137
|
*
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* `status: closed`, ...). Those are answered by one `/grouped` request on that
|
|
10
10
|
* field. A filter that narrows on something else (two fields, an operator, a
|
|
11
11
|
* list of values) cannot be read off a grouped result, so each of those costs
|
|
12
|
-
* one `/value` count. A filter that holds a LIST of values
|
|
13
|
-
* the list endpoint, because the
|
|
12
|
+
* one `/value` count. A filter that holds a LIST of values, or tests a field
|
|
13
|
+
* for no value (`isnull`), is counted through the list endpoint, because the
|
|
14
|
+
* aggregation endpoints cannot express it.
|
|
14
15
|
* Nothing is requested for an entry that did not ask.
|
|
15
16
|
*
|
|
16
17
|
* @module utils/fetchFilterCounts
|
|
@@ -38,6 +39,37 @@ export function hasListValue(filter) {
|
|
|
38
39
|
return Object.values(filter).some((value) => Array.isArray(value))
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Whether a filter tests a field for "no value" (`{ assignee: null }` or
|
|
44
|
+
* `{ supersededBy: { isnull: true } }`).
|
|
45
|
+
*
|
|
46
|
+
* OpenRegister's aggregation endpoints have no null operator: `isnull` is
|
|
47
|
+
* refused there and an empty value is dropped or matches nothing, so such a
|
|
48
|
+
* filter counted 0 while the list showed rows. The list endpoint does read
|
|
49
|
+
* `field[isnull]=true`, so a filter like this is counted through it.
|
|
50
|
+
*
|
|
51
|
+
* @param {object} filter A filter map.
|
|
52
|
+
* @return {boolean} True when any value is null or carries an `isnull` operator.
|
|
53
|
+
*/
|
|
54
|
+
export function hasNullTest(filter) {
|
|
55
|
+
if (!filter || typeof filter !== 'object') {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
return Object.values(filter).some((value) => value === null
|
|
59
|
+
|| (value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).some((op) => op.toLowerCase() === 'isnull')))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether the aggregation endpoints cannot express a filter, so it is counted
|
|
64
|
+
* through the list endpoint: a list of values, or a test for no value.
|
|
65
|
+
*
|
|
66
|
+
* @param {object} filter A filter map.
|
|
67
|
+
* @return {boolean} True when the filter needs the list endpoint.
|
|
68
|
+
*/
|
|
69
|
+
export function needsListEndpoint(filter) {
|
|
70
|
+
return hasListValue(filter) || hasNullTest(filter)
|
|
71
|
+
}
|
|
72
|
+
|
|
41
73
|
/**
|
|
42
74
|
* Count the records a filter matches by asking the LIST endpoint for one
|
|
43
75
|
* record and reading its `total`. The filter is sent the way the list request
|
|
@@ -59,6 +91,9 @@ export async function fetchListTotal(register, schema, filter, ctx) {
|
|
|
59
91
|
for (const [op, operand] of Object.entries(value)) {
|
|
60
92
|
params[`${key}[${op}]`] = operand
|
|
61
93
|
}
|
|
94
|
+
} else if (value === null) {
|
|
95
|
+
// "No value": the list endpoint reads it as the isnull operator.
|
|
96
|
+
params[`${key}[isnull]`] = 'true'
|
|
62
97
|
} else {
|
|
63
98
|
params[key] = value
|
|
64
99
|
}
|
|
@@ -125,12 +160,12 @@ export async function fetchFilterCounts({ register, schema, entries, baseFilter,
|
|
|
125
160
|
// and `/value` returns exactly its number.
|
|
126
161
|
// A list of values anywhere in a request rules the aggregation endpoints
|
|
127
162
|
// out for it. A base filter with one rules them out for every entry.
|
|
128
|
-
const baseHasList =
|
|
163
|
+
const baseHasList = needsListEndpoint(base)
|
|
129
164
|
const byField = {}
|
|
130
165
|
const single = []
|
|
131
166
|
const viaList = []
|
|
132
167
|
for (const entry of entries) {
|
|
133
|
-
if (baseHasList ||
|
|
168
|
+
if (baseHasList || needsListEndpoint(entry.filter)) {
|
|
134
169
|
viaList.push(entry)
|
|
135
170
|
continue
|
|
136
171
|
}
|
|
@@ -49,6 +49,11 @@ function formatValue(value, format, now) {
|
|
|
49
49
|
if (format === 'datetime') {
|
|
50
50
|
return formatBoardDateTime(value, now)
|
|
51
51
|
}
|
|
52
|
+
if (format === 'days') {
|
|
53
|
+
// Whole days from the date to now (0 for today; negative for a future date).
|
|
54
|
+
const date = parseDateValue(value)
|
|
55
|
+
return date === null ? '' : String(Math.floor((now.getTime() - date.getTime()) / 86400000))
|
|
56
|
+
}
|
|
52
57
|
return String(value)
|
|
53
58
|
}
|
|
54
59
|
|
|
@@ -57,7 +62,8 @@ function formatValue(value, format, now) {
|
|
|
57
62
|
* `"Termijn {deadline|date}"` from an object.
|
|
58
63
|
*
|
|
59
64
|
* Every `{path}` token is a dot path into the object; `{path|date}` and
|
|
60
|
-
* `{path|datetime}` format it as the boards write dates
|
|
65
|
+
* `{path|datetime}` format it as the boards write dates, and `{path|days}` is the
|
|
66
|
+
* whole days since that date ("has waited {createdAt|days} days"). A token without a
|
|
61
67
|
* value empties the WHOLE template, because "Termijn " is worse than nothing.
|
|
62
68
|
* A template without tokens is returned as written (a constant label).
|
|
63
69
|
*
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* initials: the letters an avatar shows for a free name.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/initials
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The initials of a name: the first letters of its first and its last word,
|
|
12
|
+
* upper-cased ("Sanne de Vries" reads "SV", "Jan" reads "J").
|
|
13
|
+
*
|
|
14
|
+
* @param {unknown} name The name.
|
|
15
|
+
* @return {string} One or two letters, or '' when there is no name.
|
|
16
|
+
* @spec openspec/changes/screens-record-list-avatar/specs/record-list-avatar/spec.md#requirement-a-record-list-row-can-carry-an-avatar
|
|
17
|
+
*/
|
|
18
|
+
export function initialsOf(name) {
|
|
19
|
+
if (typeof name !== 'string') {
|
|
20
|
+
return ''
|
|
21
|
+
}
|
|
22
|
+
const words = name.trim().split(/\s+/).filter((word) => word !== '')
|
|
23
|
+
if (words.length === 0) {
|
|
24
|
+
return ''
|
|
25
|
+
}
|
|
26
|
+
const first = Array.from(words[0])[0] || ''
|
|
27
|
+
const last = words.length > 1 ? (Array.from(words[words.length - 1])[0] || '') : ''
|
|
28
|
+
return (first + last).toLocaleUpperCase()
|
|
29
|
+
}
|
|
@@ -93,6 +93,8 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
|
|
|
93
93
|
'banner',
|
|
94
94
|
'calendar',
|
|
95
95
|
'chart',
|
|
96
|
+
'chips-field',
|
|
97
|
+
'choice-picker',
|
|
96
98
|
'container',
|
|
97
99
|
'conversation',
|
|
98
100
|
'countdown',
|
|
@@ -125,21 +127,26 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
|
|
|
125
127
|
'object-table',
|
|
126
128
|
'people',
|
|
127
129
|
'quicklinks',
|
|
130
|
+
'radio-cards',
|
|
128
131
|
'record-list',
|
|
129
132
|
'related',
|
|
130
133
|
'saved-view',
|
|
134
|
+
'saved-views',
|
|
131
135
|
'sections',
|
|
136
|
+
'settings-form',
|
|
132
137
|
'spend-analytics',
|
|
133
138
|
'stacked-bar',
|
|
134
139
|
'stages',
|
|
135
140
|
'stat',
|
|
136
141
|
'stats-block',
|
|
142
|
+
'switch-rows',
|
|
137
143
|
'table',
|
|
138
144
|
'tabs',
|
|
139
145
|
'tasks',
|
|
140
146
|
'text',
|
|
141
147
|
'tile',
|
|
142
148
|
'timeline',
|
|
149
|
+
'toggle-table',
|
|
143
150
|
'video',
|
|
144
151
|
'week-strip',
|
|
145
152
|
'workspace-filter',
|