Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vitepress/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import markdownItFootnote from "markdown-it-footnote";
import markdownItHeaderShift from "./lib/markdown-it-plugin-header-shift.mjs";
import markdownItMermaidSvg from "./lib/markdown-it-plugin-mermaid-svg.mjs";
import { codeThemeLight, codeThemeDark } from "./lib/code-theme.mjs";
import markdownItFenceTitle from "./lib/markdown-it-plugin-fence-title.mjs";
import lazyMermaid from "./lib/vite-plugin-lazy-mermaid.mjs";
import * as plantumlLanguage from "./lib/plantuml.tmlanguage.mjs";
const __filename = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -251,6 +252,8 @@ export default defineConfig({
md.use(markdownItHeaderShift);
md.use(markdownItTaskLists);
md.use(markdownItFootnote);
// ファイル名のタブ (#473)。mermaid より前に包んで、mermaid が外側に残るようにする
md.use(markdownItFenceTitle);
// withMermaid が先に fence を包むので、ここで包むと外側になり先に走る
md.use(markdownItMermaidSvg);
},
Expand Down
71 changes: 71 additions & 0 deletions .vitepress/lib/markdown-it-plugin-fence-title.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// @ts-check
/**
* @typedef {import('markdown-it').default} MarkdownIt
*/

/**
* コードフェンスのファイル名を出す (#473)。
*
* VitePress は情報行の `[title]` を **::: code-group の中でしか使わない**
* (extractTitle を呼ぶのは createCodeGroup だけ)。単独のフェンスでは
* extractLang が空白以降を捨てるので、`sh .gitattributes` も
* `json [example.json]` もファイル名が表示されないまま落ちる。
*
* 技術ブログの記法(言語の後ろに空白でファイル名)と、VitePress 素の
* `[title]` の両方を読む。code-group の中はタブが名乗るので触らない。
*/

/** 情報行のうち、ファイル名ではなく VitePress の指定を表す形 */
const META = /^[{:=]|^twoslash\b|^vue\b/u;

/**
* 情報行からファイル名を取り出す。
*
* @param {string} info フェンスの情報行
* @returns {string} ファイル名。無ければ空文字
*/
function extractTitle(info) {
const bracketed = info.match(/\[(.*)\]/u);
if (bracketed) return bracketed[1].trim();
// preWrapperPlugin が付ける ` active`(code-group の1枚目)を落としてから見る
const rest = info
.replace(/\[.*\]/u, "")
.replace(/\s+active$/u, "")
.trim();
const spaced = rest.match(/^\S+\s+(.+)$/u);
if (!spaced) return "";
const title = spaced[1].trim();
return META.test(title) ? "" : title;
}

/**
* このフェンスが ::: code-group の中にあるか。
*
* @param {import('markdown-it/index.mjs').Token[]} tokens
* @param {number} idx
* @returns {boolean}
*/
function inCodeGroup(tokens, idx) {
for (let i = idx - 1; i >= 0; i--) {
if (tokens[i].type === "container_code-group_close") return false;
if (tokens[i].type === "container_code-group_open") return true;
}
return false;
}

/**
* @param {MarkdownIt} md
*/
export default function fenceTitle(md) {
const fence = md.renderer.rules.fence;
if (!fence) return;
md.renderer.rules.fence = (...args) => {
const [tokens, idx] = args;
const title = extractTitle(tokens[idx].info);
const html = fence(...args);
if (!title || inCodeGroup(tokens, idx)) return html;
const label = `<span class="fence-title">${md.utils.escapeHtml(title)}</span>`;
// preWrapperPlugin が返す <div class="language-…"> の直後に差し込む
return html.replace(/^(<div class="language-[^"]*">)/u, `$1${label}`);
};
}
22 changes: 22 additions & 0 deletions .vitepress/theme/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,28 @@ h6 {
scrollbar-color: var(--vp-c-divider) transparent;
}

/* コードフェンスのファイル名 (#473)。ブロックの上に 1 行として置く。

はじめは言語ラベル(span.lang)と同じように pre の上の余白(20px)へ
絶対配置で浮かせていたが、名前とコードの 1 行目の間が 3.6px しか空かず、
名前が中身に貼り付いて読みにくかった。流し込みに戻すと、pre がもとから
持っている上の余白 20px がそのまま名前と中身の間隔になる。

上端(2px)と右の余白は言語ラベルに合わせる。ラベルはカーソルを乗せると
コピーボタンに譲って消えるが、ファイル名はブロックの名前なので消さない。
色は言語ラベルより一段強い段(コードの地で 5.62)*/
.vp-doc [class*="language-"] > span.fence-title {
display: block;
padding: 2px 72px 0 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
line-height: 1.4;
user-select: none;
color: var(--vp-c-text-2);
}

/* 表は縞をやめ、ヘッダー行だけに地色を敷く (#408)。技術ブログ(Zenn と同じ構成)。
構造の起点であるヘッダーが目立ち、行数の多い表でも面がうるさくならない */
.vp-doc tr:nth-child(2n) {
Expand Down