@qti-editor/core 1.1.1 → 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/composer/index.d.ts
CHANGED
|
@@ -37,5 +37,15 @@ export interface ResponseDeclaration {
|
|
|
37
37
|
}
|
|
38
38
|
export declare function extractResponseDeclarations(itemBodyRoot?: Element | null): ResponseDeclaration[];
|
|
39
39
|
export declare function buildAssessmentItemXml(itemContext?: ComposerItemContext): string;
|
|
40
|
+
/**
|
|
41
|
+
* Build multiple QTI assessment items from a single editor document.
|
|
42
|
+
*
|
|
43
|
+
* Splits the item body at qti-item-divider elements and generates
|
|
44
|
+
* a separate <qti-assessment-item> for each segment.
|
|
45
|
+
*
|
|
46
|
+
* @returns XML string containing multiple assessment items wrapped in a container,
|
|
47
|
+
* or a single assessment item if no dividers are found.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildMultipleAssessmentItemsXml(itemContext?: ComposerItemContext): string;
|
|
40
50
|
export declare function formatXml(xml: string): string;
|
|
41
51
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/composer/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAErE,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;IAC/C,QAAQ,EAAE,YAAY,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,KAAK,CAAC;YACb,MAAM,EAAE,MAAM,CAAC;YACf,WAAW,EAAE,MAAM,CAAC;YACpB,aAAa,EAAE,OAAO,CAAC;SACxB,CAAC,CAAC;KACJ,CAAC;IACF,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD,WAAW,CAAC,EAAE;QACZ,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,KAAK,CAAC;YACb,KAAK,EAAE,QAAQ,GAAG,MAAM,CAAC;YACzB,MAAM,EAAE,MAAM,CAAC;YACf,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC,CAAC;KACJ,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;CACnB;AAoCD,wBAAgB,2BAA2B,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,mBAAmB,EAAE,CAOhG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/composer/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAErE,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;IAC/C,QAAQ,EAAE,YAAY,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,KAAK,CAAC;YACb,MAAM,EAAE,MAAM,CAAC;YACf,WAAW,EAAE,MAAM,CAAC;YACpB,aAAa,EAAE,OAAO,CAAC;SACxB,CAAC,CAAC;KACJ,CAAC;IACF,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD,WAAW,CAAC,EAAE;QACZ,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,KAAK,CAAC;YACb,KAAK,EAAE,QAAQ,GAAG,MAAM,CAAC;YACzB,MAAM,EAAE,MAAM,CAAC;YACf,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC,CAAC;KACJ,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;CACnB;AAoCD,wBAAgB,2BAA2B,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,mBAAmB,EAAE,CAOhG;AAsDD,wBAAgB,sBAAsB,CAAC,WAAW,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAqHhF;AAED;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAAC,WAAW,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAkCzF;AAgLD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA4B7C"}
|
package/dist/composer/index.js
CHANGED
|
@@ -40,6 +40,51 @@ export function extractResponseDeclarations(itemBodyRoot) {
|
|
|
40
40
|
const { declarations } = composeAndNormalizeItemBody(tempRoot, tempDoc);
|
|
41
41
|
return declarations;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Split an item body document at qti-item-divider elements.
|
|
45
|
+
* Returns an array of item body fragments, one for each item.
|
|
46
|
+
*/
|
|
47
|
+
function splitItemBodyAtDividers(itemBodyDoc) {
|
|
48
|
+
const itemBodyRoot = itemBodyDoc.querySelector('qti-item-body') ??
|
|
49
|
+
(itemBodyDoc.documentElement?.tagName.toLowerCase() === 'qti-item-body'
|
|
50
|
+
? itemBodyDoc.documentElement
|
|
51
|
+
: null);
|
|
52
|
+
if (!itemBodyRoot)
|
|
53
|
+
return [];
|
|
54
|
+
// Find all dividers
|
|
55
|
+
const dividers = Array.from(itemBodyRoot.querySelectorAll('qti-item-divider'));
|
|
56
|
+
// If no dividers, return the whole body as a single item
|
|
57
|
+
if (dividers.length === 0) {
|
|
58
|
+
return [itemBodyRoot];
|
|
59
|
+
}
|
|
60
|
+
const fragments = [];
|
|
61
|
+
const children = Array.from(itemBodyRoot.childNodes);
|
|
62
|
+
let currentFragment = [];
|
|
63
|
+
for (const child of children) {
|
|
64
|
+
// Check if this child is a divider
|
|
65
|
+
if (child.nodeType === Node.ELEMENT_NODE &&
|
|
66
|
+
child.tagName.toLowerCase() === 'qti-item-divider') {
|
|
67
|
+
// Save the current fragment if it has content
|
|
68
|
+
if (currentFragment.length > 0) {
|
|
69
|
+
const fragmentBody = itemBodyDoc.createElementNS(QTI_NS, 'qti-item-body');
|
|
70
|
+
currentFragment.forEach(node => fragmentBody.appendChild(node.cloneNode(true)));
|
|
71
|
+
fragments.push(fragmentBody);
|
|
72
|
+
currentFragment = [];
|
|
73
|
+
}
|
|
74
|
+
// Skip the divider itself - it doesn't go into any fragment
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
currentFragment.push(child);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Add the last fragment if it has content
|
|
81
|
+
if (currentFragment.length > 0) {
|
|
82
|
+
const fragmentBody = itemBodyDoc.createElementNS(QTI_NS, 'qti-item-body');
|
|
83
|
+
currentFragment.forEach(node => fragmentBody.appendChild(node.cloneNode(true)));
|
|
84
|
+
fragments.push(fragmentBody);
|
|
85
|
+
}
|
|
86
|
+
return fragments;
|
|
87
|
+
}
|
|
43
88
|
export function buildAssessmentItemXml(itemContext) {
|
|
44
89
|
if (!itemContext)
|
|
45
90
|
return '';
|
|
@@ -137,6 +182,44 @@ export function buildAssessmentItemXml(itemContext) {
|
|
|
137
182
|
}
|
|
138
183
|
return new XMLSerializer().serializeToString(xmlDoc);
|
|
139
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Build multiple QTI assessment items from a single editor document.
|
|
187
|
+
*
|
|
188
|
+
* Splits the item body at qti-item-divider elements and generates
|
|
189
|
+
* a separate <qti-assessment-item> for each segment.
|
|
190
|
+
*
|
|
191
|
+
* @returns XML string containing multiple assessment items wrapped in a container,
|
|
192
|
+
* or a single assessment item if no dividers are found.
|
|
193
|
+
*/
|
|
194
|
+
export function buildMultipleAssessmentItemsXml(itemContext) {
|
|
195
|
+
if (!itemContext?.itemBody)
|
|
196
|
+
return '';
|
|
197
|
+
const fragments = splitItemBodyAtDividers(itemContext.itemBody);
|
|
198
|
+
// If only one fragment (no dividers), use the regular single-item builder
|
|
199
|
+
if (fragments.length <= 1) {
|
|
200
|
+
return buildAssessmentItemXml(itemContext);
|
|
201
|
+
}
|
|
202
|
+
// Build multiple items
|
|
203
|
+
const baseIdentifier = itemContext.identifier?.trim() || 'item';
|
|
204
|
+
const baseTitle = itemContext.title?.trim() || 'Untitled Item';
|
|
205
|
+
const lang = itemContext.lang?.trim() || 'en';
|
|
206
|
+
const itemXmls = fragments.map((fragmentBody, index) => {
|
|
207
|
+
const itemNumber = index + 1;
|
|
208
|
+
const fragmentDoc = document.implementation.createDocument(QTI_NS, 'qti-item-body', null);
|
|
209
|
+
const importedFragment = fragmentDoc.importNode(fragmentBody, true);
|
|
210
|
+
fragmentDoc.replaceChild(importedFragment, fragmentDoc.documentElement);
|
|
211
|
+
const fragmentContext = {
|
|
212
|
+
identifier: `${baseIdentifier}-${itemNumber}`,
|
|
213
|
+
title: `${baseTitle} ${itemNumber}`,
|
|
214
|
+
lang,
|
|
215
|
+
itemBody: fragmentDoc,
|
|
216
|
+
};
|
|
217
|
+
return buildAssessmentItemXml(fragmentContext);
|
|
218
|
+
});
|
|
219
|
+
// Join multiple items with a comment separator for clarity
|
|
220
|
+
const separator = '\n\n<!-- Next Assessment Item -->\n\n';
|
|
221
|
+
return itemXmls.join(separator);
|
|
222
|
+
}
|
|
140
223
|
function composeAndNormalizeItemBody(itemBody, xmlDoc) {
|
|
141
224
|
const declarations = [];
|
|
142
225
|
const seenIdentifiers = new Set();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composer.d.ts","sourceRoot":"","sources":["../../src/interactions/composer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"composer.d.ts","sourceRoot":"","sources":["../../src/interactions/composer.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,wBAAwB,CAAC;AAmChC,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,2BAA2B,GAAG,SAAS,CAEvG;AAED,wBAAgB,4CAA4C,CAC1D,YAAY,EAAE,MAAM,GACnB,2BAA2B,GAAG,SAAS,CAEzC;AAED,wBAAgB,2CAA2C,CACzD,YAAY,EAAE,MAAM,GACnB,0BAA0B,GAAG,SAAS,CAExC;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,0BAA0B,GAAG,SAAS,CAErG;AAED,wBAAgB,+BAA+B,IAAI,aAAa,CAAC,0BAA0B,CAAC,CAE3F;AAED,wBAAgB,0BAA0B,IAAI,aAAa,CAAC,qBAAqB,CAAC,CAEjF;AAED,wBAAgB,8BAA8B,IAAI,aAAa,CAC7D,WAAW,CAAC,qBAAqB,CAAC,iBAAiB,CAAC,CAAC,CAAC,MAAM,CAAC,CAC9D,CAEA"}
|
|
@@ -7,6 +7,7 @@ import { matchInteractionDescriptor } from '@qti-editor/interaction-match';
|
|
|
7
7
|
import { orderInteractionDescriptor } from '@qti-editor/interaction-order';
|
|
8
8
|
import { selectPointInteractionDescriptor } from '@qti-editor/interaction-select-point';
|
|
9
9
|
import { textEntryInteractionDescriptor } from '@qti-editor/interaction-text-entry';
|
|
10
|
+
import { qtiItemDividerDescriptor } from '@qti-editor/qti-item-divider';
|
|
10
11
|
const registeredDescriptors = [
|
|
11
12
|
associateInteractionDescriptor,
|
|
12
13
|
choiceInteractionDescriptor,
|
|
@@ -17,6 +18,7 @@ const registeredDescriptors = [
|
|
|
17
18
|
orderInteractionDescriptor,
|
|
18
19
|
selectPointInteractionDescriptor,
|
|
19
20
|
textEntryInteractionDescriptor,
|
|
21
|
+
qtiItemDividerDescriptor,
|
|
20
22
|
];
|
|
21
23
|
const metadataByTagName = new Map(registeredDescriptors.map(d => [d.tagName, d.composerMetadata]));
|
|
22
24
|
const metadataByNodeTypeName = new Map(registeredDescriptors.map(d => [d.nodeTypeName.toLowerCase(), d.composerMetadata]));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qti-editor/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "QTI semantics, composer registry, and XML export orchestration for QTI Editor",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,16 +25,17 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@qti-editor/interfaces": "1.1.0",
|
|
28
|
-
"@qti-editor/interaction-associate": "1.1.
|
|
29
|
-
"@qti-editor/interaction-choice": "1.1.
|
|
30
|
-
"@qti-editor/interaction-extended-text": "1.1.
|
|
31
|
-
"@qti-editor/interaction-hottext": "1.1.
|
|
32
|
-
"@qti-editor/interaction-inline-choice": "1.1.
|
|
33
|
-
"@qti-editor/interaction-match": "1.1.
|
|
34
|
-
"@qti-editor/interaction-order": "0.5.
|
|
35
|
-
"@qti-editor/interaction-select-point": "1.1.
|
|
36
|
-
"@qti-editor/interaction-shared": "1.
|
|
37
|
-
"@qti-editor/interaction-text-entry": "1.1.
|
|
28
|
+
"@qti-editor/interaction-associate": "1.1.3",
|
|
29
|
+
"@qti-editor/interaction-choice": "1.1.3",
|
|
30
|
+
"@qti-editor/interaction-extended-text": "1.1.3",
|
|
31
|
+
"@qti-editor/interaction-hottext": "1.1.3",
|
|
32
|
+
"@qti-editor/interaction-inline-choice": "1.1.1",
|
|
33
|
+
"@qti-editor/interaction-match": "1.1.1",
|
|
34
|
+
"@qti-editor/interaction-order": "0.5.1",
|
|
35
|
+
"@qti-editor/interaction-select-point": "1.1.1",
|
|
36
|
+
"@qti-editor/interaction-shared": "1.2.0",
|
|
37
|
+
"@qti-editor/interaction-text-entry": "1.1.2",
|
|
38
|
+
"@qti-editor/qti-item-divider": "1.0.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@types/node": "^20.0.0",
|