scorm-review 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +108 -356
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9702,9 +9702,9 @@ var require_load = __commonJS({
|
|
|
9702
9702
|
var require_lib3 = __commonJS({
|
|
9703
9703
|
"node_modules/jszip/lib/index.js"(exports2, module2) {
|
|
9704
9704
|
"use strict";
|
|
9705
|
-
function
|
|
9706
|
-
if (!(this instanceof
|
|
9707
|
-
return new
|
|
9705
|
+
function JSZip2() {
|
|
9706
|
+
if (!(this instanceof JSZip2)) {
|
|
9707
|
+
return new JSZip2();
|
|
9708
9708
|
}
|
|
9709
9709
|
if (arguments.length) {
|
|
9710
9710
|
throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");
|
|
@@ -9713,7 +9713,7 @@ var require_lib3 = __commonJS({
|
|
|
9713
9713
|
this.comment = null;
|
|
9714
9714
|
this.root = "";
|
|
9715
9715
|
this.clone = function() {
|
|
9716
|
-
var newObj = new
|
|
9716
|
+
var newObj = new JSZip2();
|
|
9717
9717
|
for (var i in this) {
|
|
9718
9718
|
if (typeof this[i] !== "function") {
|
|
9719
9719
|
newObj[i] = this[i];
|
|
@@ -9722,354 +9722,117 @@ var require_lib3 = __commonJS({
|
|
|
9722
9722
|
return newObj;
|
|
9723
9723
|
};
|
|
9724
9724
|
}
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
return new
|
|
9725
|
+
JSZip2.prototype = require_object();
|
|
9726
|
+
JSZip2.prototype.loadAsync = require_load();
|
|
9727
|
+
JSZip2.support = require_support();
|
|
9728
|
+
JSZip2.defaults = require_defaults();
|
|
9729
|
+
JSZip2.version = "3.10.1";
|
|
9730
|
+
JSZip2.loadAsync = function(content, options) {
|
|
9731
|
+
return new JSZip2().loadAsync(content, options);
|
|
9732
9732
|
};
|
|
9733
|
-
|
|
9734
|
-
module2.exports =
|
|
9733
|
+
JSZip2.external = require_external();
|
|
9734
|
+
module2.exports = JSZip2;
|
|
9735
9735
|
}
|
|
9736
9736
|
});
|
|
9737
9737
|
|
|
9738
9738
|
// src/cli.ts
|
|
9739
9739
|
var import_fs2 = __toESM(require("fs"));
|
|
9740
9740
|
var import_path2 = __toESM(require("path"));
|
|
9741
|
-
var import_jszip2 = __toESM(require_lib3());
|
|
9742
9741
|
|
|
9743
9742
|
// src/bundler.ts
|
|
9744
9743
|
var import_jszip = __toESM(require_lib3());
|
|
9745
9744
|
var import_fs = __toESM(require("fs"));
|
|
9746
9745
|
var import_path = __toESM(require("path"));
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
var p = document.body || document.documentElement;
|
|
9756
|
-
var d = document.getElementById('__bundler_err') || p.appendChild(document.createElement('div'));
|
|
9757
|
-
d.id = '__bundler_err';
|
|
9758
|
-
d.style.cssText = 'position:fixed;bottom:12px;left:12px;right:12px;font:12px/1.4 ui-monospace,monospace;background:#2a1215;color:#ff8a80;padding:10px 14px;border-radius:8px;border:1px solid #5c2b2e;z-index:99999;white-space:pre-wrap;max-height:40vh;overflow:auto';
|
|
9759
|
-
d.textContent = (d.textContent ? d.textContent + '\\n' : '') +
|
|
9760
|
-
'[bundle] ' + (e.message || e.type) +
|
|
9761
|
-
(e.filename ? ' (' + e.filename.slice(0,60) + ':' + e.lineno + ')' : '');
|
|
9762
|
-
}, true);
|
|
9763
|
-
|
|
9764
|
-
try {
|
|
9765
|
-
var manifestEl = document.querySelector('script[type="__bundler/manifest"]');
|
|
9766
|
-
var templateEl = document.querySelector('script[type="__bundler/template"]');
|
|
9767
|
-
if (!manifestEl || !templateEl) {
|
|
9768
|
-
setStatus('Error: missing bundle data');
|
|
9769
|
-
return;
|
|
9770
|
-
}
|
|
9771
|
-
|
|
9772
|
-
var manifest = JSON.parse(manifestEl.textContent);
|
|
9773
|
-
var template = JSON.parse(templateEl.textContent);
|
|
9774
|
-
|
|
9775
|
-
var uuids = Object.keys(manifest);
|
|
9776
|
-
setStatus('Unpacking ' + uuids.length + ' assets...');
|
|
9777
|
-
|
|
9778
|
-
var blobUrls = {};
|
|
9779
|
-
await Promise.all(uuids.map(async function(uuid) {
|
|
9780
|
-
var entry = manifest[uuid];
|
|
9781
|
-
try {
|
|
9782
|
-
var binaryStr = atob(entry.data);
|
|
9783
|
-
var bytes = new Uint8Array(binaryStr.length);
|
|
9784
|
-
for (var i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
|
|
9785
|
-
var finalBytes = bytes;
|
|
9786
|
-
if (entry.compressed && typeof DecompressionStream !== 'undefined') {
|
|
9787
|
-
var ds = new DecompressionStream('gzip');
|
|
9788
|
-
var writer = ds.writable.getWriter();
|
|
9789
|
-
var reader = ds.readable.getReader();
|
|
9790
|
-
writer.write(bytes); writer.close();
|
|
9791
|
-
var chunks = [], totalLen = 0;
|
|
9792
|
-
while (true) {
|
|
9793
|
-
var res = await reader.read();
|
|
9794
|
-
if (res.done) break;
|
|
9795
|
-
chunks.push(res.value); totalLen += res.value.length;
|
|
9796
|
-
}
|
|
9797
|
-
finalBytes = new Uint8Array(totalLen);
|
|
9798
|
-
var offset = 0;
|
|
9799
|
-
for (var ci = 0; ci < chunks.length; ci++) { finalBytes.set(chunks[ci], offset); offset += chunks[ci].length; }
|
|
9800
|
-
}
|
|
9801
|
-
blobUrls[uuid] = URL.createObjectURL(new Blob([finalBytes], { type: entry.mime }));
|
|
9802
|
-
} catch(err) {
|
|
9803
|
-
console.error('Failed to decode asset ' + uuid + ':', err);
|
|
9804
|
-
blobUrls[uuid] = URL.createObjectURL(new Blob([], { type: entry.mime }));
|
|
9805
|
-
}
|
|
9806
|
-
}));
|
|
9807
|
-
|
|
9808
|
-
setStatus('Rendering...');
|
|
9809
|
-
for (var uuid of uuids) template = template.split(uuid).join(blobUrls[uuid]);
|
|
9810
|
-
|
|
9811
|
-
template = template.replace(/\\s+integrity="[^"]*"/gi, '').replace(/\\s+crossorigin="[^"]*"/gi, '');
|
|
9812
|
-
|
|
9813
|
-
var resourceScript = '<script>window.__resources = ' +
|
|
9814
|
-
JSON.stringify({}).split('</' + 'script>').join('<\\\\/' + 'script>') +
|
|
9815
|
-
';</' + 'script>';
|
|
9816
|
-
|
|
9817
|
-
var headOpen = template.match(/<head[^>]*>/i);
|
|
9818
|
-
if (headOpen) {
|
|
9819
|
-
var hi = headOpen.index + headOpen[0].length;
|
|
9820
|
-
template = template.slice(0, hi) + resourceScript + template.slice(hi);
|
|
9821
|
-
}
|
|
9822
|
-
|
|
9823
|
-
var doc = new DOMParser().parseFromString(template, 'text/html');
|
|
9824
|
-
document.documentElement.replaceWith(doc.documentElement);
|
|
9825
|
-
var dead = Array.from(document.scripts);
|
|
9826
|
-
for (var old of dead) {
|
|
9827
|
-
var s = document.createElement('script');
|
|
9828
|
-
for (var a of old.attributes) s.setAttribute(a.name, a.value);
|
|
9829
|
-
s.textContent = old.textContent;
|
|
9830
|
-
if ((s.type === 'text/babel' || s.type === 'text/jsx') && s.src) {
|
|
9831
|
-
var r = await fetch(s.src);
|
|
9832
|
-
s.textContent = await r.text();
|
|
9833
|
-
s.removeAttribute('src');
|
|
9834
|
-
}
|
|
9835
|
-
var p = s.src ? new Promise(function(resolve) { s.onload = s.onerror = resolve; }) : null;
|
|
9836
|
-
old.replaceWith(s);
|
|
9837
|
-
if (p) await p;
|
|
9838
|
-
}
|
|
9839
|
-
if (window.Babel && typeof window.Babel.transformScriptTags === 'function') {
|
|
9840
|
-
window.Babel.transformScriptTags();
|
|
9841
|
-
}
|
|
9842
|
-
|
|
9843
|
-
// Inject annotate review tool
|
|
9844
|
-
window.AnnotateConfig = {
|
|
9845
|
-
project: document.title || 'scorm-review',
|
|
9846
|
-
note: 'Please review all slides. Check layout, wording, and content.',
|
|
9847
|
-
startOpen: false,
|
|
9848
|
-
theme: 'auto'
|
|
9849
|
-
};
|
|
9850
|
-
await new Promise(function(resolve) {
|
|
9851
|
-
var ann = document.createElement('script');
|
|
9852
|
-
ann.id = '__annotate_inline';
|
|
9853
|
-
ann.textContent = window.__ANNOTATE_SCRIPT__;
|
|
9854
|
-
document.head.appendChild(ann);
|
|
9855
|
-
resolve();
|
|
9856
|
-
});
|
|
9857
|
-
|
|
9858
|
-
} catch(err) {
|
|
9859
|
-
setStatus('Error unpacking: ' + err.message);
|
|
9860
|
-
console.error('Bundle unpack error:', err);
|
|
9861
|
-
}
|
|
9862
|
-
});
|
|
9863
|
-
`;
|
|
9864
|
-
var THUMBNAIL_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect width="100" height="100" fill="#6366f1"></rect><text x="50" y="58" font-size="32" font-family="Arial" font-weight="bold" text-anchor="middle" fill="#fff">\u25B6</text></svg>`;
|
|
9865
|
-
|
|
9866
|
-
// src/bundler.ts
|
|
9867
|
-
var MIME_MAP = {
|
|
9868
|
-
// Images
|
|
9869
|
-
png: "image/png",
|
|
9870
|
-
jpg: "image/jpeg",
|
|
9871
|
-
jpeg: "image/jpeg",
|
|
9872
|
-
gif: "image/gif",
|
|
9873
|
-
webp: "image/webp",
|
|
9874
|
-
svg: "image/svg+xml",
|
|
9875
|
-
ico: "image/x-icon",
|
|
9876
|
-
avif: "image/avif",
|
|
9877
|
-
// Audio / Video
|
|
9878
|
-
mp3: "audio/mpeg",
|
|
9879
|
-
mp4: "video/mp4",
|
|
9880
|
-
ogg: "audio/ogg",
|
|
9881
|
-
ogv: "video/ogg",
|
|
9882
|
-
wav: "audio/wav",
|
|
9883
|
-
webm: "video/webm",
|
|
9884
|
-
m4a: "audio/m4a",
|
|
9885
|
-
// Fonts
|
|
9886
|
-
woff: "font/woff",
|
|
9887
|
-
woff2: "font/woff2",
|
|
9888
|
-
ttf: "font/ttf",
|
|
9889
|
-
otf: "font/otf",
|
|
9890
|
-
eot: "application/vnd.ms-fontobject",
|
|
9891
|
-
// Scripts / Styles
|
|
9892
|
-
js: "application/javascript",
|
|
9893
|
-
mjs: "application/javascript",
|
|
9894
|
-
css: "text/css",
|
|
9895
|
-
json: "application/json",
|
|
9896
|
-
xml: "application/xml",
|
|
9897
|
-
// Docs
|
|
9898
|
-
pdf: "application/pdf"
|
|
9899
|
-
};
|
|
9900
|
-
function getMime(filename) {
|
|
9901
|
-
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
9902
|
-
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
9903
|
-
}
|
|
9904
|
-
function makeUUID() {
|
|
9905
|
-
const hex = () => Math.floor(Math.random() * 4294967295).toString(16).padStart(8, "0");
|
|
9906
|
-
return `${hex()}-${hex().slice(0, 4)}-4${hex().slice(0, 3)}-${hex().slice(0, 4)}-${hex()}${hex().slice(0, 4)}`;
|
|
9746
|
+
function findEntryHtml(files) {
|
|
9747
|
+
const norm = files.map((f) => f.replace(/\\/g, "/"));
|
|
9748
|
+
if (norm.includes("index.html")) return "index.html";
|
|
9749
|
+
if (norm.includes("Index.html")) return "Index.html";
|
|
9750
|
+
const nested = norm.find((f) => /^[^/]+\/index\.html$/i.test(f));
|
|
9751
|
+
if (nested) return nested;
|
|
9752
|
+
const anyHtml = norm.find((f) => /\.html?$/i.test(f));
|
|
9753
|
+
return anyHtml ?? null;
|
|
9907
9754
|
}
|
|
9908
|
-
function
|
|
9909
|
-
|
|
9755
|
+
function injectCoreviewTag(html) {
|
|
9756
|
+
const scriptTag = `<script src="./coreview.js" data-project="scorm-review" data-note="Please review all slides. Check layout, wording, and content." data-start-open="false"></script>`;
|
|
9757
|
+
html = html.replace(/<script[^>]*src="[^"]*coreview\.js"[^>]*><\/script>\s*/gi, "");
|
|
9758
|
+
if (/<\/body>/i.test(html)) {
|
|
9759
|
+
return html.replace(/<\/body>/i, `${scriptTag}
|
|
9760
|
+
</body>`);
|
|
9761
|
+
}
|
|
9762
|
+
return html + "\n" + scriptTag;
|
|
9910
9763
|
}
|
|
9911
|
-
async function bundleScormZip(zipBuffer,
|
|
9764
|
+
async function bundleScormZip(zipBuffer, coreviewScript) {
|
|
9912
9765
|
const zip = await import_jszip.default.loadAsync(zipBuffer);
|
|
9913
|
-
const
|
|
9766
|
+
const fileNames = Object.keys(zip.files).filter((f) => !zip.files[f].dir);
|
|
9767
|
+
const entryPath = findEntryHtml(fileNames);
|
|
9914
9768
|
if (!entryPath) throw new Error("No HTML entry point found in the zip file.");
|
|
9915
|
-
const entryFile = zip.file(entryPath);
|
|
9916
|
-
if (!entryFile) throw new Error(`Entry file "${entryPath}" could not be read.`);
|
|
9917
|
-
const templateHtml = await entryFile.async("string");
|
|
9918
9769
|
const entryDir = entryPath.includes("/") ? entryPath.slice(0, entryPath.lastIndexOf("/") + 1) : "";
|
|
9919
|
-
const
|
|
9920
|
-
|
|
9921
|
-
const
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
const relPath = entryDir && file.name.startsWith(entryDir) ? file.name.slice(entryDir.length) : file.name;
|
|
9931
|
-
uuidMap[relPath] = uuid;
|
|
9932
|
-
if (relPath !== file.name) uuidMap[file.name] = uuid;
|
|
9933
|
-
}
|
|
9934
|
-
let processedHtml = replaceAssetRefs(templateHtml, uuidMap);
|
|
9935
|
-
processedHtml = stripExistingBundle(processedHtml);
|
|
9936
|
-
return buildOutputHtml(processedHtml, manifest, annotateScript);
|
|
9770
|
+
const entryFile = zip.file(entryPath);
|
|
9771
|
+
if (!entryFile) throw new Error(`Could not read entry file: ${entryPath}`);
|
|
9772
|
+
const originalHtml = await entryFile.async("string");
|
|
9773
|
+
const modifiedHtml = injectCoreviewTag(originalHtml);
|
|
9774
|
+
zip.file(entryPath, modifiedHtml);
|
|
9775
|
+
zip.file(`${entryDir}coreview.js`, coreviewScript);
|
|
9776
|
+
return zip.generateAsync({
|
|
9777
|
+
type: "nodebuffer",
|
|
9778
|
+
compression: "DEFLATE",
|
|
9779
|
+
compressionOptions: { level: 6 }
|
|
9780
|
+
});
|
|
9937
9781
|
}
|
|
9938
|
-
async function bundleScormFolder(folderPath,
|
|
9939
|
-
function collectFiles(dir
|
|
9782
|
+
async function bundleScormFolder(folderPath, coreviewScript) {
|
|
9783
|
+
function collectFiles(dir) {
|
|
9940
9784
|
const results = [];
|
|
9941
9785
|
for (const entry of import_fs.default.readdirSync(dir)) {
|
|
9942
9786
|
const fullPath = import_path.default.join(dir, entry);
|
|
9943
9787
|
const stat = import_fs.default.statSync(fullPath);
|
|
9944
9788
|
if (stat.isDirectory()) {
|
|
9945
|
-
results.push(...collectFiles(fullPath
|
|
9789
|
+
results.push(...collectFiles(fullPath));
|
|
9946
9790
|
} else {
|
|
9947
9791
|
results.push(fullPath);
|
|
9948
9792
|
}
|
|
9949
9793
|
}
|
|
9950
9794
|
return results;
|
|
9951
9795
|
}
|
|
9952
|
-
const allFiles = collectFiles(folderPath
|
|
9796
|
+
const allFiles = collectFiles(folderPath);
|
|
9953
9797
|
const relFiles = allFiles.map((f) => import_path.default.relative(folderPath, f).replace(/\\/g, "/"));
|
|
9954
|
-
|
|
9955
|
-
if (files.includes("index.html")) return "index.html";
|
|
9956
|
-
if (files.includes("Index.html")) return "Index.html";
|
|
9957
|
-
const nested = files.find((f) => /^[^/]+\/index\.html$/i.test(f));
|
|
9958
|
-
if (nested) return nested;
|
|
9959
|
-
const anyHtml = files.find((f) => /\.html?$/i.test(f));
|
|
9960
|
-
return anyHtml ?? null;
|
|
9961
|
-
}
|
|
9962
|
-
const entryRelPath = findEntryHtmlInFiles(relFiles);
|
|
9798
|
+
const entryRelPath = findEntryHtml(relFiles);
|
|
9963
9799
|
if (!entryRelPath) throw new Error("No HTML entry point found in the folder.");
|
|
9964
|
-
const
|
|
9965
|
-
const templateHtml = import_fs.default.readFileSync(entryAbsPath, "utf-8");
|
|
9966
|
-
const entryDir = entryRelPath.includes("/") ? entryRelPath.slice(0, entryRelPath.lastIndexOf("/") + 1) : "";
|
|
9967
|
-
const manifest = {};
|
|
9968
|
-
const uuidMap = {};
|
|
9800
|
+
const zip = new import_jszip.default();
|
|
9969
9801
|
for (const relFile of relFiles) {
|
|
9970
|
-
if (relFile === entryRelPath) continue;
|
|
9971
|
-
if (relFile.endsWith("imsmanifest.xml")) continue;
|
|
9972
|
-
if (relFile.endsWith(".bat")) continue;
|
|
9973
9802
|
if (import_path.default.basename(relFile) === ".DS_Store") continue;
|
|
9974
9803
|
const absPath = import_path.default.join(folderPath, relFile);
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
9980
|
-
|
|
9981
|
-
|
|
9982
|
-
if (relPath !== normalizedRel) uuidMap[normalizedRel] = uuid;
|
|
9983
|
-
}
|
|
9984
|
-
let processedHtml = replaceAssetRefs(templateHtml, uuidMap);
|
|
9985
|
-
processedHtml = stripExistingBundle(processedHtml);
|
|
9986
|
-
return buildOutputHtml(processedHtml, manifest, annotateScript);
|
|
9987
|
-
}
|
|
9988
|
-
function bundlePlainHtml(htmlBuffer, annotateScript) {
|
|
9989
|
-
let html = htmlBuffer.toString("utf-8");
|
|
9990
|
-
html = stripExistingBundle(html);
|
|
9991
|
-
return injectAnnotate(html, annotateScript);
|
|
9992
|
-
}
|
|
9993
|
-
function findEntryHtml(zip) {
|
|
9994
|
-
const files = Object.keys(zip.files);
|
|
9995
|
-
if (zip.files["index.html"]) return "index.html";
|
|
9996
|
-
if (zip.files["Index.html"]) return "Index.html";
|
|
9997
|
-
const nested = files.find((f) => /^[^/]+\/index\.html$/i.test(f));
|
|
9998
|
-
if (nested) return nested;
|
|
9999
|
-
const anyHtml = files.find((f) => /\.html?$/i.test(f) && !zip.files[f].dir);
|
|
10000
|
-
return anyHtml ?? null;
|
|
10001
|
-
}
|
|
10002
|
-
function replaceAssetRefs(html, uuidMap) {
|
|
10003
|
-
const paths = Object.keys(uuidMap).sort((a, b) => b.length - a.length);
|
|
10004
|
-
for (const refPath of paths) {
|
|
10005
|
-
const uuid = uuidMap[refPath];
|
|
10006
|
-
const escaped = refPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10007
|
-
html = html.replace(new RegExp(escaped, "g"), uuid);
|
|
9804
|
+
if (relFile === entryRelPath) {
|
|
9805
|
+
const originalHtml = import_fs.default.readFileSync(absPath, "utf-8");
|
|
9806
|
+
const modifiedHtml = injectCoreviewTag(originalHtml);
|
|
9807
|
+
zip.file(relFile, modifiedHtml);
|
|
9808
|
+
} else {
|
|
9809
|
+
zip.file(relFile, import_fs.default.readFileSync(absPath));
|
|
9810
|
+
}
|
|
10008
9811
|
}
|
|
10009
|
-
|
|
10010
|
-
}
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
html = html.replace(/<div[^>]*id="__bundler_loading"[^>]*>[^<]*<\/div>/gi, "");
|
|
10017
|
-
html = html.replace(/<script[^>]*id="__annotate_inline"[^>]*>[\s\S]*?<\/script>/gi, "");
|
|
10018
|
-
return html;
|
|
9812
|
+
const entryDir = entryRelPath.includes("/") ? entryRelPath.slice(0, entryRelPath.lastIndexOf("/") + 1) : "";
|
|
9813
|
+
zip.file(`${entryDir}coreview.js`, coreviewScript);
|
|
9814
|
+
return zip.generateAsync({
|
|
9815
|
+
type: "nodebuffer",
|
|
9816
|
+
compression: "DEFLATE",
|
|
9817
|
+
compressionOptions: { level: 6 }
|
|
9818
|
+
});
|
|
10019
9819
|
}
|
|
10020
|
-
function
|
|
10021
|
-
const
|
|
10022
|
-
|
|
10023
|
-
window.AnnotateConfig = {
|
|
10024
|
-
|
|
10025
|
-
note: 'Please review this document. Check layout, wording, and content.',
|
|
10026
|
-
startOpen: false,
|
|
10027
|
-
theme: 'auto'
|
|
10028
|
-
};
|
|
10029
|
-
${annotateScript}
|
|
9820
|
+
function bundlePlainHtml(htmlBuffer, coreviewScript) {
|
|
9821
|
+
const html = htmlBuffer.toString("utf-8");
|
|
9822
|
+
const scriptTag = `<script id="__coreview_inline">
|
|
9823
|
+
window.AnnotateConfig = { project: document.title || 'review', note: 'Please review this document.', startOpen: false, theme: 'auto' };
|
|
9824
|
+
${coreviewScript}
|
|
10030
9825
|
</script>`;
|
|
10031
|
-
|
|
10032
|
-
|
|
9826
|
+
const cleaned = html.replace(/<script[^>]*id="__coreview_inline"[^>]*>[\s\S]*?<\/script>\s*/gi, "");
|
|
9827
|
+
if (/<\/body>/i.test(cleaned)) {
|
|
9828
|
+
return cleaned.replace(/<\/body>/i, `${scriptTag}
|
|
10033
9829
|
</body>`);
|
|
10034
9830
|
}
|
|
10035
|
-
return
|
|
10036
|
-
}
|
|
10037
|
-
function buildOutputHtml(templateHtml, manifest, annotateScript) {
|
|
10038
|
-
const titleMatch = templateHtml.match(/<title[^>]*>([^<]*)<\/title>/i);
|
|
10039
|
-
const title = titleMatch ? titleMatch[1].trim() : "SCORM Review";
|
|
10040
|
-
const manifestJson = JSON.stringify(manifest);
|
|
10041
|
-
const templateJson = JSON.stringify(templateHtml);
|
|
10042
|
-
const annotateEscaped = escapeForJsonString(annotateScript);
|
|
10043
|
-
return `<!DOCTYPE html>
|
|
10044
|
-
<html>
|
|
10045
|
-
<head>
|
|
10046
|
-
<meta charset="utf-8">
|
|
10047
|
-
<title>${title}</title>
|
|
10048
|
-
<style>
|
|
10049
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
10050
|
-
body { background: #faf9f5; display: flex; align-items: center; justify-content: center; min-height: 100vh; font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
|
|
10051
|
-
#__bundler_loading { position: fixed; bottom: 20px; right: 20px; font: 13px/1.4 -apple-system, BlinkMacSystemFont, sans-serif; color: #666; background: #fff; padding: 8px 14px; border-radius: 8px; box-shadow: 0 1px 4px rgba(0,0,0,0.12); z-index: 10000; }
|
|
10052
|
-
#__bundler_thumbnail { position: fixed; inset: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: #faf9f5; z-index: 9999; }
|
|
10053
|
-
#__bundler_thumbnail svg { width: 120px; height: 120px; }
|
|
10054
|
-
</style>
|
|
10055
|
-
</head>
|
|
10056
|
-
<body>
|
|
10057
|
-
<div id="__bundler_thumbnail">
|
|
10058
|
-
${THUMBNAIL_SVG}
|
|
10059
|
-
</div>
|
|
10060
|
-
<div id="__bundler_loading">Unpacking...</div>
|
|
10061
|
-
|
|
10062
|
-
<script>window.__ANNOTATE_SCRIPT__ = "${annotateEscaped}";</script>
|
|
10063
|
-
<script>${UNPACKER_SCRIPT}</script>
|
|
10064
|
-
|
|
10065
|
-
<script type="__bundler/manifest">${manifestJson}</script>
|
|
10066
|
-
<script type="__bundler/template">${templateJson}</script>
|
|
10067
|
-
</body>
|
|
10068
|
-
</html>`;
|
|
9831
|
+
return cleaned + "\n" + scriptTag;
|
|
10069
9832
|
}
|
|
10070
9833
|
|
|
10071
9834
|
// src/cli.ts
|
|
10072
|
-
var
|
|
9835
|
+
var COREVIEW_SCRIPT = `\uFEFF/* =============================================================================\r
|
|
10073
9836
|
* coreview.js \u2014 a drop-in visual review & annotation layer for any website.\r
|
|
10074
9837
|
* Open-source edition \xB7 local-only \xB7 zero backend.\r
|
|
10075
9838
|
*\r
|
|
@@ -12888,39 +12651,33 @@ var ANNOTATE_SCRIPT = `\uFEFF/* ================================================
|
|
|
12888
12651
|
`;
|
|
12889
12652
|
function printHelp() {
|
|
12890
12653
|
console.log(`
|
|
12891
|
-
scorm-review \u2014
|
|
12654
|
+
scorm-review \u2014 Inject the coreview review tool into SCORM packages
|
|
12892
12655
|
|
|
12893
12656
|
Usage:
|
|
12894
12657
|
scorm-review <input> Convert a .zip, folder, or .html file
|
|
12895
|
-
scorm-review <input> -o <output> Specify output
|
|
12658
|
+
scorm-review <input> -o <output> Specify output file path
|
|
12896
12659
|
scorm-review --help Show this help message
|
|
12897
12660
|
|
|
12898
12661
|
Examples:
|
|
12899
12662
|
scorm-review ./my-course.zip
|
|
12900
12663
|
scorm-review ./my-course/
|
|
12901
12664
|
scorm-review ./page.html
|
|
12902
|
-
scorm-review ./my-course.zip -o ./output/my-
|
|
12665
|
+
scorm-review ./my-course.zip -o ./output/my-course_review.zip
|
|
12903
12666
|
|
|
12904
|
-
Output
|
|
12905
|
-
<name>
|
|
12906
|
-
<name>_review.
|
|
12667
|
+
Output:
|
|
12668
|
+
.zip or folder input \u2192 <name>_review.zip (SCORM package with coreview.js added)
|
|
12669
|
+
.html input \u2192 <name>_review.html (HTML with coreview injected inline)
|
|
12907
12670
|
`);
|
|
12908
12671
|
}
|
|
12909
|
-
function
|
|
12672
|
+
function resolveOutputPath(inputPath, isFolder, ext, outputFlag) {
|
|
12910
12673
|
if (outputFlag) return import_path2.default.resolve(outputFlag);
|
|
12911
|
-
const resolved = import_path2.default.resolve(inputPath);
|
|
12674
|
+
const resolved = import_path2.default.resolve(inputPath.replace(/[\\/]+$/, ""));
|
|
12912
12675
|
const dir = import_path2.default.dirname(resolved);
|
|
12913
|
-
const base = import_path2.default.basename(resolved
|
|
12914
|
-
|
|
12915
|
-
|
|
12916
|
-
|
|
12917
|
-
|
|
12918
|
-
zip.file(htmlFilename, htmlContent);
|
|
12919
|
-
return zip.generateAsync({
|
|
12920
|
-
type: "nodebuffer",
|
|
12921
|
-
compression: "DEFLATE",
|
|
12922
|
-
compressionOptions: { level: 6 }
|
|
12923
|
-
});
|
|
12676
|
+
const base = import_path2.default.basename(resolved, isFolder ? "" : ext);
|
|
12677
|
+
if (ext === ".html" || ext === ".htm") {
|
|
12678
|
+
return import_path2.default.join(dir, base + "_review.html");
|
|
12679
|
+
}
|
|
12680
|
+
return import_path2.default.join(dir, base + "_review.zip");
|
|
12924
12681
|
}
|
|
12925
12682
|
async function main() {
|
|
12926
12683
|
const args = process.argv.slice(2);
|
|
@@ -12955,43 +12712,38 @@ async function main() {
|
|
|
12955
12712
|
console.error(`Error: Unsupported file type "${ext}". Only .zip, .html, .htm, or a folder are supported.`);
|
|
12956
12713
|
process.exit(1);
|
|
12957
12714
|
}
|
|
12958
|
-
const
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
const zipOutputPath = outputBase + "_review.zip";
|
|
12962
|
-
console.log(`Converting: ${import_path2.default.basename(resolvedInput)}${isFolder ? "/" : ""}`);
|
|
12963
|
-
console.log(`HTML: ${htmlOutputPath}`);
|
|
12964
|
-
if (!ext || ext === ".zip") {
|
|
12965
|
-
console.log(`ZIP: ${zipOutputPath}`);
|
|
12966
|
-
}
|
|
12715
|
+
const outputPath = resolveOutputPath(inputPath, isFolder, ext, outputFlag);
|
|
12716
|
+
console.log(`Input: ${import_path2.default.basename(resolvedInput)}${isFolder ? "/" : ""}`);
|
|
12717
|
+
console.log(`Output: ${outputPath}`);
|
|
12967
12718
|
console.log("");
|
|
12968
|
-
import_fs2.default.mkdirSync(import_path2.default.dirname(
|
|
12719
|
+
import_fs2.default.mkdirSync(import_path2.default.dirname(outputPath), { recursive: true });
|
|
12969
12720
|
try {
|
|
12970
|
-
let outputHtml;
|
|
12971
12721
|
if (isFolder) {
|
|
12972
12722
|
process.stdout.write("Processing SCORM folder...");
|
|
12973
|
-
|
|
12723
|
+
const zipBuffer = await bundleScormFolder(resolvedInput, COREVIEW_SCRIPT);
|
|
12724
|
+
console.log(" done.");
|
|
12725
|
+
import_fs2.default.writeFileSync(outputPath, zipBuffer);
|
|
12726
|
+
const sizeMb = (zipBuffer.length / 1024 / 1024).toFixed(1);
|
|
12727
|
+
console.log(`
|
|
12728
|
+
\u2713 Created: ${outputPath} (${sizeMb} MB)`);
|
|
12974
12729
|
} else if (ext === ".zip") {
|
|
12975
12730
|
process.stdout.write("Processing SCORM zip...");
|
|
12976
12731
|
const fileBuffer = import_fs2.default.readFileSync(resolvedInput);
|
|
12977
|
-
|
|
12732
|
+
const zipBuffer = await bundleScormZip(fileBuffer, COREVIEW_SCRIPT);
|
|
12733
|
+
console.log(" done.");
|
|
12734
|
+
import_fs2.default.writeFileSync(outputPath, zipBuffer);
|
|
12735
|
+
const sizeMb = (zipBuffer.length / 1024 / 1024).toFixed(1);
|
|
12736
|
+
console.log(`
|
|
12737
|
+
\u2713 Created: ${outputPath} (${sizeMb} MB)`);
|
|
12978
12738
|
} else {
|
|
12979
12739
|
process.stdout.write("Processing HTML file...");
|
|
12980
12740
|
const fileBuffer = import_fs2.default.readFileSync(resolvedInput);
|
|
12981
|
-
outputHtml = bundlePlainHtml(fileBuffer,
|
|
12982
|
-
|
|
12983
|
-
|
|
12984
|
-
|
|
12985
|
-
|
|
12986
|
-
|
|
12987
|
-
\u2713 HTML: ${htmlOutputPath} (${htmlSizeMb} MB)`);
|
|
12988
|
-
if (isFolder || ext === ".zip") {
|
|
12989
|
-
process.stdout.write("Creating review zip...");
|
|
12990
|
-
const zipBuffer = await createReviewZip(outputHtml, htmlFilename);
|
|
12991
|
-
import_fs2.default.writeFileSync(zipOutputPath, zipBuffer);
|
|
12992
|
-
const zipSizeMb = (zipBuffer.length / 1024 / 1024).toFixed(1);
|
|
12993
|
-
console.log(` done.`);
|
|
12994
|
-
console.log(`\u2713 ZIP: ${zipOutputPath} (${zipSizeMb} MB)`);
|
|
12741
|
+
const outputHtml = bundlePlainHtml(fileBuffer, COREVIEW_SCRIPT);
|
|
12742
|
+
console.log(" done.");
|
|
12743
|
+
import_fs2.default.writeFileSync(outputPath, outputHtml, "utf-8");
|
|
12744
|
+
const sizeMb = (Buffer.byteLength(outputHtml, "utf-8") / 1024 / 1024).toFixed(1);
|
|
12745
|
+
console.log(`
|
|
12746
|
+
\u2713 Created: ${outputPath} (${sizeMb} MB)`);
|
|
12995
12747
|
}
|
|
12996
12748
|
} catch (err) {
|
|
12997
12749
|
const msg = err instanceof Error ? err.message : String(err);
|