marksites 0.2.5 → 0.2.6
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 +4 -2
- package/dist/conversion/directory.js +15 -1
- package/dist/conversion/history.d.ts +4 -0
- package/dist/conversion/history.js +33 -0
- package/dist/conversion/rendering.d.ts +1 -1
- package/dist/conversion/rendering.js +1 -1
- package/dist/conversion/single-file.js +6 -1
- package/dist/conversion/types.d.ts +3 -0
- package/dist/features/annotations/index.js +2 -0
- package/dist/features/document-diff/index.d.ts +9 -0
- package/dist/features/document-diff/index.js +416 -0
- package/dist/features/file-tree/index.js +7 -6
- package/dist/features/header/index.d.ts +1 -0
- package/dist/features/header/index.js +3 -0
- package/dist/features/table-of-contents/index.js +16 -6
- package/dist/markdown-to-html.d.ts +1 -1
- package/dist/markdown-to-html.js +7 -1
- package/dist/server/html-security.js +3 -0
- package/dist/template/document.d.ts +1 -0
- package/dist/template/document.js +1 -0
- package/dist/template/styles.d.ts +1 -1
- package/dist/template/styles.js +6 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ const html = markdownToHtml(markdown, {
|
|
|
29
29
|
|
|
30
30
|
左ファイルサイドバーは幅280pxとし、開閉アイコンは枠線なしで表示します。更新日時はヘッダーのファイルパス右隣へ細い縦線で区切って表示し、狭い画面では省略します。
|
|
31
31
|
|
|
32
|
-
画面上端には固定ヘッダーを表示します。濃色背景と白文字の`marksites`ロゴ、その右に現在のファイルパスを表示します。ファイルパスから開くポップオーバーは左ファイルサイドバーを閉じている場合だけ利用できます。ブラウザのタブ名は`marksites |
|
|
32
|
+
画面上端には固定ヘッダーを表示します。濃色背景と白文字の`marksites`ロゴ、その右に現在のファイルパスを表示します。ファイルパスから開くポップオーバーは左ファイルサイドバーを閉じている場合だけ利用できます。ブラウザのタブ名は`marksites | <ファイル名>`形式です。右上では差分アイコンで前回正常ビルドからの変更と最新版を切り替え、その右の月・太陽アイコンでライト/ダークモード、地球アイコンで生成UIの日本語/英語を切り替えます。差分がなければ差分アイコンは非活性です。差分表示の希望状態は`document-view=diff`として保持し、差分のないページを経由してもHTMLページ間で引き継ぎます。Markdown本文、見出し、ファイル名、コメント本文は翻訳対象にしません。テーマと言語の選択状態も`theme`と`lang` GETパラメータでページ間に引き継がれます。
|
|
33
33
|
|
|
34
34
|
```ts
|
|
35
35
|
const html = markdownToHtml(markdown, {
|
|
@@ -93,7 +93,7 @@ npx marksites docs
|
|
|
93
93
|
|
|
94
94
|
入力フォルダ配下の`.gitignore`を読み込み、Gitと同じパターン規則で除外されたファイルとフォルダを変換対象から外します。ネストした`.gitignore`と`!`による再包含にも対応します。また、`.marksites-build.json`を持つ既存の生成フォルダと、今回指定された出力フォルダは常に探索対象外です。
|
|
95
95
|
|
|
96
|
-
2回目以降はMarkdown本文、コメント、ファイル構成、レンダー設定のSHA-256を比較し、影響したHTML
|
|
96
|
+
2回目以降はMarkdown本文、コメント、ファイル構成、レンダー設定のSHA-256を比較し、影響したHTMLだけを再生成します。前回正常ビルド時のMarkdownは出力先の`.marksites-history/`へ文書ごとに1世代だけ保持し、現在版とのブロック差分を生成HTMLへ埋め込みます。管理情報は出力先直下の `.marksites-build.json`、文書ごとのメタデータは各HTMLに隣接する `.<名前>.json` に保存されます。このJSONは再変換時にも上書きされません。現在はコメント情報を格納し、将来は文書固有のほかの情報も管理できるファイルとして扱います。
|
|
97
97
|
|
|
98
98
|
### コメントを編集する
|
|
99
99
|
|
|
@@ -101,6 +101,8 @@ npx marksites docs
|
|
|
101
101
|
|
|
102
102
|
本文で文字列を選択すると、コピー操作の隣に`文字列置換`が表示されます。選択文字列を変換元として画面中央の管理ポップアップへ置換後の文字列を入力すると、表示中の本文にある同じ文字列をすべてブラウザ上で一時的に置換でき、アクセストークンなどを当てはめてからコピーできます。本文カード右上の`置換`ボタンから選択なしで新しいルールを追加することもでき、複数ルールを一覧で管理して個別にクリアできます。ポップアップは閉じるアイコンまたは範囲外のクリックで閉じます。置換箇所は枠付きでハイライトされます。置換値自体は管理ボタンへ表示しません。元のMarkdownと生成HTMLは変更されず、ページを再読み込みしても元に戻ります。
|
|
103
103
|
|
|
104
|
+
ポップアップ表示中は背後が薄く暗くなります。一覧のルールを個別にクリアしてもポップアップは閉じないため、そのまま続けて管理できます。
|
|
105
|
+
|
|
104
106
|
右サイドバーでは目次と現在ページのコメント一覧をタブで切り替えられます。タブにはコメント件数が表示され、コメントを選ぶと本文の対象箇所へ移動します。一覧先頭の追加ボタンから、範囲を選択しない文書全体へのコメントも作成できます。ヘッダーは固定され、各パネルは独立してスクロールします。
|
|
105
107
|
|
|
106
108
|
```sh
|
|
@@ -9,6 +9,7 @@ import { buildBreadcrumbs, buildFileTree } from "./navigation.js";
|
|
|
9
9
|
import { DEFAULT_OUTPUT_DIRECTORY, findMarkdownFiles, firstExistingPath, pathExists, toLegacyMetadataPaths, } from "./paths.js";
|
|
10
10
|
import { GENERATOR_VERSION, OUTPUT_COMPATIBILITY_VERSION, contentHash, renderFingerprint, rewriteMarkdownLinks, } from "./rendering.js";
|
|
11
11
|
import { prepareImageAssets } from "./assets.js";
|
|
12
|
+
import { readHistory, removeHistory, toHistoryPath, writeHistory, } from "./history.js";
|
|
12
13
|
async function migrateLegacyMetadata(files, output) {
|
|
13
14
|
let moved = 0;
|
|
14
15
|
for (const file of files) {
|
|
@@ -35,6 +36,12 @@ async function loadSources(files) {
|
|
|
35
36
|
file.modifiedAt = sourceStat.mtime.toISOString();
|
|
36
37
|
}
|
|
37
38
|
}
|
|
39
|
+
async function loadHistories(files, previous, output) {
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
file.historyPath = toHistoryPath(file.relativePath);
|
|
42
|
+
file.previousSource = await readHistory(output, previous?.files[file.relativePath]?.history);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
38
45
|
async function prepareAssets(files, output) {
|
|
39
46
|
for (const file of files) {
|
|
40
47
|
const assets = await prepareImageAssets(file.source, file.sourcePath, file.outputPath, output);
|
|
@@ -78,6 +85,7 @@ async function moveRenamedDocuments(files, previous, output, removed) {
|
|
|
78
85
|
await rm(oldHtml);
|
|
79
86
|
deleted++;
|
|
80
87
|
}
|
|
88
|
+
await removeHistory(output, oldData.history);
|
|
81
89
|
warnAboutStaleLinks(files, old);
|
|
82
90
|
removed.splice(removed.indexOf(old), 1);
|
|
83
91
|
}
|
|
@@ -118,6 +126,7 @@ async function removeDeletedHtml(removed, previous, output) {
|
|
|
118
126
|
if (await pathExists(join(output, ...info.annotations.split("/")))) {
|
|
119
127
|
orphaned.push(info.annotations);
|
|
120
128
|
}
|
|
129
|
+
await removeHistory(output, info.history);
|
|
121
130
|
}
|
|
122
131
|
return { deleted, orphaned };
|
|
123
132
|
}
|
|
@@ -150,7 +159,7 @@ async function renderChangedFiles(files, input, output, previous, full, options)
|
|
|
150
159
|
file.rewriteImages?.(token);
|
|
151
160
|
},
|
|
152
161
|
},
|
|
153
|
-
}, file.annotations));
|
|
162
|
+
}, file.annotations, file.previousSource));
|
|
154
163
|
converted++;
|
|
155
164
|
options.onLog?.(`Converted ${file.relativePath} -> ${file.outputPath}`);
|
|
156
165
|
}
|
|
@@ -166,6 +175,7 @@ async function renderChangedFiles(files, input, output, previous, full, options)
|
|
|
166
175
|
annotations: file.metadataPath,
|
|
167
176
|
assetHash: file.assetHash,
|
|
168
177
|
assets: file.assetOutputs,
|
|
178
|
+
history: file.historyPath,
|
|
169
179
|
};
|
|
170
180
|
}
|
|
171
181
|
return { converted, skipped, files: manifestFiles };
|
|
@@ -190,6 +200,7 @@ export async function convertDirectoryDetailed(input, outputArgument, options =
|
|
|
190
200
|
console.warn(loaded.warning);
|
|
191
201
|
const previous = loaded.manifest;
|
|
192
202
|
await loadSources(files);
|
|
203
|
+
await loadHistories(files, previous, output);
|
|
193
204
|
await prepareAssets(files, output);
|
|
194
205
|
let annotationsMoved = await migrateLegacyMetadata(files, output);
|
|
195
206
|
const currentPaths = new Set(files.map((file) => file.relativePath));
|
|
@@ -211,6 +222,9 @@ export async function convertDirectoryDetailed(input, outputArgument, options =
|
|
|
211
222
|
previous.treeHash !== treeHash;
|
|
212
223
|
const removedResult = await removeDeletedHtml(removed, previous, output);
|
|
213
224
|
const rendered = await renderChangedFiles(files, input, output, previous, full, options);
|
|
225
|
+
for (const file of files) {
|
|
226
|
+
await writeHistory(output, file.historyPath, file.source);
|
|
227
|
+
}
|
|
214
228
|
await removeUnusedAssets(previous, files, output);
|
|
215
229
|
await writeManifest(manifestPath, {
|
|
216
230
|
schemaVersion: 1,
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function toHistoryPath(documentPath: string): string;
|
|
2
|
+
export declare function readHistory(outputRoot: string, historyPath: string | undefined): Promise<string | undefined>;
|
|
3
|
+
export declare function writeHistory(outputRoot: string, historyPath: string, markdown: string): Promise<void>;
|
|
4
|
+
export declare function removeHistory(outputRoot: string, historyPath: string | undefined): Promise<void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile, rm } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { atomicWriteFile } from "../utils/files.js";
|
|
5
|
+
const HISTORY_DIRECTORY = ".marksites-history";
|
|
6
|
+
const HISTORY_PATH = /^\.marksites-history\/[a-f0-9]{64}\.md$/;
|
|
7
|
+
function isHistoryPath(path) {
|
|
8
|
+
return path !== undefined && HISTORY_PATH.test(path);
|
|
9
|
+
}
|
|
10
|
+
export function toHistoryPath(documentPath) {
|
|
11
|
+
const id = createHash("sha256").update(documentPath).digest("hex");
|
|
12
|
+
return `${HISTORY_DIRECTORY}/${id}.md`;
|
|
13
|
+
}
|
|
14
|
+
export async function readHistory(outputRoot, historyPath) {
|
|
15
|
+
if (!isHistoryPath(historyPath))
|
|
16
|
+
return undefined;
|
|
17
|
+
try {
|
|
18
|
+
return await readFile(join(outputRoot, ...historyPath.split("/")), "utf8");
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (error.code === "ENOENT")
|
|
22
|
+
return undefined;
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function writeHistory(outputRoot, historyPath, markdown) {
|
|
27
|
+
await atomicWriteFile(join(outputRoot, ...historyPath.split("/")), markdown);
|
|
28
|
+
}
|
|
29
|
+
export async function removeHistory(outputRoot, historyPath) {
|
|
30
|
+
if (!isHistoryPath(historyPath))
|
|
31
|
+
return;
|
|
32
|
+
await rm(join(outputRoot, ...historyPath.split("/")), { force: true });
|
|
33
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Token } from "marked";
|
|
2
2
|
export declare const GENERATOR_VERSION: string;
|
|
3
|
-
export declare const OUTPUT_COMPATIBILITY_VERSION =
|
|
3
|
+
export declare const OUTPUT_COMPATIBILITY_VERSION = 8;
|
|
4
4
|
export declare function contentHash(value: string | Buffer): string;
|
|
5
5
|
export declare function rewriteMarkdownLinks(token: Token): void;
|
|
6
6
|
export declare function renderFingerprint(): string;
|
|
@@ -4,7 +4,7 @@ import { emptyAnnotationDocument } from "../annotations/model.js";
|
|
|
4
4
|
import { renderMarkdown } from "../markdown-to-html.js";
|
|
5
5
|
const packageMetadata = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
6
6
|
export const GENERATOR_VERSION = packageMetadata.version;
|
|
7
|
-
export const OUTPUT_COMPATIBILITY_VERSION =
|
|
7
|
+
export const OUTPUT_COMPATIBILITY_VERSION = 8;
|
|
8
8
|
export function contentHash(value) {
|
|
9
9
|
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
10
10
|
}
|
|
@@ -5,6 +5,7 @@ import { renderMarkdown } from "../markdown-to-html.js";
|
|
|
5
5
|
import { atomicWriteFile } from "../utils/files.js";
|
|
6
6
|
import { DEFAULT_OUTPUT_DIRECTORY, firstExistingPath, isMarkdown, pathExists, } from "./paths.js";
|
|
7
7
|
import { prepareImageAssets } from "./assets.js";
|
|
8
|
+
import { readHistory, toHistoryPath, writeHistory } from "./history.js";
|
|
8
9
|
export async function convertFile(input, outputArgument) {
|
|
9
10
|
if (!isMarkdown(input))
|
|
10
11
|
throw new Error(`Input file must use .md or .markdown: ${input}`);
|
|
@@ -29,12 +30,16 @@ export async function convertFile(input, outputArgument) {
|
|
|
29
30
|
]);
|
|
30
31
|
await mkdir(dirname(output), { recursive: true });
|
|
31
32
|
const assets = await prepareImageAssets(markdown, input, basename(output), dirname(output));
|
|
33
|
+
const outputRoot = dirname(output);
|
|
34
|
+
const historyPath = toHistoryPath(basename(output));
|
|
35
|
+
const previousSource = await readHistory(outputRoot, historyPath);
|
|
32
36
|
await atomicWriteFile(output, renderMarkdown(markdown, {
|
|
33
37
|
title: basename(input, extname(input)),
|
|
34
38
|
modifiedAt: sourceStat.mtime.toISOString(),
|
|
35
39
|
markedOptions: {
|
|
36
40
|
walkTokens: assets.rewrite,
|
|
37
41
|
},
|
|
38
|
-
}, annotations));
|
|
42
|
+
}, annotations, previousSource));
|
|
43
|
+
await writeHistory(outputRoot, historyPath, markdown);
|
|
39
44
|
return output;
|
|
40
45
|
}
|
|
@@ -12,6 +12,8 @@ export interface MarkdownFile {
|
|
|
12
12
|
assetHash?: string;
|
|
13
13
|
assetOutputs?: string[];
|
|
14
14
|
rewriteImages?: (token: import("marked").Token) => void;
|
|
15
|
+
previousSource?: string;
|
|
16
|
+
historyPath?: string;
|
|
15
17
|
}
|
|
16
18
|
export interface ManifestFile {
|
|
17
19
|
sourceHash: string;
|
|
@@ -21,6 +23,7 @@ export interface ManifestFile {
|
|
|
21
23
|
annotations: string;
|
|
22
24
|
assetHash?: string;
|
|
23
25
|
assets?: string[];
|
|
26
|
+
history?: string;
|
|
24
27
|
}
|
|
25
28
|
export interface BuildManifest {
|
|
26
29
|
schemaVersion: 1;
|
|
@@ -39,6 +39,7 @@ export function createAnnotationsFeature(data) {
|
|
|
39
39
|
.selection-actions button:hover:not(:disabled){background:#57606a}.selection-actions button:disabled{color:#8c959f;cursor:not-allowed}
|
|
40
40
|
.selection-actions [data-tooltip],.annotations-panel [data-tooltip]{position:relative}.selection-actions [data-tooltip]::after,.annotations-panel [data-tooltip]::after{position:absolute;z-index:30;top:calc(100% + 6px);right:0;width:max-content;max-width:220px;padding:5px 7px;color:#fff;font-size:.6875rem;font-weight:400;line-height:1.3;white-space:normal;background:#24292f;border-radius:4px;box-shadow:0 3px 10px rgba(31,35,40,.2);content:attr(data-tooltip);opacity:0;pointer-events:none;transform:translateY(-2px);transition:opacity 120ms ease,transform 120ms ease}.selection-actions [data-tooltip]:hover::after,.selection-actions [data-tooltip]:focus-visible::after,.annotations-panel [data-tooltip]:hover::after,.annotations-panel [data-tooltip]:focus-visible::after{opacity:1;transform:translateY(0)}
|
|
41
41
|
.markdown-content{position:relative}.document-replacement-menu{position:absolute;top:12px;right:12px;display:inline-flex;height:32px;align-items:center;justify-content:center;gap:6px;padding:0 9px;color:var(--fgColor-muted,#59636e);font:inherit;font-size:.75rem;font-weight:600;background:var(--button-default-bgColor-rest,#f6f8fa);border:1px solid var(--borderColor-default,#d0d7de);border-radius:6px;cursor:pointer}.document-replacement-menu:hover{color:var(--fgColor-default,#1f2328);background:var(--button-default-bgColor-hover,#eaeef2)}.document-replacement-menu:focus-visible{outline:2px solid var(--focus-outlineColor,#0969da);outline-offset:2px}.document-replacement-menu svg{width:16px;height:16px;flex:none;fill:none;stroke:currentColor;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:round}.document-replacement-menu>span:not(.text-replacement-count){line-height:16px}.text-replace-alert{position:fixed;z-index:45;top:50%;left:50%;box-sizing:border-box;display:grid;width:min(440px,calc(100vw - 24px));max-height:calc(100vh - 24px);grid-template-columns:1fr 1fr;gap:10px;padding:12px;overflow-y:auto;color:var(--fgColor-default,#1f2328);background:var(--bgColor-default,#fff);border:1px solid var(--borderColor-default,#d0d7de);border-radius:6px;box-shadow:0 8px 24px rgba(31,35,40,.2);transform:translate(-50%,-50%)}.text-replace-alert-heading,.text-replacement-list{grid-column:1/-1}.text-replace-alert-heading{display:flex;align-items:center;justify-content:space-between}.text-replace-alert label{display:block;min-width:0}.text-replace-alert label span{display:block;margin-bottom:5px;color:var(--fgColor-muted,#59636e);font-size:.75rem;font-weight:600}.text-replace-alert input{box-sizing:border-box;width:100%;height:34px;padding:5px 8px;color:var(--fgColor-default,#1f2328);font:inherit;background:var(--bgColor-default,#fff);border:1px solid var(--borderColor-default,#d0d7de);border-radius:6px}.text-replace-alert input:focus{border-color:var(--borderColor-accent-emphasis,#0969da);outline:2px solid var(--bgColor-accent-muted,#ddf4ff)}.text-replace-error{grid-column:1/-1;margin:0;color:var(--fgColor-danger,#d1242f);font-size:.75rem}.text-replace-error[hidden]{display:none}.text-replace-alert-actions{display:flex;grid-column:1/-1;justify-content:flex-end;gap:6px}.text-replace-alert button{height:34px;padding:0 10px;color:var(--fgColor-default,#1f2328);font:inherit;font-size:.8125rem;font-weight:600;background:var(--button-default-bgColor-rest,#f6f8fa);border:1px solid var(--borderColor-default,#d0d7de);border-radius:6px;cursor:pointer}.text-replace-alert button[type=submit]{color:#fff;background:var(--button-primary-bgColor-rest,#1f883d);border-color:transparent}.text-replace-alert .text-replace-alert-heading button{display:inline-flex;width:28px;height:28px;align-items:center;justify-content:center;padding:0;color:var(--fgColor-muted,#59636e);background:transparent;border:0;border-radius:4px}.text-replace-alert .text-replace-alert-heading button:hover{color:var(--fgColor-default,#1f2328);background:var(--button-default-bgColor-hover,#eaeef2)}.text-replace-alert .text-replace-alert-heading button:focus-visible{color:var(--fgColor-default,#1f2328);background:var(--button-default-bgColor-hover,#eaeef2);outline:2px solid var(--focus-outlineColor,#0969da);outline-offset:1px}.text-replace-alert-heading button svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round}.text-replacement-list:empty{display:none}.text-replacement-rule{display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr) auto;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid var(--borderColor-muted,#d8dee4);font-size:.75rem}.text-replacement-rule code{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-replacement-rule button{height:28px;padding:0 8px}.text-replacement{padding:1px 2px;background:var(--bgColor-accent-muted,#ddf4ff);border:1px solid var(--borderColor-accent-emphasis,#0969da);border-radius:3px;box-decoration-break:clone;-webkit-box-decoration-break:clone}.text-replacement-count{display:inline-flex;min-width:16px;height:16px;align-items:center;justify-content:center;margin-left:3px;padding:0 3px;color:#fff;font-size:.6875rem;line-height:16px;background:var(--fgColor-accent,#0969da);border-radius:999px}
|
|
42
|
+
.text-replace-alert:not([hidden]){box-shadow:0 0 0 100vmax rgba(31,35,40,.18),0 8px 24px rgba(31,35,40,.2)}
|
|
42
43
|
.annotations-panel{color:var(--fgColor-default,#1f2328)}
|
|
43
44
|
.annotations-actions{position:sticky;z-index:1;top:-12px;display:flex;gap:8px;margin:-12px -12px 0;padding:10px 12px;background:var(--bgColor-default,#fff);border-bottom:1px solid var(--borderColor-muted,#d8dee4)}.annotations-panel button{padding:6px 10px;color:var(--fgColor-default,#1f2328);font:inherit;font-size:.8125rem;font-weight:600;background:var(--button-default-bgColor-rest,#f6f8fa);border:1px solid var(--borderColor-default,#d0d7de);border-radius:6px;cursor:pointer}.annotations-panel button:hover:not(:disabled){background:var(--button-default-bgColor-hover,#eaeef2)}.annotations-panel button:focus-visible{outline:2px solid var(--focus-outlineColor,#0969da);outline-offset:2px}.annotations-panel button:disabled{color:var(--fgColor-muted,#818b98);cursor:not-allowed}.annotations-actions button{display:inline-flex;min-width:0;flex:1;align-items:center;justify-content:center;gap:5px}.annotations-panel .annotation-action-add:not(:disabled){color:#fff;background:var(--button-primary-bgColor-rest,#1f883d);border-color:var(--button-primary-borderColor-rest,#1f232826)}.annotations-panel .annotation-action-add:hover:not(:disabled){background:var(--button-primary-bgColor-hover,#1a7f37)}.annotations-panel .annotation-action-copy{color:var(--fgColor-muted,#59636e);background:transparent;border-color:var(--borderColor-muted,#d8dee4)}
|
|
44
45
|
.annotations-empty{margin:0;padding:28px 12px 20px;color:var(--fgColor-muted,#59636e);font-size:.8125rem;line-height:1.6;text-align:center}
|
|
@@ -72,6 +73,7 @@ function closeReplacementMenu(){replaceAlert.hidden=true;replacementMenu.setAttr
|
|
|
72
73
|
function replaceText(search,replacement){const id=++replacementSequence,walker=document.createTreeWalker(content,NodeFilter.SHOW_TEXT,{acceptNode:node=>node.parentElement?.closest(excluded+',.text-replacement')?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}),nodes=[];let node,total=0;while(node=walker.nextNode())nodes.push(node);for(const text of nodes){const parts=text.data.split(search);if(parts.length===1)continue;const fragment=document.createDocumentFragment();parts.forEach((part,index)=>{fragment.append(document.createTextNode(part));if(index<parts.length-1){const mark=document.createElement('span');mark.className='text-replacement';mark.dataset.replacementId=String(id);mark.textContent=replacement;fragment.append(mark);total++}});text.replaceWith(fragment)}if(total)replacementRules.push({id,search,replacement,total});renderReplacementRules()}
|
|
73
74
|
document.addEventListener('selectionchange',()=>{clearTimeout(window.__marksitesSelectionTimer);window.__marksitesSelectionTimer=setTimeout(()=>{const next=selectionData();if(!next){toolbar.hidden=true;return}pendingSelection=next;const r=getSelection().getRangeAt(0).getBoundingClientRect();toolbar.style.left=Math.max(8,Math.min(innerWidth-toolbar.offsetWidth-8,r.left))+'px';toolbar.style.top=Math.max(8,r.top-44)+'px';toolbar.hidden=false},80)});
|
|
74
75
|
toolbar.addEventListener('mousedown',e=>e.preventDefault());toolbar.addEventListener('click',async e=>{const button=e.target.closest('button');if(!button||!pendingSelection)return;const label=button.querySelector('[data-copy-label]');try{if(button.dataset.selectionAction==='copy')await copy(pendingSelection.exact);if(button.dataset.selectionAction==='replace'){openReplacementMenu(pendingSelection.exact);toolbar.hidden=true}if(button.dataset.selectionAction==='ai')await copy('文書: '+state.document+'\\n見出し: '+(pendingSelection.headingId||'なし')+'\\n\\n> '+pendingSelection.exact.replace(/\\n/g,'\\n> '));if(button.dataset.selectionAction==='comment'&&editable){editingId=null;dispatchEvent(new Event('marksites:show-comments'));openForm()}if(label){label.textContent='コピーしました';setTimeout(()=>label.textContent=button.dataset.selectionAction==='ai'?'AI向けコピー':'コピー',1000)}}catch{if(label)label.textContent='コピー失敗'}});
|
|
76
|
+
replacementList.addEventListener('click',e=>{if(e.target.closest('[data-clear-replacement]'))e.stopPropagation()});
|
|
75
77
|
replaceAlert.addEventListener('submit',e=>{e.preventDefault();const search=replaceAlert.search.value,replacement=replaceAlert.replacement.value;if(!search){setReplacementError('置換する文字列を入力してください。');replaceAlert.search.focus();return}if(!replacement){setReplacementError('置換後の文字列を入力してください。');replaceAlert.replacement.focus();return}const before=replacementRules.length;replaceText(search,replacement);if(replacementRules.length===before){setReplacementError('置換する文字列が本文内に見つかりません。');replaceAlert.search.focus();return}setReplacementError();replaceAlert.search.value='';replaceAlert.replacement.value='';replaceAlert.search.focus();getSelection()?.removeAllRanges()});replaceAlert.addEventListener('input',()=>setReplacementError());replaceAlert.querySelectorAll('[data-cancel-replace]').forEach(button=>button.addEventListener('click',closeReplacementMenu));replacementMenu.addEventListener('click',()=>{if(replaceAlert.hidden)openReplacementMenu();else closeReplacementMenu()});replacementList.addEventListener('click',e=>{const button=e.target.closest('[data-clear-replacement]');if(!button)return;const id=Number(button.dataset.clearReplacement),rule=replacementRules.find(item=>item.id===id);if(!rule)return;for(const mark of content.querySelectorAll('.text-replacement[data-replacement-id="'+id+'"]'))mark.replaceWith(document.createTextNode(rule.search));replacementRules=replacementRules.filter(item=>item.id!==id);renderReplacementRules()});
|
|
76
78
|
document.addEventListener('click',e=>{if(replaceAlert.hidden||replaceAlert.contains(e.target)||replacementMenu.contains(e.target)||e.target.closest('[data-selection-action=replace]'))return;closeReplacementMenu()});
|
|
77
79
|
addDocumentComment.addEventListener('click',()=>{if(!editable)return;editingId=null;pendingSelection={exact:'',prefix:'',suffix:'',headingId:null,startOffset:0,endOffset:0};openForm()});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type MarkedOptions } from "marked";
|
|
2
|
+
export interface DocumentDiffFeature {
|
|
3
|
+
content: string;
|
|
4
|
+
control: string;
|
|
5
|
+
styles: string;
|
|
6
|
+
script: string;
|
|
7
|
+
hasChanges: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function createDocumentDiffFeature(current: string, previous: string | undefined, markedOptions?: Omit<MarkedOptions, "async" | "renderer">): DocumentDiffFeature;
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import GithubSlugger from "github-slugger";
|
|
2
|
+
import { marked, Renderer } from "marked";
|
|
3
|
+
import { escapeHtml, plainTextFromHtml } from "../../utils/html.js";
|
|
4
|
+
function tokenRaw(token) {
|
|
5
|
+
return "raw" in token && typeof token.raw === "string" ? token.raw : "";
|
|
6
|
+
}
|
|
7
|
+
function diffBlocks(previous, current) {
|
|
8
|
+
const blocks = (markdown) => marked
|
|
9
|
+
.lexer(markdown)
|
|
10
|
+
.filter((token) => token.type !== "space")
|
|
11
|
+
.map((token) => ({
|
|
12
|
+
raw: tokenRaw(token),
|
|
13
|
+
key: tokenRaw(token).replace(/[ \t]+$/gm, "").trim(),
|
|
14
|
+
tokenType: token.type,
|
|
15
|
+
}))
|
|
16
|
+
.filter(({ raw }) => Boolean(raw));
|
|
17
|
+
const oldBlocks = blocks(previous);
|
|
18
|
+
const newBlocks = blocks(current);
|
|
19
|
+
if (oldBlocks.length * newBlocks.length > 2_000_000) {
|
|
20
|
+
return [
|
|
21
|
+
...oldBlocks.map(({ raw, tokenType }) => ({ kind: "delete", raw, tokenType })),
|
|
22
|
+
...newBlocks.map(({ raw, tokenType }) => ({ kind: "insert", raw, tokenType })),
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
const lengths = Array.from({ length: oldBlocks.length + 1 }, () => new Uint32Array(newBlocks.length + 1));
|
|
26
|
+
for (let oldIndex = oldBlocks.length - 1; oldIndex >= 0; oldIndex--) {
|
|
27
|
+
for (let newIndex = newBlocks.length - 1; newIndex >= 0; newIndex--) {
|
|
28
|
+
lengths[oldIndex][newIndex] =
|
|
29
|
+
oldBlocks[oldIndex].key === newBlocks[newIndex].key
|
|
30
|
+
? lengths[oldIndex + 1][newIndex + 1] + 1
|
|
31
|
+
: Math.max(lengths[oldIndex + 1][newIndex], lengths[oldIndex][newIndex + 1]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const parts = [];
|
|
35
|
+
let oldIndex = 0;
|
|
36
|
+
let newIndex = 0;
|
|
37
|
+
while (oldIndex < oldBlocks.length || newIndex < newBlocks.length) {
|
|
38
|
+
if (oldIndex < oldBlocks.length &&
|
|
39
|
+
newIndex < newBlocks.length &&
|
|
40
|
+
oldBlocks[oldIndex].key === newBlocks[newIndex].key) {
|
|
41
|
+
parts.push({ kind: "same", raw: newBlocks[newIndex].raw, tokenType: newBlocks[newIndex].tokenType });
|
|
42
|
+
oldIndex++;
|
|
43
|
+
newIndex++;
|
|
44
|
+
}
|
|
45
|
+
else if (newIndex < newBlocks.length &&
|
|
46
|
+
(oldIndex === oldBlocks.length ||
|
|
47
|
+
lengths[oldIndex][newIndex + 1] >
|
|
48
|
+
lengths[oldIndex + 1][newIndex])) {
|
|
49
|
+
const block = newBlocks[newIndex++];
|
|
50
|
+
parts.push({ kind: "insert", raw: block.raw, tokenType: block.tokenType });
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const block = oldBlocks[oldIndex++];
|
|
54
|
+
parts.push({ kind: "delete", raw: block.raw, tokenType: block.tokenType });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return parts;
|
|
58
|
+
}
|
|
59
|
+
function prefixIds(html) {
|
|
60
|
+
return html
|
|
61
|
+
.replace(/\bid="([^"]+)"/g, 'id="diff-$1"')
|
|
62
|
+
.replace(/\bhref="#([^"]+)"/g, 'href="#diff-$1"');
|
|
63
|
+
}
|
|
64
|
+
function wordDiff(previous, current) {
|
|
65
|
+
const segmenter = new Intl.Segmenter("ja", { granularity: "word" });
|
|
66
|
+
const oldParts = [...segmenter.segment(previous)].map(({ segment }) => segment);
|
|
67
|
+
const newParts = [...segmenter.segment(current)].map(({ segment }) => segment);
|
|
68
|
+
if (oldParts.length * newParts.length > 100_000)
|
|
69
|
+
return `<del class="document-diff-inline-delete">${escapeHtml(previous)}</del><ins class="document-diff-inline-insert">${escapeHtml(current)}</ins>`;
|
|
70
|
+
const lengths = Array.from({ length: oldParts.length + 1 }, () => new Uint32Array(newParts.length + 1));
|
|
71
|
+
for (let oldIndex = oldParts.length - 1; oldIndex >= 0; oldIndex--) {
|
|
72
|
+
for (let newIndex = newParts.length - 1; newIndex >= 0; newIndex--) {
|
|
73
|
+
lengths[oldIndex][newIndex] =
|
|
74
|
+
oldParts[oldIndex] === newParts[newIndex]
|
|
75
|
+
? lengths[oldIndex + 1][newIndex + 1] + 1
|
|
76
|
+
: Math.max(lengths[oldIndex + 1][newIndex], lengths[oldIndex][newIndex + 1]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const output = [];
|
|
80
|
+
let oldIndex = 0;
|
|
81
|
+
let newIndex = 0;
|
|
82
|
+
let kind;
|
|
83
|
+
let buffer = "";
|
|
84
|
+
const flush = () => {
|
|
85
|
+
if (!buffer)
|
|
86
|
+
return;
|
|
87
|
+
output.push(kind === "delete"
|
|
88
|
+
? `<del class="document-diff-inline-delete">${buffer}</del>`
|
|
89
|
+
: kind === "insert"
|
|
90
|
+
? `<ins class="document-diff-inline-insert">${buffer}</ins>`
|
|
91
|
+
: buffer);
|
|
92
|
+
buffer = "";
|
|
93
|
+
};
|
|
94
|
+
const append = (nextKind, value) => {
|
|
95
|
+
if (kind !== nextKind)
|
|
96
|
+
flush();
|
|
97
|
+
kind = nextKind;
|
|
98
|
+
buffer += escapeHtml(value);
|
|
99
|
+
};
|
|
100
|
+
while (oldIndex < oldParts.length || newIndex < newParts.length) {
|
|
101
|
+
if (oldIndex < oldParts.length &&
|
|
102
|
+
newIndex < newParts.length &&
|
|
103
|
+
oldParts[oldIndex] === newParts[newIndex]) {
|
|
104
|
+
append("same", newParts[newIndex]);
|
|
105
|
+
oldIndex++;
|
|
106
|
+
newIndex++;
|
|
107
|
+
}
|
|
108
|
+
else if (newIndex < newParts.length &&
|
|
109
|
+
(oldIndex === oldParts.length ||
|
|
110
|
+
lengths[oldIndex][newIndex + 1] >
|
|
111
|
+
lengths[oldIndex + 1][newIndex])) {
|
|
112
|
+
append("insert", newParts[newIndex++]);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
append("delete", oldParts[oldIndex++]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
flush();
|
|
119
|
+
return output.join("");
|
|
120
|
+
}
|
|
121
|
+
function oneToken(raw) {
|
|
122
|
+
const tokens = marked.lexer(raw).filter((token) => token.type !== "space");
|
|
123
|
+
return tokens.length === 1 ? tokens[0] : undefined;
|
|
124
|
+
}
|
|
125
|
+
function inlineTokens(markdown) {
|
|
126
|
+
return marked.Lexer.lexInline(markdown);
|
|
127
|
+
}
|
|
128
|
+
function renderInlineToken(token, deleted = false) {
|
|
129
|
+
if (token.type === "link") {
|
|
130
|
+
const href = escapeHtml(String(token.href ?? ""));
|
|
131
|
+
const title = token.title
|
|
132
|
+
? ` title="${escapeHtml(String(token.title))}"`
|
|
133
|
+
: "";
|
|
134
|
+
const disabled = deleted
|
|
135
|
+
? ' class="document-diff-link-delete" aria-disabled="true" tabindex="-1"'
|
|
136
|
+
: "";
|
|
137
|
+
return `<a href="${href}"${title}${disabled}>${marked.parseInline(String(token.text ?? ""), { async: false })}</a>`;
|
|
138
|
+
}
|
|
139
|
+
return marked.parseInline(String(token.raw ?? ""), { async: false });
|
|
140
|
+
}
|
|
141
|
+
function inlineTokenDiff(previous, current) {
|
|
142
|
+
const oldTokens = inlineTokens(previous);
|
|
143
|
+
const newTokens = inlineTokens(current);
|
|
144
|
+
const output = [];
|
|
145
|
+
for (let index = 0; index < Math.max(oldTokens.length, newTokens.length); index++) {
|
|
146
|
+
const oldToken = oldTokens[index];
|
|
147
|
+
const newToken = newTokens[index];
|
|
148
|
+
if (!oldToken && newToken) {
|
|
149
|
+
output.push(`<ins class="document-diff-inline-insert">${renderInlineToken(newToken)}</ins>`);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (oldToken && !newToken) {
|
|
153
|
+
output.push(`<del class="document-diff-inline-delete">${renderInlineToken(oldToken, true)}</del>`);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (!oldToken || !newToken)
|
|
157
|
+
continue;
|
|
158
|
+
if (oldToken.type === "text" && newToken.type === "text") {
|
|
159
|
+
output.push(wordDiff(String(oldToken.text), String(newToken.text)));
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (oldToken.type === newToken.type && ["strong", "em", "del"].includes(newToken.type)) {
|
|
163
|
+
const tag = newToken.type === "strong" ? "strong" : newToken.type;
|
|
164
|
+
output.push(`<${tag}>${inlineTokenDiff(String(oldToken.text), String(newToken.text))}</${tag}>`);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (oldToken.type === "link" && newToken.type === "link") {
|
|
168
|
+
const oldHref = String(oldToken.href ?? "");
|
|
169
|
+
const newHref = String(newToken.href ?? "");
|
|
170
|
+
const title = newToken.title
|
|
171
|
+
? ` title="${escapeHtml(String(newToken.title))}"`
|
|
172
|
+
: "";
|
|
173
|
+
output.push(`<a href="${escapeHtml(newHref)}"${title}>${inlineTokenDiff(String(oldToken.text ?? ""), String(newToken.text ?? ""))}</a>`);
|
|
174
|
+
if (oldHref !== newHref || oldToken.title !== newToken.title) {
|
|
175
|
+
output.push(`<span class="document-diff-link-target" aria-label="リンク先の変更"> (<del class="document-diff-inline-delete">${escapeHtml(oldHref)}</del><span aria-hidden="true"> → </span><ins class="document-diff-inline-insert">${escapeHtml(newHref)}</ins>)</span>`);
|
|
176
|
+
}
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (oldToken.raw === newToken.raw) {
|
|
180
|
+
output.push(renderInlineToken(newToken));
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
output.push(`<del class="document-diff-inline-delete">${renderInlineToken(oldToken, true)}</del><ins class="document-diff-inline-insert">${renderInlineToken(newToken)}</ins>`);
|
|
184
|
+
}
|
|
185
|
+
return output.join("");
|
|
186
|
+
}
|
|
187
|
+
function paragraphDiff(previous, current) {
|
|
188
|
+
const oldToken = oneToken(previous);
|
|
189
|
+
const newToken = oneToken(current);
|
|
190
|
+
if (oldToken?.type !== "paragraph" || newToken?.type !== "paragraph")
|
|
191
|
+
return undefined;
|
|
192
|
+
return `<p>${inlineTokenDiff(String(oldToken.text), String(newToken.text))}</p>\n`;
|
|
193
|
+
}
|
|
194
|
+
function codeLineDiff(previous, current) {
|
|
195
|
+
const lengths = Array.from({ length: previous.length + 1 }, () => new Uint32Array(current.length + 1));
|
|
196
|
+
for (let oldIndex = previous.length - 1; oldIndex >= 0; oldIndex--) {
|
|
197
|
+
for (let newIndex = current.length - 1; newIndex >= 0; newIndex--) {
|
|
198
|
+
lengths[oldIndex][newIndex] =
|
|
199
|
+
previous[oldIndex] === current[newIndex]
|
|
200
|
+
? lengths[oldIndex + 1][newIndex + 1] + 1
|
|
201
|
+
: Math.max(lengths[oldIndex + 1][newIndex], lengths[oldIndex][newIndex + 1]);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const rendered = [];
|
|
205
|
+
let oldIndex = 0;
|
|
206
|
+
let newIndex = 0;
|
|
207
|
+
while (oldIndex < previous.length || newIndex < current.length) {
|
|
208
|
+
if (previous[oldIndex] === current[newIndex]) {
|
|
209
|
+
rendered.push(`<span>${escapeHtml(current[newIndex] ?? "")}</span>`);
|
|
210
|
+
oldIndex++;
|
|
211
|
+
newIndex++;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const deleted = [];
|
|
215
|
+
const inserted = [];
|
|
216
|
+
while (oldIndex < previous.length ||
|
|
217
|
+
newIndex < current.length) {
|
|
218
|
+
if (previous[oldIndex] === current[newIndex])
|
|
219
|
+
break;
|
|
220
|
+
if (newIndex < current.length &&
|
|
221
|
+
(oldIndex === previous.length ||
|
|
222
|
+
lengths[oldIndex][newIndex + 1] >
|
|
223
|
+
lengths[oldIndex + 1][newIndex])) {
|
|
224
|
+
inserted.push(current[newIndex++]);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
deleted.push(previous[oldIndex++]);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (deleted.length === 1 && inserted.length > 1) {
|
|
231
|
+
const source = new Set([...new Intl.Segmenter("ja", { granularity: "word" }).segment(deleted[0])].map(({ segment }) => segment));
|
|
232
|
+
let bestIndex = 0;
|
|
233
|
+
let bestScore = -1;
|
|
234
|
+
inserted.forEach((line, index) => {
|
|
235
|
+
const score = [...new Intl.Segmenter("ja", { granularity: "word" }).segment(line)]
|
|
236
|
+
.filter(({ segment }) => source.has(segment)).length;
|
|
237
|
+
if (score > bestScore) {
|
|
238
|
+
bestIndex = index;
|
|
239
|
+
bestScore = score;
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
inserted.forEach((line, index) => {
|
|
243
|
+
rendered.push(index === bestIndex
|
|
244
|
+
? `<span>${wordDiff(deleted[0], line)}</span>`
|
|
245
|
+
: `<span class="document-diff-structural-insert">${escapeHtml(line)}</span>`);
|
|
246
|
+
});
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
for (let index = 0; index < Math.max(deleted.length, inserted.length); index++) {
|
|
250
|
+
const oldLine = deleted[index];
|
|
251
|
+
const newLine = inserted[index];
|
|
252
|
+
if (oldLine !== undefined && newLine !== undefined)
|
|
253
|
+
rendered.push(`<span>${wordDiff(oldLine, newLine)}</span>`);
|
|
254
|
+
else if (oldLine !== undefined)
|
|
255
|
+
rendered.push(`<span class="document-diff-structural-delete">${escapeHtml(oldLine)}</span>`);
|
|
256
|
+
else
|
|
257
|
+
rendered.push(`<span class="document-diff-structural-insert">${escapeHtml(newLine ?? "")}</span>`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return rendered;
|
|
261
|
+
}
|
|
262
|
+
function structuredDiff(previous, current, slugger) {
|
|
263
|
+
if (previous.tokenType !== current.tokenType)
|
|
264
|
+
return undefined;
|
|
265
|
+
const oldToken = oneToken(previous.raw);
|
|
266
|
+
const newToken = oneToken(current.raw);
|
|
267
|
+
if (!oldToken || !newToken)
|
|
268
|
+
return undefined;
|
|
269
|
+
if (newToken.type === "paragraph")
|
|
270
|
+
return paragraphDiff(previous.raw, current.raw);
|
|
271
|
+
if (newToken.type === "heading") {
|
|
272
|
+
const depth = Number(newToken.depth);
|
|
273
|
+
const oldText = String(oldToken.text ?? "");
|
|
274
|
+
const newText = String(newToken.text ?? "");
|
|
275
|
+
const id = slugger.slug(plainTextFromHtml(marked.parseInline(newText, { async: false })));
|
|
276
|
+
return `<h${depth} id="${escapeHtml(id)}">${inlineTokenDiff(oldText, newText)}</h${depth}>\n`;
|
|
277
|
+
}
|
|
278
|
+
if (newToken.type === "list") {
|
|
279
|
+
const oldItems = oldToken.items;
|
|
280
|
+
const newItems = newToken.items;
|
|
281
|
+
if (oldToken.ordered !== newToken.ordered ||
|
|
282
|
+
[...oldItems, ...newItems].some((item) => item.tokens.some((token) => token.type === "list")))
|
|
283
|
+
return undefined;
|
|
284
|
+
const tag = newToken.ordered ? "ol" : "ul";
|
|
285
|
+
const items = [];
|
|
286
|
+
for (let index = 0; index < Math.max(oldItems.length, newItems.length); index++) {
|
|
287
|
+
const oldItem = oldItems[index];
|
|
288
|
+
const newItem = newItems[index];
|
|
289
|
+
if (oldItem && newItem)
|
|
290
|
+
items.push(`<li>${inlineTokenDiff(String(oldItem.text), String(newItem.text))}</li>`);
|
|
291
|
+
else if (oldItem)
|
|
292
|
+
items.push(`<li class="document-diff-structural-delete">${escapeHtml(String(oldItem.text))}</li>`);
|
|
293
|
+
else if (newItem)
|
|
294
|
+
items.push(`<li class="document-diff-structural-insert">${escapeHtml(String(newItem.text))}</li>`);
|
|
295
|
+
}
|
|
296
|
+
return `<${tag}>\n${items.join("\n")}\n</${tag}>\n`;
|
|
297
|
+
}
|
|
298
|
+
if (newToken.type === "blockquote") {
|
|
299
|
+
const oldParagraphs = oldToken.tokens.filter((token) => token.type === "paragraph");
|
|
300
|
+
const newParagraphs = newToken.tokens.filter((token) => token.type === "paragraph");
|
|
301
|
+
const paragraphs = [];
|
|
302
|
+
for (let index = 0; index < Math.max(oldParagraphs.length, newParagraphs.length); index++) {
|
|
303
|
+
const oldParagraph = oldParagraphs[index];
|
|
304
|
+
const newParagraph = newParagraphs[index];
|
|
305
|
+
if (oldParagraph && newParagraph)
|
|
306
|
+
paragraphs.push(`<p>${inlineTokenDiff(String(oldParagraph.text), String(newParagraph.text))}</p>`);
|
|
307
|
+
else if (oldParagraph)
|
|
308
|
+
paragraphs.push(`<p class="document-diff-structural-delete">${escapeHtml(String(oldParagraph.text))}</p>`);
|
|
309
|
+
else if (newParagraph)
|
|
310
|
+
paragraphs.push(`<p class="document-diff-structural-insert">${escapeHtml(String(newParagraph.text))}</p>`);
|
|
311
|
+
}
|
|
312
|
+
return `<blockquote>\n${paragraphs.join("\n")}\n</blockquote>\n`;
|
|
313
|
+
}
|
|
314
|
+
if (newToken.type === "table") {
|
|
315
|
+
const oldHeader = oldToken.header;
|
|
316
|
+
const newHeader = newToken.header;
|
|
317
|
+
const oldRows = oldToken.rows;
|
|
318
|
+
const newRows = newToken.rows;
|
|
319
|
+
const cells = (oldCells = [], newCells = [], tag = "td") => Array.from({ length: Math.max(oldCells.length, newCells.length) }, (_, index) => {
|
|
320
|
+
const oldCell = oldCells[index], newCell = newCells[index];
|
|
321
|
+
const oldText = String(oldCell?.text ?? "");
|
|
322
|
+
const newText = String(newCell?.text ?? "");
|
|
323
|
+
const kind = oldCell && !newCell ? "delete" : newCell && !oldCell ? "insert" : "";
|
|
324
|
+
const content = oldCell && newCell
|
|
325
|
+
? oldText === newText
|
|
326
|
+
? marked.parseInline(newText, { async: false })
|
|
327
|
+
: inlineTokenDiff(oldText, newText)
|
|
328
|
+
: marked.parseInline(newText || oldText, { async: false });
|
|
329
|
+
return `<${tag}${kind ? ` class="document-diff-structural-${kind}"` : ""}>${content}</${tag}>`;
|
|
330
|
+
}).join("");
|
|
331
|
+
const rows = [];
|
|
332
|
+
for (let index = 0; index < Math.max(oldRows.length, newRows.length); index++) {
|
|
333
|
+
const oldRow = oldRows[index], newRow = newRows[index];
|
|
334
|
+
const kind = oldRow && !newRow ? "delete" : newRow && !oldRow ? "insert" : "";
|
|
335
|
+
rows.push(`<tr${kind ? ` class="document-diff-structural-${kind}"` : ""}>${cells(oldRow, newRow)}</tr>`);
|
|
336
|
+
}
|
|
337
|
+
return `<table>\n<thead><tr>${cells(oldHeader, newHeader, "th")}</tr></thead>\n<tbody>${rows.join("\n")}</tbody>\n</table>\n`;
|
|
338
|
+
}
|
|
339
|
+
if (newToken.type === "code") {
|
|
340
|
+
const oldLines = String(oldToken.text ?? "").split("\n");
|
|
341
|
+
const newLines = String(newToken.text ?? "").split("\n");
|
|
342
|
+
const language = escapeHtml(String(newToken.lang ?? ""));
|
|
343
|
+
return `<pre class="document-diff-code"><code${language ? ` class="language-${language}"` : ""}>${codeLineDiff(oldLines, newLines).join("\n")}</code></pre>\n`;
|
|
344
|
+
}
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
export function createDocumentDiffFeature(current, previous, markedOptions = {}) {
|
|
348
|
+
const parts = previous === undefined ? [] : diffBlocks(previous, current);
|
|
349
|
+
const hasChanges = parts.some((part) => part.kind !== "same");
|
|
350
|
+
const slugger = new GithubSlugger();
|
|
351
|
+
const diffRenderer = new Renderer();
|
|
352
|
+
diffRenderer.heading = ({ tokens, depth }) => {
|
|
353
|
+
const rendered = diffRenderer.parser.parseInline(tokens);
|
|
354
|
+
const id = slugger.slug(plainTextFromHtml(rendered));
|
|
355
|
+
return `<h${depth} id="${escapeHtml(id)}">${rendered}</h${depth}>\n`;
|
|
356
|
+
};
|
|
357
|
+
const renderedParts = [];
|
|
358
|
+
const renderPart = (part) => {
|
|
359
|
+
const html = marked.parse(part.raw, {
|
|
360
|
+
...markedOptions,
|
|
361
|
+
renderer: diffRenderer,
|
|
362
|
+
async: false,
|
|
363
|
+
});
|
|
364
|
+
return part.kind === "same"
|
|
365
|
+
? html
|
|
366
|
+
: `<div class="document-diff-block document-diff-${part.kind}">${html}</div>\n`;
|
|
367
|
+
};
|
|
368
|
+
if (hasChanges) {
|
|
369
|
+
for (let index = 0; index < parts.length; index++) {
|
|
370
|
+
const part = parts[index];
|
|
371
|
+
if (part.kind !== "same") {
|
|
372
|
+
let end = index;
|
|
373
|
+
while (end < parts.length && parts[end].kind !== "same")
|
|
374
|
+
end++;
|
|
375
|
+
const changed = parts.slice(index, end);
|
|
376
|
+
const deleted = changed.filter((item) => item.kind === "delete");
|
|
377
|
+
const inserted = changed.filter((item) => item.kind === "insert");
|
|
378
|
+
for (let changedIndex = 0; changedIndex < Math.max(deleted.length, inserted.length); changedIndex++) {
|
|
379
|
+
const oldPart = deleted[changedIndex];
|
|
380
|
+
const newPart = inserted[changedIndex];
|
|
381
|
+
if (oldPart && newPart) {
|
|
382
|
+
const structured = structuredDiff(oldPart, newPart, slugger);
|
|
383
|
+
if (structured !== undefined) {
|
|
384
|
+
renderedParts.push(structured);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (oldPart)
|
|
389
|
+
renderedParts.push(renderPart(oldPart));
|
|
390
|
+
if (newPart)
|
|
391
|
+
renderedParts.push(renderPart(newPart));
|
|
392
|
+
}
|
|
393
|
+
index = end - 1;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
renderedParts.push(renderPart(part));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const content = hasChanges ? prefixIds(renderedParts.join("")) : "";
|
|
400
|
+
const disabled = hasChanges ? "" : " disabled";
|
|
401
|
+
const label = hasChanges ? "差分を表示" : "前回からの変更はありません";
|
|
402
|
+
const control = `<button type="button" class="site-header-action document-diff-toggle" data-document-diff-toggle aria-label="${label}" title="${label}" aria-pressed="false"${disabled}><svg data-document-diff-icon viewBox="0 0 16 16" aria-hidden="true"><circle cx="4" cy="3" r="1.5"/><circle cx="4" cy="13" r="1.5"/><circle cx="12" cy="5" r="1.5"/><path d="M4 4.5v7M5.5 4h2A4.5 4.5 0 0112 8.5V10"/></svg><svg data-document-current-icon viewBox="0 0 16 16" aria-hidden="true" hidden><path d="M3 1.75h6l4 4v8.5H3z"/><path d="M9 1.75v4h4M5.5 9h5M5.5 11.5h5"/></svg></button>`;
|
|
403
|
+
const styles = `
|
|
404
|
+
body.markdown-body[data-theme="dark"]{--diff-insert-bg:#58a6ff1a;--diff-delete-bg:#ff7b7226;--diff-delete-fg:#ff938a}body.markdown-body[data-theme="light"]{--diff-insert-bg:#0969da12;--diff-delete-bg:#cf222e18;--diff-delete-fg:#b4232c}
|
|
405
|
+
.document-diff-structural-insert{background:var(--diff-insert-bg,#0969da12);box-shadow:inset 3px 0 var(--fgColor-accent,#0969da)}.document-diff-structural-delete{color:var(--diff-delete-fg,#b4232c);background:var(--diff-delete-bg,#cf222e18);text-decoration:line-through;text-decoration-thickness:2px}.document-diff-code code>span{display:block;min-height:1.5em;white-space:pre-wrap}.document-diff-link-target{margin-inline-start:2px;color:var(--fgColor-muted,#59636e);font-size:.875em;overflow-wrap:anywhere}.document-diff-link-delete{pointer-events:none}
|
|
406
|
+
.document-diff-content{grid-area:content;box-sizing:border-box;min-width:0;margin-bottom:72px;padding:clamp(28px,3vw,52px);color:var(--fgColor-default,#1f2328);background:var(--bgColor-default,#fff);border:1px solid var(--borderColor-muted,#d8dee4);border-radius:8px;box-shadow:0 1px 2px rgba(31,35,40,.04)}.document-diff-content[hidden]{display:none}.document-diff-block{position:relative;margin:0 -12px;padding:1px 12px 1px 22px;border-left:2px solid var(--fgColor-muted,#59636e)}.document-diff-block::before{position:absolute;top:3px;left:7px;font-weight:700;line-height:1;content:""}.document-diff-block.document-diff-insert{background:var(--diff-insert-bg,#0969da12);border-left-style:solid;border-left-color:var(--fgColor-accent,#0969da)}.document-diff-block.document-diff-insert::before{color:var(--fgColor-accent,#0969da);content:"+"}.document-diff-block.document-diff-delete{color:var(--diff-delete-fg,#b4232c);background:var(--diff-delete-bg,#cf222e18);border-left-color:var(--diff-delete-fg,#b4232c);border-left-style:dashed;opacity:.9}.document-diff-block.document-diff-delete::before{color:var(--diff-delete-fg,#b4232c);content:"−"}.document-diff-block.document-diff-delete :is(a,button,input,select,textarea){pointer-events:none}.document-diff-inline-insert,.document-diff-inline-delete{padding:1px 2px;border-radius:2px;box-decoration-break:clone;-webkit-box-decoration-break:clone}.document-diff-inline-insert{color:var(--fgColor-default,#1f2328);background:var(--diff-insert-bg,#0969da12);text-decoration-line:underline;text-decoration-style:double;text-decoration-color:var(--fgColor-accent,#0969da);text-underline-offset:3px}.document-diff-inline-delete{color:var(--diff-delete-fg,#b4232c);background:var(--diff-delete-bg,#cf222e18);text-decoration:line-through;text-decoration-thickness:2px}.document-diff-toggle[aria-pressed="true"]{color:var(--fgColor-accent,#0969da);background:var(--bgColor-accent-muted,#ddf4ff)}.document-diff-toggle:disabled{color:var(--fgColor-muted,#59636e);opacity:.45;cursor:not-allowed}.document-diff-toggle svg[hidden]{display:none}body[data-document-view="diff"] .markdown-content{display:none}@media(max-width:900px){.document-diff-content{margin-bottom:32px}}@media(max-width:600px){.document-diff-content{padding:24px 20px;border-radius:6px}}
|
|
407
|
+
`;
|
|
408
|
+
const script = `<script>(()=>{
|
|
409
|
+
const parameter='document-view',button=document.querySelector('[data-document-diff-toggle]'),current=document.querySelector('.markdown-content'),diff=document.querySelector('.document-diff-content'),available=${hasChanges};
|
|
410
|
+
const update=url=>{if(document.body.dataset.documentView==='diff'||(!available&&new URL(location.href).searchParams.get(parameter)==='diff'))url.searchParams.set(parameter,'diff');else url.searchParams.delete(parameter);return url};
|
|
411
|
+
function syncLinks(){for(const link of document.querySelectorAll('a[href]')){const raw=link.getAttribute('href');if(!raw||raw.startsWith('#'))continue;const url=new URL(raw,location.href);if(url.protocol===location.protocol&&url.host===location.host&&url.pathname.endsWith('.html'))link.href=update(url).href}}
|
|
412
|
+
function apply(view,write=true){const showDiff=available&&view==='diff';document.body.dataset.documentView=showDiff?'diff':'current';current.hidden=showDiff;diff.hidden=!showDiff;button.setAttribute('aria-pressed',String(showDiff));button.querySelector('[data-document-diff-icon]').hidden=showDiff;button.querySelector('[data-document-current-icon]').hidden=!showDiff;const label=available?(showDiff?'最新版を表示':'差分を表示'):'前回からの変更はありません';button.setAttribute('aria-label',label);button.title=label;if(write)history.replaceState(null,'',update(new URL(location.href)));syncLinks();window.marksitesApplyLanguage?.()}
|
|
413
|
+
apply(new URL(location.href).searchParams.get(parameter)==='diff',available);if(available)button.addEventListener('click',()=>apply(document.body.dataset.documentView==='diff'?'current':'diff'));
|
|
414
|
+
})()</script>`;
|
|
415
|
+
return { content, control, styles, script, hasChanges };
|
|
416
|
+
}
|
|
@@ -130,8 +130,7 @@ function renderRecentFiles(nodes) {
|
|
|
130
130
|
? `<span class="file-tree-comment-count" aria-label="コメント${file.commentCount}件">${file.commentCount}</span>`
|
|
131
131
|
: "";
|
|
132
132
|
const time = file.modifiedAt.slice(11, 16);
|
|
133
|
-
|
|
134
|
-
output.push(` <li class="file-tree-recent-file${grouped ? " is-grouped" : ""}" data-file-path="${escapeHtml(file.path)}" data-directory="${escapeHtml(directory)}" data-modified-at="${file.modifiedAt}"${grouped ? ` data-recent-group="${groupId}"` : ""}><a href="${escapeHtml(file.href)}"${current}><span class="file-tree-recent-label"><span class="file-tree-name"><span class="file-tree-name-text">${escapeHtml(file.name)}</span></span></span>${count}<span class="file-tree-directory-tooltip" aria-hidden="true"><span class="file-tree-directory-tooltip-path">${renderFolderIcon()}<span>${escapeHtml(directoryLabel)}</span></span><time datetime="${file.modifiedAt}">${tooltipDate}</time></span></a></li>`);
|
|
133
|
+
output.push(` <li class="file-tree-recent-file${grouped ? " is-grouped" : ""}" data-file-path="${escapeHtml(file.path)}" data-directory="${escapeHtml(directory)}" data-modified-at="${file.modifiedAt}"${grouped ? ` data-recent-group="${groupId}"` : ""}><a href="${escapeHtml(file.href)}"${current}><time class="file-tree-recent-time" datetime="${file.modifiedAt}">${time}</time><span class="file-tree-recent-label"><span class="file-tree-name"><span class="file-tree-name-text">${escapeHtml(file.name)}</span></span></span>${count}<span class="file-tree-directory-tooltip" aria-hidden="true"><span class="file-tree-directory-tooltip-path">${renderFolderIcon()}<span>${escapeHtml(directoryLabel)}</span></span></span></a></li>`);
|
|
135
134
|
}
|
|
136
135
|
}
|
|
137
136
|
return output.join("\n");
|
|
@@ -206,9 +205,11 @@ export function renderFileTreeScript(enabled) {
|
|
|
206
205
|
const positionDirectoryTooltip = (link) => {
|
|
207
206
|
const name = link.querySelector('.file-tree-name-text');
|
|
208
207
|
const tooltip = link.querySelector('.file-tree-directory-tooltip');
|
|
209
|
-
|
|
208
|
+
const recent = link.closest('.file-tree-recent');
|
|
209
|
+
if (!name || !tooltip || !recent) return;
|
|
210
210
|
const nameRect = name.getBoundingClientRect();
|
|
211
|
-
|
|
211
|
+
const recentRect = recent.getBoundingClientRect();
|
|
212
|
+
tooltip.style.left = Math.min(recentRect.right + 8, innerWidth - tooltip.offsetWidth - 8) + 'px';
|
|
212
213
|
tooltip.style.top = Math.max(8 + tooltip.offsetHeight / 2, Math.min(nameRect.top + nameRect.height / 2, innerHeight - 8 - tooltip.offsetHeight / 2)) + 'px';
|
|
213
214
|
};
|
|
214
215
|
|
|
@@ -366,8 +367,8 @@ export function renderFileTreeScript(enabled) {
|
|
|
366
367
|
const file = files[index];
|
|
367
368
|
const date = new Date(file.dataset.modifiedAt);
|
|
368
369
|
file.dataset.localDate = dateKey;
|
|
369
|
-
const
|
|
370
|
-
|
|
370
|
+
const rowTime = file.querySelector('.file-tree-recent-time');
|
|
371
|
+
rowTime.textContent = pad(date.getHours())+':'+pad(date.getMinutes());
|
|
371
372
|
if (grouped) {
|
|
372
373
|
file.classList.add('is-grouped');
|
|
373
374
|
file.dataset.recentGroup = groupId;
|
|
@@ -2,6 +2,7 @@ export function createHeaderFeature(options = {
|
|
|
2
2
|
documentNavigation: "",
|
|
3
3
|
documentMetadata: "",
|
|
4
4
|
fileTree: "",
|
|
5
|
+
documentDiffControl: "",
|
|
5
6
|
}) {
|
|
6
7
|
const markup = `<header class="site-header">
|
|
7
8
|
<div class="site-header-brand">
|
|
@@ -10,6 +11,7 @@ export function createHeaderFeature(options = {
|
|
|
10
11
|
${options.documentNavigation}${options.documentMetadata ? ` <span class="site-header-metadata-separator" aria-hidden="true"></span>\n <div class="document-metadata">${options.documentMetadata}</div>\n` : ""}${options.fileTree} </div>
|
|
11
12
|
</div>
|
|
12
13
|
<div class="site-header-actions">
|
|
14
|
+
${options.documentDiffControl}
|
|
13
15
|
<button type="button" class="site-header-action" data-theme-toggle aria-label="ダークモードに切り替え" title="ダークモードに切り替え"><svg data-theme-dark-icon viewBox="0 0 16 16" aria-hidden="true"><path d="M13.5 10.2A5.8 5.8 0 015.8 2.5 5.8 5.8 0 1013.5 10.2z" /></svg><svg data-theme-light-icon viewBox="0 0 16 16" aria-hidden="true" hidden><circle cx="8" cy="8" r="2.5"/><path d="M8 1v1.5M8 13.5V15M1 8h1.5M13.5 8H15M3.05 3.05l1.06 1.06M11.89 11.89l1.06 1.06M12.95 3.05l-1.06 1.06M4.11 11.89l-1.06 1.06"/></svg></button>
|
|
14
16
|
<button type="button" class="site-header-action language-toggle" data-language-toggle aria-label="英語に切り替え" title="英語に切り替え"><svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="6"/><path d="M2 8h12M8 2a9 9 0 010 12M8 2a9 9 0 000 12"/></svg><span data-language-label>JA</span></button>
|
|
15
17
|
</div>
|
|
@@ -37,6 +39,7 @@ const english=new Map(${JSON.stringify([
|
|
|
37
39
|
["アーカイブを開く", "Open archive"], ["アーカイブを閉じる", "Close archive"], ["コメントを保存", "Save comment"], ["編集をキャンセル", "Cancel editing"],
|
|
38
40
|
["有効なコメントをすべてコピー", "Copy all active comments"], ["有効なコメントだけをコピー", "Copy active comments only"], ["有効なコメントをコピー", "Copy active comments"], ["コメントをコピーしました", "Comment copied"], ["コピー失敗", "Copy failed"], ["コピーに失敗しました", "Copy failed"], ["保存中…", "Saving…"], ["保存に失敗しました", "Save failed"], ["このコメントを削除しますか?", "Delete this comment?"],
|
|
39
41
|
["コメントを追加するにはmarksites serveを起動してください", "Start marksites serve to add comments"], ["別の画面でコメントが更新されました。最新の内容を確認して、もう一度操作してください。", "Comments changed in another window. Review the latest version and try again."],
|
|
42
|
+
["差分を表示", "Show changes"], ["最新版を表示", "Show latest"], ["前回からの変更はありません", "No changes since the previous build"],
|
|
40
43
|
["コードをコピー", "Copy code"], ["長い行を折り返す", "Wrap long lines"], ["折り返しを解除", "Disable line wrapping"], ["コードをコピーしました", "Code copied"], ["コードをコピーできませんでした", "Could not copy code"],
|
|
41
44
|
["文書ナビゲーション", "Document navigation"], ["文書サイドバー", "Document sidebar"], ["パンくずリスト", "Breadcrumbs"]
|
|
42
45
|
])});
|
|
@@ -24,25 +24,27 @@ function renderTableOfContentsScript() {
|
|
|
24
24
|
|
|
25
25
|
const panel = navigation.querySelector('.toc-panel');
|
|
26
26
|
const links = [...panel.querySelectorAll('a[href^="#"]')];
|
|
27
|
-
const entries = links
|
|
28
|
-
.map((link) => ({ link, heading: document.getElementById(link.getAttribute('href').slice(1)) }))
|
|
29
|
-
.filter((entry) => entry.heading);
|
|
27
|
+
const entries = links.map((link) => ({ link, id: link.getAttribute('href').slice(1) }));
|
|
30
28
|
if (entries.length === 0) return;
|
|
31
29
|
|
|
30
|
+
const headingFor = (entry) => document.getElementById((document.body.dataset.documentView === 'diff' ? 'diff-' : '') + entry.id);
|
|
31
|
+
|
|
32
32
|
let scheduled = false;
|
|
33
33
|
let currentLink = null;
|
|
34
34
|
const update = () => {
|
|
35
35
|
scheduled = false;
|
|
36
36
|
const marker = Math.min(160, window.innerHeight * 0.25);
|
|
37
|
-
|
|
37
|
+
const available = entries.map(entry => ({ ...entry, heading: headingFor(entry) })).filter(entry => entry.heading);
|
|
38
|
+
if (available.length === 0) return;
|
|
39
|
+
let active = available[0];
|
|
38
40
|
|
|
39
|
-
for (const entry of
|
|
41
|
+
for (const entry of available) {
|
|
40
42
|
if (entry.heading.getBoundingClientRect().top > marker) break;
|
|
41
43
|
active = entry;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
for (const entry of entries) {
|
|
45
|
-
if (entry === active) entry.link.setAttribute('aria-current', 'location');
|
|
47
|
+
if (entry.link === active.link) entry.link.setAttribute('aria-current', 'location');
|
|
46
48
|
else entry.link.removeAttribute('aria-current');
|
|
47
49
|
}
|
|
48
50
|
if (!navigation.hidden && active.link !== currentLink) {
|
|
@@ -63,6 +65,14 @@ function renderTableOfContentsScript() {
|
|
|
63
65
|
|
|
64
66
|
addEventListener('scroll', schedule, { passive: true });
|
|
65
67
|
addEventListener('resize', schedule);
|
|
68
|
+
for (const entry of entries) entry.link.addEventListener('click', event => {
|
|
69
|
+
const heading = headingFor(entry);
|
|
70
|
+
if (!heading || document.body.dataset.documentView !== 'diff') return;
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
heading.scrollIntoView();
|
|
73
|
+
history.replaceState(null, '', '#diff-' + entry.id);
|
|
74
|
+
});
|
|
75
|
+
new MutationObserver(schedule).observe(document.body, { attributes: true, attributeFilter: ['data-document-view'] });
|
|
66
76
|
update();
|
|
67
77
|
})();
|
|
68
78
|
</script>`;
|
|
@@ -2,4 +2,4 @@ import type { AnnotationDocument } from "./annotations/model.js";
|
|
|
2
2
|
import type { RenderOptions } from "./types.js";
|
|
3
3
|
/** Convert Markdown into a standalone HTML document styled like GitHub. */
|
|
4
4
|
export declare function markdownToHtml(markdown: string, options?: RenderOptions): string;
|
|
5
|
-
export declare function renderMarkdown(markdown: string, options?: RenderOptions, annotations?: AnnotationDocument): string;
|
|
5
|
+
export declare function renderMarkdown(markdown: string, options?: RenderOptions, annotations?: AnnotationDocument, previousMarkdown?: string): string;
|
package/dist/markdown-to-html.js
CHANGED
|
@@ -6,13 +6,14 @@ import { renderBreadcrumbs, renderFileSidebar, renderFileTree, renderFileTreeScr
|
|
|
6
6
|
import { createTableOfContentsFeature } from "./features/table-of-contents/index.js";
|
|
7
7
|
import { createSidebarFeature } from "./features/sidebar/index.js";
|
|
8
8
|
import { createImageViewerFeature } from "./features/image-viewer/index.js";
|
|
9
|
+
import { createDocumentDiffFeature } from "./features/document-diff/index.js";
|
|
9
10
|
import { renderDocument } from "./template/document.js";
|
|
10
11
|
import { escapeHtml } from "./utils/html.js";
|
|
11
12
|
/** Convert Markdown into a standalone HTML document styled like GitHub. */
|
|
12
13
|
export function markdownToHtml(markdown, options = {}) {
|
|
13
14
|
return renderMarkdown(markdown, options);
|
|
14
15
|
}
|
|
15
|
-
export function renderMarkdown(markdown, options = {}, annotations) {
|
|
16
|
+
export function renderMarkdown(markdown, options = {}, annotations, previousMarkdown) {
|
|
16
17
|
const rawTitle = options.title ?? "Markdown文書";
|
|
17
18
|
const currentFileName = options.fileTree?.breadcrumbs?.find((breadcrumb) => breadcrumb.current)?.name;
|
|
18
19
|
const title = escapeHtml(`marksites | ${currentFileName ?? rawTitle}`);
|
|
@@ -37,6 +38,7 @@ export function renderMarkdown(markdown, options = {}, annotations) {
|
|
|
37
38
|
const fileSidebar = renderFileSidebar(options.fileTree);
|
|
38
39
|
const fileTreeScript = renderFileTreeScript(fileTree !== "");
|
|
39
40
|
const modifiedAt = renderModifiedAt(options.modifiedAt);
|
|
41
|
+
const documentDiff = createDocumentDiffFeature(markdown, previousMarkdown, options.markedOptions);
|
|
40
42
|
const breadcrumbs = fileTree
|
|
41
43
|
? renderBreadcrumbs(options.fileTree?.breadcrumbs)
|
|
42
44
|
: `<nav class="file-breadcrumbs" aria-label="ファイルパス"><span aria-current="page">${escapeHtml(rawTitle)}</span></nav>\n`;
|
|
@@ -44,6 +46,7 @@ export function renderMarkdown(markdown, options = {}, annotations) {
|
|
|
44
46
|
documentNavigation: breadcrumbs,
|
|
45
47
|
documentMetadata: modifiedAt,
|
|
46
48
|
fileTree,
|
|
49
|
+
documentDiffControl: documentDiff.control,
|
|
47
50
|
});
|
|
48
51
|
const annotationFeature = createAnnotationsFeature(annotations);
|
|
49
52
|
const imageViewer = createImageViewerFeature(/<img\b/i.test(content));
|
|
@@ -61,6 +64,7 @@ export function renderMarkdown(markdown, options = {}, annotations) {
|
|
|
61
64
|
regions: {
|
|
62
65
|
header: header.markup,
|
|
63
66
|
fileSidebar,
|
|
67
|
+
diffContent: `<main class="document-diff-content" aria-label="文書の差分" hidden>\n${documentDiff.content}</main>`,
|
|
64
68
|
sidebar: sidebar.markup,
|
|
65
69
|
overlays: `${annotationFeature.markup}${imageViewer.markup}`,
|
|
66
70
|
},
|
|
@@ -70,9 +74,11 @@ export function renderMarkdown(markdown, options = {}, annotations) {
|
|
|
70
74
|
annotationFeature.styles,
|
|
71
75
|
imageViewer.styles,
|
|
72
76
|
header.styles,
|
|
77
|
+
documentDiff.styles,
|
|
73
78
|
],
|
|
74
79
|
scripts: [
|
|
75
80
|
header.script,
|
|
81
|
+
documentDiff.script,
|
|
76
82
|
fileTreeScript,
|
|
77
83
|
renderModifiedAtScript(modifiedAt !== ""),
|
|
78
84
|
sidebar.script,
|
|
@@ -5,6 +5,7 @@ import { createAnnotationsFeature } from "../features/annotations/index.js";
|
|
|
5
5
|
import { createCodeBlocksFeature } from "../features/code-blocks/index.js";
|
|
6
6
|
import { createHeaderFeature } from "../features/header/index.js";
|
|
7
7
|
import { createImageViewerFeature } from "../features/image-viewer/index.js";
|
|
8
|
+
import { createDocumentDiffFeature } from "../features/document-diff/index.js";
|
|
8
9
|
import { renderFileTreeScript, renderModifiedAtScript, } from "../features/file-tree/index.js";
|
|
9
10
|
import { createSidebarFeature } from "../features/sidebar/index.js";
|
|
10
11
|
import { createTableOfContentsFeature } from "../features/table-of-contents/index.js";
|
|
@@ -36,6 +37,8 @@ function generatedScriptBodies() {
|
|
|
36
37
|
scriptBody(renderedToc.script),
|
|
37
38
|
scriptBody(annotations.script),
|
|
38
39
|
scriptBody(createImageViewerFeature(true).script),
|
|
40
|
+
scriptBody(createDocumentDiffFeature("same", undefined).script),
|
|
41
|
+
scriptBody(createDocumentDiffFeature("after", "before").script),
|
|
39
42
|
scriptBody(createSidebarFeature({
|
|
40
43
|
tableOfContents: renderedToc.markup,
|
|
41
44
|
tableOfContentsTitle: renderedToc.title,
|
|
@@ -2,4 +2,4 @@ export declare const githubMarkdownCss: string;
|
|
|
2
2
|
export declare const highlightCss: string;
|
|
3
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
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 .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-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:disabled { color: var(--fgColor-default, #1f2328); text-decoration: none; cursor: default; }\n .file-tree-popover-toggle:disabled .panel-toggle-icon { display: none; }\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-view-tabs { display: grid; grid-template-columns: 1fr 1fr; margin: 0 0 8px; padding: 2px; background: var(--bgColor-muted, #f6f8fa); border-radius: 6px; }\n .file-tree-view-tabs button { min-height: 28px; padding: 3px 8px; color: var(--fgColor-muted, #59636e); font: inherit; font-size: 0.75rem; font-weight: 600; background: transparent; border: 0; border-radius: 4px; cursor: pointer; }\n .file-tree-view-tabs button[aria-selected=\"true\"] { color: var(--fgColor-default, #1f2328); background: var(--bgColor-default, #fff); box-shadow: 0 1px 2px rgba(31,35,40,.12); }\n .file-tree-view-tabs button:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: 1px; }\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 [hidden] { display: 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 .file-tree-recent { position: relative; }\n .file-tree-recent::before { position: absolute; top: 0; bottom: 0; left: 12px; width: 1px; content: \"\"; background: var(--borderColor-muted, #d8dee4); }\n .file-tree-date { position: relative; z-index: 1; margin: 10px 0 3px; background: var(--bgColor-default, #fff); }\n .file-tree-date::before { position: absolute; top: -10px; bottom: -3px; left: 12px; width: 1px; content: \"\"; background: var(--bgColor-default, #fff); }\n .file-tree-date:first-child { margin-top: 4px; }\n .file-tree-date button { display: flex; width: 100%; min-height: 28px; align-items: center; gap: 4px; padding: 4px 7px; color: var(--fgColor-default, #1f2328); font: inherit; font-size: 0.75rem; font-weight: 600; text-align: left; background: transparent; border: 0; border-radius: 5px; cursor: pointer; }\n .file-tree-date button:hover { background: var(--bgColor-muted, #f6f8fa); }\n .file-tree-date button:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: -2px; }\n .file-tree-date button svg { width: 12px; height: 12px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; transition: transform 120ms ease; }\n .file-tree-date button[aria-expanded=\"false\"] svg { transform: rotate(-90deg); }\n .file-tree-date button span { min-width: 18px; margin-left: auto; padding: 1px 5px; color: var(--fgColor-muted, #59636e); font-size: 0.6875rem; font-weight: 600; line-height: 16px; text-align: center; background: var(--bgColor-neutral-muted, #818b981f); border-radius: 9px; }\n .file-tree-recent .is-date-collapsed { display: none; }\n .file-tree-directory-group { display: none; }\n .file-tree-directory-group .folder-icon { width: 13px; height: 13px; flex: none; }\n .file-tree-directory-group span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .file-tree-recent-file { position: relative; margin-left: 22px; }\n .file-tree-recent-file a { position: relative; margin-top: 0; margin-bottom: 0; overflow: visible; align-items: flex-start; gap: 7px; }\n .file-tree-recent-label { display: flex; min-width: 0; flex: 1; }\n .file-tree-recent-label .file-tree-name { width: 100%; color: var(--fgColor-default, #1f2328); font-size: 0.8125rem; font-weight: 400; }\n .file-tree-recent-file a:hover .file-tree-name { text-decoration: underline; text-underline-offset: 2px; }\n .file-tree-directory-tooltip { position: fixed; z-index: 100; top: 0; left: 0; display: none; max-width: min(240px, calc(100vw - 16px)); flex-direction: column; align-items: flex-start; gap: 3px; padding: 6px 8px; color: var(--fgColor-default, #1f2328); font-size: 0.6875rem; font-weight: 400; line-height: 1.35; white-space: nowrap; background: var(--bgColor-default, #fff); border: 1px solid var(--borderColor-default, #d0d7de); border-radius: 5px; box-shadow: 0 2px 8px rgba(31,35,40,.16); transform: translateY(-50%); pointer-events: none; }\n .file-tree-directory-tooltip::before { position: absolute; top: 50%; right: calc(100% - 4px); width: 7px; height: 7px; content: \"\"; background: var(--bgColor-default, #fff); border: 0 solid var(--borderColor-default, #d0d7de); border-width: 0 0 1px 1px; transform: translateY(-50%) rotate(45deg); }\n .file-tree-directory-tooltip-path { display: flex; max-width: 220px; align-items: center; gap: 5px; overflow: hidden; }\n .file-tree-directory-tooltip-path .folder-icon { width: 12px; height: 12px; flex: none; margin: 0; }\n .file-tree-directory-tooltip-path > span { overflow: hidden; text-overflow: ellipsis; }\n .file-tree-directory-tooltip > time { color: var(--fgColor-muted, #59636e); font-variant-numeric: tabular-nums; }\n .file-tree-recent-file a:hover .file-tree-directory-tooltip, .file-tree-recent-file a:focus-visible .file-tree-directory-tooltip { display: flex; }\n .file-tree-recent-file .file-tree-comment-count { margin-top: 1px; }\n .file-tree-recent-empty { margin: 10px 4px 2px; color: var(--fgColor-muted, #59636e); font-size: 0.8125rem; text-align: center; }\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 ";
|
|
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-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:disabled { color: var(--fgColor-default, #1f2328); text-decoration: none; cursor: default; }\n .file-tree-popover-toggle:disabled .panel-toggle-icon { display: none; }\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-view-tabs { display: grid; grid-template-columns: 1fr 1fr; margin: 0 0 8px; padding: 2px; background: var(--bgColor-muted, #f6f8fa); border-radius: 6px; }\n .file-tree-view-tabs button { min-height: 28px; padding: 3px 8px; color: var(--fgColor-muted, #59636e); font: inherit; font-size: 0.75rem; font-weight: 600; background: transparent; border: 0; border-radius: 4px; cursor: pointer; }\n .file-tree-view-tabs button[aria-selected=\"true\"] { color: var(--fgColor-default, #1f2328); background: var(--bgColor-default, #fff); box-shadow: 0 1px 2px rgba(31,35,40,.12); }\n .file-tree-view-tabs button:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: 1px; }\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 [hidden] { display: 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 .file-tree-recent { position: relative; isolation: isolate; }\n .file-tree-recent::before { position: absolute; z-index: -1; top: 0; bottom: 0; left: 12px; width: 1px; content: \"\"; background: var(--borderColor-muted, #d8dee4); }\n .file-tree-date { position: relative; z-index: 1; margin: 10px 0 3px; background: var(--bgColor-default, #fff); }\n .file-tree-date:first-child { margin-top: 4px; }\n .file-tree-date button { display: flex; width: 100%; min-height: 28px; align-items: center; gap: 4px; padding: 4px 7px; color: var(--fgColor-default, #1f2328); font: inherit; font-size: 0.75rem; font-weight: 600; text-align: left; background: transparent; border: 0; border-radius: 5px; cursor: pointer; }\n .file-tree-date button:hover { background: var(--bgColor-muted, #f6f8fa); }\n .file-tree-date button:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: -2px; }\n .file-tree-date button svg { width: 12px; height: 12px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; transition: transform 120ms ease; }\n .file-tree-date button[aria-expanded=\"false\"] svg { transform: rotate(-90deg); }\n .file-tree-date button span { min-width: 18px; margin-left: auto; padding: 1px 5px; color: var(--fgColor-muted, #59636e); font-size: 0.6875rem; font-weight: 600; line-height: 16px; text-align: center; background: var(--bgColor-neutral-muted, #818b981f); border-radius: 9px; }\n .file-tree-recent .is-date-collapsed { display: none; }\n .file-tree-directory-group { display: none; }\n .file-tree-directory-group .folder-icon { width: 13px; height: 13px; flex: none; }\n .file-tree-directory-group span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .file-tree-recent-file { position: relative; margin-left: 22px; }\n .file-tree-recent-file a { position: relative; margin-top: 0; margin-bottom: 0; overflow: visible; align-items: center; gap: 7px; }\n .file-tree-recent-time { width: 32px; flex: none; color: var(--fgColor-muted, #59636e); font-size: 0.6875rem; font-variant-numeric: tabular-nums; text-align: right; }\n .file-tree-recent-label { display: flex; min-width: 0; flex: 1; }\n .file-tree-recent-label .file-tree-name { position: relative; top: -1px; width: 100%; color: var(--fgColor-default, #1f2328); font-size: 0.8125rem; font-weight: 400; }\n .file-tree-recent-file a:hover .file-tree-name { text-decoration: underline; text-underline-offset: 2px; }\n .file-tree-directory-tooltip { position: fixed; z-index: 100; top: 0; left: 0; display: none; max-width: min(240px, calc(100vw - 16px)); flex-direction: column; align-items: flex-start; gap: 3px; padding: 6px 8px; color: var(--fgColor-default, #1f2328); font-size: 0.6875rem; font-weight: 400; line-height: 1.35; white-space: nowrap; background: var(--bgColor-default, #fff); border: 1px solid var(--borderColor-default, #d0d7de); border-radius: 5px; box-shadow: 0 2px 8px rgba(31,35,40,.16); transform: translateY(-50%); pointer-events: none; }\n .file-tree-directory-tooltip::before { position: absolute; top: 50%; right: calc(100% - 4px); width: 7px; height: 7px; content: \"\"; background: var(--bgColor-default, #fff); border: 0 solid var(--borderColor-default, #d0d7de); border-width: 0 0 1px 1px; transform: translateY(-50%) rotate(45deg); }\n .file-tree-directory-tooltip-path { display: flex; max-width: 220px; align-items: center; gap: 5px; overflow: hidden; }\n .file-tree-directory-tooltip-path .folder-icon { width: 12px; height: 12px; flex: none; margin: 0; }\n .file-tree-directory-tooltip-path > span { overflow: hidden; text-overflow: ellipsis; }\n .file-tree-recent-file a:hover .file-tree-directory-tooltip, .file-tree-recent-file a:focus-visible .file-tree-directory-tooltip { display: flex; }\n .file-tree-recent-file .file-tree-comment-count { margin-top: 0; }\n .file-tree-recent-empty { margin: 10px 4px 2px; color: var(--fgColor-muted, #59636e); font-size: 0.8125rem; text-align: center; }\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 ";
|
package/dist/template/styles.js
CHANGED
|
@@ -105,10 +105,9 @@ export const fileTreeStyles = ` body.markdown-body.has-file-tree { width: 100
|
|
|
105
105
|
.file-tree a:hover { color: var(--fgColor-default, #1f2328); background: var(--bgColor-muted, #f6f8fa); text-decoration: none; }
|
|
106
106
|
.file-tree a:focus-visible { outline: 2px solid var(--focus-outlineColor, #0969da); outline-offset: -2px; }
|
|
107
107
|
.file-tree a[aria-current="page"] { color: var(--fgColor-accent, #0969da); font-weight: 600; background: var(--bgColor-accent-muted, #ddf4ff); }
|
|
108
|
-
.file-tree-recent { position: relative; }
|
|
109
|
-
.file-tree-recent::before { position: absolute; top: 0; bottom: 0; left: 12px; width: 1px; content: ""; background: var(--borderColor-muted, #d8dee4); }
|
|
108
|
+
.file-tree-recent { position: relative; isolation: isolate; }
|
|
109
|
+
.file-tree-recent::before { position: absolute; z-index: -1; top: 0; bottom: 0; left: 12px; width: 1px; content: ""; background: var(--borderColor-muted, #d8dee4); }
|
|
110
110
|
.file-tree-date { position: relative; z-index: 1; margin: 10px 0 3px; background: var(--bgColor-default, #fff); }
|
|
111
|
-
.file-tree-date::before { position: absolute; top: -10px; bottom: -3px; left: 12px; width: 1px; content: ""; background: var(--bgColor-default, #fff); }
|
|
112
111
|
.file-tree-date:first-child { margin-top: 4px; }
|
|
113
112
|
.file-tree-date button { display: flex; width: 100%; min-height: 28px; align-items: center; gap: 4px; padding: 4px 7px; color: var(--fgColor-default, #1f2328); font: inherit; font-size: 0.75rem; font-weight: 600; text-align: left; background: transparent; border: 0; border-radius: 5px; cursor: pointer; }
|
|
114
113
|
.file-tree-date button:hover { background: var(--bgColor-muted, #f6f8fa); }
|
|
@@ -121,18 +120,18 @@ export const fileTreeStyles = ` body.markdown-body.has-file-tree { width: 100
|
|
|
121
120
|
.file-tree-directory-group .folder-icon { width: 13px; height: 13px; flex: none; }
|
|
122
121
|
.file-tree-directory-group span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
123
122
|
.file-tree-recent-file { position: relative; margin-left: 22px; }
|
|
124
|
-
.file-tree-recent-file a { position: relative; margin-top: 0; margin-bottom: 0; overflow: visible; align-items:
|
|
123
|
+
.file-tree-recent-file a { position: relative; margin-top: 0; margin-bottom: 0; overflow: visible; align-items: center; gap: 7px; }
|
|
124
|
+
.file-tree-recent-time { width: 32px; flex: none; color: var(--fgColor-muted, #59636e); font-size: 0.6875rem; font-variant-numeric: tabular-nums; text-align: right; }
|
|
125
125
|
.file-tree-recent-label { display: flex; min-width: 0; flex: 1; }
|
|
126
|
-
.file-tree-recent-label .file-tree-name { width: 100%; color: var(--fgColor-default, #1f2328); font-size: 0.8125rem; font-weight: 400; }
|
|
126
|
+
.file-tree-recent-label .file-tree-name { position: relative; top: -1px; width: 100%; color: var(--fgColor-default, #1f2328); font-size: 0.8125rem; font-weight: 400; }
|
|
127
127
|
.file-tree-recent-file a:hover .file-tree-name { text-decoration: underline; text-underline-offset: 2px; }
|
|
128
128
|
.file-tree-directory-tooltip { position: fixed; z-index: 100; top: 0; left: 0; display: none; max-width: min(240px, calc(100vw - 16px)); flex-direction: column; align-items: flex-start; gap: 3px; padding: 6px 8px; color: var(--fgColor-default, #1f2328); font-size: 0.6875rem; font-weight: 400; line-height: 1.35; white-space: nowrap; background: var(--bgColor-default, #fff); border: 1px solid var(--borderColor-default, #d0d7de); border-radius: 5px; box-shadow: 0 2px 8px rgba(31,35,40,.16); transform: translateY(-50%); pointer-events: none; }
|
|
129
129
|
.file-tree-directory-tooltip::before { position: absolute; top: 50%; right: calc(100% - 4px); width: 7px; height: 7px; content: ""; background: var(--bgColor-default, #fff); border: 0 solid var(--borderColor-default, #d0d7de); border-width: 0 0 1px 1px; transform: translateY(-50%) rotate(45deg); }
|
|
130
130
|
.file-tree-directory-tooltip-path { display: flex; max-width: 220px; align-items: center; gap: 5px; overflow: hidden; }
|
|
131
131
|
.file-tree-directory-tooltip-path .folder-icon { width: 12px; height: 12px; flex: none; margin: 0; }
|
|
132
132
|
.file-tree-directory-tooltip-path > span { overflow: hidden; text-overflow: ellipsis; }
|
|
133
|
-
.file-tree-directory-tooltip > time { color: var(--fgColor-muted, #59636e); font-variant-numeric: tabular-nums; }
|
|
134
133
|
.file-tree-recent-file a:hover .file-tree-directory-tooltip, .file-tree-recent-file a:focus-visible .file-tree-directory-tooltip { display: flex; }
|
|
135
|
-
.file-tree-recent-file .file-tree-comment-count { margin-top:
|
|
134
|
+
.file-tree-recent-file .file-tree-comment-count { margin-top: 0; }
|
|
136
135
|
.file-tree-recent-empty { margin: 10px 4px 2px; color: var(--fgColor-muted, #59636e); font-size: 0.8125rem; text-align: center; }
|
|
137
136
|
@media (max-width: 900px) {
|
|
138
137
|
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; }
|