Inboxに素早く入れるUserScript
操作方法
二通りある
選択範囲ではなく、選択範囲に被っている行を全て転送する
2022-11-29 11:38:04 全く使わないどころか、必要ないタイミングで誤爆して非常にうざったいので削除する
modal windowに入力して送る
選択範囲がない場合のみこちらが起動する
Screenshot
script.tsを入れた結果と動画のUIとは違うので注意してください
script.tsだと、page-info-menuの中にボタンが追加される
https://gyazo.com/838eca5e2cca56c6b6b4e95080b70c6c
機能
インデントがぶら下がっている項目はページとして予め切り出しておく
書式
projectではないとわかったら手動で外す
本文:そのまま書き込む
footer:収集日時を日付タグで書き込む
現状だとここの書式はまだ決め打ちです。すみません
特に回線の遅い環境だと、Reactの初期化とUserScriptの読み込みを待たなければならず、ストレスになる
利用例
code: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
2024-05-03
2022-11-29
11:41:34
選択範囲から投入する機能をscript.tsから削除した
arrow functionへの書き換え
バグ
うわっ申し訳ないtakker.icon
他の人が使うことは想定していなかったからなあ
…いや、でもtakker.iconに関する情報はhard codingしてないから、理論上は誰でも使えるはずなんだけど…
調べておこう
deno cacheもdeno lintも一切していないので、型エラーが発生している可能性が十二分にある
2022-03-10 09:29:09 いっぱいあったぜ()
いずれにせよlaptopを開かないと調べようがないな
実装
code: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に投入する函数
空白区切りで複数項目を入れる
ページの作成は不可
code: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に投入することはできない
投入した行は元のページから削除される
code: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 },
);
};
code: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に投入する関数
code: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に入れておく
code: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ページに追記しつつ、ページも作成する
区切り線と区切り線との間に入れる
code: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;
// 仕切り線が見つからなければ、末尾に追記する
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, page0, (lines) => [ ...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);
} finally {
const waiting = sleep(1000);
if (options?.socket) await disconnect(socket);
await waiting;
dispose();
}
};
1行の項目と、indent付き項目とで分ける関数
余分なindentは削除する
code: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: linesindex.trim() }; index++;
continue;
}
const indentNum = getIndentCount(linesindex); yield {
type: "block",
lines: lines.slice(index, index + count + 1)
.map(line => line.slice(indentNum)),
};
index += count + 1;
}
}
その他便利関数
code:util.ts
export const formatTitle = (title: string) => title
.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())
}`;