ScrapJupyter
ScrapJupyter
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 title = 'ScrapJupyter'
let isEnable = false;
const toggle = (setEnable) => {
console.log('scrapJupyter toggle', setEnable)
isEnable = (setEnable !== undefined) ? setEnable : !isEnable
$(.page-menu-extension #${title} img)?.attr('src', isEnable ? on : off);
if(!isEnable) removeAllButtons()
}
let loaded = false
function loadScript() {
if(loaded) return
loaded = true
console.log('scrapJupyter loadScript')
scrapbox.PageMenu.addMenu({
title,
image: off,
onClick: () => toggle()
})
toggle(true)
scrapbox.on('lines:changed', ()=>appendButton())
const targetProject = scrapbox.Project.name;
scrapbox.on('project:changed', () => {
if (scrapbox.Project.name !== targetProject) {
toggle(false)
}
})
scrapbox.on('layout:changed', ()=>{
const isPage = scrapbox.Layout === 'page';
toggle(isPage)
})
}
function getCodeBlocks() {
const lines = scrapbox.Page.lines ?? [];
const codeBlocks = lines.reduce( (acc,line) => {
if(line?.codeBlock?.lang !== "js") return acc
if(line?.codeBlock?.start)
if(line?.codeBlock) {
}
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() {
if(!isEnable) return
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)
})
}
loadScript()
中身の解説
scrapbox.Page.linesの情報から無理やりコードを復元してるだけ
コードブロックのパーツを表すlineにはcodeBlockという変数が入ってる
その下にstart, end, lang, textあたりの情報が入ってる
start →コードブロックの先頭か否か
end →コードブロックの末端か否か
lang →コードの言語(javascriptとか)
text →その行のテキスト
start, endを見ながらtextを結合してるだけ
start見つけたら新規作成して、都度textを結合、endが見えたら終了
面倒だからindentの空白もtrimしてないよ 多分動くっしょ
左側のボタンの配置
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に入る
なんで?miyamonz.icon
ページ移動のとこ参考にした
全部一気に実行も欲しくなってくる
以下考察
evalじゃない方法を考える
普通にapi経由で呼び出しだと、ファイル名が必要 & 同名は結合されて1つのファイルとなる
これはscrapboxの仕様
なので、ファイル名ごとに実行ボタンを配置するとかはたぶん可能 やってないけど
発展型
他にも、適当にcreateCanvasしたりしてp5.jsとか動かしたら楽しいかも? js実行するためにevalしたけど、なにか別のlispだとか, js上で処理できる言語を実行するのいいかも?
chrome拡張かなにかで外部に投げてみたい
http postすれば拡張も要らない?
scprapboxをなんかターンテーブルというか素材置場として、外部アプリに情報送信してDJみたいな感じ?をイメージしてる
雑に遊ぶ
code:js
jsなので動的に動けるとかはある
code:js
const n = Date.now()
window.open(${n})
Userscriptとして読み込むほどでもないものを直ちに実行したいときとかは便利かもしれない
まず突然コードブロックを書いてバシバシ実行しながらuserscriptを書く
いい感じになってきたらちゃんとimportして動くようにする、みたいな?
userscript自作するときに、書いたら即実行できるのはかなり便利