generated at
Mermaid記法可視化UserScript
Mermaid記法で書かれたコードを可視化するUserScript

機能概要
下図のような形で表示されます
ページ読み込み時ではコードブロックは非表示
ダイアグラムをクリックでコードブロックの表示/非表示を切り替え

バグ報告・要望等
(こちらにお願いします)
...

お試し
test.txt(mermaid)
flowchart LR A[Hard] -->|あ| B(Round) B --> C{Decision} C -->|い| D[Result 1] C -->|う| E[Result 2]

script.js
$(() => { $.getScript("https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.14.0/mermaid.min.js") .done((script, textStatus) => { mermaid.mermaidAPI.initialize({ startOnLoad: false }) const mermaidViewer = new MermaidViewer() mermaidViewer.onScrapboxPageChanged() scrapbox.on("page:changed", () => mermaidViewer.onScrapboxPageChanged()) scrapbox.on("lines:changed", () => mermaidViewer.onScrapboxLinesChanged()) }) .fail((jqxhr, settings, exception) => { console.error(exception) }) const MermaidViewer = function() { const DEFAULT_SHOW_CODE = false this.recentMermaidCodes = new Map() this.codeViewStatusRepository = new MermaidCodeViewStatusRepository() this.onScrapboxLinesChanged = function() { if (scrapbox.Page.lines) { this.updateDiagrams() } } this.onScrapboxPageChanged = function() { if (scrapbox.Page.lines) { this.updateDiagrams() this.setAllCodeViewStatus(DEFAULT_SHOW_CODE) } } // すべてのコードブロックの表示ステータスを変更 // 引数: value 表示ステータス (true|false) this.setAllCodeViewStatus = function(value) { for (const [id, code] of this.recentMermaidCodes) { code.setCodeViewStatus(value) } } // 変更があればダイアグラムを更新 this.updateDiagrams = function() { const newCodes = this.findMermaidCodes() const diff = MermaidViewerUtils.diffMermaidCodes(this.recentMermaidCodes, newCodes) for (const item of diff) { if (item.op === "delete") { item.code.deleteDiagram() } else { item.code.updateDiagram() } } this.recentMermaidCodes = newCodes } // mermaidコードをページ内から検索 // 戻り値: Map型 // キー: コードブロックのID(最初の行ID) // 値: MermaidCode this.findMermaidCodes = function() { const result = new Map() var text, filename, id, lastLineId, lineIds for (const line of scrapbox.Page.lines) { if (line.codeBlock && line.codeBlock.lang === "mermaid") { if (line.codeBlock.start) { text = "" id = line.id lineIds = new Set() } else { text += "\n" + line.text } lineIds.add(line.id) if (line.codeBlock.end) { lastLineId = line.id text = text.trim() result.set(id, new MermaidCode(id, text, lastLineId, lineIds, this.codeViewStatusRepository)) } } } return result } } const MermaidCode = function(id, text, lastLineId, lineIds, codeViewStatusRepository) { const MERMAID_SVG_ID_PREFIX = "mermaid-" this.id = id this.text = text this.lastLineId = lastLineId this.lineIds = lineIds this.codeViewStatusRepository = codeViewStatusRepository this.svgId = MERMAID_SVG_ID_PREFIX + id // mermaidダイアグラムを更新 this.updateDiagram = function() { try { const svg = mermaid.mermaidAPI.render(this.svgId, this.text) $("#" + this.svgId).remove() $("#L" + this.lastLineId).after(svg) } catch (e) { console.error(e) $("#L" + this.lastLineId).after($("#" + this.svgId)) } $("#" + this.svgId) .on("click", () => this.onSvgClicked()) .css("cursor","pointer") } // mermaidダイアグラムを削除 this.deleteDiagram = function() { $("#" + this.svgId).remove() } // mermaidダイアグラム(SVG)がクリックされたときのイベントハンドラ // コードブロックの表示ステータスを変更 this.onSvgClicked = function() { this.codeViewStatusRepository.changeStatus(this.id) this.applyCodeView() } // コードブロックの表示ステータスを適用 this.applyCodeView = function() { const status = this.codeViewStatusRepository.getStatus(this.id) for (const lineId of this.lineIds) { if (status) { $("#L" + lineId).show(100) } else { $("#L" + lineId).hide(100) } } } // コードブロックの表示ステータスを変更 this.setCodeViewStatus = function(value) { this.codeViewStatusRepository.setStatus(this.id, value) this.applyCodeView() } } const MermaidCodeViewStatusRepository = function() { this.status = new Map() this.defaultValue = true this.changeStatus = function(id) { const old = this.status.has(id) ? this.status.get(id) : this.defaultValue this.status.set(id, !old) } this.getStatus = function(id) { return this.status.has(id) ? this.status.get(id) : this.defaultValue } this.setStatus = function(id, value) { this.status.set(id, value) } } const MermaidViewerUtils = {} // 2つのMap型に格納されたコードの差分を返す // 引数: oldMap 古い値(Map型) // 引数: newMap 新しい値(Map型) MermaidViewerUtils.diffMermaidCodes = function(oldMap, newMap) { const result = [] const intersection = new Set() for (const [key, val] of newMap) { if (!oldMap.has(key)) { result.push({ op: "new", key: key, code: newMap.get(key) }) } else { intersection.add(key) } } for (const [key, val] of oldMap) { if (!newMap.has(key)) { result.push({ op: "delete", key: key, code: oldMap.get(key) }) intersection.delete(key) } } for (const key of intersection) { const oldVal = oldMap.get(key) const newVal = newMap.get(key) if (oldVal.text !== newVal.text) { result.push({ op: "changed", key: key, code: newMap.get(key) }) } } return result; } })

UserScript