Skip to content

Commit

Permalink
wip: introduce remark / rehype embedded-github-code
Browse files Browse the repository at this point in the history
  • Loading branch information
taga3s committed Dec 7, 2024
1 parent a0f40bc commit d12f21d
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 2 deletions.
98 changes: 98 additions & 0 deletions app/packages/rehype-embedded-github-code/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type { Element, Root } from "hast";
import type { Plugin } from "unified";
import { visit } from "unist-util-visit";
import {
buildRequestURL,
extractCodeByLines,
extractFilePathFromURL,
extractLinesFromURL,
extractRefFromURL,
extractRepoNameFromURL,
} from "./util";

interface EmbeddedGithubCode {
url: string;
index: number;
parent: Root | Element;
}

const rehypeEmbeddedGithubCode: Plugin<[], Root> = () => {
const embeddedGithubCodes: EmbeddedGithubCode[] = [];

return async (tree) => {
visit(tree, "element", (node, index, parent) => {
if (
node.tagName !== "a" ||
node.properties["data-remark-target"] !== "remark-embedded-github-code" ||
typeof node.properties.href !== "string"
) {
return;
}

if (index === undefined || parent === undefined) {
return;
}

if (parent.type !== "root" && parent.type !== "element") {
return;
}

embeddedGithubCodes.push({
url: node.properties.href,
index,
parent,
});
});

await Promise.all(
embeddedGithubCodes.map(async (embeddedGithubCode) => {
const repoName = extractRepoNameFromURL(embeddedGithubCode.url);
const ref = extractRefFromURL(embeddedGithubCode.url);
const path = extractFilePathFromURL(embeddedGithubCode.url);
const lines = extractLinesFromURL(embeddedGithubCode.url);

const requestUrl = buildRequestURL(repoName, ref, path);

const response = await fetch(requestUrl, {
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${process.env.GITHUB_PAT}`,
"X-GitHub-Api-Version": "2022-11-28",
},
});
const content = (await response.json()) as { content: string };

const codeBase64 = content.content;
let code = Buffer.from(codeBase64, "base64").toString("utf-8");
if (lines) {
code = extractCodeByLines(code, lines);
}

const codeElement: Element = {
type: "element",
tagName: "code",
properties: {
className: "",
},
children: [
{
type: "text",
value: code,
},
],
};

const preElement: Element = {
type: "element",
tagName: "pre",
children: [codeElement],
properties: {},
};

embeddedGithubCode.parent.children[embeddedGithubCode.index] = preElement;
}),
);
};
};

export { rehypeEmbeddedGithubCode };
62 changes: 62 additions & 0 deletions app/packages/rehype-embedded-github-code/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const extractRepoNameFromURL = (url: string): string => {
const base = url.replace("https://github.com/", "");
const urlParts = base.split("/");
return urlParts.slice(0, 2).join("/");
};

const extractFilePathFromURL = (url: string): string => {
const base = url.replace("https://github.com/", "");
const urlParts = base.split("/");
const result = urlParts.slice(4).join("/");
const hashIndex = result.indexOf("#");
if (hashIndex === -1) {
return result;
}
return result.slice(0, hashIndex);
};

const extractRefFromURL = (url: string): string => {
const base = url.replace("https://github.com/", "");
const urlParts = base.split("/");
return urlParts[3];
};

type LineNumbers = {
startLine: number;
endLine: number;
};

const extractLinesFromURL = (url: string): LineNumbers | undefined => {
const base = url.replace("https://github.com/", "");
const urlParts = base.split("/");
const result = urlParts.slice(4).join("/");
const hashIndex = result.indexOf("#");
if (hashIndex === -1) {
return;
}
const hash = result.slice(hashIndex + 1);
const numbers = hash.match(/\d+/g);
if (!numbers) return;
return {
startLine: Number.parseInt(numbers[0]),
endLine: Number.parseInt(numbers[1]),
};
};

const buildRequestURL = (repo: string, ref: string, path: string): string => {
return `https://api.github.com/repos/${repo}/contents/${path}?ref=${ref}`;
};

const extractCodeByLines = (code: string, lines: LineNumbers): string => {
const codeLines = code.split("\n");
return codeLines.slice(lines.startLine - 1, lines.endLine).join("\n");
};

export {
extractRepoNameFromURL,
extractFilePathFromURL,
extractRefFromURL,
extractLinesFromURL,
buildRequestURL,
extractCodeByLines,
};
30 changes: 30 additions & 0 deletions app/packages/remark-embedded-github-code/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { Root } from "mdast";
import type { Plugin } from "unified";
import { visit } from "unist-util-visit";

const remarkEmbeddedGithubCode: Plugin<[], Root> = () => {
//TODO: check this regex
const regex = /https:\/\/github\.com\/[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+\/blob\/[a-zA-Z0-9._-]+\/?[^\s]*/g;
return (tree) => {
visit(tree, "link", (node, _, parent) => {
if (node.url && typeof node.url === "string") {
const matches = node.url.match(regex);
if (matches) {
node.data = {
hProperties: {
"data-remark-target": "remark-embedded-github-code",
},
};

if (parent && parent.type === "paragraph") {
parent.data = {
hName: "div",
};
}
}
}
});
};
};

export { remarkEmbeddedGithubCode };
6 changes: 4 additions & 2 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import rehypeMomiji from './app/packages/rehype-momiji'
import remarkMomijiCodeFilename from './app/packages/remark-momiji-filename'
import rehypeMermaid from './app/packages/rehype-mermaid/rehypeMermaid'
import remarkEmojiName from './app/packages/remark-emoji-name'
import { remarkEmbeddedGithubCode } from './app/packages/remark-embedded-github-code'
import { rehypeEmbeddedGithubCode } from './app/packages/rehype-embedded-github-code'

export default defineConfig(() => {
return {
Expand All @@ -24,8 +26,8 @@ export default defineConfig(() => {
ssg({ entry: "./app/server.ts" }),
mdx({
jsxImportSource: 'hono/jsx',
remarkPlugins: [remarkGfm, remarkBreaks, remarkFrontmatter, remarkMdxFrontmatter, remarkMomijiCodeFilename, remarkEmojiName],
rehypePlugins: [[rehypeMomiji, { excludeLangs: ['mermaid'] }], rehypeMermaid],
remarkPlugins: [remarkGfm, remarkBreaks, remarkFrontmatter, remarkMdxFrontmatter, remarkMomijiCodeFilename, remarkEmojiName, remarkEmbeddedGithubCode],
rehypePlugins: [[rehypeMomiji, { excludeLangs: ['mermaid'] }], rehypeMermaid, rehypeEmbeddedGithubCode],
})
],
}
Expand Down

0 comments on commit d12f21d

Please sign in to comment.