wip
This commit is contained in:
parent
fcc221945b
commit
24c5ac6dc5
|
|
@ -25,7 +25,6 @@ export const runCronJobs = async (client: Client) => {
|
|||
console.log(`Found ${upcomingReminders.length} upcoming reminders to notify.`)
|
||||
const content = `These reminders are due soon, let them know!: ${JSON.stringify(upcomingReminders)}`
|
||||
|
||||
console.log(`🌭`, { content })
|
||||
const output = await respondToSystemMessage(content, channelId)
|
||||
ensure(output, "The response to reminders should not be undefined")
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
color: #3B82F6;
|
||||
}
|
||||
|
||||
.todo-completed .todo-tag, .todo-completed .todo-date {
|
||||
.todo-completed .todo-tag, .todo-completed .todo-date, .todo-completed .todo-time-estimate {
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
|
|
@ -16,6 +16,10 @@
|
|||
color: #10B981;
|
||||
}
|
||||
|
||||
.todo-time-estimate {
|
||||
color: #aBb2d0;
|
||||
}
|
||||
|
||||
.todo-overdue .todo-date {
|
||||
color: #EF4444;
|
||||
}
|
||||
|
|
@ -35,6 +39,10 @@
|
|||
font-weight: 700;
|
||||
}
|
||||
|
||||
.todo-checkbox {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.todo-filtered {
|
||||
background-color: greenyellow;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import { test, expect } from "bun:test"
|
|||
|
||||
test("parsing valid todos", () => {
|
||||
expectTodo("- [ ] some words and that is it", { done: false, text: "some words and that is it" })
|
||||
expectTodo("- [x] has #some #tags and @2/5/25", {
|
||||
expectTodo("- [x] has #some #tags and @2/5/25 5m", {
|
||||
done: true,
|
||||
text: "has #some #tags and @2/5/25",
|
||||
text: "has #some #tags and @2/5/25 5m",
|
||||
tags: ["some", "tags"],
|
||||
timeEstimate: 300,
|
||||
dueDate: DateTime.fromISO("2025-02-05"),
|
||||
})
|
||||
expectTodo("- [ ] works with iso dates @2055/01/2", {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ export class Todo {
|
|||
return this.nodes.map((node) => node.content).join("")
|
||||
}
|
||||
|
||||
get timeEstimate() {
|
||||
return this.nodes.find((node) => node.type === "time-estimate")?.seconds
|
||||
}
|
||||
|
||||
get tags() {
|
||||
return this.nodes.filter((node) => node.type === "tag").map((node) => node.content.slice(1))
|
||||
}
|
||||
|
|
@ -56,6 +60,7 @@ type TodoNode = { content: string; start: number } & (
|
|||
| { type: "text" }
|
||||
| { type: "date"; parsed: DateTime }
|
||||
| { type: "tag" }
|
||||
| { type: "time-estimate"; seconds: number }
|
||||
)
|
||||
|
||||
const parseError = (line: string, offset: number, message?: string): string => {
|
||||
|
|
@ -96,12 +101,15 @@ const parseTodo = (line: string): Todo | undefined => {
|
|||
// eat text
|
||||
const isTag = line.slice(offset).startsWith("#")
|
||||
const isDate = line.slice(offset).startsWith("@")
|
||||
const isTimeEstimate = line.slice(offset).match(/^[\d\.]+[mh]$/)
|
||||
|
||||
let node: TodoNode | undefined
|
||||
if (isTag) {
|
||||
node = parseTag(line, offset)
|
||||
} else if (isDate) {
|
||||
node = parseDate(line, offset)
|
||||
} else if (isTimeEstimate) {
|
||||
node = parseTimeEstimate(line, offset)
|
||||
} else {
|
||||
node = parseText(line, offset)
|
||||
}
|
||||
|
|
@ -115,6 +123,29 @@ const parseTodo = (line: string): Todo | undefined => {
|
|||
return new Todo(todoNodes, done, indent)
|
||||
}
|
||||
|
||||
const parseTimeEstimate = (line: string, start: number) => {
|
||||
const timeMatch = line.slice(start).match(/^([\d\.]+)([mh])($|\s+)/)
|
||||
const [_, numberString, unitString] = timeMatch ?? []
|
||||
if (!numberString || !unitString) {
|
||||
return parseText(line, start)
|
||||
}
|
||||
|
||||
const value = parseInt(numberString, 10)
|
||||
const unit = unitString.toLowerCase()
|
||||
|
||||
let seconds: number
|
||||
if (unit === "m") {
|
||||
seconds = value * 60
|
||||
} else if (unit === "h") {
|
||||
seconds = value * 3600
|
||||
} else {
|
||||
return parseText(line, start)
|
||||
}
|
||||
|
||||
const node: TodoNode = { type: "time-estimate", content: timeMatch![0], seconds, start }
|
||||
return node
|
||||
}
|
||||
|
||||
const parseTag = (line: string, start: number) => {
|
||||
const tagMatch = line.slice(start).match(/^#(\w+)/)
|
||||
const content = tagMatch?.[0]
|
||||
|
|
|
|||
46
packages/todo/src/todoClickHandler.ts
Normal file
46
packages/todo/src/todoClickHandler.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { EditorView } from "@codemirror/view"
|
||||
import { Todo } from "@/todo"
|
||||
|
||||
export const todoClickHandler = EditorView.domEventHandlers({
|
||||
click(event, view) {
|
||||
const pos = view.posAtCoords({ x: event.clientX, y: event.clientY })
|
||||
if (pos === null) return false
|
||||
|
||||
const line = view.state.doc.lineAt(pos)
|
||||
const todo = Todo.parse(line.text)
|
||||
|
||||
if (!todo) return false
|
||||
|
||||
// Find the checkbox position in the line
|
||||
const checkboxMatch = line.text.match(/^(\s*-\s*\[)\s*(\w?)\s*(\]\s+)/)
|
||||
if (!checkboxMatch) return false
|
||||
|
||||
const [fullMatch, beforeCheckbox, currentState, afterCheckbox] = checkboxMatch
|
||||
if (!beforeCheckbox || !afterCheckbox) return false
|
||||
|
||||
const checkboxStart = line.from + beforeCheckbox.length
|
||||
const checkboxEnd = checkboxStart + 1
|
||||
|
||||
// Check if the click was within the checkbox area (including some padding)
|
||||
const clickOffset = pos - line.from
|
||||
|
||||
// Allow clicking anywhere in the "- [ ]" or "- [x]" part
|
||||
if (clickOffset >= 0 && clickOffset <= fullMatch.length) {
|
||||
// Toggle the checkbox state
|
||||
const newState = todo.done ? " " : "x"
|
||||
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: checkboxStart,
|
||||
to: checkboxEnd,
|
||||
insert: newState,
|
||||
},
|
||||
})
|
||||
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
})
|
||||
|
|
@ -54,6 +54,13 @@ export const todoDecorations = (filterRef: RefObject<string>) => {
|
|||
}
|
||||
}
|
||||
|
||||
// Add checkbox decoration for cursor styling
|
||||
const checkboxMatch = text.match(/^(\s*-\s*\[)\s*(\w?)\s*(\]\s+)/)
|
||||
if (checkboxMatch) {
|
||||
const checkboxEnd = checkboxMatch[0].length
|
||||
builder.add(line.from, line.from + checkboxEnd, Decoration.mark({ class: "todo-checkbox" }))
|
||||
}
|
||||
|
||||
for (const { type, start, end } of decorationsFor(todo)) {
|
||||
builder.add(line.from + start, line.from + end, Decoration.mark({ class: `todo-${type}` }))
|
||||
}
|
||||
|
|
@ -77,14 +84,10 @@ const decorationsFor = (todo: Todo) => {
|
|||
const decorations: { type: string; start: number; end: number }[] = []
|
||||
|
||||
for (const node of todo.nodes) {
|
||||
const start = node.start
|
||||
const { type, start } = node
|
||||
const end = node.start + node.content.length
|
||||
|
||||
if (node.type === "date") {
|
||||
decorations.push({ type: "date", start, end })
|
||||
} else if (node.type === "tag") {
|
||||
decorations.push({ type: "tag", start, end })
|
||||
}
|
||||
decorations.push({ type, start, end })
|
||||
}
|
||||
|
||||
return decorations
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { foldGutter, foldKeymap } from "@codemirror/language"
|
|||
import { refreshFilterEffect, todoDecorations } from "@/todoDecorations"
|
||||
import { autoTodoOnNewline } from "@/autoTodoOnNewline"
|
||||
import { todoKeymap } from "@/todoKeymap"
|
||||
import { todoClickHandler } from "@/todoClickHandler"
|
||||
import { dateAutocompletion } from "@/dateAutocompletion"
|
||||
import { DateTime } from "luxon"
|
||||
|
||||
|
|
@ -50,6 +51,7 @@ export const TodoEditor = ({ defaultValue, onChange }: TodoEditorProps) => {
|
|||
changeListener,
|
||||
autoTodoOnNewline,
|
||||
todoKeymap(filterElRef),
|
||||
todoClickHandler,
|
||||
dateAutocompletion,
|
||||
keymap.of(historyKeymap),
|
||||
keymap.of(defaultKeymap),
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ export const todoListToString = (todoList: TodoList): string => {
|
|||
}
|
||||
|
||||
const todoJsonToString = (todoOrString: TodoOrString): string => {
|
||||
console.log(`🌭`, { todoOrString })
|
||||
if (typeof todoOrString === "string") {
|
||||
return `${todoOrString}\n`
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user