@conduction/nextcloud-vue 2.78.0 → 2.80.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/bundled-packages.json +0 -2
- package/dist/esm/bundled-packages.json +0 -2
- 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 +115 -104
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +12 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +39 -3
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +158 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +157 -96
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +139 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +140 -92
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +77 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +5 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +62 -4
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +85 -1
- 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/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 +736 -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 +551 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +183 -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 +173 -5
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +181 -99
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +29 -11
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js +252 -0
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +66 -18
- 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 +18 -3
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +330 -74
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +34 -15
- 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/CnObjectSidebar/CnFilesTab.vue2.js +100 -3
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -2
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.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/CnQuickFilterBar/CnQuickFilterBar.vue2.js +3 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- 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 +387 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +169 -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/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- 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/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.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/CnTabsWidget/CnTabsWidget.vue2.js +152 -8
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +84 -23
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +4 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
- 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/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +106 -2
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +105 -53
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useAppManifest.js +8 -3
- package/dist/esm/composables/useAppManifest.js.map +1 -1
- package/dist/esm/composables/useFormDraft.js +140 -9
- package/dist/esm/composables/useFormDraft.js.map +1 -1
- package/dist/esm/index.js +5 -0
- 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 +72 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +120 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +1314 -74
- package/dist/esm/utils/boardDate.js +193 -0
- package/dist/esm/utils/boardDate.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/dynamicProperties.js +5 -1
- package/dist/esm/utils/dynamicProperties.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +114 -0
- package/dist/esm/utils/fillTemplate.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +4 -0
- package/dist/esm/utils/libraryWidgetKeys.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 +11 -0
- package/dist/esm/utils/resolveFilterTokens.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 +3 -3
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +57 -6
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/widgetDispatch.js +7 -1
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +14278 -8169
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1314 -74
- package/l10n/en.json +72 -1
- package/l10n/nl.json +120 -2
- package/package.json +1 -1
- package/scripts/build-validators.js +26 -0
- package/src/components/CnActionButtons/CnActionButtons.vue +2 -0
- package/src/components/CnActionsBar/CnActionsBar.md +4 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +83 -42
- package/src/components/CnAppRoot/CnAppRoot.vue +12 -2
- package/src/components/CnBannerWidget/CnBannerWidget.vue +39 -3
- package/src/components/CnCellRenderer/CnCellRenderer.vue +306 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +219 -4
- package/src/components/CnDataTable/CnDataTable.md +2 -0
- package/src/components/CnDataTable/CnDataTable.vue +82 -2
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +63 -3
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +127 -1
- package/src/components/CnFactsWidget/CnFactsWidget.vue +1050 -0
- package/src/components/CnFactsWidget/index.js +29 -0
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +746 -0
- package/src/components/CnFieldListWidget/index.js +28 -0
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +232 -11
- package/src/components/CnFilesBrowser/filesBrowser.js +43 -2
- package/src/components/CnFilesBrowser/index.js +1 -0
- package/src/components/CnFilesBrowser/openRegisterSource.js +248 -0
- package/src/components/CnFormDialog/CnFormDialog.md +9 -3
- package/src/components/CnFormDialog/CnFormDialog.vue +70 -22
- package/src/components/CnFormPage/CnFormPage.vue +18 -3
- package/src/components/CnIndexPage/CnIndexPage.vue +281 -9
- package/src/components/CnIndexPage/CnIndexPageWidgets.vue +169 -0
- package/src/components/CnObjectSidebar/CnFilesTab.vue +103 -4
- package/src/components/CnPageRenderer/CnPageRenderer.vue +29 -1
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +3 -2
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +603 -0
- package/src/components/CnRecordListWidget/index.js +30 -0
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +1 -1
- package/src/components/CnSectionsWidget/CnSectionsWidget.vue +489 -0
- package/src/components/CnSectionsWidget/index.js +34 -0
- package/src/components/CnStagesWidget/CnStagesWidget.vue +81 -2
- package/src/components/CnStatWidget/CnStatWidget.vue +1 -1
- package/src/components/CnTabsWidget/CnTabsWidget.vue +214 -9
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +4 -0
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
- package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +188 -4
- package/src/components/index.js +5 -1
- package/src/composables/__tests__/useFormDraft.spec.js +69 -0
- package/src/composables/useAppManifest.js +8 -3
- package/src/composables/useFormDraft.js +139 -8
- 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 +87 -0
- package/src/css/look-board-index.css +143 -14
- package/src/css/look-board.css +114 -2
- package/src/css/table.css +56 -0
- package/src/index.js +8 -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 +149 -5
- package/src/utils/boardDate.js +189 -0
- package/src/utils/detailActionModel.js +59 -2
- package/src/utils/dynamicProperties.js +5 -1
- package/src/utils/fillTemplate.js +110 -0
- package/src/utils/libraryWidgetKeys.js +4 -0
- package/src/utils/queryPresets.js +93 -0
- package/src/utils/resolveFilterTokens.js +11 -0
- package/src/utils/schema.js +21 -0
- package/src/utils/sentinelTokens.js +3 -3
- package/src/utils/validateManifestV2.compiled.js +57 -2
- package/src/utils/widgetDispatch.js +7 -1
- package/dist/esm/_virtual/equal.js +0 -4
- package/dist/esm/_virtual/equal.js.map +0 -1
- package/dist/esm/_virtual/ucs2length.js +0 -4
- package/dist/esm/_virtual/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js +0 -9
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js +0 -25
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/fast-deep-equal/index.js +0 -47
- package/dist/esm/node_modules/fast-deep-equal/index.js.map +0 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { resolveComponent, openBlock, createElementBlock, toDisplayString, Fragment, renderList, createElementVNode, createCommentVNode, createBlock, withCtx, createTextVNode, withModifiers, createVNode } from 'vue';
|
|
2
|
+
|
|
3
|
+
const _hoisted_1 = {
|
|
4
|
+
class: "cn-field-list-widget",
|
|
5
|
+
"data-testid": "cn-field-list-widget"
|
|
6
|
+
};
|
|
7
|
+
const _hoisted_2 = {
|
|
8
|
+
key: 0,
|
|
9
|
+
class: "cn-field-list-widget__empty",
|
|
10
|
+
"data-testid": "cn-field-list-empty"
|
|
11
|
+
};
|
|
12
|
+
const _hoisted_3 = {
|
|
13
|
+
key: 1,
|
|
14
|
+
class: "cn-field-list-widget__rows"
|
|
15
|
+
};
|
|
16
|
+
const _hoisted_4 = ["data-testid"];
|
|
17
|
+
const _hoisted_5 = { class: "cn-field-list-widget__line" };
|
|
18
|
+
const _hoisted_6 = {
|
|
19
|
+
key: 0,
|
|
20
|
+
class: "cn-field-list-widget__avatar",
|
|
21
|
+
"aria-hidden": "true"
|
|
22
|
+
};
|
|
23
|
+
const _hoisted_7 = { class: "cn-field-list-widget__main" };
|
|
24
|
+
const _hoisted_8 = { class: "cn-field-list-widget__title" };
|
|
25
|
+
const _hoisted_9 = {
|
|
26
|
+
key: 0,
|
|
27
|
+
class: "cn-field-list-widget__meta"
|
|
28
|
+
};
|
|
29
|
+
const _hoisted_10 = {
|
|
30
|
+
key: 2,
|
|
31
|
+
class: "cn-field-list-widget__aside"
|
|
32
|
+
};
|
|
33
|
+
const _hoisted_11 = {
|
|
34
|
+
key: 0,
|
|
35
|
+
class: "cn-field-list-widget__detail"
|
|
36
|
+
};
|
|
37
|
+
const _hoisted_12 = {
|
|
38
|
+
key: 2,
|
|
39
|
+
class: "cn-field-list-widget__hint"
|
|
40
|
+
};
|
|
41
|
+
const _hoisted_13 = ["data-testid"];
|
|
42
|
+
|
|
43
|
+
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
44
|
+
const _component_CnStatusBadge = resolveComponent("CnStatusBadge");
|
|
45
|
+
const _component_NcButton = resolveComponent("NcButton");
|
|
46
|
+
const _component_NcSelect = resolveComponent("NcSelect");
|
|
47
|
+
const _component_NcTextField = resolveComponent("NcTextField");
|
|
48
|
+
const _component_NcNoteCard = resolveComponent("NcNoteCard");
|
|
49
|
+
|
|
50
|
+
return (openBlock(), createElementBlock("div", _hoisted_1, [
|
|
51
|
+
($options.rows.length === 0)
|
|
52
|
+
? (openBlock(), createElementBlock("p", _hoisted_2, toDisplayString($options.emptyLabel), 1 /* TEXT */))
|
|
53
|
+
: (openBlock(), createElementBlock("ul", _hoisted_3, [
|
|
54
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList($options.rows, (row) => {
|
|
55
|
+
return (openBlock(), createElementBlock("li", {
|
|
56
|
+
key: row.key,
|
|
57
|
+
class: "cn-field-list-widget__row",
|
|
58
|
+
"data-testid": `cn-field-list-row-${row.key}`
|
|
59
|
+
}, [
|
|
60
|
+
createElementVNode("div", _hoisted_5, [
|
|
61
|
+
(row.initials)
|
|
62
|
+
? (openBlock(), createElementBlock("span", _hoisted_6, toDisplayString(row.initials), 1 /* TEXT */))
|
|
63
|
+
: createCommentVNode("v-if", true),
|
|
64
|
+
createElementVNode("span", _hoisted_7, [
|
|
65
|
+
createElementVNode("span", _hoisted_8, toDisplayString(row.title), 1 /* TEXT */),
|
|
66
|
+
(row.meta)
|
|
67
|
+
? (openBlock(), createElementBlock("span", _hoisted_9, toDisplayString(row.meta), 1 /* TEXT */))
|
|
68
|
+
: createCommentVNode("v-if", true)
|
|
69
|
+
]),
|
|
70
|
+
(row.pill)
|
|
71
|
+
? (openBlock(), createBlock(_component_CnStatusBadge, {
|
|
72
|
+
key: 1,
|
|
73
|
+
label: row.pill.label,
|
|
74
|
+
variant: row.pill.variant,
|
|
75
|
+
size: "small"
|
|
76
|
+
}, null, 8 /* PROPS */, ["label", "variant"]))
|
|
77
|
+
: createCommentVNode("v-if", true),
|
|
78
|
+
(row.aside)
|
|
79
|
+
? (openBlock(), createElementBlock("span", _hoisted_10, toDisplayString(row.aside), 1 /* TEXT */))
|
|
80
|
+
: createCommentVNode("v-if", true),
|
|
81
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(row.actions, (action) => {
|
|
82
|
+
return (openBlock(), createBlock(_component_NcButton, {
|
|
83
|
+
key: action.id,
|
|
84
|
+
variant: "tertiary",
|
|
85
|
+
disabled: $data.busy !== '',
|
|
86
|
+
"data-testid": `cn-field-list-action-${action.id}-${row.key}`,
|
|
87
|
+
onClick: $event => ($options.runAction(action, row))
|
|
88
|
+
}, {
|
|
89
|
+
default: withCtx(() => [
|
|
90
|
+
createTextVNode(toDisplayString(action.label), 1 /* TEXT */)
|
|
91
|
+
]),
|
|
92
|
+
_: 2 /* DYNAMIC */
|
|
93
|
+
}, 1032 /* PROPS, DYNAMIC_SLOTS */, ["disabled", "data-testid", "onClick"]))
|
|
94
|
+
}), 128 /* KEYED_FRAGMENT */)),
|
|
95
|
+
($options.canRemove)
|
|
96
|
+
? (openBlock(), createBlock(_component_NcButton, {
|
|
97
|
+
key: 3,
|
|
98
|
+
variant: "tertiary",
|
|
99
|
+
disabled: $data.busy !== '',
|
|
100
|
+
"aria-label": $options.removeAriaLabel(row),
|
|
101
|
+
"data-testid": `cn-field-list-remove-${row.key}`,
|
|
102
|
+
onClick: $event => ($options.remove(row))
|
|
103
|
+
}, {
|
|
104
|
+
default: withCtx(() => [
|
|
105
|
+
createTextVNode(toDisplayString($options.removeLabel), 1 /* TEXT */)
|
|
106
|
+
]),
|
|
107
|
+
_: 1 /* STABLE */
|
|
108
|
+
}, 8 /* PROPS */, ["disabled", "aria-label", "data-testid", "onClick"]))
|
|
109
|
+
: createCommentVNode("v-if", true)
|
|
110
|
+
]),
|
|
111
|
+
(row.detail)
|
|
112
|
+
? (openBlock(), createElementBlock("p", _hoisted_11, toDisplayString(row.detail), 1 /* TEXT */))
|
|
113
|
+
: createCommentVNode("v-if", true)
|
|
114
|
+
], 8 /* PROPS */, _hoisted_4))
|
|
115
|
+
}), 128 /* KEYED_FRAGMENT */))
|
|
116
|
+
])),
|
|
117
|
+
($options.hint)
|
|
118
|
+
? (openBlock(), createElementBlock("p", _hoisted_12, toDisplayString($options.hint), 1 /* TEXT */))
|
|
119
|
+
: createCommentVNode("v-if", true),
|
|
120
|
+
($options.canAdd)
|
|
121
|
+
? (openBlock(), createElementBlock("form", {
|
|
122
|
+
key: 3,
|
|
123
|
+
class: "cn-field-list-widget__add",
|
|
124
|
+
"data-testid": "cn-field-list-add",
|
|
125
|
+
onSubmit: _cache[0] || (_cache[0] = withModifiers((...args) => ($options.add && $options.add(...args)), ["prevent"]))
|
|
126
|
+
}, [
|
|
127
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList($options.addFields, (field) => {
|
|
128
|
+
return (openBlock(), createElementBlock("div", {
|
|
129
|
+
key: field.key,
|
|
130
|
+
class: "cn-field-list-widget__field",
|
|
131
|
+
"data-testid": `cn-field-list-input-${field.key}`
|
|
132
|
+
}, [
|
|
133
|
+
(field.type === 'select')
|
|
134
|
+
? (openBlock(), createBlock(_component_NcSelect, {
|
|
135
|
+
key: 0,
|
|
136
|
+
modelValue: $data.draft[field.key],
|
|
137
|
+
"onUpdate:modelValue": $event => (($data.draft[field.key]) = $event),
|
|
138
|
+
inputLabel: field.label,
|
|
139
|
+
options: field.options,
|
|
140
|
+
reduce: (option) => option.value,
|
|
141
|
+
clearable: false,
|
|
142
|
+
label: "label"
|
|
143
|
+
}, null, 8 /* PROPS */, ["modelValue", "onUpdate:modelValue", "inputLabel", "options", "reduce"]))
|
|
144
|
+
: (openBlock(), createBlock(_component_NcTextField, {
|
|
145
|
+
key: 1,
|
|
146
|
+
modelValue: $data.draft[field.key],
|
|
147
|
+
"onUpdate:modelValue": $event => (($data.draft[field.key]) = $event),
|
|
148
|
+
label: field.label,
|
|
149
|
+
error: Boolean($data.errors[field.key]),
|
|
150
|
+
helperText: $data.errors[field.key] || ''
|
|
151
|
+
}, null, 8 /* PROPS */, ["modelValue", "onUpdate:modelValue", "label", "error", "helperText"]))
|
|
152
|
+
], 8 /* PROPS */, _hoisted_13))
|
|
153
|
+
}), 128 /* KEYED_FRAGMENT */)),
|
|
154
|
+
createVNode(_component_NcButton, {
|
|
155
|
+
type: "submit",
|
|
156
|
+
variant: "secondary",
|
|
157
|
+
disabled: $data.busy !== '',
|
|
158
|
+
"data-testid": "cn-field-list-submit"
|
|
159
|
+
}, {
|
|
160
|
+
default: withCtx(() => [
|
|
161
|
+
createTextVNode(toDisplayString($options.addLabel), 1 /* TEXT */)
|
|
162
|
+
]),
|
|
163
|
+
_: 1 /* STABLE */
|
|
164
|
+
}, 8 /* PROPS */, ["disabled"])
|
|
165
|
+
], 32 /* NEED_HYDRATION */))
|
|
166
|
+
: createCommentVNode("v-if", true),
|
|
167
|
+
($data.notice)
|
|
168
|
+
? (openBlock(), createBlock(_component_NcNoteCard, {
|
|
169
|
+
key: 4,
|
|
170
|
+
type: $data.noticeType,
|
|
171
|
+
"data-testid": "cn-field-list-notice"
|
|
172
|
+
}, {
|
|
173
|
+
default: withCtx(() => [
|
|
174
|
+
createTextVNode(toDisplayString($data.notice), 1 /* TEXT */)
|
|
175
|
+
]),
|
|
176
|
+
_: 1 /* STABLE */
|
|
177
|
+
}, 8 /* PROPS */, ["type"]))
|
|
178
|
+
: createCommentVNode("v-if", true)
|
|
179
|
+
]))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export { render };
|
|
183
|
+
//# sourceMappingURL=CnFieldListWidget.vue3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnFieldListWidget.vue3.js","sources":["../../../../src/components/CnFieldListWidget/CnFieldListWidget.vue?vue&type=template&id=0f29a833&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-field-list-widget\" data-testid=\"cn-field-list-widget\">\n\t\t<p v-if=\"rows.length === 0\" class=\"cn-field-list-widget__empty\" data-testid=\"cn-field-list-empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<ul v-else class=\"cn-field-list-widget__rows\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t:key=\"row.key\"\n\t\t\t\tclass=\"cn-field-list-widget__row\"\n\t\t\t\t:data-testid=\"`cn-field-list-row-${row.key}`\">\n\t\t\t\t<div class=\"cn-field-list-widget__line\">\n\t\t\t\t\t<span v-if=\"row.initials\" class=\"cn-field-list-widget__avatar\" aria-hidden=\"true\">{{ row.initials }}</span>\n\t\t\t\t\t<span class=\"cn-field-list-widget__main\">\n\t\t\t\t\t\t<span class=\"cn-field-list-widget__title\">{{ row.title }}</span>\n\t\t\t\t\t\t<span v-if=\"row.meta\" class=\"cn-field-list-widget__meta\">{{ row.meta }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\tv-if=\"row.pill\"\n\t\t\t\t\t\t:label=\"row.pill.label\"\n\t\t\t\t\t\t:variant=\"row.pill.variant\"\n\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t<span v-if=\"row.aside\" class=\"cn-field-list-widget__aside\">{{ row.aside }}</span>\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-for=\"action in row.actions\"\n\t\t\t\t\t\t:key=\"action.id\"\n\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\t\t\t:data-testid=\"`cn-field-list-action-${action.id}-${row.key}`\"\n\t\t\t\t\t\t@click=\"runAction(action, row)\">\n\t\t\t\t\t\t{{ action.label }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"canRemove\"\n\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\t\t\t:aria-label=\"removeAriaLabel(row)\"\n\t\t\t\t\t\t:data-testid=\"`cn-field-list-remove-${row.key}`\"\n\t\t\t\t\t\t@click=\"remove(row)\">\n\t\t\t\t\t\t{{ removeLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t\t<p v-if=\"row.detail\" class=\"cn-field-list-widget__detail\">\n\t\t\t\t\t{{ row.detail }}\n\t\t\t\t</p>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<p v-if=\"hint\" class=\"cn-field-list-widget__hint\">\n\t\t\t{{ hint }}\n\t\t</p>\n\n\t\t<form\n\t\t\tv-if=\"canAdd\"\n\t\t\tclass=\"cn-field-list-widget__add\"\n\t\t\tdata-testid=\"cn-field-list-add\"\n\t\t\t@submit.prevent=\"add\">\n\t\t\t<div\n\t\t\t\tv-for=\"field in addFields\"\n\t\t\t\t:key=\"field.key\"\n\t\t\t\tclass=\"cn-field-list-widget__field\"\n\t\t\t\t:data-testid=\"`cn-field-list-input-${field.key}`\">\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-if=\"field.type === 'select'\"\n\t\t\t\t\tv-model=\"draft[field.key]\"\n\t\t\t\t\t:inputLabel=\"field.label\"\n\t\t\t\t\t:options=\"field.options\"\n\t\t\t\t\t:reduce=\"(option) => option.value\"\n\t\t\t\t\t:clearable=\"false\"\n\t\t\t\t\tlabel=\"label\" />\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else\n\t\t\t\t\tv-model=\"draft[field.key]\"\n\t\t\t\t\t:label=\"field.label\"\n\t\t\t\t\t:error=\"Boolean(errors[field.key])\"\n\t\t\t\t\t:helperText=\"errors[field.key] || ''\" />\n\t\t\t</div>\n\t\t\t<NcButton type=\"submit\"\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\t:disabled=\"busy !== ''\"\n\t\t\t\tdata-testid=\"cn-field-list-submit\">\n\t\t\t\t{{ addLabel }}\n\t\t\t</NcButton>\n\t\t</form>\n\n\t\t<NcNoteCard v-if=\"notice\" :type=\"noticeType\" data-testid=\"cn-field-list-notice\">\n\t\t\t{{ notice }}\n\t\t</NcNoteCard>\n\t</div>\n</template>\n\n<script>\nimport { NcButton, NcNoteCard, NcSelect, NcTextField } from '@nextcloud/vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { isBadgeVariant } from '../../utils/badgeVariants.js'\nimport { formatBoardDate } from '../../utils/boardDate.js'\nimport { parseDateValue } from '../../utils/dateVariant.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { serverErrorMessage } from '../../utils/serverErrorMessage.js'\n\n/**\n * CnFieldListWidget — the `field-list` widget: a list stored INSIDE one field\n * of the current object (a portal's `domains[]`, its `editors[]`), shown as\n * rows and edited in place.\n *\n * Rows are drawn from configuration (title, meta line, pill, aside, a detail\n * line); a small form adds an entry, a button removes one, and each change is\n * saved as ONE PATCH of that field on the object, after re-reading the stored\n * value so a concurrent change to the same list is not overwritten with a\n * stale copy. Optional row actions call a configured endpoint (a \"check\n * again\"), and an optional `rowSource` merges live per-row state read from an\n * endpoint by key without writing it back.\n *\n * The server decides who may write: a refused PATCH shows the server's words\n * and leaves the list as stored.\n *\n * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md\n */\nexport default {\n\tname: 'CnFieldListWidget',\n\n\tcomponents: { CnStatusBadge, NcButton, NcNoteCard, NcSelect, NcTextField },\n\n\tinject: {\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget configuration.\n\t\t *\n\t\t * - `field` (required) — the object field holding the array.\n\t\t * - `key` — the item field that identifies an entry (default: the item itself for strings).\n\t\t * - `row` — `{ title, titleFallback?, avatar?, meta?: [paths|{ path, map?, format? }], pill?: { path, map?, variantMap? }, aside?: { path, format?, prefix? }, detail?: { template, when? } }`.\n\t\t * - `add` — `{ label?, fields: [{ key, label, type?: 'text'|'select', options?, required?, pattern?, patternMessage? }], unique?, defaults? }`.\n\t\t * - `remove` — `{ label? }`; omitted = no remove.\n\t\t * - `rowActions` — `[{ id, label, when?, request: { method?, url, payload? } }]`; tokens `@object.<field>`, `@objectId`, `@row.<field>`.\n\t\t * - `rowSource` — `{ url, path?, key }`: live per-row state merged by key.\n\t\t * - `readOnly`, `hint`, `emptyText`.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The current detail-page object. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The current object id. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister register slug of the surface. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema slug of the surface. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['saved'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** The list as last saved by this widget; null = read it off objectData. */\n\t\t\tsaved: null,\n\t\t\t/** Live per-row state from `rowSource`, by key. */\n\t\t\tlive: {},\n\t\t\tdraft: {},\n\t\t\terrors: {},\n\t\t\tbusy: '',\n\t\t\tnotice: '',\n\t\t\tnoticeType: 'error',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The stored list.\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\titems() {\n\t\t\tconst list = this.saved !== null ? this.saved : readPath(this.objectData || {}, this.content.field)\n\t\t\treturn Array.isArray(list) ? list : []\n\t\t},\n\n\t\t/**\n\t\t * The rows to draw.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md#requirement-a-field-list-draws-the-entries-of-one-object-field\n\t\t */\n\t\trows() {\n\t\t\tconst spec = this.content.row || {}\n\t\t\treturn this.items.map((item, index) => {\n\t\t\t\tconst key = this.keyOf(item, index)\n\t\t\t\tconst entry = item && typeof item === 'object'\n\t\t\t\t\t? { ...item, ...(this.live[key] || {}) }\n\t\t\t\t\t: { value: item, ...(this.live[key] || {}) }\n\t\t\t\tconst title = this.text(entry, spec.title) || this.text(entry, spec.titleFallback) || (typeof item === 'string' ? item : key)\n\t\t\t\tconst meta = (Array.isArray(spec.meta) ? spec.meta : [])\n\t\t\t\t\t.map((part) => this.text(entry, part))\n\t\t\t\t\t.filter((part) => part !== '')\n\t\t\t\t\t.join(' · ')\n\t\t\t\treturn {\n\t\t\t\t\tkey,\n\t\t\t\t\titem,\n\t\t\t\t\tentry,\n\t\t\t\t\ttitle,\n\t\t\t\t\tinitials: spec.avatar ? this.initials(title) : '',\n\t\t\t\t\tmeta,\n\t\t\t\t\tpill: this.pillOf(entry, spec.pill),\n\t\t\t\t\taside: spec.aside ? this.asideOf(entry, spec.aside) : '',\n\t\t\t\t\tdetail: spec.detail && this.matches(entry, spec.detail.when) ? this.fill(spec.detail.template, entry) : '',\n\t\t\t\t\tactions: this.actionsFor(entry),\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/** Whether the list is read-only. */\n\t\treadOnly() {\n\t\t\treturn this.content.readOnly === true\n\t\t},\n\n\t\t/** Whether entries can be added. */\n\t\tcanAdd() {\n\t\t\treturn !this.readOnly && this.addFields.length > 0 && Boolean(this.content.field)\n\t\t},\n\n\t\t/** Whether entries can be removed. */\n\t\tcanRemove() {\n\t\t\treturn !this.readOnly && Boolean(this.content.remove) && Boolean(this.content.field)\n\t\t},\n\n\t\t/**\n\t\t * The add form's fields, labels translated, select options translated.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\taddFields() {\n\t\t\tconst fields = this.content.add && Array.isArray(this.content.add.fields) ? this.content.add.fields : []\n\t\t\treturn fields\n\t\t\t\t.filter((field) => field && field.key)\n\t\t\t\t.map((field) => ({\n\t\t\t\t\t...field,\n\t\t\t\t\tlabel: this.tr(field.label || field.key),\n\t\t\t\t\toptions: (Array.isArray(field.options) ? field.options : []).map((option) => (\n\t\t\t\t\t\ttypeof option === 'object' ? { value: option.value, label: this.tr(option.label || String(option.value)) } : { value: option, label: this.tr(String(option)) }\n\t\t\t\t\t)),\n\t\t\t\t}))\n\t\t},\n\n\t\taddLabel() {\n\t\t\treturn this.tr((this.content.add && this.content.add.label) || 'Add')\n\t\t},\n\n\t\tremoveLabel() {\n\t\t\treturn this.tr((this.content.remove && this.content.remove.label) || 'Remove')\n\t\t},\n\n\t\thint() {\n\t\t\treturn this.tr(this.content.hint || '')\n\t\t},\n\n\t\temptyLabel() {\n\t\t\treturn this.tr(this.content.emptyText || 'Nothing listed yet')\n\t\t},\n\n\t\t/** The object URL the list is saved to, or '' when it is not known. */\n\t\tobjectUrl() {\n\t\t\tconst id = this.objectId || (this.objectData && this.objectData.id)\n\t\t\tif (!this.register || !this.schema || !id || typeof this.register !== 'string' || typeof this.schema !== 'string') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `/apps/openregister/api/objects/${encodeURIComponent(this.register)}/${encodeURIComponent(this.schema)}/${encodeURIComponent(String(id))}`\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectData() {\n\t\t\tthis.saved = null\n\t\t\tthis.loadLive()\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.resetDraft()\n\t\tthis.loadLive()\n\t},\n\n\tmethods: {\n\t\ttr(text) {\n\t\t\tif (typeof text !== 'string' || text === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\t/**\n\t\t * The identity of an entry.\n\t\t *\n\t\t * @param {unknown} item The entry.\n\t\t * @param {number} index Its position.\n\t\t * @return {string}\n\t\t */\n\t\tkeyOf(item, index) {\n\t\t\tif (item === null || typeof item !== 'object') {\n\t\t\t\treturn String(item)\n\t\t\t}\n\t\t\tconst key = this.content.key ? readPath(item, this.content.key) : undefined\n\t\t\treturn key === undefined || key === null || key === '' ? `item-${index}` : String(key)\n\t\t},\n\n\t\t/**\n\t\t * A display text off an entry: a path, or `{ path, map?, format? }`.\n\t\t *\n\t\t * @param {object} entry The entry.\n\t\t * @param {string|object} spec The spec.\n\t\t * @return {string}\n\t\t */\n\t\ttext(entry, spec) {\n\t\t\tif (!spec) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst def = typeof spec === 'string' ? { path: spec } : spec\n\t\t\tconst raw = readPath(entry, def.path)\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (def.format === 'date' || def.format === 'date-long') {\n\t\t\t\tconst date = parseDateValue(raw)\n\t\t\t\tif (date === null) {\n\t\t\t\t\treturn ''\n\t\t\t\t}\n\t\t\t\treturn def.format === 'date-long' ? formatBoardDate(date, new Date(date.getFullYear() + 1, 0, 1)) : formatBoardDate(date)\n\t\t\t}\n\t\t\tif (def.map && Object.hasOwn(def.map, String(raw))) {\n\t\t\t\treturn this.tr(def.map[String(raw)])\n\t\t\t}\n\t\t\treturn typeof raw === 'object' ? '' : String(raw)\n\t\t},\n\n\t\tpillOf(entry, spec) {\n\t\t\tif (!spec || !spec.path) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst raw = readPath(entry, spec.path)\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst label = this.text(entry, spec)\n\t\t\tconst variant = spec.variantMap && spec.variantMap[String(raw)]\n\t\t\treturn { label, variant: isBadgeVariant(variant) ? variant : 'default' }\n\t\t},\n\n\t\tasideOf(entry, spec) {\n\t\t\tconst value = this.text(entry, spec)\n\t\t\tif (value === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn spec.prefix ? `${this.tr(spec.prefix)} ${value}` : value\n\t\t},\n\n\t\t/**\n\t\t * Whether an entry matches a `{ field: value }` condition (every field equal).\n\t\t *\n\t\t * @param {object} entry The entry.\n\t\t * @param {object|undefined} when The condition.\n\t\t * @return {boolean}\n\t\t */\n\t\tmatches(entry, when) {\n\t\t\tif (!when || typeof when !== 'object') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn Object.entries(when).every(([field, expected]) => {\n\t\t\t\tconst actual = readPath(entry, field)\n\t\t\t\tif (expected === false) {\n\t\t\t\t\treturn actual === false || actual === undefined || actual === null || actual === ''\n\t\t\t\t}\n\t\t\t\treturn String(actual) === String(expected)\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Fill `{path}` placeholders from an entry; '' when one stays empty.\n\t\t *\n\t\t * @param {string} template The template, translated first.\n\t\t * @param {object} entry The entry.\n\t\t * @return {string}\n\t\t */\n\t\tfill(template, entry) {\n\t\t\tif (typeof template !== 'string') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tlet missing = false\n\t\t\tconst out = this.tr(template).replace(/\\{([\\w.@]+)\\}/g, (match, path) => {\n\t\t\t\tconst value = readPath(entry, path)\n\t\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\t\tmissing = true\n\t\t\t\t\treturn match\n\t\t\t\t}\n\t\t\t\treturn String(value)\n\t\t\t})\n\t\t\treturn missing ? '' : out\n\t\t},\n\n\t\tactionsFor(entry) {\n\t\t\tif (this.readOnly) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst actions = Array.isArray(this.content.rowActions) ? this.content.rowActions : []\n\t\t\treturn actions\n\t\t\t\t.filter((action) => action && action.id && action.request && action.request.url && this.matches(entry, action.when))\n\t\t\t\t.map((action) => ({ ...action, label: this.tr(action.label || action.id) }))\n\t\t},\n\n\t\tinitials(name) {\n\t\t\treturn String(name || '').split(/[\\s@._-]+/).filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join('')\n\t\t},\n\n\t\tremoveAriaLabel(row) {\n\t\t\treturn `${this.removeLabel}: ${row.title}`\n\t\t},\n\n\t\tresetDraft() {\n\t\t\tconst draft = {}\n\t\t\tfor (const field of this.addFields) {\n\t\t\t\tconst first = field.type === 'select' && field.options.length > 0 ? field.options[0].value : ''\n\t\t\t\tdraft[field.key] = field.default !== undefined ? field.default : first\n\t\t\t}\n\t\t\tthis.draft = draft\n\t\t\tthis.errors = {}\n\t\t},\n\n\t\t/**\n\t\t * Resolve `@objectId`, `@object.<field>` and `@row.<field>` inside a string or a payload.\n\t\t *\n\t\t * @param {unknown} value The value.\n\t\t * @param {object} entry The row entry.\n\t\t * @return {unknown}\n\t\t */\n\t\tresolve(value, entry) {\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\treturn value.map((v) => this.resolve(v, entry))\n\t\t\t}\n\t\t\tif (value && typeof value === 'object') {\n\t\t\t\treturn Object.fromEntries(Object.entries(value).map(([k, v]) => [k, this.resolve(v, entry)]))\n\t\t\t}\n\t\t\tif (typeof value !== 'string') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\tconst object = this.objectData || {}\n\t\t\tconst read = (source, path) => {\n\t\t\t\tconst v = readPath(source || {}, path)\n\t\t\t\treturn v === undefined || v === null ? '' : v\n\t\t\t}\n\t\t\tconst whole = value.match(/^@(row|object)\\.([\\w.@]+)$/)\n\t\t\tif (whole) {\n\t\t\t\treturn read(whole[1] === 'row' ? entry : object, whole[2])\n\t\t\t}\n\t\t\treturn value\n\t\t\t\t.replace(/@objectId/g, () => encodeURIComponent(String(this.objectId || object.id || '')))\n\t\t\t\t.replace(/@(row|object)\\.([\\w.@]+)/g, (m, scope, path) => encodeURIComponent(String(read(scope === 'row' ? entry : object, path))))\n\t\t},\n\n\t\tasync http() {\n\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\t\t\treturn { axios }\n\t\t},\n\n\t\t/**\n\t\t * Read live per-row state from `rowSource` and keep it by key.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadLive() {\n\t\t\tconst source = this.content.rowSource\n\t\t\tif (!source || !source.url || !source.key || !this.objectData) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { axios } = await this.http()\n\t\t\t\tconst response = await axios.get(prefixUrl(this.resolve(source.url, {})))\n\t\t\t\tconst data = response && response.data\n\t\t\t\tconst list = source.path ? readPath(data, source.path) : data\n\t\t\t\tconst live = {}\n\t\t\t\tfor (const item of Array.isArray(list) ? list : []) {\n\t\t\t\t\tconst key = item && readPath(item, source.key)\n\t\t\t\t\tif (key !== undefined && key !== null) {\n\t\t\t\t\t\tlive[String(key)] = item\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.live = live\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn('[CnFieldListWidget] rowSource could not be read:', e?.message || e)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Save a new list: re-read the stored value, apply the change to it, and\n\t\t * PATCH that one field.\n\t\t *\n\t\t * @param {(stored: Array) => Array} change Builds the next list from the stored one, or throws an Error with a `userMessage`.\n\t\t * @param {string} busyKey What is busy.\n\t\t * @return {Promise<boolean>} Whether it saved.\n\t\t * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md#requirement-a-field-list-saves-one-field-with-one-patch\n\t\t */\n\t\tasync save(change, busyKey) {\n\t\t\tif (!this.objectUrl) {\n\t\t\t\tthis.notify(this.tr('This list cannot be saved here.'))\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tthis.busy = busyKey\n\t\t\tthis.notice = ''\n\t\t\ttry {\n\t\t\t\tconst { axios } = await this.http()\n\t\t\t\tconst { data: stored } = await axios.get(prefixUrl(this.objectUrl))\n\t\t\t\tconst current = readPath(stored || {}, this.content.field)\n\t\t\t\tconst next = change(Array.isArray(current) ? current : [])\n\t\t\t\tconst { data } = await axios.patch(prefixUrl(this.objectUrl), { [this.content.field]: next })\n\t\t\t\tconst written = readPath(data || {}, this.content.field)\n\t\t\t\tthis.saved = Array.isArray(written) ? written : next\n\t\t\t\t/**\n\t\t\t\t * @event saved The list was saved: one PATCH of the field succeeded.\n\t\t\t\t * @type {{field: string, value: Array}}\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', { field: this.content.field, value: this.saved })\n\t\t\t\treturn true\n\t\t\t} catch (e) {\n\t\t\t\tthis.notify(e && e.userMessage ? e.userMessage : serverErrorMessage(e))\n\t\t\t\treturn false\n\t\t\t} finally {\n\t\t\t\tthis.busy = ''\n\t\t\t}\n\t\t},\n\n\t\tnotify(message, type = 'error') {\n\t\t\tthis.notice = message\n\t\t\tthis.noticeType = type\n\t\t},\n\n\t\t/**\n\t\t * Validate the draft and add it.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md#requirement-a-field-list-adds-and-removes-entries\n\t\t */\n\t\tasync add() {\n\t\t\tconst errors = {}\n\t\t\tfor (const field of this.addFields) {\n\t\t\t\tconst value = this.draft[field.key]\n\t\t\t\tconst empty = value === undefined || value === null || String(value).trim() === ''\n\t\t\t\tif (field.required && empty) {\n\t\t\t\t\terrors[field.key] = this.tr('Fill this in.')\n\t\t\t\t} else if (!empty && field.pattern && !new RegExp(field.pattern).test(String(value).trim())) {\n\t\t\t\t\terrors[field.key] = this.tr(field.patternMessage || 'This value is not valid.')\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.errors = errors\n\t\t\tif (Object.keys(errors).length > 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst entry = { ...((this.content.add && this.content.add.defaults) || {}) }\n\t\t\tfor (const field of this.addFields) {\n\t\t\t\tconst value = this.draft[field.key]\n\t\t\t\tif (value !== undefined && value !== null && String(value).trim() !== '') {\n\t\t\t\t\tentry[field.key] = typeof value === 'string' ? value.trim() : value\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst unique = this.content.add && this.content.add.unique\n\t\t\tconst saved = await this.save((stored) => {\n\t\t\t\tif (unique && stored.some((item) => item && String(readPath(item, unique)) === String(entry[unique]))) {\n\t\t\t\t\tconst error = new Error('duplicate')\n\t\t\t\t\terror.userMessage = this.tr('This is already in the list.')\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t\treturn [...stored, entry]\n\t\t\t}, '*')\n\t\t\tif (saved) {\n\t\t\t\tthis.resetDraft()\n\t\t\t\tthis.loadLive()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove an entry by its key.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync remove(row) {\n\t\t\tawait this.save((stored) => stored.filter((item, index) => this.keyOf(item, index) !== row.key), row.key)\n\t\t},\n\n\t\t/**\n\t\t * Run a row action's request, then re-read the live state.\n\t\t *\n\t\t * @param {object} action The action.\n\t\t * @param {object} row The row.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/abstract-field-list/specs/field-list/spec.md#requirement-a-field-list-row-can-run-a-configured-request\n\t\t */\n\t\tasync runAction(action, row) {\n\t\t\tthis.busy = row.key\n\t\t\tthis.notice = ''\n\t\t\ttry {\n\t\t\t\tconst { axios } = await this.http()\n\t\t\t\tconst method = String(action.request.method || 'POST').toLowerCase()\n\t\t\t\tconst payload = this.resolve(action.request.payload || {}, row.entry)\n\t\t\t\tconst response = method === 'get'\n\t\t\t\t\t? await axios.get(prefixUrl(this.resolve(action.request.url, row.entry)))\n\t\t\t\t\t: await axios[method](prefixUrl(this.resolve(action.request.url, row.entry)), payload)\n\t\t\t\tconst message = response && response.data && typeof response.data.message === 'string' ? response.data.message : ''\n\t\t\t\tif (message) {\n\t\t\t\t\tthis.notify(message, 'info')\n\t\t\t\t}\n\t\t\t\tawait this.loadLive()\n\t\t\t} catch (e) {\n\t\t\t\tthis.notify(serverErrorMessage(e))\n\t\t\t} finally {\n\t\t\t\tthis.busy = ''\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-field-list-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tmin-width: 0;\n}\n\n.cn-field-list-widget__rows {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-field-list-widget__row {\n\tpadding: 10px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-field-list-widget__row:first-child {\n\tborder-top: none;\n\tpadding-top: 0;\n}\n\n.cn-field-list-widget__line {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tmin-width: 0;\n}\n\n.cn-field-list-widget__avatar {\n\tflex: 0 0 auto;\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: 50%;\n\tbackground: var(--color-primary-element-light);\n\tcolor: var(--color-primary-element-light-text, var(--color-main-text));\n\tfont-size: 12px;\n\tfont-weight: 700;\n}\n\n.cn-field-list-widget__main {\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-field-list-widget__title {\n\tfont-weight: 600;\n\toverflow-wrap: anywhere;\n}\n\n.cn-field-list-widget__meta,\n.cn-field-list-widget__aside,\n.cn-field-list-widget__detail,\n.cn-field-list-widget__hint,\n.cn-field-list-widget__empty {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-list-widget__meta,\n.cn-field-list-widget__aside,\n.cn-field-list-widget__detail {\n\tfont-size: 13px;\n}\n\n.cn-field-list-widget__aside {\n\twhite-space: nowrap;\n}\n\n.cn-field-list-widget__detail,\n.cn-field-list-widget__hint,\n.cn-field-list-widget__empty {\n\tmargin: 4px 0 0;\n\toverflow-wrap: anywhere;\n}\n\n.cn-field-list-widget__add {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-end;\n\tgap: 8px 12px;\n}\n\n.cn-field-list-widget__add > * {\n\tflex: 1 1 180px;\n}\n\n.cn-field-list-widget__add > button {\n\tflex: 0 0 auto;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_toDisplayString","_Fragment","_renderList","_createElementVNode","_createBlock","_createTextVNode","_createVNode"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import __sfc_main from './CnFieldListWidget.vue.js';
|
|
2
|
+
import { registerDashboardWidget } from '../CnWidgetGrid/dashboardWidgetRegistry.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
6
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
7
|
+
*
|
|
8
|
+
* CnFieldListWidget — self-registers the `field-list` widget type (a list
|
|
9
|
+
* stored inside one field of the current object, shown as rows and edited
|
|
10
|
+
* with one PATCH of that field) into the shared dashboardWidgetRegistry at
|
|
11
|
+
* module load. Renderer only: declared in the manifest.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
registerDashboardWidget('field-list', {
|
|
16
|
+
renderer: __sfc_main,
|
|
17
|
+
form: null,
|
|
18
|
+
defaultContent: {
|
|
19
|
+
field: '',
|
|
20
|
+
row: {},
|
|
21
|
+
readOnly: false,
|
|
22
|
+
},
|
|
23
|
+
displayName: 'Field list',
|
|
24
|
+
icon: 'FormatListBulleted',
|
|
25
|
+
surfaces: ['detail-page'],
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export { __sfc_main as CnFieldListWidget, __sfc_main as default };
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/components/CnFieldListWidget/index.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * CnFieldListWidget — self-registers the `field-list` widget type (a list\n * stored inside one field of the current object, shown as rows and edited\n * with one PATCH of that field) into the shared dashboardWidgetRegistry at\n * module load. Renderer only: declared in the manifest.\n */\n\nimport CnFieldListWidget from './CnFieldListWidget.vue'\nimport { registerDashboardWidget } from '../CnWidgetGrid/dashboardWidgetRegistry.js'\n\nregisterDashboardWidget('field-list', {\n\trenderer: CnFieldListWidget,\n\tform: null,\n\tdefaultContent: {\n\t\tfield: '',\n\t\trow: {},\n\t\treadOnly: false,\n\t},\n\tdisplayName: 'Field list',\n\ticon: 'FormatListBulleted',\n\tsurfaces: ['detail-page'],\n})\n\nexport { CnFieldListWidget }\nexport default CnFieldListWidget\n"],"names":["CnFieldListWidget"],"mappings":";;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA,uBAAuB,CAAC,YAAY,EAAE;AACtC,CAAC,QAAQ,EAAEA,UAAiB;AAC5B,CAAC,IAAI,EAAE,IAAI;AACX,CAAC,cAAc,EAAE;AACjB,EAAE,KAAK,EAAE,EAAE;AACX,EAAE,GAAG,EAAE,EAAE;AACT,EAAE,QAAQ,EAAE,KAAK;AACjB,EAAE;AACF,CAAC,WAAW,EAAE,YAAY;AAC1B,CAAC,IAAI,EAAE,oBAAoB;AAC3B,CAAC,QAAQ,EAAE,CAAC,aAAa,CAAC;AAC1B,CAAC;;;;"}
|
|
@@ -14,6 +14,7 @@ import FolderOutline from 'vue-material-design-icons/FolderOutline.vue';
|
|
|
14
14
|
import OpenInNew from 'vue-material-design-icons/OpenInNew.vue';
|
|
15
15
|
import Pencil from 'vue-material-design-icons/Pencil.vue';
|
|
16
16
|
import Plus from 'vue-material-design-icons/Plus.vue';
|
|
17
|
+
import TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue';
|
|
17
18
|
import Upload from 'vue-material-design-icons/Upload.vue';
|
|
18
19
|
import __sfc_main from '../CnCellRenderer/CnCellRenderer.vue.js';
|
|
19
20
|
import __sfc_main$2 from '../CnEmptyContent/CnEmptyContent.vue.js';
|
|
@@ -57,6 +58,7 @@ var script = {
|
|
|
57
58
|
OpenInNew,
|
|
58
59
|
Pencil,
|
|
59
60
|
Plus,
|
|
61
|
+
TrashCanOutline,
|
|
60
62
|
Upload,
|
|
61
63
|
},
|
|
62
64
|
|
|
@@ -67,6 +69,30 @@ var script = {
|
|
|
67
69
|
},
|
|
68
70
|
|
|
69
71
|
props: {
|
|
72
|
+
/**
|
|
73
|
+
* Where the folder is read from and written to. Null (the default)
|
|
74
|
+
* is WebDAV under the signed-in person's files root, with the Files
|
|
75
|
+
* app's own actions and New menu. An object from
|
|
76
|
+
* `createOpenRegisterSource()` reads one OpenRegister object's folder
|
|
77
|
+
* through OpenRegister's files API instead, under the object's access
|
|
78
|
+
* rule: `rootPath` is then `/`, the Files app's actions and New menu
|
|
79
|
+
* are not offered (they act over WebDAV), and upload, new folder,
|
|
80
|
+
* rename and delete appear only when the listing says the person may
|
|
81
|
+
* change the folder.
|
|
82
|
+
*
|
|
83
|
+
* @type {object|null}
|
|
84
|
+
*/
|
|
85
|
+
source: {
|
|
86
|
+
type: Object,
|
|
87
|
+
default: null,
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/** Label of the delete action and its dialog, with a `source`. */
|
|
91
|
+
deleteLabel: {
|
|
92
|
+
type: String,
|
|
93
|
+
default: () => translate('nextcloud-vue', 'Delete'),
|
|
94
|
+
},
|
|
95
|
+
|
|
70
96
|
/**
|
|
71
97
|
* The host's own actions on each file row, declared the way a
|
|
72
98
|
* manifest declares any action (`open-modal`, `handler`, ...), and
|
|
@@ -337,6 +363,9 @@ var script = {
|
|
|
337
363
|
renaming: null,
|
|
338
364
|
renameName: '',
|
|
339
365
|
renameError: '',
|
|
366
|
+
/** The row being deleted (with a `source`), or null. */
|
|
367
|
+
removing: null,
|
|
368
|
+
removeError: '',
|
|
340
369
|
fileInputEl: null,
|
|
341
370
|
/**
|
|
342
371
|
* The view the Files app's actions are handed. They read `view.id`
|
|
@@ -356,6 +385,20 @@ var script = {
|
|
|
356
385
|
},
|
|
357
386
|
|
|
358
387
|
computed: {
|
|
388
|
+
/**
|
|
389
|
+
* Whether this person may add to and change the open folder. Over
|
|
390
|
+
* WebDAV the server answers each write on its own, so this is true;
|
|
391
|
+
* with a `source` it is what the listing said.
|
|
392
|
+
*
|
|
393
|
+
* @return {boolean} True when upload, new folder, rename and delete are offered.
|
|
394
|
+
*/
|
|
395
|
+
canChange() {
|
|
396
|
+
if (!this.source) {
|
|
397
|
+
return true
|
|
398
|
+
}
|
|
399
|
+
return this.folder !== null && this.folder.canChange === true
|
|
400
|
+
},
|
|
401
|
+
|
|
359
402
|
/**
|
|
360
403
|
* The breadcrumb trail from the root to the open folder.
|
|
361
404
|
*
|
|
@@ -460,7 +503,7 @@ var script = {
|
|
|
460
503
|
* @return {Array<object>} The entries, in the plugins' order.
|
|
461
504
|
*/
|
|
462
505
|
newMenuEntries() {
|
|
463
|
-
if (this.folder === null) {
|
|
506
|
+
if (this.folder === null || this.source) {
|
|
464
507
|
return []
|
|
465
508
|
}
|
|
466
509
|
try {
|
|
@@ -518,6 +561,10 @@ var script = {
|
|
|
518
561
|
async refresh() {
|
|
519
562
|
this.loading = true;
|
|
520
563
|
this.error = '';
|
|
564
|
+
if (this.source) {
|
|
565
|
+
await this.refreshFromSource();
|
|
566
|
+
return
|
|
567
|
+
}
|
|
521
568
|
try {
|
|
522
569
|
const client = getClient();
|
|
523
570
|
const { data } = await client.getDirectoryContents(this.davPath(this.currentPath), {
|
|
@@ -545,6 +592,70 @@ var script = {
|
|
|
545
592
|
}
|
|
546
593
|
},
|
|
547
594
|
|
|
595
|
+
/**
|
|
596
|
+
* List the open folder through the `source`.
|
|
597
|
+
*
|
|
598
|
+
* @return {Promise<void>}
|
|
599
|
+
*/
|
|
600
|
+
async refreshFromSource() {
|
|
601
|
+
try {
|
|
602
|
+
const { folder, nodes } = await this.source.list(this.currentPath);
|
|
603
|
+
this.folder = folder;
|
|
604
|
+
this.nodes = nodes;
|
|
605
|
+
await this.resolveRowData(this.nodes);
|
|
606
|
+
} catch (err) {
|
|
607
|
+
const atRoot = (this.currentPath.replace(/\/+$/, '') || '/') === (this.rootPath.replace(/\/+$/, '') || '/');
|
|
608
|
+
this.error = err?.status === 404
|
|
609
|
+
? (atRoot ? translate('nextcloud-vue', 'You cannot see these files') : translate('nextcloud-vue', 'This folder no longer exists'))
|
|
610
|
+
: (err?.status === 403 ? translate('nextcloud-vue', 'You cannot see this folder') : translate('nextcloud-vue', 'The folder could not be read'));
|
|
611
|
+
this.folder = null;
|
|
612
|
+
this.nodes = [];
|
|
613
|
+
} finally {
|
|
614
|
+
this.loading = false;
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Where a file downloads from, through the `source`.
|
|
620
|
+
*
|
|
621
|
+
* @param {object} node The row.
|
|
622
|
+
* @return {string|null} The url.
|
|
623
|
+
*/
|
|
624
|
+
sourceDownloadUrl(node) {
|
|
625
|
+
return this.source && typeof this.source.downloadUrl === 'function' ? this.source.downloadUrl(node) : null
|
|
626
|
+
},
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Open the delete dialog on a row.
|
|
630
|
+
*
|
|
631
|
+
* @param {object} node The row.
|
|
632
|
+
* @return {void}
|
|
633
|
+
*/
|
|
634
|
+
askRemove(node) {
|
|
635
|
+
this.removing = node;
|
|
636
|
+
this.removeError = '';
|
|
637
|
+
},
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Delete the row named in the dialog, through the `source`.
|
|
641
|
+
*
|
|
642
|
+
* @return {Promise<void>}
|
|
643
|
+
*/
|
|
644
|
+
async remove() {
|
|
645
|
+
const node = this.removing;
|
|
646
|
+
if (node === null || !this.source) {
|
|
647
|
+
return
|
|
648
|
+
}
|
|
649
|
+
try {
|
|
650
|
+
await this.source.remove(node);
|
|
651
|
+
this.removing = null;
|
|
652
|
+
await this.refresh();
|
|
653
|
+
this.$emit('changed');
|
|
654
|
+
} catch (err) {
|
|
655
|
+
this.removeError = err?.message || translate('nextcloud-vue', '{action} did not succeed', { action: this.deleteLabel });
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
|
|
548
659
|
/**
|
|
549
660
|
* Open a folder below the root.
|
|
550
661
|
*
|
|
@@ -582,6 +693,13 @@ var script = {
|
|
|
582
693
|
this.navigate(node.path);
|
|
583
694
|
return
|
|
584
695
|
}
|
|
696
|
+
if (this.source) {
|
|
697
|
+
const url = this.sourceDownloadUrl(node);
|
|
698
|
+
if (url) {
|
|
699
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
700
|
+
}
|
|
701
|
+
return
|
|
702
|
+
}
|
|
585
703
|
const view = this.actionsFor(node).find((action) => action.id === 'view');
|
|
586
704
|
if (view) {
|
|
587
705
|
await this.run(view, node);
|
|
@@ -628,6 +746,16 @@ var script = {
|
|
|
628
746
|
* @return {void}
|
|
629
747
|
*/
|
|
630
748
|
openInNewTab(node) {
|
|
749
|
+
if (this.source) {
|
|
750
|
+
if (this.isFolder(node)) {
|
|
751
|
+
return
|
|
752
|
+
}
|
|
753
|
+
const url = this.sourceDownloadUrl(node);
|
|
754
|
+
if (url) {
|
|
755
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
756
|
+
}
|
|
757
|
+
return
|
|
758
|
+
}
|
|
631
759
|
if (node.fileid) {
|
|
632
760
|
window.open(this.permalink(node), '_blank', 'noopener,noreferrer');
|
|
633
761
|
}
|
|
@@ -655,6 +783,9 @@ var script = {
|
|
|
655
783
|
* @return {Array<object>} The actions.
|
|
656
784
|
*/
|
|
657
785
|
actionsFor(node) {
|
|
786
|
+
if (this.source) {
|
|
787
|
+
return []
|
|
788
|
+
}
|
|
658
789
|
let actions;
|
|
659
790
|
try {
|
|
660
791
|
actions = getFileActions();
|
|
@@ -784,7 +915,11 @@ var script = {
|
|
|
784
915
|
return
|
|
785
916
|
}
|
|
786
917
|
try {
|
|
787
|
-
|
|
918
|
+
if (this.source) {
|
|
919
|
+
await this.source.createFolder(this.currentPath, name);
|
|
920
|
+
} else {
|
|
921
|
+
await getClient().createDirectory(this.davPath(joinPath(this.currentPath, name)));
|
|
922
|
+
}
|
|
788
923
|
this.newFolderOpen = false;
|
|
789
924
|
await this.refresh();
|
|
790
925
|
this.$emit('changed');
|
|
@@ -825,7 +960,11 @@ var script = {
|
|
|
825
960
|
return
|
|
826
961
|
}
|
|
827
962
|
try {
|
|
828
|
-
|
|
963
|
+
if (this.source) {
|
|
964
|
+
await this.source.rename(node, name);
|
|
965
|
+
} else {
|
|
966
|
+
await getClient().moveFile(this.davPath(node.path), this.davPath(joinPath(this.currentPath, name)));
|
|
967
|
+
}
|
|
829
968
|
this.renaming = null;
|
|
830
969
|
await this.refresh();
|
|
831
970
|
this.$emit('changed');
|
|
@@ -869,6 +1008,9 @@ var script = {
|
|
|
869
1008
|
this.dragging = false;
|
|
870
1009
|
this.dragDepth = 0;
|
|
871
1010
|
this.dropStatus = '';
|
|
1011
|
+
if (!this.canChange) {
|
|
1012
|
+
return
|
|
1013
|
+
}
|
|
872
1014
|
const files = event.dataTransfer?.files;
|
|
873
1015
|
if (files?.length) {
|
|
874
1016
|
await this.uploadFiles(files);
|
|
@@ -886,7 +1028,7 @@ var script = {
|
|
|
886
1028
|
* @return {void}
|
|
887
1029
|
*/
|
|
888
1030
|
onDragEnter(event) {
|
|
889
|
-
if (!this.carriesFiles(event)) {
|
|
1031
|
+
if (!this.canChange || !this.carriesFiles(event)) {
|
|
890
1032
|
return
|
|
891
1033
|
}
|
|
892
1034
|
this.dragDepth++;
|
|
@@ -903,7 +1045,7 @@ var script = {
|
|
|
903
1045
|
* @return {void}
|
|
904
1046
|
*/
|
|
905
1047
|
onDragOver(event) {
|
|
906
|
-
if (this.carriesFiles(event)) {
|
|
1048
|
+
if (this.canChange && this.carriesFiles(event)) {
|
|
907
1049
|
this.dragging = true;
|
|
908
1050
|
}
|
|
909
1051
|
},
|
|
@@ -956,6 +1098,10 @@ var script = {
|
|
|
956
1098
|
const batch = [...files].map((file) => ({ key: `u${++uploadSeq}`, name: file.name, progress: 0, error: '', file }));
|
|
957
1099
|
this.uploads = [...this.uploads, ...batch];
|
|
958
1100
|
for (const upload of batch) {
|
|
1101
|
+
if (this.source) {
|
|
1102
|
+
await this.uploadThroughSource(upload);
|
|
1103
|
+
continue
|
|
1104
|
+
}
|
|
959
1105
|
try {
|
|
960
1106
|
await axios__default.put(`${getRemoteURL()}${this.davPath(joinPath(this.currentPath, upload.name))}`, upload.file, {
|
|
961
1107
|
headers: {
|
|
@@ -984,6 +1130,25 @@ var script = {
|
|
|
984
1130
|
}, 1500);
|
|
985
1131
|
},
|
|
986
1132
|
|
|
1133
|
+
/**
|
|
1134
|
+
* Store one upload through the `source`, keeping its progress row.
|
|
1135
|
+
*
|
|
1136
|
+
* @param {object} upload The progress row, carrying the file.
|
|
1137
|
+
* @return {Promise<void>}
|
|
1138
|
+
*/
|
|
1139
|
+
async uploadThroughSource(upload) {
|
|
1140
|
+
try {
|
|
1141
|
+
await this.source.upload(this.currentPath, upload.file, (percent) => {
|
|
1142
|
+
upload.progress = percent;
|
|
1143
|
+
});
|
|
1144
|
+
upload.progress = 100;
|
|
1145
|
+
} catch (err) {
|
|
1146
|
+
upload.error = err?.status === 409
|
|
1147
|
+
? translate('nextcloud-vue', 'A file with that name is already here')
|
|
1148
|
+
: (err?.message || translate('nextcloud-vue', 'The upload failed'));
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
|
|
987
1152
|
/**
|
|
988
1153
|
* Change the sort column, or flip the order on the same one.
|
|
989
1154
|
*
|
|
@@ -1216,6 +1381,9 @@ var script = {
|
|
|
1216
1381
|
if (!node.fileid || this.isFolder(node) || !/^image\//i.test(mime) || /svg/i.test(mime) || this.previewFailed[node.fileid]) {
|
|
1217
1382
|
return null
|
|
1218
1383
|
}
|
|
1384
|
+
if (this.source) {
|
|
1385
|
+
return typeof this.source.previewUrl === 'function' ? this.source.previewUrl(node) : null
|
|
1386
|
+
}
|
|
1219
1387
|
return generateUrl('/core/preview?fileId={fileId}&x=64&y=64&a=1', { fileId: node.fileid })
|
|
1220
1388
|
},
|
|
1221
1389
|
|