Merge pull request #59005 from nextcloud/backport/58680/stable31
Some checks are pending
Integration sqlite / changes (push) Waiting to run
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, --tags ~@large files_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, capabilities_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, collaboration_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, comments_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, dav_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, federation_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, file_conversions) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, files_reminders) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, filesdrop_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, ldap_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, openldap_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, openldap_numerical_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, remoteapi_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, setup_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, sharees_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, sharing_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, theming_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, videoverification_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite-summary (push) Blocked by required conditions

[stable31] fix(files): properly handle dropping files
This commit is contained in:
Arthur Schiwon 2026-03-17 18:00:33 +01:00 committed by GitHub
commit c0c81958fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 80 additions and 52 deletions

View file

@ -6,17 +6,18 @@
import type { PropType } from 'vue'
import type { FileSource } from '../types.ts'
import { extname } from 'path'
import { FileType, Permission, Folder, File as NcFile, NodeStatus, Node, getFileActions } from '@nextcloud/files'
import { generateUrl } from '@nextcloud/router'
import { isPublicShare } from '@nextcloud/sharing/public'
import { showError } from '@nextcloud/dialogs'
import { openConflictPicker } from '@nextcloud/dialogs'
import { FileType, Folder, getFileActions, File as NcFile, Node, NodeStatus, Permission } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { extname } from '@nextcloud/paths'
import { isPublicShare } from '@nextcloud/sharing/public'
import { generateUrl } from '@nextcloud/router'
import { getConflicts, getUploader } from '@nextcloud/upload'
import { vOnClickOutside } from '@vueuse/components'
import Vue, { computed, defineComponent } from 'vue'
import { action as sidebarAction } from '../actions/sidebarAction.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'
@ -421,42 +422,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.currentView?.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
const nodes = selection.map(source => this.filesStore.getNode(source)) as Node[]
await onDropInternalFiles(nodes, folder, contents.contents, isCopy)
// 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, this.source, contents, isCopy)
// Reset selection after we dropped the files
// if the dropped files are within the selection

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/files-main.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long