Note tabs and favoriting ready for production
This commit is contained in:
+145
-111
@@ -2,8 +2,9 @@ import { type Component, createSignal, createMemo, createEffect, onCleanup, For,
|
||||
import { store, renameTagDefinition, updateNote, removeNote, restoreNote, setActiveNoteId, cleanupNoteAttachments, getFavoriteTag } from "@/store";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import { type Note } from "@/store";
|
||||
import { Trash2, Lock, Unlock, Search, Link, X, ChevronRight, ChevronDown, FileText, Plus, Star } from "lucide-solid";
|
||||
import { Trash2, Lock, Unlock, Search, Link, X, ChevronRight, ChevronDown, FileText, Plus, Star, MoreHorizontal } from "lucide-solid";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover";
|
||||
import { DragDropProvider, DragDropSensors, SortableProvider, createSortable, closestCenter } from "@thisbeyond/solid-dnd";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { NOTES_COLLECTION } from "@/lib/constants";
|
||||
@@ -382,7 +383,7 @@ export const NotepadView: Component<{
|
||||
</div>
|
||||
|
||||
{/* Editor Area Container */}
|
||||
<div class="flex-1 md:flex-initial md:shrink w-full md:w-auto flex flex-col md:flex-row min-w-0 md:h-full md:bg-card md:border md:border-border md:rounded-xl md:shadow-[0_4px_12px_rgba(0,0,0,0.02)] overflow-hidden z-20 relative shadow-[-4px_0_12px_rgba(0,0,0,0.02)] transition-all duration-300">
|
||||
<div class="flex-1 md:flex-initial md:shrink w-full md:w-auto flex flex-col md:flex-row min-w-0 md:h-full md:bg-card md:border md:border-border md:rounded-xl md:shadow-[0_4px_12px_rgba(0,0,0,0.02)] overflow-visible md:overflow-hidden z-20 relative shadow-[-4px_0_12px_rgba(0,0,0,0.02)] transition-all duration-300">
|
||||
|
||||
{/* Editor Area */}
|
||||
<div class="flex-1 md:flex-initial md:shrink flex flex-col min-w-0 md:w-[960px] lg:w-[1120px] 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 bg-background z-20">
|
||||
@@ -421,89 +422,7 @@ export const NotepadView: Component<{
|
||||
</Show>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0 pt-1">
|
||||
{/* Mobile Tabs Dropdown */}
|
||||
<div class="md:hidden flex items-center">
|
||||
<Show when={rootParentNote()}>
|
||||
<div class="relative group">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="h-8 px-2 text-xs gap-1 max-w-[120px]"
|
||||
>
|
||||
<FileText size={12} />
|
||||
<span class="truncate">Tabs ({childTabs().length + (rootParentNote() ? 1 : 0)})</span>
|
||||
<ChevronDown size={12} class="opacity-50" />
|
||||
</Button>
|
||||
|
||||
<div class="absolute right-0 top-full mt-1 w-64 bg-popover text-popover-foreground rounded-lg border border-border shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all z-50 overflow-hidden flex flex-col">
|
||||
<Show when={rootParentNote()}>
|
||||
{(root) => (
|
||||
<div
|
||||
class={cn(
|
||||
"w-full text-left px-3 py-2 text-sm hover:bg-muted transition-colors border-b border-border/50 truncate flex-1 cursor-pointer",
|
||||
props.selectedNoteId === root().id && "bg-muted font-medium text-primary"
|
||||
)}
|
||||
onClick={() => {
|
||||
if (props.selectedNoteId !== root().id) props.setSelectedNoteId(root().id);
|
||||
}}
|
||||
>
|
||||
<span class="truncate">General</span>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
<For each={childTabs()}>
|
||||
{(tab) => (
|
||||
<>
|
||||
<div
|
||||
class={cn(
|
||||
"w-full text-left px-3 py-2 text-sm hover:bg-muted transition-colors border-b border-border/50 truncate flex-1 cursor-pointer",
|
||||
props.selectedNoteId === tab.id && "bg-muted font-medium text-primary"
|
||||
)}
|
||||
onClick={() => {
|
||||
if (props.selectedNoteId !== tab.id) props.setSelectedNoteId(tab.id);
|
||||
}}
|
||||
>
|
||||
<Show when={props.selectedNoteId === tab.id} fallback={
|
||||
<span class="truncate">{tab.title || "Untitled Tab"}</span>
|
||||
}>
|
||||
<input
|
||||
class="text-sm bg-transparent border-none outline-none focus:ring-0 px-0 min-w-0 w-full truncate font-semibold"
|
||||
value={tab.title}
|
||||
placeholder="Untitled Tab"
|
||||
readOnly={!canEdit}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onBlur={(e) => {
|
||||
const val = e.currentTarget.value.trim() || 'Untitled Tab';
|
||||
if (val !== tab.title) handleRenameNote(tab.id, tab.title, val);
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
<button
|
||||
class="w-full text-left px-3 py-2 text-sm hover:bg-primary/10 text-primary font-medium transition-colors flex items-center gap-2"
|
||||
onClick={() => handleCreateTab()}
|
||||
>
|
||||
<Plus size={14} /> Add Tab
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={childTabs().length === 0}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-8 w-8 p-0 text-muted-foreground hover:bg-muted hover:text-foreground rounded-xl"
|
||||
onClick={() => handleCreateTab()}
|
||||
title="Add Tab"
|
||||
>
|
||||
<Plus size={16} />
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
{/* Mobile Tabs Dropdown Moved to Footer */}
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -537,7 +456,7 @@ export const NotepadView: Component<{
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class={cn("h-8 text-[0.625rem] font-bold uppercase tracking-widest", note().isPrivate ? "text-orange-500 hover:bg-orange-500/10" : "text-muted-foreground")}
|
||||
class={cn("hidden md:flex h-8 text-[0.625rem] font-bold uppercase tracking-widest", note().isPrivate ? "text-orange-500 hover:bg-orange-500/10" : "text-muted-foreground")}
|
||||
onClick={() => handleUpdateNote(note().id, { isPrivate: !note().isPrivate })}
|
||||
>
|
||||
<Show when={note().isPrivate} fallback={<><Unlock size={12} class="mr-1.5" /> Public</>}>
|
||||
@@ -693,32 +612,147 @@ export const NotepadView: Component<{
|
||||
</div>
|
||||
</div>
|
||||
{/* Sticky Footer Actions (Match TaskDetail style) */}
|
||||
<div class="px-6 py-4 border-t border-border/50 md:hidden flex items-center justify-between shrink-0 bg-background/95 backdrop-blur-sm z-40 w-full sticky bottom-0">
|
||||
{/* Delete button (persistent for owner) */}
|
||||
<Show when={isOwner}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-9 px-3 gap-2 hover:bg-destructive/10 hover:text-destructive text-muted-foreground transition-all rounded-xl border border-border/40"
|
||||
onClick={() => handleDeleteNote(note().id)}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
<span class="text-xs font-bold uppercase tracking-wider">Delete</span>
|
||||
</Button>
|
||||
</Show>
|
||||
<div class="px-4 py-3 border-t border-border/50 md:hidden flex items-center justify-between shrink-0 bg-background/95 backdrop-blur-sm z-40 w-full sticky bottom-0">
|
||||
<div class="flex items-center gap-2 w-16 shrink-0">
|
||||
{/* "..." popover menu containing Public/Private toggle and Delete */}
|
||||
<Show when={isOwner}>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
as={Button}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-9 w-9 p-0 hover:bg-muted text-muted-foreground transition-all rounded-xl border border-border/40 shrink-0"
|
||||
>
|
||||
<MoreHorizontal size={16} />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-48 p-2" align="start">
|
||||
<div class="flex flex-col gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class={cn("w-full justify-start text-[0.625rem] font-bold uppercase tracking-widest gap-2", note().isPrivate ? "text-orange-500 hover:bg-orange-500/10" : "text-muted-foreground hover:bg-muted")}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleUpdateNote(note().id, { isPrivate: !note().isPrivate });
|
||||
}}
|
||||
>
|
||||
<Show when={note().isPrivate} fallback={<><Unlock size={14} /> Public</>}>
|
||||
<><Lock size={14} /> Private</>
|
||||
</Show>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="w-full justify-start text-[0.625rem] font-bold uppercase tracking-widest gap-2 hover:bg-destructive/10 hover:text-destructive text-muted-foreground transition-all"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteNote(note().id);
|
||||
}}
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Show when={!props.hideNavigation}>
|
||||
{/* Close button (Mobile only) */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="md:hidden h-9 px-3 gap-2 hover:bg-muted text-muted-foreground transition-all rounded-xl border border-border/40"
|
||||
onClick={() => props.setSelectedNoteId(null)}
|
||||
>
|
||||
<X size={16} />
|
||||
<span class="text-xs font-bold uppercase tracking-wider">Close</span>
|
||||
</Button>
|
||||
</Show>
|
||||
{/* Tab dropdown moved to middle */}
|
||||
<div class="flex-1 flex justify-center px-2 min-w-0">
|
||||
<Show when={rootParentNote()}>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
as={Button}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-9 px-3 text-xs gap-1 max-w-[150px] bg-muted/30 hover:bg-muted/50 rounded-xl w-full"
|
||||
>
|
||||
<FileText size={14} class="shrink-0" />
|
||||
<span class="truncate">Tabs ({childTabs().length + (rootParentNote() ? 1 : 0)})</span>
|
||||
<ChevronDown size={14} class="opacity-50 shrink-0" />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-[200px] p-2 bg-popover text-popover-foreground rounded-xl shadow-lg border-border" align="center">
|
||||
<div class="flex flex-col gap-1 max-h-60 overflow-y-auto">
|
||||
<Show when={rootParentNote()}>
|
||||
{(root) => (
|
||||
<Button
|
||||
variant="ghost"
|
||||
class={cn(
|
||||
"w-full justify-start px-3 py-2 text-sm hover:bg-muted border-border/50 truncate flex items-center h-auto font-normal rounded-lg",
|
||||
props.selectedNoteId === root().id && "bg-muted font-medium text-primary border"
|
||||
)}
|
||||
onClick={() => props.setSelectedNoteId(root().id)}
|
||||
>
|
||||
<span class="truncate">General</span>
|
||||
</Button>
|
||||
)}
|
||||
</Show>
|
||||
<For each={childTabs()}>
|
||||
{(tab) => (
|
||||
<Button
|
||||
variant="ghost"
|
||||
class={cn(
|
||||
"w-full justify-start px-3 py-2 text-sm hover:bg-muted border-border/50 truncate flex items-center h-auto font-normal rounded-lg group",
|
||||
props.selectedNoteId === tab.id && "bg-muted font-medium text-primary border"
|
||||
)}
|
||||
onClick={() => props.setSelectedNoteId(tab.id)}
|
||||
>
|
||||
<Show when={props.selectedNoteId === tab.id} fallback={
|
||||
<span class="truncate">{tab.title || "Untitled Tab"}</span>
|
||||
}>
|
||||
<input
|
||||
class="text-sm bg-transparent border-none outline-none focus:ring-0 px-0 min-w-0 w-full truncate font-semibold cursor-text"
|
||||
value={tab.title}
|
||||
placeholder="Untitled Tab"
|
||||
readOnly={!canEdit}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onBlur={(e) => {
|
||||
const val = e.currentTarget.value.trim() || 'Untitled Tab';
|
||||
if (val !== tab.title) handleRenameNote(tab.id, tab.title, val);
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
</Button>
|
||||
)}
|
||||
</For>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="w-full justify-start px-3 py-2 text-sm hover:bg-primary/10 text-primary font-medium transition-colors gap-2 h-auto rounded-lg mt-1"
|
||||
onClick={handleCreateTab}
|
||||
>
|
||||
<Plus size={14} /> Add Tab
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</Show>
|
||||
<Show when={childTabs().length === 0 && !rootParentNote()}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-9 px-3 text-xs gap-1 bg-muted/30 hover:bg-muted/50 rounded-xl"
|
||||
onClick={() => handleCreateTab()}
|
||||
>
|
||||
<Plus size={14} /> Add Tab
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 w-16 justify-end shrink-0">
|
||||
<Show when={!props.hideNavigation}>
|
||||
{/* Close button (Mobile only) */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="md:hidden h-9 px-3 gap-2 hover:bg-muted text-muted-foreground transition-all rounded-xl border border-border/40 shrink-0"
|
||||
onClick={() => props.setSelectedNoteId(null)}
|
||||
>
|
||||
<X size={16} />
|
||||
<span class="text-xs font-bold uppercase tracking-wider">Close</span>
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user