@qti-editor/core 1.2.0 → 1.3.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/dist/composer/index.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ export interface ResponseDeclaration {
|
|
|
34
34
|
}>;
|
|
35
35
|
};
|
|
36
36
|
sourceTag: string;
|
|
37
|
+
score?: number;
|
|
37
38
|
}
|
|
38
39
|
export declare function extractResponseDeclarations(itemBodyRoot?: Element | null): ResponseDeclaration[];
|
|
39
40
|
export declare function buildAssessmentItemXml(itemContext?: ComposerItemContext): string;
|
|
@@ -47,5 +48,25 @@ export declare function buildAssessmentItemXml(itemContext?: ComposerItemContext
|
|
|
47
48
|
* or a single assessment item if no dividers are found.
|
|
48
49
|
*/
|
|
49
50
|
export declare function buildMultipleAssessmentItemsXml(itemContext?: ComposerItemContext): string;
|
|
51
|
+
/**
|
|
52
|
+
* Build a single QTI assessment item, converting any dividers to <hr /> elements.
|
|
53
|
+
* Use this when you want to export the entire editor content as one item.
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildSingleAssessmentItemXml(itemContext?: ComposerItemContext): string;
|
|
56
|
+
/**
|
|
57
|
+
* Count how many items would be generated from an item body document.
|
|
58
|
+
* Returns 1 if no dividers, or dividers.length + 1 otherwise.
|
|
59
|
+
*/
|
|
60
|
+
export declare function countItemFragments(itemContext?: ComposerItemContext): number;
|
|
61
|
+
/**
|
|
62
|
+
* Get an array of individual assessment item XMLs.
|
|
63
|
+
* Each item is generated from a segment between dividers.
|
|
64
|
+
* Returns an array with identifier and XML for each item.
|
|
65
|
+
*/
|
|
66
|
+
export declare function getItemFragmentXmls(itemContext?: ComposerItemContext): Array<{
|
|
67
|
+
identifier: string;
|
|
68
|
+
title: string;
|
|
69
|
+
xml: string;
|
|
70
|
+
}>;
|
|
50
71
|
export declare function formatXml(xml: string): string;
|
|
51
72
|
//# 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;
|
|
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;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoCD,wBAAgB,2BAA2B,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,mBAAmB,EAAE,CAOhG;AAkFD,wBAAgB,sBAAsB,CAAC,WAAW,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAqHhF;AAED;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAAC,WAAW,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAkCzF;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAUtF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAK5E;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,CAAC,EAAE,mBAAmB,GAAG,KAAK,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAyChI;AAgMD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA4B7C"}
|
package/dist/composer/index.js
CHANGED
|
@@ -40,6 +40,29 @@ export function extractResponseDeclarations(itemBodyRoot) {
|
|
|
40
40
|
const { declarations } = composeAndNormalizeItemBody(tempRoot, tempDoc);
|
|
41
41
|
return declarations;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Convert qti-item-divider elements to <hr /> elements.
|
|
45
|
+
* Used when composing a single item that should preserve dividers as visual separators.
|
|
46
|
+
*/
|
|
47
|
+
function convertDividersToHr(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 itemBodyDoc;
|
|
54
|
+
// Clone the document to avoid mutating the original
|
|
55
|
+
const clonedDoc = document.implementation.createDocument(QTI_NS, 'qti-item-body', null);
|
|
56
|
+
const clonedRoot = clonedDoc.importNode(itemBodyRoot, true);
|
|
57
|
+
clonedDoc.replaceChild(clonedRoot, clonedDoc.documentElement);
|
|
58
|
+
// Find and replace all dividers with <hr />
|
|
59
|
+
const dividers = Array.from(clonedRoot.querySelectorAll('qti-item-divider'));
|
|
60
|
+
for (const divider of dividers) {
|
|
61
|
+
const hr = clonedDoc.createElementNS(QTI_NS, 'hr');
|
|
62
|
+
divider.parentNode?.replaceChild(hr, divider);
|
|
63
|
+
}
|
|
64
|
+
return clonedDoc;
|
|
65
|
+
}
|
|
43
66
|
/**
|
|
44
67
|
* Split an item body document at qti-item-divider elements.
|
|
45
68
|
* Returns an array of item body fragments, one for each item.
|
|
@@ -220,6 +243,71 @@ export function buildMultipleAssessmentItemsXml(itemContext) {
|
|
|
220
243
|
const separator = '\n\n<!-- Next Assessment Item -->\n\n';
|
|
221
244
|
return itemXmls.join(separator);
|
|
222
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Build a single QTI assessment item, converting any dividers to <hr /> elements.
|
|
248
|
+
* Use this when you want to export the entire editor content as one item.
|
|
249
|
+
*/
|
|
250
|
+
export function buildSingleAssessmentItemXml(itemContext) {
|
|
251
|
+
if (!itemContext?.itemBody)
|
|
252
|
+
return '';
|
|
253
|
+
// Convert dividers to <hr /> elements
|
|
254
|
+
const convertedDoc = convertDividersToHr(itemContext.itemBody);
|
|
255
|
+
return buildAssessmentItemXml({
|
|
256
|
+
...itemContext,
|
|
257
|
+
itemBody: convertedDoc,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Count how many items would be generated from an item body document.
|
|
262
|
+
* Returns 1 if no dividers, or dividers.length + 1 otherwise.
|
|
263
|
+
*/
|
|
264
|
+
export function countItemFragments(itemContext) {
|
|
265
|
+
if (!itemContext?.itemBody)
|
|
266
|
+
return 0;
|
|
267
|
+
const fragments = splitItemBodyAtDividers(itemContext.itemBody);
|
|
268
|
+
return fragments.length;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Get an array of individual assessment item XMLs.
|
|
272
|
+
* Each item is generated from a segment between dividers.
|
|
273
|
+
* Returns an array with identifier and XML for each item.
|
|
274
|
+
*/
|
|
275
|
+
export function getItemFragmentXmls(itemContext) {
|
|
276
|
+
if (!itemContext?.itemBody)
|
|
277
|
+
return [];
|
|
278
|
+
const fragments = splitItemBodyAtDividers(itemContext.itemBody);
|
|
279
|
+
// If only one fragment (no dividers), return single item
|
|
280
|
+
if (fragments.length <= 1) {
|
|
281
|
+
const xml = buildAssessmentItemXml(itemContext);
|
|
282
|
+
return [{
|
|
283
|
+
identifier: itemContext.identifier?.trim() || 'item-1',
|
|
284
|
+
title: itemContext.title?.trim() || 'Untitled Item',
|
|
285
|
+
xml,
|
|
286
|
+
}];
|
|
287
|
+
}
|
|
288
|
+
const baseIdentifier = itemContext.identifier?.trim() || 'item';
|
|
289
|
+
const baseTitle = itemContext.title?.trim() || 'Untitled Item';
|
|
290
|
+
const lang = itemContext.lang?.trim() || 'en';
|
|
291
|
+
return fragments.map((fragmentBody, index) => {
|
|
292
|
+
const itemNumber = index + 1;
|
|
293
|
+
const fragmentDoc = document.implementation.createDocument(QTI_NS, 'qti-item-body', null);
|
|
294
|
+
const importedFragment = fragmentDoc.importNode(fragmentBody, true);
|
|
295
|
+
fragmentDoc.replaceChild(importedFragment, fragmentDoc.documentElement);
|
|
296
|
+
const identifier = `${baseIdentifier}-${itemNumber}`;
|
|
297
|
+
const title = `${baseTitle} ${itemNumber}`;
|
|
298
|
+
const fragmentContext = {
|
|
299
|
+
identifier,
|
|
300
|
+
title,
|
|
301
|
+
lang,
|
|
302
|
+
itemBody: fragmentDoc,
|
|
303
|
+
};
|
|
304
|
+
return {
|
|
305
|
+
identifier,
|
|
306
|
+
title,
|
|
307
|
+
xml: buildAssessmentItemXml(fragmentContext),
|
|
308
|
+
};
|
|
309
|
+
});
|
|
310
|
+
}
|
|
223
311
|
function composeAndNormalizeItemBody(itemBody, xmlDoc) {
|
|
224
312
|
const declarations = [];
|
|
225
313
|
const seenIdentifiers = new Set();
|
|
@@ -238,9 +326,21 @@ function composeAndNormalizeItemBody(itemBody, xmlDoc) {
|
|
|
238
326
|
const parent = element.parentNode;
|
|
239
327
|
if (parent) {
|
|
240
328
|
parent.replaceChild(composeResult.normalizedElement, element);
|
|
329
|
+
// Insert any additional elements (like rubric blocks) after the interaction
|
|
330
|
+
if (composeResult.additionalElements?.length) {
|
|
331
|
+
const nextSibling = composeResult.normalizedElement.nextSibling;
|
|
332
|
+
for (const additionalElement of composeResult.additionalElements) {
|
|
333
|
+
if (nextSibling) {
|
|
334
|
+
parent.insertBefore(additionalElement, nextSibling);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
parent.appendChild(additionalElement);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
241
341
|
}
|
|
242
342
|
if (composeResult.responseDeclaration) {
|
|
243
|
-
maxScore += 1;
|
|
343
|
+
maxScore += composeResult.responseDeclaration.score ?? 1;
|
|
244
344
|
}
|
|
245
345
|
if (composeResult.responseDeclaration && !seenIdentifiers.has(composeResult.responseDeclaration.identifier)) {
|
|
246
346
|
const responseProcessingKind = composeResult
|
|
@@ -263,6 +363,9 @@ function composeAndNormalizeItemBody(itemBody, xmlDoc) {
|
|
|
263
363
|
itemBody.querySelectorAll('[correct-response]').forEach(interaction => {
|
|
264
364
|
interaction.removeAttribute('correct-response');
|
|
265
365
|
});
|
|
366
|
+
itemBody.querySelectorAll('[score]').forEach(element => {
|
|
367
|
+
element.removeAttribute('score');
|
|
368
|
+
});
|
|
266
369
|
normalizeResponseIdentifiers(itemBody, declarations);
|
|
267
370
|
if (declarations.length === 1 && templateCandidates.size === 1) {
|
|
268
371
|
return { declarations, responseTemplate: Array.from(templateCandidates)[0], maxScore };
|
|
@@ -274,7 +377,7 @@ function buildMultiInteractionResponseProcessing(xmlDoc, declarations) {
|
|
|
274
377
|
declarations.forEach(declaration => {
|
|
275
378
|
const kind = declaration.responseProcessingKind ?? 'match_correct';
|
|
276
379
|
if (kind === 'match_correct') {
|
|
277
|
-
responseProcessing.appendChild(createMatchCorrectContribution(xmlDoc, declaration.identifier));
|
|
380
|
+
responseProcessing.appendChild(createMatchCorrectContribution(xmlDoc, declaration.identifier, declaration.score ?? 1));
|
|
278
381
|
return;
|
|
279
382
|
}
|
|
280
383
|
if (kind === 'map_response') {
|
|
@@ -285,7 +388,7 @@ function buildMultiInteractionResponseProcessing(xmlDoc, declarations) {
|
|
|
285
388
|
});
|
|
286
389
|
return responseProcessing;
|
|
287
390
|
}
|
|
288
|
-
function createMatchCorrectContribution(xmlDoc, responseIdentifier) {
|
|
391
|
+
function createMatchCorrectContribution(xmlDoc, responseIdentifier, score = 1) {
|
|
289
392
|
const responseCondition = xmlDoc.createElementNS(QTI_NS, 'qti-response-condition');
|
|
290
393
|
const responseIf = xmlDoc.createElementNS(QTI_NS, 'qti-response-if');
|
|
291
394
|
const match = xmlDoc.createElementNS(QTI_NS, 'qti-match');
|
|
@@ -298,7 +401,7 @@ function createMatchCorrectContribution(xmlDoc, responseIdentifier) {
|
|
|
298
401
|
responseIf.appendChild(match);
|
|
299
402
|
const incrementValue = xmlDoc.createElementNS(QTI_NS, 'qti-base-value');
|
|
300
403
|
incrementValue.setAttribute('base-type', 'float');
|
|
301
|
-
incrementValue.textContent =
|
|
404
|
+
incrementValue.textContent = String(score);
|
|
302
405
|
responseIf.appendChild(createScoreIncrement(xmlDoc, incrementValue));
|
|
303
406
|
responseCondition.appendChild(responseIf);
|
|
304
407
|
return responseCondition;
|
|
@@ -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":"AAYA,OAAO,KAAK,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,wBAAwB,CAAC;AAoChC,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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { associateInteractionDescriptor } from '@qti-editor/interaction-associate';
|
|
2
2
|
import { choiceInteractionDescriptor } from '@qti-editor/interaction-choice';
|
|
3
3
|
import { extendedTextInteractionDescriptor } from '@qti-editor/interaction-extended-text';
|
|
4
|
+
import { gapMatchInteractionDescriptor } from '@qti-editor/interaction-gap-match';
|
|
4
5
|
import { hottextInteractionDescriptor } from '@qti-editor/interaction-hottext';
|
|
5
6
|
import { inlineChoiceInteractionDescriptor } from '@qti-editor/interaction-inline-choice';
|
|
6
7
|
import { matchInteractionDescriptor } from '@qti-editor/interaction-match';
|
|
@@ -12,6 +13,7 @@ const registeredDescriptors = [
|
|
|
12
13
|
associateInteractionDescriptor,
|
|
13
14
|
choiceInteractionDescriptor,
|
|
14
15
|
extendedTextInteractionDescriptor,
|
|
16
|
+
gapMatchInteractionDescriptor,
|
|
15
17
|
hottextInteractionDescriptor,
|
|
16
18
|
inlineChoiceInteractionDescriptor,
|
|
17
19
|
matchInteractionDescriptor,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qti-editor/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "QTI semantics, composer registry, and XML export orchestration for QTI Editor",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,18 +24,19 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@qti-editor/interfaces": "1.
|
|
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-
|
|
32
|
-
"@qti-editor/interaction-
|
|
33
|
-
"@qti-editor/interaction-
|
|
34
|
-
"@qti-editor/interaction-
|
|
35
|
-
"@qti-editor/interaction-
|
|
36
|
-
"@qti-editor/interaction-
|
|
37
|
-
"@qti-editor/interaction-
|
|
38
|
-
"@qti-editor/
|
|
27
|
+
"@qti-editor/interfaces": "1.2.0",
|
|
28
|
+
"@qti-editor/interaction-associate": "1.2.1",
|
|
29
|
+
"@qti-editor/interaction-choice": "1.2.1",
|
|
30
|
+
"@qti-editor/interaction-extended-text": "1.2.1",
|
|
31
|
+
"@qti-editor/interaction-gap-match": "1.1.0",
|
|
32
|
+
"@qti-editor/interaction-hottext": "1.2.2",
|
|
33
|
+
"@qti-editor/interaction-inline-choice": "1.2.1",
|
|
34
|
+
"@qti-editor/interaction-match": "1.2.0",
|
|
35
|
+
"@qti-editor/interaction-order": "0.6.0",
|
|
36
|
+
"@qti-editor/interaction-select-point": "1.2.0",
|
|
37
|
+
"@qti-editor/interaction-shared": "1.3.0",
|
|
38
|
+
"@qti-editor/interaction-text-entry": "1.2.0",
|
|
39
|
+
"@qti-editor/qti-item-divider": "1.1.0"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"@types/node": "^20.0.0",
|