Projectを横断してリンクを置換するUserScript
本文を直接編集することなくリンク置換 (scrapbox)するscript
リンクを置換するPopup Menuとはちがい、指定した全てのprojectに含まれる同一リンクを全て置換する
https://gyazo.com/4543a17e3436d7dcebc50e99eb39bf16
↑Gyazo GIFがなぜかガクガクしてしまったので、別途GIFを作った
https://gyazo.com/ab4f4583e92efa03b516a64f2c17a08f.mp4
使い方
code:script.js
import { replace, getLink } from "./mod.js";
scrapbox.PopupMenu.addButton({
title: (text) => getLink(text) ? "update link" : "",
onClick: (text) => replace(
text,
"takker", "takker-memex", "takker-private",
),
});
takker-workflow@0.0.1を動かす上でもあると便利なのでほしい
projectを横断して言及しているproject (GTD)や次に取るべき行動のタイトルを置換しやすくなる
以下の修正が必要
✅ページ書き込み時のリンク更新がcase insensitiveになってた
✅patch()で空配列を返されたらページを削除する
✅websocketを使い回す (scrapbox-userscript-std)
実装したいこと
merge page
現状だと、置換後のリンクと同名のタイトルがあるとエラーになる
✅️DuplicationTitleErrorに対応しないと実装できない
置換対象のprojectを選択できるようにする?
UIが複雑になりそう
現在のprojectも対象にしたい
各自でreplace()の引数に入れてください
2024-11-13
16:36:58 scrapbox-userscript-stdの破壊的変更に対応
2024-07-05
12:58:53 リンク検知処理を複数のリンクをまとめて置換するUserScriptと共通化した
微妙に正規表現が違うせいで、popup menu上は検知されているのに押しても置換windowが表示されないことがしょっちゅうあってまあまあストレスだった
重い腰を上げて、ようやくなおした
実装はリンクを置換するPopup Menuとほとんど同じ
dependencies
scrapbox-userscript-std
code:mod.ts
import {
disconnect,
connect,
patch,
replaceLinks,
useStatusBar,
} from "../scrapbox-userscript-std/mod.ts";
import type { ErrorLike } from "../scrapbox-jp%2Ftypes/rest.ts";
import { getLinks } from "../複数のリンクをまとめて置換するUserScript/mod.ts";
import { isErr, unwrapErr, unwrapOk } from "npm:option-t@49/plain_result";
export const replace = async (text: string, projects: string[]) => {
const link = getLinks(text)0;
if (!link) return;
const newLink = window.prompt(
`Replace "${link}" to this in ${
projects.map((project) => "/${project}").join(", ")
}`,
link,
)
?.replace?.(/[\\\n]/g, " ") ?? "";
if (newLink === "") return;
const { render, dispose } = useStatusBar();
const socket = unwrapOk(await connect())!;
try {
render(
{ type: "spinner" },
{
type: "text",
text: Replacing links in ${projects.length} projects...,
},
);
let count = 0;
const replacedNums = await Promise.all(projects.map(async (project) => {
const result = await Promise.all([
// 本当はhasBackLinksOrIcons === trueのときのみ置換したい
replaceLinks(project, link, newLink),
patch(project, link, (lines, { persistent }) => {
if (!persistent) return;
return [
newLink,
...lines.map((line) => line.text).slice(1),
];
}, { socket }),
]);
if (isErr(result)) {
render(
{ type: "exclamation-triangle" },
{
type: "text",
text: ${unwrapErr(result).name} ${unwrapErr(result).message},
},
);
throw toError(unwrapErr(result));
}
count++;
render(
{ type: "spinner" },
{
type: "text",
text: Replacing links in ${projects.length - count} projects...,
},
);
return unwrapOk(result);
}));
const replaced = replacedNums.reduce((acc, cur) => acc + cur, 0);
render(
{ type: "check-circle" },
{ type: "text", text: Successfully replaced ${replaced} links. },
);
} finally {
await disconnect(socket);
setTimeout(dispose, 1000);
}
}
const toError = (e: ErrorLike) => {
const error = new Error();
error.name = e.name;
error.message = e.message;
return error;
}
#2024-11-13 16:36:26
#2024-07-05 13:00:02
#2024-07-05 12:58:33
#2022-03-03 19:18:51
#2022-02-14 14:23:29