Files
bizzleandClaude Opus 4.8 4036ad5839 Sync Canvas LMS export feature + refreshed Settings screenshot
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>
2026-06-25 16:43:35 -04:00

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;
}