From cd527754a8929aaf0dcb7993a568972cf38d0873 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 16:32:41 +0000 Subject: [PATCH] Obsidian Parser (YouTube) --- quartz/plugins/parsers/obsidian/index.ts | 1 + quartz/plugins/parsers/obsidian/youtube.ts | 80 ++++++++++++++++++++++ quartz/plugins/transformers/markdown.ts | 6 +- 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 quartz/plugins/parsers/obsidian/youtube.ts diff --git a/quartz/plugins/parsers/obsidian/index.ts b/quartz/plugins/parsers/obsidian/index.ts index b1115200f..4c37fe3c0 100644 --- a/quartz/plugins/parsers/obsidian/index.ts +++ b/quartz/plugins/parsers/obsidian/index.ts @@ -7,3 +7,4 @@ export { ObsidianMermaid } from "./mermaid" export { ObsidianTags } from "./tags" export { ObsidianWikilinks } from "./wikilinks" export { ObsidianVideo } from "./video" +export { ObsidianYouTube } from "./youtube" diff --git a/quartz/plugins/parsers/obsidian/youtube.ts b/quartz/plugins/parsers/obsidian/youtube.ts new file mode 100644 index 000000000..3da312e04 --- /dev/null +++ b/quartz/plugins/parsers/obsidian/youtube.ts @@ -0,0 +1,80 @@ +import { QuartzParser } from "../../types" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" +import { JSResource } from "../../../util/resources" +import { visit } from "unist-util-visit" +import { Root } from "mdast" +import { Element, Literal, Root as HtmlRoot } from "hast" +import { Pluggable } from "unified" + +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + +const ytLinkRegex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/ +const ytPlaylistLinkRegex = /[?&]list=([^#?&]*)/ + +export const ObsidianYouTube: QuartzParser> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "ObsidianYouTube", + textTransform(_ctx, src: string | Buffer) { + if (src instanceof Buffer) { + src = src.toString() + } + return src + }, + markdownPlugins(_ctx) { + const plug: Pluggable = (tree: Root, _file) => { + const replacements: [RegExp, string | ReplaceFunction][] = [] + mdastFindReplace(tree, replacements) + } + return plug + }, + htmlPlugins() { + if (opts.enabled) { + const plug: Pluggable = (tree: HtmlRoot, _file) => { + visit(tree, "element", (node) => { + if (node.tagName === "img" && typeof node.properties.src === "string") { + const match = node.properties.src.match(ytLinkRegex) + const videoId = match && match[2].length == 11 ? match[2] : null + const playlistId = node.properties.src.match(ytPlaylistLinkRegex)?.[1] + if (videoId) { + // YouTube video (with optional playlist) + node.tagName = "iframe" + node.properties = { + class: "external-embed youtube", + allow: "fullscreen", + frameborder: 0, + width: "600px", + src: playlistId + ? `https://www.youtube.com/embed/${videoId}?list=${playlistId}` + : `https://www.youtube.com/embed/${videoId}`, + } + } else if (playlistId) { + // YouTube playlist only. + node.tagName = "iframe" + node.properties = { + class: "external-embed youtube", + allow: "fullscreen", + frameborder: 0, + width: "600px", + src: `https://www.youtube.com/embed/videoseries?list=${playlistId}`, + } + } + } + }) + } + return plug + } + return {} as Pluggable + }, + externalResources() { + const js = {} as JSResource + return js + }, + } +} diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index f1d6d758e..6c54df83c 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -47,6 +47,7 @@ import { ObsidianTags, ObsidianVideo, ObsidianWikilinks, + ObsidianYouTube, } from "../parsers/obsidian" export interface CommonMarkOptions { @@ -322,7 +323,10 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin