@playcademy/vite-plugin 1.2.1-beta.11 → 1.2.1-beta.13
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/index.js +297 -63
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -24315,7 +24315,7 @@ import path2 from "node:path";
|
|
|
24315
24315
|
// package.json
|
|
24316
24316
|
var package_default = {
|
|
24317
24317
|
name: "@playcademy/vite-plugin",
|
|
24318
|
-
version: "1.2.1-beta.
|
|
24318
|
+
version: "1.2.1-beta.13",
|
|
24319
24319
|
type: "module",
|
|
24320
24320
|
exports: {
|
|
24321
24321
|
".": {
|
|
@@ -25921,7 +25921,7 @@ var package_default2;
|
|
|
25921
25921
|
var init_package = __esm(() => {
|
|
25922
25922
|
package_default2 = {
|
|
25923
25923
|
name: "@playcademy/sandbox",
|
|
25924
|
-
version: "0.7.1-beta.
|
|
25924
|
+
version: "0.7.1-beta.13",
|
|
25925
25925
|
description: "Local development server for Playcademy game development",
|
|
25926
25926
|
type: "module",
|
|
25927
25927
|
exports: {
|
|
@@ -34173,6 +34173,23 @@ var init_v3 = __esm(() => {
|
|
|
34173
34173
|
var init_esm = __esm(() => {
|
|
34174
34174
|
init_v3();
|
|
34175
34175
|
});
|
|
34176
|
+
function qtiDirectedPairValidationIssue(values, contract) {
|
|
34177
|
+
const sourceCounts = new Map;
|
|
34178
|
+
const targetCounts = new Map;
|
|
34179
|
+
for (const value of values) {
|
|
34180
|
+
const [source, target, extra] = value.split(" ");
|
|
34181
|
+
if (extra !== undefined || !source || !target || !contract.sources.has(source) || !contract.targets.has(target)) {
|
|
34182
|
+
return { kind: "invalid-pair", value };
|
|
34183
|
+
}
|
|
34184
|
+
if (!withinQtiCapacity(contract.sources, sourceCounts, source)) {
|
|
34185
|
+
return { kind: "source-capacity", identifier: source };
|
|
34186
|
+
}
|
|
34187
|
+
if (!withinQtiCapacity(contract.targets, targetCounts, target)) {
|
|
34188
|
+
return { kind: "target-capacity", identifier: target };
|
|
34189
|
+
}
|
|
34190
|
+
}
|
|
34191
|
+
return null;
|
|
34192
|
+
}
|
|
34176
34193
|
function isRecord2(value) {
|
|
34177
34194
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
34178
34195
|
}
|
|
@@ -34475,19 +34492,15 @@ function assessmentResponseValidationMessage(assessment, responses) {
|
|
|
34475
34492
|
return null;
|
|
34476
34493
|
}
|
|
34477
34494
|
function directedPairsValidationMessage(itemIdentifier, values, contract) {
|
|
34478
|
-
const
|
|
34479
|
-
|
|
34480
|
-
|
|
34481
|
-
const [source, target, extra] = value.split(" ");
|
|
34482
|
-
if (extra !== undefined || !source || !target || !contract.sources.has(source) || !contract.targets.has(target)) {
|
|
34495
|
+
const issue = qtiDirectedPairValidationIssue(values, contract);
|
|
34496
|
+
if (issue) {
|
|
34497
|
+
if (issue.kind === "invalid-pair") {
|
|
34483
34498
|
return `Response for ${itemIdentifier} contains an invalid association pair.`;
|
|
34484
34499
|
}
|
|
34485
|
-
if (
|
|
34500
|
+
if (issue.kind === "source-capacity") {
|
|
34486
34501
|
return `Response for ${itemIdentifier} uses a token beyond its allowed placements.`;
|
|
34487
34502
|
}
|
|
34488
|
-
|
|
34489
|
-
return `Response for ${itemIdentifier} fills a target beyond its capacity.`;
|
|
34490
|
-
}
|
|
34503
|
+
return `Response for ${itemIdentifier} fills a target beyond its capacity.`;
|
|
34491
34504
|
}
|
|
34492
34505
|
return null;
|
|
34493
34506
|
}
|
|
@@ -34567,8 +34580,14 @@ function interactionResponseValidationMessage(itemIdentifier, interaction, respo
|
|
|
34567
34580
|
allowed = new Set(interaction.choices.map((candidate) => candidate.identifier));
|
|
34568
34581
|
} else if (interaction.type === "match") {
|
|
34569
34582
|
const message = directedPairsValidationMessage(itemIdentifier, values, {
|
|
34570
|
-
sources: new Map(interaction.sourceChoices.map((candidate) => [
|
|
34571
|
-
|
|
34583
|
+
sources: new Map(interaction.sourceChoices.map((candidate) => [
|
|
34584
|
+
candidate.identifier,
|
|
34585
|
+
candidate.matchMax
|
|
34586
|
+
])),
|
|
34587
|
+
targets: new Map(interaction.targetChoices.map((candidate) => [
|
|
34588
|
+
candidate.identifier,
|
|
34589
|
+
candidate.matchMax
|
|
34590
|
+
]))
|
|
34572
34591
|
});
|
|
34573
34592
|
if (message) {
|
|
34574
34593
|
return message;
|
|
@@ -35354,6 +35373,7 @@ var init_assessment_runtime = __esm(() => {
|
|
|
35354
35373
|
init_fxp();
|
|
35355
35374
|
init_timeback3();
|
|
35356
35375
|
init_timeback3();
|
|
35376
|
+
init_timeback3();
|
|
35357
35377
|
init_src();
|
|
35358
35378
|
init_timeback3();
|
|
35359
35379
|
init_src();
|
|
@@ -112256,18 +112276,18 @@ function serializeSanitizedMathMl(node) {
|
|
|
112256
112276
|
const children = node.children.map(serializeSanitizedMathMl).join("");
|
|
112257
112277
|
return `<${node.localName}${attributes2}>${children}</${node.localName}>`;
|
|
112258
112278
|
}
|
|
112259
|
-
function tableContentNode(element, blankForInteraction) {
|
|
112279
|
+
function tableContentNode(element, blankForInteraction, gapForElement) {
|
|
112260
112280
|
const rows = xmlDescendants(element, (candidate) => candidate.localName === "tr").map((row) => {
|
|
112261
112281
|
const cells = row.children.filter((cell) => cell.kind === "element" && (cell.localName === "td" || cell.localName === "th"));
|
|
112262
112282
|
return {
|
|
112263
112283
|
header: cells.length > 0 && cells.every((cell) => cell.localName === "th"),
|
|
112264
|
-
cells: cells.map((cell) => contentNodesFrom(cell.children, blankForInteraction))
|
|
112284
|
+
cells: cells.map((cell) => contentNodesFrom(cell.children, blankForInteraction, gapForElement))
|
|
112265
112285
|
};
|
|
112266
112286
|
});
|
|
112267
112287
|
return { kind: "table", rows };
|
|
112268
112288
|
}
|
|
112269
|
-
function listContentNode(element, blankForInteraction) {
|
|
112270
|
-
const items = element.children.filter((child) => child.kind === "element" && child.localName === "li").map((item) => contentNodesFrom(item.children, blankForInteraction));
|
|
112289
|
+
function listContentNode(element, blankForInteraction, gapForElement) {
|
|
112290
|
+
const items = element.children.filter((child) => child.kind === "element" && child.localName === "li").map((item) => contentNodesFrom(item.children, blankForInteraction, gapForElement));
|
|
112271
112291
|
return { kind: "list", ordered: element.localName === "ol", items };
|
|
112272
112292
|
}
|
|
112273
112293
|
function contentText(raw) {
|
|
@@ -112292,15 +112312,18 @@ function trimContentBoundaries(nodes) {
|
|
|
112292
112312
|
}
|
|
112293
112313
|
return kept.filter((node) => node.kind !== "text" || node.text !== "");
|
|
112294
112314
|
}
|
|
112295
|
-
function collectContentNodes(nodes, blankForInteraction) {
|
|
112315
|
+
function collectContentNodes(nodes, blankForInteraction, gapForElement) {
|
|
112296
112316
|
const result = [];
|
|
112297
112317
|
for (const node of nodes) {
|
|
112318
|
+
const gapIdentifier = node.kind === "element" ? gapForElement(node) : null;
|
|
112298
112319
|
if (node.kind === "text") {
|
|
112299
112320
|
const text3 = contentText(node.value);
|
|
112300
112321
|
if (text3) {
|
|
112301
112322
|
result.push({ kind: "text", text: text3 });
|
|
112302
112323
|
}
|
|
112303
|
-
} else if (CONTENT_SKIPPED_TAGS2.has(node.localName)) {} else if (
|
|
112324
|
+
} else if (CONTENT_SKIPPED_TAGS2.has(node.localName)) {} else if (gapIdentifier !== null) {
|
|
112325
|
+
result.push({ kind: "gap", identifier: gapIdentifier });
|
|
112326
|
+
} else if (isInteractionElement(node)) {
|
|
112304
112327
|
const responseIdentifier = blankForInteraction(node);
|
|
112305
112328
|
if (responseIdentifier) {
|
|
112306
112329
|
result.push({ kind: "blank", responseIdentifier });
|
|
@@ -112325,19 +112348,19 @@ function collectContentNodes(nodes, blankForInteraction) {
|
|
|
112325
112348
|
});
|
|
112326
112349
|
}
|
|
112327
112350
|
} else if (node.localName === "table") {
|
|
112328
|
-
result.push(tableContentNode(node, blankForInteraction));
|
|
112351
|
+
result.push(tableContentNode(node, blankForInteraction, gapForElement));
|
|
112329
112352
|
} else if (node.localName === "ul" || node.localName === "ol") {
|
|
112330
|
-
const list = listContentNode(node, blankForInteraction);
|
|
112353
|
+
const list = listContentNode(node, blankForInteraction, gapForElement);
|
|
112331
112354
|
if (list.kind === "list" && list.items.length > 0) {
|
|
112332
112355
|
result.push(list);
|
|
112333
112356
|
}
|
|
112334
112357
|
} else if (node.localName === "p") {
|
|
112335
|
-
const children = contentNodesFrom(node.children, blankForInteraction);
|
|
112358
|
+
const children = contentNodesFrom(node.children, blankForInteraction, gapForElement);
|
|
112336
112359
|
if (children.length > 0) {
|
|
112337
112360
|
result.push({ kind: "paragraph", children });
|
|
112338
112361
|
}
|
|
112339
112362
|
} else if (EMPHASIS_TAGS2.has(node.localName) || STRONG_TAGS2.has(node.localName)) {
|
|
112340
|
-
const children = collectContentNodes(node.children, blankForInteraction);
|
|
112363
|
+
const children = collectContentNodes(node.children, blankForInteraction, gapForElement);
|
|
112341
112364
|
const hasContent = children.some((child) => child.kind !== "text" || Boolean(child.text.trim()));
|
|
112342
112365
|
if (hasContent) {
|
|
112343
112366
|
result.push({
|
|
@@ -112346,17 +112369,17 @@ function collectContentNodes(nodes, blankForInteraction) {
|
|
|
112346
112369
|
});
|
|
112347
112370
|
}
|
|
112348
112371
|
} else {
|
|
112349
|
-
result.push(...collectContentNodes(node.children, blankForInteraction));
|
|
112372
|
+
result.push(...collectContentNodes(node.children, blankForInteraction, gapForElement));
|
|
112350
112373
|
}
|
|
112351
112374
|
}
|
|
112352
112375
|
return result;
|
|
112353
112376
|
}
|
|
112354
|
-
function contentNodesFrom(nodes, blankForInteraction) {
|
|
112355
|
-
return trimContentBoundaries(collectContentNodes(nodes, blankForInteraction));
|
|
112377
|
+
function contentNodesFrom(nodes, blankForInteraction, gapForElement = () => null) {
|
|
112378
|
+
return trimContentBoundaries(collectContentNodes(nodes, blankForInteraction, gapForElement));
|
|
112356
112379
|
}
|
|
112357
112380
|
function hasRichContent(nodes) {
|
|
112358
112381
|
return nodes.some((node) => {
|
|
112359
|
-
if (node.kind === "math" || node.kind === "image" || node.kind === "table" || node.kind === "list" || node.kind === "blank") {
|
|
112382
|
+
if (node.kind === "math" || node.kind === "image" || node.kind === "table" || node.kind === "list" || node.kind === "blank" || node.kind === "gap") {
|
|
112360
112383
|
return true;
|
|
112361
112384
|
}
|
|
112362
112385
|
if (node.kind === "paragraph" || node.kind === "emphasis" || node.kind === "strong") {
|
|
@@ -112377,7 +112400,14 @@ function parseGapMatchText(interaction) {
|
|
|
112377
112400
|
}
|
|
112378
112401
|
return GAP_MATCH_TOKEN_TAGS2.has(element.localName) ? "" : undefined;
|
|
112379
112402
|
});
|
|
112380
|
-
|
|
112403
|
+
const passageNodes = interaction.children.filter((node) => node.kind === "text" || !GAP_MATCH_TOKEN_TAGS2.has(node.localName));
|
|
112404
|
+
const gapContent = contentNodesFrom(passageNodes, () => null, (element) => {
|
|
112405
|
+
if (element.localName !== "qti-gap") {
|
|
112406
|
+
return null;
|
|
112407
|
+
}
|
|
112408
|
+
return parseXmlAttributes(element.attributes).identifier ?? null;
|
|
112409
|
+
});
|
|
112410
|
+
return { textWithGaps, gaps, gapContent };
|
|
112381
112411
|
}
|
|
112382
112412
|
function isInlineInteractionElement(element) {
|
|
112383
112413
|
const type = INTERACTION_TYPES2[element.localName];
|
|
@@ -112404,6 +112434,7 @@ function parseInteraction(source, interaction, context2) {
|
|
|
112404
112434
|
let gapImages = [];
|
|
112405
112435
|
let gaps = [];
|
|
112406
112436
|
let textWithGaps;
|
|
112437
|
+
let gapContent;
|
|
112407
112438
|
function ownStageGraphic() {
|
|
112408
112439
|
return firstGraphic(interaction, STAGE_GRAPHIC_EXCLUDED_CONTAINERS2);
|
|
112409
112440
|
}
|
|
@@ -112425,7 +112456,7 @@ function parseInteraction(source, interaction, context2) {
|
|
|
112425
112456
|
graphic = ownStageGraphic();
|
|
112426
112457
|
} else if (type === "gap-match") {
|
|
112427
112458
|
gapTexts = parseGapTexts(interaction);
|
|
112428
|
-
({ textWithGaps, gaps } = parseGapMatchText(interaction));
|
|
112459
|
+
({ textWithGaps, gaps, gapContent } = parseGapMatchText(interaction));
|
|
112429
112460
|
} else if (type === "graphic-gap-match") {
|
|
112430
112461
|
graphic = ownStageGraphic();
|
|
112431
112462
|
gapImages = parseGapImages(interaction);
|
|
@@ -112453,6 +112484,7 @@ function parseInteraction(source, interaction, context2) {
|
|
|
112453
112484
|
gapImages,
|
|
112454
112485
|
gaps,
|
|
112455
112486
|
...textWithGaps === undefined ? {} : { textWithGaps },
|
|
112487
|
+
...gapContent === undefined ? {} : { gapContent },
|
|
112456
112488
|
rawXml: xmlElementSource(source, interaction)
|
|
112457
112489
|
};
|
|
112458
112490
|
}
|
|
@@ -112543,6 +112575,23 @@ function parseQtiItemXml(rawXml) {
|
|
|
112543
112575
|
rawXml
|
|
112544
112576
|
};
|
|
112545
112577
|
}
|
|
112578
|
+
function qtiDirectedPairValidationIssue2(values, contract) {
|
|
112579
|
+
const sourceCounts = new Map;
|
|
112580
|
+
const targetCounts = new Map;
|
|
112581
|
+
for (const value of values) {
|
|
112582
|
+
const [source, target, extra] = value.split(" ");
|
|
112583
|
+
if (extra !== undefined || !source || !target || !contract.sources.has(source) || !contract.targets.has(target)) {
|
|
112584
|
+
return { kind: "invalid-pair", value };
|
|
112585
|
+
}
|
|
112586
|
+
if (!withinQtiCapacity(contract.sources, sourceCounts, source)) {
|
|
112587
|
+
return { kind: "source-capacity", identifier: source };
|
|
112588
|
+
}
|
|
112589
|
+
if (!withinQtiCapacity(contract.targets, targetCounts, target)) {
|
|
112590
|
+
return { kind: "target-capacity", identifier: target };
|
|
112591
|
+
}
|
|
112592
|
+
}
|
|
112593
|
+
return null;
|
|
112594
|
+
}
|
|
112546
112595
|
function usableQtiRegionGeometry(shape, coords) {
|
|
112547
112596
|
const arity2 = REGION_SHAPE_COORDS[shape];
|
|
112548
112597
|
if (!arity2) {
|
|
@@ -113007,6 +113056,17 @@ function interactionBody(item, node, type, declaration, attributes2) {
|
|
|
113007
113056
|
if (!attributes2.has("max-associations")) {
|
|
113008
113057
|
attributes2.set("max-associations", String(correctValueStrings(declaration).length || sources.length));
|
|
113009
113058
|
}
|
|
113059
|
+
const capacityIssue = qtiDirectedPairValidationIssue2(correctValueStrings(declaration), {
|
|
113060
|
+
sources: new Map(sources.map((choice) => [choice.identifier, 1])),
|
|
113061
|
+
targets: new Map(targets.map((choice) => [choice.identifier, 1]))
|
|
113062
|
+
});
|
|
113063
|
+
if (capacityIssue) {
|
|
113064
|
+
if (capacityIssue.kind === "invalid-pair") {
|
|
113065
|
+
compileError(`Match pair “${capacityIssue.value}” must name one declared source and target choice`);
|
|
113066
|
+
}
|
|
113067
|
+
const role = capacityIssue.kind === "source-capacity" ? "Source" : "Target";
|
|
113068
|
+
compileError(`${role} “${capacityIssue.identifier}” takes part in more associations than its match-max allows`);
|
|
113069
|
+
}
|
|
113010
113070
|
const sets = [sources, targets].map((choices) => `<qti-simple-match-set>${choices.map((choice) => choiceXml(item, "qti-simple-associable-choice", choice, {
|
|
113011
113071
|
"match-max": "1"
|
|
113012
113072
|
})).join("")}</qti-simple-match-set>`).join("");
|
|
@@ -113292,6 +113352,14 @@ function qtiAttributeNumber(attributes2, name3, fallback) {
|
|
|
113292
113352
|
const value = Number(raw);
|
|
113293
113353
|
return Number.isFinite(value) ? value : fallback;
|
|
113294
113354
|
}
|
|
113355
|
+
function qtiAttributeCapacity(attributes2, name3, fallback) {
|
|
113356
|
+
const raw = attributes2[name3]?.trim();
|
|
113357
|
+
if (!raw) {
|
|
113358
|
+
return fallback;
|
|
113359
|
+
}
|
|
113360
|
+
const value = Number(raw);
|
|
113361
|
+
return Number.isInteger(value) && value >= 0 ? value : null;
|
|
113362
|
+
}
|
|
113295
113363
|
function qtiAttributeMaximum(attributes2, name3, fallback, unlimited) {
|
|
113296
113364
|
const maximum = qtiAttributeNumber(attributes2, name3, fallback);
|
|
113297
113365
|
return maximum === 0 ? unlimited : maximum;
|
|
@@ -113300,6 +113368,27 @@ function qtiAttributeBoolean(attributes2, name3) {
|
|
|
113300
113368
|
const value = attributes2[name3];
|
|
113301
113369
|
return value === "true" || value === "1";
|
|
113302
113370
|
}
|
|
113371
|
+
function gapContentIdentifiers(nodes) {
|
|
113372
|
+
return nodes.flatMap((node) => {
|
|
113373
|
+
if (node.kind === "gap") {
|
|
113374
|
+
return [node.identifier];
|
|
113375
|
+
}
|
|
113376
|
+
if (node.kind === "paragraph" || node.kind === "emphasis" || node.kind === "strong") {
|
|
113377
|
+
return gapContentIdentifiers(node.children);
|
|
113378
|
+
}
|
|
113379
|
+
if (node.kind === "list") {
|
|
113380
|
+
return node.items.flatMap(gapContentIdentifiers);
|
|
113381
|
+
}
|
|
113382
|
+
if (node.kind === "table") {
|
|
113383
|
+
return node.rows.flatMap((row) => row.cells.flatMap(gapContentIdentifiers));
|
|
113384
|
+
}
|
|
113385
|
+
return [];
|
|
113386
|
+
});
|
|
113387
|
+
}
|
|
113388
|
+
function qtiGapContentMatchesGaps(nodes, gaps) {
|
|
113389
|
+
const identifiers = gapContentIdentifiers(nodes);
|
|
113390
|
+
return identifiers.length === gaps.length && identifiers.every((identifier, index2) => identifier === gaps[index2]);
|
|
113391
|
+
}
|
|
113303
113392
|
function normalizedInteractionType(value) {
|
|
113304
113393
|
if (typeof value !== "string") {
|
|
113305
113394
|
return;
|
|
@@ -113310,6 +113399,9 @@ function normalizedInteractionType(value) {
|
|
|
113310
113399
|
function usableChoices(choices) {
|
|
113311
113400
|
return choices !== undefined && choices.length > 0 && choices.every((choice) => Boolean(choice.identifier));
|
|
113312
113401
|
}
|
|
113402
|
+
function usableAssociableChoices(choices) {
|
|
113403
|
+
return usableChoices(choices) && choices.every((choice) => qtiAttributeCapacity(choice.attributes, "match-max", 0) !== null);
|
|
113404
|
+
}
|
|
113313
113405
|
function transportableIdentifiers(identifiers) {
|
|
113314
113406
|
return identifiers.every((identifier) => Boolean(identifier) && !/\s/.test(identifier));
|
|
113315
113407
|
}
|
|
@@ -113382,7 +113474,7 @@ function authorableQtiInteractionProjection(interaction) {
|
|
|
113382
113474
|
}
|
|
113383
113475
|
case "match": {
|
|
113384
113476
|
const [sourceChoices, targetChoices] = interaction.choiceSets;
|
|
113385
|
-
if (interaction.choiceSets.length !== 2 || !
|
|
113477
|
+
if (interaction.choiceSets.length !== 2 || !usableAssociableChoices(sourceChoices) || !usableAssociableChoices(targetChoices) || !transportableIdentifiers([...sourceChoices, ...targetChoices].map((choice) => choice.identifier))) {
|
|
113386
113478
|
return null;
|
|
113387
113479
|
}
|
|
113388
113480
|
return {
|
|
@@ -113451,8 +113543,8 @@ function authorableQtiInteractionProjection(interaction) {
|
|
|
113451
113543
|
};
|
|
113452
113544
|
}
|
|
113453
113545
|
case "gap-match": {
|
|
113454
|
-
const { gapTexts, gaps, textWithGaps } = interaction;
|
|
113455
|
-
if (gapTexts.length === 0 || gaps.length === 0 || textWithGaps === undefined || !gapTexts.every((gapText) => Boolean(gapText.identifier && gapText.content)) || !transportableIdentifiers([...gapTexts.map((gapText) => gapText.identifier), ...gaps])) {
|
|
113546
|
+
const { gapTexts, gaps, textWithGaps, gapContent } = interaction;
|
|
113547
|
+
if (gapTexts.length === 0 || gaps.length === 0 || textWithGaps === undefined || !gapContent || !qtiGapContentMatchesGaps(gapContent, gaps) || !gapTexts.every((gapText) => Boolean(gapText.identifier && gapText.content)) || !transportableIdentifiers([...gapTexts.map((gapText) => gapText.identifier), ...gaps])) {
|
|
113456
113548
|
return null;
|
|
113457
113549
|
}
|
|
113458
113550
|
return {
|
|
@@ -113461,6 +113553,7 @@ function authorableQtiInteractionProjection(interaction) {
|
|
|
113461
113553
|
gapTexts,
|
|
113462
113554
|
gaps,
|
|
113463
113555
|
textWithGaps,
|
|
113556
|
+
gapContent,
|
|
113464
113557
|
shuffle: qtiAttributeBoolean(attributes2, "shuffle")
|
|
113465
113558
|
};
|
|
113466
113559
|
}
|
|
@@ -113484,7 +113577,7 @@ function contributesScore(declaration) {
|
|
|
113484
113577
|
function responsePairs(left, right) {
|
|
113485
113578
|
return left.flatMap((first) => right.map((second) => `${first} ${second}`));
|
|
113486
113579
|
}
|
|
113487
|
-
function
|
|
113580
|
+
function interactionResponseSpace(item, declaration) {
|
|
113488
113581
|
const interaction = item.interactions.find((candidate) => candidate.responseIdentifier === declaration.identifier);
|
|
113489
113582
|
if (!interaction) {
|
|
113490
113583
|
return {};
|
|
@@ -113576,21 +113669,49 @@ function processesResponseMapping(item, declaration) {
|
|
|
113576
113669
|
}
|
|
113577
113670
|
return item.responseProcessing.elements.some((element) => element.tagName === "qti-map-response" && (element.attributes.identifier ?? "RESPONSE") === declaration.identifier);
|
|
113578
113671
|
}
|
|
113579
|
-
function responseMappingMaximum(item, declaration) {
|
|
113672
|
+
function responseMappingMaximum(item, declaration, space) {
|
|
113580
113673
|
if (!declaration.mapping || !processesResponseMapping(item, declaration)) {
|
|
113581
113674
|
return null;
|
|
113582
113675
|
}
|
|
113583
113676
|
return qtiResponseMappingMaximum({
|
|
113584
113677
|
cardinality: declaration.cardinality,
|
|
113585
113678
|
mapping: declaration.mapping,
|
|
113586
|
-
...
|
|
113679
|
+
...space ?? interactionResponseSpace(item, declaration)
|
|
113587
113680
|
});
|
|
113588
113681
|
}
|
|
113589
113682
|
function qtiItemScoringValidationMessage(item) {
|
|
113590
113683
|
for (const declaration of item.responseDeclarations) {
|
|
113684
|
+
const space = interactionResponseSpace(item, declaration);
|
|
113685
|
+
const { responseValues: responseValues2, maximumResponses } = space;
|
|
113686
|
+
if (responseValues2 && declaration.correctValues.some((value) => !responseValues2.includes(value))) {
|
|
113687
|
+
return `Response ${declaration.identifier} declares a correct value its interaction cannot produce.`;
|
|
113688
|
+
}
|
|
113689
|
+
const interaction = item.interactions.find((candidate) => candidate.responseIdentifier === declaration.identifier);
|
|
113690
|
+
if (interaction?.type === "match") {
|
|
113691
|
+
const [sources = [], targets = []] = interaction.choiceSets;
|
|
113692
|
+
const issue6 = qtiDirectedPairValidationIssue2(declaration.correctValues, {
|
|
113693
|
+
sources: new Map(sources.map((choice) => [
|
|
113694
|
+
choice.identifier,
|
|
113695
|
+
qtiAttributeNumber(choice.attributes, "match-max", 0)
|
|
113696
|
+
])),
|
|
113697
|
+
targets: new Map(targets.map((choice) => [
|
|
113698
|
+
choice.identifier,
|
|
113699
|
+
qtiAttributeNumber(choice.attributes, "match-max", 0)
|
|
113700
|
+
]))
|
|
113701
|
+
});
|
|
113702
|
+
if (issue6?.kind === "source-capacity") {
|
|
113703
|
+
return `Response ${declaration.identifier} declares correct pairs that exceed source ${issue6.identifier}'s match-max.`;
|
|
113704
|
+
}
|
|
113705
|
+
if (issue6?.kind === "target-capacity") {
|
|
113706
|
+
return `Response ${declaration.identifier} declares correct pairs that exceed target ${issue6.identifier}'s match-max.`;
|
|
113707
|
+
}
|
|
113708
|
+
}
|
|
113709
|
+
if (declaration.cardinality !== "single" && maximumResponses !== undefined && maximumResponses !== 0 && declaration.correctValues.length > maximumResponses) {
|
|
113710
|
+
return `Response ${declaration.identifier} declares more correct values than its interaction accepts.`;
|
|
113711
|
+
}
|
|
113591
113712
|
if (declaration.mapping) {
|
|
113592
113713
|
if (processesResponseMapping(item, declaration)) {
|
|
113593
|
-
if (responseMappingMaximum(item, declaration) === null) {
|
|
113714
|
+
if (responseMappingMaximum(item, declaration, space) === null) {
|
|
113594
113715
|
return `Response ${declaration.identifier} has a mapping with no reachable positive score.`;
|
|
113595
113716
|
}
|
|
113596
113717
|
} else if (declaration.correctValues.length === 0) {
|
|
@@ -113960,6 +114081,7 @@ var init_qti = __esm(() => {
|
|
|
113960
114081
|
init_fxp();
|
|
113961
114082
|
init_timeback3();
|
|
113962
114083
|
init_timeback3();
|
|
114084
|
+
init_timeback3();
|
|
113963
114085
|
EVENT_HANDLER_ATTRIBUTE = /^on/i;
|
|
113964
114086
|
SCRIPT_SCHEMES2 = {
|
|
113965
114087
|
javascript: ["java", "script:"].join(""),
|
|
@@ -116786,6 +116908,16 @@ function playableChoice(choice) {
|
|
|
116786
116908
|
...choice.contentNodes ? { contentNodes: choice.contentNodes } : {}
|
|
116787
116909
|
};
|
|
116788
116910
|
}
|
|
116911
|
+
function playableAssociableChoice(choice) {
|
|
116912
|
+
const matchMax = qtiAttributeCapacity(choice.attributes, "match-max", 0);
|
|
116913
|
+
if (matchMax === null) {
|
|
116914
|
+
throw new AssessmentRuntimeError("UNSUPPORTED_INTERACTION", "The selected assessment contains an invalid Match capacity.");
|
|
116915
|
+
}
|
|
116916
|
+
return {
|
|
116917
|
+
...playableChoice(choice),
|
|
116918
|
+
matchMax
|
|
116919
|
+
};
|
|
116920
|
+
}
|
|
116789
116921
|
function requireChoices2(choices) {
|
|
116790
116922
|
if (choices.length === 0 || choices.some((candidate) => !candidate.identifier)) {
|
|
116791
116923
|
throw new AssessmentRuntimeError("UNSUPPORTED_INTERACTION", "The selected assessment contains an interaction Playcademy cannot render.");
|
|
@@ -116871,8 +117003,8 @@ function parseInteraction2(questionIdentifier, interaction) {
|
|
|
116871
117003
|
if (sets.length !== 2) {
|
|
116872
117004
|
throw new AssessmentRuntimeError("UNSUPPORTED_INTERACTION", "Match interactions require exactly two choice sets.");
|
|
116873
117005
|
}
|
|
116874
|
-
const sourceChoices = requireChoices2(sets[0].map(
|
|
116875
|
-
const targetChoices = requireChoices2(sets[1].map(
|
|
117006
|
+
const sourceChoices = requireChoices2(sets[0].map(playableAssociableChoice));
|
|
117007
|
+
const targetChoices = requireChoices2(sets[1].map(playableAssociableChoice));
|
|
116876
117008
|
return {
|
|
116877
117009
|
type,
|
|
116878
117010
|
responseIdentifier,
|
|
@@ -116897,7 +117029,7 @@ function parseInteraction2(questionIdentifier, interaction) {
|
|
|
116897
117029
|
};
|
|
116898
117030
|
}
|
|
116899
117031
|
if (type === "gap-match") {
|
|
116900
|
-
if (interaction.gapTexts.length === 0 || interaction.gaps.length === 0 || interaction.textWithGaps === undefined) {
|
|
117032
|
+
if (interaction.gapTexts.length === 0 || interaction.gaps.length === 0 || interaction.textWithGaps === undefined || !interaction.gapContent || !qtiGapContentMatchesGaps(interaction.gapContent, interaction.gaps)) {
|
|
116901
117033
|
throw new AssessmentRuntimeError("UNSUPPORTED_INTERACTION", `Assessment item ${questionIdentifier} has an incomplete gap-match interaction.`);
|
|
116902
117034
|
}
|
|
116903
117035
|
return {
|
|
@@ -116905,6 +117037,7 @@ function parseInteraction2(questionIdentifier, interaction) {
|
|
|
116905
117037
|
responseIdentifier,
|
|
116906
117038
|
...prompt,
|
|
116907
117039
|
content: interaction.textWithGaps,
|
|
117040
|
+
contentNodes: interaction.gapContent,
|
|
116908
117041
|
gapTexts: interaction.gapTexts.map((gapText) => ({
|
|
116909
117042
|
identifier: gapText.identifier,
|
|
116910
117043
|
content: gapText.content,
|
|
@@ -119009,8 +119142,7 @@ function assertPlayableQtiQuestion(question) {
|
|
|
119009
119142
|
}
|
|
119010
119143
|
const item = parsedQtiItemOrNull(question.rawXml);
|
|
119011
119144
|
const playable = item && playableQtiInteractions(item);
|
|
119012
|
-
|
|
119013
|
-
if (!playable || scoringIssue) {
|
|
119145
|
+
if (!playable || qtiItemScoringValidationMessage(item)) {
|
|
119014
119146
|
throw new ValidationError(`Question ${question.identifier} uses an interaction Playcademy cannot play or score.`);
|
|
119015
119147
|
}
|
|
119016
119148
|
}
|
|
@@ -119263,31 +119395,38 @@ ${input.itemBody}
|
|
|
119263
119395
|
</qti-response-processing>
|
|
119264
119396
|
</qti-assessment-item>`;
|
|
119265
119397
|
}
|
|
119266
|
-
function
|
|
119267
|
-
|
|
119268
|
-
|
|
119398
|
+
function parseStructuredChoices(value, options) {
|
|
119399
|
+
const { label, minimum, validate: validate2 } = options;
|
|
119400
|
+
if (!Array.isArray(value) || value.length < minimum) {
|
|
119401
|
+
throw new ValidationError(`${label} questions require at least ${minimum === 1 ? "one choice" : `${minimum} choices`}`);
|
|
119269
119402
|
}
|
|
119270
119403
|
const choices = value.map((choice) => {
|
|
119271
119404
|
const record3 = recordValue(choice);
|
|
119272
119405
|
const identifier = record3?.identifier;
|
|
119273
119406
|
const content = record3?.content;
|
|
119274
|
-
if (typeof identifier !== "string" || !identifier.trim() || typeof content !== "string" || !content.trim()) {
|
|
119275
|
-
throw new ValidationError(
|
|
119407
|
+
if (typeof identifier !== "string" || !identifier.trim() || typeof content !== "string" || !content.trim() || validate2 !== undefined && record3 !== undefined && !validate2(record3)) {
|
|
119408
|
+
throw new ValidationError(`${label} options are invalid`);
|
|
119276
119409
|
}
|
|
119277
119410
|
return { identifier: identifier.trim(), content: content.trim() };
|
|
119278
119411
|
});
|
|
119279
119412
|
if (new Set(choices.map((choice) => choice.identifier)).size !== choices.length) {
|
|
119280
|
-
throw new ValidationError(
|
|
119413
|
+
throw new ValidationError(`${label} option identifiers must be unique`);
|
|
119281
119414
|
}
|
|
119282
119415
|
return choices;
|
|
119283
119416
|
}
|
|
119284
|
-
function
|
|
119417
|
+
function declaredCorrectResponse(input, responseIdentifier) {
|
|
119285
119418
|
const declarations = Array.isArray(input.responseDeclarations) ? input.responseDeclarations : [];
|
|
119286
119419
|
const declaration = declarations.map(recordValue).find((candidate) => candidate?.identifier === responseIdentifier);
|
|
119287
|
-
const
|
|
119288
|
-
|
|
119289
|
-
|
|
119290
|
-
|
|
119420
|
+
const rawValues = recordValue(declaration?.correctResponse)?.value;
|
|
119421
|
+
return {
|
|
119422
|
+
cardinality: declaration?.cardinality,
|
|
119423
|
+
baseType: declaration?.baseType,
|
|
119424
|
+
values: Array.isArray(rawValues) ? rawValues.filter((value) => typeof value === "string") : []
|
|
119425
|
+
};
|
|
119426
|
+
}
|
|
119427
|
+
function inlineChoiceCorrectIdentifier(input, responseIdentifier, choiceIdentifiers) {
|
|
119428
|
+
const { cardinality, baseType, values } = declaredCorrectResponse(input, responseIdentifier);
|
|
119429
|
+
if (cardinality !== "single" || baseType !== "identifier") {
|
|
119291
119430
|
throw new ValidationError("Inline-choice questions require a single-identifier response declaration");
|
|
119292
119431
|
}
|
|
119293
119432
|
if (values.length !== 1 || !choiceIdentifiers.has(values[0])) {
|
|
@@ -119295,27 +119434,41 @@ function inlineChoiceCorrectIdentifier(input, responseIdentifier, choiceIdentifi
|
|
|
119295
119434
|
}
|
|
119296
119435
|
return values[0];
|
|
119297
119436
|
}
|
|
119298
|
-
function
|
|
119437
|
+
function structuredQuestionEnvelope(input, type, label, title) {
|
|
119299
119438
|
const interaction = recordValue(input.interaction);
|
|
119300
|
-
|
|
119301
|
-
if (interactionType !== "inline-choice") {
|
|
119439
|
+
if (normalizedQtiInteractionType(interaction?.type ?? input.type) !== type) {
|
|
119302
119440
|
return null;
|
|
119303
119441
|
}
|
|
119304
|
-
const responseIdentifier = interaction?.responseIdentifier;
|
|
119305
119442
|
const structure = recordValue(interaction?.questionStructure);
|
|
119443
|
+
const responseIdentifier = interaction?.responseIdentifier;
|
|
119306
119444
|
const prompt = structure?.prompt;
|
|
119307
|
-
if (!structure || typeof responseIdentifier !== "string" || !responseIdentifier.trim()) {
|
|
119308
|
-
throw new ValidationError(
|
|
119445
|
+
if (!interaction || !structure || typeof responseIdentifier !== "string" || !responseIdentifier.trim()) {
|
|
119446
|
+
throw new ValidationError(`${label} question data is invalid`);
|
|
119309
119447
|
}
|
|
119310
119448
|
if (!title || typeof prompt !== "string" || !prompt.trim()) {
|
|
119311
|
-
throw new ValidationError(
|
|
119449
|
+
throw new ValidationError(`${label} questions require a title and prompt`);
|
|
119450
|
+
}
|
|
119451
|
+
return {
|
|
119452
|
+
interaction,
|
|
119453
|
+
structure,
|
|
119454
|
+
responseIdentifier: responseIdentifier.trim(),
|
|
119455
|
+
prompt: prompt.trim()
|
|
119456
|
+
};
|
|
119457
|
+
}
|
|
119458
|
+
function buildInlineChoiceQuestionXml(input, identifier, title) {
|
|
119459
|
+
const envelope = structuredQuestionEnvelope(input, "inline-choice", "Inline-choice", title);
|
|
119460
|
+
if (!envelope) {
|
|
119461
|
+
return null;
|
|
119312
119462
|
}
|
|
119313
|
-
const
|
|
119314
|
-
const promptParts =
|
|
119463
|
+
const { structure, responseIdentifier, prompt } = envelope;
|
|
119464
|
+
const promptParts = prompt.split(INLINE_CHOICE_BLANK);
|
|
119315
119465
|
if (promptParts.length !== 2) {
|
|
119316
119466
|
throw new ValidationError("Inline-choice questions require exactly one blank");
|
|
119317
119467
|
}
|
|
119318
|
-
const choices =
|
|
119468
|
+
const choices = parseStructuredChoices(structure.inlineChoices, {
|
|
119469
|
+
label: "Inline-choice",
|
|
119470
|
+
minimum: 2
|
|
119471
|
+
});
|
|
119319
119472
|
const choiceIdentifiers = new Set(choices.map((choice) => choice.identifier));
|
|
119320
119473
|
const correctIdentifier = inlineChoiceCorrectIdentifier(input, responseIdentifier, choiceIdentifiers);
|
|
119321
119474
|
const choicesXml = choices.map((choice) => ` <qti-inline-choice identifier="${escapeXml(choice.identifier)}">${escapeXml(choice.content)}</qti-inline-choice>`).join(`
|
|
@@ -119333,6 +119486,86 @@ ${choicesXml}
|
|
|
119333
119486
|
itemBody
|
|
119334
119487
|
});
|
|
119335
119488
|
}
|
|
119489
|
+
function parseStructuredMatchChoices(value, label) {
|
|
119490
|
+
return parseStructuredChoices(value, {
|
|
119491
|
+
label: `Match ${label}`,
|
|
119492
|
+
minimum: 1,
|
|
119493
|
+
validate: (choice) => choice.matchMax === 1
|
|
119494
|
+
});
|
|
119495
|
+
}
|
|
119496
|
+
function structuredMatchCorrectPairs(input, responseIdentifier, sources, targets) {
|
|
119497
|
+
const { cardinality, baseType, values } = declaredCorrectResponse(input, responseIdentifier);
|
|
119498
|
+
if (cardinality !== "multiple" || baseType !== "directedPair") {
|
|
119499
|
+
throw new ValidationError("Match questions require a multiple directed-pair response declaration");
|
|
119500
|
+
}
|
|
119501
|
+
const sourceIdentifiers = new Set(sources.map((choice) => choice.identifier));
|
|
119502
|
+
const targetIdentifiers = new Set(targets.map((choice) => choice.identifier));
|
|
119503
|
+
const valid = values.length > 0 && new Set(values).size === values.length && values.every((value) => {
|
|
119504
|
+
const [source, target, extra] = value.split(" ");
|
|
119505
|
+
return extra === undefined && Boolean(source && target) && sourceIdentifiers.has(source) && targetIdentifiers.has(target);
|
|
119506
|
+
});
|
|
119507
|
+
if (!valid) {
|
|
119508
|
+
throw new ValidationError("Every Match correct response must name one declared source and target choice");
|
|
119509
|
+
}
|
|
119510
|
+
return values;
|
|
119511
|
+
}
|
|
119512
|
+
function buildMatchQuestionXml(input, identifier, title) {
|
|
119513
|
+
const envelope = structuredQuestionEnvelope(input, "match", "Match", title);
|
|
119514
|
+
if (!envelope) {
|
|
119515
|
+
return null;
|
|
119516
|
+
}
|
|
119517
|
+
const { interaction, structure, responseIdentifier, prompt } = envelope;
|
|
119518
|
+
const sources = parseStructuredMatchChoices(structure.sourceChoices, "source");
|
|
119519
|
+
const targets = parseStructuredMatchChoices(structure.targetChoices, "target");
|
|
119520
|
+
const sourceIdentifiers = new Set(sources.map((choice) => choice.identifier));
|
|
119521
|
+
if (targets.some((choice) => sourceIdentifiers.has(choice.identifier))) {
|
|
119522
|
+
throw new ValidationError("Match source and target choice identifiers must be unique");
|
|
119523
|
+
}
|
|
119524
|
+
const correctValues = structuredMatchCorrectPairs(input, responseIdentifier, sources, targets);
|
|
119525
|
+
const maxAssociations = interaction.maxAssociations;
|
|
119526
|
+
if (typeof maxAssociations !== "number" || !Number.isInteger(maxAssociations) || maxAssociations < 0 || maxAssociations !== 0 && maxAssociations < correctValues.length) {
|
|
119527
|
+
throw new ValidationError("Match max associations must fit every correct pair");
|
|
119528
|
+
}
|
|
119529
|
+
const item = {
|
|
119530
|
+
key: identifier,
|
|
119531
|
+
title,
|
|
119532
|
+
body: [
|
|
119533
|
+
{
|
|
119534
|
+
kind: "interaction",
|
|
119535
|
+
type: "match",
|
|
119536
|
+
responseIdentifier,
|
|
119537
|
+
attributes: {
|
|
119538
|
+
shuffle: interaction.shuffle === true,
|
|
119539
|
+
"max-associations": maxAssociations
|
|
119540
|
+
},
|
|
119541
|
+
content: [
|
|
119542
|
+
{
|
|
119543
|
+
kind: "element",
|
|
119544
|
+
name: "qti-prompt",
|
|
119545
|
+
children: [{ kind: "text", text: prompt }]
|
|
119546
|
+
}
|
|
119547
|
+
],
|
|
119548
|
+
choiceSets: [sources, targets].map((choices) => choices.map((choice) => ({
|
|
119549
|
+
identifier: choice.identifier,
|
|
119550
|
+
content: [{ kind: "text", text: choice.content }]
|
|
119551
|
+
})))
|
|
119552
|
+
}
|
|
119553
|
+
],
|
|
119554
|
+
responseDeclarations: [
|
|
119555
|
+
{
|
|
119556
|
+
identifier: responseIdentifier,
|
|
119557
|
+
cardinality: "multiple",
|
|
119558
|
+
baseType: "directedPair",
|
|
119559
|
+
correctValues
|
|
119560
|
+
}
|
|
119561
|
+
]
|
|
119562
|
+
};
|
|
119563
|
+
try {
|
|
119564
|
+
return compileQtiAuthoringItemXml(item, { identifier, title });
|
|
119565
|
+
} catch (error88) {
|
|
119566
|
+
throw new ValidationError(`This Match question cannot be compiled to QTI: ${errorMessage2(error88)}`);
|
|
119567
|
+
}
|
|
119568
|
+
}
|
|
119336
119569
|
function parseHottextSegments2(value) {
|
|
119337
119570
|
if (!Array.isArray(value) || value.length === 0) {
|
|
119338
119571
|
throw new ValidationError("Hottext questions require passage segments");
|
|
@@ -119637,7 +119870,8 @@ var init_timeback_qti_authoring_util = __esm(() => {
|
|
|
119637
119870
|
buildNumericQuestionXml,
|
|
119638
119871
|
buildAuthoringDocumentQuestionXml,
|
|
119639
119872
|
buildHottextQuestionXml,
|
|
119640
|
-
buildInlineChoiceQuestionXml
|
|
119873
|
+
buildInlineChoiceQuestionXml,
|
|
119874
|
+
buildMatchQuestionXml
|
|
119641
119875
|
];
|
|
119642
119876
|
});
|
|
119643
119877
|
function newPlaycademyTestIdentifier() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcademy/vite-plugin",
|
|
3
|
-
"version": "1.2.1-beta.
|
|
3
|
+
"version": "1.2.1-beta.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"archiver": "^7.0.1",
|
|
21
21
|
"picocolors": "^1.1.1",
|
|
22
|
-
"playcademy": "0.28.1-beta.
|
|
22
|
+
"playcademy": "0.28.1-beta.13"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@electric-sql/pglite": "^0.3.16",
|
|
26
26
|
"@inquirer/prompts": "^7.8.6",
|
|
27
27
|
"@playcademy/constants": "0.0.1",
|
|
28
|
-
"@playcademy/sandbox": "0.7.1-beta.
|
|
29
|
-
"@playcademy/sdk": "0.16.1-beta.
|
|
28
|
+
"@playcademy/sandbox": "0.7.1-beta.13",
|
|
29
|
+
"@playcademy/sdk": "0.16.1-beta.13",
|
|
30
30
|
"@playcademy/types": "0.0.1",
|
|
31
31
|
"@playcademy/utils": "0.0.1",
|
|
32
32
|
"@types/archiver": "^6.0.3",
|