pageMenu.tsimport { transportPage } from "./transportPage.ts";
import type { Scrapbox } from "../scrapbox-jp%2Ftypes/userscript.ts";
declare const scrapbox: Scrapbox;
scrapbox.PageMenu.addItem({
title: "Transport",
onClick: async () => {
await transportPage();
},
});
scrapbox.PageMenu.addItem({
title: "Transport and Open",
onClick: async () => {
const url = await transportPage();
if (!url) return;
window.open(url);
},
});
transportPage.tsimport { transport } from "./mod.ts";
import {
useStatusBar,
encodeTitleURI,
} from "../scrapbox-userscript-std/dom.ts";
import type { Scrapbox } from "../scrapbox-jp%2Ftypes/userscript.ts";
declare const scrapbox: Scrapbox;
export const transportPage = async (): Promise<string | undefined> => {
const title = scrapbox.Page.title!;
const from = scrapbox.Project.name;
const to = globalThis.prompt(
`Take "/${from}/${title}" from "${from}" to:`,
"takker",
);
if (!to) return;
const { render, dispose } = useStatusBar();
render(
{ type: "spinner" },
{ type: "text", text: `/${from}/${title} → /${to}/${title}` }
);
try {
const result = await transport(title, { from, to, merge: true });
if (!result.success) {
render(
{ type: "exclamation-triangle" },
{ type: "text", text: `${result.name} ${result.message}`,
},
);
return;
}
render(
{ type: "check-circle" },
{
type: "text",
text: `Moved ${result.dup ? "and merged " : ""}to "/${to}/${title}".`
},
);
return `https://scrapbox.io/${to}/${encodeTitleURI(title)}`;
} catch(e: unknown) {
render(
{ type: "exclamation-triangle" },
{ type: "text", text: e instanceof Error ?
`${e.name} ${e.message}` :
`Unknown error! (see developper console)`,
},
);
console.error(e);
} finally {
setTimeout(() => dispose(), 1000);
}
};
mod.ts/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference lib="dom" />
import { deletePage, getPage, patch } from "../scrapbox-userscript-std/mod.ts";
import { isErr, unwrapErr, unwrapOk } from "npm:option-t@49/plain_result";
export interface TransportInit {
/** 転送元project */
from: string;
/** 転送先project */
to: string;
/** 転送先に同じタイトルのページがあったときの挙動
*
* - `true`: 末尾に追記する
* - `false`: 何もしない
* @default false
*/
merge?: boolean;
}
export type TransportResult = {
success: true;
/** 同名のページが転送先にあったらtrue */
dup: boolean;
} | {
success: false;
at: "from" | "to";
name: string;
message: string;
};
export async function transport(title: string, {
from,
to,
merge,
}: TransportInit): Promise<TransportResult> {
if (from === to) return { success: true, dup: false };
// 転送するページを取得
const res = await getPage(from, title);
if (isErr(res)) {
return {
success: false,
at: "from",
name: unwrapErr(res).name,
message: unwrapErr(res).message,
};
}
const page = unwrapOk(res);
if (!page.persistent) {
return {
success: false,
at: "from",
name: "EmptyPageError",
message: "The request page is empty.",
};
}
// 転送先ページがあるかどうかを調べる
const res2 = await getPage(to, title);
if (isErr(res2)) {
return {
success: false,
at: "to",
name: unwrapErr(res2).name,
message: unwrapErr(res2).message,
};
}
const page2 = unwrapOk(res);
if (page2.persistent && !merge) {
return {
success: false,
at: "to",
name: "NoEmptyPageError",
message: `The same page already exists in "/${to}"`,
};
}
// 転送する
await patch(
to,
title,
(lines) => [...lines, ...page.lines.slice(1)].map((line) => line.text),
);
await deletePage(from, title);
return { success: true, dup: page2.persistent };
}