@qti-editor/core 1.3.2 → 1.4.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 +4 -0
- package/dist/composer/index.d.ts.map +1 -1
- package/dist/composer/index.js +106 -70
- package/package.json +14 -14
package/dist/composer/index.d.ts
CHANGED
|
@@ -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;
|
|
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;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxD;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,GAAG,MAAM,EAAE,CAAC;IACpC,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;AAkGD,wBAAgB,2BAA2B,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,mBAAmB,EAAE,CAOhG;AAkFD,wBAAgB,sBAAsB,CAAC,WAAW,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAmIhF;AAED;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAAC,WAAW,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAqDzF;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,CA0DhI;AAoKD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA4B7C"}
|
package/dist/composer/index.js
CHANGED
|
@@ -24,6 +24,28 @@ const TEXT_ENTRY_DATA_ATTRIBUTE_MAPPINGS = [
|
|
|
24
24
|
const SELECT_POINT_DATA_ATTRIBUTE_MAPPINGS = [
|
|
25
25
|
{ source: 'area-mappings', target: 'data-area-mappings' },
|
|
26
26
|
];
|
|
27
|
+
function sanitizeIdentifier(value, fallback) {
|
|
28
|
+
const sanitized = value?.trim().replace(/[^A-Za-z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
29
|
+
return sanitized || fallback;
|
|
30
|
+
}
|
|
31
|
+
function hashIdentifierSeed(value) {
|
|
32
|
+
let hash = 0x811c9dc5;
|
|
33
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
34
|
+
hash ^= value.charCodeAt(i);
|
|
35
|
+
hash = Math.imul(hash, 0x01000193);
|
|
36
|
+
}
|
|
37
|
+
return (hash >>> 0).toString(36);
|
|
38
|
+
}
|
|
39
|
+
function createAutoIdentifier(options) {
|
|
40
|
+
const titleBase = sanitizeIdentifier(options.title, '');
|
|
41
|
+
const base = titleBase || sanitizeIdentifier(options.baseIdentifier, 'item');
|
|
42
|
+
const serializedBody = options.body == null
|
|
43
|
+
? ''
|
|
44
|
+
: new XMLSerializer().serializeToString(options.body instanceof Document ? options.body.documentElement : options.body);
|
|
45
|
+
const seed = [base, options.itemNumber ?? 1, serializedBody].join('|');
|
|
46
|
+
const suffix = hashIdentifierSeed(seed).slice(0, 8);
|
|
47
|
+
return `${base}-${suffix}`;
|
|
48
|
+
}
|
|
27
49
|
function parseCorrectResponseValues(declaration) {
|
|
28
50
|
const value = declaration.correctResponse;
|
|
29
51
|
if (value == null)
|
|
@@ -140,7 +162,11 @@ export function buildAssessmentItemXml(itemContext) {
|
|
|
140
162
|
const root = xmlDoc.documentElement;
|
|
141
163
|
root.setAttribute('xmlns', QTI_NS);
|
|
142
164
|
root.setAttributeNS(XSI_NS, 'xsi:schemaLocation', SCHEMA_LOCATION);
|
|
143
|
-
root.setAttribute('identifier', itemContext.identifier
|
|
165
|
+
root.setAttribute('identifier', sanitizeIdentifier(itemContext.identifier, createAutoIdentifier({
|
|
166
|
+
title: itemContext.title,
|
|
167
|
+
baseIdentifier: itemContext.identifier,
|
|
168
|
+
body: itemContext.itemBody,
|
|
169
|
+
})));
|
|
144
170
|
root.setAttribute('title', itemContext.title?.trim() || 'Untitled Item');
|
|
145
171
|
root.setAttribute('adaptive', 'false');
|
|
146
172
|
root.setAttribute('time-dependent', 'false');
|
|
@@ -153,7 +179,7 @@ export function buildAssessmentItemXml(itemContext) {
|
|
|
153
179
|
const composedItemBody = sourceBodyRoot != null
|
|
154
180
|
? xmlDoc.importNode(sourceBodyRoot, true)
|
|
155
181
|
: xmlDoc.createElementNS(QTI_NS, 'qti-item-body');
|
|
156
|
-
const { declarations, responseTemplate, maxScore } = composeAndNormalizeItemBody(composedItemBody, xmlDoc);
|
|
182
|
+
const { declarations, responseTemplate, maxScore, hasAutomatedProcessing } = composeAndNormalizeItemBody(composedItemBody, xmlDoc);
|
|
157
183
|
declarations.forEach(declaration => {
|
|
158
184
|
const responseDeclaration = xmlDoc.createElementNS(QTI_NS, 'qti-response-declaration');
|
|
159
185
|
responseDeclaration.setAttribute('identifier', declaration.identifier);
|
|
@@ -218,17 +244,20 @@ export function buildAssessmentItemXml(itemContext) {
|
|
|
218
244
|
root.appendChild(maxScoreOutcomeDeclaration);
|
|
219
245
|
}
|
|
220
246
|
root.appendChild(composedItemBody);
|
|
221
|
-
if (maxScore > 0) {
|
|
222
|
-
|
|
247
|
+
if (maxScore > 0 && hasAutomatedProcessing) {
|
|
248
|
+
const single = declarations.length === 1 ? declarations[0] : null;
|
|
249
|
+
const canUseTemplate = single !== null &&
|
|
250
|
+
!(single.responseProcessingKind === 'match_correct' && (single.score ?? 1) !== 1);
|
|
251
|
+
if (canUseTemplate) {
|
|
223
252
|
const responseProcessing = xmlDoc.createElementNS(QTI_NS, 'qti-response-processing');
|
|
224
253
|
responseProcessing.setAttribute('template', responseTemplate);
|
|
225
254
|
root.appendChild(responseProcessing);
|
|
226
255
|
}
|
|
227
|
-
else
|
|
256
|
+
else {
|
|
228
257
|
root.appendChild(buildMultiInteractionResponseProcessing(xmlDoc, declarations));
|
|
229
258
|
}
|
|
230
259
|
}
|
|
231
|
-
return new XMLSerializer().serializeToString(xmlDoc);
|
|
260
|
+
return new XMLSerializer().serializeToString(xmlDoc).replace(/\s+xmlns=""/g, '');
|
|
232
261
|
}
|
|
233
262
|
/**
|
|
234
263
|
* Build multiple QTI assessment items from a single editor document.
|
|
@@ -248,17 +277,36 @@ export function buildMultipleAssessmentItemsXml(itemContext) {
|
|
|
248
277
|
return buildAssessmentItemXml(itemContext);
|
|
249
278
|
}
|
|
250
279
|
// Build multiple items
|
|
251
|
-
const baseIdentifier = itemContext.identifier
|
|
280
|
+
const baseIdentifier = sanitizeIdentifier(itemContext.identifier, 'item');
|
|
252
281
|
const baseTitle = itemContext.title?.trim() || 'Untitled Item';
|
|
253
282
|
const lang = itemContext.lang?.trim() || 'en';
|
|
283
|
+
const usedIdentifiers = new Set();
|
|
254
284
|
const itemXmls = fragments.map((fragmentBody, index) => {
|
|
255
285
|
const itemNumber = index + 1;
|
|
286
|
+
const perItem = itemContext.items?.[index];
|
|
256
287
|
const fragmentDoc = document.implementation.createDocument(QTI_NS, 'qti-item-body', null);
|
|
257
288
|
const importedFragment = fragmentDoc.importNode(fragmentBody, true);
|
|
258
289
|
fragmentDoc.replaceChild(importedFragment, fragmentDoc.documentElement);
|
|
290
|
+
let identifier = perItem?.identifier?.trim();
|
|
291
|
+
if (!identifier) {
|
|
292
|
+
identifier = createAutoIdentifier({
|
|
293
|
+
title: perItem?.title || `${baseTitle} ${itemNumber}`,
|
|
294
|
+
baseIdentifier: `${baseIdentifier}-${itemNumber}`,
|
|
295
|
+
itemNumber,
|
|
296
|
+
body: fragmentDoc,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
identifier = sanitizeIdentifier(identifier, `${baseIdentifier}-${itemNumber}`);
|
|
300
|
+
let uniqueIdentifier = identifier;
|
|
301
|
+
let duplicateIndex = 2;
|
|
302
|
+
while (usedIdentifiers.has(uniqueIdentifier)) {
|
|
303
|
+
uniqueIdentifier = `${identifier}-${duplicateIndex}`;
|
|
304
|
+
duplicateIndex += 1;
|
|
305
|
+
}
|
|
306
|
+
usedIdentifiers.add(uniqueIdentifier);
|
|
259
307
|
const fragmentContext = {
|
|
260
|
-
identifier:
|
|
261
|
-
title: `${baseTitle} ${itemNumber}`,
|
|
308
|
+
identifier: uniqueIdentifier,
|
|
309
|
+
title: perItem?.title?.trim() || `${baseTitle} ${itemNumber}`,
|
|
262
310
|
lang,
|
|
263
311
|
itemBody: fragmentDoc,
|
|
264
312
|
};
|
|
@@ -303,33 +351,49 @@ export function getItemFragmentXmls(itemContext) {
|
|
|
303
351
|
const fragments = splitItemBodyAtDividers(itemContext.itemBody);
|
|
304
352
|
// If only one fragment (no dividers), return single item
|
|
305
353
|
if (fragments.length <= 1) {
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
354
|
+
const perItem = itemContext.items?.[0];
|
|
355
|
+
const identifier = sanitizeIdentifier(perItem?.identifier || itemContext.identifier, createAutoIdentifier({
|
|
356
|
+
title: perItem?.title || itemContext.title,
|
|
357
|
+
baseIdentifier: itemContext.identifier,
|
|
358
|
+
itemNumber: 1,
|
|
359
|
+
body: itemContext.itemBody,
|
|
360
|
+
}));
|
|
361
|
+
const title = perItem?.title?.trim() || itemContext.title?.trim() || 'Untitled Item';
|
|
362
|
+
const xml = buildAssessmentItemXml({ ...itemContext, identifier, title });
|
|
363
|
+
return [{ identifier, title, xml }];
|
|
312
364
|
}
|
|
313
|
-
const baseIdentifier = itemContext.identifier
|
|
365
|
+
const baseIdentifier = sanitizeIdentifier(itemContext.identifier, 'item');
|
|
314
366
|
const baseTitle = itemContext.title?.trim() || 'Untitled Item';
|
|
315
367
|
const lang = itemContext.lang?.trim() || 'en';
|
|
368
|
+
const usedIdentifiers = new Set();
|
|
316
369
|
return fragments.map((fragmentBody, index) => {
|
|
317
370
|
const itemNumber = index + 1;
|
|
371
|
+
const perItem = itemContext.items?.[index];
|
|
372
|
+
const title = perItem?.title?.trim() || `${baseTitle} ${itemNumber}`;
|
|
318
373
|
const fragmentDoc = document.implementation.createDocument(QTI_NS, 'qti-item-body', null);
|
|
319
374
|
const importedFragment = fragmentDoc.importNode(fragmentBody, true);
|
|
320
375
|
fragmentDoc.replaceChild(importedFragment, fragmentDoc.documentElement);
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
376
|
+
let identifier = perItem?.identifier?.trim();
|
|
377
|
+
if (!identifier) {
|
|
378
|
+
identifier = createAutoIdentifier({
|
|
379
|
+
title,
|
|
380
|
+
baseIdentifier: `${baseIdentifier}-${itemNumber}`,
|
|
381
|
+
itemNumber,
|
|
382
|
+
body: fragmentDoc,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
identifier = sanitizeIdentifier(identifier, `${baseIdentifier}-${itemNumber}`);
|
|
386
|
+
let uniqueIdentifier = identifier;
|
|
387
|
+
let duplicateIndex = 2;
|
|
388
|
+
while (usedIdentifiers.has(uniqueIdentifier)) {
|
|
389
|
+
uniqueIdentifier = `${identifier}-${duplicateIndex}`;
|
|
390
|
+
duplicateIndex += 1;
|
|
391
|
+
}
|
|
392
|
+
usedIdentifiers.add(uniqueIdentifier);
|
|
329
393
|
return {
|
|
330
|
-
identifier,
|
|
394
|
+
identifier: uniqueIdentifier,
|
|
331
395
|
title,
|
|
332
|
-
xml: buildAssessmentItemXml(
|
|
396
|
+
xml: buildAssessmentItemXml({ identifier: uniqueIdentifier, title, lang, itemBody: fragmentDoc }),
|
|
333
397
|
};
|
|
334
398
|
});
|
|
335
399
|
}
|
|
@@ -368,14 +432,17 @@ function composeAndNormalizeItemBody(itemBody, xmlDoc) {
|
|
|
368
432
|
if (composeResult.responseDeclaration) {
|
|
369
433
|
maxScore += composeResult.responseDeclaration.score ?? 1;
|
|
370
434
|
}
|
|
371
|
-
if (composeResult.responseDeclaration
|
|
435
|
+
if (composeResult.responseDeclaration) {
|
|
436
|
+
const declaration = composeResult.responseDeclaration;
|
|
437
|
+
if (seenIdentifiers.has(declaration.identifier)) {
|
|
438
|
+
const freshId = `RESPONSE_${crypto.randomUUID()}`;
|
|
439
|
+
composeResult.normalizedElement.setAttribute('response-identifier', freshId);
|
|
440
|
+
declaration.identifier = freshId;
|
|
441
|
+
}
|
|
372
442
|
const responseProcessingKind = composeResult
|
|
373
443
|
.responseProcessingKind;
|
|
374
|
-
declarations.push({
|
|
375
|
-
|
|
376
|
-
responseProcessingKind,
|
|
377
|
-
});
|
|
378
|
-
seenIdentifiers.add(composeResult.responseDeclaration.identifier);
|
|
444
|
+
declarations.push({ ...declaration, responseProcessingKind });
|
|
445
|
+
seenIdentifiers.add(declaration.identifier);
|
|
379
446
|
}
|
|
380
447
|
if (composeResult.responseProcessingTemplate && composeResult.responseDeclaration) {
|
|
381
448
|
templateCandidates.add(composeResult.responseProcessingTemplate);
|
|
@@ -392,16 +459,17 @@ function composeAndNormalizeItemBody(itemBody, xmlDoc) {
|
|
|
392
459
|
itemBody.querySelectorAll('[score]').forEach(element => {
|
|
393
460
|
element.removeAttribute('score');
|
|
394
461
|
});
|
|
395
|
-
normalizeResponseIdentifiers(itemBody, declarations);
|
|
396
462
|
if (declarations.length === 1 && templateCandidates.size === 1) {
|
|
397
|
-
return { declarations, responseTemplate: Array.from(templateCandidates)[0], maxScore };
|
|
463
|
+
return { declarations, responseTemplate: Array.from(templateCandidates)[0], maxScore, hasAutomatedProcessing: true };
|
|
398
464
|
}
|
|
399
|
-
return { declarations, responseTemplate: MATCH_CORRECT_TEMPLATE, maxScore };
|
|
465
|
+
return { declarations, responseTemplate: MATCH_CORRECT_TEMPLATE, maxScore, hasAutomatedProcessing: templateCandidates.size > 0 };
|
|
400
466
|
}
|
|
401
467
|
function buildMultiInteractionResponseProcessing(xmlDoc, declarations) {
|
|
402
468
|
const responseProcessing = xmlDoc.createElementNS(QTI_NS, 'qti-response-processing');
|
|
403
469
|
declarations.forEach(declaration => {
|
|
404
|
-
|
|
470
|
+
if (declaration.responseProcessingKind === undefined)
|
|
471
|
+
return;
|
|
472
|
+
const kind = declaration.responseProcessingKind;
|
|
405
473
|
if (kind === 'match_correct') {
|
|
406
474
|
responseProcessing.appendChild(createMatchCorrectContribution(xmlDoc, declaration.identifier, declaration.score ?? 1));
|
|
407
475
|
return;
|
|
@@ -434,16 +502,12 @@ function createMatchCorrectContribution(xmlDoc, responseIdentifier, score = 1) {
|
|
|
434
502
|
}
|
|
435
503
|
function createMapResponseContribution(xmlDoc, responseIdentifier) {
|
|
436
504
|
const mapResponse = xmlDoc.createElementNS(QTI_NS, 'qti-map-response');
|
|
437
|
-
|
|
438
|
-
variable.setAttribute('identifier', responseIdentifier);
|
|
439
|
-
mapResponse.appendChild(variable);
|
|
505
|
+
mapResponse.setAttribute('identifier', responseIdentifier);
|
|
440
506
|
return createScoreIncrement(xmlDoc, mapResponse);
|
|
441
507
|
}
|
|
442
508
|
function createMapResponsePointContribution(xmlDoc, responseIdentifier) {
|
|
443
509
|
const mapResponsePoint = xmlDoc.createElementNS(QTI_NS, 'qti-map-response-point');
|
|
444
|
-
|
|
445
|
-
variable.setAttribute('identifier', responseIdentifier);
|
|
446
|
-
mapResponsePoint.appendChild(variable);
|
|
510
|
+
mapResponsePoint.setAttribute('identifier', responseIdentifier);
|
|
447
511
|
return createScoreIncrement(xmlDoc, mapResponsePoint);
|
|
448
512
|
}
|
|
449
513
|
function createScoreIncrement(xmlDoc, contribution) {
|
|
@@ -457,34 +521,6 @@ function createScoreIncrement(xmlDoc, contribution) {
|
|
|
457
521
|
setOutcomeValue.appendChild(sum);
|
|
458
522
|
return setOutcomeValue;
|
|
459
523
|
}
|
|
460
|
-
function normalizeResponseIdentifiers(itemBody, declarations) {
|
|
461
|
-
if (declarations.length === 0)
|
|
462
|
-
return;
|
|
463
|
-
const identifierMap = new Map();
|
|
464
|
-
if (declarations.length === 1) {
|
|
465
|
-
identifierMap.set(declarations[0].identifier, 'RESPONSE');
|
|
466
|
-
}
|
|
467
|
-
else {
|
|
468
|
-
declarations.forEach((declaration, index) => {
|
|
469
|
-
identifierMap.set(declaration.identifier, `RESPONSE${index + 1}`);
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
itemBody.querySelectorAll('[response-identifier]').forEach(interaction => {
|
|
473
|
-
const currentIdentifier = interaction.getAttribute('response-identifier')?.trim();
|
|
474
|
-
if (!currentIdentifier)
|
|
475
|
-
return;
|
|
476
|
-
const mappedIdentifier = identifierMap.get(currentIdentifier);
|
|
477
|
-
if (mappedIdentifier) {
|
|
478
|
-
interaction.setAttribute('response-identifier', mappedIdentifier);
|
|
479
|
-
}
|
|
480
|
-
});
|
|
481
|
-
declarations.forEach(declaration => {
|
|
482
|
-
const mappedIdentifier = identifierMap.get(declaration.identifier);
|
|
483
|
-
if (mappedIdentifier) {
|
|
484
|
-
declaration.identifier = mappedIdentifier;
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
524
|
export function formatXml(xml) {
|
|
489
525
|
const PADDING = ' ';
|
|
490
526
|
const reg = /(>)(<)(\/*)/g;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qti-editor/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "QTI semantics, composer registry, and XML export orchestration for QTI Editor",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,19 +24,19 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@qti-editor/interfaces": "1.2.
|
|
28
|
-
"@qti-editor/interaction-associate": "1.2.
|
|
29
|
-
"@qti-editor/interaction-choice": "1.2.
|
|
30
|
-
"@qti-editor/interaction-extended-text": "1.
|
|
31
|
-
"@qti-editor/interaction-gap-match": "1.1.
|
|
32
|
-
"@qti-editor/interaction-hottext": "1.2.
|
|
33
|
-
"@qti-editor/interaction-inline-choice": "1.
|
|
34
|
-
"@qti-editor/interaction-match": "1.2.
|
|
35
|
-
"@qti-editor/interaction-order": "0.6.
|
|
36
|
-
"@qti-editor/interaction-select-point": "1.2.
|
|
37
|
-
"@qti-editor/interaction-shared": "1.3.
|
|
38
|
-
"@qti-editor/interaction-text-entry": "1.2.
|
|
39
|
-
"@qti-editor/qti-item-divider": "1.1.
|
|
27
|
+
"@qti-editor/interfaces": "1.2.1",
|
|
28
|
+
"@qti-editor/interaction-associate": "1.2.4",
|
|
29
|
+
"@qti-editor/interaction-choice": "1.2.3",
|
|
30
|
+
"@qti-editor/interaction-extended-text": "1.3.1",
|
|
31
|
+
"@qti-editor/interaction-gap-match": "1.1.2",
|
|
32
|
+
"@qti-editor/interaction-hottext": "1.2.4",
|
|
33
|
+
"@qti-editor/interaction-inline-choice": "1.3.1",
|
|
34
|
+
"@qti-editor/interaction-match": "1.2.2",
|
|
35
|
+
"@qti-editor/interaction-order": "0.6.2",
|
|
36
|
+
"@qti-editor/interaction-select-point": "1.2.2",
|
|
37
|
+
"@qti-editor/interaction-shared": "1.3.2",
|
|
38
|
+
"@qti-editor/interaction-text-entry": "1.2.2",
|
|
39
|
+
"@qti-editor/qti-item-divider": "1.1.3"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^20.0.0",
|