Compare commits

...

4 Commits

6 changed files with 97 additions and 28 deletions
+12 -8
View File
@@ -23,8 +23,8 @@ export const QuickEntry: Component = () => {
const [isOpen, setIsOpen] = createSignal(false);
const [input, setInput] = createSignal("");
const [priority, setPriority] = createSignal<string>("5");
const [urgency, setUrgency] = createSignal<string>("5");
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);
@@ -211,8 +211,8 @@ export const QuickEntry: Component = () => {
});
setInput("");
setPriority("5");
setUrgency("5");
setPriority("");
setUrgency("");
setTags([]);
setDescription("");
setSize("5");
@@ -281,7 +281,9 @@ export const QuickEntry: Component = () => {
{/* Row 1: Priority, Size, Urgency */}
<div class="flex flex-col md:flex-row items-stretch md:items-center p-4 gap-4 border-b border-border bg-muted/10">
<div class="flex-1 space-y-1.5">
<label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground ml-1">Priority</label>
<label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground ml-1">
Priority <span class="text-destructive">*</span>
</label>
<Select<any>
value={priority()}
onChange={(val) => {
@@ -302,7 +304,7 @@ export const QuickEntry: Component = () => {
<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()}
{PRIORITY_OPTIONS.find(o => o.value === priority())?.label || (priority() === "" ? "Pick Priority" : priority())}
</span>
</div>
</SelectTrigger>
@@ -341,7 +343,9 @@ export const QuickEntry: Component = () => {
</div>
<div class="flex-1 space-y-1.5">
<label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground ml-1">Urgency (Time)</label>
<label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground ml-1">
Urgency <span class="text-destructive">*</span>
</label>
<Select<any>
value={urgency()}
onChange={(val) => onUrgencySimpleChange(val)}
@@ -359,7 +363,7 @@ export const QuickEntry: Component = () => {
<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()}
{URGENCY_OPTIONS.find(o => o.value === urgency())?.label || (urgency() === "" ? "Pick Urgency" : urgency())}
</span>
</div>
</SelectTrigger>
+69 -6
View File
@@ -1,10 +1,12 @@
import { type Component, createEffect, untrack } from "solid-js";
import { type Component, createEffect, untrack, Show, createSignal } from "solid-js";
import { createTiptapEditor } from "solid-tiptap";
import StarterKit from "@tiptap/starter-kit";
import Placeholder from "@tiptap/extension-placeholder";
import TaskList from "@tiptap/extension-task-list";
import TaskItem from "@tiptap/extension-task-item";
import { cn } from "@/lib/utils";
import { Indent, Outdent } from "lucide-solid";
import { Button } from "./ui/button";
import { SlashCommands } from "@/lib/slash-command";
import { suggestion } from "@/lib/slash-renderer";
@@ -19,6 +21,7 @@ interface TaskEditorProps {
export const TaskEditor: Component<TaskEditorProps> = (props) => {
let editorRef: HTMLDivElement | undefined;
const [isFocused, setIsFocused] = createSignal(false);
const editor = createTiptapEditor(() => ({
element: editorRef!,
@@ -59,6 +62,11 @@ export const TaskEditor: Component<TaskEditorProps> = (props) => {
onUpdate: ({ editor }) => {
props.onUpdate(editor.getHTML());
},
onFocus: () => setIsFocused(true),
onBlur: () => {
// Delay blur to allow button clicks
setTimeout(() => setIsFocused(false), 200);
},
editorProps: {
attributes: {
class: cn(
@@ -95,11 +103,66 @@ export const TaskEditor: Component<TaskEditorProps> = (props) => {
}
});
const handleIndent = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const eInstance = editor();
if (!eInstance) return;
if (eInstance.can().sinkListItem('taskItem')) {
eInstance.chain().focus().sinkListItem('taskItem').run();
} else if (eInstance.can().sinkListItem('listItem')) {
eInstance.chain().focus().sinkListItem('listItem').run();
}
};
const handleOutdent = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const eInstance = editor();
if (!eInstance) return;
if (eInstance.can().liftListItem('taskItem')) {
eInstance.chain().focus().liftListItem('taskItem').run();
} else if (eInstance.can().liftListItem('listItem')) {
eInstance.chain().focus().liftListItem('listItem').run();
}
};
return (
<div
ref={editorRef}
class="w-full cursor-text"
onClick={() => editor()?.chain().focus().run()}
/>
<div class="relative w-full">
<div
ref={editorRef}
class="w-full cursor-text"
onClick={() => editor()?.chain().focus().run()}
/>
{/* Mobile Indentation Toolbar */}
<Show when={isFocused() && props.editable !== false}>
<div class="md:hidden fixed bottom-[env(safe-area-inset-bottom,0px)] left-0 right-0 z-[100] bg-background/95 backdrop-blur-md border-t border-border px-4 py-2 flex items-center justify-center gap-6 animate-in slide-in-from-bottom duration-200">
<Button
variant="ghost"
size="sm"
class="h-10 w-12 flex flex-col items-center gap-0.5 text-muted-foreground active:scale-95 active:bg-muted"
onMouseDown={handleOutdent}
>
<Outdent size={18} />
<span class="text-[9px] font-bold uppercase tracking-tighter">Outdent</span>
</Button>
<div class="w-px h-6 bg-border/50" />
<Button
variant="ghost"
size="sm"
class="h-10 w-12 flex flex-col items-center gap-0.5 text-muted-foreground active:scale-95 active:bg-muted"
onMouseDown={handleIndent}
>
<Indent size={18} />
<span class="text-[9px] font-bold uppercase tracking-tighter">Indent</span>
</Button>
</div>
</Show>
</div>
);
};
+1
View File
@@ -34,6 +34,7 @@ export interface ButtonProps extends VariantProps<typeof buttonVariants> {
onClick?: (e: MouseEvent) => void
onMouseEnter?: (e: MouseEvent) => void
onMouseLeave?: (e: MouseEvent) => void
onMouseDown?: (e: MouseEvent) => void
children?: JSX.Element
size?: "default" | "sm" | "lg" | "icon"
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"
+4 -4
View File
@@ -18,10 +18,10 @@ export const URGENCY_OPTIONS = [
{ value: "7", label: "7 - Half-Week" },
{ value: "6", label: "6 - This Week/5 Days" },
{ value: "5", label: "5 - Next Week/10 Days" },
{ value: "4", label: "4 - 3 Weeks" },
{ value: "3", label: "3 - 6 Weeks" },
{ value: "2", label: "2 - 3 Months" },
{ value: "1", label: "1 - 6 Months" }
{ value: "4", label: "4 - Three Weeks" },
{ value: "3", label: "3 - Six Weeks" },
{ value: "2", label: "2 - Three Months" },
{ value: "1", label: "1 - Six Months" }
];
export const URGENCY_HOURS: Record<number, number> = {
10: 12,
+5 -5
View File
@@ -38,7 +38,7 @@ export interface Task {
export const checkRecurringTasks = () => {
const tasks = store.tasks;
const nowObj = new Date();
const todayStr = nowObj.toISOString().split('T')[0];
const todayStr = nowObj.toLocaleDateString('en-CA'); // YYYY-MM-DD in local time
tasks.forEach(task => {
if (!task.completed || !task.recurrence) return;
@@ -51,14 +51,14 @@ export const checkRecurringTasks = () => {
// Check if we already reset it today/this cycle
if (lastUncompleted) {
const lastDate = new Date(lastUncompleted).toISOString().split('T')[0];
const lastDate = new Date(lastUncompleted).toLocaleDateString('en-CA');
if (lastDate === todayStr) return; // Already reset today
}
// Also check 'updated' date. If it was marked completed TODAY, don't reset immediately.
// We only reset if the completion happened BEFORE the current cycle trigger.
// E.g. Completed yesterday, today is a new day -> Reset.
const updatedDate = new Date(task.updated).toISOString().split('T')[0];
const updatedDate = new Date(task.updated).toLocaleDateString('en-CA');
if (updatedDate === todayStr) {
// If manual loop: user completes it, we shouldn't immediately uncomplete it.
// But what if it's supposed to be uncompleted today?
@@ -210,8 +210,8 @@ export const matchesFilter = (task: Task) => {
// Edited Today
if (f.editedToday) {
const today = new Date().toISOString().split('T')[0];
const updated = new Date(task.updated).toISOString().split('T')[0];
const today = new Date().toLocaleDateString('en-CA');
const updated = new Date(task.updated).toLocaleDateString('en-CA');
if (today !== updated) return false;
}
+6 -5
View File
@@ -1,5 +1,5 @@
import { type Component, For, createMemo } from "solid-js";
import { store, calculateUrgencyScore, setActiveTaskId, setStore, getCombinedScore, matchesFilter } from "@/store";
import { store, calculateUrgencyScore, setActiveTaskId, setStore, matchesFilter } from "@/store";
import { cn } from "@/lib/utils";
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
@@ -85,11 +85,11 @@ export const MatrixView: Component = () => {
};
const urgencyScore = calculateUrgencyScore(task.dueDate);
const combinedScore = getCombinedScore(task);
// Map 1-10 to HSL Hue (0 = Red, 240 = Blue)
// Using a slightly limited range for better aesthetics (0 to 220)
const hue = ((10 - combinedScore) / 9) * 220;
// Map task size (0-10) to HSL Hue (240 = Blue/Cool, 0 = Red/Warm)
// This creates a "true gradient" from small to large tasks.
const taskSize = task.size ?? 5;
const hue = (1 - (taskSize / 10)) * 240;
const dotColor = `hsl(${hue}, 85%, 55%)`;
return (
@@ -123,6 +123,7 @@ export const MatrixView: Component = () => {
<p class="text-xs font-bold leading-tight">{task.title}</p>
<div class="flex items-center justify-between mt-2 pt-2 border-t border-border">
<span class="text-[9px] text-muted-foreground">U: {urgencyScore}</span>
<span class="text-[9px] text-muted-foreground">S: {task.size ?? 5}</span>
<span class="text-[9px] text-muted-foreground">P: {task.priority}</span>
</div>
</div>