feat: process tags in content
This commit is contained in:
		| @@ -2,7 +2,6 @@ | |||||||
| draft: true | draft: true | ||||||
| --- | --- | ||||||
|  |  | ||||||
| - parse tags in content |  | ||||||
| - breadcrumbs component | - breadcrumbs component | ||||||
| - filetree component | - filetree component | ||||||
| - recent notes component | - recent notes component | ||||||
| @@ -10,10 +9,7 @@ draft: true | |||||||
| - [https://giscus.app/](https://giscus.app/) extension | - [https://giscus.app/](https://giscus.app/) extension | ||||||
| - custom md blocks (e.g. for poetry) | - custom md blocks (e.g. for poetry) | ||||||
| - sidenotes? [https://github.com/capnfabs/paperesque](https://github.com/capnfabs/paperesque) | - sidenotes? [https://github.com/capnfabs/paperesque](https://github.com/capnfabs/paperesque) | ||||||
| - watch mode | - watch mode for config/source code | ||||||
|   - watch for markdown changes and quartz config changes |  | ||||||
|   - markdown changes only involve processing that single markdown file (at least for parsing) and then rerunning the filter and emitters |  | ||||||
|   - config changes rebuild the whole thing |  | ||||||
| - direct match in search using double quotes | - direct match in search using double quotes | ||||||
| - attachments path | - attachments path | ||||||
| - [https://help.obsidian.md/Advanced+topics/Using+Obsidian+URI](https://help.obsidian.md/Advanced+topics/Using+Obsidian+URI) | - [https://help.obsidian.md/Advanced+topics/Using+Obsidian+URI](https://help.obsidian.md/Advanced+topics/Using+Obsidian+URI) | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ function TagList({ fileData }: QuartzComponentProps) { | |||||||
|           const linkDest = baseDir + `/tags/${slugAnchor(tag)}` |           const linkDest = baseDir + `/tags/${slugAnchor(tag)}` | ||||||
|           return ( |           return ( | ||||||
|             <li> |             <li> | ||||||
|               <a href={linkDest} class="internal"> |               <a href={linkDest} class="internal tag-link"> | ||||||
|                 {display} |                 {display} | ||||||
|               </a> |               </a> | ||||||
|             </li> |             </li> | ||||||
| @@ -42,7 +42,7 @@ TagList.css = ` | |||||||
|   overflow-wrap: normal; |   overflow-wrap: normal; | ||||||
| } | } | ||||||
|  |  | ||||||
| .tags > li > a { | a.tag-link { | ||||||
|   border-radius: 8px; |   border-radius: 8px; | ||||||
|   background-color: var(--highlight); |   background-color: var(--highlight); | ||||||
|   padding: 0.2rem 0.5rem; |   padding: 0.2rem 0.5rem; | ||||||
|   | |||||||
| @@ -72,7 +72,8 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> = | |||||||
|                 typeof node.properties.href === "string" |                 typeof node.properties.href === "string" | ||||||
|               ) { |               ) { | ||||||
|                 let dest = node.properties.href as RelativeURL |                 let dest = node.properties.href as RelativeURL | ||||||
|                 node.properties.className = isAbsoluteUrl(dest) ? "external" : "internal" |                 node.properties.className ??= [] | ||||||
|  |                 node.properties.className.push(isAbsoluteUrl(dest) ? "external" : "internal") | ||||||
|  |  | ||||||
|                 // don't process external links or intra-document anchors |                 // don't process external links or intra-document anchors | ||||||
|                 if (!(isAbsoluteUrl(dest) || dest.startsWith("#"))) { |                 if (!(isAbsoluteUrl(dest) || dest.startsWith("#"))) { | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ import path from "path" | |||||||
| import { JSResource } from "../../resources" | import { JSResource } from "../../resources" | ||||||
| // @ts-ignore | // @ts-ignore | ||||||
| import calloutScript from "../../components/scripts/callout.inline.ts" | import calloutScript from "../../components/scripts/callout.inline.ts" | ||||||
| import { FilePath, slugifyFilePath } from "../../path" | import { FilePath, canonicalizeServer, pathToRoot, slugifyFilePath } from "../../path" | ||||||
|  |  | ||||||
| export interface Options { | export interface Options { | ||||||
|   comments: boolean |   comments: boolean | ||||||
| @@ -17,6 +17,7 @@ export interface Options { | |||||||
|   wikilinks: boolean |   wikilinks: boolean | ||||||
|   callouts: boolean |   callouts: boolean | ||||||
|   mermaid: boolean |   mermaid: boolean | ||||||
|  |   parseTags: boolean | ||||||
| } | } | ||||||
|  |  | ||||||
| const defaultOptions: Options = { | const defaultOptions: Options = { | ||||||
| @@ -25,6 +26,7 @@ const defaultOptions: Options = { | |||||||
|   wikilinks: true, |   wikilinks: true, | ||||||
|   callouts: true, |   callouts: true, | ||||||
|   mermaid: true, |   mermaid: true, | ||||||
|  |   parseTags: true, | ||||||
| } | } | ||||||
|  |  | ||||||
| const icons = { | const icons = { | ||||||
| @@ -97,22 +99,19 @@ const capitalize = (s: string): string => { | |||||||
|   return s.substring(0, 1).toUpperCase() + s.substring(1) |   return s.substring(0, 1).toUpperCase() + s.substring(1) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Match wikilinks |  | ||||||
| // !?               -> optional embedding | // !?               -> optional embedding | ||||||
| // \[\[             -> open brace | // \[\[             -> open brace | ||||||
| // ([^\[\]\|\#]+)   -> one or more non-special characters ([,],|, or #) (name) | // ([^\[\]\|\#]+)   -> one or more non-special characters ([,],|, or #) (name) | ||||||
| // (#[^\[\]\|\#]+)? -> # then one or more non-special characters (heading link) | // (#[^\[\]\|\#]+)? -> # then one or more non-special characters (heading link) | ||||||
| // (|[^\[\]\|\#]+)? -> | then one or more non-special characters (alias) | // (|[^\[\]\|\#]+)? -> | then one or more non-special characters (alias) | ||||||
| const wikilinkRegex = new RegExp(/!?\[\[([^\[\]\|\#]+)(#[^\[\]\|\#]+)?(\|[^\[\]\|\#]+)?\]\]/, "g") | const wikilinkRegex = new RegExp(/!?\[\[([^\[\]\|\#]+)(#[^\[\]\|\#]+)?(\|[^\[\]\|\#]+)?\]\]/, "g") | ||||||
|  |  | ||||||
| // Match highlights |  | ||||||
| const highlightRegex = new RegExp(/==(.+)==/, "g") | const highlightRegex = new RegExp(/==(.+)==/, "g") | ||||||
|  |  | ||||||
| // Match comments |  | ||||||
| const commentRegex = new RegExp(/%%(.+)%%/, "g") | const commentRegex = new RegExp(/%%(.+)%%/, "g") | ||||||
|  |  | ||||||
| // from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts | // from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts | ||||||
| const calloutRegex = new RegExp(/^\[\!(\w+)\]([+-]?)/) | const calloutRegex = new RegExp(/^\[\!(\w+)\]([+-]?)/) | ||||||
|  | // (?:^| )   -> non-capturing group, tag should start be separated by a space or be the start of the line | ||||||
|  | // #(\w+)    -> tag itself is # followed by a string of alpha-numeric characters  | ||||||
|  | const tagRegex = new RegExp(/(?:^| )#(\w+)/, "g") | ||||||
|  |  | ||||||
| export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> | undefined> = ( | export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> | undefined> = ( | ||||||
|   userOpts, |   userOpts, | ||||||
| @@ -226,7 +225,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> | |||||||
|             findAndReplace(tree, commentRegex, (_value: string, ..._capture: string[]) => { |             findAndReplace(tree, commentRegex, (_value: string, ..._capture: string[]) => { | ||||||
|               return { |               return { | ||||||
|                 type: "text", |                 type: "text", | ||||||
|                 value: "", |                 value: "" | ||||||
|               } |               } | ||||||
|             }) |             }) | ||||||
|           } |           } | ||||||
| @@ -297,9 +296,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> | |||||||
|                 node.data = { |                 node.data = { | ||||||
|                   hProperties: { |                   hProperties: { | ||||||
|                     ...(node.data?.hProperties ?? {}), |                     ...(node.data?.hProperties ?? {}), | ||||||
|                     className: `callout ${collapse ? "is-collapsible" : ""} ${ |                     className: `callout ${collapse ? "is-collapsible" : ""} ${defaultState === "collapsed" ? "is-collapsed" : "" | ||||||
|                       defaultState === "collapsed" ? "is-collapsed" : "" |                       }`, | ||||||
|                     }`, |  | ||||||
|                     "data-callout": calloutType, |                     "data-callout": calloutType, | ||||||
|                     "data-callout-fold": collapse, |                     "data-callout-fold": collapse, | ||||||
|                   }, |                   }, | ||||||
| @@ -317,7 +315,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> | |||||||
|               if (node.lang === "mermaid") { |               if (node.lang === "mermaid") { | ||||||
|                 node.data = { |                 node.data = { | ||||||
|                   hProperties: { |                   hProperties: { | ||||||
|                     className: "mermaid", |                     className: ["mermaid"], | ||||||
|                   }, |                   }, | ||||||
|                 } |                 } | ||||||
|               } |               } | ||||||
| @@ -326,6 +324,32 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> | |||||||
|         }) |         }) | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       if (opts.parseTags) { | ||||||
|  |         plugins.push(() => { | ||||||
|  |           return (tree: Root, file) => { | ||||||
|  |             const slug = canonicalizeServer(file.data.slug!) | ||||||
|  |             const base = pathToRoot(slug) | ||||||
|  |             findAndReplace(tree, tagRegex, (value: string, tag: string) => { | ||||||
|  |               return { | ||||||
|  |                 type: "link", | ||||||
|  |                 url: base + `/tags/${slugAnchor(tag)}`, | ||||||
|  |                 data: { | ||||||
|  |                   hProperties: { | ||||||
|  |                     className: ["tag-link"], | ||||||
|  |                   }, | ||||||
|  |                 }, | ||||||
|  |                 children: [ | ||||||
|  |                   { | ||||||
|  |                     type: "text", | ||||||
|  |                     value, | ||||||
|  |                   }, | ||||||
|  |                 ], | ||||||
|  |               } | ||||||
|  |             }) | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |  | ||||||
|       return plugins |       return plugins | ||||||
|     }, |     }, | ||||||
|     htmlPlugins() { |     htmlPlugins() { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user