Added new NOTES feature

This commit is contained in:
2026-02-26 19:06:43 -06:00
parent e5c8e42709
commit 3907de048b
9 changed files with 698 additions and 35 deletions
+101
View File
@@ -0,0 +1,101 @@
import { type Component, createSignal, createMemo, For, Show } from "solid-js";
import { store } from "@/store";
import { pb } from "@/lib/pocketbase";
import { Plus, Lock, Search } from "lucide-solid";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { NOTES_COLLECTION } from "@/lib/constants";
export const NotesSidebar: Component<{
selectedNoteId: string | null;
setSelectedNoteId: (id: string | null) => void;
}> = (props) => {
const [searchQuery, setSearchQuery] = createSignal("");
const currentUserId = pb.authStore.model?.id;
const filteredNotes = createMemo(() => {
let n = store.notes;
if (searchQuery()) {
const q = searchQuery().toLowerCase();
n = n.filter(note => note.title.toLowerCase().includes(q) || note.tags.some(t => t.toLowerCase().includes(q)));
}
return n;
});
const handleCreateNote = async () => {
if (!currentUserId) return;
try {
const result = await pb.collection(NOTES_COLLECTION).create({
title: "New Note",
content: "",
tags: [],
isPrivate: false,
user: currentUserId,
});
props.setSelectedNoteId(result.id);
} catch (e) {
console.error("Failed to create note", e);
}
};
return (
<div class="flex flex-col h-full bg-card">
{/* Header */}
<div class="p-4 border-b border-border bg-card/50 backdrop-blur flex justify-between items-center gap-2">
<div class="relative flex-1">
<Search size={14} class="absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground/50" />
<input
type="text"
placeholder="Search notes..."
value={searchQuery()}
onInput={(e) => setSearchQuery(e.currentTarget.value)}
class="w-full bg-muted/50 border border-border/50 rounded-lg pl-8 pr-3 py-1.5 text-xs outline-none focus:ring-1 focus:ring-primary/30"
/>
</div>
<Button onClick={handleCreateNote} size="sm" variant="default" class="h-8 w-8 p-0 shrink-0 shadow-sm">
<Plus size={16} />
</Button>
</div>
{/* List */}
<div class="flex-1 overflow-y-auto p-2 space-y-1 pb-20 md:pb-2">
<For each={filteredNotes()} fallback={<div class="p-4 text-center text-xs text-muted-foreground">No notes found.</div>}>
{(note) => (
<button
onClick={() => props.setSelectedNoteId(note.id)}
class={cn(
"w-full text-left p-3 rounded-xl transition-all border",
props.selectedNoteId === note.id
? "bg-primary/5 border-primary/20 shadow-sm"
: "bg-card border-transparent hover:border-border hover:bg-muted/50"
)}
>
<div class="font-semibold text-sm truncate flex items-center justify-between">
<span>{note.title || "Untitled"}</span>
<Show when={note.isPrivate}>
<Lock size={12} class="text-orange-500 shrink-0 ml-2" />
</Show>
</div>
<Show when={note.tags && note.tags.length > 0}>
<div class="flex gap-1 mt-2 flex-wrap">
<For each={note.tags.slice(0, 3)}>
{(t) => (
<span class="text-[0.6rem] px-1.5 py-0.5 bg-muted rounded-md text-muted-foreground whitespace-nowrap border border-border/50">
#{t}
</span>
)}
</For>
<Show when={note.tags.length > 3}>
<span class="text-[0.6rem] px-1.5 py-0.5 text-muted-foreground whitespace-nowrap shrink-0">
+{note.tags.length - 3}
</span>
</Show>
</div>
</Show>
</button>
)}
</For>
</div>
</div>
);
};