generated at
Inboxに素早く入れるUserScript
inbox (GTD)に少ない手間で投入するUserScript
inbox (GTD)リスト的に作るっている人向け

操作方法
二通りある
scrapboxのeditorに書いた文字列を選択してinbox (GTD)に送る
選択範囲ではなく、選択範囲に被っている行を全て転送する
2022-11-29 11:38:04 全く使わないどころか、必要ないタイミングで誤爆して非常にうざったいので削除する
modal windowに入力して送る
選択範囲がない場合のみこちらが起動する
Screenshot
script.ts を入れた結果と動画のUIとは違うので注意してください
script.ts だと、page-info-menuの中にボタンが追加される


機能
タブを開かずにinbox (GTD)ページに項目を追加できる
インデントがぶら下がっている項目はページとして予め切り出しておく
inbox (GTD)にはページへのリンクを書き込んでおく
書式
タイトル:project (GTD)の識別子を先頭につける
projectではないとわかったら手動で外す
本文:そのまま書き込む
footer:収集日時を日付タグで書き込む
現状だとここの書式はまだ決め打ちです。すみません


window.open()を使うシステムとの違い
window.open()を使うと、新しいタブが開くまで待たないといけない
特に回線の遅い環境だと、Reactの初期化とUserScriptの読み込みを待たなければならず、ストレスになる


利用例
script.ts
import { addToInboxFromPrompt } from "./mod.ts"; import type { Scrapbox } from "../scrapbox-jp%2Ftypes/userscript.ts"; declare const scrapbox: Scrapbox; scrapbox.PageMenu.addItem({ title: "Add to inbox", onClick: () => addToInboxFromPrompt( "takker-memex", "メモ帳", ), });

2024-11-13
16:42:37 scrapbox-userscript-stdの破壊的変更に対応
2024-05-03
2022-11-29
11:41:34
選択範囲から投入する機能を script.ts から削除した
arrow functionへの書き換え

バグ
うわっ申し訳ないtakker
他の人が使うことは想定していなかったからなあ
…いや、でもtakkerに関する情報はhard codingしてないから、理論上は誰でも使えるはずなんだけど…
調べておこう
deno cache deno lint も一切していないので、型エラーが発生している可能性が十二分にある
2022-03-10 09:29:09 いっぱいあったぜ()
いずれにせよlaptopを開かないと調べようがないな

実装
$ deno cache -r=https://scrapbox.io https://scrapbox.io/api/code/takker/Inboxに素早く入れるUserScript/mod.ts
mod.ts
import { addToInbox } from "./addToInbox.ts"; import { caret, patch, getLines, connect, } from "../scrapbox-userscript-std/mod.ts"; import { unwrapOk } from "npm:option-t@49/plain_result"; import type { Scrapbox } from "../scrapbox-jp%2Ftypes/userscript.ts"; declare const scrapbox: Scrapbox;

