fix(files): properly handle dropping files

- resolves https://github.com/nextcloud/server/issues/58454

use upload library for dropped files instead of custom logic

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2026-03-03 03:15:23 +01:00 committed by nextcloud-command
parent f6c79c0d33
commit 0e0af702ac

View file

@ -7,17 +7,18 @@ import type { IFileAction } from '@nextcloud/files'
import type { PropType } from 'vue'
import type { FileSource } from '../types.ts'
import { showError } from '@nextcloud/dialogs'
import { openConflictPicker } from '@nextcloud/dialogs'
import { FileType, Folder, File as NcFile, Node, NodeStatus, Permission } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
import { isPublicShare } from '@nextcloud/sharing/public'
import { getConflicts, getUploader } from '@nextcloud/upload'
import { vOnClickOutside } from '@vueuse/components'
import { extname } from 'path'
import Vue, { computed, defineComponent } from 'vue'
import { action as sidebarAction } from '../actions/sidebarAction.ts'
import logger from '../logger.ts'
import { dataTransferToFileTree, onDropExternalFiles, onDropInternalFiles } from '../services/DropService.ts'
import { onDropInternalFiles } from '../services/DropService.ts'
import { getDragAndDropPreview } from '../utils/dragUtils.ts'
import { hashCode } from '../utils/hashUtils.ts'
import { isDownloadable } from '../utils/permissions.ts'
@ -476,42 +477,69 @@ export default defineComponent({
event.preventDefault()
event.stopPropagation()
// Caching the selection
const selection = this.draggingFiles
const items = [...event.dataTransfer?.items || []] as DataTransferItem[]
// We need to process the dataTransfer ASAP before the
// browser clears it. This is why we cache the items too.
const fileTree = await dataTransferToFileTree(items)
// We might not have the target directory fetched yet
const contents = await this.activeView?.getContents(this.source.path)
const folder = contents?.folder
if (!folder) {
showError(this.t('files', 'Target folder does not exist any more'))
return
}
// If another button is pressed, cancel it. This
// allows cancelling the drag with the right click.
if (!this.canDrop || event.button) {
return
}
const isCopy = event.ctrlKey
this.dragover = false
// Caching the selection
const selection = this.draggingFiles
const items = Array.from(event.dataTransfer?.items || [])
logger.debug('Dropped', { event, folder, selection, fileTree })
if (selection.length === 0 && items.some((item) => item.kind === 'file')) {
const uploader = getUploader()
await uploader.batchUpload(
this.source.path,
items.filter((item) => item.kind === 'file')
.map((item) => 'webkitGetAsEntry' in item ? item.webkitGetAsEntry() : item.getAsFile())
.filter(Boolean) as (FileSystemEntry | File)[],
async (nodes, path) => {
try {
const { contents, folder } = await this.activeView!.getContents(path)
const conflicts = getConflicts(nodes, contents)
if (conflicts.length === 0) {
return nodes
}
// Check whether we're uploading files
if (selection.length === 0 && fileTree.contents.length > 0) {
await onDropExternalFiles(fileTree, folder, contents.contents)
const result = await openConflictPicker(
folder.displayname,
conflicts,
(contents as Node[]).filter((node) => conflicts.some((conflict) => conflict.name === node.basename)),
{
recursive: true,
},
)
if (result === null) {
return false
}
return [
...nodes.filter((node) => !conflicts.some((conflict) => conflict.name === node.name)),
...result.selected,
...result.renamed,
]
} catch {
return nodes
}
},
)
this.dragover = false
return
}
// Else we're moving/copying files
// We might not have the target directory fetched yet
const cachedContents = this.filesStore.getNodesByPath(this.activeView.id, this.source.path)
const contents = cachedContents.length === 0
? (await this.activeView!.getContents(this.source.path)).contents
: cachedContents
const isCopy = event.ctrlKey
this.dragover = false
logger.debug('Dropped', { event, folder: this.source, selection, fileTree })
const nodes = selection.map((source) => this.filesStore.getNode(source)) as Node[]
await onDropInternalFiles(nodes, folder, contents.contents, isCopy)
await onDropInternalFiles(nodes, this.source, contents, isCopy)
// Reset selection after we dropped the files
// if the dropped files are within the selection