mirror of
https://gitlab.com/ArkHost/HelixNotes.git
synced 2026-09-21 10:27:28 +02:00
Fix dragging selected notes to notebooks
This commit is contained in:
@@ -40,6 +40,7 @@
|
|||||||
} from '$lib/api';
|
} from '$lib/api';
|
||||||
import { formatRelativeTime, formatDate, dateBucketLabel } from '$lib/utils/time';
|
import { formatRelativeTime, formatDate, dateBucketLabel } from '$lib/utils/time';
|
||||||
import { openNoteWindow } from '$lib/utils/window';
|
import { openNoteWindow } from '$lib/utils/window';
|
||||||
|
import { encodeNoteDragPaths } from '$lib/utils/note-drag';
|
||||||
import { revealItemInDir } from '@tauri-apps/plugin-opener';
|
import { revealItemInDir } from '@tauri-apps/plugin-opener';
|
||||||
import type { NoteEntry, TrashNotebookEntry, SortMode, TaskItem } from '$lib/types';
|
import type { NoteEntry, TrashNotebookEntry, SortMode, TaskItem } from '$lib/types';
|
||||||
import TasksView from './TasksView.svelte';
|
import TasksView from './TasksView.svelte';
|
||||||
@@ -265,6 +266,19 @@
|
|||||||
if (listContainer) listContainer.scrollTop = 0;
|
if (listContainer) listContainer.scrollTop = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Notes can also be removed from this list by a drop handled in the sidebar.
|
||||||
|
$effect(() => {
|
||||||
|
if (selectedPaths.size === 0) return;
|
||||||
|
const availablePaths = new Set($notes.map((note) => note.path));
|
||||||
|
const remaining = new Set([...selectedPaths].filter((path) => availablePaths.has(path)));
|
||||||
|
if (remaining.size === selectedPaths.size) return;
|
||||||
|
if (remaining.size === 0) {
|
||||||
|
clearSelection();
|
||||||
|
} else {
|
||||||
|
selectedPaths = remaining;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Invalidate quickaccess cache when starred notes change (e.g. from Editor star toggle)
|
// Invalidate quickaccess cache when starred notes change (e.g. from Editor star toggle)
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
$quickAccessPaths;
|
$quickAccessPaths;
|
||||||
@@ -1259,11 +1273,10 @@
|
|||||||
e.dataTransfer!.effectAllowed = 'move';
|
e.dataTransfer!.effectAllowed = 'move';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (selectedPaths.size > 1 && selectedPaths.has(note.path)) {
|
const dragPaths = selectedPaths.size > 1 && selectedPaths.has(note.path)
|
||||||
e.dataTransfer!.setData('text/plain', [...selectedPaths].join('\n'));
|
? selectedPaths
|
||||||
} else {
|
: [note.path];
|
||||||
e.dataTransfer!.setData('text/plain', note.path);
|
e.dataTransfer!.setData('text/plain', encodeNoteDragPaths(dragPaths));
|
||||||
}
|
|
||||||
e.dataTransfer!.effectAllowed = 'move';
|
e.dataTransfer!.effectAllowed = 'move';
|
||||||
}}
|
}}
|
||||||
ondragover={(e) => {
|
ondragover={(e) => {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||||
import type { NotebookEntry } from '$lib/types';
|
import type { NotebookEntry } from '$lib/types';
|
||||||
import { isMobile } from '$lib/platform';
|
import { isMobile } from '$lib/platform';
|
||||||
|
import { decodeNoteDragPaths } from '$lib/utils/note-drag';
|
||||||
|
|
||||||
let { onViewChanged = () => {} }: {
|
let { onViewChanged = () => {} }: {
|
||||||
onViewChanged?: () => void;
|
onViewChanged?: () => void;
|
||||||
@@ -340,22 +341,32 @@
|
|||||||
async function handleNoteDrop(e: DragEvent, nb: NotebookEntry) {
|
async function handleNoteDrop(e: DragEvent, nb: NotebookEntry) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dropTargetPath = null;
|
dropTargetPath = null;
|
||||||
const notePath = e.dataTransfer?.getData('text/plain');
|
const payload = e.dataTransfer?.getData('text/plain') ?? '';
|
||||||
if (!notePath) return;
|
const notePaths = [...new Set(decodeNoteDragPaths(payload))]
|
||||||
// Don't move if already in this notebook
|
.filter((path) => norm(parentOf(path)) !== norm(nb.path));
|
||||||
const noteDir = notePath.substring(0, notePath.lastIndexOf('/'));
|
if (notePaths.length === 0) return;
|
||||||
if (noteDir === nb.path) return;
|
|
||||||
|
const movedPaths = new Map<string, string>();
|
||||||
|
for (const notePath of notePaths) {
|
||||||
try {
|
try {
|
||||||
const newPath = await moveNote(notePath, nb.path);
|
movedPaths.set(notePath, await moveNote(notePath, nb.path));
|
||||||
$notes = $notes.filter(n => n.path !== notePath);
|
} catch (e) {
|
||||||
if ($activeNotePath === notePath) {
|
console.error('Failed to move note:', notePath, e);
|
||||||
$activeNotePath = newPath;
|
}
|
||||||
$activeNote = await readNote(newPath);
|
}
|
||||||
|
if (movedPaths.size === 0) return;
|
||||||
|
|
||||||
|
$notes = $notes.filter((note) => !movedPaths.has(note.path));
|
||||||
|
const activeNewPath = $activeNotePath ? movedPaths.get($activeNotePath) : undefined;
|
||||||
|
if (activeNewPath) {
|
||||||
|
$activeNotePath = activeNewPath;
|
||||||
|
try {
|
||||||
|
$activeNote = await readNote(activeNewPath);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to reload moved note:', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await refresh();
|
await refresh();
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to move note:', e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleNotebookDrop(e: DragEvent, destPath: string) {
|
async function handleNotebookDrop(e: DragEvent, destPath: string) {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export function encodeNoteDragPaths(paths: Iterable<string>): string {
|
||||||
|
return [...paths].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeNoteDragPaths(payload: string): string[] {
|
||||||
|
return payload.split(/\r?\n/).filter((path) => path.length > 0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { readFile } from 'node:fs/promises';
|
||||||
|
import test from 'node:test';
|
||||||
|
import { transformWithEsbuild } from 'vite';
|
||||||
|
|
||||||
|
const source = await readFile(
|
||||||
|
new URL('../src/lib/utils/note-drag.ts', import.meta.url),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
const { code } = await transformWithEsbuild(source, 'note-drag.ts', {
|
||||||
|
loader: 'ts',
|
||||||
|
format: 'esm',
|
||||||
|
target: 'esnext'
|
||||||
|
});
|
||||||
|
const noteDrag = await import(`data:text/javascript;base64,${Buffer.from(code).toString('base64')}`);
|
||||||
|
|
||||||
|
test('round-trips every selected note path in a drag payload', () => {
|
||||||
|
const paths = ['/vault/Alpha.md', '/vault/Projects/Beta.md'];
|
||||||
|
assert.deepEqual(noteDrag.decodeNoteDragPaths(noteDrag.encodeNoteDragPaths(paths)), paths);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('keeps single-note and Windows path payloads compatible', () => {
|
||||||
|
assert.deepEqual(noteDrag.decodeNoteDragPaths('/vault/Alpha.md'), ['/vault/Alpha.md']);
|
||||||
|
assert.deepEqual(
|
||||||
|
noteDrag.decodeNoteDragPaths('C:\\Vault\\Alpha.md\r\nC:\\Vault\\Beta.md'),
|
||||||
|
['C:\\Vault\\Alpha.md', 'C:\\Vault\\Beta.md']
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user