@Pietzaahh This is a great script.
We have so many options that I don’t know which one to use these days
Anyhow, I checked back with my colleague “Gemini” and it gave me back this script with priorities working.
It also refactored a bit more the code.
Hope you can use it to continue implementing your ideas:
```space-script
const rrule = import("https://esm.sh/[email protected]")
const checkbox = /^\[([^\]]+)]\s+/
const hashTags = /(^|\s)#[^ !@#$%^&*(),.?":{}|<>]+/g
const taskId = /[a-zA-Z0-9-_]+/.source
// Priority emojis and their mappings
const priorityEmojis = {
"❗": 1, // highest
"⏫": 2, // high
"🔼": 3, // medium
"🔽": 4, // low
"⏬": 5, // lowest
}
const matchPriority = Object.keys(priorityEmojis).join("|")
// Task-related emojis for date attributes
const taskEmojis = {
"🛫": "start",
"➕": "created",
"⏳": "scheduled",
"📅": "deadline",
"✅": "completed",
"❌": "cancelled"
};
// Matchers including priority and additional task emojis
const matchers = {
priority: new RegExp(`\\s+(${matchPriority})`, "u"),
...Object.fromEntries(Object.entries(taskEmojis).map(
([emoji, attribute]) => [attribute, new RegExp(`\\s*${emoji}\\s*(\\d{4}-\\d{2}-\\d{2})`, "u")]
)),
recurrence: /\s+🔁\s*([a-zA-Z0-9, !]+)/iu,
depends: new RegExp(`\\s+⛔️\\s*(${taskId}( *, *${taskId} *)*)`, "iu"),
id: new RegExp(`\\s+🆔\s*(${taskId})`, "iu"),
tags: new RegExp(hashTags.source, "iu"),
}
const extractors = Object.entries(matchers).map(([prop, regex]) => [
prop,
new RegExp(regex.source + "$", regex.flags),
])
// Helper function to parse priorities from strings (fallback)
const parsePriority = (emoji) => emoji ? priorityEmojis[emoji] || 3 : 3; // Default to medium
// Extract attributes including priority and dates
const extractAttributes = (task) => {
let attrs = { tags: [] }
task = task.replace(checkbox, "").trim(); // Trim checkbox first
for ([prop, regex] of extractors) {
const match = regex.exec(task)
if (!match) continue;
const value = attrs[prop];
if (Array.isArray(value)) value.push(match[1])
else attrs[prop] = match[1]
task = task.slice(0, match.index).trimEnd();
}
// Prioritize emoji-based priorities
attrs.priority = priorityEmojis[task.match(new RegExp(matchPriority))] || parsePriority(attrs.priority);
attrs.name = task.trim();
return attrs;
}
silverbullet.registerAttributeExtractor({ tags: ["task"] }, (text) => {
let { tags, ...attrs } = extractAttributes(text);
// Tags stay at the end for feature parity
if (tags.length) attrs.name += ` ${tags.join(" ")}`
// If the task is cancelled, override the done state
if (text.startsWith("[-]")) attrs.done = true
return attrs
})
const appendDate = async (from, emoji) => {
const now = Temporal.Now.plainDateISO().toString();
await syscall("editor.dispatch", {
changes: { from, insert: ` ${emoji} ${now}` },
});
}
const finalDates = new RegExp(
`${matchers.completed.source}|${matchers.cancelled.source}`,
"gu"
)
const stateChanges = {
// Completed
"x": ({ to }) => appendDate(to, "✅"),
// Incomplete, remove finalization dates from the task
" ": async ({ from, text, to }) => {
const insert = text.replaceAll(finalDates, "")
if (insert === text) return
// The raw text includes the checkbox area which has the old task
// state and we don't want that.
const [current] = checkbox.exec(text) || [""]
const offset = current.length
await syscall("editor.dispatch", {
changes: {
from: from + offset,
to,
insert: insert.slice(offset),
},
})
},
// Cancelled
"-": ({ to }) => appendDate(to, "❌")
}
// Add or remove finalization date when changing a task
silverbullet.registerEventListener(
{ name: "task:stateChange" },
({ data }) => stateChanges[data.newState]?.(data)
)
const toDateString = (date) => date
.toTemporalInstant()
.toZonedDateTimeISO(Temporal.Now.timeZoneId())
.toPlainDate()
// Setup a new recurring task when one is completed.
silverbullet.registerEventListener(
{ name: "task:stateChange" },
async ({ data }) => {
if (data.newState !== "x") return
const { from, text } = data
const match = matchers.recurrence.exec(text)
if (!match) return // non-recurring
const [,deadline] = matchers.deadline.exec(text) || []
if (!deadline) {
return syscall(
"editor.flashNotification",
"No deadline found for recurrence"
)
}
const { RRule } = await rrule
const start = new Date(`${deadline}T00:00:00`)
const rule = new RRule({
...RRule.parseText(match[1].trimEnd()),
dtstart: start,
})
const next = rule.after(start)
const nextLine = text.replace(
matchers.deadline,
` 📅 ${toDateString(next)}`
)
await syscall("editor.insertAtPos", `${nextLine}\n- `, from)
}
)
```
One thing that would be great to improve is to update “cancelled” tasks without the need to click on the - [-]
part, just when the -
is inserted, at change task state.
I tried to fix that but it didn’t work out.
I tried to add the following, in case it matters:
// Cancelled
"-": async ({ to }) => { // Changed to async
await appendDate(to, "❌"); // Directly append the date
}