postext-scorm 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +51 -2
- package/dist/fragments.d.ts +18 -0
- package/dist/fragments.d.ts.map +1 -0
- package/dist/fragments.js +99 -0
- package/dist/fragments.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +215 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +27 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +84 -0
- package/dist/manifest.js.map +1 -0
- package/dist/outline.d.ts +41 -0
- package/dist/outline.d.ts.map +1 -0
- package/dist/outline.js +90 -0
- package/dist/outline.js.map +1 -0
- package/dist/player.d.ts +12 -0
- package/dist/player.d.ts.map +1 -0
- package/dist/player.js +649 -0
- package/dist/player.js.map +1 -0
- package/dist/schemas.json +1 -0
- package/dist/scorm.test.d.ts +2 -0
- package/dist/scorm.test.d.ts.map +1 -0
- package/dist/scorm.test.js +177 -0
- package/dist/scorm.test.js.map +1 -0
- package/dist/types.d.ts +116 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Postext contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,52 @@
|
|
|
1
|
-
#
|
|
1
|
+
# postext-scorm
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
SCORM package writer for [Postext](https://postext.dev/) books. It writes a laid-out book, or the chapters and sections picked from it, as a SCORM 1.2 or SCORM 2004 (4th edition) package: the zip a learning management system (Moodle, Canvas, Blackboard, SCORM Cloud and most others) imports as a course. It runs in the browser or in Node, with no server.
|
|
4
|
+
|
|
5
|
+
The content is [postext-epub](https://www.npmjs.com/package/postext-epub)'s, as HTML5 pages: the printed pages (`layout: 'fixed'`) or the text set again for the window (`layout: 'reflowable'`). Each SCO has a launch page with a small player, and the book's exercises send their score, completion and pass or fail to the LMS.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install postext postext-epub postext-scorm
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`postext` and `postext-epub` are peer dependencies: upgrade the three together.
|
|
12
|
+
|
|
13
|
+
## Use
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { buildBundle, openBundle } from 'postext';
|
|
17
|
+
import { renderToScorm, scormOrganization, scormTree, treeLeaves } from 'postext-scorm';
|
|
18
|
+
|
|
19
|
+
const bundle = await openBundle(bytes);
|
|
20
|
+
const docs = buildBundle(bundle); // one VDTDocument per chapter, in book order
|
|
21
|
+
|
|
22
|
+
const tree = scormTree(bundle.chapters.map((c) => ({ markdown: c.markdown, title: c.title })));
|
|
23
|
+
const picked = new Set(tree.flatMap(treeLeaves).map((n) => n.id)); // everything
|
|
24
|
+
|
|
25
|
+
const zip = await renderToScorm(docs, {
|
|
26
|
+
version: '2004',
|
|
27
|
+
layout: 'reflowable',
|
|
28
|
+
items: scormOrganization(tree, picked),
|
|
29
|
+
metadata: { title: 'Lantern', language: 'en' },
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- `renderToScorm(docs, options)` returns the zip as bytes. `options`: `version` (`'1.2'` | `'2004'`), `layout` (`'fixed'` | `'reflowable'`), `items` (the organization; default one SCO per chapter), `metadata`, `fonts`, `svgFonts`, `resourceBytes`, `cover` (as `renderToEpub` takes them), `passingScore` (a percentage, over the books' `exercises.digital.passingScore`), `onProgress`, `onWarning`, `signal`.
|
|
34
|
+
- `items` is a tree of `{ title, doc?, range?, children? }`. An item with `children` is a group; one without is a SCO: chapter `doc` whole, or its `range` (`{ start, end? }`, offsets in the chapter's Markdown as written).
|
|
35
|
+
- `scormTree(chapters, config?)` lists the chapters and their headings to the third level, each with its range. `scormOrganization(tree, picked)` makes `items` from a set of picked leaf ids: a node picked whole is a SCO, a node picked in part groups its picked children.
|
|
36
|
+
|
|
37
|
+
## What the LMS receives
|
|
38
|
+
|
|
39
|
+
- **Score**: each exercise's last check, graded slots right out of graded slots; a SCO's score is the sum over its exercises (SCORM 1.2: a percentage in `cmi.core.score.raw`; SCORM 2004: `cmi.score.scaled`, with the slots as `raw` and `max`).
|
|
40
|
+
- **Completion**: every page or document shown and every exercise checked.
|
|
41
|
+
- **Success**: passed or failed against the passing score, the manifest's (`adlcp:masteryscore`, or a primary objective's `minNormalizedMeasure`) or the one the LMS hands back.
|
|
42
|
+
- **Interactions**: one per check (SCORM 1.2) or one per exercise, updated (SCORM 2004), with the response, the right one, the result and a weighting.
|
|
43
|
+
- **Rollup**: SCORM 2004 weighs each SCO by its graded slots; with SCORM 1.2 the LMS's grading method decides.
|
|
44
|
+
- **Answers** and the checks and fresh starts used, in `cmi.suspend_data`, so the limits of an attempt (`exercises.digital.maxChecks`, `maxResets`, `showSolution`) hold across visits. Past SCORM 1.2's 4,096 characters, the answers stay in the browser and the counts still go to the LMS.
|
|
45
|
+
|
|
46
|
+
Opened without an LMS, the package reads the same and keeps the answers in the browser.
|
|
47
|
+
|
|
48
|
+
The package carries the IMS and ADL schemas its manifest names; the tests validate both versions' manifests against them with `xmllint`. Docs: [SCORM packages](https://postext.dev/en/docs/configuration-html-folio-epub#scorm-packages-postext-scorm).
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
MIT. The schema files are the IMS Global and ADL definitions every SCORM package carries.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ExerciseRecord, VDTDocument } from 'postext';
|
|
2
|
+
import type { ScormLayout, ScormRange } from './types.js';
|
|
3
|
+
/** `doc` cut to `range`, for `layout`. Undefined when nothing of the
|
|
4
|
+
* chapter lies in it. */
|
|
5
|
+
export declare function cutDocument(doc: VDTDocument, range: ScormRange | undefined, layout: ScormLayout): VDTDocument | undefined;
|
|
6
|
+
/** The answers' key of an exercise below the book's: what the exercise
|
|
7
|
+
* script keeps it under in the content document whose data block names
|
|
8
|
+
* `scope` (the fixed layout's document index, the reflowable file's
|
|
9
|
+
* href; see `attachExercises`). */
|
|
10
|
+
export declare function exerciseKey(scope: string | number, record: Pick<ExerciseRecord, 'anchor' | 'ordinal' | 'id'>): string;
|
|
11
|
+
/** The exercises a SCO counts: those of the chapter `doc` whose fence lies
|
|
12
|
+
* in `range` (all of them for a whole chapter), with something to grade,
|
|
13
|
+
* and their graded slots. */
|
|
14
|
+
export declare function ownedExercises(doc: VDTDocument, range: ScormRange | undefined): {
|
|
15
|
+
record: ExerciseRecord;
|
|
16
|
+
points: number;
|
|
17
|
+
}[];
|
|
18
|
+
//# sourceMappingURL=fragments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fragments.d.ts","sourceRoot":"","sources":["../src/fragments.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAY,WAAW,EAAW,MAAM,SAAS,CAAC;AAC9E,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAkB1D;0BAC0B;AAC1B,wBAAgB,WAAW,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,GAAG,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,WAAW,GAAG,SAAS,CAqCzH;AASD;;;oCAGoC;AACpC,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,SAAS,GAAG,IAAI,CAAC,GAAG,MAAM,CAErH;AAED;;8BAE8B;AAC9B,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,CAW5H"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// The documents each SCO holds (#1265): a chapter whole, or the part of it
|
|
2
|
+
// between two heading lines, cut from its laid-out print document by the
|
|
3
|
+
// source offset of each block. The fixed layout keeps whole pages (every
|
|
4
|
+
// page the part touches, so a page two parts share shows in both); the
|
|
5
|
+
// reflowable one keeps exactly the part's blocks.
|
|
6
|
+
//
|
|
7
|
+
// An exercise belongs to the part its box's source lies in: only that SCO
|
|
8
|
+
// counts it, even where its page also shows in a neighbour's.
|
|
9
|
+
import { exercisePoints } from 'postext';
|
|
10
|
+
/** Whether a source offset lies in a range. */
|
|
11
|
+
const within = (at, range) => at >= range.start && (range.end === undefined || at < range.end);
|
|
12
|
+
/** A verdict per block, in reading order: a block that has no source offset
|
|
13
|
+
* of its own (or whose text comes from another chapter) goes with the
|
|
14
|
+
* block before it. */
|
|
15
|
+
function verdicts(blocks, range, state) {
|
|
16
|
+
return blocks.map((b) => {
|
|
17
|
+
if (b.sourceStart !== undefined && b.sourceChapter === undefined)
|
|
18
|
+
state.last = within(b.sourceStart, range);
|
|
19
|
+
return state.last;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** The column blocks of a page, in reading order. */
|
|
23
|
+
const pageBlocks = (page) => page.columns.flatMap((c) => c.blocks);
|
|
24
|
+
/** `doc` cut to `range`, for `layout`. Undefined when nothing of the
|
|
25
|
+
* chapter lies in it. */
|
|
26
|
+
export function cutDocument(doc, range, layout) {
|
|
27
|
+
if (!range)
|
|
28
|
+
return doc;
|
|
29
|
+
if (layout === 'fixed') {
|
|
30
|
+
// Every page from the first to the last that sets a block of the part.
|
|
31
|
+
const state = { last: false };
|
|
32
|
+
let first = -1;
|
|
33
|
+
let last = -1;
|
|
34
|
+
doc.pages.forEach((page, i) => {
|
|
35
|
+
const hits = verdicts([...pageBlocks(page), ...(page.floats ?? []).filter((f) => f.footnoteNote === undefined)], range, state);
|
|
36
|
+
if (hits.some(Boolean)) {
|
|
37
|
+
if (first < 0)
|
|
38
|
+
first = i;
|
|
39
|
+
last = i;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
if (first < 0)
|
|
43
|
+
return undefined;
|
|
44
|
+
return { ...doc, pages: doc.pages.slice(first, last + 1) };
|
|
45
|
+
}
|
|
46
|
+
// Reflowable: the part's blocks, and the footnotes of the pages it keeps.
|
|
47
|
+
const state = { last: false };
|
|
48
|
+
const pages = [];
|
|
49
|
+
for (const page of doc.pages) {
|
|
50
|
+
let kept = 0;
|
|
51
|
+
const columns = page.columns.map((col) => {
|
|
52
|
+
const hits = verdicts(col.blocks, range, state);
|
|
53
|
+
const blocks = col.blocks.filter((_, i) => hits[i]);
|
|
54
|
+
kept += blocks.length;
|
|
55
|
+
return { ...col, blocks };
|
|
56
|
+
});
|
|
57
|
+
const inPart = (b) => b.sourceStart === undefined || b.sourceChapter !== undefined || within(b.sourceStart, range);
|
|
58
|
+
const floats = (page.floats ?? []).filter((f) => (f.footnoteNote !== undefined ? kept > 0 : inPart(f)));
|
|
59
|
+
const marginNotes = page.marginNotes.filter((f) => kept > 0 && inPart(f));
|
|
60
|
+
if (kept === 0 && !floats.some((f) => f.footnoteNote === undefined))
|
|
61
|
+
continue;
|
|
62
|
+
pages.push({ ...page, columns, floats, marginNotes });
|
|
63
|
+
}
|
|
64
|
+
if (pages.length === 0)
|
|
65
|
+
return undefined;
|
|
66
|
+
const all = verdicts(doc.blocks, range, { last: false });
|
|
67
|
+
return { ...doc, pages, blocks: doc.blocks.filter((_, i) => all[i]) };
|
|
68
|
+
}
|
|
69
|
+
/** The source offset of an exercise's box: where the fence opens. */
|
|
70
|
+
function exerciseSource(doc, record) {
|
|
71
|
+
const frames = [...doc.blocks, ...doc.pages.flatMap((p) => p.floats ?? [])];
|
|
72
|
+
const box = frames.find((b) => b.type === 'callout' && b.containerId === record.id && b.sourceStart !== undefined);
|
|
73
|
+
return box?.sourceStart;
|
|
74
|
+
}
|
|
75
|
+
/** The answers' key of an exercise below the book's: what the exercise
|
|
76
|
+
* script keeps it under in the content document whose data block names
|
|
77
|
+
* `scope` (the fixed layout's document index, the reflowable file's
|
|
78
|
+
* href; see `attachExercises`). */
|
|
79
|
+
export function exerciseKey(scope, record) {
|
|
80
|
+
return `${scope}:${record.anchor || `#${record.ordinal}.${record.id}`}`;
|
|
81
|
+
}
|
|
82
|
+
/** The exercises a SCO counts: those of the chapter `doc` whose fence lies
|
|
83
|
+
* in `range` (all of them for a whole chapter), with something to grade,
|
|
84
|
+
* and their graded slots. */
|
|
85
|
+
export function ownedExercises(doc, range) {
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const record of doc.exercises ?? []) {
|
|
88
|
+
if (range) {
|
|
89
|
+
const at = exerciseSource(doc, record);
|
|
90
|
+
if (at === undefined || !within(at, range))
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const points = exercisePoints(record);
|
|
94
|
+
if (points > 0)
|
|
95
|
+
out.push({ record, points });
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=fragments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fragments.js","sourceRoot":"","sources":["../src/fragments.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { VDTDocument } from 'postext';
|
|
2
|
+
import type { RenderToScormOptions } from './types.js';
|
|
3
|
+
/** The content documents' exercise script in a SCORM package (#1267): the
|
|
4
|
+
* exercise runtime, attached with the player's store and results when the
|
|
5
|
+
* player has an LMS, as in an EPUB otherwise. */
|
|
6
|
+
export declare const SCORM_EXERCISE_SCRIPT: string;
|
|
7
|
+
/**
|
|
8
|
+
* Write a laid-out book, or the parts of it `options.items` picks, as a
|
|
9
|
+
* SCORM package (a zip an LMS imports), entirely in memory. `docs` are the
|
|
10
|
+
* book's print documents, one per chapter in order (what `renderToEpub`
|
|
11
|
+
* takes); each leaf of the organization is a SCO cut from one of them. The
|
|
12
|
+
* content is postext-epub's, fixed pages or reflowable text, written as
|
|
13
|
+
* HTML5 under `content/`; each SCO has a launch page (`sco-<n>.html`) with
|
|
14
|
+
* the player and the LMS adapter.
|
|
15
|
+
*/
|
|
16
|
+
export declare function renderToScorm(docs: readonly VDTDocument[], options: RenderToScormOptions): Promise<Uint8Array>;
|
|
17
|
+
export { cutDocument, exerciseKey, ownedExercises } from './fragments.js';
|
|
18
|
+
export { manifestXml } from './manifest.js';
|
|
19
|
+
export { scormTree, scormOrganization, scormLeaves, treeLeaves, pickState, SCORM_TREE_DEPTH, type ScormTreeNode } from './outline.js';
|
|
20
|
+
export { scormWords, PLAYER_SCRIPT } from './player.js';
|
|
21
|
+
export type { RenderToScormOptions, ScormItem, ScormLayout, ScormProgress, ScormRange, ScormVersion, ScormWarning, ScormWords, ScoData, ScoExercise, } from './types.js';
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAS3C,OAAO,KAAK,EAAE,oBAAoB,EAAmC,MAAM,YAAY,CAAC;AAOxF;;kDAEkD;AAClD,eAAO,MAAM,qBAAqB,QAajC,CAAC;AAuCF;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,CAAC,CAsIpH;AAED,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AACtI,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EACV,oBAAoB,EACpB,SAAS,EACT,WAAW,EACX,aAAa,EACb,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,OAAO,EACP,WAAW,GACZ,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { directionOf, EXERCISE_RUNTIME } from 'postext';
|
|
2
|
+
import { bookIdentifier, buildFixedPublication, buildReflowablePublication } from 'postext-epub';
|
|
3
|
+
import { zipSync } from 'fflate';
|
|
4
|
+
import { cutDocument, exerciseKey, ownedExercises } from './fragments.js';
|
|
5
|
+
import { manifestXml, ncName } from './manifest.js';
|
|
6
|
+
import { scormLeaves } from './outline.js';
|
|
7
|
+
import { PLAYER_CSS, PLAYER_SCRIPT, scoPage, scormWords } from './player.js';
|
|
8
|
+
import schemas from './schemas.json' with { type: 'json' };
|
|
9
|
+
/** Where the content documents and their files go in the package. */
|
|
10
|
+
const CONTENT_DIR = 'content/';
|
|
11
|
+
/** The EPUB writer's exercise script, which the package replaces. */
|
|
12
|
+
const EXERCISE_SCRIPT_ID = 'pt-exercise-script';
|
|
13
|
+
/** The content documents' exercise script in a SCORM package (#1267): the
|
|
14
|
+
* exercise runtime, attached with the player's store and results when the
|
|
15
|
+
* player has an LMS, as in an EPUB otherwise. */
|
|
16
|
+
export const SCORM_EXERCISE_SCRIPT = `(function () {
|
|
17
|
+
var runtime = (${EXERCISE_RUNTIME})();
|
|
18
|
+
function host() {
|
|
19
|
+
try { var w = window.parent; if (w && w !== window && w.PostextScorm) return w.PostextScorm; } catch (e) { return null; }
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
function go() {
|
|
23
|
+
var h = host();
|
|
24
|
+
runtime.attach(document, h && h.live ? { store: h.store, onResult: h.result } : {});
|
|
25
|
+
}
|
|
26
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', go);
|
|
27
|
+
else go();
|
|
28
|
+
})();
|
|
29
|
+
`;
|
|
30
|
+
/** Types stored as they are (already compressed). */
|
|
31
|
+
const STORED = /^(image\/(png|jpeg|webp|gif)|font\/woff2?|video\/|audio\/)/;
|
|
32
|
+
/** A path relative to a document's directory, resolved from the root. */
|
|
33
|
+
function resolvePath(fromHref, target) {
|
|
34
|
+
const parts = fromHref.split('/').slice(0, -1);
|
|
35
|
+
for (const seg of target.split('/')) {
|
|
36
|
+
if (seg === '..')
|
|
37
|
+
parts.pop();
|
|
38
|
+
else if (seg !== '.' && seg !== '')
|
|
39
|
+
parts.push(seg);
|
|
40
|
+
}
|
|
41
|
+
return parts.join('/');
|
|
42
|
+
}
|
|
43
|
+
/** A content document with its links to another SCO's documents taken
|
|
44
|
+
* out: an LMS tracks a SCO by what it opens, so each one keeps to its own. */
|
|
45
|
+
function keepLinksHome(xhtml, href, foreign) {
|
|
46
|
+
return xhtml.replace(/(<a\b[^>]*?)\shref="([^"#:]+)(#[^"]*)?"/g, (whole, open, path) => foreign.has(resolvePath(href, path)) ? open : whole);
|
|
47
|
+
}
|
|
48
|
+
/** The headings of a publication's contents, with their depth. */
|
|
49
|
+
function flatToc(points, depth = 0) {
|
|
50
|
+
return points.flatMap((p) => [{ label: p.label, href: p.href, depth }, ...flatToc(p.children ?? [], depth + 1)]);
|
|
51
|
+
}
|
|
52
|
+
const text = (data) => (typeof data === 'string' ? data : new TextDecoder().decode(data));
|
|
53
|
+
/** The default organization: each document a SCO, titled after its first
|
|
54
|
+
* heading. */
|
|
55
|
+
function defaultItems(docs) {
|
|
56
|
+
return docs.map((doc, i) => {
|
|
57
|
+
const heading = doc.blocks.find((b) => b.headingLevel !== undefined);
|
|
58
|
+
const title = heading ? heading.lines.map((l) => (l.segments ?? []).map((s) => s.text).join('')).join(' ').trim() : '';
|
|
59
|
+
return { title: title || String(i + 1), doc: i };
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Write a laid-out book, or the parts of it `options.items` picks, as a
|
|
64
|
+
* SCORM package (a zip an LMS imports), entirely in memory. `docs` are the
|
|
65
|
+
* book's print documents, one per chapter in order (what `renderToEpub`
|
|
66
|
+
* takes); each leaf of the organization is a SCO cut from one of them. The
|
|
67
|
+
* content is postext-epub's, fixed pages or reflowable text, written as
|
|
68
|
+
* HTML5 under `content/`; each SCO has a launch page (`sco-<n>.html`) with
|
|
69
|
+
* the player and the LMS adapter.
|
|
70
|
+
*/
|
|
71
|
+
export async function renderToScorm(docs, options) {
|
|
72
|
+
if (docs.length === 0)
|
|
73
|
+
throw new Error('renderToScorm: no documents');
|
|
74
|
+
const { layout, version, signal } = options;
|
|
75
|
+
const items = options.items ?? defaultItems(docs);
|
|
76
|
+
const metadata = { ...options.metadata, identifier: bookIdentifier(options.metadata) };
|
|
77
|
+
// The SCOs and the documents they hold, one per SCO, in order.
|
|
78
|
+
const parts = [];
|
|
79
|
+
for (const item of scormLeaves(items)) {
|
|
80
|
+
const source = item.doc !== undefined ? docs[item.doc] : undefined;
|
|
81
|
+
if (!source)
|
|
82
|
+
continue;
|
|
83
|
+
const cut = cutDocument(source, item.range, layout);
|
|
84
|
+
if (cut)
|
|
85
|
+
parts.push({ item, doc: cut, source });
|
|
86
|
+
else
|
|
87
|
+
options.onWarning?.({ kind: 'unsupported', detail: `"${item.title}" holds nothing to show` });
|
|
88
|
+
}
|
|
89
|
+
if (parts.length === 0)
|
|
90
|
+
throw new Error('renderToScorm: nothing to write');
|
|
91
|
+
const epubOptions = {
|
|
92
|
+
layout,
|
|
93
|
+
metadata,
|
|
94
|
+
...(options.fonts ? { fonts: options.fonts } : {}),
|
|
95
|
+
...(options.svgFonts ? { svgFonts: options.svgFonts } : {}),
|
|
96
|
+
...(options.resourceBytes ? { resourceBytes: options.resourceBytes } : {}),
|
|
97
|
+
...(options.cover && layout === 'reflowable' ? { cover: options.cover } : {}),
|
|
98
|
+
...(options.onProgress ? { onProgress: (p) => options.onProgress({ ...p }) } : {}),
|
|
99
|
+
...(options.onWarning ? { onWarning: options.onWarning } : {}),
|
|
100
|
+
...(signal ? { signal } : {}),
|
|
101
|
+
};
|
|
102
|
+
const cuts = parts.map((p) => p.doc);
|
|
103
|
+
const pub = layout === 'fixed'
|
|
104
|
+
? await buildFixedPublication(cuts, epubOptions)
|
|
105
|
+
: await buildReflowablePublication(cuts, epubOptions);
|
|
106
|
+
signal?.throwIfAborted();
|
|
107
|
+
options.onProgress?.({ phase: 'package', done: 0, total: 1 });
|
|
108
|
+
// Each SCO's content documents, in reading order.
|
|
109
|
+
const byId = new Map(pub.items.map((i) => [i.id, i]));
|
|
110
|
+
const docsOf = parts.map(() => []);
|
|
111
|
+
pub.spine.forEach((entry, i) => {
|
|
112
|
+
const item = byId.get(entry.idref);
|
|
113
|
+
if (!item)
|
|
114
|
+
return;
|
|
115
|
+
// The cover opens the first SCO.
|
|
116
|
+
const sco = entry.doc ?? (i === 0 ? 0 : undefined);
|
|
117
|
+
if (sco !== undefined)
|
|
118
|
+
docsOf[sco].push(item);
|
|
119
|
+
});
|
|
120
|
+
const scoOfHref = new Map();
|
|
121
|
+
docsOf.forEach((list, s) => list.forEach((d) => scoOfHref.set(d.href, s)));
|
|
122
|
+
const labelOf = new Map(pub.pageList.map((p) => [p.href.split('#')[0], p.label]));
|
|
123
|
+
const toc = flatToc(pub.toc);
|
|
124
|
+
const lang = metadata.language;
|
|
125
|
+
const words = scormWords(lang);
|
|
126
|
+
const storageKey = `pt-ex:${metadata.identifier}`;
|
|
127
|
+
const passingPercent = options.passingScore ?? docs.find((d) => d.exerciseDigital?.passingScore)?.exerciseDigital?.passingScore ?? 0;
|
|
128
|
+
const passing = passingPercent > 0 ? Math.min(100, passingPercent) / 100 : undefined;
|
|
129
|
+
const page = pub.fixed?.viewport;
|
|
130
|
+
const files = {};
|
|
131
|
+
const enc = new TextEncoder();
|
|
132
|
+
const put = (path, data, stored = false) => {
|
|
133
|
+
files[path] = [typeof data === 'string' ? enc.encode(data) : data, { level: stored ? 0 : 9 }];
|
|
134
|
+
};
|
|
135
|
+
const scos = [];
|
|
136
|
+
const scoOfItem = new Map();
|
|
137
|
+
parts.forEach((part, s) => {
|
|
138
|
+
const own = docsOf[s];
|
|
139
|
+
const ownSet = new Set(own.map((d) => d.href));
|
|
140
|
+
const foreign = new Set([...scoOfHref.keys()].filter((h) => !ownSet.has(h)));
|
|
141
|
+
for (const d of own)
|
|
142
|
+
d.data = keepLinksHome(text(d.data), d.href, foreign);
|
|
143
|
+
const exercises = [];
|
|
144
|
+
for (const { record, points } of ownedExercises(part.source, part.item.range)) {
|
|
145
|
+
// The fixed layout keeps answers per document, the reflowable book
|
|
146
|
+
// per file: the file that sets the exercise's box.
|
|
147
|
+
const scope = layout === 'fixed'
|
|
148
|
+
? String(s)
|
|
149
|
+
: own.find((d) => text(d.data).includes(`data-pt-exercise="${record.id}"`))?.href;
|
|
150
|
+
if (scope === undefined)
|
|
151
|
+
continue;
|
|
152
|
+
exercises.push({ key: exerciseKey(scope, record), points, number: record.number, ...(record.title ? { title: record.title } : {}) });
|
|
153
|
+
}
|
|
154
|
+
const data = {
|
|
155
|
+
version,
|
|
156
|
+
layout,
|
|
157
|
+
title: part.item.title,
|
|
158
|
+
book: metadata.title,
|
|
159
|
+
lang,
|
|
160
|
+
dir: directionOf(lang),
|
|
161
|
+
docs: own.map((d, i) => ({ href: CONTENT_DIR + d.href, label: labelOf.get(d.href) ?? String(i + 1) })),
|
|
162
|
+
toc: toc.flatMap((e) => {
|
|
163
|
+
const file = e.href.split('#')[0];
|
|
164
|
+
const i = own.findIndex((d) => d.href === file);
|
|
165
|
+
return i < 0 ? [] : [{ label: e.label, doc: i, href: CONTENT_DIR + e.href, depth: e.depth }];
|
|
166
|
+
}),
|
|
167
|
+
...(page ? { page } : {}),
|
|
168
|
+
...(pub.pageProgression === 'rtl' ? { rtl: true } : {}),
|
|
169
|
+
storageKey,
|
|
170
|
+
exercises,
|
|
171
|
+
...(passing !== undefined ? { passing } : {}),
|
|
172
|
+
words,
|
|
173
|
+
};
|
|
174
|
+
const href = `sco-${s + 1}.html`;
|
|
175
|
+
put(href, scoPage(data));
|
|
176
|
+
const sco = { item: part.item, href, files: own.map((d) => CONTENT_DIR + d.href), points: exercises.reduce((n, e) => n + e.points, 0) };
|
|
177
|
+
scos.push(sco);
|
|
178
|
+
scoOfItem.set(part.item, sco);
|
|
179
|
+
});
|
|
180
|
+
// The content, with the exercise script that reports to the player.
|
|
181
|
+
const shared = ['player/player.js', 'player/player.css'];
|
|
182
|
+
put('player/player.js', PLAYER_SCRIPT);
|
|
183
|
+
put('player/player.css', PLAYER_CSS);
|
|
184
|
+
for (const item of pub.items) {
|
|
185
|
+
if (item.remote)
|
|
186
|
+
continue;
|
|
187
|
+
const data = item.id === EXERCISE_SCRIPT_ID ? SCORM_EXERCISE_SCRIPT : item.data;
|
|
188
|
+
put(CONTENT_DIR + item.href, data, STORED.test(item.mediaType));
|
|
189
|
+
// A content document no SCO shows (a navigation document) is shared.
|
|
190
|
+
if (!scoOfHref.has(item.href))
|
|
191
|
+
shared.push(CONTENT_DIR + item.href);
|
|
192
|
+
}
|
|
193
|
+
// The schemas the manifest names, at the package's root.
|
|
194
|
+
for (const name of Object.keys(schemas[version] ?? {})) {
|
|
195
|
+
put(name, schemas[version][name]);
|
|
196
|
+
}
|
|
197
|
+
put('imsmanifest.xml', manifestXml({
|
|
198
|
+
version,
|
|
199
|
+
identifier: ncName(`postext-${metadata.identifier}`),
|
|
200
|
+
title: metadata.title,
|
|
201
|
+
language: lang,
|
|
202
|
+
items,
|
|
203
|
+
scoOf: (item) => scoOfItem.get(item),
|
|
204
|
+
shared,
|
|
205
|
+
...(passing !== undefined ? { passing } : {}),
|
|
206
|
+
}));
|
|
207
|
+
const zip = zipSync(files, { mtime: new Date('2026-01-01T00:00:00Z') });
|
|
208
|
+
options.onProgress?.({ phase: 'package', done: 1, total: 1 });
|
|
209
|
+
return zip;
|
|
210
|
+
}
|
|
211
|
+
export { cutDocument, exerciseKey, ownedExercises } from './fragments.js';
|
|
212
|
+
export { manifestXml } from './manifest.js';
|
|
213
|
+
export { scormTree, scormOrganization, scormLeaves, treeLeaves, pickState, SCORM_TREE_DEPTH } from './outline.js';
|
|
214
|
+
export { scormWords, PLAYER_SCRIPT } from './player.js';
|
|
215
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ScormItem, ScormVersion } from './types.js';
|
|
2
|
+
export interface ManifestSco {
|
|
3
|
+
/** Its item in the organization. */
|
|
4
|
+
item: ScormItem;
|
|
5
|
+
/** Launch page and the files only it uses, from the package root. */
|
|
6
|
+
href: string;
|
|
7
|
+
files: string[];
|
|
8
|
+
/** Its exercises' graded slots. */
|
|
9
|
+
points: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ManifestInput {
|
|
12
|
+
version: ScormVersion;
|
|
13
|
+
identifier: string;
|
|
14
|
+
title: string;
|
|
15
|
+
language: string;
|
|
16
|
+
items: ScormItem[];
|
|
17
|
+
/** The SCO of each leaf item. */
|
|
18
|
+
scoOf: (item: ScormItem) => ManifestSco | undefined;
|
|
19
|
+
/** The shared files. */
|
|
20
|
+
shared: string[];
|
|
21
|
+
/** The passing score as a fraction, when set. */
|
|
22
|
+
passing?: number;
|
|
23
|
+
}
|
|
24
|
+
/** An XML NCName from any text. */
|
|
25
|
+
export declare function ncName(s: string): string;
|
|
26
|
+
export declare function manifestXml(input: ManifestInput): string;
|
|
27
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1D,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,IAAI,EAAE,SAAS,CAAC;IAChB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,YAAY,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,iCAAiC;IACjC,KAAK,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,WAAW,GAAG,SAAS,CAAC;IACpD,wBAAwB;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,mCAAmC;AACnC,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAGxC;AAID,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAiExD"}
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// The package's manifest (`imsmanifest.xml`, #1265): the organization the
|
|
2
|
+
// LMS shows, mirroring the items picked, one resource per SCO (its launch
|
|
3
|
+
// page and documents) and one shared resource (styles, fonts, pictures,
|
|
4
|
+
// scripts) every SCO depends on.
|
|
5
|
+
//
|
|
6
|
+
// SCORM 1.2 carries the passing score as each SCO's `adlcp:masteryscore`.
|
|
7
|
+
// SCORM 2004 carries it as each SCO's primary objective, satisfied by
|
|
8
|
+
// measure, and weights each SCO's measure by its graded slots (over the
|
|
9
|
+
// largest SCO's: the schema keeps weights between 0 and 1), so the course's
|
|
10
|
+
// score is every right slot over every graded slot.
|
|
11
|
+
const xml = (s) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
12
|
+
/** An XML NCName from any text. */
|
|
13
|
+
export function ncName(s) {
|
|
14
|
+
const n = s.replace(/[^A-Za-z0-9_.-]/g, '-');
|
|
15
|
+
return /^[A-Za-z_]/.test(n) ? n : `id-${n}`;
|
|
16
|
+
}
|
|
17
|
+
const fmt = (n) => String(Math.round(n * 10000) / 10000);
|
|
18
|
+
export function manifestXml(input) {
|
|
19
|
+
const v2004 = input.version === '2004';
|
|
20
|
+
const scos = [];
|
|
21
|
+
let n = 0;
|
|
22
|
+
const leaves = (list) => list.flatMap((i) => (i.children ? leaves(i.children) : [i]));
|
|
23
|
+
const most = Math.max(0, ...leaves(input.items).map((i) => input.scoOf(i)?.points ?? 0));
|
|
24
|
+
const itemXml = (item, depth) => {
|
|
25
|
+
const pad = ' '.repeat(depth + 3);
|
|
26
|
+
const id = `item-${++n}`;
|
|
27
|
+
const title = `${pad} <title>${xml(item.title)}</title>\n`;
|
|
28
|
+
if (item.children) {
|
|
29
|
+
return `${pad}<item identifier="${id}">\n${title}${item.children.map((c) => itemXml(c, depth + 1)).join('')}${pad}</item>\n`;
|
|
30
|
+
}
|
|
31
|
+
const sco = input.scoOf(item);
|
|
32
|
+
if (!sco)
|
|
33
|
+
return '';
|
|
34
|
+
scos.push(sco);
|
|
35
|
+
const res = `res-${scos.length}`;
|
|
36
|
+
let extra = '';
|
|
37
|
+
if (v2004) {
|
|
38
|
+
const objective = input.passing !== undefined && sco.points > 0
|
|
39
|
+
? `${pad} <imsss:objectives>\n${pad} <imsss:primaryObjective objectiveID="${id}-score" satisfiedByMeasure="true">\n${pad} <imsss:minNormalizedMeasure>${fmt(input.passing)}</imsss:minNormalizedMeasure>\n${pad} </imsss:primaryObjective>\n${pad} </imsss:objectives>\n`
|
|
40
|
+
: '';
|
|
41
|
+
extra = `${pad} <imsss:sequencing>\n${pad} <imsss:rollupRules objectiveMeasureWeight="${most > 0 ? fmt(sco.points / most) : 1}"/>\n${objective}${pad} </imsss:sequencing>\n`;
|
|
42
|
+
}
|
|
43
|
+
else if (input.passing !== undefined && sco.points > 0) {
|
|
44
|
+
extra = `${pad} <adlcp:masteryscore>${Math.round(input.passing * 100)}</adlcp:masteryscore>\n`;
|
|
45
|
+
}
|
|
46
|
+
return `${pad}<item identifier="${id}" identifierref="${res}"${v2004 ? '' : ' isvisible="true"'}>\n${title}${extra}${pad}</item>\n`;
|
|
47
|
+
};
|
|
48
|
+
const items = input.items.map((i) => itemXml(i, 0)).join('');
|
|
49
|
+
const scormType = v2004 ? 'adlcp:scormType' : 'adlcp:scormtype';
|
|
50
|
+
const files = (list, pad) => list.map((f) => `${pad}<file href="${xml(f)}"/>\n`).join('');
|
|
51
|
+
const resources = scos.map((sco, i) => ` <resource identifier="res-${i + 1}" type="webcontent" ${scormType}="sco" href="${xml(sco.href)}">\n` +
|
|
52
|
+
files([sco.href, ...sco.files], ' ') +
|
|
53
|
+
` <dependency identifierref="shared"/>\n` +
|
|
54
|
+
` </resource>\n`).join('') +
|
|
55
|
+
` <resource identifier="shared" type="webcontent" ${scormType}="asset">\n${files(input.shared, ' ')} </resource>\n`;
|
|
56
|
+
const head = v2004
|
|
57
|
+
? `<manifest identifier="${xml(input.identifier)}" version="1.0"\n` +
|
|
58
|
+
` xmlns="http://www.imsglobal.org/xsd/imscp_v1p1"\n` +
|
|
59
|
+
` xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_v1p3"\n` +
|
|
60
|
+
` xmlns:adlseq="http://www.adlnet.org/xsd/adlseq_v1p3"\n` +
|
|
61
|
+
` xmlns:adlnav="http://www.adlnet.org/xsd/adlnav_v1p3"\n` +
|
|
62
|
+
` xmlns:imsss="http://www.imsglobal.org/xsd/imsss"\n` +
|
|
63
|
+
` xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n` +
|
|
64
|
+
` xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd http://www.adlnet.org/xsd/adlcp_v1p3 adlcp_v1p3.xsd http://www.adlnet.org/xsd/adlseq_v1p3 adlseq_v1p3.xsd http://www.adlnet.org/xsd/adlnav_v1p3 adlnav_v1p3.xsd http://www.imsglobal.org/xsd/imsss imsss_v1p0.xsd">\n` +
|
|
65
|
+
` <metadata>\n <schema>ADL SCORM</schema>\n <schemaversion>2004 4th Edition</schemaversion>\n </metadata>\n`
|
|
66
|
+
: `<manifest identifier="${xml(input.identifier)}" version="1.0"\n` +
|
|
67
|
+
` xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2"\n` +
|
|
68
|
+
` xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2"\n` +
|
|
69
|
+
` xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n` +
|
|
70
|
+
` xsi:schemaLocation="http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd">\n` +
|
|
71
|
+
` <metadata>\n <schema>ADL SCORM</schema>\n <schemaversion>1.2</schemaversion>\n </metadata>\n`;
|
|
72
|
+
const orgSequencing = v2004 ? ` <imsss:sequencing>\n <imsss:controlMode choice="true" flow="true"/>\n </imsss:sequencing>\n` : '';
|
|
73
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n` + head +
|
|
74
|
+
` <organizations default="org">\n` +
|
|
75
|
+
` <organization identifier="org">\n` +
|
|
76
|
+
` <title>${xml(input.title)}</title>\n` +
|
|
77
|
+
items.replace(/^ {6}/gm, ' ') +
|
|
78
|
+
orgSequencing +
|
|
79
|
+
` </organization>\n` +
|
|
80
|
+
` </organizations>\n` +
|
|
81
|
+
` <resources>\n${resources} </resources>\n` +
|
|
82
|
+
`</manifest>\n`;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
|