generated at
複数行で数式を書くためのUserScript

概要
複数行で数式をかけるようにします
マクロに登録し数式表記で参照することで表示
コードブロック直下の行で逐次レンダリング
数式のコードブロックの折りたたみ

how to use
数式間で任意のマクロを共有するためのUserScriptを入れた後、これで使っているmacroをこのスクリプトに以下のように食わせて有効化
js
import { setCodeFormula, setup } from "../MultiLineFormula/script.js" function mullineformula() { if (scrapbox.Layout === "page") { setCodeFormula(macros).then(() => { forceUpdate(); setup(macros); }) } } window.scrapbox.addListener("page:changed", mullineformula);
まとめて動作するようにしたもの : import "/api/code/RrMg4Kn7Pgk-HQwximSddmt9/katex-patch/patch.js"


ファイル名をxxx.mathとしてコードブロックに数式を書く。
直下の行に [$ \mullinedefxxx ] で参照する
参照がコードブロックの直下にあればコードブロックの編集で逐次に再レンダリングされる
コードブロックを隠したいときは▼を押す

アップデート
2022/10/07 : マクロ参照の行にインデントがある場合にクリックできない問題を修正

script.js
function getReactFiber(obj) { return obj[Object.keys(obj).find((key) => key.startsWith("__reactFiber"))] } const lines = document.getElementsByClassName("lines")[0] const linesReactObj = getReactFiber(lines) const lineObjs = linesReactObj.pendingProps.children; const textarea = document.getElementById('text-input') const textareaReactObj = getReactFiber(textarea) function getFormula(lines, lineObjs, index){ let content = "" let seekFirst = index while (true) { if (lineObjs[seekFirst].props.codeBlock.start) break; content = lines.children[seekFirst].textContent.trim() + content seekFirst -= 1 } let seekEnd = index while (true) { if (lineObjs[seekEnd].props.codeBlock.end) break; seekEnd += 1; content += lines.children[seekEnd].textContent.trim() } return [content, [seekFirst, seekEnd]] } const closure = (macros) => function keyupevent(e) { const position = textareaReactObj.return.return.stateNode.props.position; const lineObjs = linesReactObj.pendingProps.children; const cursorindex = position.line; const currentline = lineObjs[cursorindex].props if (currentline.codeBlock !== undefined && currentline.codeBlock.lang === "math"){ //console.log(currentline) const name = currentline.codeBlock.filename.slice(0, -5) const [formula, [firstLine, lastLine]] = getFormula(lines, lineObjs, cursorindex) macros["\\mullinedef"+name] = formula const firstline = lines.children[firstLine] const nextline = lines.children[lastLine+1] const nextlineObj = getReactFiber(nextline).child .return.return.stateNode; nextlineObj.updater.enqueueForceUpdate(nextlineObj) if (isSetFoldBtns(lines.children[firstLine])) { if (isFolded(nextline)) nextline.children[0].click() } else { setFoldBtns(firstline.getElementsByClassName("indent")[0] ,nextline) nextline.children[0].style.display = "none" } } } function fold(line) { if (Array.from(line.children).some(e=>e.classList.contains("code-block"))) { line.style.display = "none" return fold(line.nextSibling) } return line } function unfold(line) { if (Array.from(line.children).some(e=>e.classList.contains("code-block"))) { line.style.display = "" return unfold(line.nextSibling) } return line } function isFolded(line) { return line.children[0].style.display === "" } function isSetFoldBtns(codefirstline) { return codefirstline.getElementsByClassName("indent")[0].children[0].textContent === '▼' } function setFoldBtns(codeindentelem, codesiblingline) { const codefirstline = codeindentelem.parentElement.parentElement const foldbtn = document.createElement("span") foldbtn.style.cursor = "pointer" foldbtn.textContent = "▼" const expnbtn = document.createElement("span") expnbtn.textContent = "▶" const indent_style = codesiblingline.querySelector( "span.text > span > span > span.indent")?.getAttribute("style") ?? "" const exp_style = "cursor: pointer; padding-right: 10px;" + indent_style expnbtn.setAttribute("style", exp_style) foldbtn.onclick = (e)=>{ unfoldbtn.style.display = "" fold(codefirstline) } unfoldbtn.onclick = (e)=>{ unfoldbtn.style.display = "none" unfold(codefirstline) } codeindentelem.prepend(foldbtn) codesiblingline.prepend(unfoldbtn) } function setFold(codefirstelem) { const codesiblingline = fold(codefirstline) setFoldBtns(codefirstelem, codesiblingline) } export function setCodeFormula(macros, callback) { const tasks = [] for (const elem of lines.getElementsByClassName("code-block-start")) { const titleelem = elem.children[0] if (titleelem.title !== "math") continue const codeurl = titleelem.children[0].href const name = codeurl.slice(codeurl.lastIndexOf("/")+1, -5) tasks.push(fetch(codeurl).then( result => result.text() ).then( formula => { macros["\\mullinedef"+name] = formula } )) const codeindentelem = elem.parentElement.parentElement const codesiblingline = fold(codeindentelem.parentElement.parentElement) setFoldBtns(codeindentelem, codesiblingline) } return Promise.all(tasks) } export function setup(macros) { textarea.addEventListener('keyup', closure(macros)); }

備考
マクロ名はどうにかうまい命名に変えたい
同じファイル名でコードブロックが書かれることは気にしていない実装なのでバグる

実装メモ
lineのオブジェクトの updater.enqueueForceUpdate を呼ぶことで再レンダリングを実現している
入力の反映タイミングがよくわからなかったのでtextareaのkeyupで呼ぶことで誤魔化している
ロード時のコードブロックの判定が面倒だったのでurlから取得することにした