@conduction/nextcloud-vue 2.78.0 → 2.79.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/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +113 -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/CnCellRenderer/CnCellRenderer.vue2.js +80 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +115 -91
- 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 +4 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +61 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- 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 +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +25 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +3 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- 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/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/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/CnTabsWidget/CnTabsWidget.vue2.js +30 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.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/composables/useAppManifest.js +8 -3
- package/dist/esm/composables/useAppManifest.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +61 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +106 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +314 -15
- package/dist/esm/utils/boardDate.js +193 -0
- package/dist/esm/utils/boardDate.js.map +1 -0
- package/dist/esm/utils/dynamicProperties.js +5 -1
- package/dist/esm/utils/dynamicProperties.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/nextcloud-vue.cjs.js +2039 -581
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +314 -15
- package/l10n/en.json +61 -1
- package/l10n/nl.json +106 -2
- package/package.json +1 -1
- package/scripts/build-validators.js +26 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +48 -39
- package/src/components/CnAppRoot/CnAppRoot.vue +12 -2
- package/src/components/CnCellRenderer/CnCellRenderer.vue +119 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +219 -4
- package/src/components/CnDataTable/CnDataTable.md +2 -0
- package/src/components/CnDataTable/CnDataTable.vue +81 -2
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +61 -1
- 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.vue +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +27 -3
- package/src/components/CnObjectSidebar/CnFilesTab.vue +103 -4
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +3 -2
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +1 -1
- package/src/components/CnTabsWidget/CnTabsWidget.vue +30 -1
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
- package/src/components/index.js +1 -1
- package/src/composables/useAppManifest.js +8 -3
- package/src/css/look-board-detail.css +10 -0
- package/src/css/look-board-index.css +92 -14
- package/src/css/look-board.css +77 -0
- package/src/css/table.css +37 -0
- package/src/index.js +4 -0
- package/src/schemas/app-manifest-v2.schema.json +46 -1
- package/src/utils/boardDate.js +189 -0
- package/src/utils/dynamicProperties.js +5 -1
- package/src/utils/validateManifestV2.compiled.js +57 -2
- 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
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
{{ columnLabel(column) }}
|
|
72
72
|
</NcActionCheckbox>
|
|
73
73
|
</NcActions>
|
|
74
|
-
<NcActions
|
|
74
|
+
<NcActions v-if="canChange"
|
|
75
75
|
:menuName="newLabel"
|
|
76
76
|
:forceMenu="true"
|
|
77
77
|
:forceName="true"
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
<!-- A plain upload as its own button, when the host names it
|
|
117
117
|
(`uploadButton`). It takes the primary place and the New
|
|
118
118
|
menu steps back beside it. -->
|
|
119
|
-
<NcButton v-if="uploadButton"
|
|
119
|
+
<NcButton v-if="uploadButton && canChange"
|
|
120
120
|
variant="primary"
|
|
121
121
|
:disabled="folder === null"
|
|
122
122
|
data-testid="cn-files-browser-upload-button"
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
<CnEmptyContent
|
|
167
167
|
v-else-if="sorted.length === 0 && linkedItems.length === 0"
|
|
168
168
|
:name="emptyLabel"
|
|
169
|
-
:description="emptyHint"
|
|
169
|
+
:description="canChange ? emptyHint : ''"
|
|
170
170
|
class="cn-files-browser__state">
|
|
171
171
|
<template #icon>
|
|
172
172
|
<FolderOutline :size="44" />
|
|
@@ -284,14 +284,40 @@
|
|
|
284
284
|
<NcActionSeparator v-if="actionsFor(node).length > 0 || (!isFolder(node) && rowActions.length > 0)" />
|
|
285
285
|
<!-- The Files app's own rename is its list's inline input,
|
|
286
286
|
which is not here; this one is a dialog over a DAV move. -->
|
|
287
|
-
|
|
287
|
+
<!-- With a `source`, the Files app's actions (which act over
|
|
288
|
+
WebDAV) are not offered; download and delete go through
|
|
289
|
+
the source instead. -->
|
|
290
|
+
<NcActionLink
|
|
291
|
+
v-if="source && sourceDownloadUrl(node)"
|
|
292
|
+
:href="sourceDownloadUrl(node)"
|
|
293
|
+
:download="node.basename"
|
|
294
|
+
:closeAfterClick="true"
|
|
295
|
+
data-testid="cn-files-browser-action-download">
|
|
296
|
+
<template #icon>
|
|
297
|
+
<Download :size="20" />
|
|
298
|
+
</template>
|
|
299
|
+
{{ downloadLabel }}
|
|
300
|
+
</NcActionLink>
|
|
301
|
+
<NcActionButton v-if="source && canChange"
|
|
302
|
+
:closeAfterClick="true"
|
|
303
|
+
data-testid="cn-files-browser-action-delete"
|
|
304
|
+
@click="askRemove(node)">
|
|
305
|
+
<template #icon>
|
|
306
|
+
<TrashCanOutline :size="20" />
|
|
307
|
+
</template>
|
|
308
|
+
{{ deleteLabel }}
|
|
309
|
+
</NcActionButton>
|
|
310
|
+
<NcActionButton v-if="canChange"
|
|
311
|
+
:closeAfterClick="true"
|
|
312
|
+
data-testid="cn-files-browser-action-rename"
|
|
313
|
+
@click="askRename(node)">
|
|
288
314
|
<template #icon>
|
|
289
315
|
<Pencil :size="20" />
|
|
290
316
|
</template>
|
|
291
317
|
{{ renameLabel }}
|
|
292
318
|
</NcActionButton>
|
|
293
319
|
<NcActionLink
|
|
294
|
-
v-if="node.fileid"
|
|
320
|
+
v-if="node.fileid && !source"
|
|
295
321
|
:href="permalink(node)"
|
|
296
322
|
target="_blank"
|
|
297
323
|
:closeAfterClick="true">
|
|
@@ -376,7 +402,7 @@
|
|
|
376
402
|
</tbody>
|
|
377
403
|
</table>
|
|
378
404
|
|
|
379
|
-
<p v-if="dropHint && !loading && error === ''"
|
|
405
|
+
<p v-if="dropHint && canChange && !loading && error === ''"
|
|
380
406
|
class="cn-files-browser__drop-hint"
|
|
381
407
|
data-testid="cn-files-browser-drop-hint">
|
|
382
408
|
<Upload :size="16" />
|
|
@@ -410,6 +436,26 @@
|
|
|
410
436
|
</template>
|
|
411
437
|
</NcDialog>
|
|
412
438
|
|
|
439
|
+
<NcDialog
|
|
440
|
+
v-if="removing !== null"
|
|
441
|
+
:name="deleteLabel"
|
|
442
|
+
size="small"
|
|
443
|
+
data-testid="cn-files-browser-delete"
|
|
444
|
+
@closing="removing = null">
|
|
445
|
+
<p>{{ t('nextcloud-vue', 'Delete {name}? This cannot be undone here.', { name: removing.basename }) }}</p>
|
|
446
|
+
<p v-if="removeError" class="cn-files-browser__upload-error" role="alert">
|
|
447
|
+
{{ removeError }}
|
|
448
|
+
</p>
|
|
449
|
+
<template #actions>
|
|
450
|
+
<NcButton @click="removing = null">
|
|
451
|
+
{{ t('nextcloud-vue', 'Cancel') }}
|
|
452
|
+
</NcButton>
|
|
453
|
+
<NcButton variant="error" data-testid="cn-files-browser-delete-confirm" @click="remove">
|
|
454
|
+
{{ deleteLabel }}
|
|
455
|
+
</NcButton>
|
|
456
|
+
</template>
|
|
457
|
+
</NcDialog>
|
|
458
|
+
|
|
413
459
|
<NcDialog
|
|
414
460
|
v-if="newFolderOpen"
|
|
415
461
|
:name="newFolderLabel"
|
|
@@ -494,6 +540,7 @@ import FolderOutline from 'vue-material-design-icons/FolderOutline.vue'
|
|
|
494
540
|
import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
|
|
495
541
|
import Pencil from 'vue-material-design-icons/Pencil.vue'
|
|
496
542
|
import Plus from 'vue-material-design-icons/Plus.vue'
|
|
543
|
+
import TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'
|
|
497
544
|
import Upload from 'vue-material-design-icons/Upload.vue'
|
|
498
545
|
import CnCellRenderer from '../CnCellRenderer/CnCellRenderer.vue'
|
|
499
546
|
import CnEmptyContent from '../CnEmptyContent/CnEmptyContent.vue'
|
|
@@ -543,6 +590,7 @@ export default {
|
|
|
543
590
|
OpenInNew,
|
|
544
591
|
Pencil,
|
|
545
592
|
Plus,
|
|
593
|
+
TrashCanOutline,
|
|
546
594
|
Upload,
|
|
547
595
|
},
|
|
548
596
|
|
|
@@ -553,6 +601,30 @@ export default {
|
|
|
553
601
|
},
|
|
554
602
|
|
|
555
603
|
props: {
|
|
604
|
+
/**
|
|
605
|
+
* Where the folder is read from and written to. Null (the default)
|
|
606
|
+
* is WebDAV under the signed-in person's files root, with the Files
|
|
607
|
+
* app's own actions and New menu. An object from
|
|
608
|
+
* `createOpenRegisterSource()` reads one OpenRegister object's folder
|
|
609
|
+
* through OpenRegister's files API instead, under the object's access
|
|
610
|
+
* rule: `rootPath` is then `/`, the Files app's actions and New menu
|
|
611
|
+
* are not offered (they act over WebDAV), and upload, new folder,
|
|
612
|
+
* rename and delete appear only when the listing says the person may
|
|
613
|
+
* change the folder.
|
|
614
|
+
*
|
|
615
|
+
* @type {object|null}
|
|
616
|
+
*/
|
|
617
|
+
source: {
|
|
618
|
+
type: Object,
|
|
619
|
+
default: null,
|
|
620
|
+
},
|
|
621
|
+
|
|
622
|
+
/** Label of the delete action and its dialog, with a `source`. */
|
|
623
|
+
deleteLabel: {
|
|
624
|
+
type: String,
|
|
625
|
+
default: () => t('nextcloud-vue', 'Delete'),
|
|
626
|
+
},
|
|
627
|
+
|
|
556
628
|
/**
|
|
557
629
|
* The host's own actions on each file row, declared the way a
|
|
558
630
|
* manifest declares any action (`open-modal`, `handler`, ...), and
|
|
@@ -823,6 +895,9 @@ export default {
|
|
|
823
895
|
renaming: null,
|
|
824
896
|
renameName: '',
|
|
825
897
|
renameError: '',
|
|
898
|
+
/** The row being deleted (with a `source`), or null. */
|
|
899
|
+
removing: null,
|
|
900
|
+
removeError: '',
|
|
826
901
|
fileInputEl: null,
|
|
827
902
|
/**
|
|
828
903
|
* The view the Files app's actions are handed. They read `view.id`
|
|
@@ -842,6 +917,20 @@ export default {
|
|
|
842
917
|
},
|
|
843
918
|
|
|
844
919
|
computed: {
|
|
920
|
+
/**
|
|
921
|
+
* Whether this person may add to and change the open folder. Over
|
|
922
|
+
* WebDAV the server answers each write on its own, so this is true;
|
|
923
|
+
* with a `source` it is what the listing said.
|
|
924
|
+
*
|
|
925
|
+
* @return {boolean} True when upload, new folder, rename and delete are offered.
|
|
926
|
+
*/
|
|
927
|
+
canChange() {
|
|
928
|
+
if (!this.source) {
|
|
929
|
+
return true
|
|
930
|
+
}
|
|
931
|
+
return this.folder !== null && this.folder.canChange === true
|
|
932
|
+
},
|
|
933
|
+
|
|
845
934
|
/**
|
|
846
935
|
* The breadcrumb trail from the root to the open folder.
|
|
847
936
|
*
|
|
@@ -946,7 +1035,7 @@ export default {
|
|
|
946
1035
|
* @return {Array<object>} The entries, in the plugins' order.
|
|
947
1036
|
*/
|
|
948
1037
|
newMenuEntries() {
|
|
949
|
-
if (this.folder === null) {
|
|
1038
|
+
if (this.folder === null || this.source) {
|
|
950
1039
|
return []
|
|
951
1040
|
}
|
|
952
1041
|
try {
|
|
@@ -1004,6 +1093,10 @@ export default {
|
|
|
1004
1093
|
async refresh() {
|
|
1005
1094
|
this.loading = true
|
|
1006
1095
|
this.error = ''
|
|
1096
|
+
if (this.source) {
|
|
1097
|
+
await this.refreshFromSource()
|
|
1098
|
+
return
|
|
1099
|
+
}
|
|
1007
1100
|
try {
|
|
1008
1101
|
const client = getClient()
|
|
1009
1102
|
const { data } = await client.getDirectoryContents(this.davPath(this.currentPath), {
|
|
@@ -1031,6 +1124,70 @@ export default {
|
|
|
1031
1124
|
}
|
|
1032
1125
|
},
|
|
1033
1126
|
|
|
1127
|
+
/**
|
|
1128
|
+
* List the open folder through the `source`.
|
|
1129
|
+
*
|
|
1130
|
+
* @return {Promise<void>}
|
|
1131
|
+
*/
|
|
1132
|
+
async refreshFromSource() {
|
|
1133
|
+
try {
|
|
1134
|
+
const { folder, nodes } = await this.source.list(this.currentPath)
|
|
1135
|
+
this.folder = folder
|
|
1136
|
+
this.nodes = nodes
|
|
1137
|
+
await this.resolveRowData(this.nodes)
|
|
1138
|
+
} catch (err) {
|
|
1139
|
+
const atRoot = (this.currentPath.replace(/\/+$/, '') || '/') === (this.rootPath.replace(/\/+$/, '') || '/')
|
|
1140
|
+
this.error = err?.status === 404
|
|
1141
|
+
? (atRoot ? t('nextcloud-vue', 'You cannot see these files') : t('nextcloud-vue', 'This folder no longer exists'))
|
|
1142
|
+
: (err?.status === 403 ? t('nextcloud-vue', 'You cannot see this folder') : t('nextcloud-vue', 'The folder could not be read'))
|
|
1143
|
+
this.folder = null
|
|
1144
|
+
this.nodes = []
|
|
1145
|
+
} finally {
|
|
1146
|
+
this.loading = false
|
|
1147
|
+
}
|
|
1148
|
+
},
|
|
1149
|
+
|
|
1150
|
+
/**
|
|
1151
|
+
* Where a file downloads from, through the `source`.
|
|
1152
|
+
*
|
|
1153
|
+
* @param {object} node The row.
|
|
1154
|
+
* @return {string|null} The url.
|
|
1155
|
+
*/
|
|
1156
|
+
sourceDownloadUrl(node) {
|
|
1157
|
+
return this.source && typeof this.source.downloadUrl === 'function' ? this.source.downloadUrl(node) : null
|
|
1158
|
+
},
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* Open the delete dialog on a row.
|
|
1162
|
+
*
|
|
1163
|
+
* @param {object} node The row.
|
|
1164
|
+
* @return {void}
|
|
1165
|
+
*/
|
|
1166
|
+
askRemove(node) {
|
|
1167
|
+
this.removing = node
|
|
1168
|
+
this.removeError = ''
|
|
1169
|
+
},
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* Delete the row named in the dialog, through the `source`.
|
|
1173
|
+
*
|
|
1174
|
+
* @return {Promise<void>}
|
|
1175
|
+
*/
|
|
1176
|
+
async remove() {
|
|
1177
|
+
const node = this.removing
|
|
1178
|
+
if (node === null || !this.source) {
|
|
1179
|
+
return
|
|
1180
|
+
}
|
|
1181
|
+
try {
|
|
1182
|
+
await this.source.remove(node)
|
|
1183
|
+
this.removing = null
|
|
1184
|
+
await this.refresh()
|
|
1185
|
+
this.$emit('changed')
|
|
1186
|
+
} catch (err) {
|
|
1187
|
+
this.removeError = err?.message || t('nextcloud-vue', '{action} did not succeed', { action: this.deleteLabel })
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1190
|
+
|
|
1034
1191
|
/**
|
|
1035
1192
|
* Open a folder below the root.
|
|
1036
1193
|
*
|
|
@@ -1068,6 +1225,13 @@ export default {
|
|
|
1068
1225
|
this.navigate(node.path)
|
|
1069
1226
|
return
|
|
1070
1227
|
}
|
|
1228
|
+
if (this.source) {
|
|
1229
|
+
const url = this.sourceDownloadUrl(node)
|
|
1230
|
+
if (url) {
|
|
1231
|
+
window.open(url, '_blank', 'noopener,noreferrer')
|
|
1232
|
+
}
|
|
1233
|
+
return
|
|
1234
|
+
}
|
|
1071
1235
|
const view = this.actionsFor(node).find((action) => action.id === 'view')
|
|
1072
1236
|
if (view) {
|
|
1073
1237
|
await this.run(view, node)
|
|
@@ -1114,6 +1278,16 @@ export default {
|
|
|
1114
1278
|
* @return {void}
|
|
1115
1279
|
*/
|
|
1116
1280
|
openInNewTab(node) {
|
|
1281
|
+
if (this.source) {
|
|
1282
|
+
if (this.isFolder(node)) {
|
|
1283
|
+
return
|
|
1284
|
+
}
|
|
1285
|
+
const url = this.sourceDownloadUrl(node)
|
|
1286
|
+
if (url) {
|
|
1287
|
+
window.open(url, '_blank', 'noopener,noreferrer')
|
|
1288
|
+
}
|
|
1289
|
+
return
|
|
1290
|
+
}
|
|
1117
1291
|
if (node.fileid) {
|
|
1118
1292
|
window.open(this.permalink(node), '_blank', 'noopener,noreferrer')
|
|
1119
1293
|
}
|
|
@@ -1141,6 +1315,9 @@ export default {
|
|
|
1141
1315
|
* @return {Array<object>} The actions.
|
|
1142
1316
|
*/
|
|
1143
1317
|
actionsFor(node) {
|
|
1318
|
+
if (this.source) {
|
|
1319
|
+
return []
|
|
1320
|
+
}
|
|
1144
1321
|
let actions
|
|
1145
1322
|
try {
|
|
1146
1323
|
actions = getFileActions()
|
|
@@ -1270,7 +1447,11 @@ export default {
|
|
|
1270
1447
|
return
|
|
1271
1448
|
}
|
|
1272
1449
|
try {
|
|
1273
|
-
|
|
1450
|
+
if (this.source) {
|
|
1451
|
+
await this.source.createFolder(this.currentPath, name)
|
|
1452
|
+
} else {
|
|
1453
|
+
await getClient().createDirectory(this.davPath(joinPath(this.currentPath, name)))
|
|
1454
|
+
}
|
|
1274
1455
|
this.newFolderOpen = false
|
|
1275
1456
|
await this.refresh()
|
|
1276
1457
|
this.$emit('changed')
|
|
@@ -1311,7 +1492,11 @@ export default {
|
|
|
1311
1492
|
return
|
|
1312
1493
|
}
|
|
1313
1494
|
try {
|
|
1314
|
-
|
|
1495
|
+
if (this.source) {
|
|
1496
|
+
await this.source.rename(node, name)
|
|
1497
|
+
} else {
|
|
1498
|
+
await getClient().moveFile(this.davPath(node.path), this.davPath(joinPath(this.currentPath, name)))
|
|
1499
|
+
}
|
|
1315
1500
|
this.renaming = null
|
|
1316
1501
|
await this.refresh()
|
|
1317
1502
|
this.$emit('changed')
|
|
@@ -1355,6 +1540,9 @@ export default {
|
|
|
1355
1540
|
this.dragging = false
|
|
1356
1541
|
this.dragDepth = 0
|
|
1357
1542
|
this.dropStatus = ''
|
|
1543
|
+
if (!this.canChange) {
|
|
1544
|
+
return
|
|
1545
|
+
}
|
|
1358
1546
|
const files = event.dataTransfer?.files
|
|
1359
1547
|
if (files?.length) {
|
|
1360
1548
|
await this.uploadFiles(files)
|
|
@@ -1372,7 +1560,7 @@ export default {
|
|
|
1372
1560
|
* @return {void}
|
|
1373
1561
|
*/
|
|
1374
1562
|
onDragEnter(event) {
|
|
1375
|
-
if (!this.carriesFiles(event)) {
|
|
1563
|
+
if (!this.canChange || !this.carriesFiles(event)) {
|
|
1376
1564
|
return
|
|
1377
1565
|
}
|
|
1378
1566
|
this.dragDepth++
|
|
@@ -1389,7 +1577,7 @@ export default {
|
|
|
1389
1577
|
* @return {void}
|
|
1390
1578
|
*/
|
|
1391
1579
|
onDragOver(event) {
|
|
1392
|
-
if (this.carriesFiles(event)) {
|
|
1580
|
+
if (this.canChange && this.carriesFiles(event)) {
|
|
1393
1581
|
this.dragging = true
|
|
1394
1582
|
}
|
|
1395
1583
|
},
|
|
@@ -1442,6 +1630,10 @@ export default {
|
|
|
1442
1630
|
const batch = [...files].map((file) => ({ key: `u${++uploadSeq}`, name: file.name, progress: 0, error: '', file }))
|
|
1443
1631
|
this.uploads = [...this.uploads, ...batch]
|
|
1444
1632
|
for (const upload of batch) {
|
|
1633
|
+
if (this.source) {
|
|
1634
|
+
await this.uploadThroughSource(upload)
|
|
1635
|
+
continue
|
|
1636
|
+
}
|
|
1445
1637
|
try {
|
|
1446
1638
|
await axios.put(`${getRemoteURL()}${this.davPath(joinPath(this.currentPath, upload.name))}`, upload.file, {
|
|
1447
1639
|
headers: {
|
|
@@ -1470,6 +1662,25 @@ export default {
|
|
|
1470
1662
|
}, 1500)
|
|
1471
1663
|
},
|
|
1472
1664
|
|
|
1665
|
+
/**
|
|
1666
|
+
* Store one upload through the `source`, keeping its progress row.
|
|
1667
|
+
*
|
|
1668
|
+
* @param {object} upload The progress row, carrying the file.
|
|
1669
|
+
* @return {Promise<void>}
|
|
1670
|
+
*/
|
|
1671
|
+
async uploadThroughSource(upload) {
|
|
1672
|
+
try {
|
|
1673
|
+
await this.source.upload(this.currentPath, upload.file, (percent) => {
|
|
1674
|
+
upload.progress = percent
|
|
1675
|
+
})
|
|
1676
|
+
upload.progress = 100
|
|
1677
|
+
} catch (err) {
|
|
1678
|
+
upload.error = err?.status === 409
|
|
1679
|
+
? t('nextcloud-vue', 'A file with that name is already here')
|
|
1680
|
+
: (err?.message || t('nextcloud-vue', 'The upload failed'))
|
|
1681
|
+
}
|
|
1682
|
+
},
|
|
1683
|
+
|
|
1473
1684
|
/**
|
|
1474
1685
|
* Change the sort column, or flip the order on the same one.
|
|
1475
1686
|
*
|
|
@@ -1702,6 +1913,9 @@ export default {
|
|
|
1702
1913
|
if (!node.fileid || this.isFolder(node) || !/^image\//i.test(mime) || /svg/i.test(mime) || this.previewFailed[node.fileid]) {
|
|
1703
1914
|
return null
|
|
1704
1915
|
}
|
|
1916
|
+
if (this.source) {
|
|
1917
|
+
return typeof this.source.previewUrl === 'function' ? this.source.previewUrl(node) : null
|
|
1918
|
+
}
|
|
1705
1919
|
return generateUrl('/core/preview?fileId={fileId}&x=64&y=64&a=1', { fileId: node.fileid })
|
|
1706
1920
|
},
|
|
1707
1921
|
|
|
@@ -1880,6 +2094,13 @@ export default {
|
|
|
1880
2094
|
flex: 1 1 auto;
|
|
1881
2095
|
}
|
|
1882
2096
|
|
|
2097
|
+
/* The crumbs give way, the controls do not: a long crumb (a folder named
|
|
2098
|
+
after a uuid) shrank the upload button to "Bestanden..." while the crumbs
|
|
2099
|
+
kept their width. */
|
|
2100
|
+
.cn-files-browser__bar > :not(.cn-files-browser__crumbs) {
|
|
2101
|
+
flex-shrink: 0;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
1883
2104
|
.cn-files-browser__file-input {
|
|
1884
2105
|
display: none;
|
|
1885
2106
|
}
|
|
@@ -151,14 +151,55 @@ export function firstHrefIn(xml) {
|
|
|
151
151
|
* @param {string} options.remoteUrl The DAV remote url, `…/remote.php/dav`.
|
|
152
152
|
* @return {Promise<string|null>} The user-relative folder path, or null.
|
|
153
153
|
*/
|
|
154
|
-
export async function resolveObjectFolder(
|
|
154
|
+
export async function resolveObjectFolder(options) {
|
|
155
|
+
const info = await resolveObjectFolderInfo(options)
|
|
156
|
+
return info === null ? null : info.path
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The object's display name: `@self.name`, else `title`, else `name`. A
|
|
161
|
+
* value equal to the object's uuid is no name (the folder already reads so).
|
|
162
|
+
*
|
|
163
|
+
* @param {object|null} object The object as OpenRegister returns it.
|
|
164
|
+
* @param {string} objectId The object's uuid.
|
|
165
|
+
* @return {string|null} The name, or null.
|
|
166
|
+
* @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
|
|
167
|
+
*/
|
|
168
|
+
export function objectDisplayName(object, objectId) {
|
|
169
|
+
const candidates = [object?.['@self']?.name, object?.title, object?.name]
|
|
170
|
+
for (const candidate of candidates) {
|
|
171
|
+
if (typeof candidate === 'string' && candidate.trim() !== '' && candidate.trim() !== String(objectId)) {
|
|
172
|
+
return candidate.trim()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return null
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* `resolveObjectFolder`, plus the object's display name from the same read,
|
|
180
|
+
* so the browser can name the root crumb after the object instead of the
|
|
181
|
+
* uuid its folder is called on disk.
|
|
182
|
+
*
|
|
183
|
+
* @param {object} options The lookup, as for `resolveObjectFolder`.
|
|
184
|
+
* @param {string} options.apiBase OpenRegister's API base, `/apps/openregister/api`.
|
|
185
|
+
* @param {string} options.register The register slug.
|
|
186
|
+
* @param {string} options.schema The schema slug.
|
|
187
|
+
* @param {string} options.objectId The object's uuid.
|
|
188
|
+
* @param {string} options.uid The current user's id.
|
|
189
|
+
* @param {string} options.remoteUrl The DAV remote url, `…/remote.php/dav`.
|
|
190
|
+
* @return {Promise<{path: string, name: (string|null)}|null>} The folder and the object's name, or null.
|
|
191
|
+
* @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
|
|
192
|
+
*/
|
|
193
|
+
export async function resolveObjectFolderInfo({ apiBase, register, schema, objectId, uid, remoteUrl }) {
|
|
155
194
|
if (!register || !schema || !objectId || !uid) {
|
|
156
195
|
return null
|
|
157
196
|
}
|
|
158
197
|
let folderId
|
|
198
|
+
let name
|
|
159
199
|
try {
|
|
160
200
|
const { data } = await axios.get(generateUrl(`${apiBase}/objects/{register}/{schema}/{objectId}`, { register, schema, objectId }))
|
|
161
201
|
folderId = data?.['@self']?.folder ?? null
|
|
202
|
+
name = objectDisplayName(data, objectId)
|
|
162
203
|
} catch {
|
|
163
204
|
return null
|
|
164
205
|
}
|
|
@@ -174,7 +215,7 @@ export async function resolveObjectFolder({ apiBase, register, schema, objectId,
|
|
|
174
215
|
responseType: 'text',
|
|
175
216
|
})
|
|
176
217
|
const href = firstHrefIn(response.data)
|
|
177
|
-
return href === null ? null : userRelativePathFromHref(href, uid)
|
|
218
|
+
return href === null ? null : { path: userRelativePathFromHref(href, uid), name }
|
|
178
219
|
} catch {
|
|
179
220
|
return null
|
|
180
221
|
}
|
|
@@ -5,5 +5,6 @@
|
|
|
5
5
|
import CnFilesBrowser from './CnFilesBrowser.vue'
|
|
6
6
|
|
|
7
7
|
export { CnFilesBrowser }
|
|
8
|
+
export { createOpenRegisterSource, entryToNode, toApiPath } from './openRegisterSource.js'
|
|
8
9
|
export { ACTIONS_NEEDING_THE_FILES_PAGE, crumbsFor, joinPath, resolveObjectFolder, userRelativePathFromHref } from './filesBrowser.js'
|
|
9
10
|
export default CnFilesBrowser
|