From bd560d9ca57eeb7f17611bf755fde8f4d83eca61 Mon Sep 17 00:00:00 2001 From: 4513ECHO Date: Sun, 21 Jun 2026 20:07:36 +0900 Subject: [PATCH] feat: Support GitHub Gist --- README.md | 1 + src/source/gist.ts | 95 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 src/source/gist.ts diff --git a/README.md b/README.md index 663fa7a..e6a19c7 100644 --- a/README.md +++ b/README.md @@ -17,4 +17,5 @@ Since Obsidian officially supports only [a few kinds of web pages](https://obsid ## Supported web pages - [Bluesky](https://bsky.app/) +- [GitHub Gist](https://gist.github.com/) - [Niconico](https://www.nicovideo.jp/) diff --git a/src/source/gist.ts b/src/source/gist.ts new file mode 100644 index 0000000..d661b1f --- /dev/null +++ b/src/source/gist.ts @@ -0,0 +1,95 @@ +import { requestUrl } from "obsidian"; +import { EmbedSource } from "../embed_source.ts"; + +const EMBED_URL = "https://gist.github.com"; +const urlPattern = new URLPattern({ + pathname: "/:username?/:gistId", + baseURL: EMBED_URL, +}); + +export default class Gist extends EmbedSource { + static #heightCache: Map = new Map(); + static id = 0; + #id = Gist.id++; + #srcdoc?: string; + constructor(url: string) { + super(url); + } + + static override get meta() { + return { + name: "GitHub Gist", + logo: "https://github.githubassets.com/favicons/favicon.svg", + origin: "https://gist.github.com", + }; + } + + render(): HTMLElement { + const iframe = createEl("iframe", { + cls: ["external-embed", "node-insert-event"], + attr: { + srcdoc: this.#srcdoc ?? "", + loading: "lazy", + sandbox: "allow-scripts allow-top-navigation-by-user-activation", + }, + }); + if (this.height) { + iframe.style.height = `${this.height}px`; + } + return iframe; + } + + override resolveSrc(): void | Promise { + if (this.#srcdoc) { + return; + } + const matched = urlPattern.exec(this.url)!; + const apiUrl = + `${EMBED_URL}/${matched.pathname.groups.gistId}.json?` + + new URLSearchParams({ + file: matched.hash.input, + }).toString(); + return requestUrl(apiUrl).json.then(async (result) => { + const stylesheet = await requestUrl(result.stylesheet).text; + const styleDecl = getComputedStyle(document.body); + const interfaceFont = styleDecl.getPropertyValue("--font-interface"); + const monospaceFont = styleDecl.getPropertyValue("--font-monospace"); + this.#srcdoc = ` + + + + + + + + ${result.div} + + `; + }); + } + + override get height(): number | undefined { + return Gist.#heightCache.get(this.#id); + } + + override onMessage(event: MessageEvent<{ id: number; height: number }>): boolean { + if (event.origin !== "null") { + return false; + } + const { id, height } = event.data; + if (id !== this.#id || height <= 0) { + return false; + } + Gist.#heightCache.set(this.#id, height); + this.loaded(); + return true; + } +}