Compare commits
4 Commits
fc3f1066e1
...
2f4207a44a
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f4207a44a | |||
| 8c318bdfb5 | |||
| ca8de37310 | |||
| 61f0f2185a |
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user