From 7790db45c4ff7cae2a63542fca304c45156022b2 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 15:09:56 +0000 Subject: [PATCH] Obsidian Parser (Tags) --- quartz/plugins/parsers/obsidian/index.ts | 1 + quartz/plugins/parsers/obsidian/tags.ts | 82 ++++++++++++++++++++++++ quartz/plugins/transformers/markdown.ts | 2 + 3 files changed, 85 insertions(+) diff --git a/quartz/plugins/parsers/obsidian/index.ts b/quartz/plugins/parsers/obsidian/index.ts index baf0fd627..4f1555b84 100644 --- a/quartz/plugins/parsers/obsidian/index.ts +++ b/quartz/plugins/parsers/obsidian/index.ts @@ -4,4 +4,5 @@ export { ObsidianCheckboxes } from "./checkboxes" export { ObsidianComments } from "./comments" export { ObsidianHighlights } from "./highlights" export { ObsidianMermaid } from "./mermaid" +export { ObsidianTags } from "./tags" export { ObsidianWikilinks } from "./wikilinks" diff --git a/quartz/plugins/parsers/obsidian/tags.ts b/quartz/plugins/parsers/obsidian/tags.ts index e69de29bb..7f30d5911 100644 --- a/quartz/plugins/parsers/obsidian/tags.ts +++ b/quartz/plugins/parsers/obsidian/tags.ts @@ -0,0 +1,82 @@ +import { QuartzParser } from "../../types" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" +import { FilePath, pathToRoot, slugTag, slugifyFilePath } from "../../../util/path" +import { JSResource } from "../../../util/resources" +import { Root } from "mdast" +import { Pluggable } from "unified" + +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + +// (?:^| ) -> non-capturing group, tag should start be separated by a space or be the start of the line +// #(...) -> capturing group, tag itself must start with # +// (?:[-_\p{L}\d\p{Z}])+ -> non-capturing group, non-empty string of (Unicode-aware) alpha-numeric characters and symbols, hyphens and/or underscores +// (?:\/[-_\p{L}\d\p{Z}]+)*) -> non-capturing group, matches an arbitrary number of tag strings separated by "/" +const tagRegex = new RegExp( + /(?:^| )#((?:[-_\p{L}\p{Emoji}\p{M}\d])+(?:\/[-_\p{L}\p{Emoji}\p{M}\d]+)*)/gu, +) + +export const ObsidianTags: QuartzParser> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "ObsidianTags", + textTransform(_ctx, src: string | Buffer) { + if (src instanceof Buffer) { + src = src.toString() + } + return src + }, + markdownPlugins(_ctx) { + const plug: Pluggable = (tree: Root, file) => { + const base = pathToRoot(file.data.slug!) + const replacements: [RegExp, string | ReplaceFunction][] = [] + replacements.push([ + tagRegex, + (_value: string, tag: string) => { + // Check if the tag only includes numbers and slashes + if (/^[\/\d]+$/.test(tag)) { + return false + } + + tag = slugTag(tag) + if (file.data.frontmatter) { + const noteTags = file.data.frontmatter.tags ?? [] + file.data.frontmatter.tags = [...new Set([...noteTags, tag])] + } + + return { + type: "link", + url: base + `/tags/${tag}`, + data: { + hProperties: { + className: ["tag-link"], + }, + }, + children: [ + { + type: "text", + value: tag, + }, + ], + } + }, + ]) + mdastFindReplace(tree, replacements) + } + return plug + }, + htmlPlugins() { + const plug: Pluggable = () => {} + return plug + }, + externalResources() { + const js = {} as JSResource + return js + }, + } +} diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index c40e47336..681169306 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -44,6 +44,7 @@ import { ObsidianComments, ObsidianHighlights, ObsidianMermaid, + ObsidianTags, ObsidianWikilinks, } from "../parsers/obsidian" @@ -267,6 +268,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin