generated at
scrapbox-external-backup@0.2.0
scrapbox-external-backupの実装その2
TypeScriptで書いた
要bundle

sh
deno check --remote -r=https://scrapbox.io https://scrapbox.io/api/code/takker/scrapbox-external-backup@0.2.0/script.ts
script.ts
import { createExport } from "./mod.ts"; import { lightFormat } from "../date-fns/lightFormat.ts"; const project = "villagepump"; const exports = await createExport(project, 10); const exported = new Date(exports.exported * 1000); const a = document.createElement("a"); a.download = `${exports.name}-${ lightFormat(exported, "yyyy-MM-dd") }T${lightFormat(exported, "HH:mm:ss")}`; a.href = URL.createObjectURL(new Blob([JSON.stringify(exports)], { type: "application/json" })); document.body.append(a); a.click();

sh
deno check --remote -r=https://scrapbox.io https://scrapbox.io/api/code/takker/scrapbox-external-backup@0.2.0/mod.ts
dependencies
mod.ts
import { getPage, readLinksBulk, getProject } from "../scrapbox-userscript-std/rest.ts"; import { Page, UnixTime } from "../scrapbox-jp%2Ftypes/rest.ts"; import { useStatusBar } from "../scrapbox-userscript-std/mod.ts"; import { pool, Result, sort } from "../async-lib/mod.ts"; export type ExportPage = Pick<Page, "id" | "title" | "created" | "updated" | "lines">; export interface ExportData { name: string; displayName: string; exported: UnixTime; pages: ExportPage[]; } export const createExport = async ( project: string, threshold = 100, ): Promise<ExportData> => { const { render, dispose } = useStatusBar(); render( { type: "spinner"}, { type: "text", text: `Loading pages from "${project}"` }, ); try { const result = await getProject(project); if (!result.ok) throw result.value; const { name, displayName } = result.value; const reader = await readLinksBulk(project); if ("name" in reader) throw reader; const pages: ExportPage[] = []; const { render, dispose } = useStatusBar(); try { for await (const titles of reader) { const reader = sort([...pool( threshold, titles, async (page) => { const result = await getPage(project, page.title); if (!result.ok) { console.error(result.value); throw result.value; } const { id, title, created, updated, lines } = result.value; pages.push({ id, title, created, updated, lines }); return `[${pages.length}] ${title}`; }, )]); let animationId: number | undefined; for await (const result of reader) { if (!result.success) continue; if (animationId !== undefined) cancelAnimationFrame(animationId); animationId = requestAnimationFrame( () => render({ type: "text", text: result.value }) ); } } } finally { dispose(); } const exported = (new Date()).getTime() / 1000; render( { type: "check-circle" }, { type: "text", text: `Exported ${pages.length} pages from /${name}`} ); return { name, displayName, exported, pages }; } catch(e) { render( { type: "exclamation-triangle" }, { type: "text", text: typeof e === "object" && "name" in e && "message" in e ? `${e.name} ${e.message}` : "Unexpected error! (see developer console)", }, ); throw e; } finally { setTimeout(() => dispose(), 1000); } };

#2022-05-21 17:58:51