tag prefixing migration and improvements
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { type Component, createSignal, For, Show } from "solid-js";
|
import { type Component, createSignal, For, Show } from "solid-js";
|
||||||
import { Search, X, Hash, ArrowUpCircle, Clock, Minus, Save, Trash2, Bookmark } from "lucide-solid";
|
import { Search, X, Tag, ArrowUpCircle, Clock, Minus, Save, Trash2, Bookmark } from "lucide-solid";
|
||||||
import { store, setFilter, clearFilter, saveFilterTemplate, applyFilterTemplate, removeFilterTemplate, loadAllHistory } from "@/store";
|
import { store, setFilter, clearFilter, saveFilterTemplate, applyFilterTemplate, removeFilterTemplate, loadAllHistory } from "@/store";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -235,7 +235,7 @@ export const FilterBar: Component = () => {
|
|||||||
setFilter({ tags: newTags });
|
setFilter({ tags: newTags });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={tag.excluded} fallback={<Hash size={12} class="opacity-70" />}>
|
<Show when={tag.excluded} fallback={<Tag size={12} class="opacity-70" />}>
|
||||||
<Minus size={12} class="opacity-100" />
|
<Minus size={12} class="opacity-100" />
|
||||||
</Show>
|
</Show>
|
||||||
<span class={cn(tag.excluded && "line-through opacity-70")}>{tag.name}</span>
|
<span class={cn(tag.excluded && "line-through opacity-70")}>{tag.name}</span>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { store, upsertTagDefinition } from "@/store";
|
|||||||
import { useTheme } from "./ThemeProvider";
|
import { useTheme } from "./ThemeProvider";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Hash, Plus } from "lucide-solid";
|
import { Tag, Plus } from "lucide-solid";
|
||||||
|
|
||||||
interface TagPickerProps {
|
interface TagPickerProps {
|
||||||
selectedTags: string[];
|
selectedTags: string[];
|
||||||
@@ -71,7 +71,7 @@ export const TagPicker: Component<TagPickerProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<Popover open={open()} onOpenChange={setOpen}>
|
<Popover open={open()} onOpenChange={setOpen}>
|
||||||
<PopoverTrigger as={Button} variant="ghost" size="sm" class="h-8 border-dashed flex gap-2 items-center text-muted-foreground hover:text-foreground">
|
<PopoverTrigger as={Button} variant="ghost" size="sm" class="h-8 border-dashed flex gap-2 items-center text-muted-foreground hover:text-foreground">
|
||||||
<Hash size={14} />
|
<Tag size={14} />
|
||||||
<span>Add Tag</span>
|
<span>Add Tag</span>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="p-3 w-[min(calc(100vw-2rem),280px)] flex flex-col gap-3" align="start">
|
<PopoverContent class="p-3 w-[min(calc(100vw-2rem),280px)] flex flex-col gap-3" align="start">
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ import { type Component, createSignal, createMemo, For, Show } from "solid-js";
|
|||||||
import { store, renameTagDefinition } from "@/store";
|
import { store, renameTagDefinition } from "@/store";
|
||||||
import { pb } from "@/lib/pocketbase";
|
import { pb } from "@/lib/pocketbase";
|
||||||
import { type Note } from "@/store";
|
import { type Note } from "@/store";
|
||||||
import { Trash2, Lock, Unlock, Hash, StickyNote, Search, X, Link } from "lucide-solid";
|
import { Trash2, Lock, Unlock, StickyNote, Search, Link, X } from "lucide-solid";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { TaskEditor } from "@/components/TaskEditor";
|
import { TaskEditor } from "@/components/TaskEditor";
|
||||||
import { TagPicker } from "@/components/TagPicker";
|
|
||||||
import { NOTES_COLLECTION } from "@/lib/constants";
|
import { NOTES_COLLECTION } from "@/lib/constants";
|
||||||
import { TaskCard } from "@/components/TaskCard";
|
import { TaskCard } from "@/components/TaskCard";
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { NotesSidebar } from "@/components/NotesSidebar";
|
import { NotesSidebar } from "@/components/NotesSidebar";
|
||||||
|
|
||||||
export const NotepadView: Component<{
|
export const NotepadView: Component<{
|
||||||
@@ -147,7 +145,6 @@ export const NotepadView: Component<{
|
|||||||
)}>
|
)}>
|
||||||
<Show when={activeNote()} fallback={
|
<Show when={activeNote()} fallback={
|
||||||
<div class="text-center space-y-4 opacity-40 select-none">
|
<div class="text-center space-y-4 opacity-40 select-none">
|
||||||
<StickyNote size={64} class="mx-auto" />
|
|
||||||
<p class="text-sm font-medium tracking-wide">Select a note or create a new one</p>
|
<p class="text-sm font-medium tracking-wide">Select a note or create a new one</p>
|
||||||
</div>
|
</div>
|
||||||
}>
|
}>
|
||||||
@@ -203,34 +200,6 @@ export const NotepadView: Component<{
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tags */}
|
|
||||||
<div class="flex flex-wrap gap-2 items-center">
|
|
||||||
<For each={note().tags}>
|
|
||||||
{(tag) => (
|
|
||||||
<Badge variant="secondary" class="h-6 px-2 text-xs bg-muted/30 border border-border/50 text-foreground shadow-sm">
|
|
||||||
{tag}
|
|
||||||
<Show when={canEdit}>
|
|
||||||
<button
|
|
||||||
class="ml-2 hover:text-destructive transition-colors shrink-0 outline-none"
|
|
||||||
onClick={() => {
|
|
||||||
handleUpdateNote(note().id, { tags: note().tags.filter(t => t !== tag) });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<X size={10} />
|
|
||||||
</button>
|
|
||||||
</Show>
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
<Show when={canEdit}>
|
|
||||||
<div class="opacity-70 hover:opacity-100 transition-opacity">
|
|
||||||
<TagPicker
|
|
||||||
selectedTags={note().tags || []}
|
|
||||||
onTagsChange={(newTags) => handleUpdateNote(note().id, { tags: newTags })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Editor Instance */}
|
{/* Editor Instance */}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { type Component, For, Show, createSignal, lazy, Suspense } from "solid-j
|
|||||||
import { ThemeToggle } from "../components/ThemeToggle";
|
import { ThemeToggle } from "../components/ThemeToggle";
|
||||||
import { store, restoreTask, deleteTaskPermanently, addTemplate, removeTemplate, updateTemplate, upsertTagDefinition, removeTagDefinition, renameTagDefinition, addShareRule, removeShareRule, updateShareRule, createBucket, updateBucket, deleteBucket, toggleBucketSubscription } from "@/store";
|
import { store, restoreTask, deleteTaskPermanently, addTemplate, removeTemplate, updateTemplate, upsertTagDefinition, removeTagDefinition, renameTagDefinition, addShareRule, removeShareRule, updateShareRule, createBucket, updateBucket, deleteBucket, toggleBucketSubscription } from "@/store";
|
||||||
import { useTheme } from "@/components/ThemeProvider";
|
import { useTheme } from "@/components/ThemeProvider";
|
||||||
import { Trash2, Undo2, ArrowLeftRight, Hash, Copy, Plus, ChevronDown, ChevronRight, Type, Share2, Users, Tag, Upload, Box, Edit2, Archive, HelpCircle } from "lucide-solid";
|
import { Trash2, Undo2, ArrowLeftRight, Tag, ChevronDown, ChevronRight, Share2, Users, HelpCircle, Copy, Plus, Type, Upload, Box, Edit2, Archive } from "lucide-solid";
|
||||||
import { TagPicker } from "@/components/TagPicker";
|
import { TagPicker } from "@/components/TagPicker";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -268,7 +268,7 @@ export const SettingsView: Component<{ setView?: (v: string) => void }> = (props
|
|||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<p class="text-xs font-bold truncate">To: {getUserName(rule.targetUserId)}</p>
|
<p class="text-xs font-bold truncate">To: {getUserName(rule.targetUserId)}</p>
|
||||||
<p class="text-[0.5625rem] text-muted-foreground uppercase font-black tracking-widest opacity-60 flex items-center gap-1">
|
<p class="text-[0.5625rem] text-muted-foreground uppercase font-black tracking-widest opacity-60 flex items-center gap-1">
|
||||||
{rule.type === 'all' ? 'All Tasks' : `Tag: ${rule.tagName}`}
|
{rule.type === 'all' ? 'All Tasks' : `Tag: ${rule.tagName} `}
|
||||||
<span class="mx-1">•</span>
|
<span class="mx-1">•</span>
|
||||||
<button
|
<button
|
||||||
class="hover:text-primary transition-colors hover:underline"
|
class="hover:text-primary transition-colors hover:underline"
|
||||||
@@ -320,7 +320,7 @@ export const SettingsView: Component<{ setView?: (v: string) => void }> = (props
|
|||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<p class="text-xs font-bold truncate">From: {getUserName(rule.ownerId)}</p>
|
<p class="text-xs font-bold truncate">From: {getUserName(rule.ownerId)}</p>
|
||||||
<p class="text-[0.5625rem] text-muted-foreground uppercase font-black tracking-widest opacity-60 flex items-center gap-1">
|
<p class="text-[0.5625rem] text-muted-foreground uppercase font-black tracking-widest opacity-60 flex items-center gap-1">
|
||||||
{rule.type === 'all' ? 'All Tasks' : `Tag: ${rule.tagName}`}
|
{rule.type === 'all' ? 'All Tasks' : `Tag: ${rule.tagName} `}
|
||||||
<span class="mx-1">•</span>
|
<span class="mx-1">•</span>
|
||||||
{rule.share_mode === 'SEND_TASK' ? 'HANDOFF' : 'COLLAB'}
|
{rule.share_mode === 'SEND_TASK' ? 'HANDOFF' : 'COLLAB'}
|
||||||
</p>
|
</p>
|
||||||
@@ -528,7 +528,7 @@ export const SettingsView: Component<{ setView?: (v: string) => void }> = (props
|
|||||||
size="icon"
|
size="icon"
|
||||||
class="h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10 opacity-0 group-hover/bucket:opacity-100 transition-opacity"
|
class="h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10 opacity-0 group-hover/bucket:opacity-100 transition-opacity"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (confirm(`Delete the bucket "${bucket.name}"? This cannot be undone and will affect all users.`)) {
|
if (confirm(`Delete the bucket "${bucket.name}" ? This cannot be undone and will affect all users.`)) {
|
||||||
deleteBucket(bucket.id);
|
deleteBucket(bucket.id);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -889,7 +889,7 @@ export const SettingsView: Component<{ setView?: (v: string) => void }> = (props
|
|||||||
>
|
>
|
||||||
<div class="space-y-0.5">
|
<div class="space-y-0.5">
|
||||||
<h3 class="text-base font-semibold flex items-center gap-2">
|
<h3 class="text-base font-semibold flex items-center gap-2">
|
||||||
<Hash size={16} />
|
<Tag size={16} />
|
||||||
Global Tags
|
Global Tags
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-muted-foreground">Manage your global tag definitions and values.</p>
|
<p class="text-sm text-muted-foreground">Manage your global tag definitions and values.</p>
|
||||||
@@ -902,7 +902,7 @@ export const SettingsView: Component<{ setView?: (v: string) => void }> = (props
|
|||||||
<Show when={isTagsOpen()}>
|
<Show when={isTagsOpen()}>
|
||||||
<div class="space-y-2 pt-2 animate-in fade-in slide-in-from-top-2 duration-300">
|
<div class="space-y-2 pt-2 animate-in fade-in slide-in-from-top-2 duration-300">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<For each={store.tagDefinitions.filter(t => !t.isUser && !store.buckets.some(b => b.name === t.name)).sort((a, b) => a.name.localeCompare(b.name))} fallback={
|
<For each={store.tagDefinitions.filter(t => !t.isUser && !t.name.startsWith('#') && !store.buckets.some(b => b.name === t.name || `@${b.name} ` === t.name)).sort((a, b) => a.name.localeCompare(b.name))} fallback={
|
||||||
<div class="text-center py-8 text-muted-foreground text-sm italic border border-dashed border-border rounded-xl">
|
<div class="text-center py-8 text-muted-foreground text-sm italic border border-dashed border-border rounded-xl">
|
||||||
No custom tags found.
|
No custom tags found.
|
||||||
</div>
|
</div>
|
||||||
@@ -988,10 +988,10 @@ export const SettingsView: Component<{ setView?: (v: string) => void }> = (props
|
|||||||
<div class="bg-muted/50 p-1 rounded-md group-open/details:rotate-90 transition-transform">
|
<div class="bg-muted/50 p-1 rounded-md group-open/details:rotate-90 transition-transform">
|
||||||
<ChevronRight size={12} />
|
<ChevronRight size={12} />
|
||||||
</div>
|
</div>
|
||||||
System Tags (Users & Buckets)
|
System Tags (Users, Buckets & Notes)
|
||||||
</summary>
|
</summary>
|
||||||
<div class="pt-2 pl-4 space-y-2 animate-in fade-in slide-in-from-top-1 duration-200">
|
<div class="pt-2 pl-4 space-y-2 animate-in fade-in slide-in-from-top-1 duration-200">
|
||||||
<For each={store.tagDefinitions.filter(t => t.isUser || store.buckets.some(b => b.name === t.name)).sort((a, b) => a.name.localeCompare(b.name))}>
|
<For each={store.tagDefinitions.filter(t => t.isUser || t.name.startsWith('#') || store.buckets.some(b => b.name === t.name || `@${b.name}` === t.name)).sort((a, b) => a.name.localeCompare(b.name))}>
|
||||||
{(tag) => (
|
{(tag) => (
|
||||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between p-2 rounded-lg bg-muted/10 border border-border/20 gap-3 sm:gap-4 group opacity-80">
|
<div class="flex flex-col sm:flex-row sm:items-center justify-between p-2 rounded-lg bg-muted/10 border border-border/20 gap-3 sm:gap-4 group opacity-80">
|
||||||
<div class="flex items-center gap-3 min-w-0 flex-1">
|
<div class="flex items-center gap-3 min-w-0 flex-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user