@conduction/nextcloud-vue 2.56.0-beta.30 → 2.56.0-beta.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +3 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +80 -4
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +95 -91
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +140 -10
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +52 -16
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +17 -3
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +63 -52
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js +10 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js +252 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js +140 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/index.js +26 -0
- package/dist/esm/components/CnChipsFieldWidget/index.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js +10 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js +354 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js +172 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/index.js +27 -0
- package/dist/esm/components/CnChoicePickerWidget/index.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +2 -2
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +65 -5
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +19 -11
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +3 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +72 -0
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +21 -4
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js +7 -7
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +131 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +80 -5
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +282 -69
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +302 -4
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +417 -244
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +101 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +141 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +60 -0
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +74 -49
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +29 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +48 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +7 -7
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +65 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +63 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +8 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +14 -0
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js +5 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +10 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +174 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +75 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/index.js +28 -0
- package/dist/esm/components/CnRadioCardsWidget/index.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +30 -6
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +34 -25
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js +10 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js +222 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js +64 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/index.js +30 -0
- package/dist/esm/components/CnSavedViewsWidget/index.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +10 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +347 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +184 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/index.js +27 -0
- package/dist/esm/components/CnSettingsFormWidget/index.js.map +1 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +87 -6
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +84 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +85 -41
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +10 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +159 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js +60 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/index.js +26 -0
- package/dist/esm/components/CnSwitchRowsWidget/index.js.map +1 -0
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +47 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +16 -14
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +80 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +75 -32
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +10 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +269 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js +106 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/index.js +27 -0
- package/dist/esm/components/CnToggleTableWidget/index.js.map +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +7 -2
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
- package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +197 -6
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +96 -50
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useListView.js +4 -4
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/index.js +17 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +9 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +55 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +1041 -12
- package/dist/esm/utils/contentHeaderLink.js +51 -0
- package/dist/esm/utils/contentHeaderLink.js.map +1 -0
- package/dist/esm/utils/facets.js +75 -3
- package/dist/esm/utils/facets.js.map +1 -1
- package/dist/esm/utils/fetchFilterCounts.js +40 -5
- package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +7 -1
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/initials.js +32 -0
- package/dist/esm/utils/initials.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +7 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/objectWrite.js +59 -0
- package/dist/esm/utils/objectWrite.js.map +1 -0
- package/dist/esm/utils/pageControls.js +81 -0
- package/dist/esm/utils/pageControls.js.map +1 -0
- package/dist/esm/utils/personalLenses.js +40 -3
- package/dist/esm/utils/personalLenses.js.map +1 -1
- package/dist/esm/utils/resolveFilterTokens.js +1 -1
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/resolveManifestTokens.js +2 -0
- package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +19 -1
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/esm/utils/userGroupsPeek.js +91 -0
- package/dist/esm/utils/userGroupsPeek.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/visibleWhen.js +57 -5
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/esm/utils/widgetDispatch.js +1 -1
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/esm/utils/widgetVisibility.js +4 -57
- package/dist/esm/utils/widgetVisibility.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +13589 -7833
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1041 -12
- package/l10n/en.json +9 -1
- package/l10n/nl.json +55 -1
- package/package.json +2 -1
- package/scripts/check-docs-mdx.mjs +108 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +6 -1
- package/src/components/CnAppRoot/CnAppRoot.md +4 -2
- package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
- package/src/components/CnBannerWidget/CnBannerWidget.vue +205 -20
- package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +47 -4
- package/src/components/CnChipsFieldWidget/CnChipsFieldWidget.vue +433 -0
- package/src/components/CnChipsFieldWidget/index.js +25 -0
- package/src/components/CnChoicePickerWidget/CnChoicePickerWidget.vue +556 -0
- package/src/components/CnChoicePickerWidget/index.js +26 -0
- package/src/components/CnConversationThread/CnConversationWidget.vue +1 -1
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +2 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +78 -5
- package/src/components/CnDataTable/CnDataTable.vue +21 -12
- package/src/components/CnDetailPage/CnDetailPage.vue +89 -3
- package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
- package/src/components/CnFactsWidget/CnFactsWidget.vue +142 -2
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +268 -6
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +588 -117
- package/src/components/CnFilesBrowser/filesBrowser.js +100 -0
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +140 -0
- package/src/components/CnFormPage/CnFormPage.vue +81 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +56 -4
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +7 -7
- package/src/components/CnNextStepCard/CnNextStepWidget.vue +1 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +69 -2
- package/src/components/CnObjectSidebar/CnFilesTab.vue +70 -1
- package/src/components/CnObjectSidebar/CnNotesTab.vue +16 -0
- package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
- package/src/components/CnRadioCardsWidget/index.js +27 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +55 -6
- package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
- package/src/components/CnSavedViewsWidget/index.js +29 -0
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +13 -0
- package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +555 -0
- package/src/components/CnSettingsFormWidget/index.js +26 -0
- package/src/components/CnStatWidget/CnStatWidget.vue +87 -6
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +180 -3
- package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +226 -0
- package/src/components/CnSwitchRowsWidget/index.js +25 -0
- package/src/components/CnTabsWidget/CnTabsWidget.vue +82 -3
- package/src/components/CnTasksWidget/CnTasksWidget.vue +175 -15
- package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +386 -0
- package/src/components/CnToggleTableWidget/index.js +26 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
- package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
- package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +309 -11
- package/src/components/index.js +15 -0
- package/src/composables/useListView.js +4 -4
- package/src/css/look-board-detail.css +36 -2
- package/src/css/look-board-index.css +39 -8
- package/src/css/look-board.css +72 -0
- package/src/index.js +15 -0
- package/src/schemas/app-manifest-v2.schema.json +93 -9
- package/src/utils/contentHeaderLink.js +48 -0
- package/src/utils/facets.js +74 -2
- package/src/utils/fetchFilterCounts.js +39 -4
- package/src/utils/fillTemplate.js +7 -1
- package/src/utils/initials.js +29 -0
- package/src/utils/libraryWidgetKeys.js +7 -0
- package/src/utils/objectWrite.js +55 -0
- package/src/utils/pageControls.js +78 -0
- package/src/utils/personalLenses.js +39 -2
- package/src/utils/resolveFilterTokens.js +3 -1
- package/src/utils/resolveManifestTokens.js +2 -0
- package/src/utils/sentinelTokens.js +19 -1
- package/src/utils/userGroupsPeek.js +87 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +56 -4
- package/src/utils/widgetDispatch.js +1 -1
- package/src/utils/widgetVisibility.js +7 -56
|
@@ -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":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,qBAAqB,GAAG;AACrC,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE;AAChG,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;AACxF,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE;AAClG,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE;AAC1E,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;AACvE,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,wBAAwB,EAAE,QAAQ,EAAE,KAAK,EAAE;AAC1F,EAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,oBAAoB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE;AAC5B,CAAC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,EAAE,EAAE;AACvD,EAAE,OAAO,SAAS;AAClB,EAAE;AACF,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,MAAM,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,GAAG,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC;AAC9G,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,mBAAmB,CAAC,IAAI,EAAE;AAC1C,CAAC,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,qBAAqB,EAAE,IAAI,CAAC;AAC9E,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,oBAAoB,CAAC,OAAO,EAAE;AAC9C,CAAC,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,GAAG,OAAO,GAAG,qBAAoB;AAC/F,CAAC,MAAM,GAAG,GAAG,GAAE;AACf,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK;AAC7B,EAAE,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AACjC,GAAG,IAAI,mBAAmB,CAAC,KAAK,CAAC,EAAE;AACnC,IAAI,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,qBAAqB,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAC;AAChE,IAAI;AACJ,GAAG,MAAM;AACT,GAAG;AACH,EAAE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,GAAG,KAAK,EAAE,EAAE;AAChG,GAAG,MAAM;AACT,GAAG;AACH,EAAE,IAAI,mBAAmB,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE;AACpE,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAC;AAC7E,GAAG,MAAM;AACT,GAAG;AACH,EAAE,GAAG,CAAC,IAAI,CAAC;AACX,GAAG,MAAM,EAAE,MAAM;AACjB,GAAG,QAAQ,EAAE,KAAK;AAClB,GAAG,GAAG,KAAK;AACX,GAAG,OAAO,EAAE,KAAK;AACjB,GAAG,EAAC;AACJ,EAAE,EAAC;AACH,CAAC,OAAO,GAAG;AACX,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,kBAAkB,CAAC,QAAQ,EAAE,WAAW,EAAE;AAC1D,CAAC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,GAAG,GAAE;AACxD,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE;AAClC,EAAE,OAAO,OAAO;AAChB,EAAE;AACF,CAAC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,WAAW,EAAC;AACpC,CAAC,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,sBAAsB,CAAC,OAAO,EAAE;AAChD,CAAC,MAAM,GAAG,GAAG,EAAE;AACf,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC,MAAM,KAAK;AAC9D,EAAE,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,KAAK,EAAE,EAAE;AAClH,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE;AACxC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAC;AAC9B,IAAI;AACJ,GAAG;AACH,EAAE,EAAC;AACH,CAAC,OAAO,GAAG;AACX,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,EAAE;AAC9D,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE;AACvB,EAAE,OAAO,SAAS;AAClB,EAAE;AACF,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE;AACpC;AACA;AACA;AACA;AACA,EAAE,MAAM,UAAU,GAAG,IAAI,CAAC,WAAU;AACpC,EAAE,OAAO,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,SAAS;AAChG,EAAE;AACF,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,KAAK,EAAE;AAC9B,EAAE,MAAM,IAAI,GAAG,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,GAAG,OAAO,GAAG,KAAI;AACtE,EAAE,IAAI,CAAC,IAAI,EAAE;AACb,GAAG,OAAO,SAAS;AACnB,GAAG;AACH,EAAE,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAC;AAC9D,EAAE,OAAO,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS;AAC9C,EAAE;AACF,CAAC,OAAO,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC;AAClD,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,EAAE,QAAQ,GAAG,MAAM,KAAK,EAAE;AACpG,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,GAAE;AACpD,CAAC,IAAI,CAAC,MAAM,EAAE;AACd,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,SAAS,GAAG,SAAS,GAAG,CAAC,GAAG,CAAC,EAAC;AACrC,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK;AAC5B,EAAE,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,EAAC;AAC7B,EAAE,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,EAAC;AAC7B,EAAE,IAAI,OAAO,KAAK,OAAO,EAAE;AAC3B,GAAG,OAAO,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC;AAC1B,GAAG;AACH,EAAE,MAAM,IAAI,GAAG,eAAe,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAC;AAClD,EAAE,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAC;AACnD,EAAE,MAAM,WAAW,GAAG,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,GAAE;AACxE,EAAE,MAAM,YAAY,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,GAAE;AAC5E;AACA;AACA;AACA,EAAE,IAAI,WAAW,KAAK,YAAY,EAAE;AACpC,GAAG,OAAO,WAAW,GAAG,CAAC,GAAG,CAAC,CAAC;AAC9B,GAAG;AACH,EAAE,IAAI,WAAW,IAAI,YAAY,EAAE;AACnC,GAAG,OAAO,CAAC;AACX,GAAG;AACH,EAAE,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAC7D,GAAG,OAAO,CAAC,IAAI,GAAG,KAAK,IAAI,SAAS;AACpC,GAAG;AACH,EAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,SAAS;AAC9D,EAAE,CAAC;AACH,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,QAAQ,CAAC,KAAK,EAAE;AACzB,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE;AAC5D,EAAE,OAAO,KAAK;AACd,EAAE;AACF,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC;AACrD,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,UAAU,CAAC,KAAK,EAAE;AAC3B,CAAC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,MAAK;AACtD,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;AACvB,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAChC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,GAAE;AACpE,EAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI;AAC/C,EAAE;AACF,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC;AACrB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE;AAC9C,CAAC,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAC;AACrD,CAAC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,KAAK,EAAC;AACvD,CAAC,MAAM,GAAG,GAAG,GAAE;AACf,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK;AAC5B,EAAE,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,EAAC;AAC/B,EAAE,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AAC1C,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,EAAC;AAChB,GAAG;AACH,EAAE,EAAC;AACH,CAAC,OAAO,GAAG;AACX,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE;AAC1D,CAAC,MAAM,MAAM,GAAG,GAAE;AAClB,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,GAAE;AACxB,CAAC,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE;AACzC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC,IAAI,KAAK;AACxD,EAAE,MAAM,GAAG,GAAG,MAAM,GAAG,UAAU,CAAC,eAAe,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,GAAG,KAAI;AAChF,EAAE,IAAI,GAAG,KAAK,IAAI,EAAE;AACpB,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAC;AACzB,GAAG,MAAM;AACT,GAAG;AACH,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AACvB,GAAG,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,GAAE;AAC1C,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAC;AACxB,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAC;AACrB,GAAG;AACH,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAC;AACjC,EAAE,EAAC;AACH,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;AAC7B,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,EAAC;AACpB,EAAE;AACF,CAAC,OAAO,MAAM;AACd,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE;AAC3D,CAAC,MAAM,MAAM,GAAG,GAAE;AAClB,CAAC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAE;AAC1B,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC,IAAI,KAAK;AACxD,EAAE,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK;AAC1D,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;AAC5B,IAAI,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,GAAE;AACrC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAC;AAC7B,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,EAAC;AACtB,IAAI;AACJ,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAC;AAChC,GAAG,EAAC;AACJ,EAAE,EAAC;AACH,CAAC,OAAO,MAAM;AACd,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5C,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;AACzE,GAAG,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,KAAK,CAAC;AAC5B,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,GAAG,IAAI,EAAE;AAClF,CAAC,OAAO,MAAM,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK;AAClE,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE;AACrD,GAAG,OAAO,IAAI;AACd,GAAG;AACH,EAAE,MAAM,MAAM,GAAG,YAAY,IAAI,YAAY,CAAC,GAAG,EAAC;AAClD,EAAE,IAAI,CAAC,MAAM,EAAE;AACf,GAAG,OAAO,IAAI;AACd,GAAG;AACH,EAAE,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAC;AACrD,EAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACvD,EAAE,CAAC;AACH;;;;"}
|
|
@@ -222,6 +222,17 @@ var script = {
|
|
|
222
222
|
default: '',
|
|
223
223
|
},
|
|
224
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Name of a honeypot field for a form anonymous visitors fill in.
|
|
227
|
+
* The field is rendered out of sight and out of the tab order; when
|
|
228
|
+
* it is filled, submit sends nothing and shows the normal success,
|
|
229
|
+
* so a bot learns nothing. Empty (default): no honeypot.
|
|
230
|
+
*/
|
|
231
|
+
honeypot: {
|
|
232
|
+
type: String,
|
|
233
|
+
default: '',
|
|
234
|
+
},
|
|
235
|
+
|
|
225
236
|
/** HTTP method for endpoint mode. POST | PUT | PATCH. */
|
|
226
237
|
submitMethod: {
|
|
227
238
|
type: String,
|
|
@@ -360,6 +371,8 @@ var script = {
|
|
|
360
371
|
summaryKeys: [],
|
|
361
372
|
/** Per-field validation error messages, keyed by field.key. */
|
|
362
373
|
fieldErrors: {},
|
|
374
|
+
/** What a bot typed into the honeypot field; stays empty for a person. */
|
|
375
|
+
honeypotValue: '',
|
|
363
376
|
/**
|
|
364
377
|
* Cache of resolved `endpoint`/`source` visibleWhen outcomes,
|
|
365
378
|
* keyed by field.key. Resolved ONCE in `mounted()` — see the
|
|
@@ -1395,6 +1408,11 @@ var script = {
|
|
|
1395
1408
|
}
|
|
1396
1409
|
|
|
1397
1410
|
this.lastError = null;
|
|
1411
|
+
if (this.honeypot && this.honeypotValue !== '') {
|
|
1412
|
+
// A bot filled the field no person sees: send nothing, look done.
|
|
1413
|
+
this.submitted = true;
|
|
1414
|
+
return
|
|
1415
|
+
}
|
|
1398
1416
|
this.submitting = true;
|
|
1399
1417
|
try {
|
|
1400
1418
|
if (this.submitEndpoint) {
|
|
@@ -1420,6 +1438,9 @@ var script = {
|
|
|
1420
1438
|
// describes the transport ("Request failed with status code 400")
|
|
1421
1439
|
// and leaves the user to guess which field it meant.
|
|
1422
1440
|
this.lastError = serverErrorMessage(err);
|
|
1441
|
+
// A refusal that names its fields (`findings[]`, as OpenRegister's
|
|
1442
|
+
// form destination check answers 422) is marked on each field.
|
|
1443
|
+
this.showServerFindings(err);
|
|
1423
1444
|
/**
|
|
1424
1445
|
* Submit failure event. Payload is the thrown error / rejected reason.
|
|
1425
1446
|
*
|
|
@@ -1432,6 +1453,45 @@ var script = {
|
|
|
1432
1453
|
}
|
|
1433
1454
|
},
|
|
1434
1455
|
|
|
1456
|
+
/**
|
|
1457
|
+
* Put each finding of a refused submit on its field.
|
|
1458
|
+
*
|
|
1459
|
+
* Reads `response.data.findings[]`, each `{ field?, property, message }`,
|
|
1460
|
+
* and marks the field whose key is the finding's `field`, or else its
|
|
1461
|
+
* `property`. Findings on no field of this form stay in the general
|
|
1462
|
+
* error above the form. Nothing happens for an answer without findings.
|
|
1463
|
+
*
|
|
1464
|
+
* @param {object} err The rejected request.
|
|
1465
|
+
* @return {void}
|
|
1466
|
+
*/
|
|
1467
|
+
showServerFindings(err) {
|
|
1468
|
+
const findings = err?.response?.data?.findings;
|
|
1469
|
+
if (!Array.isArray(findings) || findings.length === 0) {
|
|
1470
|
+
return
|
|
1471
|
+
}
|
|
1472
|
+
const keys = new Set(this.fields.map((f) => f && f.key));
|
|
1473
|
+
const marked = [];
|
|
1474
|
+
for (const finding of findings) {
|
|
1475
|
+
const key = finding && (finding.field || finding.property);
|
|
1476
|
+
if (!key || !keys.has(key) || this.fieldErrors[key]) {
|
|
1477
|
+
continue
|
|
1478
|
+
}
|
|
1479
|
+
this.fieldErrors[key] = String(finding.message || translate('nextcloud-vue', 'This answer was refused.'));
|
|
1480
|
+
marked.push(key);
|
|
1481
|
+
}
|
|
1482
|
+
if (marked.length === 0) {
|
|
1483
|
+
return
|
|
1484
|
+
}
|
|
1485
|
+
this.summaryKeys = this.fields.map((f) => f.key).filter((key) => marked.includes(key));
|
|
1486
|
+
if (this.hasSteps) {
|
|
1487
|
+
const stepIndex = this.steps.findIndex((step) => this.stepFields(step).some((f) => f.key === this.summaryKeys[0]));
|
|
1488
|
+
if (stepIndex >= 0) {
|
|
1489
|
+
this.currentStepIndex = stepIndex;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
this.focusFirstError(this.summaryKeys[0]);
|
|
1493
|
+
},
|
|
1494
|
+
|
|
1435
1495
|
async submitViaEndpoint() {
|
|
1436
1496
|
const method = (this.submitMethod || 'POST').toLowerCase();
|
|
1437
1497
|
const url = resolveParams(this.submitEndpoint, this.$route?.params || {});
|