Notes layout improvements and soft delete
This commit is contained in:
+46
-29
@@ -1,14 +1,14 @@
|
||||
import { type Component, createSignal, createMemo, For, Show } from "solid-js";
|
||||
import { store, renameTagDefinition } from "@/store";
|
||||
import { type Component, createSignal, createMemo, createEffect, For, Show } from "solid-js";
|
||||
import { store, renameTagDefinition, updateNote, removeNote, restoreNote } from "@/store";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import { type Note } from "@/store";
|
||||
import { Trash2, Lock, Unlock, Search, Link, X, ChevronRight } from "lucide-solid";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { TaskEditor } from "@/components/TaskEditor";
|
||||
import { NOTES_COLLECTION } from "@/lib/constants";
|
||||
import { TaskCard } from "@/components/TaskCard";
|
||||
import { NotesSidebar } from "@/components/NotesSidebar";
|
||||
import { toast } from "solid-sonner";
|
||||
|
||||
export const NotepadView: Component<{
|
||||
selectedNoteId: string | null;
|
||||
@@ -28,26 +28,14 @@ export const NotepadView: Component<{
|
||||
});
|
||||
|
||||
const handleUpdateNote = async (id: string, data: Partial<Note>) => {
|
||||
try {
|
||||
await pb.collection(NOTES_COLLECTION).update(id, data);
|
||||
} catch (e: any) {
|
||||
if (!e.isAbort) {
|
||||
console.error("Failed to update note", e);
|
||||
}
|
||||
}
|
||||
await updateNote(id, data);
|
||||
};
|
||||
|
||||
let contentUpdateTimeout: number | undefined;
|
||||
const handleUpdateContent = (id: string, html: string) => {
|
||||
clearTimeout(contentUpdateTimeout);
|
||||
contentUpdateTimeout = window.setTimeout(async () => {
|
||||
try {
|
||||
await pb.collection(NOTES_COLLECTION).update(id, { content: html });
|
||||
} catch (e: any) {
|
||||
if (!e.isAbort) {
|
||||
console.error("Failed to update note content", e);
|
||||
}
|
||||
}
|
||||
await updateNote(id, { content: html });
|
||||
}, 500);
|
||||
};
|
||||
|
||||
@@ -59,15 +47,22 @@ export const NotepadView: Component<{
|
||||
// Use '#' prefix for note tags
|
||||
await renameTagDefinition(`#${oldTitle}`, `#${newTitle}`);
|
||||
};
|
||||
|
||||
const handleDeleteNote = async (id: string) => {
|
||||
try {
|
||||
await pb.collection(NOTES_COLLECTION).delete(id);
|
||||
if (props.selectedNoteId === id) {
|
||||
props.setSelectedNoteId(null);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to delete note", e);
|
||||
removeNote(id);
|
||||
if (props.selectedNoteId === id) {
|
||||
props.setSelectedNoteId(null);
|
||||
}
|
||||
|
||||
toast.success("Note moved to trash", {
|
||||
action: {
|
||||
label: "Undo",
|
||||
onClick: () => {
|
||||
restoreNote(id);
|
||||
props.setSelectedNoteId(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleLinkTask = async (taskId: string) => {
|
||||
@@ -174,21 +169,42 @@ export const NotepadView: Component<{
|
||||
return (
|
||||
<>
|
||||
{/* Editor Area */}
|
||||
<div class="flex-1 flex flex-col min-w-0 md:h-full overflow-visible md:overflow-y-auto relative p-4 md:p-6 space-y-4 md:space-y-6 scroll-smooth">
|
||||
<div class="space-y-4 sticky top-0 z-30 bg-card/95 backdrop-blur-sm pt-4 pb-2 -mt-4 -mx-4 px-4 md:pt-6 md:-mt-6 md:-mx-6 md:px-6 md:pb-2">
|
||||
<div class="flex-1 flex flex-col min-w-0 md:h-full overflow-visible md:overflow-y-auto relative px-4 md:px-6 pb-4 md:pb-6 space-y-4 md:space-y-6 scroll-smooth pt-0">
|
||||
<div class="space-y-4 sticky top-0 z-30 bg-card/95 backdrop-blur-sm pt-4 pb-2 -mx-4 px-4 md:pt-6 md:-mx-6 md:px-6 md:pb-2">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<input
|
||||
class="text-3xl sm:text-4xl font-black tracking-tight bg-transparent border-none outline-none focus:ring-0 flex-1 placeholder:text-muted-foreground/30 px-0 min-w-0"
|
||||
<textarea
|
||||
class="text-3xl sm:text-4xl font-black tracking-tight bg-transparent border-none outline-none focus:ring-0 flex-1 placeholder:text-muted-foreground/30 px-0 min-w-0 resize-none overflow-hidden h-auto"
|
||||
value={note().title}
|
||||
placeholder="Note Title"
|
||||
readOnly={!canEdit}
|
||||
rows={1}
|
||||
onBlur={(e) => {
|
||||
const val = e.currentTarget.value.trim() || 'Untitled';
|
||||
if (val !== note().title) handleRenameNote(note().id, note().title, val);
|
||||
}}
|
||||
onInput={(e) => {
|
||||
e.currentTarget.style.height = 'auto';
|
||||
e.currentTarget.style.height = e.currentTarget.scrollHeight + 'px';
|
||||
}}
|
||||
ref={(el) => {
|
||||
createEffect(() => {
|
||||
note().title;
|
||||
el.style.height = 'auto';
|
||||
el.style.height = el.scrollHeight + 'px';
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div class="flex items-center gap-2 shrink-0 pt-1">
|
||||
<Show when={isOwner}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="hidden md:flex h-8 w-8 p-0 hover:bg-destructive/10 hover:text-destructive text-muted-foreground transition-all rounded-xl border border-border/40 shadow-sm"
|
||||
onClick={() => handleDeleteNote(note().id)}
|
||||
title="Move to Trash"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@@ -206,13 +222,14 @@ export const NotepadView: Component<{
|
||||
</div>
|
||||
|
||||
{/* Editor Instance */}
|
||||
<div class="flex-1 min-h-[300px] border border-border/50 rounded-xl p-4 bg-background shadow-sm mb-4">
|
||||
<div class="grow shrink-0 flex flex-col min-h-[300px] border border-border/50 rounded-xl p-4 bg-background shadow-sm mb-4">
|
||||
<TaskEditor
|
||||
content={note().content}
|
||||
onUpdate={(html) => handleUpdateContent(note().id, html)}
|
||||
editable={canEdit}
|
||||
/>
|
||||
</div>
|
||||
<div class="shrink-0 h-16 md:h-8" />
|
||||
</div>
|
||||
|
||||
{/* Linked Tasks Sidebar */}
|
||||
|
||||
Reference in New Issue
Block a user