scrapbox-external-backup@0.2.0
TypeScriptで書いた
要bundle
code:sh
code: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")}`;
document.body.append(a);
a.click();
code:sh
dependencies
code: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);
}
};