外部から使う関数
Promptからinboxに投入する函数
空白区切りで複数項目を入れる
ページの作成は不可
mod.ts
/** Promptからinboxに投入する * * @param project 投入先inboxページがあるproject * @param title 投入先inboxページのタイトル */ export const addToInboxFromPrompt = async ( project: string, title: string, ): Promise<void> => { const text = window.prompt("Type all you think of", ""); if (!text || text.trim() === "") return; const items = text.trim().split(/\s+/); await addToInbox(project, title, items); };
選択範囲にかかっている行をinboxに投入する函数
行のすべての文字を選択範囲に入れる必要はない
逆に行の一部分だけをinboxに投入することはできない
投入した行は元のページから削除される
mod.ts
/** 選択範囲にかかっている行をinboxに投入する * * @param project 投入先inboxページがあるproject * @param title 投入先inboxページのタイトル * @param option `fallbackToPrompt`を`true`にすると、選択範囲がないとに`addToInboxFromPrompt()`を起動するようになる */ export const addToInboxFromSelection = async ( project: string, title: string, option?: { fallbackToPrompt?: boolean }, ): Promise<void> => { // scrapbox.Page.title がnullでない条件 if (scrapbox.Layout !== "page") return; const { selectionRange, selectedText } = caret(); if (selectedText === "") { if (!option?.fallbackToPrompt) return; return addToInboxFromPrompt(project, title); } const start = Math.min( selectionRange.start.line, selectionRange.end.line, ); const end = Math.max( selectionRange.start.line, selectionRange.end.line, ); const lines = getLines().slice(start, end + 1); const socket = unwrapOk(await connect()); await addToInbox(project, title, lines.map((line) => line.text), { socket }); await patch( scrapbox.Project.name, scrapbox.Page.title, (lines_) => { const lines2 = lines_.map((line) => line.text); return [ ...lines2.slice(0, start), ...lines2.slice(end + 1), ]; }, { socket }, ); };

addToInbox.ts
import { patch, useStatusBar, openInTheSameTab, type ScrapboxSocket, connect, disconnect, caret, } from "../scrapbox-userscript-std/mod.ts"; import { delay as sleep } from "../deno_std%2Fasync/mod.ts"; import { getIndentLineCount, getIndentCount, } from "../scrapbox-userscript-std/text.ts"; import { formatTitle, toYYYYMMDD_HHMMSS, toYYYYMMDD } from "./util.ts"; import { unwrapOk } from "npm:option-t@49/plain_result"; type Page = string[];

実際にInboxに投入する関数
Inboxに素早く投入するPage Menu Aで使ったコードをベースに作った
scrapbox-userscript-stdを使って、タブを開かずに書き込んでいる
うまく書き込めなかったら:projectname/:pagetitle?body=:textを使う
addToInbox.ts
export const addToInbox = async ( project: string, title: string, lines: string[], options?: { socket: ScrapboxSocket }, ): Promise<void> => { const { render, dispose } = useStatusBar();
items は一行の項目
まとめてinboxに入れる
pages はページに切り出す項目
リンクだけ items に入れておく
addToInbox.ts
const items: string[] = []; const pages: Page[] = []; for (const block of parse(lines)) { if (block.type === "line") { items.push(`[${formatTitle(block.text)}~@${toYYYYMMDD(new Date())}]`); continue; } const title = `${formatTitle(block.lines[0])}~@${toYYYYMMDD(new Date())}`; pages.push([ title, ...block.lines.slice(1), "", `#${toYYYYMMDD_HHMMSS(new Date())}`, ]); items.push(`[${title}]`); }
inboxページに追記しつつ、ページも作成する
inboxページへの追記はメモ帳 (scrapbox)用にカスタマイズしてある
区切り線と区切り線との間に入れる
addToInbox.ts
const socket = unwrapOk(await connect(options?.socket)); try { render( { type: "spinner" }, { type: "text", text: `Adding ${items.length} items...` }, ); await patch(project, title, (_lines) => { const lines = _lines.map((line) => line.text); const lastSeparatorIndex = lines.flatMap( (line, i) => line.trim() === "[/icons/hr.icon]" ? [i] : [] ).pop() ?? -1; // 仕切り線が見つからなければ、末尾に追記する if (lastSeparatorIndex < 0) return [...lines, ...items]; return [ ...lines.slice(0, lastSeparatorIndex), ...items, ...lines.slice(lastSeparatorIndex), ]; }, { socket }); render( { type: "spinner" }, { type: "text", text: `Create ${pages.length} pages...` }, ); await Promise.all(pages.map( (page) => patch(project, page[0], (lines) => [ lines[0].text, ...lines.slice(1).map((line) => line.text), ...page.slice(1), ], { socket }) )); render( { type: "check-circle" }, { type: "text", text: "Added to the inbox." }, ); } catch(e: unknown) { render( { type: "exclamation-triangle" }, { type: "text", text: "Failed to add (see console). Write directory instead." }, ); console.error(e); openInTheSameTab(project, title, [...items, ...pages].join("\n")); } finally { const waiting = sleep(1000); if (options?.socket) await disconnect(socket); await waiting; dispose(); } };
1行の項目と、indent付き項目とで分ける関数
余分なindentは削除する
addToInbox.ts
type Block = { type: "line"; text: string; } | { type: "block"; lines: string[]; }; function* parse(lines: string[]): Generator<Block, void, unknown> { let index = 0; while (index < lines.length) { const count = getIndentLineCount(index, lines); if (count === 0) { yield { type: "line", text: lines[index].trim() }; index++; continue; } const indentNum = getIndentCount(lines[index]); yield { type: "block", lines: lines.slice(index, index + count + 1) .map(line => line.slice(indentNum)), }; index += count + 1; } }
その他便利関数
util.ts
export const formatTitle = (title: string) => title .replace(/[\[\]]/g, "") .replace(/\s/g, " "); const zero = (n: number) => `${n}`.padStart(2, "0"); export const toYYYYMMDD_HHMMSS = (date: Date) => `${date.getFullYear()}-${zero(date.getMonth() + 1)}-${ zero(date.getDate()) } ${zero(date.getHours())}:${ zero(date.getMinutes()) }:${zero(date.getSeconds())}`; export const toYYYYMMDD = (date: Date) => `${date.getFullYear()}-${zero(date.getMonth() + 1)}-${ zero(date.getDate()) }`;

#2024-11-13 16:46:32
#2024-05-03 17:58:49
#2022-11-29 11:41:18
#2022-03-15 03:15:43
#2022-03-10 09:29:17
#2022-03-09 09:06:49
#2022-02-21 18:44:21