diff --git a/packages/plugs/core/core.plug.yaml b/packages/plugs/core/core.plug.yaml index ef6a14e..7c7c7a1 100644 --- a/packages/plugs/core/core.plug.yaml +++ b/packages/plugs/core/core.plug.yaml @@ -1,17 +1,5 @@ name: core syntax: - HashTag: - firstCharacters: - - "#" - regex: "#[A-Za-z\\.]+" - styles: - color: blue - AtMention: - firstCharacters: - - "@" - regex: "@[A-Za-z\\.]+" - styles: - color: blue NakedURL: firstCharacters: - "h" diff --git a/packages/plugs/query/data.ts b/packages/plugs/query/data.ts index 5508e70..eded6a1 100644 --- a/packages/plugs/query/data.ts +++ b/packages/plugs/query/data.ts @@ -7,6 +7,7 @@ import { queryPrefix, } from "@silverbulletmd/plugos-silverbullet-syscall"; import { + addParentPointers, collectNodesOfType, findNodeOfType, ParseTree, @@ -67,7 +68,22 @@ export function extractMeta( removeKeys: string[] = [] ): any { let data: any = {}; + addParentPointers(parseTree); replaceNodesMatching(parseTree, (t) => { + if (t.type === "Hashtag") { + // Check if if nested directly into a Paragraph + if (t.parent && t.parent.type === "Paragraph") { + let tagname = t.children![0].text; + if (!data.tags) { + data.tags = []; + } + if (!data.tags.includes(tagname)) { + data.tags.push(tagname); + } + } + return; + } + // Find a fenced code block if (t.type !== "FencedCode") { return; } diff --git a/packages/plugs/tags/tags.plug.yaml b/packages/plugs/tags/tags.plug.yaml new file mode 100644 index 0000000..1ac8330 --- /dev/null +++ b/packages/plugs/tags/tags.plug.yaml @@ -0,0 +1,17 @@ +name: tags +syntax: + Hashtag: + firstCharacters: + - "#" + regex: "#[^#\\s]+" + styles: + color: blue +functions: + indexTags: + path: "./tags.ts:indexTags" + events: + - page:index + tagComplete: + path: "./tags.ts:tagComplete" + events: + - page:complete diff --git a/packages/plugs/tags/tags.ts b/packages/plugs/tags/tags.ts new file mode 100644 index 0000000..c274fa1 --- /dev/null +++ b/packages/plugs/tags/tags.ts @@ -0,0 +1,39 @@ +import { collectNodesOfType } from "@silverbulletmd/common/tree"; +import { + batchSet, + queryPrefix, +} from "@silverbulletmd/plugos-silverbullet-syscall"; +import { matchBefore } from "@silverbulletmd/plugos-silverbullet-syscall/editor"; +import type { IndexTreeEvent } from "@silverbulletmd/web/app_event"; +import { removeQueries } from "../query/util"; + +// Key space +// ht:TAG => true (for completion) + +export async function indexTags({ name, tree }: IndexTreeEvent) { + removeQueries(tree); + let allTags = new Set(); + collectNodesOfType(tree, "Hashtag").forEach((n) => { + allTags.add(n.children![0].text!); + }); + batchSet( + name, + [...allTags].map((t) => ({ key: `ht:${t}`, value: t })) + ); +} + +export async function tagComplete() { + let prefix = await matchBefore("#[^#\\s]+"); + // console.log("Running tag complete", prefix); + if (!prefix) { + return null; + } + let allTags = await queryPrefix(`ht:${prefix.text}`); + return { + from: prefix.from, + options: allTags.map((tag) => ({ + label: tag.value, + type: "tag", + })), + }; +} diff --git a/packages/plugs/tasks/task.ts b/packages/plugs/tasks/task.ts index 667aa3f..5e00467 100644 --- a/packages/plugs/tasks/task.ts +++ b/packages/plugs/tasks/task.ts @@ -23,6 +23,7 @@ import { nodeAtPos, ParseTree, renderToText, + replaceNodesMatching, } from "@silverbulletmd/common/tree"; import { removeQueries } from "../query/util"; import { applyQuery, QueryProviderEvent, renderQuery } from "../query/engine"; @@ -32,6 +33,7 @@ export type Task = { name: string; done: boolean; deadline?: string; + tags?: string[]; nested?: string; // Not saved in DB, just added when pulled out (from key) pos?: number; @@ -47,28 +49,40 @@ export async function indexTasks({ name, tree }: IndexTreeEvent) { let tasks: { key: string; value: Task }[] = []; removeQueries(tree); collectNodesOfType(tree, "Task").forEach((n) => { - let task = n.children!.slice(1).map(renderToText).join("").trim(); let complete = n.children![0].children![0].text! !== "[ ]"; - let value: Task = { - name: task, + let task: Task = { + name: "", done: complete, }; - let deadlineNode = findNodeOfType(n, "DeadlineDate"); - if (deadlineNode) { - value.deadline = getDeadline(deadlineNode); - } + replaceNodesMatching(n, (tree) => { + if (tree.type === "DeadlineDate") { + task.deadline = getDeadline(tree); + // Remove this node from the tree + return null; + } + if (tree.type === "Hashtag") { + if (!task.tags) { + task.tags = []; + } + task.tags.push(tree.children![0].text!); + // Remove this node from the tree + return null; + } + }); + + task.name = n.children!.slice(1).map(renderToText).join("").trim(); let taskIndex = n.parent!.children!.indexOf(n); let nestedItems = n.parent!.children!.slice(taskIndex + 1); if (nestedItems.length > 0) { - value.nested = nestedItems.map(renderToText).join("").trim(); + task.nested = nestedItems.map(renderToText).join("").trim(); } tasks.push({ key: `task:${n.from}`, - value, + value: task, }); - // console.log("Task", value); + console.log("Task", task); }); console.log("Found", tasks.length, "task(s)");