alosaur-lite + deno deploy で markdown の表示
データとか
コード置き場
雰囲気はつかめたので、Zenn のスクラップを対象にしてやってみる
準備の準備
↓これの markdown の example をベースにする
とりあえずやること
- clone するなりして、alosaur-lite の example/markdown 一式を取得する
- (別にする必要はないが、main.ts を server.ts にリネームする)
- このままでは
deployctl
では動くものの実際の deploy には失敗するので、server.ts を修正する - 特に理由はないが、markdown パーサーを deno 版のものに変更する
server.ts (main.ts) の修正
// 33行あたり
async function getMarkDownPage(path: string) {
if (globalRenderCache.has(path)) {
return globalRenderCache.get(path);
}
let result = null;
- let rootPath = import.meta.url;
- rootPath = rootPath.substring(0, rootPath.lastIndexOf("/") + 1);
-
- const request = new Request(rootPath + "views/" + path);
- const response = await fetch(request);
-
- if (response && response.status !== 404) {
- const text = await response.text();
- result = getHtmlPage(Marked.parse(text));
- }
+ const text = await Deno.readTextFile(`${Deno.cwd()}/views/${path}`);
+ result = getHtmlPage(Marked.parse(text));
globalRenderCache.set(path, result);
return result;
}
// ...
deno 版 markdown へのパーサーの変更
-import { Marked } from "https://jspm.dev/@ts-stack/markdown";
+import { Marked } from 'https://deno.land/x/markdown@v2.0.0/mod.ts';
// ...
// 上のスクラップで書き換えた部分の末尾
- result = getHtmlPage(Marked.parse(text));
+ result = getHtmlPage(Marked.parse(text).content);
// ...
markdown のパーサー
- Marked
- @ts-stack/markdown:Marked の written in TypeScript な fork 版
-
markdown:@ts-stack/markdown の deno 用 fork 版
少なくとも @ts-stack/markdown
と deno 版のmarkdown
には特に大きな違いはないっぽい[1]
-
でも deno 版の
markdown
には document がないので、結局@ts-stack/markdown
(https://github.com/ts-stack/markdown) の doc を読むことになる ↩︎
Zenn のスクラップを対象にしてやってみる
スクラップにおいて『JSONで内容を出力』を行い、blob.json
として保存
↓
markdown 部分を適当に切り出す 確認のため、とりあえず5個目以降は無視
const json_text = await Deno.readTextFile(`blob.json`);
const json_obj = JSON.parse(json_text);
const page_title = json_obj.title;
const sep_post = "\n\n<br>\n\n--------\n\n--------\n\n<br>\n\n"
let md_text_list: string[] = [];
json_obj.comments.forEach((elem: any, idx: number) => {
let base_md: string = elem.body_markdown;
if ("children" in elem) {
elem.children.forEach((child: any) => {
base_md += "\n\n<br><br>\n\n" + child.body_markdown;
})
}
md_text_list.push( (idx>0)? sep_post + base_md : base_md)
})
const out = md_text_list.slice(0,4).join()
await Deno.writeTextFileSync("views/scrap.md", out);
console.log("end!!")
HTML 側の調整
フォントを Kosugi Maru に変える あと、最近増えたらしい Kaisei Decol も使ってみる
<head>
<meta charset="utf-8"/>
<title>Alosaur-Lite + Deno Deploy で Markdown をあれこれする</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/4.0.0/github-markdown.min.css">
+ <link rel="preconnect" href="https://fonts.googleapis.com">
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+ <link href="https://fonts.googleapis.com/css2?family=Kosugi+Maru&display=swap" rel="stylesheet">
+ <link href="https://fonts.googleapis.com/css2?family=Kaisei+Decol:wght@500&display=swap" rel="stylesheet">
ヘッダーメニューを追加する CSSよくわからないので調べて出てきたものを適当に調整
<style>
header {
width: 100vw;
padding: 10px;
padding-left: 50px;
background-color: #f0f8ff;
position: absolute;
display: flex;
top: 0;
left: 0;
align-items: center;
font-family: 'Kaisei Decol';
}
</style>
<!-- ... -->
<header>
<h3>Alosaur-Lite + Deno Deploy で Markdown をあれこれする</h3>
<nav class="pc-nav">
<ul style="list-style:none; display:flex;">
<li style="margin: 0 0 0 15px;"><a href="#1">特に</a></li>
<li style="margin: 0 0 0 15px;"><a href="#2">どこにも</a></li>
<li style="margin: 0 0 0 15px;"><a href="#3">飛ばない</a></li>
</ul>
</nav>
</header>
Kosugi Maru は本文だけに適用したいので直書き
あとスクロールについてくるサイドバーも追加
<style>
.container {
display: -ms-flexbox;
display: -webkit-box;
display: -webkit-flex;
display: flex;
margin: 0 auto;
}
.markdown-body {
max-width: 780px;
}
.sidebar {
width: 200px;
margin:100px 0px 0px 50px;
font-family: 'Kaisei Decol';
}
.sidebar-fixed {
position: sticky;
top: 100px;
}
</style>
<!-- ... -->
<body>
<div class="container">
<div class="markdown-body" style="margin-top:100px;font-family:'Kosugi Maru'">
${content}
</div>
<aside class="sidebar">
<div class="sidebar-fixed">サ<br>イ<br>ド<br><br><br><br><br>バー</div>
</aside>
</div>
</body>
初期状態
server.ts の中身
読み込む markdown のファイル名を scrap.md
に変えているので注意
import { Marked } from 'https://deno.land/x/markdown@v2.0.0/mod.ts';
import { App, Controller, Get, View } from "https://deno.land/x/alosaur_lite/dist/mod.js";
import { getHtmlPage } from "./html-page.ts";
@Controller()
export class MainController {
@Get()
indexPage() {
return View("scrap.md", {});
}
}
const app = new App({
controllers: [MainController],
});
app.useViewRender({
type: "markdown",
basePath: `/views/`,
getBody: async (path: string, model: Object, config: any) => {
return await getMarkDownPage(path);
},
});
const globalRenderCache = new Map();
async function getMarkDownPage(path: string) {
if (globalRenderCache.has(path)) {
return globalRenderCache.get(path);
}
let result = null;
const text = await Deno.readTextFile(`${Deno.cwd()}/views/${path}`);
result = getHtmlPage(Marked.parse(text).content);
globalRenderCache.set(path, result);
return result;
}
addEventListener("fetch", (event: FetchEvent) => {
event.respondWith(app.handleRequest(event.request));
});
シンタックスハイライト
highlight.js を使ってシンタックスハイライトを適用する[1]
Marked の Doc に記載されている方法が利用できたので、server.ts
に以下の部分を追加
import highlightJs from 'https://cdn.skypack.dev/highlight.js?dts';
// ...
marked.setOptions({
highlight: function(code, lang) {
const language = highlightJs.getLanguage(lang) ? lang : 'plaintext';
return highlightJs.highlight(code, { language }).value;
},
// langPrefix: 'hljs language-', // これは無くても問題ない(と思う)
});
HTML に 使用する highlight.js テーマを追加
<head>
<!-- ... -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/styles/xcode.min.css">
-
prism.js も試してみたが、とりあえず skypack から持ってくる方法ではだめだった。知識なさすぎて何がだめなのかもわからない・・・ ↩︎
インラインのコードにもハイライトを適用する
パーサーの HTML タグ設定に介入する処理は、markdown の Renderer を override して実現する[1]
今回なら、codespan(text: string): string { ... }
を書き直すことになる
やること
「インラインコードもコードブロックと同じ処理にする → ハイライトされる」と考えて、super
を使って override 前のcode()
の処理を適用させ、不要な<pre>
タグを取り除く
// Renderer も import 対象に追加する
import { Marked, Renderer } from 'https://deno.land/x/markdown@v2.0.0/mod.ts';
// ...
class MyRenderer extends Renderer{
// インラインコードの処理
codespan(text: string): string {
const lang = "python"; // 手動で設定...
//コードブロックと同じ処理を行うことで、ハイライト処理を適用させる
const code_out = super.code(text.replace(/\"\;/g, '"'), lang)
const matched = code_out.match(/<pre>([\S\s]+)<\/pre>/)
if (matched !== null) {
return matched[1].replace(/\n<\/code>/, "</code>");
} else {
return code_out
}
}
}
Marked.setOptions({
renderer: new MyRenderer, // oberride した Renderer を使う
highlight: function(code, lang) {
// ...
},
})
-
@ts-stack/markdow の Doc が参考になる + その下にある API から各処理の定義に飛べる ↩︎
行番号を表示する
highlight.js には行番号を表示させるプラグイン highlightjs-line-numbers.js があるが、DOMを操作する?想定になっているようで Marked の処理に組み込む感じでは動かなかった
↓
実装を見ると、(少なくともこの部分は)複雑な処理をしていないので、コピペして調整する
要するに、highlight.js がタグをつけたコードブロックの各行の文字列データを
<tr>
<td data-line-number="${行番号}">${行番号}</td>
<td data-line-number="${行番号}">${コード}</td>
</tr>
に変換し、最後に<table>
で囲むという処理になる
ただし、そのままでは CSS のテーブル設定が適用されてしまうので、適当に上書きする
<tr style="border-top:0px">
<td style="border:0px; border-right:1px solid #b9b9b9; padding:0px 7px 0px 0px" data-line-number="${行番号}">${行番号}</td>
<td style="border:0px;padding:0px 0px 0px 7px" data-line-number="${行番号}"> ${コード}</td>
</tr>
というわけで以下のような関数を作り
function addLineNumbers (inputHtml: string, use_singleLine=false, startFrom=1): string{
var lines = inputHtml.split(/\r\n|\r|\n/g)
if (lines[lines.length-1].trim() === '') {
lines.pop();
}
if (lines.length > 1 || use_singleLine) {
var html = '';
lines.forEach((li, idx) => {
html += `<tr style="border-top:0px;">`
+ `<td style="border:0px; border-right:1px solid #b9b9b9; padding:0px 7px 0px 0px" data-line-number="${idx + startFrom}">${idx + startFrom}</td>`
+ `<td style="border:0px;padding:0px 0px 0px 7px" data-line-number="${idx + startFrom}"> ${li.length > 0 ? li : ' '}</td></tr>`;
})
return `<table style="margin:0px;">${html}</table>`;
}
return inputHtml;
}
行番号の表示の有無を変更できるような雰囲気を出しつつ、ハイライト処理に追加する
// ...
+const ADD_LINE_NUM = true
//...
Marked.setOptions({
renderer: new MyRenderer,
highlight: function(code, lang) {
const language = highlightJs.getLanguage(lang) ? lang : 'plaintext';
- return highlightJs.highlight(code, { language }).value;
+ const out = highlightJs.highlight(code, { language }).value;
+ return ADD_LINE_NUM? addLineNumbers(out) : out
},
})
これで行番号の追加は実現できるが、末端に謎の空白が入るという現象が起こる
これは、Renderer が "... ${タグがついたコード}\n</code></pre>\n"
返す処理になっているために、"...</table>\n</code></pre>"
となって表の下に改行が入ってしまうため
↓
codespan()
と同様に code()
も override して "\n" を入れないようにする
class MyRenderer extends Renderer{
code(code: string, lang?: string, escaped?: boolean, meta?: string): string {
const [c, l, e] = [...arguments] // なぜか引数は3つしか入っていない meta はどこへ?
const code_out = super.code(c, l, e);
return code_out.replace(/\n<\/code>/, "</code>");
}
codespan(text: string): string {
// ...
}
}
diff の表示
「行番号の処理 = コード各行を逐次処理 → diff も判定できる!」ということで diff も表示
addLineNumbers 関数に以下の処理を追加する[1]
- 行頭の "+" と "-" の有無をチェックする
- 結果に応じて "+" と "-" を番号の方に移す
- 結果に応じて
<tr>
の背景色を変える
function addLineNumbers (inputHtml: string, use_singleLine=false, startFrom=1): string{
// ...
if (lines.length > 1 || use_singleLine) {
//...
lines.forEach((li, idx) => {
+ let diff_prefix = "";
+ let bc_color = "#f6f8fa";
+ if (li.length) {
+ if (li[0]=="-") {
+ diff_prefix = " -";
+ li = li.replace(/^-/g, "");
+ bc_color = "#ffe6e6";
+ } else if (li[0]=="+") {
+ diff_prefix =" +";
+ li = li.replace(/^\+/g, "");
+ bc_color = "#c8f5d0";
+ }
+ const num_text = `${idx + startFrom}${diff_prefix}`;
+ html += `<tr style="border-top:0px; background-color:${bc_color}">`
+ + `<td style="border:0px; border-right:1px solid #b9b9b9; padding:0px 7px 0px 0px" data-line-number="${idx + startFrom}">${num_text}</td>`
//....
-
つまり行番号の表示が無いときは別の diff 表示関数が必要なわけで、この辺の面倒さが highlight.js が行番号の表示を導入しない宣言を出している理由の1つなのかなと思った ↩︎
ここまでの結果
行番号の有無でメイン表示部の幅が変わるなど、意図しない副作用が発生している感がすごい
サイドメニュー作成
自分の見出しの振り方が下手すぎてあれなのだが、適宜調整してサイドメニューに各<h1>
要素へのリンクを置いてみる
方法
- まず、
<h>
要素をパースする際に、内容と id 要素から<a>
タグを作り、リストに格納する - パースが完了したあとで
<a>
タグリストの中身を結合し、サイドメニューの html を作る -
getHtmlPage
関数を変更し、サイドメニューに渡した html を流し込むようにする
heading()
を override する
//...
let HEADER_STORE: string[] = [] // こういうグローバル変数の使い方はやっぱよくないんだろうか?
// ...
class MyRenderer extends Renderer{
heading(text: string, level: number, raw: string): string {
const id: string = this.options.headerPrefix + String(text).trim().toLowerCase().replace(/\s+/g, '-');
if (level==1) {
HEADER_STORE.push(`<a href="#${id}">${text}</a>`);
}
return `<h${level} id="${id}">${text}</h${level}>\n`;
}
//...
リストの中身を結合して getHtmlPage()
にわたす
async function getMarkDownPage(path: string, config: any) {
// ...
const text = await Deno.readTextFile(`${Deno.cwd()}/views/${path}`);
+ const parsed = Marked.parse(text).content;
+ const side_texts = HEADER_STORE.join("<br><br>");
+ HEADER_STORE = [];
- result = getHtmlPage(Marked.parse(text).content);
+ result = getHtmlPage(parsed, side_texts);
getHtmlPgae()
の引数を増やし、サイドメニューの中に流すようにする
export function getHtmlPage(content: string, side_content: string) {
return `
/...
<aside class="sidebar">
<div class="sidebar-fixed">${side_content}</div>
</aside>
/ ...
`
ここまでの結果
サイドバーの幅やらマージンやらを調整して完成
注釈の処理
未調整の場合、こんな感じになる (スクラップのセル?の切れ目のEND_OF_SCRAP
については後述)
- 問題1:注釈番号がリンクされているのに注釈の内容が表示されていない
- 問題2:注釈番号の表示が適切でない (記号
^
が残っている) - 問題3:そもそもリンクになっていない注釈がある
リンク化された注釈の調整
リンク化された注釈でやるべきこと
- 表示を
^NUM
から(NUM)
に直す +<sup>
タグを使って上付き文字にする - リンク先として
href="#fn:NUM"
を設定する - リンク先から戻ってこれるように、
id="#fn_ref:NUM"
を設定する
というわけで、注釈番号の部分で使う html タグを返してくれる関数を追加し
function get_refnumber_html (ft_count: number, content: string): string{
const ref_text = `
<sup id="fn_ref:${ft_count}">
<a href="#fn:${ft_count}">(${ft_count})</a>
</sup>`
return ref_text
}
Renderer の リンクの処理 link()
を override する
// ...
class MyRenderer extends Renderer{
// ...
link(href: string, title: string, text: string): string {
if (text.match(/\^(\d+)/) !== null) {
let footnote_count = Number(text.match(/\^(\d+)/)![1])
return get_refnumber_html(footnote_count, href)
}
return super.link(href, title, text)
}
// ...
ただ、これでは画像の ^1 の部分が (1) になるものの注釈が表示されていないのは変わらない
というわけで、以下の方法で注釈を表示させることにする
- リンク化された注釈を処理する際に、注釈の内容を変数
FOOTNOTE_STORE
に格納する - 1まとまりの『親スクラップ+子コメントスクラップ』のパースが終了した時点で
FOOTNOTE_STORE
を確認し、中身があれば注釈部分の html を差し込む -
FOOTNOTE_STORE
を空にし、次の『親スクラップ+子コメントスクラップ』のパースへ
まず準備として、scrap の markdown 切り出し処理を変更し、区切り線ではなくEND_OF_SCRAP
を差し込むようにして、markdown を取得し直す
で、FOOTNOTE_STORE
なのだが、せっかくなので interface を使ってみる
// ...
interface FtnoteInfo {
index : number; // 注釈番号
text : string; // 注釈の内容
is_tip_set : boolean; // 後で使う
}
// 注釈番号でソートしたいので、配列ではなく Map にする
let FOOTNOTE_STORE = new Map<number, FtnoteInfo>();
上でいじった link()
を、これを使う形式に変更
// ...
class MyRenderer extends Renderer{
// ...
link(href: string, title: string, text: string): string {
if (text.match(/\^(\d+)/) !== null) {
let footnote_count = Number(text.match(/\^(\d+)/)![1])
+ const ft_info: FtnoteInfo = {
+ index: footnote_count,
+ text: href,
+ is_tip_set: true
+ }
+ FOOTNOTE_STORE.set(footnote_count, ft_info);
return get_refnumber_html(footnote_count, href);
}
return super.link(href, title, text);
}
// ...
さらに注釈部分の html を返してくれる関数を追加し
function make_footnote(): string{
const keys = [...FOOTNOTE_STORE.keys()].sort(); // 注釈番号順に並べ替える
const start_num = keys[0]; //一番若い番号を取得
// .map を使って<li><a> で囲んだ注釈のリストを作る
const L = keys.map((idx:number) =>{
const ft_info = FOOTNOTE_STORE.get(idx)
if (ft_info !== undefined){
// 注釈番号のid: fn_Ref:NUM、注釈のid: fn:NUM
const ft_text = `
<li id="fn:${ft_info.index}" >
<font size="1">${ft_info.text}</font>
<a href="#fn_ref:${ft_info.index}"> ↩</a>
</li>`;
return ft_text;
} else {
return ""
}
})
FOOTNOTE_STORE = new Map<number, FtnoteInfo>(); // FOOTNOTE_STORE のクリア
if (L.length) {
// .map で得た注釈のリストを <ol> で囲む
const ft_htmls = `
<div style="border-bottom:1px solid #b9b9b9;font-size:90%">脚注</div>
<ol start="${start_num}" style="color:dimgray;">
${L.join("\n")}
</ol><br>`;
return ft_htmls;
} else {
return ""
}
}
END_OF_SCRAP
があったときに注釈を差し込むように、Renderer の paragraph
を override
///...
class MyRenderer extends Renderer{
// ....
paragraph(text: string): string {
if (text.includes(SCRAP_SEPARATION)) {
let ft_html = ""
if ([...FOOTNOTE_STORE.keys()].length) {
ft_html = make_footnote()
}
// 注釈の有無によらず、END_... は 区切り線に置き換える
return `${ft_html}<hr><hr>\n`
}
return super.paragraph(text);
}
// ...
さらに、末尾のスクラップのために、ファイル全体のパースが終わったあとに未処理の注釈を差し込む処理も追加
async function getMarkDownPage(path: string, config: any) {
// ...
const text = await Deno.readTextFile(`${Deno.cwd()}/views/${path}`);
let parsed = Marked.parse(text).content;
+ if ([...FOOTNOTE_STORE.keys()].length) {
+ parsed = parsed + make_footnote()
+ }
//...
こんな感じになる
注釈のツールチップ化
以前から試してみたかった、『注釈内容のツールチップ表示』をやってみる
と言っても、↓で説明されている CSS を注釈番号の部分に追加するだけでできてしまう[1]
ただし、ツールチップは1行で表示されるが注釈が長文だと読みづらくなるので、適宜 style を上書きする
というわけで、tooltip
やらの設定を html-page.ts
に追加した上で、 get_refnumber_html()
が返す html を少し変更する
function get_refnumber_html (ft_count: number, content: string): string{
+ let style_setting = ""
+ if (content.length > 30) {
+ style_setting = ' style="min_width=450px; white-space: break-spaces;"'
+ }
const ref_text = `
- <sup id="fn_ref:${ft_count}">
+ <sup class="tooltip" id="fn_ref:${ft_count}">
<a href="#fn:${ft_count}">(${ft_count})</a>
+ <span class="tooltip-text"${style_setting}>${content}</span>
</sup>`;
return ref_text
}
マウスを乗せると注釈の内容を見ることができる (表示位置は少し調整している)
-
これだけで実現するのめっちゃ感動した。CSS いじるの本当に苦手なので、こういう情報はとても助かる ↩︎
リンク化されていない注釈の調整
[^NUM]
を注釈番号の表示に変え、注釈へのリンクを張る
文中の 基本的には、文章中に[^NUM]
があった場合にリンクと同じ処理をすれば良いので
-
.matchAll(/\[\^(\d+)\]/g)
で[^NUM]
の有無を調べる (ただし[^NUM]:
は避ける) - ヒットした部分の数字をもとに注釈番号としての html を取得する
- 取得した html で
[^NUM]
を置き換える
というわけで、paragraph()
の overrider をさらに追加する
class MyRenderer extends Renderer{
// ...
paragraph(text: string): string {
// 注釈内容の html を追加する処理
if (text.includes(SCRAP_SEPARATION)) {
//...
}
// 文中の注釈内容 ( [^1]: ) の処理:今はなにもしない
if ([...text.matchAll(/^\[\^(\d+)\]:/g)].length) {
return super.paragraph(text)
}
// 文中の注釈番号 ( [^1] ) の処理
if ([...text.matchAll(/\[\^(\d+)\]/g)].length) { // if の前に変数定義を置きたくないけど、汚い・・・
[...text.matchAll(/\[\^(\d+)\]/g)].forEach( (matched: string[]) => {
// 基本は link の場合と同じだが、注釈の内容を取得できないので仮のもので処理
const footnote_count = Number(matched[1]);
const ft_info: FtnoteInfo = {
index: footnote_count,
text: `TEMP_TOOLTIP_${footnote_count}`, // 仮の注釈の内容
is_tip_set: false // 注釈が仮であるフラグ (注釈が仮 = ツールチップの内容が不正確)
};
FOOTNOTE_STORE.set(footnote_count, ft_info);
const new_html = get_refnumber_html(footnote_count, `TEMP_TOOLTIP_${footnote_count}`);
text = text.replace(matched[0], new_html); // 取得した html で置き換え
})
return `<p>${text}</p>\n`
}
return super.paragraph(text);
}
こんな感じになる
[^NUM]:
を注釈の表示に変え、注釈番号へのリンクを張る
文中の 見た目は1文の中に複数の [^NUM]:
があるように見えるが、内部的にはちゃんと(markdown で入力している)\n
が間に入っている
というわけで、
-
\n
で切り分ける - それぞれの部分から注釈番号と注釈内容を取り出す
- 注釈番号を使って
FOOTNOTE_STORE
からオブジェクトを取り出し、.text
の内容をTEMP_TOOLTIP_*
から正しい注釈内容に差し替える
class MyRenderer extends Renderer{
// ...
paragraph(text: string): string {
// 注釈内容の html を追加する処理
if (text.includes(SCRAP_SEPARATION)) {
//...
}
// 文中の注釈内容 ( [^1]: ) の処理
if ([...text.matchAll(/^\[\^(\d+)\]:/g)].length) {
const text_lis = text.split(/\n/g);
text_lis.forEach((tx:string) => {
const [num, ref_text] = [...tx.matchAll(/^\[\^(\d+)\]: ([\s\S]+)/g)][0].slice(1);
const footnote_count = Number(num);
if (FOOTNOTE_STORE.has(footnote_count)) {
FOOTNOTE_STORE.get(footnote_count)!.text = ref_text
}
})
return ""
}
// 文中の注釈番号 ( [^1] ) の処理
if ([...text.matchAll(/\[\^(\d+)\]/g)].length) {
// ...
}
return super.paragraph(text);
}
こんな感じになる
ツールチップの差し替え
-
make_footnote()
での注釈作成時に.is_tip_set
フラグをチェックし、差し替えが必要な注釈、つまり<span>TEMP_TOOLTIP_*</span>
になっているものを判定する - 差し替えが必要な注釈は、変数
TOOLTIP_STORE
に格納する - 全ての markdown のパースが終わったあとで
TOOLTIP_STORE
を確認し、中身があれば、それぞれについて 「TEMP_TOOLTIP_*
→ 適当な内容」の差し替えを行う - 差し替える際には注釈の長さを確認し、長いものは
<span>
タグに style を差し込む形で差し替えを行う
+let TOOLTIP_STORE= new Map<string, string>();
// ...
function make_footnote(): string{
// ...
const L = keys.map((idx:number) =>{
const ft_info = FOOTNOTE_STORE.get(idx)
// ...
+ if (!ft_info.is_tip_set) {
+ TOOLTIP_STORE.set(`TEMP_TOOLTIP_${idx}`,ft_info.text);
+ }
return ft_text;
} else {
return ""
}
})
// ...
}
// ...
async function getMarkDownPage(path: string, config: any) {
// ...
const text = await Deno.readTextFile(`${Deno.cwd()}/views/${path}`);
let parsed = Marked.parse(text).content;
if ([...FOOTNOTE_STORE.keys()].length) {
parsed = parsed + make_footnote()
}
+ if ([...TOOLTIP_STORE.keys()].length) {
+ TOOLTIP_STORE.forEach((tooltip_text: string, target_txt: string) => {
+ if (tooltip_text.length > 30) {
+ const syle_setting = ' style="min-width:450px; white-space: break-spaces;"';
+ parsed = parsed.replace(`>${target_txt}`, `${syle_setting}}>${tooltip_text}`);
+ } else {
+ parsed = parsed.replace(target_txt, tooltip_text);
+ }
+ })
+ }
+ TOOLTIP_STORE = new Map<string, string>();
const side_texts = HEADER_STORE.join("<br><br>");
HEADER_STORE = [];
// ...
ここまでの結果
連番の処理
悩み中・・・
markdown 内で注釈番号に重複がある場合、Renderer に来る前の時点でパーサーによるリンク張り自体が失敗するっぽい
失敗するというか『同名のリンクならば同名のとこに飛ぶ』という当たり前の法則にしたがっているわけで、単一で完結する想定でできているスクラップをまとめて処理しているのが良くないな、これ
json からの markdown の切り出しとパースをセットでやるように変える必要がある
debug()
使えば BlockLexer.parser() の結果が見れるけど、リンク張りここで介入できないかな
ファイル名付きのコードブロックの処理
ミニページ(アコーディオン・トグル)周り
ts_stack/markdown の説明にあるように、Marked.setBlockRule
を使えば対処できる
これ系の独自記法は大体コードブロックとしてパースされるので、code()
内でフラグに応じて処理を変えるようにして力技で実現させることもできる
その際、Renderer 内で Marked.parse(code.replace(/\t/, ""))
とかするとミニページ内の文章をパースさせることができる