marksites 0.2.4 → 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 +5 -3
- 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 +21 -5
- 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 +16 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,11 +25,11 @@ const html = markdownToHtml(markdown, {
|
|
|
25
25
|
});
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
見出しレベル2から6までの目次と、GitHub互換の見出しアンカーが自動生成されます。ファイルツリーはGitHubのFiles
|
|
28
|
+
見出しレベル2から6までの目次と、GitHub互換の見出しアンカーが自動生成されます。ファイルツリーはGitHubのFilesペインと同様、画面左端へ接する全高のサイドバーとして常時表示し、本文を中央、`目次`とコメントを画面右側へ配置します。ファイル一覧は階層ツリーと更新順を切り替えられます。更新順ではMarkdownのローカルファイル更新日時(mtime)を新しい順に日付でまとめ、連続した縦線からインデントした位置に通常ウェイトのファイル名を表示します。親ディレクトリと更新日時は通常時には隠し、ファイルへホバーすると変換ルートを`/`、配下を`/src/`とする明色の吹き出しでファイル名の右側へ直ちに表示します。日付見出しではファイル件数を確認でき、クリックで折り畳めます。初期状態では最新日と現在ファイルを含む日を開き、検索中は一致する日を一時的に展開します。左サイドバーはヘッダーの四角いペインアイコンで折り畳むことができ、閉じると本文が左へ広がります。閉じた場合はページ上部に同形の復帰アイコンと、従来どおりポップオーバーでファイル一覧を開く`ファイル`ボタンを並べます。サイドバーの閉状態は`file-sidebar=closed`、ポップオーバーは`marksites-files=open`、更新順表示は`file-view=recent`、開いたフォルダは短縮IDを値とする`open` GETパラメータでページ間に引き継がれます。ファイルパス直後のアイコンでパスをコピーできます。Markdownの更新日時は本文カード上部で、ブラウザのローカルタイムゾーンを使った`YYYY-MM-DD HH:mm:ss`形式にして表示します。タイムゾーン名やUTCオフセットは表示しません。JavaScript実行前はUTCの日時を同じ形式で表示します。現在位置に対応する見出しもハイライトされます。狭い画面では右サイドバーを本文上部、ファイルサイドバーを必要なときだけ開く固定オーバーレイとして表示します。
|
|
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
|
+
}
|
|
@@ -122,7 +122,7 @@ function renderRecentFiles(nodes) {
|
|
|
122
122
|
const groupId = grouped ? `recent-${groupNumber++}` : "";
|
|
123
123
|
const directoryLabel = directory ? `/${directory}` : "/";
|
|
124
124
|
if (grouped)
|
|
125
|
-
output.push(` <li class="file-tree-directory-group" data-recent-group="${groupId}"
|
|
125
|
+
output.push(` <li class="file-tree-directory-group" data-recent-group="${groupId}" aria-hidden="true">${renderFolderIcon()}<span>${escapeHtml(directoryLabel)}</span></li>`);
|
|
126
126
|
for (; index < end; index++) {
|
|
127
127
|
const file = files[index];
|
|
128
128
|
const current = file.current ? ' aria-current="page"' : "";
|
|
@@ -130,7 +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
|
-
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)}"
|
|
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>`);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
return output.join("\n");
|
|
@@ -202,6 +202,22 @@ export function renderFileTreeScript(enabled) {
|
|
|
202
202
|
const ignoredToggles = new WeakSet();
|
|
203
203
|
const initialOpenState = new Map();
|
|
204
204
|
|
|
205
|
+
const positionDirectoryTooltip = (link) => {
|
|
206
|
+
const name = link.querySelector('.file-tree-name-text');
|
|
207
|
+
const tooltip = link.querySelector('.file-tree-directory-tooltip');
|
|
208
|
+
const recent = link.closest('.file-tree-recent');
|
|
209
|
+
if (!name || !tooltip || !recent) return;
|
|
210
|
+
const nameRect = name.getBoundingClientRect();
|
|
211
|
+
const recentRect = recent.getBoundingClientRect();
|
|
212
|
+
tooltip.style.left = Math.min(recentRect.right + 8, innerWidth - tooltip.offsetWidth - 8) + 'px';
|
|
213
|
+
tooltip.style.top = Math.max(8 + tooltip.offsetHeight / 2, Math.min(nameRect.top + nameRect.height / 2, innerHeight - 8 - tooltip.offsetHeight / 2)) + 'px';
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
for (const link of document.querySelectorAll('.file-tree-recent-file > a')) {
|
|
217
|
+
link.addEventListener('pointerenter', () => positionDirectoryTooltip(link));
|
|
218
|
+
link.addEventListener('focus', () => positionDirectoryTooltip(link));
|
|
219
|
+
}
|
|
220
|
+
|
|
205
221
|
for (const directory of directories) {
|
|
206
222
|
const details = directory.querySelector(':scope > details');
|
|
207
223
|
details.open = openPaths.has(details.dataset.folderId);
|
|
@@ -339,8 +355,8 @@ export function renderFileTreeScript(enabled) {
|
|
|
339
355
|
group.className = 'file-tree-directory-group';
|
|
340
356
|
group.dataset.recentGroup = groupId;
|
|
341
357
|
group.dataset.localDate = dateKey;
|
|
358
|
+
group.setAttribute('aria-hidden', 'true');
|
|
342
359
|
const directoryLabel = first.dataset.directory ? '/'+first.dataset.directory : '/';
|
|
343
|
-
group.title = directoryLabel;
|
|
344
360
|
group.innerHTML = ${JSON.stringify(renderFolderIcon())};
|
|
345
361
|
const name = document.createElement('span');
|
|
346
362
|
name.textContent = directoryLabel;
|
|
@@ -351,8 +367,8 @@ export function renderFileTreeScript(enabled) {
|
|
|
351
367
|
const file = files[index];
|
|
352
368
|
const date = new Date(file.dataset.modifiedAt);
|
|
353
369
|
file.dataset.localDate = dateKey;
|
|
354
|
-
const
|
|
355
|
-
|
|
370
|
+
const rowTime = file.querySelector('.file-tree-recent-time');
|
|
371
|
+
rowTime.textContent = pad(date.getHours())+':'+pad(date.getMinutes());
|
|
356
372
|
if (grouped) {
|
|
357
373
|
file.classList.add('is-grouped');
|
|
358
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-date { margin: 10px 0 3px; }\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: flex; min-width: 0; align-items: center; gap: 5px; margin: 7px 4px 2px; padding: 4px 6px; overflow: hidden; color: var(--fgColor-muted, #59636e); font-size: 0.6875rem; font-weight: 600; line-height: 1.25; background: var(--bgColor-muted, #f6f8fa); border-radius: 4px; }\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 a { align-items: flex-start; gap: 7px; }\n .file-tree-recent-label { display: flex; min-width: 0; flex: 1; flex-direction: column; gap: 1px; }\n .file-tree-recent-label .file-tree-name { width: 100%; color: var(--fgColor-default, #1f2328); font-size: 0.8125rem; font-weight: 600; }\n .file-tree-recent-file a:hover .file-tree-name { text-decoration: underline; text-underline-offset: 2px; }\n .file-tree-directory-path { display: flex; width: 100%; min-width: 0; align-items: center; gap: 4px; overflow: hidden; color: var(--fgColor-muted, #59636e); font-size: 0.6875rem; font-weight: 400; line-height: 1.25; white-space: nowrap; }\n .file-tree-directory-path .folder-icon { width: 11px; height: 11px; flex: none; margin: 0; }\n .file-tree-directory-path > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n .file-tree-recent-file.is-grouped .file-tree-directory-path { display: none; }\n .file-tree-recent-file .file-tree-comment-count { margin-top: 1px; }\n .file-tree-recent-file > a > time { width: 34px; flex: none; padding-top: 1px; color: var(--fgColor-muted, #59636e); font-size: 0.6875rem; font-variant-numeric: tabular-nums; line-height: 1.35; }\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,7 +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-
|
|
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
|
+
.file-tree-date { position: relative; z-index: 1; margin: 10px 0 3px; background: var(--bgColor-default, #fff); }
|
|
109
111
|
.file-tree-date:first-child { margin-top: 4px; }
|
|
110
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; }
|
|
111
113
|
.file-tree-date button:hover { background: var(--bgColor-muted, #f6f8fa); }
|
|
@@ -114,19 +116,22 @@ export const fileTreeStyles = ` body.markdown-body.has-file-tree { width: 100
|
|
|
114
116
|
.file-tree-date button[aria-expanded="false"] svg { transform: rotate(-90deg); }
|
|
115
117
|
.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; }
|
|
116
118
|
.file-tree-recent .is-date-collapsed { display: none; }
|
|
117
|
-
.file-tree-directory-group { display:
|
|
119
|
+
.file-tree-directory-group { display: none; }
|
|
118
120
|
.file-tree-directory-group .folder-icon { width: 13px; height: 13px; flex: none; }
|
|
119
121
|
.file-tree-directory-group span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
120
|
-
.file-tree-recent-file
|
|
121
|
-
.file-tree-recent-
|
|
122
|
-
.file-tree-recent-
|
|
122
|
+
.file-tree-recent-file { position: relative; margin-left: 22px; }
|
|
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
|
+
.file-tree-recent-label { display: flex; min-width: 0; flex: 1; }
|
|
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; }
|
|
123
127
|
.file-tree-recent-file a:hover .file-tree-name { text-decoration: underline; text-underline-offset: 2px; }
|
|
124
|
-
.file-tree-directory-
|
|
125
|
-
.file-tree-directory-
|
|
126
|
-
.file-tree-directory-path
|
|
127
|
-
.file-tree-
|
|
128
|
-
.file-tree-
|
|
129
|
-
.file-tree-recent-file
|
|
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
|
+
.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
|
+
.file-tree-directory-tooltip-path { display: flex; max-width: 220px; align-items: center; gap: 5px; overflow: hidden; }
|
|
131
|
+
.file-tree-directory-tooltip-path .folder-icon { width: 12px; height: 12px; flex: none; margin: 0; }
|
|
132
|
+
.file-tree-directory-tooltip-path > span { overflow: hidden; text-overflow: ellipsis; }
|
|
133
|
+
.file-tree-recent-file a:hover .file-tree-directory-tooltip, .file-tree-recent-file a:focus-visible .file-tree-directory-tooltip { display: flex; }
|
|
134
|
+
.file-tree-recent-file .file-tree-comment-count { margin-top: 0; }
|
|
130
135
|
.file-tree-recent-empty { margin: 10px 4px 2px; color: var(--fgColor-muted, #59636e); font-size: 0.8125rem; text-align: center; }
|
|
131
136
|
@media (max-width: 900px) {
|
|
132
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; }
|