From 8637ccfc4c401bd0f32fd071ef00638a8cdfffdd Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Sun, 26 Jul 2026 13:27:40 +0200 Subject: [PATCH] feat(drag&drop): no drop zone on search page --- frontend/src/routes/search/+page.svelte | 52 +++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/frontend/src/routes/search/+page.svelte b/frontend/src/routes/search/+page.svelte index 0d01f6ad..5cee5e4a 100644 --- a/frontend/src/routes/search/+page.svelte +++ b/frontend/src/routes/search/+page.svelte @@ -11,6 +11,7 @@ import Icon from '$lib/icons/Icon.svelte'; import { t } from '$lib/i18n/index.svelte'; import { files as filesStore } from '$lib/stores/files.svelte'; + import { ui } from '$lib/stores/ui.svelte'; import { formatBytes } from '$lib/utils/format'; import { formatDate, iconNameFromClass, fileIconKindClass } from '$lib/utils/display'; @@ -202,10 +203,61 @@ void dateFilter; void run(query); }); + + /** + * Page-wide OS drag/drop guard. + * + * `/search` has no upload path, but without capturing the drop the + * browser's default handler navigates to the dropped file — the tab + * REPLACES the app with the file itself, which is data-loss-esque + * (user loses their in-progress search + any unsaved UI state). + * The `ResourceList`-based views (`/photos`, `/shared`, `/trash`, …) + * already fire a "wrong drop zone" toast via their `.rl-root` + * wrapper when `enableSystemDrop` is false — this handler brings + * `/search` to the same contract, and covers the whole viewport + * (not just a list surface) so drops on the sticky header / + * result-card margins are caught too. Same toast copy + "Go to + * Files" action as `ResourceList` for a consistent recovery UX. + */ + function onWindowDragOver(e: DragEvent) { + if (!e.dataTransfer?.types?.includes('Files')) return; + if (e.defaultPrevented) return; + e.preventDefault(); + // `dropEffect = 'none'` would tell the browser to REJECT the + // drop before `drop` fires — the toast path below never runs. + // Accept at the pointer level and let `onWindowDrop` decide. + if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'; + } + function onWindowDrop(e: DragEvent) { + if (!e.dataTransfer?.types?.includes('Files')) return; + if (e.defaultPrevented) return; + e.preventDefault(); + ui.notify( + t( + 'resource_list.wrong_drop_zone_msg', + 'Uploads only work in Files — open the Files section and drop there.' + ), + 'warning', + 6000, + true, + { + action: { + label: t('resource_list.wrong_drop_zone_action', 'Go to Files'), + // Best-effort recovery — the drop's `DataTransfer` is + // discarded once this handler returns (browsers don't + // persist it across a navigation), so we land the + // user in `/files` where they can re-drag from the OS. + onClick: () => goto(resolve('/files')) + } + } + ); + } {t('search.title', 'Search')} · OxiCloud + +

{#if query}{t('search.results_for', { q: query }, 'Results for “{{q}}”')}{:else}{t(