@conduction/nextcloud-vue 2.79.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/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +3 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +32 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +13 -11
- 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 +179 -13
- 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 +96 -4
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +158 -110
- 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 +4 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +134 -4
- 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/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +24 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +43 -4
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.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.vue.js +11 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +867 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +238 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/index.js +30 -0
- package/dist/esm/components/CnFactsWidget/index.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +10 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +626 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +396 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/index.js +29 -0
- package/dist/esm/components/CnFieldListWidget/index.js.map +1 -0
- 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/CnFormDialog/CnFormDialog.vue2.js +65 -17
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +4 -4
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +78 -3
- 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 +333 -73
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +79 -17
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +10 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +99 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +48 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -0
- 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/CnPageRenderer/CnPageRenderer.vue2.js +29 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.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.vue.js +10 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +411 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +178 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/index.js +31 -0
- package/dist/esm/components/CnRecordListWidget/index.js.map +1 -0
- 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/CnSectionsWidget/CnSectionsWidget.vue.js +10 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +351 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +71 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/index.js +35 -0
- package/dist/esm/components/CnSectionsWidget/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/CnStagesWidget/CnStagesWidget.vue2.js +81 -2
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- 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 +1 -0
- 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 +168 -7
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +86 -23
- 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 +11 -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 +301 -6
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +153 -55
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useFormDraft.js +140 -9
- package/dist/esm/composables/useFormDraft.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 +21 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +28 -20
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js +9 -0
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection.js +21 -0
- package/dist/esm/integrations/builtin/field-inspection.js.map +1 -1
- package/dist/esm/integrations/offline/checklistSubmission.js +141 -0
- package/dist/esm/integrations/offline/checklistSubmission.js.map +1 -0
- package/dist/esm/integrations/offline/fieldCollectionHelpers.js +70 -5
- package/dist/esm/integrations/offline/fieldCollectionHelpers.js.map +1 -1
- package/dist/esm/integrations/offline/index.js +2 -1
- package/dist/esm/integrations/offline/index.js.map +1 -1
- package/dist/esm/integrations/offline/offlineDb.js +6 -1
- package/dist/esm/integrations/offline/offlineDb.js.map +1 -1
- package/dist/esm/integrations/offline/syncReplayService.js +8 -2
- package/dist/esm/integrations/offline/syncReplayService.js.map +1 -1
- package/dist/esm/l10n/en.json.js +20 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +70 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +2192 -222
- package/dist/esm/utils/contentHeaderLink.js +51 -0
- package/dist/esm/utils/contentHeaderLink.js.map +1 -0
- package/dist/esm/utils/detailActionModel.js +60 -3
- package/dist/esm/utils/detailActionModel.js.map +1 -1
- 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 +120 -0
- package/dist/esm/utils/fillTemplate.js.map +1 -0
- package/dist/esm/utils/initials.js +32 -0
- package/dist/esm/utils/initials.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +11 -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/queryPresets.js +96 -0
- package/dist/esm/utils/queryPresets.js.map +1 -0
- package/dist/esm/utils/resolveFilterTokens.js +12 -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/schema.js +21 -0
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +22 -4
- 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 +7 -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 +19708 -9301
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +2192 -222
- package/l10n/en.json +20 -1
- package/l10n/nl.json +70 -2
- package/package.json +2 -1
- package/scripts/check-docs-mdx.mjs +108 -0
- package/src/components/CnActionButtons/CnActionButtons.vue +2 -0
- package/src/components/CnActionsBar/CnActionsBar.md +4 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +48 -11
- package/src/components/CnAppRoot/CnAppRoot.md +4 -2
- package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
- package/src/components/CnBannerWidget/CnBannerWidget.vue +244 -23
- package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +235 -5
- 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 +22 -12
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +152 -6
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +66 -0
- package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
- package/src/components/CnFactsWidget/CnFactsWidget.vue +1190 -0
- package/src/components/CnFactsWidget/index.js +29 -0
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +1008 -0
- package/src/components/CnFieldListWidget/index.js +28 -0
- 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/CnFormDialog/CnFormDialog.md +9 -3
- package/src/components/CnFormDialog/CnFormDialog.vue +69 -21
- package/src/components/CnFormPage/CnFormPage.vue +99 -3
- package/src/components/CnIndexPage/CnIndexPage.vue +309 -9
- package/src/components/CnIndexPage/CnIndexPageWidgets.vue +169 -0
- 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/CnPageRenderer/CnPageRenderer.vue +29 -1
- package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
- package/src/components/CnRadioCardsWidget/index.js +27 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +652 -0
- package/src/components/CnRecordListWidget/index.js +30 -0
- package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
- package/src/components/CnSavedViewsWidget/index.js +29 -0
- package/src/components/CnSectionsWidget/CnSectionsWidget.vue +489 -0
- package/src/components/CnSectionsWidget/index.js +34 -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/CnStagesWidget/CnStagesWidget.vue +81 -2
- package/src/components/CnStatWidget/CnStatWidget.vue +88 -7
- 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 +263 -8
- 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 +11 -0
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
- package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
- package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +493 -11
- package/src/components/index.js +19 -0
- package/src/composables/__tests__/useFormDraft.spec.js +69 -0
- package/src/composables/useFormDraft.js +139 -8
- package/src/composables/useListView.js +4 -4
- package/src/css/form-field.css +14 -0
- package/src/css/index-page.css +71 -0
- package/src/css/kpi-card.css +32 -0
- package/src/css/look-board-detail.css +113 -2
- package/src/css/look-board-index.css +90 -8
- package/src/css/look-board.css +109 -2
- package/src/css/table.css +19 -0
- package/src/index.js +19 -0
- package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +35 -20
- package/src/integrations/builtin/field-inspection.js +21 -0
- package/src/integrations/offline/checklistSubmission.js +137 -0
- package/src/integrations/offline/fieldCollectionHelpers.js +69 -4
- package/src/integrations/offline/index.js +3 -0
- package/src/integrations/offline/offlineDb.js +6 -1
- package/src/integrations/offline/syncReplayService.js +8 -2
- package/src/schemas/app-manifest-v2.schema.json +196 -13
- package/src/utils/contentHeaderLink.js +48 -0
- package/src/utils/detailActionModel.js +59 -2
- package/src/utils/facets.js +74 -2
- package/src/utils/fetchFilterCounts.js +39 -4
- package/src/utils/fillTemplate.js +116 -0
- package/src/utils/initials.js +29 -0
- package/src/utils/libraryWidgetKeys.js +11 -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/queryPresets.js +93 -0
- package/src/utils/resolveFilterTokens.js +14 -1
- package/src/utils/resolveManifestTokens.js +2 -0
- package/src/utils/schema.js +21 -0
- package/src/utils/sentinelTokens.js +22 -4
- 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 +7 -1
- package/src/utils/widgetVisibility.js +7 -56
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
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
2
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
3
|
+
|
|
4
|
+
import { boardDateLocale, formatBoardDate } from './boardDate.js'
|
|
5
|
+
import { parseDateValue } from './dateVariant.js'
|
|
6
|
+
import { readPath } from './readPath.js'
|
|
7
|
+
|
|
8
|
+
const TOKEN = /\{([^{}\s|]+)(?:\|([a-z]+))?\}/g
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A moment in the board's short form with the time: "4 okt, 10:52", or
|
|
12
|
+
* "14 feb 2024, 09:00" outside the current year.
|
|
13
|
+
*
|
|
14
|
+
* @param {unknown} value A Date, a timestamp or an ISO string.
|
|
15
|
+
* @param {Date} [now] The reference moment.
|
|
16
|
+
* @return {string} The text, or '' when the value is not a date.
|
|
17
|
+
* @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record
|
|
18
|
+
*/
|
|
19
|
+
export function formatBoardDateTime(value, now = new Date()) {
|
|
20
|
+
const day = formatBoardDate(value, now)
|
|
21
|
+
const date = parseDateValue(value)
|
|
22
|
+
if (day === '' || date === null) {
|
|
23
|
+
return ''
|
|
24
|
+
}
|
|
25
|
+
let time
|
|
26
|
+
try {
|
|
27
|
+
time = new Intl.DateTimeFormat(boardDateLocale(), { hour: '2-digit', minute: '2-digit' }).format(date)
|
|
28
|
+
} catch {
|
|
29
|
+
time = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit' }).format(date)
|
|
30
|
+
}
|
|
31
|
+
return `${day}, ${time}`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Format one token value.
|
|
36
|
+
*
|
|
37
|
+
* @param {unknown} value The raw value.
|
|
38
|
+
* @param {string} format `date`, `datetime` or '' (as is).
|
|
39
|
+
* @param {Date} now The reference moment.
|
|
40
|
+
* @return {string} The text, '' when nothing can be shown.
|
|
41
|
+
*/
|
|
42
|
+
function formatValue(value, format, now) {
|
|
43
|
+
if (value === null || value === undefined || value === '' || typeof value === 'object') {
|
|
44
|
+
return ''
|
|
45
|
+
}
|
|
46
|
+
if (format === 'date') {
|
|
47
|
+
return formatBoardDate(value, now)
|
|
48
|
+
}
|
|
49
|
+
if (format === 'datetime') {
|
|
50
|
+
return formatBoardDateTime(value, now)
|
|
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
|
+
}
|
|
57
|
+
return String(value)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fill a row template such as `"{identifier} · {caseTypeName}"` or
|
|
62
|
+
* `"Termijn {deadline|date}"` from an object.
|
|
63
|
+
*
|
|
64
|
+
* Every `{path}` token is a dot path into the object; `{path|date}` and
|
|
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
|
|
67
|
+
* value empties the WHOLE template, because "Termijn " is worse than nothing.
|
|
68
|
+
* A template without tokens is returned as written (a constant label).
|
|
69
|
+
*
|
|
70
|
+
* @param {string} template The template, already translated.
|
|
71
|
+
* @param {object|null} object The object to read.
|
|
72
|
+
* @param {Date} [now] The reference moment for dates.
|
|
73
|
+
* @return {string} The filled text, or ''.
|
|
74
|
+
* @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record
|
|
75
|
+
*/
|
|
76
|
+
export function fillTemplate(template, object, now = new Date()) {
|
|
77
|
+
if (typeof template !== 'string' || template.trim() === '') {
|
|
78
|
+
return ''
|
|
79
|
+
}
|
|
80
|
+
let missing = false
|
|
81
|
+
const text = template.replace(TOKEN, (_match, path, format) => {
|
|
82
|
+
const value = object && typeof object === 'object' ? readPath(object, path) : undefined
|
|
83
|
+
const shown = formatValue(value, format || '', now)
|
|
84
|
+
if (shown === '') {
|
|
85
|
+
missing = true
|
|
86
|
+
}
|
|
87
|
+
return shown
|
|
88
|
+
})
|
|
89
|
+
return missing ? '' : text.trim()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The first of several templates that fills: `["Klaar {closedAt|date}",
|
|
94
|
+
* "Termijn {deadline|date}"]` reads "Klaar ..." once the case is closed and
|
|
95
|
+
* "Termijn ..." while it is open. A single string is one candidate.
|
|
96
|
+
*
|
|
97
|
+
* @param {string|string[]} templates The candidates, in order.
|
|
98
|
+
* @param {object|null} object The object to read.
|
|
99
|
+
* @param {(key: string) => string} [translate] Applied to each template before filling.
|
|
100
|
+
* @param {Date} [now] The reference moment for dates.
|
|
101
|
+
* @return {string} The first filled text, or ''.
|
|
102
|
+
* @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record
|
|
103
|
+
*/
|
|
104
|
+
export function firstFilled(templates, object, translate = (key) => key, now = new Date()) {
|
|
105
|
+
const list = Array.isArray(templates) ? templates : [templates]
|
|
106
|
+
for (const template of list) {
|
|
107
|
+
if (typeof template !== 'string') {
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
const text = fillTemplate(translate(template), object, now)
|
|
111
|
+
if (text !== '') {
|
|
112
|
+
return text
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return ''
|
|
116
|
+
}
|
|
@@ -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',
|
|
@@ -100,6 +102,8 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
|
|
|
100
102
|
'delta',
|
|
101
103
|
'divider',
|
|
102
104
|
'document-review',
|
|
105
|
+
'facts',
|
|
106
|
+
'field-list',
|
|
103
107
|
'files',
|
|
104
108
|
'flow-runs',
|
|
105
109
|
'gauge',
|
|
@@ -123,19 +127,26 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
|
|
|
123
127
|
'object-table',
|
|
124
128
|
'people',
|
|
125
129
|
'quicklinks',
|
|
130
|
+
'radio-cards',
|
|
131
|
+
'record-list',
|
|
126
132
|
'related',
|
|
127
133
|
'saved-view',
|
|
134
|
+
'saved-views',
|
|
135
|
+
'sections',
|
|
136
|
+
'settings-form',
|
|
128
137
|
'spend-analytics',
|
|
129
138
|
'stacked-bar',
|
|
130
139
|
'stages',
|
|
131
140
|
'stat',
|
|
132
141
|
'stats-block',
|
|
142
|
+
'switch-rows',
|
|
133
143
|
'table',
|
|
134
144
|
'tabs',
|
|
135
145
|
'tasks',
|
|
136
146
|
'text',
|
|
137
147
|
'tile',
|
|
138
148
|
'timeline',
|
|
149
|
+
'toggle-table',
|
|
139
150
|
'video',
|
|
140
151
|
'week-strip',
|
|
141
152
|
'workspace-filter',
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* objectWrite: the one-object PATCH the settings widgets share.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/objectWrite
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { prefixUrl } from './headers.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The OpenRegister URL of one object, or '' when register, schema or id is missing.
|
|
14
|
+
*
|
|
15
|
+
* @param {string|object} register Register slug.
|
|
16
|
+
* @param {string|object} schema Schema slug.
|
|
17
|
+
* @param {string|number} id Object id.
|
|
18
|
+
* @return {string} The object URL.
|
|
19
|
+
*/
|
|
20
|
+
export function objectUrlOf(register, schema, id) {
|
|
21
|
+
if (typeof register !== 'string' || typeof schema !== 'string' || !register || !schema || id === undefined || id === null || id === '') {
|
|
22
|
+
return ''
|
|
23
|
+
}
|
|
24
|
+
return `/apps/openregister/api/objects/${encodeURIComponent(register)}/${encodeURIComponent(schema)}/${encodeURIComponent(String(id))}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* PATCH only the given fields of one object.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} url The object URL (see objectUrlOf).
|
|
31
|
+
* @param {object} patch The fields to write.
|
|
32
|
+
* @return {Promise<object>} The object the server answered with.
|
|
33
|
+
*/
|
|
34
|
+
export async function patchObject(url, patch) {
|
|
35
|
+
const { default: axios } = await import('@nextcloud/axios')
|
|
36
|
+
const { data } = await axios.patch(prefixUrl(url), patch)
|
|
37
|
+
return data && typeof data === 'object' ? data : {}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The fields of `next` that differ from `base`, compared by value.
|
|
42
|
+
*
|
|
43
|
+
* @param {object} base What the server last held.
|
|
44
|
+
* @param {object} next What the form holds.
|
|
45
|
+
* @return {object} The changed fields only.
|
|
46
|
+
*/
|
|
47
|
+
export function changedFields(base, next) {
|
|
48
|
+
const patch = {}
|
|
49
|
+
for (const [key, value] of Object.entries(next)) {
|
|
50
|
+
if (JSON.stringify(value === undefined ? null : value) !== JSON.stringify(base[key] === undefined ? null : base[key])) {
|
|
51
|
+
patch[key] = value
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return patch
|
|
55
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* pageControls: how a list widget listens to the page's controls.
|
|
6
|
+
*
|
|
7
|
+
* A page control (a `workspace-filter` select or switch) writes a value to the
|
|
8
|
+
* page workspace. A list widget declares which control it listens to and what
|
|
9
|
+
* the value means for it:
|
|
10
|
+
*
|
|
11
|
+
* pageControls: {
|
|
12
|
+
* sort: { key: "sort", map: { deadline: { field: "slaDeadline", dir: "asc" } } },
|
|
13
|
+
* toggle: { key: "showDone", removes: ["status"] }
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* `sort`: the workspace value at `key` picks an entry of `map` (the sort the
|
|
17
|
+
* list uses); a value with no entry leaves the list's own sort.
|
|
18
|
+
* `toggle`: while the workspace value at `key` is on (true, "1", "true"), the
|
|
19
|
+
* filter keys in `removes` are dropped, so a list that hides finished work
|
|
20
|
+
* shows it.
|
|
21
|
+
*
|
|
22
|
+
* @module utils/pageControls
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether a workspace value reads as on.
|
|
27
|
+
*
|
|
28
|
+
* @param {unknown} value The value.
|
|
29
|
+
* @return {boolean} True for true, 1, "1" and "true".
|
|
30
|
+
*/
|
|
31
|
+
export function isOn(value) {
|
|
32
|
+
return value === true || value === 1 || value === '1' || value === 'true'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The sort a list uses once the page's sort control is read.
|
|
37
|
+
*
|
|
38
|
+
* @param {object|null} pageControls The widget's `pageControls`.
|
|
39
|
+
* @param {object} workspace The plain workspace bag.
|
|
40
|
+
* @param {{field: string, dir?: string}|null} own The list's own sort.
|
|
41
|
+
* @return {{field: string, dir?: string}|null} The sort to use.
|
|
42
|
+
* @spec openspec/changes/screens-page-controls/specs/page-controls/spec.md#requirement-a-list-listens-to-the-pages-controls
|
|
43
|
+
*/
|
|
44
|
+
export function pageSort(pageControls, workspace, own) {
|
|
45
|
+
const listen = pageControls && typeof pageControls === 'object' ? pageControls.sort : null
|
|
46
|
+
if (!listen || typeof listen.key !== 'string' || !listen.map || typeof listen.map !== 'object') {
|
|
47
|
+
return own
|
|
48
|
+
}
|
|
49
|
+
const chosen = workspace ? workspace[listen.key] : undefined
|
|
50
|
+
const entry = chosen !== undefined && chosen !== null ? listen.map[String(chosen)] : null
|
|
51
|
+
return entry && typeof entry === 'object' && typeof entry.field === 'string' && entry.field !== '' ? entry : own
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The filter a list uses once the page's toggle is read: with the toggle on,
|
|
56
|
+
* the keys it `removes` are gone.
|
|
57
|
+
*
|
|
58
|
+
* @param {object|null} pageControls The widget's `pageControls`.
|
|
59
|
+
* @param {object} workspace The plain workspace bag.
|
|
60
|
+
* @param {object} filter The list's own filter (before token resolution).
|
|
61
|
+
* @return {object} The filter to use (the same object when nothing is removed).
|
|
62
|
+
* @spec openspec/changes/screens-page-controls/specs/page-controls/spec.md#requirement-a-list-listens-to-the-pages-controls
|
|
63
|
+
*/
|
|
64
|
+
export function pageFilter(pageControls, workspace, filter) {
|
|
65
|
+
const listen = pageControls && typeof pageControls === 'object' ? pageControls.toggle : null
|
|
66
|
+
const base = filter && typeof filter === 'object' ? filter : {}
|
|
67
|
+
if (!listen || typeof listen.key !== 'string' || !Array.isArray(listen.removes)) {
|
|
68
|
+
return base
|
|
69
|
+
}
|
|
70
|
+
if (!isOn(workspace ? workspace[listen.key] : undefined)) {
|
|
71
|
+
return base
|
|
72
|
+
}
|
|
73
|
+
const out = { ...base }
|
|
74
|
+
for (const key of listen.removes) {
|
|
75
|
+
delete out[key]
|
|
76
|
+
}
|
|
77
|
+
return out
|
|
78
|
+
}
|
|
@@ -37,6 +37,42 @@ export function personalLensTabs(lenses) {
|
|
|
37
37
|
.map((id) => ({ label: labels[id], filter: { [LENS_FILTER_KEYS[id]]: true }, lens: id }))
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Turn the `empty` / `notEmpty` shorthand of quick filters into a filter.
|
|
42
|
+
*
|
|
43
|
+
* `{ label: "Not logged in yet", empty: "lastLoginAt" }` is the rows where the
|
|
44
|
+
* field has no value: the filter `{ lastLoginAt: { isnull: true } }`, which
|
|
45
|
+
* OpenRegister reads as `lastLoginAt[isnull]=true`. `notEmpty` is the rows
|
|
46
|
+
* where it has one. Either takes a field or a list of fields and merges with
|
|
47
|
+
* the entry's own `filter`. Returns the same array when no entry uses it.
|
|
48
|
+
*
|
|
49
|
+
* @param {Array<object>|null} quickFilters The page's quick filters.
|
|
50
|
+
* @return {Array<object>|null} The quick filters with the shorthand expanded.
|
|
51
|
+
*/
|
|
52
|
+
export function expandEmptyFilters(quickFilters) {
|
|
53
|
+
if (!Array.isArray(quickFilters)) {
|
|
54
|
+
return quickFilters
|
|
55
|
+
}
|
|
56
|
+
const uses = (tab) => tab && typeof tab === 'object' && (tab.empty || tab.notEmpty)
|
|
57
|
+
if (!quickFilters.some(uses)) {
|
|
58
|
+
return quickFilters
|
|
59
|
+
}
|
|
60
|
+
const fieldsOf = (value) => (Array.isArray(value) ? value : [value]).filter((f) => typeof f === 'string' && f !== '')
|
|
61
|
+
return quickFilters.map((tab) => {
|
|
62
|
+
if (!uses(tab)) {
|
|
63
|
+
return tab
|
|
64
|
+
}
|
|
65
|
+
const filter = { ...(tab.filter && typeof tab.filter === 'object' ? tab.filter : {}) }
|
|
66
|
+
for (const field of fieldsOf(tab.empty)) {
|
|
67
|
+
filter[field] = { isnull: true }
|
|
68
|
+
}
|
|
69
|
+
for (const field of fieldsOf(tab.notEmpty)) {
|
|
70
|
+
filter[field] = { isnull: false }
|
|
71
|
+
}
|
|
72
|
+
return { ...tab, filter }
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
40
76
|
/**
|
|
41
77
|
* The page's quick filters with its personal lenses appended. A page with no
|
|
42
78
|
* quick filters of its own gets an "All" tab first, so a lens is not active
|
|
@@ -48,10 +84,11 @@ export function personalLensTabs(lenses) {
|
|
|
48
84
|
*/
|
|
49
85
|
export function withPersonalLenses(quickFilters, lenses) {
|
|
50
86
|
const tabs = personalLensTabs(lenses)
|
|
87
|
+
const expanded = expandEmptyFilters(quickFilters)
|
|
51
88
|
if (tabs.length === 0) {
|
|
52
|
-
return
|
|
89
|
+
return expanded
|
|
53
90
|
}
|
|
54
|
-
const own = Array.isArray(
|
|
91
|
+
const own = Array.isArray(expanded) ? expanded : []
|
|
55
92
|
return own.length > 0
|
|
56
93
|
? [...own, ...tabs]
|
|
57
94
|
: [{ label: t('nextcloud-vue', 'All'), filter: {}, default: true }, ...tabs]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* Query presets of an index page (`config.queryPresets`): a menu entry that
|
|
6
|
+
* deep-links to a list with a query (`{ caseType: '<uuid>' }`, the shape hydra
|
|
7
|
+
* gate-68 asks for instead of a second page) can bring its own lenses,
|
|
8
|
+
* columns and copy. The page names, per preset, the query it answers to and
|
|
9
|
+
* the keys it replaces; the first preset whose every `match` pair is in the
|
|
10
|
+
* current route query wins.
|
|
11
|
+
*
|
|
12
|
+
* @module utils/queryPresets
|
|
13
|
+
* @spec openspec/changes/screens-index-query-presets/specs/index-query-presets/spec.md#requirement-a-query-preset-overlays-lenses-columns-and-copy
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The config keys a preset may replace. Anything else in a preset is ignored:
|
|
18
|
+
* a preset changes what the list shows, never which register or schema it
|
|
19
|
+
* reads or what it may write.
|
|
20
|
+
*
|
|
21
|
+
* @type {ReadonlyArray<string>}
|
|
22
|
+
*/
|
|
23
|
+
export const QUERY_PRESET_KEYS = Object.freeze([
|
|
24
|
+
'title',
|
|
25
|
+
'quickFilters',
|
|
26
|
+
'columns',
|
|
27
|
+
'cardFields',
|
|
28
|
+
'countText',
|
|
29
|
+
'searchPlaceholder',
|
|
30
|
+
'footerNote',
|
|
31
|
+
'defaultSort',
|
|
32
|
+
'viewSwitch',
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether one route query value satisfies one `match` value. A repeated query
|
|
37
|
+
* parameter (an array) matches when it holds the value.
|
|
38
|
+
*
|
|
39
|
+
* @param {unknown} actual The route query value.
|
|
40
|
+
* @param {string|number|boolean} expected The preset's value.
|
|
41
|
+
* @return {boolean} True when they match.
|
|
42
|
+
*/
|
|
43
|
+
function queryValueMatches(actual, expected) {
|
|
44
|
+
const want = String(expected)
|
|
45
|
+
if (Array.isArray(actual)) {
|
|
46
|
+
return actual.some((v) => String(v) === want)
|
|
47
|
+
}
|
|
48
|
+
return actual !== undefined && actual !== null && String(actual) === want
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The index of the first preset whose every `match` pair is in the query.
|
|
53
|
+
* A preset without a non-empty `match` never matches.
|
|
54
|
+
*
|
|
55
|
+
* @param {Array<object>} presets The page's `queryPresets`.
|
|
56
|
+
* @param {object} query The current route query.
|
|
57
|
+
* @return {number} The preset index, or -1.
|
|
58
|
+
*/
|
|
59
|
+
export function matchQueryPreset(presets, query) {
|
|
60
|
+
if (!Array.isArray(presets)) {
|
|
61
|
+
return -1
|
|
62
|
+
}
|
|
63
|
+
const q = query && typeof query === 'object' ? query : {}
|
|
64
|
+
return presets.findIndex((preset) => {
|
|
65
|
+
const match = preset && typeof preset.match === 'object' && preset.match !== null ? preset.match : null
|
|
66
|
+
if (!match || Object.keys(match).length === 0) {
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
return Object.entries(match).every(([key, value]) => queryValueMatches(q[key], value))
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The page config with the preset's allowed keys laid over it, and without
|
|
75
|
+
* `queryPresets` itself (the page component has no such prop).
|
|
76
|
+
*
|
|
77
|
+
* @param {object} config The page config.
|
|
78
|
+
* @param {object|null} preset The matched preset, or null.
|
|
79
|
+
* @return {object} The effective config.
|
|
80
|
+
*/
|
|
81
|
+
export function applyQueryPreset(config, preset) {
|
|
82
|
+
const { queryPresets, ...rest } = config || {}
|
|
83
|
+
if (!preset || typeof preset !== 'object') {
|
|
84
|
+
return rest
|
|
85
|
+
}
|
|
86
|
+
const out = { ...rest }
|
|
87
|
+
for (const key of QUERY_PRESET_KEYS) {
|
|
88
|
+
if (key !== 'title' && preset[key] !== undefined) {
|
|
89
|
+
out[key] = preset[key]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return out
|
|
93
|
+
}
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
* - `@today±Nd` → relative-date arithmetic at day granularity: N days
|
|
23
23
|
* from today (e.g. `@today-30d`, `@today+7d`)
|
|
24
24
|
* - `@monthStart` → first day of the current month, `YYYY-MM-DD`
|
|
25
|
+
* - `@monthEnd` → last day of the current month, `YYYY-MM-DD`; on a
|
|
26
|
+
* date-time field use `lt: '@nextMonthStart'` instead,
|
|
27
|
+
* since `lte` a date stops at that day's midnight
|
|
28
|
+
* - `@nextMonthStart` → first day of the next month, `YYYY-MM-DD`
|
|
25
29
|
* - `@quarterStart` → first day of the current quarter, `YYYY-MM-DD`
|
|
26
30
|
* - `@yearStart` → first day of the current year, `YYYY-MM-DD`
|
|
27
31
|
* - `@currentFiscalYear` → the current calendar year as a number string, e.g. `2026`
|
|
@@ -63,7 +67,9 @@
|
|
|
63
67
|
|
|
64
68
|
import { getCurrentUser } from '@nextcloud/auth'
|
|
65
69
|
import { TODAY_DELTA_RE } from './sentinelTokens.js'
|
|
66
|
-
|
|
70
|
+
// userGroupsPeek, not widgetVisibility: that one imports @nextcloud/axios, and
|
|
71
|
+
// this resolver runs on public pages and in node (see userGroupsPeek.js).
|
|
72
|
+
import { peekCurrentUserGroups } from './userGroupsPeek.js'
|
|
67
73
|
|
|
68
74
|
/** The filter token for the current user's group ids. */
|
|
69
75
|
export const MY_GROUPS_TOKEN = '@myGroups'
|
|
@@ -214,6 +220,13 @@ export function resolveFilterValue(v, ctx) {
|
|
|
214
220
|
if (v === '@monthStart') {
|
|
215
221
|
return ymd(new Date(now.getFullYear(), now.getMonth(), 1))
|
|
216
222
|
}
|
|
223
|
+
if (v === '@monthEnd') {
|
|
224
|
+
// Day 0 of the next month is the last day of this one.
|
|
225
|
+
return ymd(new Date(now.getFullYear(), now.getMonth() + 1, 0))
|
|
226
|
+
}
|
|
227
|
+
if (v === '@nextMonthStart') {
|
|
228
|
+
return ymd(new Date(now.getFullYear(), now.getMonth() + 1, 1))
|
|
229
|
+
}
|
|
217
230
|
if (v === '@quarterStart') {
|
|
218
231
|
return ymd(new Date(now.getFullYear(), Math.floor(now.getMonth() / 3) * 3, 1))
|
|
219
232
|
}
|