70 lines
2.5 KiB
JavaScript
70 lines
2.5 KiB
JavaScript
import { buildBundle } from "./interchange.js";
|
|
|
|
/** Convert an ArrayBuffer to the base64 representation used in JSON bundles. */
|
|
function arrayBufferToBase64(buffer) {
|
|
const bytes = new Uint8Array(buffer);
|
|
let binary = "";
|
|
const chunkSize = 0x8000;
|
|
for (let offset = 0; offset < bytes.length; offset += chunkSize) {
|
|
binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
|
|
}
|
|
return btoa(binary);
|
|
}
|
|
|
|
/**
|
|
* Export a stored CMap model and its linked resources as a complete JSON bundle.
|
|
* CMaps are loaded through their repository. Page loading, bundle construction
|
|
* and attachment encoding remain independent of workspace presentation.
|
|
*/
|
|
export class CmapJsonExporter {
|
|
constructor(cmapRepository, loadWikiPage, fetchFile, generator = "Racket Wiki") {
|
|
if (!cmapRepository || typeof cmapRepository.load !== "function" ||
|
|
typeof loadWikiPage !== "function" || typeof fetchFile !== "function") {
|
|
throw new TypeError("A CMap repository, wiki-page loader and file loader are required");
|
|
}
|
|
this.cmapRepository = cmapRepository;
|
|
this.loadWikiPage = loadWikiPage;
|
|
this.fetchFile = fetchFile;
|
|
this.generator = generator;
|
|
}
|
|
|
|
/**
|
|
* Build a validated JSON bundle without changing the source CMaps.
|
|
* Linked CMaps are followed up to maxDepth; linked pages and attachments
|
|
* are included by the interchange format.
|
|
*/
|
|
async export(rootMap, maxDepth = 0) {
|
|
return buildBundle({
|
|
rootMap: this.bundleMap(rootMap),
|
|
maxDepth,
|
|
generator: this.generator,
|
|
loadConceptMap: async (slug) => this.bundleMap(await this.cmapRepository.load(slug)),
|
|
loadWikiPage: this.loadWikiPage,
|
|
loadAttachment: (url) => this.loadAttachment(url)
|
|
});
|
|
}
|
|
|
|
/** Present one stored model through the public interchange record shape. */
|
|
bundleMap(storedMap) {
|
|
if (!storedMap?.slug || typeof storedMap.toDocument !== "function") {
|
|
throw new TypeError("JSON export requires a stored CMap");
|
|
}
|
|
return {
|
|
slug: storedMap.slug,
|
|
title: storedMap.title,
|
|
document: storedMap.toDocument()
|
|
};
|
|
}
|
|
|
|
/** Load and encode one attachment referenced by an exported wiki page. */
|
|
async loadAttachment(url) {
|
|
const response = await this.fetchFile(url);
|
|
if (!response.ok) throw new Error(`Attachment could not be exported: ${url}`);
|
|
const content = await response.arrayBuffer();
|
|
return {
|
|
mimeType: response.headers.get("content-type") || "application/octet-stream",
|
|
contentBase64: arrayBufferToBase64(content)
|
|
};
|
|
}
|
|
}
|