@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
|
@@ -220,3 +220,103 @@ export async function resolveObjectFolderInfo({ apiBase, register, schema, objec
|
|
|
220
220
|
return null
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Read a dotted path off a value without throwing on a missing segment.
|
|
226
|
+
*
|
|
227
|
+
* @param {(Array|object|string|number|boolean|null|undefined)} value The value.
|
|
228
|
+
* @param {string} path A dotted path; empty answers the value itself.
|
|
229
|
+
* @return {(Array|object|string|number|boolean|null|undefined)} What sits at the path, or undefined.
|
|
230
|
+
*/
|
|
231
|
+
function readPath(value, path) {
|
|
232
|
+
if (typeof path !== 'string' || path === '') {
|
|
233
|
+
return value
|
|
234
|
+
}
|
|
235
|
+
return path.split('.').reduce((acc, key) => (acc === null || acc === undefined ? undefined : acc[key]), value)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Turn a host endpoint's answer into the browser's `rowData`: the host's own
|
|
240
|
+
* record for each file, keyed by file id.
|
|
241
|
+
*
|
|
242
|
+
* The answer may be a list of records (directly, at `path`, or under `items`
|
|
243
|
+
* or `results`), each naming its file in `key` (`fileId` unless the host says
|
|
244
|
+
* otherwise); a record without one is skipped. An object that is not a list
|
|
245
|
+
* and holds neither is taken as already keyed by file id. Anything else is
|
|
246
|
+
* an empty map: the extra columns stay empty, the files still list.
|
|
247
|
+
*
|
|
248
|
+
* @param {(Array|object|string|number|boolean|null|undefined)} data The endpoint answer.
|
|
249
|
+
* @param {object} [options] Where to look.
|
|
250
|
+
* @param {string} [options.path] A dotted path to the list in the answer.
|
|
251
|
+
* @param {string} [options.key] The record field that holds the file id.
|
|
252
|
+
* @return {object} The records by file id.
|
|
253
|
+
*/
|
|
254
|
+
export function indexRowData(data, { path = '', key = 'fileId' } = {}) {
|
|
255
|
+
let picked = readPath(data, path)
|
|
256
|
+
if (picked && typeof picked === 'object' && !Array.isArray(picked)) {
|
|
257
|
+
if (Array.isArray(picked.items)) {
|
|
258
|
+
picked = picked.items
|
|
259
|
+
} else if (Array.isArray(picked.results)) {
|
|
260
|
+
picked = picked.results
|
|
261
|
+
} else {
|
|
262
|
+
return { ...picked }
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (!Array.isArray(picked)) {
|
|
266
|
+
return {}
|
|
267
|
+
}
|
|
268
|
+
const byId = {}
|
|
269
|
+
picked.forEach((record) => {
|
|
270
|
+
if (!record || typeof record !== 'object') {
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
const id = record[key]
|
|
274
|
+
if (id === null || id === undefined || id === '') {
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
byId[String(id)] = record
|
|
278
|
+
})
|
|
279
|
+
return byId
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Whether a host row action is offered on a file, by its `visibleIf`.
|
|
284
|
+
*
|
|
285
|
+
* `visibleIf.extension` lists file extensions (no case, a leading dot is
|
|
286
|
+
* allowed); `visibleIf.mime` lists mime types, where one ending in `/` or
|
|
287
|
+
* `/*` names the whole family. Every condition named must hold. An action
|
|
288
|
+
* without `visibleIf` is offered on every file, as before.
|
|
289
|
+
*
|
|
290
|
+
* @param {object} action The declared action.
|
|
291
|
+
* @param {object} node The file (`basename`, `mime`).
|
|
292
|
+
* @return {boolean} True when the action belongs on this file.
|
|
293
|
+
*/
|
|
294
|
+
export function hostActionApplies(action, node) {
|
|
295
|
+
const condition = action && action.visibleIf
|
|
296
|
+
if (!condition || typeof condition !== 'object') {
|
|
297
|
+
return true
|
|
298
|
+
}
|
|
299
|
+
if (Array.isArray(condition.extension)) {
|
|
300
|
+
const name = String((node && node.basename) || '')
|
|
301
|
+
const dot = name.lastIndexOf('.')
|
|
302
|
+
const extension = dot > 0 ? name.slice(dot + 1).toLowerCase() : ''
|
|
303
|
+
const wanted = condition.extension.map((entry) => String(entry).replace(/^\./, '').toLowerCase())
|
|
304
|
+
if (extension === '' || !wanted.includes(extension)) {
|
|
305
|
+
return false
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (Array.isArray(condition.mime)) {
|
|
309
|
+
const mime = String((node && node.mime) || '').toLowerCase()
|
|
310
|
+
const matches = condition.mime.some((entry) => {
|
|
311
|
+
const wanted = String(entry).toLowerCase()
|
|
312
|
+
if (wanted.endsWith('/*') || wanted.endsWith('/')) {
|
|
313
|
+
return mime.startsWith(wanted.replace(/\*$/, ''))
|
|
314
|
+
}
|
|
315
|
+
return mime === wanted
|
|
316
|
+
})
|
|
317
|
+
if (!matches) {
|
|
318
|
+
return false
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return true
|
|
322
|
+
}
|
|
@@ -219,3 +219,143 @@ export function sortNodesByColumn(nodes, column, ascending, rowData = null, isFo
|
|
|
219
219
|
return String(left).localeCompare(String(right)) * direction
|
|
220
220
|
})
|
|
221
221
|
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Whether a cell value counts as filled in.
|
|
225
|
+
*
|
|
226
|
+
* @param {(Array|object|string|number|boolean|null|undefined)} value The value.
|
|
227
|
+
* @return {boolean} False for nothing, an empty string or an empty list.
|
|
228
|
+
*/
|
|
229
|
+
function isFilled(value) {
|
|
230
|
+
if (value === null || value === undefined || value === '') {
|
|
231
|
+
return false
|
|
232
|
+
}
|
|
233
|
+
return !(Array.isArray(value) && value.length === 0)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The single value a node is grouped under, as a string key: a list groups
|
|
238
|
+
* under its first entry, an object under its label, title, name or id.
|
|
239
|
+
*
|
|
240
|
+
* @param {(Array|object|string|number|boolean|null|undefined)} value The cell value.
|
|
241
|
+
* @return {(string|null)} The group key, or null when nothing is filled in.
|
|
242
|
+
*/
|
|
243
|
+
function groupKeyOf(value) {
|
|
244
|
+
const first = Array.isArray(value) ? value[0] : value
|
|
245
|
+
if (!isFilled(first)) {
|
|
246
|
+
return null
|
|
247
|
+
}
|
|
248
|
+
if (typeof first === 'object') {
|
|
249
|
+
const label = first.label ?? first.title ?? first.name ?? first.id
|
|
250
|
+
return isFilled(label) ? String(label) : null
|
|
251
|
+
}
|
|
252
|
+
return String(first)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* The values a node carries in a column, as strings, for counting and
|
|
257
|
+
* filtering: every entry of a list, the one value otherwise.
|
|
258
|
+
*
|
|
259
|
+
* @param {object} column A resolved column.
|
|
260
|
+
* @param {object} node The node.
|
|
261
|
+
* @param {object} [rowData] The host's per-file data.
|
|
262
|
+
* @return {string[]} The values, deduplicated.
|
|
263
|
+
*/
|
|
264
|
+
function facetValuesOf(column, node, rowData) {
|
|
265
|
+
const value = fileColumnValue(column, node, rowData)
|
|
266
|
+
const entries = Array.isArray(value) ? value : [value]
|
|
267
|
+
const out = []
|
|
268
|
+
entries.forEach((entry) => {
|
|
269
|
+
const key = groupKeyOf(entry)
|
|
270
|
+
if (key !== null && !out.includes(key)) {
|
|
271
|
+
out.push(key)
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
return out
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Group nodes on a column's value, in the order each value is first met.
|
|
279
|
+
* Nodes with no value come last, in one group whose value is null.
|
|
280
|
+
*
|
|
281
|
+
* @param {Array<object>} nodes The nodes, already in display order.
|
|
282
|
+
* @param {object} column The resolved column.
|
|
283
|
+
* @param {object} [rowData] The host's per-file data.
|
|
284
|
+
* @return {Array<{value: (string|null), nodes: Array<object>}>} The groups.
|
|
285
|
+
*/
|
|
286
|
+
export function groupNodes(nodes, column, rowData = null) {
|
|
287
|
+
const groups = []
|
|
288
|
+
const byKey = new Map()
|
|
289
|
+
const empty = { value: null, nodes: [] }
|
|
290
|
+
;(Array.isArray(nodes) ? nodes : []).forEach((node) => {
|
|
291
|
+
const key = column ? groupKeyOf(fileColumnValue(column, node, rowData)) : null
|
|
292
|
+
if (key === null) {
|
|
293
|
+
empty.nodes.push(node)
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
if (!byKey.has(key)) {
|
|
297
|
+
const group = { value: key, nodes: [] }
|
|
298
|
+
byKey.set(key, group)
|
|
299
|
+
groups.push(group)
|
|
300
|
+
}
|
|
301
|
+
byKey.get(key).nodes.push(node)
|
|
302
|
+
})
|
|
303
|
+
if (empty.nodes.length > 0) {
|
|
304
|
+
groups.push(empty)
|
|
305
|
+
}
|
|
306
|
+
return groups
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* The values in use in a column across the nodes, with how many nodes carry
|
|
311
|
+
* each; a list counts once per entry. Most used first, then in first-seen
|
|
312
|
+
* order.
|
|
313
|
+
*
|
|
314
|
+
* @param {Array<object>} nodes The nodes.
|
|
315
|
+
* @param {object} column The resolved column.
|
|
316
|
+
* @param {object} [rowData] The host's per-file data.
|
|
317
|
+
* @return {Array<{value: string, count: number}>} The values and counts.
|
|
318
|
+
*/
|
|
319
|
+
export function facetCounts(nodes, column, rowData = null) {
|
|
320
|
+
const counts = []
|
|
321
|
+
const byValue = new Map()
|
|
322
|
+
;(Array.isArray(nodes) ? nodes : []).forEach((node) => {
|
|
323
|
+
facetValuesOf(column, node, rowData).forEach((value) => {
|
|
324
|
+
if (!byValue.has(value)) {
|
|
325
|
+
const entry = { value, count: 0 }
|
|
326
|
+
byValue.set(value, entry)
|
|
327
|
+
counts.push(entry)
|
|
328
|
+
}
|
|
329
|
+
byValue.get(value).count += 1
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
return counts
|
|
333
|
+
.map((entry, index) => ({ entry, index }))
|
|
334
|
+
.sort((a, b) => (b.entry.count - a.entry.count) || (a.index - b.index))
|
|
335
|
+
.map(({ entry }) => entry)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Whether a node passes the chosen facet values: within one facet any chosen
|
|
340
|
+
* value will do, and every facet with a choice must pass. A facet with
|
|
341
|
+
* nothing chosen does not filter.
|
|
342
|
+
*
|
|
343
|
+
* @param {object} node The node.
|
|
344
|
+
* @param {object} selections Chosen values by column key.
|
|
345
|
+
* @param {object} columnsByKey Resolved columns by key.
|
|
346
|
+
* @param {object} [rowData] The host's per-file data.
|
|
347
|
+
* @return {boolean} True when the node stays in the list.
|
|
348
|
+
*/
|
|
349
|
+
export function nodeMatchesFacets(node, selections, columnsByKey, rowData = null) {
|
|
350
|
+
return Object.entries(selections || {}).every(([key, chosen]) => {
|
|
351
|
+
if (!Array.isArray(chosen) || chosen.length === 0) {
|
|
352
|
+
return true
|
|
353
|
+
}
|
|
354
|
+
const column = columnsByKey && columnsByKey[key]
|
|
355
|
+
if (!column) {
|
|
356
|
+
return true
|
|
357
|
+
}
|
|
358
|
+
const values = facetValuesOf(column, node, rowData)
|
|
359
|
+
return chosen.some((value) => values.includes(value))
|
|
360
|
+
})
|
|
361
|
+
}
|
|
@@ -113,7 +113,7 @@ export default {
|
|
|
113
113
|
| `allowDraft` | `false` | Offer a **Save draft** button that stores the record with a draft marker instead of validating it. Inert unless the schema declares `draftField`. |
|
|
114
114
|
| `draftField` | `'isDraft'` | The boolean property that marks a record as a draft. |
|
|
115
115
|
| `draftAppId` | `''` | The app the draft belongs to, for the storage key. |
|
|
116
|
-
| `draftUserId` | `''` | Who is typing, for the storage key.
|
|
116
|
+
| `draftUserId` | `''` | Who is typing, for the storage key. Empty: the signed-in Nextcloud user, and `anonymous` only when nobody is signed in. |
|
|
117
117
|
|
|
118
118
|
#### What the draft does and does not do
|
|
119
119
|
|
|
@@ -122,9 +122,15 @@ Restore and Discard; the values go in only when somebody presses Restore. A form
|
|
|
122
122
|
that filled itself would be indistinguishable from one the server prefilled, and
|
|
123
123
|
somebody would submit last week's answers without knowing they were there.
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
The user is part of the storage key. A shared browser profile at a service
|
|
126
126
|
desk is the ordinary case in a municipality, and a draft keyed without the user
|
|
127
|
-
hands the next person at the counter what the last one typed.
|
|
127
|
+
hands the next person at the counter what the last one typed. Without
|
|
128
|
+
`draftUserId` the dialog uses the signed-in Nextcloud user. A draft stored
|
|
129
|
+
under `anonymous` is removed, never offered, once somebody is signed in.
|
|
130
|
+
|
|
131
|
+
A draft is written only after a person changes a field, and only while that
|
|
132
|
+
field differs from the value the form opened with. Opening a dialog, or the
|
|
133
|
+
form filling in values by itself, writes nothing and shows no Saving or Saved.
|
|
128
134
|
|
|
129
135
|
A draft is dropped rather than offered when it is more than seven days old, when
|
|
130
136
|
the record has been saved by somebody else since it was written, or when the
|
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
:error="!!errors[field.key]"
|
|
187
187
|
:type="field.widget === 'email' ? 'email' : field.widget === 'url' ? 'url' : 'text'"
|
|
188
188
|
:disabled="field.readOnly"
|
|
189
|
-
:placeholder="field
|
|
189
|
+
:placeholder="fieldPlaceholder(field)"
|
|
190
190
|
@update:modelValue="value => updateField(field.key, value)" />
|
|
191
191
|
<CnFieldHelper
|
|
192
192
|
:id="helpId(field)"
|
|
@@ -208,7 +208,7 @@
|
|
|
208
208
|
:error="!!errors[field.key]"
|
|
209
209
|
type="number"
|
|
210
210
|
:disabled="field.readOnly"
|
|
211
|
-
:placeholder="field
|
|
211
|
+
:placeholder="fieldPlaceholder(field)"
|
|
212
212
|
@update:modelValue="value => updateField(field.key, value !== '' ? Number(value) : null)" />
|
|
213
213
|
<CnFieldHelper
|
|
214
214
|
:id="helpId(field)"
|
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
v-bind="fieldAria(field)"
|
|
231
231
|
:value="formData[field.key] || ''"
|
|
232
232
|
:disabled="field.readOnly"
|
|
233
|
-
:placeholder="field
|
|
233
|
+
:placeholder="fieldPlaceholder(field)"
|
|
234
234
|
rows="4"
|
|
235
235
|
@input="updateField(field.key, $event.target.value)" />
|
|
236
236
|
<CnFieldHelper
|
|
@@ -652,7 +652,7 @@
|
|
|
652
652
|
:modelValue="formData[field.key] != null ? String(formData[field.key]) : ''"
|
|
653
653
|
:error="!!errors[field.key]"
|
|
654
654
|
:disabled="field.readOnly"
|
|
655
|
-
:placeholder="field
|
|
655
|
+
:placeholder="fieldPlaceholder(field)"
|
|
656
656
|
@update:modelValue="value => updateField(field.key, value)" />
|
|
657
657
|
<CnFieldHelper
|
|
658
658
|
:id="helpId(field)"
|
|
@@ -787,7 +787,7 @@ import CnPropertySourceField from '../CnPropertySourceField/CnPropertySourceFiel
|
|
|
787
787
|
import CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'
|
|
788
788
|
import CnSubObjectsField from '../CnSubObjectsField/CnSubObjectsField.vue'
|
|
789
789
|
import { describeRowProblem, useChildRecords } from '../../composables/useChildRecords.js'
|
|
790
|
-
import { draftIndicatorText, draftKey, formDraftMixin, readDraft } from '../../composables/useFormDraft.js'
|
|
790
|
+
import { clearAnonymousDrafts, draftIndicatorText, draftKey, formDraftMixin, hasDraftChanges, readDraft, resolveDraftUserId } from '../../composables/useFormDraft.js'
|
|
791
791
|
import { heldFromFailures, splitHeldFiles, uploadHeldFiles } from '../../composables/useHeldFileUpload.js'
|
|
792
792
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'
|
|
793
793
|
import { TENANT_CONTEXT_KEY } from '../../composables/useTenantContext.js'
|
|
@@ -1172,16 +1172,16 @@ export default {
|
|
|
1172
1172
|
* 🔴 THE USER IS PART OF THE DRAFT KEY. A shared browser profile at a
|
|
1173
1173
|
* service desk is the ordinary case in a municipality, and a draft
|
|
1174
1174
|
* keyed without the user hands the next person at the counter what the
|
|
1175
|
-
* last one typed. A host that passes nothing gets
|
|
1176
|
-
*
|
|
1177
|
-
*
|
|
1175
|
+
* last one typed. A host that passes nothing gets the signed-in
|
|
1176
|
+
* Nextcloud user (`@nextcloud/auth`), and `anonymous` only when
|
|
1177
|
+
* nobody is signed in.
|
|
1178
1178
|
*/
|
|
1179
1179
|
draftAppId: {
|
|
1180
1180
|
type: String,
|
|
1181
1181
|
default: '',
|
|
1182
1182
|
},
|
|
1183
1183
|
|
|
1184
|
-
/** Who is typing, for the draft key. See `draftAppId`. */
|
|
1184
|
+
/** Who is typing, for the draft key. Empty: the signed-in user. See `draftAppId`. */
|
|
1185
1185
|
draftUserId: {
|
|
1186
1186
|
type: String,
|
|
1187
1187
|
default: '',
|
|
@@ -1442,10 +1442,20 @@ export default {
|
|
|
1442
1442
|
appId: this.draftAppId,
|
|
1443
1443
|
schema: (this.schema?.slug || this.schema?.title || ''),
|
|
1444
1444
|
objectId: (this.item?.id || this.item?.uuid || ''),
|
|
1445
|
-
userId: this.
|
|
1445
|
+
userId: this.draftUserOwner,
|
|
1446
1446
|
})
|
|
1447
1447
|
},
|
|
1448
1448
|
|
|
1449
|
+
/**
|
|
1450
|
+
* Who the draft belongs to: `draftUserId`, else the signed-in user.
|
|
1451
|
+
*
|
|
1452
|
+
* @return {string} The user id, or '' when nobody is signed in.
|
|
1453
|
+
* @spec openspec/changes/form-draft-on-change-and-help-text-spacing/specs/form-draft-on-change-and-help-text-spacing/spec.md#requirement-a-draft-belongs-to-the-signed-in-user
|
|
1454
|
+
*/
|
|
1455
|
+
draftUserOwner() {
|
|
1456
|
+
return resolveDraftUserId(this.draftUserId)
|
|
1457
|
+
},
|
|
1458
|
+
|
|
1449
1459
|
/**
|
|
1450
1460
|
* Whether "Save draft" may be offered.
|
|
1451
1461
|
*
|
|
@@ -1757,17 +1767,6 @@ export default {
|
|
|
1757
1767
|
formData: {
|
|
1758
1768
|
deep: true,
|
|
1759
1769
|
handler(values) {
|
|
1760
|
-
// 🔴 THE FIRST CHANGE IS THE COMPONENT SEEDING THE FORM, NOT
|
|
1761
|
-
// SOMEBODY TYPING. Writing a draft for it stores the empty
|
|
1762
|
-
// form over a real one, and the indicator announces "Saving
|
|
1763
|
-
// draft" about work nobody did — an instrument reporting
|
|
1764
|
-
// activity that did not happen, which is the whole class of
|
|
1765
|
-
// defect this library keeps paying down.
|
|
1766
|
-
if (this.draftSeeded !== true) {
|
|
1767
|
-
this.draftSeeded = true
|
|
1768
|
-
return
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
1770
|
// Nothing is written while a draft is being offered: the user
|
|
1772
1771
|
// has not chosen yet, and overwriting the stored draft with the
|
|
1773
1772
|
// empty form they are looking at would destroy the thing they
|
|
@@ -1776,6 +1775,19 @@ export default {
|
|
|
1776
1775
|
return
|
|
1777
1776
|
}
|
|
1778
1777
|
|
|
1778
|
+
// 🔴 ONLY A PERSON'S CHANGE IS WORTH A DRAFT. The form fills
|
|
1779
|
+
// in values by itself after it opens (an input reporting its
|
|
1780
|
+
// value back, normalised dates, defaults, the organisation),
|
|
1781
|
+
// and each of those used to write a draft and say "Saved just
|
|
1782
|
+
// now" on a dialog nobody had touched. A field counts once a
|
|
1783
|
+
// person edited it AND it differs from what the form opened
|
|
1784
|
+
// with, so undoing the edit drops the draft again.
|
|
1785
|
+
const edited = Object.keys(this.touchedFields || {}).filter((key) => this.touchedFields[key])
|
|
1786
|
+
if (!hasDraftChanges(values, this.draftBaseline, edited)) {
|
|
1787
|
+
this.settleUnchangedDraft(this.formDraftKey)
|
|
1788
|
+
return
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1779
1791
|
this.scheduleDraftWrite(this.formDraftKey, values)
|
|
1780
1792
|
},
|
|
1781
1793
|
},
|
|
@@ -1940,6 +1952,12 @@ export default {
|
|
|
1940
1952
|
// Silently refilling a form is how somebody submits last week's answers
|
|
1941
1953
|
// without noticing they were there, and a form that fills itself is
|
|
1942
1954
|
// indistinguishable from one the server prefilled.
|
|
1955
|
+
// A draft stored under `anonymous` was written by a page that did not
|
|
1956
|
+
// know who was typing. Once somebody is signed in it is removed, never
|
|
1957
|
+
// offered: it may be another person's.
|
|
1958
|
+
if (this.formDraftKey && this.draftUserOwner) {
|
|
1959
|
+
clearAnonymousDrafts()
|
|
1960
|
+
}
|
|
1943
1961
|
if (this.formDraftKey) {
|
|
1944
1962
|
this.draftOffer = readDraft(this.formDraftKey, {
|
|
1945
1963
|
objectUpdated: (this.item?.updated || this.item?.['@self']?.updated || ''),
|
|
@@ -2298,6 +2316,10 @@ export default {
|
|
|
2298
2316
|
this.runAssignments(null)
|
|
2299
2317
|
this.loadProfileDefaults()
|
|
2300
2318
|
}
|
|
2319
|
+
// What the form opened with. A draft is written only once a field
|
|
2320
|
+
// a person edited differs from this (see the `formData` watcher).
|
|
2321
|
+
// Not reactive: nothing renders from it.
|
|
2322
|
+
this.draftBaseline = JSON.parse(JSON.stringify(this.formData || {}))
|
|
2301
2323
|
},
|
|
2302
2324
|
|
|
2303
2325
|
/**
|
|
@@ -3085,6 +3107,27 @@ export default {
|
|
|
3085
3107
|
return this.showsOuterLabel(field) ? '' : this.errors[field.key]
|
|
3086
3108
|
},
|
|
3087
3109
|
|
|
3110
|
+
/**
|
|
3111
|
+
* The placeholder of a text, number or textarea control.
|
|
3112
|
+
*
|
|
3113
|
+
* The field's description already shows as the help line under the
|
|
3114
|
+
* control, so the placeholder stays empty rather than repeat it word
|
|
3115
|
+
* for word. The help line is the one kept: it is still there after
|
|
3116
|
+
* somebody typed (a placeholder disappears with the first keystroke),
|
|
3117
|
+
* it is read out through `aria-describedby`, and in the Nextcloud look
|
|
3118
|
+
* a placeholder is hidden until the field has focus anyway. While an
|
|
3119
|
+
* error takes the help line's place, the description returns as the
|
|
3120
|
+
* placeholder so it is not lost.
|
|
3121
|
+
*
|
|
3122
|
+
* @param {object} field The field definition.
|
|
3123
|
+
* @return {string} The placeholder, or '' when the help line says it.
|
|
3124
|
+
* @spec openspec/changes/form-draft-on-change-and-help-text-spacing/specs/form-draft-on-change-and-help-text-spacing/spec.md#requirement-a-field-description-shows-once
|
|
3125
|
+
*/
|
|
3126
|
+
fieldPlaceholder(field) {
|
|
3127
|
+
const description = field.description || ''
|
|
3128
|
+
return this.helperError(field) ? description : ''
|
|
3129
|
+
},
|
|
3130
|
+
|
|
3088
3131
|
/**
|
|
3089
3132
|
* The accessibility attributes of a text control. In both looks an invalid
|
|
3090
3133
|
* control is `aria-invalid` and `aria-describedby` lists the error before
|
|
@@ -4567,6 +4610,11 @@ export default {
|
|
|
4567
4610
|
}
|
|
4568
4611
|
|
|
4569
4612
|
this.formData = { ...this.formData, ...this.draftOffer.values }
|
|
4613
|
+
// What the person typed last time is their edit now: it counts for
|
|
4614
|
+
// the next draft write and survives the record arriving late.
|
|
4615
|
+
for (const key of Object.keys(this.draftOffer.values)) {
|
|
4616
|
+
this.touchedFields[key] = true
|
|
4617
|
+
}
|
|
4570
4618
|
this.draftOffer = null
|
|
4571
4619
|
},
|
|
4572
4620
|
|
|
@@ -116,6 +116,19 @@
|
|
|
116
116
|
v-if="!submitted || mode !== 'public'"
|
|
117
117
|
class="cn-form-page__form"
|
|
118
118
|
@submit.prevent="submit">
|
|
119
|
+
<!-- Spam protection: a field no person sees or reaches. A filled one
|
|
120
|
+
means a bot; the submit then sends nothing (see `honeypot`). -->
|
|
121
|
+
<div v-if="honeypot" class="cn-form-page__honeypot" aria-hidden="true">
|
|
122
|
+
<label :for="`cn-form-page-hp-${honeypot}`">{{ t('nextcloud-vue', 'Leave this field empty') }}</label>
|
|
123
|
+
<input
|
|
124
|
+
:id="`cn-form-page-hp-${honeypot}`"
|
|
125
|
+
v-model="honeypotValue"
|
|
126
|
+
:name="honeypot"
|
|
127
|
+
type="text"
|
|
128
|
+
tabindex="-1"
|
|
129
|
+
autocomplete="off"
|
|
130
|
+
data-testid="cn-form-page-honeypot">
|
|
131
|
+
</div>
|
|
119
132
|
<!-- Paste to fill: only where it may be used (see smartPasteAvailable). -->
|
|
120
133
|
<div v-if="smartPasteAvailable" class="cn-form-page__smart-paste">
|
|
121
134
|
<NcButton type="button" data-testid="cn-form-page-smart-paste" @click="smartPasteOpen = true">
|
|
@@ -397,7 +410,7 @@ import CnFormErrorSummary from '../CnFormErrorSummary/CnFormErrorSummary.vue'
|
|
|
397
410
|
import CnFormField from '../CnFormField/CnFormField.vue'
|
|
398
411
|
import CnStepper from '../CnStepper/CnStepper.vue'
|
|
399
412
|
import { cnRenderFormField } from '../../composables/cnFormFieldRenderer.js'
|
|
400
|
-
import { draftIndicatorText, draftKey, formDraftMixin, readDraft } from '../../composables/useFormDraft.js'
|
|
413
|
+
import { clearAnonymousDrafts, draftIndicatorText, draftKey, formDraftMixin, readDraft, resolveDraftUserId } from '../../composables/useFormDraft.js'
|
|
401
414
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
402
415
|
import { cnSelectAria } from '../../directives/cnSelectAria.js'
|
|
403
416
|
import { loadCurrentUserProfile } from '../../utils/currentUserProfile.js'
|
|
@@ -529,7 +542,7 @@ export default {
|
|
|
529
542
|
default: '',
|
|
530
543
|
},
|
|
531
544
|
|
|
532
|
-
/** Who is typing; part of the draft's storage key so a shared browser profile never leaks a draft between users. */
|
|
545
|
+
/** Who is typing; part of the draft's storage key so a shared browser profile never leaks a draft between users. Empty: the signed-in Nextcloud user. */
|
|
533
546
|
draftUserId: {
|
|
534
547
|
type: String,
|
|
535
548
|
default: '',
|
|
@@ -610,6 +623,17 @@ export default {
|
|
|
610
623
|
default: '',
|
|
611
624
|
},
|
|
612
625
|
|
|
626
|
+
/**
|
|
627
|
+
* Name of a honeypot field for a form anonymous visitors fill in.
|
|
628
|
+
* The field is rendered out of sight and out of the tab order; when
|
|
629
|
+
* it is filled, submit sends nothing and shows the normal success,
|
|
630
|
+
* so a bot learns nothing. Empty (default): no honeypot.
|
|
631
|
+
*/
|
|
632
|
+
honeypot: {
|
|
633
|
+
type: String,
|
|
634
|
+
default: '',
|
|
635
|
+
},
|
|
636
|
+
|
|
613
637
|
/** HTTP method for endpoint mode. POST | PUT | PATCH. */
|
|
614
638
|
submitMethod: {
|
|
615
639
|
type: String,
|
|
@@ -748,6 +772,8 @@ export default {
|
|
|
748
772
|
summaryKeys: [],
|
|
749
773
|
/** Per-field validation error messages, keyed by field.key. */
|
|
750
774
|
fieldErrors: {},
|
|
775
|
+
/** What a bot typed into the honeypot field; stays empty for a person. */
|
|
776
|
+
honeypotValue: '',
|
|
751
777
|
/**
|
|
752
778
|
* Cache of resolved `endpoint`/`source` visibleWhen outcomes,
|
|
753
779
|
* keyed by field.key. Resolved ONCE in `mounted()` — see the
|
|
@@ -949,7 +975,17 @@ export default {
|
|
|
949
975
|
if (this.recoverDraft !== true) {
|
|
950
976
|
return ''
|
|
951
977
|
}
|
|
952
|
-
return draftKey({ appId: this.draftAppId, schema: this.draftScope, objectId: 'new', userId: this.
|
|
978
|
+
return draftKey({ appId: this.draftAppId, schema: this.draftScope, objectId: 'new', userId: this.draftUserOwner })
|
|
979
|
+
},
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Who the draft belongs to: `draftUserId`, else the signed-in user.
|
|
983
|
+
*
|
|
984
|
+
* @return {string} The user id, or '' when nobody is signed in.
|
|
985
|
+
* @spec openspec/changes/form-draft-on-change-and-help-text-spacing/specs/form-draft-on-change-and-help-text-spacing/spec.md#requirement-a-draft-belongs-to-the-signed-in-user
|
|
986
|
+
*/
|
|
987
|
+
draftUserOwner() {
|
|
988
|
+
return resolveDraftUserId(this.draftUserId)
|
|
953
989
|
},
|
|
954
990
|
|
|
955
991
|
/**
|
|
@@ -997,6 +1033,11 @@ export default {
|
|
|
997
1033
|
this.calcTimers = {}
|
|
998
1034
|
// Offered, never applied: a form that fills itself is indistinguishable
|
|
999
1035
|
// from one the server prefilled.
|
|
1036
|
+
// A draft under `anonymous` may be somebody else's: removed once a
|
|
1037
|
+
// signed-in user is known, never offered.
|
|
1038
|
+
if (this.formDraftKey && this.draftUserOwner) {
|
|
1039
|
+
clearAnonymousDrafts()
|
|
1040
|
+
}
|
|
1000
1041
|
if (this.formDraftKey) {
|
|
1001
1042
|
this.draftOffer = readDraft(this.formDraftKey)
|
|
1002
1043
|
}
|
|
@@ -1768,6 +1809,11 @@ export default {
|
|
|
1768
1809
|
}
|
|
1769
1810
|
|
|
1770
1811
|
this.lastError = null
|
|
1812
|
+
if (this.honeypot && this.honeypotValue !== '') {
|
|
1813
|
+
// A bot filled the field no person sees: send nothing, look done.
|
|
1814
|
+
this.submitted = true
|
|
1815
|
+
return
|
|
1816
|
+
}
|
|
1771
1817
|
this.submitting = true
|
|
1772
1818
|
try {
|
|
1773
1819
|
if (this.submitEndpoint) {
|
|
@@ -1793,6 +1839,9 @@ export default {
|
|
|
1793
1839
|
// describes the transport ("Request failed with status code 400")
|
|
1794
1840
|
// and leaves the user to guess which field it meant.
|
|
1795
1841
|
this.lastError = serverErrorMessage(err)
|
|
1842
|
+
// A refusal that names its fields (`findings[]`, as OpenRegister's
|
|
1843
|
+
// form destination check answers 422) is marked on each field.
|
|
1844
|
+
this.showServerFindings(err)
|
|
1796
1845
|
/**
|
|
1797
1846
|
* Submit failure event. Payload is the thrown error / rejected reason.
|
|
1798
1847
|
*
|
|
@@ -1805,6 +1854,45 @@ export default {
|
|
|
1805
1854
|
}
|
|
1806
1855
|
},
|
|
1807
1856
|
|
|
1857
|
+
/**
|
|
1858
|
+
* Put each finding of a refused submit on its field.
|
|
1859
|
+
*
|
|
1860
|
+
* Reads `response.data.findings[]`, each `{ field?, property, message }`,
|
|
1861
|
+
* and marks the field whose key is the finding's `field`, or else its
|
|
1862
|
+
* `property`. Findings on no field of this form stay in the general
|
|
1863
|
+
* error above the form. Nothing happens for an answer without findings.
|
|
1864
|
+
*
|
|
1865
|
+
* @param {object} err The rejected request.
|
|
1866
|
+
* @return {void}
|
|
1867
|
+
*/
|
|
1868
|
+
showServerFindings(err) {
|
|
1869
|
+
const findings = err?.response?.data?.findings
|
|
1870
|
+
if (!Array.isArray(findings) || findings.length === 0) {
|
|
1871
|
+
return
|
|
1872
|
+
}
|
|
1873
|
+
const keys = new Set(this.fields.map((f) => f && f.key))
|
|
1874
|
+
const marked = []
|
|
1875
|
+
for (const finding of findings) {
|
|
1876
|
+
const key = finding && (finding.field || finding.property)
|
|
1877
|
+
if (!key || !keys.has(key) || this.fieldErrors[key]) {
|
|
1878
|
+
continue
|
|
1879
|
+
}
|
|
1880
|
+
this.fieldErrors[key] = String(finding.message || t('nextcloud-vue', 'This answer was refused.'))
|
|
1881
|
+
marked.push(key)
|
|
1882
|
+
}
|
|
1883
|
+
if (marked.length === 0) {
|
|
1884
|
+
return
|
|
1885
|
+
}
|
|
1886
|
+
this.summaryKeys = this.fields.map((f) => f.key).filter((key) => marked.includes(key))
|
|
1887
|
+
if (this.hasSteps) {
|
|
1888
|
+
const stepIndex = this.steps.findIndex((step) => this.stepFields(step).some((f) => f.key === this.summaryKeys[0]))
|
|
1889
|
+
if (stepIndex >= 0) {
|
|
1890
|
+
this.currentStepIndex = stepIndex
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
this.focusFirstError(this.summaryKeys[0])
|
|
1894
|
+
},
|
|
1895
|
+
|
|
1808
1896
|
async submitViaEndpoint() {
|
|
1809
1897
|
const method = (this.submitMethod || 'POST').toLowerCase()
|
|
1810
1898
|
const url = resolveParams(this.submitEndpoint, this.$route?.params || {})
|
|
@@ -1828,6 +1916,14 @@ export default {
|
|
|
1828
1916
|
</script>
|
|
1829
1917
|
|
|
1830
1918
|
<style>
|
|
1919
|
+
.cn-form-page__honeypot {
|
|
1920
|
+
position: absolute;
|
|
1921
|
+
left: -10000px;
|
|
1922
|
+
width: 1px;
|
|
1923
|
+
height: 1px;
|
|
1924
|
+
overflow: hidden;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1831
1927
|
.cn-form-page {
|
|
1832
1928
|
display: flex;
|
|
1833
1929
|
flex-direction: column;
|