From c709f4e4be59e04b5b6e138f92d9dfa5a6118237 Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Fri, 22 Dec 2023 13:59:16 +0100 Subject: [PATCH] More ways to define tags in frontmatter --- .../index => plug-api/lib}/cheap_yaml.test.ts | 6 ++++ {plugs/index => plug-api/lib}/cheap_yaml.ts | 10 +++--- plug-api/lib/frontmatter.ts | 8 +++-- plugs/index/attributes.ts | 2 +- plugs/index/tags.ts | 23 ++++++++++---- plugs/template/util.ts | 31 +++---------------- website/Frontmatter.md | 13 +++++++- 7 files changed, 53 insertions(+), 40 deletions(-) rename {plugs/index => plug-api/lib}/cheap_yaml.test.ts (63%) rename {plugs/index => plug-api/lib}/cheap_yaml.ts (73%) diff --git a/plugs/index/cheap_yaml.test.ts b/plug-api/lib/cheap_yaml.test.ts similarity index 63% rename from plugs/index/cheap_yaml.test.ts rename to plug-api/lib/cheap_yaml.test.ts index ed007483..492a7c22 100644 --- a/plugs/index/cheap_yaml.test.ts +++ b/plug-api/lib/cheap_yaml.test.ts @@ -7,4 +7,10 @@ Deno.test("cheap yaml", () => { assertEquals(["template"], determineTags("tags: template")); assertEquals(["bla", "template"], determineTags("tags: bla,template")); assertEquals(["bla", "template"], determineTags("tags:\n- bla\n- template")); + assertEquals(["bla", "template"], determineTags(`tags: "#bla,#template"`)); + assertEquals(["bla", "template"], determineTags(`tags: '#bla, #template'`)); + assertEquals( + ["bla", "template"], + determineTags(`tags:\n- "#bla"\n- template`), + ); }); diff --git a/plugs/index/cheap_yaml.ts b/plug-api/lib/cheap_yaml.ts similarity index 73% rename from plugs/index/cheap_yaml.ts rename to plug-api/lib/cheap_yaml.ts index fc792fa8..feae44f9 100644 --- a/plugs/index/cheap_yaml.ts +++ b/plug-api/lib/cheap_yaml.ts @@ -1,5 +1,5 @@ -const yamlKvRegex = /^\s*(\w+):\s*(.*)/; -const yamlListItemRegex = /^\s*-\s+(.+)/; +const yamlKvRegex = /^\s*(\w+):\s*["']?([^'"]*)["']?$/; +const yamlListItemRegex = /^\s*-\s+["']?([^'"]+)["']?$/; /** * Cheap YAML parser to determine tags (ugly, regex based but fast) @@ -19,7 +19,9 @@ export function determineTags(yamlText: string): string[] { inTagsSection = true; // 'template' there? Yay! if (value) { - tags.push(...value.split(/,\s*/)); + tags.push( + ...value.split(/,\s*|\s+/).map((t) => t.replace(/^#/, "")), + ); } } else { inTagsSection = false; @@ -27,7 +29,7 @@ export function determineTags(yamlText: string): string[] { } const yamlListem = yamlListItemRegex.exec(line); if (yamlListem && inTagsSection) { - tags.push(yamlListem[1]); + tags.push(yamlListem[1].replace(/^#/, "")); } } return tags; diff --git a/plug-api/lib/frontmatter.ts b/plug-api/lib/frontmatter.ts index 40fb27d5..80d98f12 100644 --- a/plug-api/lib/frontmatter.ts +++ b/plug-api/lib/frontmatter.ts @@ -62,10 +62,14 @@ export async function extractFrontmatter( if (!data.tags) { data.tags = []; } - // Normalize tags to an array and support a "tag1, tag2" notation + // Normalize tags to an array + // support "tag1, tag2" as well as "tag1 tag2" as well as "#tag1 #tag2" notations if (typeof data.tags === "string") { - data.tags = (data.tags as string).split(/,\s*/); + data.tags = (data.tags as string).split(/,\s*|\s+/); } + + // Strip # from tags + data.tags = data.tags.map((t) => t.replace(/^#/, "")); if (options.removeKeys && options.removeKeys.length > 0) { let removedOne = false; diff --git a/plugs/index/attributes.ts b/plugs/index/attributes.ts index ca14992e..aa85c10f 100644 --- a/plugs/index/attributes.ts +++ b/plugs/index/attributes.ts @@ -2,7 +2,7 @@ import type { CompleteEvent } from "$sb/app_event.ts"; import { events } from "$sb/syscalls.ts"; import { queryObjects } from "./api.ts"; import { ObjectValue, QueryExpression } from "$sb/types.ts"; -import { determineTags } from "./cheap_yaml.ts"; +import { determineTags } from "../../plug-api/lib/cheap_yaml.ts"; export type AttributeObject = ObjectValue<{ name: string; diff --git a/plugs/index/tags.ts b/plugs/index/tags.ts index 2943fc03..74337a36 100644 --- a/plugs/index/tags.ts +++ b/plugs/index/tags.ts @@ -62,17 +62,28 @@ export async function tagComplete(completeEvent: CompleteEvent) { } const tagPrefix = match[0].substring(1); let parent = "page"; - if (taskPrefixRegex.test(completeEvent.linePrefix)) { - parent = "task"; - } else if (itemPrefixRegex.test(completeEvent.linePrefix)) { - parent = "item"; + if (!completeEvent.parentNodes.find((n) => n.startsWith("FrontMatter:"))) { + if (taskPrefixRegex.test(completeEvent.linePrefix)) { + parent = "task"; + } else if (itemPrefixRegex.test(completeEvent.linePrefix)) { + parent = "item"; + } } - // Query all tags - const allTags = await queryObjects("tag", { + // Query all tags with a matching parent + const allTags: any[] = await queryObjects("tag", { filter: ["=", ["attr", "parent"], ["string", parent]], + select: [{ name: "name" }], + distinct: true, }); + if (parent === "page") { + // Also add template, even though that would otherwise not appear because has "builtin" as a parent + allTags.push({ + name: "template", + }); + } + return { from: completeEvent.pos - tagPrefix.length, options: allTags.map((tag) => ({ diff --git a/plugs/template/util.ts b/plugs/template/util.ts index da96808a..32b8befc 100644 --- a/plugs/template/util.ts +++ b/plugs/template/util.ts @@ -1,6 +1,6 @@ +import { determineTags } from "$sb/lib/cheap_yaml.ts"; + const frontMatterRegex = /^---\n(([^\n]|\n)*?)---\n/; -const yamlKvRegex = /^\s*(\w+):\s*(.*)/; -const yamlListItemRegex = /^\s*-\s+(.+)/; /** * Quick and dirty way to check if a page is a template or not @@ -13,30 +13,9 @@ export function isTemplate(pageText: string): boolean { if (frontmatter) { pageText = pageText.slice(frontmatter[0].length); const frontmatterText = frontmatter[1]; - const lines = frontmatterText.split("\n"); - let inTagsSection = false; - for (const line of lines) { - const yamlKv = yamlKvRegex.exec(line); - if (yamlKv) { - const [key, value] = yamlKv.slice(1); - // Looking for a 'tags' key - if (key === "tags") { - inTagsSection = true; - // 'template' there? Yay! - if (value.split(/,\s*/).includes("template")) { - return true; - } - } else { - inTagsSection = false; - } - } - const yamlListem = yamlListItemRegex.exec(line); - if (yamlListem && inTagsSection) { - // List item is 'template'? Yay! - if (yamlListem[1] === "template") { - return true; - } - } + const tags = determineTags(frontmatterText); + if (tags.includes("template")) { + return true; } } // Or if the page text starts with a #template tag diff --git a/website/Frontmatter.md b/website/Frontmatter.md index 864e70cf..1c2b5fef 100644 --- a/website/Frontmatter.md +++ b/website/Frontmatter.md @@ -21,6 +21,17 @@ While SilverBullet allows arbitrary metadata to be added to pages, there are a f * `name` (==DISALLOWED==): is an attribute used for page names, _you should not set it_. * `displayName` (`string`): very similar in effect as `aliases` but will use this name for the page in certain contexts. * `aliases` (`array of strings`): allow you to specify a list of alternative names for this page, which can be used to navigate or link to this page -* `tags` (`array of strings` or `string`): an alternative (and perhaps preferred) way to assign [[Tags]] to a page. In principle you specify them as a list of strings, but for convenience you can also specify them as (possibly comma-separated) string, e.g. `tags: tag1, tag2, tag3` +* `tags` (`array of strings` or `string`): an alternative (and perhaps preferred) way to assign [[Tags]] to a page. There are various ways to define these, take your pick: + ```yaml + tags: tag1, tag2 # with commas + tags: tag1 tag2 # with spaces + tags: "#tag1 #tag2" # with pound signs and quotes (you get completion) + tags: # as a list + - tag1 + - tag2 + tags: # as a list with pound signs and quotes + - "#tag1" + - "#tag2" + ``` In addition, in the context of [[Templates]] frontmatter has a very specific interpretation. \ No newline at end of file