複数行で数式を書くためのUserScript
概要
複数行で数式をかけるようにします
マクロに登録し数式表記で参照することで表示
コードブロック直下の行で逐次レンダリング
数式のコードブロックの折りたたみ
how to use
code: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 ]で参照する
https://gyazo.com/7bf54cb1f9a9ece138d3b5159b2981fc
参照がコードブロックの直下にあればコードブロックの編集で逐次に再レンダリングされる
コードブロックを隠したいときは▼を押す
アップデート
2022/10/07 : マクロ参照の行にインデントがある場合にクリックできない問題を修正
code:script.js
function getReactFiber(obj) {
}
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 (lineObjsseekFirst.props.codeBlock.start) break; content = lines.childrenseekFirst.textContent.trim() + content seekFirst -= 1
}
let seekEnd = index
while (true) {
if (lineObjsseekEnd.props.codeBlock.end) break; seekEnd += 1;
content += lines.childrenseekEnd.textContent.trim() }
}
const closure = (macros) => function keyupevent(e) {
const position = textareaReactObj.return.return.stateNode.props.position;
const lineObjs = linesReactObj.pendingProps.children;
const cursorindex = position.line;
if (currentline.codeBlock !== undefined && currentline.codeBlock.lang === "math"){
//console.log(currentline)
const name = currentline.codeBlock.filename.slice(0, -5)
const nextlineObj = getReactFiber(nextline).child
.return.return.stateNode;
nextlineObj.updater.enqueueForceUpdate(nextlineObj)
if (isSetFoldBtns(lines.childrenfirstLine)) { if (isFolded(nextline)) nextline.children0.click() } else {
setFoldBtns(firstline.getElementsByClassName("indent")0 ,nextline) nextline.children0.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.children0.style.display === "" }
function isSetFoldBtns(codefirstline) {
return codefirstline.getElementsByClassName("indent")0.children0.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.children0 if (titleelem.title !== "math") continue
const codeurl = titleelem.children0.href const name = codeurl.slice(codeurl.lastIndexOf("/")+1, -5)
tasks.push(fetch(codeurl).then(
result => result.text()
).then(
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から取得することにした