ScrapJupyter
ScrapJupyter
コードブロックごとにjsを実行できるUserScript
https://gyazo.com/cab41adb9f3b9d8bd01368d2bf1a3c58
jupyter notebookみたいなので、ScrapJupyterという名前にした
ただ単にevalしてるだけなので自己責任でお願いします
使い方
メニューでenable disableを切り替えられる
https://gyazo.com/1d545e50f6e23e253be81acf60343172
enableすると実行ボタンが出てくる
jsのコードブロックのみ
ボタンを押すとそのブロックのjsが実行される
ページ移動すると無効化される
ページ内の1行目にScrapJupyterと入力すると、右のメニューを押さなくても勝手に有効になる
デバッグするときとか、よくリロードするときに便利
もっと良い指定方法ないかな?
[ScrapJupyter.icon]とかどうだろうか
code:script.js
const off = 'https://i.gyazo.com/b12404a0c17e0808af3c8366419073b2.png'
const on = 'https://i.gyazo.com/fcaa624a3b739bb3cb35d7f60bf5ae39.png'
const title = 'ScrapJupyter'
let isEnable = false;
const toggle = (setEnable) => {
isEnable = (setEnable !== undefined) ? setEnable : !isEnable
$(.page-menu-extension #${title} img).attr('src', isEnable ? on : off);
if(isEnable) _enable()
else _disable()
}
function observeUrlChange(handler) {
let lastUrl = location.href;
const titleObserver = new MutationObserver((records, observer) => {
for (const record of records) {
if (lastUrl !== location.href) {
const newUrl = location.href;
const oldUrl = lastUrl;
lastUrl = location.href;
handler(newUrl, oldUrl, observer);
}
}
});
const title = document.querySelector('head title');
titleObserver.observe(title, {childList: true});
}
function loadScript() {
console.log('loadScript')
scrapbox.PageMenu.addMenu({
title,
image: off,
onClick: () => toggle()
})
const autoEnable = () => {
//const condition = scrapbox.Page?.lines1?.text === title
const condition = true //いつでもon
toggle(condition)
}
autoEnable()
const targetProject = scrapbox.Project.name;
observeUrlChange((newUrl, oldUrl, observer) => {
//ここ適当にsetTimeoutしてる
setTimeout(()=>autoEnable(),10)
if (scrapbox.Project.name !== targetProject) {
observer.disconnect();
toggle(false)
}
});
}
function getCodeBlocks() {
const lines = scrapbox.Page.lines ?? [];
const codeBlocks = lines.reduce( (acc,line) => {
if(line?.codeBlock?.lang !== "js") return acc
if(line?.codeBlock?.start)
return ...acc,{line, content: ""};
if(line?.codeBlock) {
accacc.length-1.content += line.text + "\n"
}
return acc;
},[])
return codeBlocks
}
const buttons = new Map();
const createButton = () => {
const r = $('<div>').css("position","absolute")
r.append("▶")
return r
}
const removeAllButtons = () => buttons.forEach( b => b.remove())
function appendButton() {
removeAllButtons()
getCodeBlocks().forEach( block => {
const id = block.line.id
if(!buttons.get(id)) buttons.set(id, createButton());
const b = buttons.get(id)
b.off()
const {left, top} = $([id=L${block.line.id}]).position()
b.css({left: left - 25,top, 'z-index': 900})
b.click(function() {
const block = getCodeBlocks().find( b => b.line.id === id);
(1,eval)(block.content)
})
$('.lines').append(b)
})
}
// 適当にデバウンスしておく
const debounce = (func, delay) => {
let inDebounce
return function() {
const context = this
const args = arguments
clearTimeout(inDebounce)
inDebounce = setTimeout(() => func.apply(context, args), delay)
}
}
const update = debounce(appendButton, 50)
function _enable() {
window.addEventListener('keydown', update)
appendButton()
}
function _disable() {
removeAllButtons()
window.removeEventListener('keydown', update)
}
loadScript()
中身の解説
scrapbox.Page.linesの情報から無理やりコードを復元してるだけ
コードブロックのパーツを表すlineにはcodeBlockという変数が入ってる
その下にstart, end, lang, textあたりの情報が入ってる
start →コードブロックの先頭か否か
end →コードブロックの末端か否か
lang →コードの言語(javascriptとか)
text →その行のテキスト
start, endを見ながらtextを結合してるだけ
start見つけたら新規作成して、都度textを結合、endが見えたら終了
面倒だからindentの空白もtrimしてないよ 多分動くっしょ
左側のボタンの配置
/shokai/Scrapboxの開発 - React & Websocketで作るリアルタイムWiki#583538dc97c29100000f55ad
jqueryで絶対座標とればよさそうなのでそうした
座標を計算するための親は.linesにしてある 多分合ってる
コードブロックの情報をなめる際に、先頭行のidを持っておく
これは行ごとのdivのid attributeに等しいので、jqueryで該当divが取れる
code:js
const {left, top} = $([id=L${block.line.id}]).position()
こんな感じで該当行を見つけたら、それの左の方にボタンを置くだけ
このボタンがクリックされたらevalする
debounceしながらkeydown時に適当にボタンを作り直す
eval
(1,eval)('hoge = 1')
これでwindow.hogeに入る
https://stackoverflow.com/questions/4670805/javascript-eval-on-global-scope
なんで?miyamonz.icon
ページ移動のとこ参考にした
/customize/Scrapboxで暗記シートを作る#5d6a202965ab960000ffb6f8
MutationObserver
全部一気に実行も欲しくなってくる
以下考察
evalじゃない方法を考える
普通にapi経由で呼び出しだと、ファイル名が必要 & 同名は結合されて1つのファイルとなる
これはscrapboxの仕様
なので、ファイル名ごとに実行ボタンを配置するとかはたぶん可能 やってないけど
発展型
コードブロックを実行して画面下にログを出す
他にも、適当にcreateCanvasしたりしてp5.jsとか動かしたら楽しいかも?
js実行するためにevalしたけど、なにか別のlispだとか, js上で処理できる言語を実行するのいいかも?
chrome拡張かなにかで外部に投げてみたい
http postすれば拡張も要らない?
scprapboxをなんかターンテーブルというか素材置場として、外部アプリに情報送信してDJみたいな感じ?をイメージしてる
雑に遊ぶ
code:js
window.open('https://example.com')
https://example.com 直リンと同じじゃんmiyamonz.icon
jsなので動的に動けるとかはある
code:js
const n = Date.now()
window.open(${n})
Userscriptとして読み込むほどでもないものを直ちに実行したいときとかは便利かもしれない
まず突然コードブロックを書いてバシバシ実行しながらuserscriptを書く
いい感じになってきたらちゃんとimportして動くようにする、みたいな?
scrapbox上でuserscriptでできること検証
userscript自作するときに、書いたら即実行できるのはかなり便利