Brings the share build up to date with the main project: QTI .zip export and optional direct Canvas API push, plus the Canvas settings card. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
50 lines
2.1 KiB
JavaScript
50 lines
2.1 KiB
JavaScript
// lib/canvas/export.js — assemble a Canvas QTI .zip from an assignment and download it.
|
|
// Client-side only (like lib/exporter.js): no data leaves the browser.
|
|
import { mapAssignmentToCanvas } from "./map";
|
|
import { renderAssessmentXml } from "./qti";
|
|
import { renderManifest, renderAssessmentMeta } from "./manifest";
|
|
import { storeZip } from "./zip";
|
|
|
|
// A QTI identifier must be a valid NCName-ish token (starts with a letter, no spaces).
|
|
function slugIdent(seed) {
|
|
const clean = String(seed || "").replace(/[^a-zA-Z0-9]/g, "");
|
|
return "g" + (clean || "quiz");
|
|
}
|
|
|
|
function safeName(title) {
|
|
const base = String(title || "quiz")
|
|
.replace(/[^\w\- ]+/g, "").trim().replace(/\s+/g, "-").slice(0, 60) || "quiz";
|
|
return `${base}-canvas.zip`;
|
|
}
|
|
|
|
// Build the package in memory. Returns { bytes: Uint8Array, ir, files } — pure, testable.
|
|
export function buildCanvasPackage(assignment, opts = {}) {
|
|
const ir = mapAssignmentToCanvas(assignment, opts);
|
|
const assessmentIdent = slugIdent(assignment?.id || assignment?.title);
|
|
const manifestIdent = "m" + assessmentIdent;
|
|
const files = [
|
|
{ name: "imsmanifest.xml", data: renderManifest(assessmentIdent, manifestIdent) },
|
|
{ name: `${assessmentIdent}/${assessmentIdent}.xml`, data: renderAssessmentXml(ir, assessmentIdent) },
|
|
{ name: `${assessmentIdent}/assessment_meta.xml`, data: renderAssessmentMeta(ir, assessmentIdent) },
|
|
];
|
|
return { bytes: storeZip(files), ir, files };
|
|
}
|
|
|
|
function download(filename, blob) {
|
|
const a = document.createElement("a");
|
|
a.href = URL.createObjectURL(blob);
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
setTimeout(() => { URL.revokeObjectURL(a.href); a.remove(); }, 800);
|
|
}
|
|
|
|
// Build and trigger the browser download. Returns the IR (so callers can show a summary).
|
|
export function exportCanvasZip(assignment, opts = {}) {
|
|
const { bytes, ir } = buildCanvasPackage(assignment, opts);
|
|
// Copy into a fresh ArrayBuffer so Blob gets a clean, correctly-sized buffer.
|
|
const blob = new Blob([bytes.slice()], { type: "application/zip" });
|
|
download(safeName(assignment?.title), blob);
|
|
return ir;
|
|
}
|