deduplicated quick add for embedding

This commit is contained in:
2026-02-27 12:54:00 -06:00
parent 820395b901
commit d8bc5e7561
5 changed files with 487 additions and 786 deletions
-15
View File
@@ -1,15 +0,0 @@
import { type Component, createSignal } from "solid-js";
import { NotepadView } from "./NotepadView";
export const EmbedNotesView: Component = () => {
const [selectedNoteId, setSelectedNoteId] = createSignal<string | null>(null);
return (
<div class="h-full w-full">
<NotepadView
selectedNoteId={selectedNoteId()}
setSelectedNoteId={setSelectedNoteId}
/>
</div>
);
};
-287
View File
@@ -1,287 +0,0 @@
import { type Component, createSignal, onMount, For, Show } from "solid-js";
import { addTask, calculateDateFromUrgency, calculateUrgencyFromDate, store, type TaskTemplate } from "@/store";
import { Search, Clock, ArrowUpCircle, Copy, ChevronDown, Gauge } from "lucide-solid";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { TextField, TextFieldInput } from "@/components/ui/textfield";
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
import { TagPicker } from "@/components/TagPicker";
import { useTheme } from "@/components/ThemeProvider";
import { getThemeAdjustedColor } from "@/lib/colors";
import { lazy, Suspense } from "solid-js";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { toast } from "solid-sonner";
import { PRIORITY_OPTIONS, URGENCY_OPTIONS, SIZE_OPTIONS } from "@/lib/constants";
const TaskEditor = lazy(() => import("@/components/TaskEditor").then(m => ({ default: m.TaskEditor })));
export const EmbedQuickAddView: Component = () => {
const { resolvedTheme } = useTheme();
const [input, setInput] = createSignal("");
const [priority, setPriority] = createSignal<string>("");
const [urgency, setUrgency] = createSignal<string>("");
const [tags, setTags] = createSignal<string[]>([]);
const [description, setDescription] = createSignal("");
const [editorInstance, setEditorInstance] = createSignal<any>(null);
const [size, setSize] = createSignal<string>("3");
const [dateString, setDateString] = createSignal<string>("");
let inputRef: HTMLInputElement | undefined;
onMount(() => {
setTimeout(() => inputRef?.focus(), 100);
});
const onUrgencySimpleChange = (val: any) => {
const value = typeof val === 'object' ? val.value : val;
if (!value) return;
setUrgency(value);
const level = parseInt(value);
if (!isNaN(level)) {
const newDateIso = calculateDateFromUrgency(level);
const dateObj = new Date(newDateIso);
const localIso = new Date(dateObj.getTime() - (dateObj.getTimezoneOffset() * 60000)).toISOString().slice(0, 16);
setDateString(localIso);
}
};
const onDateInputChange = (e: Event) => {
const val = (e.currentTarget as HTMLInputElement).value;
setDateString(val);
if (val) {
const level = calculateUrgencyFromDate(new Date(val).toISOString());
setUrgency(level.toString());
}
};
const handleCreateTask = async () => {
let text = input();
if (!text || !priority() || !urgency()) return;
let finalPriority = parseInt(priority());
let finalUrgency = parseInt(urgency());
const finalTags = [...tags()];
let finalDueDate: Date | null = null;
if (dateString()) {
finalDueDate = new Date(dateString());
} else {
const dateStr = calculateDateFromUrgency(finalUrgency);
finalDueDate = new Date(dateStr);
}
await addTask(text.trim(), {
priority: finalPriority,
dueDate: finalDueDate || undefined,
tags: finalTags,
content: description(),
size: parseInt(size())
});
toast.success("Task created successfully");
// Reset
setInput("");
setPriority("5");
setUrgency("5");
setTags([]);
setDescription("");
setSize("3");
setDateString("");
const instance = editorInstance();
if (instance) instance.commands.setContent("");
};
const applyTemplate = (template: TaskTemplate) => {
setInput(template.title);
setPriority(template.priority.toString());
setUrgency(template.urgency.toString());
setTags([...(template.tags || [])]);
setDescription(template.content || "");
const level = template.urgency;
const newDateIso = calculateDateFromUrgency(level);
const dateObj = new Date(newDateIso);
const localIso = new Date(dateObj.getTime() - (dateObj.getTimezoneOffset() * 60000)).toISOString().slice(0, 16);
setDateString(localIso);
const instance = editorInstance();
if (instance) instance.commands.setContent(template.content || "");
toast.info(`Applied template: ${template.name}`);
};
return (
<div class="w-full max-w-xl mx-auto bg-card border border-border rounded-xl shadow-lg overflow-hidden flex flex-col">
<div class="flex-1 overflow-y-auto">
<div class="flex items-center px-4 py-3 border-b border-border sticky top-0 bg-card z-10">
<Search class="text-muted-foreground mr-3" size={20} />
<TextField class="flex-1">
<TextFieldInput
ref={inputRef}
value={input()}
onInput={(e: any) => setInput(e.currentTarget.value)}
onKeyDown={(e: any) => e.key === "Enter" && handleCreateTask()}
placeholder="Task title..."
class="border-none shadow-none focus-visible:ring-0 text-lg"
/>
</TextField>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 p-4 gap-4 border-b border-border bg-muted/5">
<div class="space-y-1.5">
<label class="text-[0.625rem] font-bold uppercase tracking-wider text-muted-foreground ml-1">Priority</label>
<Select<any>
value={priority()}
onChange={(val) => {
const v = typeof val === 'object' ? val.value : val;
if (v) setPriority(v);
}}
options={PRIORITY_OPTIONS}
optionValue="value"
optionTextValue="label"
placeholder="Priority"
itemComponent={(props) => <SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem>}
>
<SelectTrigger class="w-full bg-background border-none shadow-sm h-10 px-3">
<div class="flex items-center gap-2 truncate">
<ArrowUpCircle size={14} class="text-primary shrink-0" />
<span class="text-sm font-medium truncate">{PRIORITY_OPTIONS.find(o => o.value === priority())?.label || priority()}</span>
</div>
</SelectTrigger>
<SelectContent />
</Select>
</div>
<div class="space-y-1.5">
<label class="text-[0.625rem] font-bold uppercase tracking-wider text-muted-foreground ml-1">Size</label>
<Select<any>
value={size()}
onChange={(val) => {
const v = typeof val === 'object' ? val.value : val;
if (v) setSize(v);
}}
options={SIZE_OPTIONS}
optionValue="value"
optionTextValue="label"
placeholder="Size"
itemComponent={(props) => <SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem>}
>
<SelectTrigger class="w-full bg-background border-none shadow-sm h-10 px-3">
<div class="flex items-center gap-2 truncate">
<Gauge size={14} class="text-primary shrink-0" />
<span class="text-sm font-medium truncate">{SIZE_OPTIONS.find(o => o.value === size())?.label || size()}</span>
</div>
</SelectTrigger>
<SelectContent />
</Select>
</div>
<div class="space-y-1.5">
<label class="text-[0.625rem] font-bold uppercase tracking-wider text-muted-foreground ml-1">Urgency</label>
<Select<any>
value={urgency()}
onChange={(val) => onUrgencySimpleChange(val)}
options={URGENCY_OPTIONS}
optionValue="value"
optionTextValue="label"
placeholder="Urgency"
itemComponent={(props) => <SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem>}
>
<SelectTrigger class="w-full bg-background border-none shadow-sm h-10 px-3">
<div class="flex items-center gap-2 truncate">
<Clock size={14} class="text-primary shrink-0" />
<span class="text-sm font-medium truncate">{URGENCY_OPTIONS.find(o => o.value === urgency())?.label || urgency()}</span>
</div>
</SelectTrigger>
<SelectContent />
</Select>
</div>
</div>
<div class="flex flex-col sm:flex-row items-stretch sm:items-center px-4 py-3 gap-4 bg-muted/5 border-b border-border">
<div class="flex-1 flex flex-wrap gap-2 items-center">
<TagPicker selectedTags={tags()} onTagsChange={setTags} />
<For each={tags()}>
{(tag) => (
<Badge
variant="secondary"
class="h-7 px-2 text-xs gap-1 bg-background border border-border/50 cursor-pointer hover:bg-destructive/10 group/tag transition-all"
style={{
"background-color": (() => {
const def = store.tagDefinitions.find(d => d.name === tag);
const color = getThemeAdjustedColor(def?.color, def?.theme, resolvedTheme());
return color ? `${color}15` : undefined;
})(),
"color": (() => {
const def = store.tagDefinitions.find(d => d.name === tag);
return getThemeAdjustedColor(def?.color, def?.theme, resolvedTheme()) || "inherit";
})()
}}
onClick={() => setTags(tags().filter(t => t !== tag))}
>
{tag}
</Badge>
)}
</For>
</div>
<div class="w-full sm:w-auto sm:min-w-[200px] space-y-1.5">
<label class="text-[0.625rem] font-bold uppercase tracking-wider text-muted-foreground ml-1">Due Date</label>
<input
type="datetime-local"
value={dateString()}
onInput={onDateInputChange}
class="w-full h-10 px-3 bg-background border border-border/50 rounded-md text-sm shadow-sm focus:outline-none focus:ring-1 focus:ring-ring"
/>
</div>
</div>
<div class="px-4 py-3 bg-card min-h-[120px]">
<Suspense fallback={<div class="h-20 animate-pulse bg-muted/20 rounded-lg" />}>
<TaskEditor
content={description()}
onUpdate={setDescription}
onEditorReady={setEditorInstance}
class="min-h-[100px] prose-sm"
/>
</Suspense>
</div>
</div>
<div class="p-4 bg-muted/10 flex justify-between items-center border-t border-border shrink-0">
<Show when={(store.templates || []).length > 0}>
<Popover>
<PopoverTrigger as={Button} variant="ghost" size="sm" class="h-9 px-3 gap-2">
<Copy size={14} />
<span class="text-[0.625rem] font-bold uppercase tracking-wider">Templates</span>
<ChevronDown size={12} class="opacity-50" />
</PopoverTrigger>
<PopoverContent class="w-64 p-2 bg-card border-border shadow-xl">
<div class="space-y-1">
<For each={store.templates}>
{(template) => (
<button
class="w-full flex items-center gap-3 p-2 rounded-lg hover:bg-muted transition-colors text-left"
onClick={() => applyTemplate(template)}
>
<div class="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center text-primary shrink-0">
<Copy size={14} />
</div>
<div class="min-w-0">
<p class="text-sm font-bold truncate">{template.name}</p>
</div>
</button>
)}
</For>
</div>
</PopoverContent>
</Popover>
</Show>
<Button size="sm" onClick={handleCreateTask} disabled={!input()}>
Create Task
</Button>
</div>
</div>
);
};