Add note duplication (#24)

This commit is contained in:
Yuri Karamian
2026-08-03 13:23:18 +02:00
parent 1a880a4f96
commit e6e1ed1ed6
6 changed files with 158 additions and 7 deletions
+2 -2
View File
@@ -970,7 +970,7 @@
<Sidebar bind:this={sidebar} onViewChanged={handleViewChanged} />
</div>
<div class="mobile-panel" class:active={$mobileView === 'notelist'}>
<NoteList bind:this={noteList} onNoteSelected={handleNoteSelected} onBeforeNoteSwitch={() => editor?.flushSave()} onNoteMoved={() => sidebar?.refresh()} onNoteCreated={() => { editor?.focusTitle(); }} onToggleTask={toggleTask} onSetTaskPriority={changeTaskPriority} onSetTaskDue={changeTaskDue} />
<NoteList bind:this={noteList} onNoteSelected={handleNoteSelected} onBeforeNoteSwitch={() => editor?.flushSave()} onBeforeNoteDuplicate={() => editor?.forceSave() ?? Promise.resolve(true)} onNoteMoved={() => sidebar?.refresh()} onNoteCreated={() => { editor?.focusTitle(); }} onToggleTask={toggleTask} onSetTaskPriority={changeTaskPriority} onSetTaskDue={changeTaskDue} />
</div>
<div class="mobile-panel" class:active={$mobileView === 'editor'}>
<Editor bind:this={editor} onMoveToTrash={trashOpenNote} />
@@ -1031,7 +1031,7 @@
{#if !$notelistCollapsed}
<div class="notelist-panel" style="width: {$notelistWidth}px">
<NoteList bind:this={noteList} onNoteSelected={handleNoteSelected} onBeforeNoteSwitch={() => editor?.flushSave()} onNoteMoved={() => sidebar?.refresh()} onNoteCreated={() => { editor?.focusTitle(); }} onToggleTask={toggleTask} onSetTaskPriority={changeTaskPriority} onSetTaskDue={changeTaskDue} />
<NoteList bind:this={noteList} onNoteSelected={handleNoteSelected} onBeforeNoteSwitch={() => editor?.flushSave()} onBeforeNoteDuplicate={() => editor?.forceSave() ?? Promise.resolve(true)} onNoteMoved={() => sidebar?.refresh()} onNoteCreated={() => { editor?.focusTitle(); }} onToggleTask={toggleTask} onSetTaskPriority={changeTaskPriority} onSetTaskDue={changeTaskDue} />
</div>
<ResizeHandle onResize={handleNotelistResize} />
+35 -4
View File
@@ -23,6 +23,7 @@
getNotes,
readNote,
createNote,
duplicateNote,
deleteNote,
renameNote,
saveNote,
@@ -47,10 +48,11 @@
import TagSuggestInput from './TagSuggestInput.svelte';
import { isMobile, isAndroid } from '$lib/platform';
let { onNoteSelected = (_path: string, _content: string, _task?: TaskItem) => {}, onNoteMoved = () => {}, onBeforeNoteSwitch = () => {}, onNoteCreated = () => {}, onToggleTask = async (_t: TaskItem) => {}, onSetTaskPriority = async (_t: TaskItem, _p: string | null) => {}, onSetTaskDue = async (_t: TaskItem, _d: string | null) => {} }: {
let { onNoteSelected = (_path: string, _content: string, _task?: TaskItem) => {}, onNoteMoved = () => {}, onBeforeNoteSwitch = () => {}, onBeforeNoteDuplicate = async () => true, onNoteCreated = () => {}, onToggleTask = async (_t: TaskItem) => {}, onSetTaskPriority = async (_t: TaskItem, _p: string | null) => {}, onSetTaskDue = async (_t: TaskItem, _d: string | null) => {} }: {
onNoteSelected?: (path: string, content: string, task?: TaskItem) => void;
onNoteMoved?: () => void;
onBeforeNoteSwitch?: () => void;
onBeforeNoteDuplicate?: () => Promise<boolean>;
onNoteCreated?: () => void;
onToggleTask?: (t: TaskItem) => Promise<void>;
onSetTaskPriority?: (t: TaskItem, p: string | null) => Promise<void>;
@@ -468,6 +470,27 @@
}
}
async function handleDuplicate(note: NoteEntry) {
contextMenu = null;
try {
if ($activeNotePath === note.path && $editorDirty) {
const saved = await onBeforeNoteDuplicate();
if (!saved) {
console.error('Failed to save note before duplicating');
return;
}
}
const entry = await duplicateNote(note.path);
if ($sortMode === 'custom') appendManualNoteOrder(entry.path);
noteCache.clear();
if ($viewMode !== 'quickaccess') $notes = [entry, ...$notes];
await selectNote(entry);
} catch (e) {
console.error('Failed to duplicate note:', e);
}
}
function handleListDoubleClick(e: MouseEvent) {
const target = e.target as HTMLElement | null;
if (target?.closest('button, input, .note-item, .context-menu, .sort-menu, .batch-move-picker, .cal')) return;
@@ -1385,7 +1408,7 @@
</div>
{#if contextMenu}
<div class="context-menu" style="left: {contextMenu.x}px; top: {contextMenu.y}px" onclick={(e) => e.stopPropagation()} role="menu">
<div class="context-menu" class:mobile={isMobile} style="left: {contextMenu.x}px; top: {contextMenu.y}px" onclick={(e) => e.stopPropagation()} role="menu">
{#if selectedPaths.size > 1 && selectedPaths.has(contextMenu.note.path)}
<!-- Batch context menu -->
{#if $viewMode === 'trash'}
@@ -1549,6 +1572,12 @@
</svg>
Rename
</button>
<button onclick={() => handleDuplicate(contextMenu!.note)}>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="8" y="8" width="13" height="13" rx="2"/><path d="M16 8V5a2 2 0 00-2-2H5a2 2 0 00-2 2v9a2 2 0 002 2h3"/>
</svg>
Duplicate Note
</button>
<button onclick={() => { movePickerNote = contextMenu!.note; }}>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/>
@@ -2291,13 +2320,15 @@
border-radius: 10px;
}
.note-list.mobile .context-menu {
.context-menu.mobile {
min-width: 220px;
border-radius: 12px;
padding: 6px;
max-height: calc(100dvh - 16px);
overflow-y: auto;
}
.note-list.mobile .context-menu button {
.context-menu.mobile button {
padding: 12px 16px;
font-size: 15px;
min-height: 44px;