marksites 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/dist/annotations/model.d.ts +29 -0
- package/dist/annotations/model.js +56 -0
- package/dist/annotations/storage.d.ts +4 -0
- package/dist/annotations/storage.js +31 -0
- package/dist/cli/directory.d.ts +4 -0
- package/dist/cli/directory.js +3 -0
- package/dist/cli/open-browser.d.ts +12 -0
- package/dist/cli/open-browser.js +37 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +125 -0
- package/dist/conversion/directory.d.ts +3 -0
- package/dist/conversion/directory.js +208 -0
- package/dist/conversion/gitignore.d.ts +8 -0
- package/dist/conversion/gitignore.js +43 -0
- package/dist/conversion/manifest.d.ts +8 -0
- package/dist/conversion/manifest.js +40 -0
- package/dist/conversion/navigation.d.ts +4 -0
- package/dist/conversion/navigation.js +70 -0
- package/dist/conversion/paths.d.ts +10 -0
- package/dist/conversion/paths.js +86 -0
- package/dist/conversion/rendering.d.ts +5 -0
- package/dist/conversion/rendering.js +39 -0
- package/dist/conversion/single-file.d.ts +1 -0
- package/dist/conversion/single-file.js +35 -0
- package/dist/conversion/types.d.ts +39 -0
- package/dist/conversion/types.js +1 -0
- package/dist/features/annotations/index.d.ts +7 -0
- package/dist/features/annotations/index.js +79 -0
- package/dist/features/annotations.d.ts +9 -0
- package/dist/features/annotations.js +78 -0
- package/dist/features/code-blocks/index.d.ts +5 -0
- package/dist/features/code-blocks/index.js +97 -0
- package/dist/features/code-blocks.d.ts +5 -0
- package/dist/features/code-blocks.js +89 -0
- package/dist/features/file-tree/index.d.ts +7 -0
- package/dist/features/file-tree/index.js +347 -0
- package/dist/features/file-tree.d.ts +7 -0
- package/dist/features/file-tree.js +325 -0
- package/dist/features/header/index.d.ts +4 -0
- package/dist/features/header/index.js +47 -0
- package/dist/features/header.d.ts +6 -0
- package/dist/features/header.js +47 -0
- package/dist/features/sidebar/index.d.ts +10 -0
- package/dist/features/sidebar/index.js +55 -0
- package/dist/features/sidebar.d.ts +13 -0
- package/dist/features/sidebar.js +55 -0
- package/dist/features/table-of-contents/index.d.ts +16 -0
- package/dist/features/table-of-contents/index.js +91 -0
- package/dist/features/table-of-contents.d.ts +16 -0
- package/dist/features/table-of-contents.js +91 -0
- package/dist/features/types.d.ts +6 -0
- package/dist/features/types.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/markdown-to-html.d.ts +5 -0
- package/dist/markdown-to-html.js +77 -0
- package/dist/server/annotation-repository.d.ts +35 -0
- package/dist/server/annotation-repository.js +184 -0
- package/dist/server/api.d.ts +4 -0
- package/dist/server/api.js +77 -0
- package/dist/server/constants.d.ts +2 -0
- package/dist/server/constants.js +2 -0
- package/dist/server/html-security.d.ts +4 -0
- package/dist/server/html-security.js +61 -0
- package/dist/server/response.d.ts +2 -0
- package/dist/server/response.js +9 -0
- package/dist/server/server.d.ts +3 -0
- package/dist/server/server.js +77 -0
- package/dist/server/static-files.d.ts +2 -0
- package/dist/server/static-files.js +72 -0
- package/dist/server/types.d.ts +16 -0
- package/dist/server/types.js +1 -0
- package/dist/template/document.d.ts +20 -0
- package/dist/template/document.js +33 -0
- package/dist/template/styles.d.ts +5 -0
- package/dist/template/styles.js +109 -0
- package/dist/types.d.ts +52 -0
- package/dist/types.js +1 -0
- package/dist/utils/files.d.ts +1 -0
- package/dist/utils/files.js +9 -0
- package/dist/utils/html.d.ts +2 -0
- package/dist/utils/html.js +17 -0
- package/dist/utils/icons.d.ts +8 -0
- package/dist/utils/icons.js +24 -0
- package/package.json +43 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { marked, Renderer } from "marked";
|
|
3
|
+
import { emptyAnnotationDocument } from "../annotations/model.js";
|
|
4
|
+
import { createAnnotationsFeature } from "../features/annotations/index.js";
|
|
5
|
+
import { createCodeBlocksFeature } from "../features/code-blocks/index.js";
|
|
6
|
+
import { createHeaderFeature } from "../features/header/index.js";
|
|
7
|
+
import { renderFileTreeScript, renderModifiedAtScript, } from "../features/file-tree/index.js";
|
|
8
|
+
import { createSidebarFeature } from "../features/sidebar/index.js";
|
|
9
|
+
import { createTableOfContentsFeature } from "../features/table-of-contents/index.js";
|
|
10
|
+
function scriptBody(script) {
|
|
11
|
+
return /^<script[^>]*>([\s\S]*)<\/script>$/.exec(script)?.[1] ?? "";
|
|
12
|
+
}
|
|
13
|
+
function generatedScriptBodies() {
|
|
14
|
+
const codeRenderer = new Renderer();
|
|
15
|
+
const code = createCodeBlocksFeature(codeRenderer, true);
|
|
16
|
+
marked.parse("```js\nconst value = 1;\n```", {
|
|
17
|
+
renderer: codeRenderer,
|
|
18
|
+
async: false,
|
|
19
|
+
});
|
|
20
|
+
const tocRenderer = new Renderer();
|
|
21
|
+
const toc = createTableOfContentsFeature(tocRenderer, {
|
|
22
|
+
enabled: true,
|
|
23
|
+
title: "目次",
|
|
24
|
+
minDepth: 2,
|
|
25
|
+
maxDepth: 6,
|
|
26
|
+
});
|
|
27
|
+
marked.parse("## Heading", { renderer: tocRenderer, async: false });
|
|
28
|
+
const renderedToc = toc.render();
|
|
29
|
+
const annotations = createAnnotationsFeature(emptyAnnotationDocument("index.md"));
|
|
30
|
+
return new Set([
|
|
31
|
+
scriptBody(code.renderScript()),
|
|
32
|
+
scriptBody(createHeaderFeature().script),
|
|
33
|
+
scriptBody(renderFileTreeScript(true)),
|
|
34
|
+
scriptBody(renderModifiedAtScript(true)),
|
|
35
|
+
scriptBody(renderedToc.script),
|
|
36
|
+
scriptBody(annotations.script),
|
|
37
|
+
scriptBody(createSidebarFeature({
|
|
38
|
+
tableOfContents: renderedToc.markup,
|
|
39
|
+
tableOfContentsTitle: renderedToc.title,
|
|
40
|
+
annotations: annotations.panel,
|
|
41
|
+
annotationCount: annotations.count,
|
|
42
|
+
}).script),
|
|
43
|
+
scriptBody(createSidebarFeature({
|
|
44
|
+
tableOfContents: "",
|
|
45
|
+
tableOfContentsTitle: renderedToc.title,
|
|
46
|
+
annotations: annotations.panel,
|
|
47
|
+
annotationCount: annotations.count,
|
|
48
|
+
}).script),
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
const allowedScripts = generatedScriptBodies();
|
|
52
|
+
export function secureHtml(source) {
|
|
53
|
+
const nonce = randomBytes(18).toString("base64");
|
|
54
|
+
const body = source.replace(/<script data-marksites-script="true">([\s\S]*?)<\/script>/g, (whole, content) => allowedScripts.has(content)
|
|
55
|
+
? `<script data-marksites-script="true" nonce="${nonce}">${content}</script>`
|
|
56
|
+
: whole);
|
|
57
|
+
return {
|
|
58
|
+
body,
|
|
59
|
+
csp: `default-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'nonce-${nonce}'; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function sendJson(response, status, data) {
|
|
2
|
+
response.writeHead(status, {
|
|
3
|
+
"cache-control": "no-store",
|
|
4
|
+
"content-type": "application/json; charset=utf-8",
|
|
5
|
+
"referrer-policy": "no-referrer",
|
|
6
|
+
"x-content-type-options": "nosniff",
|
|
7
|
+
});
|
|
8
|
+
response.end(JSON.stringify(status >= 400 ? { error: { code: status, message: data } } : { data }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { lstat, realpath } from "node:fs/promises";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { AnnotationRepository } from "./annotation-repository.js";
|
|
5
|
+
import { handleApi } from "./api.js";
|
|
6
|
+
import { MARKSITES_RESERVED_PATH } from "./constants.js";
|
|
7
|
+
import { sendJson } from "./response.js";
|
|
8
|
+
import { handleStaticFile } from "./static-files.js";
|
|
9
|
+
async function listen(server, host, initialPort, fallbackPort) {
|
|
10
|
+
let port = initialPort;
|
|
11
|
+
while (true) {
|
|
12
|
+
try {
|
|
13
|
+
await new Promise((done, fail) => {
|
|
14
|
+
const onError = (error) => fail(error);
|
|
15
|
+
server.once("error", onError);
|
|
16
|
+
server.listen(port, host, () => {
|
|
17
|
+
server.off("error", onError);
|
|
18
|
+
done();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (!fallbackPort ||
|
|
25
|
+
port === 0 ||
|
|
26
|
+
port >= 65535 ||
|
|
27
|
+
error.code !== "EADDRINUSE") {
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
port += 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function assertReservedPathIsAvailable(root) {
|
|
35
|
+
try {
|
|
36
|
+
await lstat(join(root, MARKSITES_RESERVED_PATH));
|
|
37
|
+
throw new Error(`Reserved output path exists: ${MARKSITES_RESERVED_PATH}`);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error.code !== "ENOENT")
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export async function startMarksitesServer(options) {
|
|
45
|
+
const host = options.host ?? "127.0.0.1";
|
|
46
|
+
const port = options.port ?? 3000;
|
|
47
|
+
const fallbackPort = options.fallbackPort ?? options.port === undefined;
|
|
48
|
+
const root = resolve(options.outputRoot);
|
|
49
|
+
const rootReal = await realpath(root);
|
|
50
|
+
await assertReservedPathIsAvailable(root);
|
|
51
|
+
const repository = new AnnotationRepository(root, options.documents, async (document) => options.onAnnotationsChange(document));
|
|
52
|
+
let ownOrigin = "";
|
|
53
|
+
const server = createServer(async (request, response) => {
|
|
54
|
+
try {
|
|
55
|
+
const hostHeader = request.headers.host;
|
|
56
|
+
if (!hostHeader ||
|
|
57
|
+
![`${host}:`, "127.0.0.1:", "localhost:"].some((prefix) => hostHeader.startsWith(prefix))) {
|
|
58
|
+
return sendJson(response, 403, "Invalid Host header");
|
|
59
|
+
}
|
|
60
|
+
const url = new URL(request.url ?? "/", ownOrigin);
|
|
61
|
+
if (url.pathname.startsWith("/_marksites/")) {
|
|
62
|
+
return await handleApi(request, response, url, ownOrigin, options, repository);
|
|
63
|
+
}
|
|
64
|
+
await handleStaticFile(request, response, url, root, rootReal, options.entryPath);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
sendJson(response, error.statusCode ?? 500, error instanceof Error ? error.message : String(error));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
await listen(server, host, port, fallbackPort);
|
|
71
|
+
const address = server.address();
|
|
72
|
+
ownOrigin = `http://${host}:${address.port}`;
|
|
73
|
+
return {
|
|
74
|
+
url: ownOrigin,
|
|
75
|
+
close: () => new Promise((done, fail) => server.close((error) => (error ? fail(error) : done()))),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createReadStream } from "node:fs";
|
|
2
|
+
import { lstat, readFile, realpath } from "node:fs/promises";
|
|
3
|
+
import { extname, join, normalize, resolve, sep } from "node:path";
|
|
4
|
+
import { secureHtml } from "./html-security.js";
|
|
5
|
+
import { sendJson } from "./response.js";
|
|
6
|
+
const MIME = {
|
|
7
|
+
".css": "text/css; charset=utf-8",
|
|
8
|
+
".gif": "image/gif",
|
|
9
|
+
".html": "text/html; charset=utf-8",
|
|
10
|
+
".jpeg": "image/jpeg",
|
|
11
|
+
".jpg": "image/jpeg",
|
|
12
|
+
".js": "text/javascript; charset=utf-8",
|
|
13
|
+
".json": "application/json; charset=utf-8",
|
|
14
|
+
".png": "image/png",
|
|
15
|
+
".svg": "image/svg+xml",
|
|
16
|
+
".webp": "image/webp",
|
|
17
|
+
};
|
|
18
|
+
export async function handleStaticFile(request, response, url, root, rootReal, entryPath) {
|
|
19
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
20
|
+
response.setHeader("allow", "GET, HEAD");
|
|
21
|
+
return sendJson(response, 405, "Method not allowed");
|
|
22
|
+
}
|
|
23
|
+
let decoded;
|
|
24
|
+
try {
|
|
25
|
+
decoded = decodeURIComponent(url.pathname);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return sendJson(response, 400, "Invalid URL encoding");
|
|
29
|
+
}
|
|
30
|
+
if (decoded.includes("\\") ||
|
|
31
|
+
decoded.includes("\0") ||
|
|
32
|
+
decoded.split("/").includes("..")) {
|
|
33
|
+
return sendJson(response, 403, "Invalid path");
|
|
34
|
+
}
|
|
35
|
+
const requestedPath = decoded === "/" && entryPath ? `/${entryPath}` : decoded;
|
|
36
|
+
let path = resolve(root, `.${normalize(requestedPath)}`);
|
|
37
|
+
if (!path.startsWith(root + sep) && path !== root)
|
|
38
|
+
return sendJson(response, 403, "Invalid path");
|
|
39
|
+
let info;
|
|
40
|
+
try {
|
|
41
|
+
info = await lstat(path);
|
|
42
|
+
if (info.isDirectory()) {
|
|
43
|
+
path = join(path, "index.html");
|
|
44
|
+
info = await lstat(path);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return sendJson(response, 404, "Not found");
|
|
49
|
+
}
|
|
50
|
+
if (!info.isFile())
|
|
51
|
+
return sendJson(response, 404, "Not found");
|
|
52
|
+
const actual = await realpath(path);
|
|
53
|
+
if (!actual.startsWith(rootReal + sep) && actual !== rootReal)
|
|
54
|
+
return sendJson(response, 403, "Invalid symlink target");
|
|
55
|
+
const headers = {
|
|
56
|
+
"content-type": MIME[extname(path).toLowerCase()] ?? "application/octet-stream",
|
|
57
|
+
"x-content-type-options": "nosniff",
|
|
58
|
+
};
|
|
59
|
+
if (extname(path).toLowerCase() === ".html") {
|
|
60
|
+
const secured = secureHtml(await readFile(path, "utf8"));
|
|
61
|
+
headers["content-security-policy"] = secured.csp;
|
|
62
|
+
response.writeHead(200, headers);
|
|
63
|
+
response.end(request.method === "HEAD" ? undefined : secured.body);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
response.writeHead(200, headers);
|
|
67
|
+
if (request.method === "HEAD") {
|
|
68
|
+
response.end();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
createReadStream(path).pipe(response);
|
|
72
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface MarksitesServerOptions {
|
|
2
|
+
outputRoot: string;
|
|
3
|
+
entryPath?: string;
|
|
4
|
+
host?: string;
|
|
5
|
+
port?: number;
|
|
6
|
+
fallbackPort?: boolean;
|
|
7
|
+
projectId: string;
|
|
8
|
+
projectName: string;
|
|
9
|
+
documents: Map<string, string>;
|
|
10
|
+
editable?: boolean;
|
|
11
|
+
onAnnotationsChange: (document: string) => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export interface RunningServer {
|
|
14
|
+
url: string;
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface DocumentParts {
|
|
2
|
+
title: string;
|
|
3
|
+
language: string;
|
|
4
|
+
content: string;
|
|
5
|
+
highlight: boolean;
|
|
6
|
+
regions: {
|
|
7
|
+
header: string;
|
|
8
|
+
breadcrumbs: string;
|
|
9
|
+
fileTree: string;
|
|
10
|
+
fileSidebar: string;
|
|
11
|
+
sidebar: string;
|
|
12
|
+
overlays: string;
|
|
13
|
+
};
|
|
14
|
+
assets: {
|
|
15
|
+
styles: string[];
|
|
16
|
+
scripts: string[];
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export declare function renderDocument(parts: DocumentParts): string;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { documentStyles, fileTreeStyles, githubMarkdownCss, highlightCss, highlightThemeStyles, } from "./styles.js";
|
|
2
|
+
export function renderDocument(parts) {
|
|
3
|
+
const bodyClass = parts.regions.fileTree
|
|
4
|
+
? "markdown-body has-file-tree"
|
|
5
|
+
: "markdown-body";
|
|
6
|
+
const trustedScript = (script) => script.replace("<script>", '<script data-marksites-script="true">');
|
|
7
|
+
return `<!doctype html>
|
|
8
|
+
<html lang="${parts.language}">
|
|
9
|
+
<head>
|
|
10
|
+
<meta charset="utf-8">
|
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
12
|
+
<title>${parts.title}</title>
|
|
13
|
+
<style>${githubMarkdownCss}</style>
|
|
14
|
+
${parts.highlight ? `<style>${highlightCss}\n${highlightThemeStyles}</style>` : ""}
|
|
15
|
+
<style>
|
|
16
|
+
${documentStyles}${parts.regions.fileTree ? `\n${fileTreeStyles}` : ""}${parts.assets.styles.join("")}
|
|
17
|
+
</style>
|
|
18
|
+
</head>
|
|
19
|
+
<body class="${bodyClass}">
|
|
20
|
+
${parts.regions.header}
|
|
21
|
+
${parts.regions.fileSidebar}
|
|
22
|
+
<main class="markdown-content">
|
|
23
|
+
${parts.regions.fileTree ? `<div class="file-navigation">
|
|
24
|
+
${parts.regions.breadcrumbs}${parts.regions.fileTree}</div>
|
|
25
|
+
` : parts.regions.breadcrumbs}${parts.content}
|
|
26
|
+
</main>
|
|
27
|
+
${parts.regions.sidebar}
|
|
28
|
+
${parts.regions.overlays}
|
|
29
|
+
${parts.assets.scripts.map(trustedScript).join("")}
|
|
30
|
+
</body>
|
|
31
|
+
</html>
|
|
32
|
+
`;
|
|
33
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const githubMarkdownCss: string;
|
|
2
|
+
export declare const highlightCss: string;
|
|
3
|
+
export declare const highlightThemeStyles = "body[data-theme=\"dark\"] .hljs{color:var(--codeBlock-fgColor);background:var(--codeBlock-bgColor)}\nbody[data-theme=\"dark\"] :is(.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_){color:var(--color-prettylights-syntax-keyword)}\nbody[data-theme=\"dark\"] :is(.hljs-title,.hljs-title.class_,.hljs-title.function_){color:var(--color-prettylights-syntax-entity)}\nbody[data-theme=\"dark\"] :is(.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id){color:var(--color-prettylights-syntax-constant)}\nbody[data-theme=\"dark\"] :is(.hljs-regexp,.hljs-string,.hljs-meta .hljs-string){color:var(--color-prettylights-syntax-string)}\nbody[data-theme=\"dark\"] :is(.hljs-built_in,.hljs-symbol){color:var(--color-prettylights-syntax-variable)}\nbody[data-theme=\"dark\"] :is(.hljs-comment,.hljs-code,.hljs-formula){color:var(--color-prettylights-syntax-comment)}\nbody[data-theme=\"dark\"] :is(.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo){color:var(--color-prettylights-syntax-entity-tag)}";
|
|
4
|
+
export declare const documentStyles = " body.markdown-body { box-sizing: border-box; min-width: 200px; width: calc(100% - 64px); max-width: none; margin: 0 32px; padding: 32px 0 0; display: grid; grid-template-columns: minmax(0, 1fr) 300px; grid-template-areas: \"content toc\"; column-gap: 32px; align-items: start; }\n .markdown-content, .document-sidebar { box-sizing: border-box; }\n .markdown-content { grid-area: content; min-width: 0; margin-bottom: 72px; padding: clamp(28px, 3vw, 52px); border: 1px solid var(--borderColor-muted, #d8dee4); border-radius: 8px; box-shadow: 0 1px 2px rgba(31, 35, 40, 0.04); }\n .markdown-content :is(h1, h2, h3, h4, h5, h6) { scroll-margin-top: 32px; }\n .document-metadata { display: flex; justify-content: flex-end; margin: -12px 0 28px; padding-bottom: 14px; border-bottom: 1px solid var(--borderColor-muted, #d8dee4); }\n .document-modified { margin-left: auto; color: var(--fgColor-muted, #59636e); font-size: 0.75rem; line-height: 28px; white-space: nowrap; }\n .code-block { margin-bottom: 16px; overflow: hidden; border: 1px solid var(--borderColor-muted, #d8dee4); border-radius: 8px; }\n .code-toolbar { display: flex; min-height: 38px; align-items: center; justify-content: space-between; padding: 0 8px 0 14px; color: var(--fgColor-muted, #59636e); background: var(--bgColor-muted, #f6f8fa); border-bottom: 1px solid var(--borderColor-muted, #d8dee4); }\n .code-language { font-size: 0.75rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; }\n .code-tools { display: flex; gap: 2px; }\n .code-tool { display: inline-flex; min-height: 30px; align-items: center; justify-content: center; gap: 5px; padding: 4px 8px; color: inherit; font: inherit; font-size: 0.75rem; line-height: 1; background: transparent; border: 0; border-radius: 5px; cursor: pointer; }\n .code-tool-label, [data-copy-label] { display: inline-flex; align-items: center; line-height: 1; }\n .code-tool:hover { color: var(--fgColor-default, #1f2328); background: var(--button-default-bgColor-hover, #eaeef2); }\n .code-tool:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: -2px; }\n .code-tool[aria-pressed=\"true\"] { color: var(--fgColor-accent, #0969da); background: var(--bgColor-accent-muted, #ddf4ff); }\n .code-block pre { margin: 0; border-radius: 0; }\n .code-block pre, .code-block pre code { color: var(--codeBlock-fgColor, #24292f); background: var(--codeBlock-bgColor, #fff); }\n .code-block pre code { display: block; }\n .code-block.is-wrapped pre code { white-space: pre-wrap; overflow-wrap: anywhere; }\n .panel-toggle-icon { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; transition: transform 120ms ease; }\n .action-icon { display: block; width: 14px; height: 14px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.25; stroke-linecap: round; stroke-linejoin: round; }\n .table-of-contents ul { margin: 0; padding: 0; list-style: none; }\n .table-of-contents li { margin: 2px 0 2px calc(var(--toc-level) * 12px); }\n .table-of-contents a { position: relative; display: block; padding: 6px 10px; color: var(--fgColor-muted, #59636e); font-size: 0.875rem; line-height: 1.4; overflow-wrap: anywhere; text-decoration: none; border-radius: 0 6px 6px 0; transition: color 120ms ease, background-color 120ms ease; }\n .table-of-contents a::before { position: absolute; top: 0; bottom: 0; left: 0; width: 2px; background: transparent; content: \"\"; }\n .table-of-contents a:hover { color: var(--fgColor-default, #1f2328); background: var(--bgColor-muted, #f6f8fa); text-decoration: none; }\n .table-of-contents a:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: -2px; }\n .table-of-contents a[aria-current=\"location\"] { color: var(--fgColor-accent, #0969da); font-weight: 600; background: linear-gradient(90deg, var(--bgColor-accent-muted, #ddf4ff), transparent); }\n .table-of-contents a[aria-current=\"location\"]::before { background: var(--borderColor-accent-emphasis, #0969da); }\n @media (max-width: 900px) {\n body.markdown-body { width: calc(100% - 24px); max-width: none; margin: 0 12px; padding: 12px 0 0; grid-template-columns: minmax(0, 1fr); grid-template-areas: \"toc\" \"content\"; gap: 16px; }\n .markdown-content { margin-bottom: 32px; }\n }\n @media (max-width: 600px) { .markdown-content { padding: 24px 20px; border-radius: 6px; } }\n @media (prefers-reduced-motion: reduce) { .table-of-contents a, .panel-toggle-icon { transition: none; } }";
|
|
5
|
+
export declare const fileTreeStyles = " body.markdown-body.has-file-tree { width: 100%; margin: 0; padding: 32px 32px 0 0; grid-template-columns: 280px minmax(0, 1fr) 300px; grid-template-areas: \"files content toc\"; column-gap: 32px; }\n body.markdown-body.has-file-tree.file-sidebar-collapsed { width: calc(100% - 64px); margin: 0 32px; padding: 32px 0 0; grid-template-columns: minmax(0, 1fr) 300px; grid-template-areas: \"content toc\"; }\n .file-sidebar { grid-area: files; position: fixed; z-index: 45; top: 0; bottom: 0; left: 0; box-sizing: border-box; display: flex; width: 280px; min-height: 0; flex-direction: column; color: var(--fgColor-default, #1f2328); background: var(--bgColor-default, #fff); border: 0; border-right: 1px solid var(--borderColor-muted, #d8dee4); border-radius: 0; overflow: hidden; }\n .file-sidebar-header { display: flex; min-height: 52px; flex: none; align-items: center; gap: 9px; padding: 0 12px; font-size: 0.875rem; font-weight: 700; border-bottom: 1px solid var(--borderColor-muted, #d8dee4); }\n .file-sidebar-header > span { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .file-sidebar[hidden], .file-sidebar-open[hidden], .file-sidebar-close[hidden] { display: none; }\n .file-sidebar-close { position: fixed; z-index: 50; top: 13px; left: 12px; display: inline-flex; width: 30px; height: 30px; flex: none; align-items: center; justify-content: center; padding: 0; color: var(--fgColor-muted, #59636e); background: transparent; border: 0; border-radius: 6px; cursor: pointer; }\n .file-sidebar-close:hover { color: var(--fgColor-default, #1f2328); background: var(--button-default-bgColor-hover, #eaeef2); }\n .file-sidebar-close:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: -2px; }\n .file-navigation { position: relative; margin: -12px 0 28px; }\n .file-breadcrumbs { display: flex; align-items: center; gap: 8px; margin: 0; padding-bottom: 14px; border-bottom: 1px solid var(--borderColor-muted, #d8dee4); }\n .file-breadcrumbs ol { display: flex; min-width: 0; min-height: 28px; flex: 0 1 auto; flex-wrap: wrap; align-items: baseline; gap: 6px; margin: 0; padding: 0; list-style: none; }\n .file-breadcrumbs li { display: inline-block; min-width: 0; height: 28px; color: var(--fgColor-muted, #59636e); font-size: 0.875rem; line-height: 28px; }\n .file-breadcrumbs li + li::before { margin-right: 6px; color: var(--fgColor-muted, #818b98); content: \"/\"; }\n .file-breadcrumb-separator { height: 28px; margin-right: -2px; color: var(--fgColor-muted, #818b98); font-size: 0.875rem; line-height: 28px; }\n .file-breadcrumbs a { display: inline-block; height: 28px; color: var(--fgColor-accent, #0969da); font-weight: 500; line-height: 28px; text-decoration: none; vertical-align: baseline; }\n .file-breadcrumbs a:hover { text-decoration: underline; }\n .file-breadcrumbs [aria-current=\"page\"] { color: var(--fgColor-default, #1f2328); font-weight: 600; white-space: nowrap; }\n .file-sidebar-open { position: fixed; z-index: 50; top: 13px; left: 12px; display: inline-flex; width: 30px; height: 30px; flex: none; align-items: center; justify-content: center; padding: 0; color: var(--fgColor-muted, #59636e); background: transparent; border: 0; border-radius: 6px; cursor: pointer; }\n .file-tree-popover-toggle { position: relative; top: 1px; display: inline-flex; height: 28px; min-width: 0; flex: none; align-items: center; justify-content: center; gap: 3px; padding: 0; color: var(--fgColor-accent, #0969da); font: inherit; font-size: 0.875rem; font-weight: 600; line-height: 28px; background: transparent; border: 0; border-radius: 4px; cursor: pointer; }\n .file-tree-popover-toggle span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .file-sidebar-toggle-icon { display: block; width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.2; stroke-linecap: round; stroke-linejoin: round; }\n .file-sidebar-open:hover { color: var(--fgColor-default, #1f2328); background: var(--button-default-bgColor-hover, #eaeef2); }\n .file-sidebar-open:focus-visible, .file-tree-popover-toggle:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: 2px; }\n .file-tree-popover-toggle:hover { color: var(--fgColor-accent, #0969da); text-decoration: underline; background: transparent; }\n .file-tree-popover-toggle .panel-toggle-icon { width: 13px; height: 13px; flex: none; transform: translateY(1px); }\n .copy-file-path .copy-icon { transform: none; }\n .file-tree-popover-toggle[aria-expanded=\"true\"] .panel-toggle-icon { transform: translateY(1px) rotate(180deg); }\n .copy-file-path { display: inline-flex; width: 28px; height: 28px; flex: none; align-items: center; justify-content: center; padding: 0; color: var(--fgColor-muted, #59636e); background: transparent; border: 0; border-radius: 5px; cursor: pointer; }\n .copy-file-path:hover { color: var(--fgColor-default, #1f2328); background: var(--button-default-bgColor-hover, #eaeef2); }\n .copy-file-path:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: 2px; }\n .file-tree { box-sizing: border-box; overflow: auto; padding: 12px; color: var(--fgColor-default, #1f2328); background: var(--bgColor-default, #fff); scrollbar-width: thin; scrollbar-color: var(--borderColor-default, #d0d7de) transparent; }\n .file-tree-sidebar { min-height: 0; flex: 1; padding: 10px 12px 16px; }\n .file-tree-popover { position: absolute; z-index: 15; top: calc(100% + 6px); left: 0; width: min(360px, calc(100vw - 48px)); max-height: min(520px, calc(100vh - 96px)); border: 1px solid var(--borderColor-default, #d0d7de); border-radius: 8px; box-shadow: 0 8px 24px rgba(31,35,40,.16); }\n .file-tree-filter { margin: 0 0 8px; }\n .file-tree-filter-input { box-sizing: border-box; width: 100%; min-height: 32px; padding: 5px 9px; color: var(--fgColor-default, #1f2328); font: inherit; font-size: 0.8125rem; line-height: 1.4; background: var(--bgColor-default, #fff); border: 1px solid var(--borderColor-default, #d0d7de); border-radius: 6px; outline: none; }\n .file-tree-filter-input::placeholder { color: var(--fgColor-muted, #59636e); }\n .file-tree-filter-input:focus { border-color: var(--borderColor-accent-emphasis, #0969da); box-shadow: 0 0 0 2px var(--bgColor-accent-muted, #ddf4ff); }\n .file-tree-filter-empty { margin: 10px 4px 2px; color: var(--fgColor-muted, #59636e); font-size: 0.8125rem; text-align: center; }\n .file-tree ul { margin: 0; padding: 0; list-style: none; }\n .file-tree details { margin: 0; }\n .file-tree details > ul { margin-left: 10px; padding-left: 10px; border-left: 1px solid var(--borderColor-muted, #d8dee4); }\n .file-tree summary { padding: 5px 7px; color: var(--fgColor-default, #1f2328); font-size: 0.875rem; font-weight: 600; line-height: 1.35; border-radius: 5px; cursor: pointer; user-select: none; }\n .folder-icon { display: inline-block; width: 14px; height: 14px; margin-right: 5px; vertical-align: -2px; fill: none; stroke: currentColor; stroke-width: 1.25; stroke-linecap: round; stroke-linejoin: round; }\n .file-tree summary:hover { background: var(--bgColor-muted, #f6f8fa); }\n .file-tree summary::marker { color: var(--fgColor-muted, #59636e); }\n .file-tree a { display: flex; margin: 1px 0; padding: 5px 8px; align-items: center; gap: 6px; overflow: hidden; color: var(--fgColor-muted, #59636e); font-size: 0.875rem; line-height: 1.35; text-decoration: none; white-space: nowrap; border-radius: 5px; }\n .file-tree-name { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; }\n .file-tree-comment-count { display: inline-flex; min-width: 18px; height: 18px; flex: none; align-items: center; justify-content: center; padding: 0 5px; color: var(--fgColor-muted, #59636e); font-size: 0.6875rem; font-weight: 600; line-height: 18px; background: var(--bgColor-neutral-muted, #818b981f); border-radius: 9px; }\n .file-tree-directory-comment-count { float: right; margin-left: 6px; }\n .file-tree details[open] > summary > .file-tree-directory-comment-count { display: none; }\n .file-tree a[aria-current=\"page\"] .file-tree-comment-count { color: var(--fgColor-accent, #0969da); background: var(--bgColor-default, #fff); }\n .file-tree a:hover { color: var(--fgColor-default, #1f2328); background: var(--bgColor-muted, #f6f8fa); text-decoration: none; }\n .file-tree a:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: -2px; }\n .file-tree a[aria-current=\"page\"] { color: var(--fgColor-accent, #0969da); font-weight: 600; background: var(--bgColor-accent-muted, #ddf4ff); }\n @media (max-width: 900px) {\n body.markdown-body.has-file-tree, body.markdown-body.has-file-tree.file-sidebar-collapsed { width: calc(100% - 24px); margin: 0 12px; padding: 12px 0 0; grid-template-columns: minmax(0, 1fr); grid-template-areas: \"toc\" \"content\"; gap: 16px; }\n .file-sidebar { width: min(280px, calc(100vw - 24px)); box-shadow: 8px 0 24px rgba(31,35,40,.18); }\n }\n @media (max-width: 600px) { .file-tree-popover { width: calc(100vw - 40px); max-height: calc(100vh - 80px); } }\n ";
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
export const githubMarkdownCss = readFileSync(require.resolve("github-markdown-css/github-markdown.css"), "utf8");
|
|
5
|
+
export const highlightCss = readFileSync(require.resolve("highlight.js/styles/github.css"), "utf8");
|
|
6
|
+
export const highlightThemeStyles = `body[data-theme="dark"] .hljs{color:var(--codeBlock-fgColor);background:var(--codeBlock-bgColor)}
|
|
7
|
+
body[data-theme="dark"] :is(.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_){color:var(--color-prettylights-syntax-keyword)}
|
|
8
|
+
body[data-theme="dark"] :is(.hljs-title,.hljs-title.class_,.hljs-title.function_){color:var(--color-prettylights-syntax-entity)}
|
|
9
|
+
body[data-theme="dark"] :is(.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id){color:var(--color-prettylights-syntax-constant)}
|
|
10
|
+
body[data-theme="dark"] :is(.hljs-regexp,.hljs-string,.hljs-meta .hljs-string){color:var(--color-prettylights-syntax-string)}
|
|
11
|
+
body[data-theme="dark"] :is(.hljs-built_in,.hljs-symbol){color:var(--color-prettylights-syntax-variable)}
|
|
12
|
+
body[data-theme="dark"] :is(.hljs-comment,.hljs-code,.hljs-formula){color:var(--color-prettylights-syntax-comment)}
|
|
13
|
+
body[data-theme="dark"] :is(.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo){color:var(--color-prettylights-syntax-entity-tag)}`;
|
|
14
|
+
export const documentStyles = ` body.markdown-body { box-sizing: border-box; min-width: 200px; width: calc(100% - 64px); max-width: none; margin: 0 32px; padding: 32px 0 0; display: grid; grid-template-columns: minmax(0, 1fr) 300px; grid-template-areas: "content toc"; column-gap: 32px; align-items: start; }
|
|
15
|
+
.markdown-content, .document-sidebar { box-sizing: border-box; }
|
|
16
|
+
.markdown-content { grid-area: content; min-width: 0; margin-bottom: 72px; padding: clamp(28px, 3vw, 52px); border: 1px solid var(--borderColor-muted, #d8dee4); border-radius: 8px; box-shadow: 0 1px 2px rgba(31, 35, 40, 0.04); }
|
|
17
|
+
.markdown-content :is(h1, h2, h3, h4, h5, h6) { scroll-margin-top: 32px; }
|
|
18
|
+
.document-metadata { display: flex; justify-content: flex-end; margin: -12px 0 28px; padding-bottom: 14px; border-bottom: 1px solid var(--borderColor-muted, #d8dee4); }
|
|
19
|
+
.document-modified { margin-left: auto; color: var(--fgColor-muted, #59636e); font-size: 0.75rem; line-height: 28px; white-space: nowrap; }
|
|
20
|
+
.code-block { margin-bottom: 16px; overflow: hidden; border: 1px solid var(--borderColor-muted, #d8dee4); border-radius: 8px; }
|
|
21
|
+
.code-toolbar { display: flex; min-height: 38px; align-items: center; justify-content: space-between; padding: 0 8px 0 14px; color: var(--fgColor-muted, #59636e); background: var(--bgColor-muted, #f6f8fa); border-bottom: 1px solid var(--borderColor-muted, #d8dee4); }
|
|
22
|
+
.code-language { font-size: 0.75rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
23
|
+
.code-tools { display: flex; gap: 2px; }
|
|
24
|
+
.code-tool { display: inline-flex; min-height: 30px; align-items: center; justify-content: center; gap: 5px; padding: 4px 8px; color: inherit; font: inherit; font-size: 0.75rem; line-height: 1; background: transparent; border: 0; border-radius: 5px; cursor: pointer; }
|
|
25
|
+
.code-tool-label, [data-copy-label] { display: inline-flex; align-items: center; line-height: 1; }
|
|
26
|
+
.code-tool:hover { color: var(--fgColor-default, #1f2328); background: var(--button-default-bgColor-hover, #eaeef2); }
|
|
27
|
+
.code-tool:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: -2px; }
|
|
28
|
+
.code-tool[aria-pressed="true"] { color: var(--fgColor-accent, #0969da); background: var(--bgColor-accent-muted, #ddf4ff); }
|
|
29
|
+
.code-block pre { margin: 0; border-radius: 0; }
|
|
30
|
+
.code-block pre, .code-block pre code { color: var(--codeBlock-fgColor, #24292f); background: var(--codeBlock-bgColor, #fff); }
|
|
31
|
+
.code-block pre code { display: block; }
|
|
32
|
+
.code-block.is-wrapped pre code { white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
33
|
+
.panel-toggle-icon { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; transition: transform 120ms ease; }
|
|
34
|
+
.action-icon { display: block; width: 14px; height: 14px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.25; stroke-linecap: round; stroke-linejoin: round; }
|
|
35
|
+
.table-of-contents ul { margin: 0; padding: 0; list-style: none; }
|
|
36
|
+
.table-of-contents li { margin: 2px 0 2px calc(var(--toc-level) * 12px); }
|
|
37
|
+
.table-of-contents a { position: relative; display: block; padding: 6px 10px; color: var(--fgColor-muted, #59636e); font-size: 0.875rem; line-height: 1.4; overflow-wrap: anywhere; text-decoration: none; border-radius: 0 6px 6px 0; transition: color 120ms ease, background-color 120ms ease; }
|
|
38
|
+
.table-of-contents a::before { position: absolute; top: 0; bottom: 0; left: 0; width: 2px; background: transparent; content: ""; }
|
|
39
|
+
.table-of-contents a:hover { color: var(--fgColor-default, #1f2328); background: var(--bgColor-muted, #f6f8fa); text-decoration: none; }
|
|
40
|
+
.table-of-contents a:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: -2px; }
|
|
41
|
+
.table-of-contents a[aria-current="location"] { color: var(--fgColor-accent, #0969da); font-weight: 600; background: linear-gradient(90deg, var(--bgColor-accent-muted, #ddf4ff), transparent); }
|
|
42
|
+
.table-of-contents a[aria-current="location"]::before { background: var(--borderColor-accent-emphasis, #0969da); }
|
|
43
|
+
@media (max-width: 900px) {
|
|
44
|
+
body.markdown-body { width: calc(100% - 24px); max-width: none; margin: 0 12px; padding: 12px 0 0; grid-template-columns: minmax(0, 1fr); grid-template-areas: "toc" "content"; gap: 16px; }
|
|
45
|
+
.markdown-content { margin-bottom: 32px; }
|
|
46
|
+
}
|
|
47
|
+
@media (max-width: 600px) { .markdown-content { padding: 24px 20px; border-radius: 6px; } }
|
|
48
|
+
@media (prefers-reduced-motion: reduce) { .table-of-contents a, .panel-toggle-icon { transition: none; } }`;
|
|
49
|
+
export const fileTreeStyles = ` body.markdown-body.has-file-tree { width: 100%; margin: 0; padding: 32px 32px 0 0; grid-template-columns: 280px minmax(0, 1fr) 300px; grid-template-areas: "files content toc"; column-gap: 32px; }
|
|
50
|
+
body.markdown-body.has-file-tree.file-sidebar-collapsed { width: calc(100% - 64px); margin: 0 32px; padding: 32px 0 0; grid-template-columns: minmax(0, 1fr) 300px; grid-template-areas: "content toc"; }
|
|
51
|
+
.file-sidebar { grid-area: files; position: fixed; z-index: 45; top: 0; bottom: 0; left: 0; box-sizing: border-box; display: flex; width: 280px; min-height: 0; flex-direction: column; color: var(--fgColor-default, #1f2328); background: var(--bgColor-default, #fff); border: 0; border-right: 1px solid var(--borderColor-muted, #d8dee4); border-radius: 0; overflow: hidden; }
|
|
52
|
+
.file-sidebar-header { display: flex; min-height: 52px; flex: none; align-items: center; gap: 9px; padding: 0 12px; font-size: 0.875rem; font-weight: 700; border-bottom: 1px solid var(--borderColor-muted, #d8dee4); }
|
|
53
|
+
.file-sidebar-header > span { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
54
|
+
.file-sidebar[hidden], .file-sidebar-open[hidden], .file-sidebar-close[hidden] { display: none; }
|
|
55
|
+
.file-sidebar-close { position: fixed; z-index: 50; top: 13px; left: 12px; display: inline-flex; width: 30px; height: 30px; flex: none; align-items: center; justify-content: center; padding: 0; color: var(--fgColor-muted, #59636e); background: transparent; border: 0; border-radius: 6px; cursor: pointer; }
|
|
56
|
+
.file-sidebar-close:hover { color: var(--fgColor-default, #1f2328); background: var(--button-default-bgColor-hover, #eaeef2); }
|
|
57
|
+
.file-sidebar-close:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: -2px; }
|
|
58
|
+
.file-navigation { position: relative; margin: -12px 0 28px; }
|
|
59
|
+
.file-breadcrumbs { display: flex; align-items: center; gap: 8px; margin: 0; padding-bottom: 14px; border-bottom: 1px solid var(--borderColor-muted, #d8dee4); }
|
|
60
|
+
.file-breadcrumbs ol { display: flex; min-width: 0; min-height: 28px; flex: 0 1 auto; flex-wrap: wrap; align-items: baseline; gap: 6px; margin: 0; padding: 0; list-style: none; }
|
|
61
|
+
.file-breadcrumbs li { display: inline-block; min-width: 0; height: 28px; color: var(--fgColor-muted, #59636e); font-size: 0.875rem; line-height: 28px; }
|
|
62
|
+
.file-breadcrumbs li + li::before { margin-right: 6px; color: var(--fgColor-muted, #818b98); content: "/"; }
|
|
63
|
+
.file-breadcrumb-separator { height: 28px; margin-right: -2px; color: var(--fgColor-muted, #818b98); font-size: 0.875rem; line-height: 28px; }
|
|
64
|
+
.file-breadcrumbs a { display: inline-block; height: 28px; color: var(--fgColor-accent, #0969da); font-weight: 500; line-height: 28px; text-decoration: none; vertical-align: baseline; }
|
|
65
|
+
.file-breadcrumbs a:hover { text-decoration: underline; }
|
|
66
|
+
.file-breadcrumbs [aria-current="page"] { color: var(--fgColor-default, #1f2328); font-weight: 600; white-space: nowrap; }
|
|
67
|
+
.file-sidebar-open { position: fixed; z-index: 50; top: 13px; left: 12px; display: inline-flex; width: 30px; height: 30px; flex: none; align-items: center; justify-content: center; padding: 0; color: var(--fgColor-muted, #59636e); background: transparent; border: 0; border-radius: 6px; cursor: pointer; }
|
|
68
|
+
.file-tree-popover-toggle { position: relative; top: 1px; display: inline-flex; height: 28px; min-width: 0; flex: none; align-items: center; justify-content: center; gap: 3px; padding: 0; color: var(--fgColor-accent, #0969da); font: inherit; font-size: 0.875rem; font-weight: 600; line-height: 28px; background: transparent; border: 0; border-radius: 4px; cursor: pointer; }
|
|
69
|
+
.file-tree-popover-toggle span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
70
|
+
.file-sidebar-toggle-icon { display: block; width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.2; stroke-linecap: round; stroke-linejoin: round; }
|
|
71
|
+
.file-sidebar-open:hover { color: var(--fgColor-default, #1f2328); background: var(--button-default-bgColor-hover, #eaeef2); }
|
|
72
|
+
.file-sidebar-open:focus-visible, .file-tree-popover-toggle:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: 2px; }
|
|
73
|
+
.file-tree-popover-toggle:hover { color: var(--fgColor-accent, #0969da); text-decoration: underline; background: transparent; }
|
|
74
|
+
.file-tree-popover-toggle .panel-toggle-icon { width: 13px; height: 13px; flex: none; transform: translateY(1px); }
|
|
75
|
+
.copy-file-path .copy-icon { transform: none; }
|
|
76
|
+
.file-tree-popover-toggle[aria-expanded="true"] .panel-toggle-icon { transform: translateY(1px) rotate(180deg); }
|
|
77
|
+
.copy-file-path { display: inline-flex; width: 28px; height: 28px; flex: none; align-items: center; justify-content: center; padding: 0; color: var(--fgColor-muted, #59636e); background: transparent; border: 0; border-radius: 5px; cursor: pointer; }
|
|
78
|
+
.copy-file-path:hover { color: var(--fgColor-default, #1f2328); background: var(--button-default-bgColor-hover, #eaeef2); }
|
|
79
|
+
.copy-file-path:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: 2px; }
|
|
80
|
+
.file-tree { box-sizing: border-box; overflow: auto; padding: 12px; color: var(--fgColor-default, #1f2328); background: var(--bgColor-default, #fff); scrollbar-width: thin; scrollbar-color: var(--borderColor-default, #d0d7de) transparent; }
|
|
81
|
+
.file-tree-sidebar { min-height: 0; flex: 1; padding: 10px 12px 16px; }
|
|
82
|
+
.file-tree-popover { position: absolute; z-index: 15; top: calc(100% + 6px); left: 0; width: min(360px, calc(100vw - 48px)); max-height: min(520px, calc(100vh - 96px)); border: 1px solid var(--borderColor-default, #d0d7de); border-radius: 8px; box-shadow: 0 8px 24px rgba(31,35,40,.16); }
|
|
83
|
+
.file-tree-filter { margin: 0 0 8px; }
|
|
84
|
+
.file-tree-filter-input { box-sizing: border-box; width: 100%; min-height: 32px; padding: 5px 9px; color: var(--fgColor-default, #1f2328); font: inherit; font-size: 0.8125rem; line-height: 1.4; background: var(--bgColor-default, #fff); border: 1px solid var(--borderColor-default, #d0d7de); border-radius: 6px; outline: none; }
|
|
85
|
+
.file-tree-filter-input::placeholder { color: var(--fgColor-muted, #59636e); }
|
|
86
|
+
.file-tree-filter-input:focus { border-color: var(--borderColor-accent-emphasis, #0969da); box-shadow: 0 0 0 2px var(--bgColor-accent-muted, #ddf4ff); }
|
|
87
|
+
.file-tree-filter-empty { margin: 10px 4px 2px; color: var(--fgColor-muted, #59636e); font-size: 0.8125rem; text-align: center; }
|
|
88
|
+
.file-tree ul { margin: 0; padding: 0; list-style: none; }
|
|
89
|
+
.file-tree details { margin: 0; }
|
|
90
|
+
.file-tree details > ul { margin-left: 10px; padding-left: 10px; border-left: 1px solid var(--borderColor-muted, #d8dee4); }
|
|
91
|
+
.file-tree summary { padding: 5px 7px; color: var(--fgColor-default, #1f2328); font-size: 0.875rem; font-weight: 600; line-height: 1.35; border-radius: 5px; cursor: pointer; user-select: none; }
|
|
92
|
+
.folder-icon { display: inline-block; width: 14px; height: 14px; margin-right: 5px; vertical-align: -2px; fill: none; stroke: currentColor; stroke-width: 1.25; stroke-linecap: round; stroke-linejoin: round; }
|
|
93
|
+
.file-tree summary:hover { background: var(--bgColor-muted, #f6f8fa); }
|
|
94
|
+
.file-tree summary::marker { color: var(--fgColor-muted, #59636e); }
|
|
95
|
+
.file-tree a { display: flex; margin: 1px 0; padding: 5px 8px; align-items: center; gap: 6px; overflow: hidden; color: var(--fgColor-muted, #59636e); font-size: 0.875rem; line-height: 1.35; text-decoration: none; white-space: nowrap; border-radius: 5px; }
|
|
96
|
+
.file-tree-name { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; }
|
|
97
|
+
.file-tree-comment-count { display: inline-flex; min-width: 18px; height: 18px; flex: none; align-items: center; justify-content: center; padding: 0 5px; color: var(--fgColor-muted, #59636e); font-size: 0.6875rem; font-weight: 600; line-height: 18px; background: var(--bgColor-neutral-muted, #818b981f); border-radius: 9px; }
|
|
98
|
+
.file-tree-directory-comment-count { float: right; margin-left: 6px; }
|
|
99
|
+
.file-tree details[open] > summary > .file-tree-directory-comment-count { display: none; }
|
|
100
|
+
.file-tree a[aria-current="page"] .file-tree-comment-count { color: var(--fgColor-accent, #0969da); background: var(--bgColor-default, #fff); }
|
|
101
|
+
.file-tree a:hover { color: var(--fgColor-default, #1f2328); background: var(--bgColor-muted, #f6f8fa); text-decoration: none; }
|
|
102
|
+
.file-tree a:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: -2px; }
|
|
103
|
+
.file-tree a[aria-current="page"] { color: var(--fgColor-accent, #0969da); font-weight: 600; background: var(--bgColor-accent-muted, #ddf4ff); }
|
|
104
|
+
@media (max-width: 900px) {
|
|
105
|
+
body.markdown-body.has-file-tree, body.markdown-body.has-file-tree.file-sidebar-collapsed { width: calc(100% - 24px); margin: 0 12px; padding: 12px 0 0; grid-template-columns: minmax(0, 1fr); grid-template-areas: "toc" "content"; gap: 16px; }
|
|
106
|
+
.file-sidebar { width: min(280px, calc(100vw - 24px)); box-shadow: 8px 0 24px rgba(31,35,40,.18); }
|
|
107
|
+
}
|
|
108
|
+
@media (max-width: 600px) { .file-tree-popover { width: calc(100vw - 40px); max-height: calc(100vh - 80px); } }
|
|
109
|
+
`;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { MarkedOptions } from "marked";
|
|
2
|
+
export interface RenderOptions {
|
|
3
|
+
/** Text used for the HTML document title. */
|
|
4
|
+
title?: string;
|
|
5
|
+
/** Language assigned to the root HTML element. */
|
|
6
|
+
language?: string;
|
|
7
|
+
/** Enable syntax highlighting for fenced code blocks. Defaults to true. */
|
|
8
|
+
highlight?: boolean;
|
|
9
|
+
/** Add a table of contents generated from headings. Defaults to true. */
|
|
10
|
+
tableOfContents?: boolean | TableOfContentsOptions;
|
|
11
|
+
/** Add a repository-style file tree for a converted Markdown collection. */
|
|
12
|
+
fileTree?: FileTreeOptions;
|
|
13
|
+
/** ISO 8601 timestamp shown as the Markdown source's last update time. */
|
|
14
|
+
modifiedAt?: string;
|
|
15
|
+
/** Options forwarded to marked. Async parsing is not supported. */
|
|
16
|
+
markedOptions?: Omit<MarkedOptions, "async" | "renderer">;
|
|
17
|
+
}
|
|
18
|
+
export interface FileTreeOptions {
|
|
19
|
+
/** Heading shown above the file tree. Defaults to "ファイル". */
|
|
20
|
+
title?: string;
|
|
21
|
+
/** Nested directories and Markdown files to render. */
|
|
22
|
+
items: FileTreeNode[];
|
|
23
|
+
/** Path segments shown above the document content. */
|
|
24
|
+
breadcrumbs?: FileBreadcrumb[];
|
|
25
|
+
}
|
|
26
|
+
export interface FileBreadcrumb {
|
|
27
|
+
name: string;
|
|
28
|
+
href?: string;
|
|
29
|
+
current?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export type FileTreeNode = FileTreeDirectory | FileTreeFile;
|
|
32
|
+
export interface FileTreeDirectory {
|
|
33
|
+
type: "directory";
|
|
34
|
+
name: string;
|
|
35
|
+
children: FileTreeNode[];
|
|
36
|
+
}
|
|
37
|
+
export interface FileTreeFile {
|
|
38
|
+
type: "file";
|
|
39
|
+
name: string;
|
|
40
|
+
href: string;
|
|
41
|
+
current?: boolean;
|
|
42
|
+
/** Number of comments associated with the file. Omitted when unavailable. */
|
|
43
|
+
commentCount?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface TableOfContentsOptions {
|
|
46
|
+
/** Heading shown above the generated links. */
|
|
47
|
+
title?: string;
|
|
48
|
+
/** Shallowest heading level to include. Defaults to 2. */
|
|
49
|
+
minDepth?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
50
|
+
/** Deepest heading level to include. Defaults to 6. */
|
|
51
|
+
maxDepth?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
52
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function atomicWriteFile(path: string, content: string): Promise<void>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { mkdir, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
export async function atomicWriteFile(path, content) {
|
|
5
|
+
await mkdir(dirname(path), { recursive: true });
|
|
6
|
+
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
7
|
+
await writeFile(temporary, content, "utf8");
|
|
8
|
+
await rename(temporary, path);
|
|
9
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function escapeHtml(value) {
|
|
2
|
+
return value
|
|
3
|
+
.replaceAll("&", "&")
|
|
4
|
+
.replaceAll("<", "<")
|
|
5
|
+
.replaceAll(">", ">")
|
|
6
|
+
.replaceAll('"', """)
|
|
7
|
+
.replaceAll("'", "'");
|
|
8
|
+
}
|
|
9
|
+
export function plainTextFromHtml(value) {
|
|
10
|
+
return value
|
|
11
|
+
.replace(/<[^>]*>/g, "")
|
|
12
|
+
.replaceAll("&", "&")
|
|
13
|
+
.replaceAll("<", "<")
|
|
14
|
+
.replaceAll(">", ">")
|
|
15
|
+
.replaceAll(""", '"')
|
|
16
|
+
.replaceAll("'", "'");
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function renderCopyIcon(): string;
|
|
2
|
+
export declare function renderAddIcon(): string;
|
|
3
|
+
export declare function renderWrapIcon(): string;
|
|
4
|
+
export declare function renderEditIcon(): string;
|
|
5
|
+
export declare function renderDeleteIcon(): string;
|
|
6
|
+
export declare function renderArchiveIcon(): string;
|
|
7
|
+
export declare function renderRestoreIcon(): string;
|
|
8
|
+
export declare function renderFolderIcon(): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function renderCopyIcon() {
|
|
2
|
+
return '<svg class="action-icon copy-icon" viewBox="0 0 16 16" aria-hidden="true"><rect x="6" y="6" width="7.5" height="7.5" rx="1"/><path d="M10 6V3.5a1 1 0 0 0-1-1H3.5a1 1 0 0 0-1 1V10a1 1 0 0 0 1 1H6"/></svg>';
|
|
3
|
+
}
|
|
4
|
+
export function renderAddIcon() {
|
|
5
|
+
return '<svg class="action-icon add-icon" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 3v10M3 8h10"/></svg>';
|
|
6
|
+
}
|
|
7
|
+
export function renderWrapIcon() {
|
|
8
|
+
return '<svg class="action-icon wrap-icon" viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 4h8a3 3 0 0 1 0 6H6"/><path d="m8 7.5-2.5 2.5L8 12.5"/></svg>';
|
|
9
|
+
}
|
|
10
|
+
export function renderEditIcon() {
|
|
11
|
+
return '<svg class="action-icon edit-icon" viewBox="0 0 16 16" aria-hidden="true"><path d="m10.5 2.5 3 3-7.75 7.75-3.5.5.5-3.5Z"/><path d="m9 4 3 3"/></svg>';
|
|
12
|
+
}
|
|
13
|
+
export function renderDeleteIcon() {
|
|
14
|
+
return '<svg class="action-icon delete-icon" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.5 5h9M6 2.5h4l.75 2.5h-5.5Z"/><path d="m4.5 5 .5 8.5h6L11.5 5M7 7.5v3.5m2-3.5v3.5"/></svg>';
|
|
15
|
+
}
|
|
16
|
+
export function renderArchiveIcon() {
|
|
17
|
+
return '<svg class="action-icon archive-icon" viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 3h11v3h-11zM3.5 6v7.5h9V6M6 9h4"/></svg>';
|
|
18
|
+
}
|
|
19
|
+
export function renderRestoreIcon() {
|
|
20
|
+
return '<svg class="action-icon restore-icon" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 5.5h7a3.5 3.5 0 1 1 0 7H7.5"/><path d="m5.5 3-2.5 2.5L5.5 8"/></svg>';
|
|
21
|
+
}
|
|
22
|
+
export function renderFolderIcon() {
|
|
23
|
+
return '<svg class="folder-icon" viewBox="0 0 16 16" aria-hidden="true"><path d="M1.75 5.5h12.5v6.75A1.25 1.25 0 0 1 13 13.5H3a1.25 1.25 0 0 1-1.25-1.25Z"/><path d="M1.75 5.5V3.75A1.25 1.25 0 0 1 3 2.5h2.25L7 4.25h6A1.25 1.25 0 0 1 14.25 5.5"/></svg>';
|
|
24
|
+
}
|