@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
|
@@ -199,5 +199,105 @@ async function resolveObjectFolderInfo({ apiBase, register, schema, objectId, ui
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
|
|
202
|
+
/**
|
|
203
|
+
* Read a dotted path off a value without throwing on a missing segment.
|
|
204
|
+
*
|
|
205
|
+
* @param {(Array|object|string|number|boolean|null|undefined)} value The value.
|
|
206
|
+
* @param {string} path A dotted path; empty answers the value itself.
|
|
207
|
+
* @return {(Array|object|string|number|boolean|null|undefined)} What sits at the path, or undefined.
|
|
208
|
+
*/
|
|
209
|
+
function readPath(value, path) {
|
|
210
|
+
if (typeof path !== 'string' || path === '') {
|
|
211
|
+
return value
|
|
212
|
+
}
|
|
213
|
+
return path.split('.').reduce((acc, key) => (acc === null || acc === undefined ? undefined : acc[key]), value)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Turn a host endpoint's answer into the browser's `rowData`: the host's own
|
|
218
|
+
* record for each file, keyed by file id.
|
|
219
|
+
*
|
|
220
|
+
* The answer may be a list of records (directly, at `path`, or under `items`
|
|
221
|
+
* or `results`), each naming its file in `key` (`fileId` unless the host says
|
|
222
|
+
* otherwise); a record without one is skipped. An object that is not a list
|
|
223
|
+
* and holds neither is taken as already keyed by file id. Anything else is
|
|
224
|
+
* an empty map: the extra columns stay empty, the files still list.
|
|
225
|
+
*
|
|
226
|
+
* @param {(Array|object|string|number|boolean|null|undefined)} data The endpoint answer.
|
|
227
|
+
* @param {object} [options] Where to look.
|
|
228
|
+
* @param {string} [options.path] A dotted path to the list in the answer.
|
|
229
|
+
* @param {string} [options.key] The record field that holds the file id.
|
|
230
|
+
* @return {object} The records by file id.
|
|
231
|
+
*/
|
|
232
|
+
function indexRowData(data, { path = '', key = 'fileId' } = {}) {
|
|
233
|
+
let picked = readPath(data, path);
|
|
234
|
+
if (picked && typeof picked === 'object' && !Array.isArray(picked)) {
|
|
235
|
+
if (Array.isArray(picked.items)) {
|
|
236
|
+
picked = picked.items;
|
|
237
|
+
} else if (Array.isArray(picked.results)) {
|
|
238
|
+
picked = picked.results;
|
|
239
|
+
} else {
|
|
240
|
+
return { ...picked }
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (!Array.isArray(picked)) {
|
|
244
|
+
return {}
|
|
245
|
+
}
|
|
246
|
+
const byId = {};
|
|
247
|
+
picked.forEach((record) => {
|
|
248
|
+
if (!record || typeof record !== 'object') {
|
|
249
|
+
return
|
|
250
|
+
}
|
|
251
|
+
const id = record[key];
|
|
252
|
+
if (id === null || id === undefined || id === '') {
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
byId[String(id)] = record;
|
|
256
|
+
});
|
|
257
|
+
return byId
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Whether a host row action is offered on a file, by its `visibleIf`.
|
|
262
|
+
*
|
|
263
|
+
* `visibleIf.extension` lists file extensions (no case, a leading dot is
|
|
264
|
+
* allowed); `visibleIf.mime` lists mime types, where one ending in `/` or
|
|
265
|
+
* `/*` names the whole family. Every condition named must hold. An action
|
|
266
|
+
* without `visibleIf` is offered on every file, as before.
|
|
267
|
+
*
|
|
268
|
+
* @param {object} action The declared action.
|
|
269
|
+
* @param {object} node The file (`basename`, `mime`).
|
|
270
|
+
* @return {boolean} True when the action belongs on this file.
|
|
271
|
+
*/
|
|
272
|
+
function hostActionApplies(action, node) {
|
|
273
|
+
const condition = action && action.visibleIf;
|
|
274
|
+
if (!condition || typeof condition !== 'object') {
|
|
275
|
+
return true
|
|
276
|
+
}
|
|
277
|
+
if (Array.isArray(condition.extension)) {
|
|
278
|
+
const name = String((node && node.basename) || '');
|
|
279
|
+
const dot = name.lastIndexOf('.');
|
|
280
|
+
const extension = dot > 0 ? name.slice(dot + 1).toLowerCase() : '';
|
|
281
|
+
const wanted = condition.extension.map((entry) => String(entry).replace(/^\./, '').toLowerCase());
|
|
282
|
+
if (extension === '' || !wanted.includes(extension)) {
|
|
283
|
+
return false
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (Array.isArray(condition.mime)) {
|
|
287
|
+
const mime = String((node && node.mime) || '').toLowerCase();
|
|
288
|
+
const matches = condition.mime.some((entry) => {
|
|
289
|
+
const wanted = String(entry).toLowerCase();
|
|
290
|
+
if (wanted.endsWith('/*') || wanted.endsWith('/')) {
|
|
291
|
+
return mime.startsWith(wanted.replace(/\*$/, ''))
|
|
292
|
+
}
|
|
293
|
+
return mime === wanted
|
|
294
|
+
});
|
|
295
|
+
if (!matches) {
|
|
296
|
+
return false
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return true
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export { ACTIONS_NEEDING_THE_FILES_PAGE, crumbsFor, fileIdSearchBody, firstHrefIn, hostActionApplies, indexRowData, joinPath, objectDisplayName, resolveObjectFolderInfo, userRelativePathFromHref };
|
|
203
303
|
//# sourceMappingURL=filesBrowser.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesBrowser.js","sources":["../../../../src/components/CnFilesBrowser/filesBrowser.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * The pure half of CnFilesBrowser: paths, crumbs and the folder lookup.\n *\n * Kept out of the component so a test can pin them without a DAV server:\n * a wrong path here sends every listing to the wrong folder, silently.\n */\nimport axios from '@nextcloud/axios'\nimport { generateUrl } from '@nextcloud/router'\n\n/**\n * Actions the Files app registers that cannot run on a page without the Files\n * app's own list: the details action opens the Files sidebar, which on\n * Nextcloud 34 is a store bound to the Files router and node list; the\n * folder-navigation actions route through that list; and rename turns the\n * list's own name cell into an input, which is not here. Verified on a case\n * page: Rename ran and nothing happened. The browser offers its own rename\n * instead, a dialog over a DAV move.\n *\n * @type {string[]}\n */\nexport const ACTIONS_NEEDING_THE_FILES_PAGE = ['details', 'sidebar', 'open-folder', 'view-in-folder', 'rename']\n\n/**\n * A DAV href for a node under the user's files root, as a path relative to\n * that root: `/remote.php/dav/files/admin/Open%20Registers/x/` becomes\n * `/Open Registers/x`.\n *\n * @param {string} href The href as the DAV server returned it.\n * @param {string} uid The current user's id.\n * @return {string} The user-relative path without a trailing slash, `/` at least.\n */\nexport function userRelativePathFromHref(href, uid) {\n\tlet decoded\n\ttry {\n\t\tdecoded = decodeURIComponent(String(href || ''))\n\t} catch {\n\t\tdecoded = String(href || '')\n\t}\n\tconst marker = `/dav/files/${uid}`\n\tconst at = decoded.indexOf(marker)\n\tconst rest = at === -1 ? decoded : decoded.slice(at + marker.length)\n\tconst trimmed = rest.replace(/\\/+$/, '')\n\treturn trimmed === '' ? '/' : trimmed\n}\n\n/**\n * Join a directory and a name into a path, with exactly one slash between.\n *\n * @param {string} dir The directory path.\n * @param {string} name The child name.\n * @return {string} The child's path.\n */\nexport function joinPath(dir, name) {\n\treturn `${String(dir || '').replace(/\\/+$/, '')}/${String(name || '').replace(/^\\/+/, '')}`\n}\n\n/**\n * The breadcrumb trail: every folder from the user's files root down to the\n * open folder, the way the Files app draws it.\n *\n * The folders ABOVE the browser's root are part of the trail too (a reader\n * wants to see where this folder lives), but the browser never navigates\n * above its root, so those crumbs carry `aboveRoot: true` and the component\n * links them into the Files app instead. The root reads `rootLabel` when the\n * caller gave one and its own basename otherwise; every other crumb is the\n * folder's basename.\n *\n * @param {string} rootPath The browser's root, user-relative.\n * @param {string} currentPath The open folder, user-relative, at or below the root.\n * @param {string|null} [rootLabel] What the root crumb reads; null for its basename.\n * @return {Array<{name: string, path: string, aboveRoot: boolean}>} The crumbs, outermost first.\n */\nexport function crumbsFor(rootPath, currentPath, rootLabel = null) {\n\tconst root = String(rootPath || '/').replace(/\\/+$/, '') || '/'\n\tconst current = String(currentPath || root).replace(/\\/+$/, '') || '/'\n\tconst crumbs = []\n\tlet path = ''\n\tfor (const segment of root.split('/').filter(Boolean)) {\n\t\tpath = joinPath(path, segment)\n\t\tcrumbs.push({ name: segment, path, aboveRoot: path !== root })\n\t}\n\tif (root === '/') {\n\t\tcrumbs.push({ name: rootLabel || '/', path: '/', aboveRoot: false })\n\t} else if (rootLabel) {\n\t\tcrumbs[crumbs.length - 1].name = rootLabel\n\t}\n\tif (current === root || !current.startsWith(root === '/' ? '/' : `${root}/`)) {\n\t\treturn crumbs\n\t}\n\tconst below = root === '/' ? current.slice(1) : current.slice(root.length + 1)\n\tpath = root\n\tfor (const segment of below.split('/').filter(Boolean)) {\n\t\tpath = joinPath(path, segment)\n\t\tcrumbs.push({ name: segment, path, aboveRoot: false })\n\t}\n\treturn crumbs\n}\n\n/**\n * The DAV SEARCH body that finds one node by its file id under the user's root.\n *\n * @param {number|string} fileId The Nextcloud file id.\n * @param {string} uid The current user's id.\n * @return {string} The request body.\n */\nexport function fileIdSearchBody(fileId, uid) {\n\tconst id = String(fileId).replace(/[^0-9]/g, '')\n\tconst scope = `/files/${uid}`.replace(/[<>&]/g, '')\n\treturn '<?xml version=\"1.0\"?>'\n\t\t+ '<d:searchrequest xmlns:d=\"DAV:\" xmlns:oc=\"http://owncloud.org/ns\"><d:basicsearch>'\n\t\t+ '<d:select><d:prop><oc:fileid/></d:prop></d:select>'\n\t\t+ `<d:from><d:scope><d:href>${scope}</d:href><d:depth>infinity</d:depth></d:scope></d:from>`\n\t\t+ `<d:where><d:eq><d:prop><oc:fileid/></d:prop><d:literal>${id}</d:literal></d:eq></d:where>`\n\t\t+ '</d:basicsearch></d:searchrequest>'\n}\n\n/**\n * The first `d:href` in a DAV multistatus body, or null.\n *\n * @param {string} xml The response body.\n * @return {string|null} The href.\n */\nexport function firstHrefIn(xml) {\n\ttry {\n\t\tconst doc = new DOMParser().parseFromString(String(xml || ''), 'application/xml')\n\t\tconst href = doc.getElementsByTagNameNS('DAV:', 'href')[0]\n\t\treturn href ? href.textContent : null\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Where an OpenRegister object keeps its files, as a user-relative path.\n *\n * The object carries the folder's file id in `@self.folder`; a DAV search\n * by that id turns it into a path the DAV client can list. Null when the\n * object has no folder, or the search cannot see it (a user the folder is\n * not shared with), in which case the caller falls back to the object's\n * files endpoint.\n *\n * @param {object} options The lookup.\n * @param {string} options.apiBase OpenRegister's API base, `/apps/openregister/api`.\n * @param {string} options.register The register slug.\n * @param {string} options.schema The schema slug.\n * @param {string} options.objectId The object's uuid.\n * @param {string} options.uid The current user's id.\n * @param {string} options.remoteUrl The DAV remote url, `…/remote.php/dav`.\n * @return {Promise<string|null>} The user-relative folder path, or null.\n */\nexport async function resolveObjectFolder(options) {\n\tconst info = await resolveObjectFolderInfo(options)\n\treturn info === null ? null : info.path\n}\n\n/**\n * The object's display name: `@self.name`, else `title`, else `name`. A\n * value equal to the object's uuid is no name (the folder already reads so).\n *\n * @param {object|null} object The object as OpenRegister returns it.\n * @param {string} objectId The object's uuid.\n * @return {string|null} The name, or null.\n * @spec openspec/changes/dutch-library-strings-and-files-crumb/specs/dutch-library-strings-and-files-crumb/spec.md#requirement-the-files-browser-names-the-object-in-its-root-crumb\n */\nexport function objectDisplayName(object, objectId) {\n\tconst candidates = [object?.['@self']?.name, object?.title, object?.name]\n\tfor (const candidate of candidates) {\n\t\tif (typeof candidate === 'string' && candidate.trim() !== '' && candidate.trim() !== String(objectId)) {\n\t\t\treturn candidate.trim()\n\t\t}\n\t}\n\treturn null\n}\n\n/**\n * `resolveObjectFolder`, plus the object's display name from the same read,\n * so the browser can name the root crumb after the object instead of the\n * uuid its folder is called on disk.\n *\n * @param {object} options The lookup, as for `resolveObjectFolder`.\n * @param {string} options.apiBase OpenRegister's API base, `/apps/openregister/api`.\n * @param {string} options.register The register slug.\n * @param {string} options.schema The schema slug.\n * @param {string} options.objectId The object's uuid.\n * @param {string} options.uid The current user's id.\n * @param {string} options.remoteUrl The DAV remote url, `…/remote.php/dav`.\n * @return {Promise<{path: string, name: (string|null)}|null>} The folder and the object's name, or null.\n * @spec openspec/changes/dutch-library-strings-and-files-crumb/specs/dutch-library-strings-and-files-crumb/spec.md#requirement-the-files-browser-names-the-object-in-its-root-crumb\n */\nexport async function resolveObjectFolderInfo({ apiBase, register, schema, objectId, uid, remoteUrl }) {\n\tif (!register || !schema || !objectId || !uid) {\n\t\treturn null\n\t}\n\tlet folderId\n\tlet name\n\ttry {\n\t\tconst { data } = await axios.get(generateUrl(`${apiBase}/objects/{register}/{schema}/{objectId}`, { register, schema, objectId }))\n\t\tfolderId = data?.['@self']?.folder ?? null\n\t\tname = objectDisplayName(data, objectId)\n\t} catch {\n\t\treturn null\n\t}\n\tif (folderId === null || folderId === '' || Number.isNaN(Number(folderId))) {\n\t\treturn null\n\t}\n\ttry {\n\t\tconst response = await axios.request({\n\t\t\tmethod: 'SEARCH',\n\t\t\turl: `${remoteUrl.replace(/\\/+$/, '')}/`,\n\t\t\theaders: { 'Content-Type': 'application/xml' },\n\t\t\tdata: fileIdSearchBody(folderId, uid),\n\t\t\tresponseType: 'text',\n\t\t})\n\t\tconst href = firstHrefIn(response.data)\n\t\treturn href === null ? null : { path: userRelativePathFromHref(href, uid), name }\n\t} catch {\n\t\treturn null\n\t}\n}\n"],"names":["axios"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"filesBrowser.js","sources":["../../../../src/components/CnFilesBrowser/filesBrowser.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * The pure half of CnFilesBrowser: paths, crumbs and the folder lookup.\n *\n * Kept out of the component so a test can pin them without a DAV server:\n * a wrong path here sends every listing to the wrong folder, silently.\n */\nimport axios from '@nextcloud/axios'\nimport { generateUrl } from '@nextcloud/router'\n\n/**\n * Actions the Files app registers that cannot run on a page without the Files\n * app's own list: the details action opens the Files sidebar, which on\n * Nextcloud 34 is a store bound to the Files router and node list; the\n * folder-navigation actions route through that list; and rename turns the\n * list's own name cell into an input, which is not here. Verified on a case\n * page: Rename ran and nothing happened. The browser offers its own rename\n * instead, a dialog over a DAV move.\n *\n * @type {string[]}\n */\nexport const ACTIONS_NEEDING_THE_FILES_PAGE = ['details', 'sidebar', 'open-folder', 'view-in-folder', 'rename']\n\n/**\n * A DAV href for a node under the user's files root, as a path relative to\n * that root: `/remote.php/dav/files/admin/Open%20Registers/x/` becomes\n * `/Open Registers/x`.\n *\n * @param {string} href The href as the DAV server returned it.\n * @param {string} uid The current user's id.\n * @return {string} The user-relative path without a trailing slash, `/` at least.\n */\nexport function userRelativePathFromHref(href, uid) {\n\tlet decoded\n\ttry {\n\t\tdecoded = decodeURIComponent(String(href || ''))\n\t} catch {\n\t\tdecoded = String(href || '')\n\t}\n\tconst marker = `/dav/files/${uid}`\n\tconst at = decoded.indexOf(marker)\n\tconst rest = at === -1 ? decoded : decoded.slice(at + marker.length)\n\tconst trimmed = rest.replace(/\\/+$/, '')\n\treturn trimmed === '' ? '/' : trimmed\n}\n\n/**\n * Join a directory and a name into a path, with exactly one slash between.\n *\n * @param {string} dir The directory path.\n * @param {string} name The child name.\n * @return {string} The child's path.\n */\nexport function joinPath(dir, name) {\n\treturn `${String(dir || '').replace(/\\/+$/, '')}/${String(name || '').replace(/^\\/+/, '')}`\n}\n\n/**\n * The breadcrumb trail: every folder from the user's files root down to the\n * open folder, the way the Files app draws it.\n *\n * The folders ABOVE the browser's root are part of the trail too (a reader\n * wants to see where this folder lives), but the browser never navigates\n * above its root, so those crumbs carry `aboveRoot: true` and the component\n * links them into the Files app instead. The root reads `rootLabel` when the\n * caller gave one and its own basename otherwise; every other crumb is the\n * folder's basename.\n *\n * @param {string} rootPath The browser's root, user-relative.\n * @param {string} currentPath The open folder, user-relative, at or below the root.\n * @param {string|null} [rootLabel] What the root crumb reads; null for its basename.\n * @return {Array<{name: string, path: string, aboveRoot: boolean}>} The crumbs, outermost first.\n */\nexport function crumbsFor(rootPath, currentPath, rootLabel = null) {\n\tconst root = String(rootPath || '/').replace(/\\/+$/, '') || '/'\n\tconst current = String(currentPath || root).replace(/\\/+$/, '') || '/'\n\tconst crumbs = []\n\tlet path = ''\n\tfor (const segment of root.split('/').filter(Boolean)) {\n\t\tpath = joinPath(path, segment)\n\t\tcrumbs.push({ name: segment, path, aboveRoot: path !== root })\n\t}\n\tif (root === '/') {\n\t\tcrumbs.push({ name: rootLabel || '/', path: '/', aboveRoot: false })\n\t} else if (rootLabel) {\n\t\tcrumbs[crumbs.length - 1].name = rootLabel\n\t}\n\tif (current === root || !current.startsWith(root === '/' ? '/' : `${root}/`)) {\n\t\treturn crumbs\n\t}\n\tconst below = root === '/' ? current.slice(1) : current.slice(root.length + 1)\n\tpath = root\n\tfor (const segment of below.split('/').filter(Boolean)) {\n\t\tpath = joinPath(path, segment)\n\t\tcrumbs.push({ name: segment, path, aboveRoot: false })\n\t}\n\treturn crumbs\n}\n\n/**\n * The DAV SEARCH body that finds one node by its file id under the user's root.\n *\n * @param {number|string} fileId The Nextcloud file id.\n * @param {string} uid The current user's id.\n * @return {string} The request body.\n */\nexport function fileIdSearchBody(fileId, uid) {\n\tconst id = String(fileId).replace(/[^0-9]/g, '')\n\tconst scope = `/files/${uid}`.replace(/[<>&]/g, '')\n\treturn '<?xml version=\"1.0\"?>'\n\t\t+ '<d:searchrequest xmlns:d=\"DAV:\" xmlns:oc=\"http://owncloud.org/ns\"><d:basicsearch>'\n\t\t+ '<d:select><d:prop><oc:fileid/></d:prop></d:select>'\n\t\t+ `<d:from><d:scope><d:href>${scope}</d:href><d:depth>infinity</d:depth></d:scope></d:from>`\n\t\t+ `<d:where><d:eq><d:prop><oc:fileid/></d:prop><d:literal>${id}</d:literal></d:eq></d:where>`\n\t\t+ '</d:basicsearch></d:searchrequest>'\n}\n\n/**\n * The first `d:href` in a DAV multistatus body, or null.\n *\n * @param {string} xml The response body.\n * @return {string|null} The href.\n */\nexport function firstHrefIn(xml) {\n\ttry {\n\t\tconst doc = new DOMParser().parseFromString(String(xml || ''), 'application/xml')\n\t\tconst href = doc.getElementsByTagNameNS('DAV:', 'href')[0]\n\t\treturn href ? href.textContent : null\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Where an OpenRegister object keeps its files, as a user-relative path.\n *\n * The object carries the folder's file id in `@self.folder`; a DAV search\n * by that id turns it into a path the DAV client can list. Null when the\n * object has no folder, or the search cannot see it (a user the folder is\n * not shared with), in which case the caller falls back to the object's\n * files endpoint.\n *\n * @param {object} options The lookup.\n * @param {string} options.apiBase OpenRegister's API base, `/apps/openregister/api`.\n * @param {string} options.register The register slug.\n * @param {string} options.schema The schema slug.\n * @param {string} options.objectId The object's uuid.\n * @param {string} options.uid The current user's id.\n * @param {string} options.remoteUrl The DAV remote url, `…/remote.php/dav`.\n * @return {Promise<string|null>} The user-relative folder path, or null.\n */\nexport async function resolveObjectFolder(options) {\n\tconst info = await resolveObjectFolderInfo(options)\n\treturn info === null ? null : info.path\n}\n\n/**\n * The object's display name: `@self.name`, else `title`, else `name`. A\n * value equal to the object's uuid is no name (the folder already reads so).\n *\n * @param {object|null} object The object as OpenRegister returns it.\n * @param {string} objectId The object's uuid.\n * @return {string|null} The name, or null.\n * @spec openspec/changes/dutch-library-strings-and-files-crumb/specs/dutch-library-strings-and-files-crumb/spec.md#requirement-the-files-browser-names-the-object-in-its-root-crumb\n */\nexport function objectDisplayName(object, objectId) {\n\tconst candidates = [object?.['@self']?.name, object?.title, object?.name]\n\tfor (const candidate of candidates) {\n\t\tif (typeof candidate === 'string' && candidate.trim() !== '' && candidate.trim() !== String(objectId)) {\n\t\t\treturn candidate.trim()\n\t\t}\n\t}\n\treturn null\n}\n\n/**\n * `resolveObjectFolder`, plus the object's display name from the same read,\n * so the browser can name the root crumb after the object instead of the\n * uuid its folder is called on disk.\n *\n * @param {object} options The lookup, as for `resolveObjectFolder`.\n * @param {string} options.apiBase OpenRegister's API base, `/apps/openregister/api`.\n * @param {string} options.register The register slug.\n * @param {string} options.schema The schema slug.\n * @param {string} options.objectId The object's uuid.\n * @param {string} options.uid The current user's id.\n * @param {string} options.remoteUrl The DAV remote url, `…/remote.php/dav`.\n * @return {Promise<{path: string, name: (string|null)}|null>} The folder and the object's name, or null.\n * @spec openspec/changes/dutch-library-strings-and-files-crumb/specs/dutch-library-strings-and-files-crumb/spec.md#requirement-the-files-browser-names-the-object-in-its-root-crumb\n */\nexport async function resolveObjectFolderInfo({ apiBase, register, schema, objectId, uid, remoteUrl }) {\n\tif (!register || !schema || !objectId || !uid) {\n\t\treturn null\n\t}\n\tlet folderId\n\tlet name\n\ttry {\n\t\tconst { data } = await axios.get(generateUrl(`${apiBase}/objects/{register}/{schema}/{objectId}`, { register, schema, objectId }))\n\t\tfolderId = data?.['@self']?.folder ?? null\n\t\tname = objectDisplayName(data, objectId)\n\t} catch {\n\t\treturn null\n\t}\n\tif (folderId === null || folderId === '' || Number.isNaN(Number(folderId))) {\n\t\treturn null\n\t}\n\ttry {\n\t\tconst response = await axios.request({\n\t\t\tmethod: 'SEARCH',\n\t\t\turl: `${remoteUrl.replace(/\\/+$/, '')}/`,\n\t\t\theaders: { 'Content-Type': 'application/xml' },\n\t\t\tdata: fileIdSearchBody(folderId, uid),\n\t\t\tresponseType: 'text',\n\t\t})\n\t\tconst href = firstHrefIn(response.data)\n\t\treturn href === null ? null : { path: userRelativePathFromHref(href, uid), name }\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Read a dotted path off a value without throwing on a missing segment.\n *\n * @param {(Array|object|string|number|boolean|null|undefined)} value The value.\n * @param {string} path A dotted path; empty answers the value itself.\n * @return {(Array|object|string|number|boolean|null|undefined)} What sits at the path, or undefined.\n */\nfunction readPath(value, path) {\n\tif (typeof path !== 'string' || path === '') {\n\t\treturn value\n\t}\n\treturn path.split('.').reduce((acc, key) => (acc === null || acc === undefined ? undefined : acc[key]), value)\n}\n\n/**\n * Turn a host endpoint's answer into the browser's `rowData`: the host's own\n * record for each file, keyed by file id.\n *\n * The answer may be a list of records (directly, at `path`, or under `items`\n * or `results`), each naming its file in `key` (`fileId` unless the host says\n * otherwise); a record without one is skipped. An object that is not a list\n * and holds neither is taken as already keyed by file id. Anything else is\n * an empty map: the extra columns stay empty, the files still list.\n *\n * @param {(Array|object|string|number|boolean|null|undefined)} data The endpoint answer.\n * @param {object} [options] Where to look.\n * @param {string} [options.path] A dotted path to the list in the answer.\n * @param {string} [options.key] The record field that holds the file id.\n * @return {object} The records by file id.\n */\nexport function indexRowData(data, { path = '', key = 'fileId' } = {}) {\n\tlet picked = readPath(data, path)\n\tif (picked && typeof picked === 'object' && !Array.isArray(picked)) {\n\t\tif (Array.isArray(picked.items)) {\n\t\t\tpicked = picked.items\n\t\t} else if (Array.isArray(picked.results)) {\n\t\t\tpicked = picked.results\n\t\t} else {\n\t\t\treturn { ...picked }\n\t\t}\n\t}\n\tif (!Array.isArray(picked)) {\n\t\treturn {}\n\t}\n\tconst byId = {}\n\tpicked.forEach((record) => {\n\t\tif (!record || typeof record !== 'object') {\n\t\t\treturn\n\t\t}\n\t\tconst id = record[key]\n\t\tif (id === null || id === undefined || id === '') {\n\t\t\treturn\n\t\t}\n\t\tbyId[String(id)] = record\n\t})\n\treturn byId\n}\n\n/**\n * Whether a host row action is offered on a file, by its `visibleIf`.\n *\n * `visibleIf.extension` lists file extensions (no case, a leading dot is\n * allowed); `visibleIf.mime` lists mime types, where one ending in `/` or\n * `/*` names the whole family. Every condition named must hold. An action\n * without `visibleIf` is offered on every file, as before.\n *\n * @param {object} action The declared action.\n * @param {object} node The file (`basename`, `mime`).\n * @return {boolean} True when the action belongs on this file.\n */\nexport function hostActionApplies(action, node) {\n\tconst condition = action && action.visibleIf\n\tif (!condition || typeof condition !== 'object') {\n\t\treturn true\n\t}\n\tif (Array.isArray(condition.extension)) {\n\t\tconst name = String((node && node.basename) || '')\n\t\tconst dot = name.lastIndexOf('.')\n\t\tconst extension = dot > 0 ? name.slice(dot + 1).toLowerCase() : ''\n\t\tconst wanted = condition.extension.map((entry) => String(entry).replace(/^\\./, '').toLowerCase())\n\t\tif (extension === '' || !wanted.includes(extension)) {\n\t\t\treturn false\n\t\t}\n\t}\n\tif (Array.isArray(condition.mime)) {\n\t\tconst mime = String((node && node.mime) || '').toLowerCase()\n\t\tconst matches = condition.mime.some((entry) => {\n\t\t\tconst wanted = String(entry).toLowerCase()\n\t\t\tif (wanted.endsWith('/*') || wanted.endsWith('/')) {\n\t\t\t\treturn mime.startsWith(wanted.replace(/\\*$/, ''))\n\t\t\t}\n\t\t\treturn mime === wanted\n\t\t})\n\t\tif (!matches) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"],"names":["axios"],"mappings":"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"}
|
|
@@ -220,5 +220,145 @@ function sortNodesByColumn(nodes, column, ascending, rowData = null, isFolder =
|
|
|
220
220
|
})
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
|
|
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
|
+
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
|
+
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
|
+
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
|
+
}
|
|
362
|
+
|
|
363
|
+
export { BUILT_IN_FILE_COLUMNS, DEFAULT_FILE_COLUMNS, attributePropertiesFor, facetCounts, fileColumnValue, groupNodes, isBuiltInFileColumn, nodeMatchesFacets, normaliseFileColumns, sortNodesByColumn, visibleFileColumns };
|
|
224
364
|
//# sourceMappingURL=filesBrowserColumns.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesBrowserColumns.js","sources":["../../../../src/components/CnFilesBrowser/filesBrowserColumns.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/**\n * The built-in columns a host may name by string, and what each one reads.\n *\n * `nodeKey` is the property on the @nextcloud/files node. `sortKey` is what\n * `sortNodes` understands, so a built-in sorts the way the Files app sorts;\n * a built-in with no `sortKey` sorts client-side like a declared column.\n *\n * @type {object}\n */\nexport const BUILT_IN_FILE_COLUMNS = {\n\tname: { key: 'name', source: 'node', nodeKey: 'basename', sortKey: 'basename', sortable: true },\n\tsize: { key: 'size', source: 'node', nodeKey: 'size', sortKey: 'size', sortable: true },\n\tmodified: { key: 'modified', source: 'node', nodeKey: 'mtime', sortKey: 'mtime', sortable: true },\n\towner: { key: 'owner', source: 'node', nodeKey: 'owner', sortable: true },\n\ttype: { key: 'type', source: 'node', nodeKey: 'mime', sortable: true },\n\ttags: { key: 'tags', source: 'node', nodeKey: 'attributes.system-tags', sortable: false },\n}\n\n/**\n * The columns a browser shows when its host declares none: what the table has\n * always shown, in the order it has always shown it.\n *\n * @type {string[]}\n */\nexport const DEFAULT_FILE_COLUMNS = ['name', 'size', 'modified']\n\n/**\n * Read a dotted path off a node without throwing on a missing segment.\n *\n * @param {object} node The node.\n * @param {string} path A dotted path.\n * @return {(Array|object|string|number|boolean|undefined)} The value.\n */\nfunction atPath(node, path) {\n\tif (!node || typeof path !== 'string' || path === '') {\n\t\treturn undefined\n\t}\n\treturn path.split('.').reduce((acc, key) => (acc === null || acc === undefined ? undefined : acc[key]), node)\n}\n\n/**\n * Whether a string names a built-in column.\n *\n * @param {string} name The candidate name.\n * @return {boolean} True when it is a built-in.\n */\nexport function isBuiltInFileColumn(name) {\n\treturn typeof name === 'string' && Object.hasOwn(BUILT_IN_FILE_COLUMNS, name)\n}\n\n/**\n * Resolve a host's `columns` declaration into column objects.\n *\n * A string names a built-in. An object declares its own: `key`, `label`,\n * `source` (`node`, `attribute` or `row`), `attribute`, `formatter` and\n * `sortable`. An entry naming an unknown built-in is dropped here and refused\n * by the manifest validator, so an author hears about it at authoring time\n * rather than finding a column missing at runtime.\n *\n * @param {(Array|null|undefined)} columns The declaration, or nothing.\n * @return {Array<object>} The resolved columns, in the declared order.\n */\nexport function normaliseFileColumns(columns) {\n\tconst declared = Array.isArray(columns) && columns.length > 0 ? columns : DEFAULT_FILE_COLUMNS\n\tconst out = []\n\tdeclared.forEach((entry) => {\n\t\tif (typeof entry === 'string') {\n\t\t\tif (isBuiltInFileColumn(entry)) {\n\t\t\t\tout.push({ ...BUILT_IN_FILE_COLUMNS[entry], builtIn: true })\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif (!entry || typeof entry !== 'object' || typeof entry.key !== 'string' || entry.key === '') {\n\t\t\treturn\n\t\t}\n\t\tif (isBuiltInFileColumn(entry.key) && entry.source === undefined) {\n\t\t\tout.push({ ...BUILT_IN_FILE_COLUMNS[entry.key], ...entry, builtIn: true })\n\t\t\treturn\n\t\t}\n\t\tout.push({\n\t\t\tsource: 'node',\n\t\t\tsortable: false,\n\t\t\t...entry,\n\t\t\tbuiltIn: false,\n\t\t})\n\t})\n\treturn out\n}\n\n/**\n * The columns a browser renders: the host's declaration, narrowed by what this\n * user chose to see.\n *\n * Which side wins on what:\n *\n * - The HOST decides MEMBERSHIP. The result is built by walking the\n * declaration, so a column exists only because the host declared it, in the\n * order the host declared it.\n * - The USER decides VISIBILITY, and only downward. A stored preference can\n * hide a declared column; it can never add one. A key in the stored list\n * that the host no longer declares is passed over, so unticking a column\n * last month cannot bring it back once the host removed it, and neither can\n * a preference written while a different column set was declared.\n *\n * A stored value of `null` is a user who has never chosen, which shows\n * everything. An empty list is a user who hid everything, which is a choice\n * and is honoured.\n *\n * @param {Array<object>} declared The resolved declared columns.\n * @param {(string[]|null|undefined)} visibleKeys The user's stored visible keys.\n * @return {Array<object>} The columns to render.\n */\nexport function visibleFileColumns(declared, visibleKeys) {\n\tconst columns = Array.isArray(declared) ? declared : []\n\tif (!Array.isArray(visibleKeys)) {\n\t\treturn columns\n\t}\n\tconst wanted = new Set(visibleKeys)\n\treturn columns.filter((column) => wanted.has(column.key))\n}\n\n/**\n * The DAV properties a declaration needs in the PROPFIND, so an `attribute`\n * column's value is there when the folder lists rather than one request later.\n *\n * @param {Array<object>} columns The resolved columns.\n * @return {string[]} The property names, deduplicated, in declaration order.\n */\nexport function attributePropertiesFor(columns) {\n\tconst out = []\n\t;(Array.isArray(columns) ? columns : []).forEach((column) => {\n\t\tif (column && column.source === 'attribute' && typeof column.attribute === 'string' && column.attribute !== '') {\n\t\t\tif (!out.includes(column.attribute)) {\n\t\t\t\tout.push(column.attribute)\n\t\t\t}\n\t\t}\n\t})\n\treturn out\n}\n\n/**\n * The value one column shows for one node.\n *\n * @param {object} column A resolved column.\n * @param {object} node The node.\n * @param {object} [rowData] The host's per-file data, keyed by file id.\n * @return {(Array|object|string|number|boolean|undefined)} The cell value.\n */\nexport function fileColumnValue(column, node, rowData = null) {\n\tif (!column || !node) {\n\t\treturn undefined\n\t}\n\tif (column.source === 'attribute') {\n\t\t// Read the key directly, NOT through the dotted-path reader: a DAV\n\t\t// property name is `{http://owncloud.org/ns}av-status`, and splitting\n\t\t// that on dots looks for a property called `org/ns}av-status` inside\n\t\t// one called `owncloud`, which is undefined every time.\n\t\tconst attributes = node.attributes\n\t\treturn attributes && typeof attributes === 'object' ? attributes[column.attribute] : undefined\n\t}\n\tif (column.source === 'row') {\n\t\tconst byId = rowData && typeof rowData === 'object' ? rowData : null\n\t\tif (!byId) {\n\t\t\treturn undefined\n\t\t}\n\t\tconst entry = byId[node.fileid] ?? byId[String(node.fileid)]\n\t\treturn entry ? entry[column.key] : undefined\n\t}\n\treturn atPath(node, column.nodeKey || column.key)\n}\n\n/**\n * Sort nodes on a declared column's resolved value, within the listed folder.\n *\n * Folders stay first, the way the Files app lists them and the way the\n * built-in sorts already do, so switching to a declared column does not\n * reshuffle the folders into the files.\n *\n * @param {Array<object>} nodes The nodes.\n * @param {object} column The resolved column.\n * @param {boolean} ascending Sort direction.\n * @param {object} [rowData] The host's per-file data.\n * @param {(node: object) => boolean} [isFolder] Whether a node is a folder.\n * @return {Array<object>} A new, sorted array.\n */\nexport function sortNodesByColumn(nodes, column, ascending, rowData = null, isFolder = () => false) {\n\tconst list = Array.isArray(nodes) ? [...nodes] : []\n\tif (!column) {\n\t\treturn list\n\t}\n\tconst direction = ascending ? 1 : -1\n\treturn list.sort((a, b) => {\n\t\tconst folderA = isFolder(a)\n\t\tconst folderB = isFolder(b)\n\t\tif (folderA !== folderB) {\n\t\t\treturn folderA ? -1 : 1\n\t\t}\n\t\tconst left = fileColumnValue(column, a, rowData)\n\t\tconst right = fileColumnValue(column, b, rowData)\n\t\tconst leftMissing = left === undefined || left === null || left === ''\n\t\tconst rightMissing = right === undefined || right === null || right === ''\n\t\t// A value nobody filled in sorts last in BOTH directions. Letting it\n\t\t// ride to the top on a descending sort hides the rows that do carry\n\t\t// one, which is the opposite of what sorting was asked for.\n\t\tif (leftMissing !== rightMissing) {\n\t\t\treturn leftMissing ? 1 : -1\n\t\t}\n\t\tif (leftMissing && rightMissing) {\n\t\t\treturn 0\n\t\t}\n\t\tif (typeof left === 'number' && typeof right === 'number') {\n\t\t\treturn (left - right) * direction\n\t\t}\n\t\treturn String(left).localeCompare(String(right)) * direction\n\t})\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"filesBrowserColumns.js","sources":["../../../../src/components/CnFilesBrowser/filesBrowserColumns.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/**\n * The built-in columns a host may name by string, and what each one reads.\n *\n * `nodeKey` is the property on the @nextcloud/files node. `sortKey` is what\n * `sortNodes` understands, so a built-in sorts the way the Files app sorts;\n * a built-in with no `sortKey` sorts client-side like a declared column.\n *\n * @type {object}\n */\nexport const BUILT_IN_FILE_COLUMNS = {\n\tname: { key: 'name', source: 'node', nodeKey: 'basename', sortKey: 'basename', sortable: true },\n\tsize: { key: 'size', source: 'node', nodeKey: 'size', sortKey: 'size', sortable: true },\n\tmodified: { key: 'modified', source: 'node', nodeKey: 'mtime', sortKey: 'mtime', sortable: true },\n\towner: { key: 'owner', source: 'node', nodeKey: 'owner', sortable: true },\n\ttype: { key: 'type', source: 'node', nodeKey: 'mime', sortable: true },\n\ttags: { key: 'tags', source: 'node', nodeKey: 'attributes.system-tags', sortable: false },\n}\n\n/**\n * The columns a browser shows when its host declares none: what the table has\n * always shown, in the order it has always shown it.\n *\n * @type {string[]}\n */\nexport const DEFAULT_FILE_COLUMNS = ['name', 'size', 'modified']\n\n/**\n * Read a dotted path off a node without throwing on a missing segment.\n *\n * @param {object} node The node.\n * @param {string} path A dotted path.\n * @return {(Array|object|string|number|boolean|undefined)} The value.\n */\nfunction atPath(node, path) {\n\tif (!node || typeof path !== 'string' || path === '') {\n\t\treturn undefined\n\t}\n\treturn path.split('.').reduce((acc, key) => (acc === null || acc === undefined ? undefined : acc[key]), node)\n}\n\n/**\n * Whether a string names a built-in column.\n *\n * @param {string} name The candidate name.\n * @return {boolean} True when it is a built-in.\n */\nexport function isBuiltInFileColumn(name) {\n\treturn typeof name === 'string' && Object.hasOwn(BUILT_IN_FILE_COLUMNS, name)\n}\n\n/**\n * Resolve a host's `columns` declaration into column objects.\n *\n * A string names a built-in. An object declares its own: `key`, `label`,\n * `source` (`node`, `attribute` or `row`), `attribute`, `formatter` and\n * `sortable`. An entry naming an unknown built-in is dropped here and refused\n * by the manifest validator, so an author hears about it at authoring time\n * rather than finding a column missing at runtime.\n *\n * @param {(Array|null|undefined)} columns The declaration, or nothing.\n * @return {Array<object>} The resolved columns, in the declared order.\n */\nexport function normaliseFileColumns(columns) {\n\tconst declared = Array.isArray(columns) && columns.length > 0 ? columns : DEFAULT_FILE_COLUMNS\n\tconst out = []\n\tdeclared.forEach((entry) => {\n\t\tif (typeof entry === 'string') {\n\t\t\tif (isBuiltInFileColumn(entry)) {\n\t\t\t\tout.push({ ...BUILT_IN_FILE_COLUMNS[entry], builtIn: true })\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif (!entry || typeof entry !== 'object' || typeof entry.key !== 'string' || entry.key === '') {\n\t\t\treturn\n\t\t}\n\t\tif (isBuiltInFileColumn(entry.key) && entry.source === undefined) {\n\t\t\tout.push({ ...BUILT_IN_FILE_COLUMNS[entry.key], ...entry, builtIn: true })\n\t\t\treturn\n\t\t}\n\t\tout.push({\n\t\t\tsource: 'node',\n\t\t\tsortable: false,\n\t\t\t...entry,\n\t\t\tbuiltIn: false,\n\t\t})\n\t})\n\treturn out\n}\n\n/**\n * The columns a browser renders: the host's declaration, narrowed by what this\n * user chose to see.\n *\n * Which side wins on what:\n *\n * - The HOST decides MEMBERSHIP. The result is built by walking the\n * declaration, so a column exists only because the host declared it, in the\n * order the host declared it.\n * - The USER decides VISIBILITY, and only downward. A stored preference can\n * hide a declared column; it can never add one. A key in the stored list\n * that the host no longer declares is passed over, so unticking a column\n * last month cannot bring it back once the host removed it, and neither can\n * a preference written while a different column set was declared.\n *\n * A stored value of `null` is a user who has never chosen, which shows\n * everything. An empty list is a user who hid everything, which is a choice\n * and is honoured.\n *\n * @param {Array<object>} declared The resolved declared columns.\n * @param {(string[]|null|undefined)} visibleKeys The user's stored visible keys.\n * @return {Array<object>} The columns to render.\n */\nexport function visibleFileColumns(declared, visibleKeys) {\n\tconst columns = Array.isArray(declared) ? declared : []\n\tif (!Array.isArray(visibleKeys)) {\n\t\treturn columns\n\t}\n\tconst wanted = new Set(visibleKeys)\n\treturn columns.filter((column) => wanted.has(column.key))\n}\n\n/**\n * The DAV properties a declaration needs in the PROPFIND, so an `attribute`\n * column's value is there when the folder lists rather than one request later.\n *\n * @param {Array<object>} columns The resolved columns.\n * @return {string[]} The property names, deduplicated, in declaration order.\n */\nexport function attributePropertiesFor(columns) {\n\tconst out = []\n\t;(Array.isArray(columns) ? columns : []).forEach((column) => {\n\t\tif (column && column.source === 'attribute' && typeof column.attribute === 'string' && column.attribute !== '') {\n\t\t\tif (!out.includes(column.attribute)) {\n\t\t\t\tout.push(column.attribute)\n\t\t\t}\n\t\t}\n\t})\n\treturn out\n}\n\n/**\n * The value one column shows for one node.\n *\n * @param {object} column A resolved column.\n * @param {object} node The node.\n * @param {object} [rowData] The host's per-file data, keyed by file id.\n * @return {(Array|object|string|number|boolean|undefined)} The cell value.\n */\nexport function fileColumnValue(column, node, rowData = null) {\n\tif (!column || !node) {\n\t\treturn undefined\n\t}\n\tif (column.source === 'attribute') {\n\t\t// Read the key directly, NOT through the dotted-path reader: a DAV\n\t\t// property name is `{http://owncloud.org/ns}av-status`, and splitting\n\t\t// that on dots looks for a property called `org/ns}av-status` inside\n\t\t// one called `owncloud`, which is undefined every time.\n\t\tconst attributes = node.attributes\n\t\treturn attributes && typeof attributes === 'object' ? attributes[column.attribute] : undefined\n\t}\n\tif (column.source === 'row') {\n\t\tconst byId = rowData && typeof rowData === 'object' ? rowData : null\n\t\tif (!byId) {\n\t\t\treturn undefined\n\t\t}\n\t\tconst entry = byId[node.fileid] ?? byId[String(node.fileid)]\n\t\treturn entry ? entry[column.key] : undefined\n\t}\n\treturn atPath(node, column.nodeKey || column.key)\n}\n\n/**\n * Sort nodes on a declared column's resolved value, within the listed folder.\n *\n * Folders stay first, the way the Files app lists them and the way the\n * built-in sorts already do, so switching to a declared column does not\n * reshuffle the folders into the files.\n *\n * @param {Array<object>} nodes The nodes.\n * @param {object} column The resolved column.\n * @param {boolean} ascending Sort direction.\n * @param {object} [rowData] The host's per-file data.\n * @param {(node: object) => boolean} [isFolder] Whether a node is a folder.\n * @return {Array<object>} A new, sorted array.\n */\nexport function sortNodesByColumn(nodes, column, ascending, rowData = null, isFolder = () => false) {\n\tconst list = Array.isArray(nodes) ? [...nodes] : []\n\tif (!column) {\n\t\treturn list\n\t}\n\tconst direction = ascending ? 1 : -1\n\treturn list.sort((a, b) => {\n\t\tconst folderA = isFolder(a)\n\t\tconst folderB = isFolder(b)\n\t\tif (folderA !== folderB) {\n\t\t\treturn folderA ? -1 : 1\n\t\t}\n\t\tconst left = fileColumnValue(column, a, rowData)\n\t\tconst right = fileColumnValue(column, b, rowData)\n\t\tconst leftMissing = left === undefined || left === null || left === ''\n\t\tconst rightMissing = right === undefined || right === null || right === ''\n\t\t// A value nobody filled in sorts last in BOTH directions. Letting it\n\t\t// ride to the top on a descending sort hides the rows that do carry\n\t\t// one, which is the opposite of what sorting was asked for.\n\t\tif (leftMissing !== rightMissing) {\n\t\t\treturn leftMissing ? 1 : -1\n\t\t}\n\t\tif (leftMissing && rightMissing) {\n\t\t\treturn 0\n\t\t}\n\t\tif (typeof left === 'number' && typeof right === 'number') {\n\t\t\treturn (left - right) * direction\n\t\t}\n\t\treturn String(left).localeCompare(String(right)) * direction\n\t})\n}\n\n/**\n * Whether a cell value counts as filled in.\n *\n * @param {(Array|object|string|number|boolean|null|undefined)} value The value.\n * @return {boolean} False for nothing, an empty string or an empty list.\n */\nfunction isFilled(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn false\n\t}\n\treturn !(Array.isArray(value) && value.length === 0)\n}\n\n/**\n * The single value a node is grouped under, as a string key: a list groups\n * under its first entry, an object under its label, title, name or id.\n *\n * @param {(Array|object|string|number|boolean|null|undefined)} value The cell value.\n * @return {(string|null)} The group key, or null when nothing is filled in.\n */\nfunction groupKeyOf(value) {\n\tconst first = Array.isArray(value) ? value[0] : value\n\tif (!isFilled(first)) {\n\t\treturn null\n\t}\n\tif (typeof first === 'object') {\n\t\tconst label = first.label ?? first.title ?? first.name ?? first.id\n\t\treturn isFilled(label) ? String(label) : null\n\t}\n\treturn String(first)\n}\n\n/**\n * The values a node carries in a column, as strings, for counting and\n * filtering: every entry of a list, the one value otherwise.\n *\n * @param {object} column A resolved column.\n * @param {object} node The node.\n * @param {object} [rowData] The host's per-file data.\n * @return {string[]} The values, deduplicated.\n */\nfunction facetValuesOf(column, node, rowData) {\n\tconst value = fileColumnValue(column, node, rowData)\n\tconst entries = Array.isArray(value) ? value : [value]\n\tconst out = []\n\tentries.forEach((entry) => {\n\t\tconst key = groupKeyOf(entry)\n\t\tif (key !== null && !out.includes(key)) {\n\t\t\tout.push(key)\n\t\t}\n\t})\n\treturn out\n}\n\n/**\n * Group nodes on a column's value, in the order each value is first met.\n * Nodes with no value come last, in one group whose value is null.\n *\n * @param {Array<object>} nodes The nodes, already in display order.\n * @param {object} column The resolved column.\n * @param {object} [rowData] The host's per-file data.\n * @return {Array<{value: (string|null), nodes: Array<object>}>} The groups.\n */\nexport function groupNodes(nodes, column, rowData = null) {\n\tconst groups = []\n\tconst byKey = new Map()\n\tconst empty = { value: null, nodes: [] }\n\t;(Array.isArray(nodes) ? nodes : []).forEach((node) => {\n\t\tconst key = column ? groupKeyOf(fileColumnValue(column, node, rowData)) : null\n\t\tif (key === null) {\n\t\t\tempty.nodes.push(node)\n\t\t\treturn\n\t\t}\n\t\tif (!byKey.has(key)) {\n\t\t\tconst group = { value: key, nodes: [] }\n\t\t\tbyKey.set(key, group)\n\t\t\tgroups.push(group)\n\t\t}\n\t\tbyKey.get(key).nodes.push(node)\n\t})\n\tif (empty.nodes.length > 0) {\n\t\tgroups.push(empty)\n\t}\n\treturn groups\n}\n\n/**\n * The values in use in a column across the nodes, with how many nodes carry\n * each; a list counts once per entry. Most used first, then in first-seen\n * order.\n *\n * @param {Array<object>} nodes The nodes.\n * @param {object} column The resolved column.\n * @param {object} [rowData] The host's per-file data.\n * @return {Array<{value: string, count: number}>} The values and counts.\n */\nexport function facetCounts(nodes, column, rowData = null) {\n\tconst counts = []\n\tconst byValue = new Map()\n\t;(Array.isArray(nodes) ? nodes : []).forEach((node) => {\n\t\tfacetValuesOf(column, node, rowData).forEach((value) => {\n\t\t\tif (!byValue.has(value)) {\n\t\t\t\tconst entry = { value, count: 0 }\n\t\t\t\tbyValue.set(value, entry)\n\t\t\t\tcounts.push(entry)\n\t\t\t}\n\t\t\tbyValue.get(value).count += 1\n\t\t})\n\t})\n\treturn counts\n\t\t.map((entry, index) => ({ entry, index }))\n\t\t.sort((a, b) => (b.entry.count - a.entry.count) || (a.index - b.index))\n\t\t.map(({ entry }) => entry)\n}\n\n/**\n * Whether a node passes the chosen facet values: within one facet any chosen\n * value will do, and every facet with a choice must pass. A facet with\n * nothing chosen does not filter.\n *\n * @param {object} node The node.\n * @param {object} selections Chosen values by column key.\n * @param {object} columnsByKey Resolved columns by key.\n * @param {object} [rowData] The host's per-file data.\n * @return {boolean} True when the node stays in the list.\n */\nexport function nodeMatchesFacets(node, selections, columnsByKey, rowData = null) {\n\treturn Object.entries(selections || {}).every(([key, chosen]) => {\n\t\tif (!Array.isArray(chosen) || chosen.length === 0) {\n\t\t\treturn true\n\t\t}\n\t\tconst column = columnsByKey && columnsByKey[key]\n\t\tif (!column) {\n\t\t\treturn true\n\t\t}\n\t\tconst values = facetValuesOf(column, node, rowData)\n\t\treturn chosen.some((value) => values.includes(value))\n\t})\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -17,7 +17,7 @@ import __sfc_main$2 from '../CnPropertySourceField/CnPropertySourceField.vue.js'
|
|
|
17
17
|
import __sfc_main$a from '../CnResourceSelect/CnResourceSelect.vue.js';
|
|
18
18
|
import __sfc_main$4 from '../CnSubObjectsField/CnSubObjectsField.vue.js';
|
|
19
19
|
import { describeRowProblem, useChildRecords } from '../../composables/useChildRecords.js';
|
|
20
|
-
import { formDraftMixin, draftKey, draftIndicatorText, readDraft } from '../../composables/useFormDraft.js';
|
|
20
|
+
import { formDraftMixin, draftKey, resolveDraftUserId, draftIndicatorText, hasDraftChanges, clearAnonymousDrafts, readDraft } from '../../composables/useFormDraft.js';
|
|
21
21
|
import { splitHeldFiles, uploadHeldFiles, heldFromFailures } from '../../composables/useHeldFileUpload.js';
|
|
22
22
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js';
|
|
23
23
|
import { TENANT_CONTEXT_KEY } from '../../composables/useTenantContext.js';
|
|
@@ -393,16 +393,16 @@ var script = {
|
|
|
393
393
|
* 🔴 THE USER IS PART OF THE DRAFT KEY. A shared browser profile at a
|
|
394
394
|
* service desk is the ordinary case in a municipality, and a draft
|
|
395
395
|
* keyed without the user hands the next person at the counter what the
|
|
396
|
-
* last one typed. A host that passes nothing gets
|
|
397
|
-
*
|
|
398
|
-
*
|
|
396
|
+
* last one typed. A host that passes nothing gets the signed-in
|
|
397
|
+
* Nextcloud user (`@nextcloud/auth`), and `anonymous` only when
|
|
398
|
+
* nobody is signed in.
|
|
399
399
|
*/
|
|
400
400
|
draftAppId: {
|
|
401
401
|
type: String,
|
|
402
402
|
default: '',
|
|
403
403
|
},
|
|
404
404
|
|
|
405
|
-
/** Who is typing, for the draft key. See `draftAppId`. */
|
|
405
|
+
/** Who is typing, for the draft key. Empty: the signed-in user. See `draftAppId`. */
|
|
406
406
|
draftUserId: {
|
|
407
407
|
type: String,
|
|
408
408
|
default: '',
|
|
@@ -663,10 +663,20 @@ var script = {
|
|
|
663
663
|
appId: this.draftAppId,
|
|
664
664
|
schema: (this.schema?.slug || this.schema?.title || ''),
|
|
665
665
|
objectId: (this.item?.id || this.item?.uuid || ''),
|
|
666
|
-
userId: this.
|
|
666
|
+
userId: this.draftUserOwner,
|
|
667
667
|
})
|
|
668
668
|
},
|
|
669
669
|
|
|
670
|
+
/**
|
|
671
|
+
* Who the draft belongs to: `draftUserId`, else the signed-in user.
|
|
672
|
+
*
|
|
673
|
+
* @return {string} The user id, or '' when nobody is signed in.
|
|
674
|
+
* @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
|
|
675
|
+
*/
|
|
676
|
+
draftUserOwner() {
|
|
677
|
+
return resolveDraftUserId(this.draftUserId)
|
|
678
|
+
},
|
|
679
|
+
|
|
670
680
|
/**
|
|
671
681
|
* Whether "Save draft" may be offered.
|
|
672
682
|
*
|
|
@@ -978,17 +988,6 @@ var script = {
|
|
|
978
988
|
formData: {
|
|
979
989
|
deep: true,
|
|
980
990
|
handler(values) {
|
|
981
|
-
// 🔴 THE FIRST CHANGE IS THE COMPONENT SEEDING THE FORM, NOT
|
|
982
|
-
// SOMEBODY TYPING. Writing a draft for it stores the empty
|
|
983
|
-
// form over a real one, and the indicator announces "Saving
|
|
984
|
-
// draft" about work nobody did — an instrument reporting
|
|
985
|
-
// activity that did not happen, which is the whole class of
|
|
986
|
-
// defect this library keeps paying down.
|
|
987
|
-
if (this.draftSeeded !== true) {
|
|
988
|
-
this.draftSeeded = true;
|
|
989
|
-
return
|
|
990
|
-
}
|
|
991
|
-
|
|
992
991
|
// Nothing is written while a draft is being offered: the user
|
|
993
992
|
// has not chosen yet, and overwriting the stored draft with the
|
|
994
993
|
// empty form they are looking at would destroy the thing they
|
|
@@ -997,6 +996,19 @@ var script = {
|
|
|
997
996
|
return
|
|
998
997
|
}
|
|
999
998
|
|
|
999
|
+
// 🔴 ONLY A PERSON'S CHANGE IS WORTH A DRAFT. The form fills
|
|
1000
|
+
// in values by itself after it opens (an input reporting its
|
|
1001
|
+
// value back, normalised dates, defaults, the organisation),
|
|
1002
|
+
// and each of those used to write a draft and say "Saved just
|
|
1003
|
+
// now" on a dialog nobody had touched. A field counts once a
|
|
1004
|
+
// person edited it AND it differs from what the form opened
|
|
1005
|
+
// with, so undoing the edit drops the draft again.
|
|
1006
|
+
const edited = Object.keys(this.touchedFields || {}).filter((key) => this.touchedFields[key]);
|
|
1007
|
+
if (!hasDraftChanges(values, this.draftBaseline, edited)) {
|
|
1008
|
+
this.settleUnchangedDraft(this.formDraftKey);
|
|
1009
|
+
return
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1000
1012
|
this.scheduleDraftWrite(this.formDraftKey, values);
|
|
1001
1013
|
},
|
|
1002
1014
|
},
|
|
@@ -1161,6 +1173,12 @@ var script = {
|
|
|
1161
1173
|
// Silently refilling a form is how somebody submits last week's answers
|
|
1162
1174
|
// without noticing they were there, and a form that fills itself is
|
|
1163
1175
|
// indistinguishable from one the server prefilled.
|
|
1176
|
+
// A draft stored under `anonymous` was written by a page that did not
|
|
1177
|
+
// know who was typing. Once somebody is signed in it is removed, never
|
|
1178
|
+
// offered: it may be another person's.
|
|
1179
|
+
if (this.formDraftKey && this.draftUserOwner) {
|
|
1180
|
+
clearAnonymousDrafts();
|
|
1181
|
+
}
|
|
1164
1182
|
if (this.formDraftKey) {
|
|
1165
1183
|
this.draftOffer = readDraft(this.formDraftKey, {
|
|
1166
1184
|
objectUpdated: (this.item?.updated || this.item?.['@self']?.updated || ''),
|
|
@@ -1519,6 +1537,10 @@ var script = {
|
|
|
1519
1537
|
this.runAssignments(null);
|
|
1520
1538
|
this.loadProfileDefaults();
|
|
1521
1539
|
}
|
|
1540
|
+
// What the form opened with. A draft is written only once a field
|
|
1541
|
+
// a person edited differs from this (see the `formData` watcher).
|
|
1542
|
+
// Not reactive: nothing renders from it.
|
|
1543
|
+
this.draftBaseline = JSON.parse(JSON.stringify(this.formData || {}));
|
|
1522
1544
|
},
|
|
1523
1545
|
|
|
1524
1546
|
/**
|
|
@@ -2306,6 +2328,27 @@ var script = {
|
|
|
2306
2328
|
return this.showsOuterLabel(field) ? '' : this.errors[field.key]
|
|
2307
2329
|
},
|
|
2308
2330
|
|
|
2331
|
+
/**
|
|
2332
|
+
* The placeholder of a text, number or textarea control.
|
|
2333
|
+
*
|
|
2334
|
+
* The field's description already shows as the help line under the
|
|
2335
|
+
* control, so the placeholder stays empty rather than repeat it word
|
|
2336
|
+
* for word. The help line is the one kept: it is still there after
|
|
2337
|
+
* somebody typed (a placeholder disappears with the first keystroke),
|
|
2338
|
+
* it is read out through `aria-describedby`, and in the Nextcloud look
|
|
2339
|
+
* a placeholder is hidden until the field has focus anyway. While an
|
|
2340
|
+
* error takes the help line's place, the description returns as the
|
|
2341
|
+
* placeholder so it is not lost.
|
|
2342
|
+
*
|
|
2343
|
+
* @param {object} field The field definition.
|
|
2344
|
+
* @return {string} The placeholder, or '' when the help line says it.
|
|
2345
|
+
* @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
|
|
2346
|
+
*/
|
|
2347
|
+
fieldPlaceholder(field) {
|
|
2348
|
+
const description = field.description || '';
|
|
2349
|
+
return this.helperError(field) ? description : ''
|
|
2350
|
+
},
|
|
2351
|
+
|
|
2309
2352
|
/**
|
|
2310
2353
|
* The accessibility attributes of a text control. In both looks an invalid
|
|
2311
2354
|
* control is `aria-invalid` and `aria-describedby` lists the error before
|
|
@@ -3788,6 +3831,11 @@ var script = {
|
|
|
3788
3831
|
}
|
|
3789
3832
|
|
|
3790
3833
|
this.formData = { ...this.formData, ...this.draftOffer.values };
|
|
3834
|
+
// What the person typed last time is their edit now: it counts for
|
|
3835
|
+
// the next draft write and survives the record arriving late.
|
|
3836
|
+
for (const key of Object.keys(this.draftOffer.values)) {
|
|
3837
|
+
this.touchedFields[key] = true;
|
|
3838
|
+
}
|
|
3791
3839
|
this.draftOffer = null;
|
|
3792
3840
|
},
|
|
3793
3841
|
|