@pie-element/simple-cloze 0.1.4-next.8 → 0.1.4

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.
Files changed (48) hide show
  1. package/dist/author/index.d.ts +2 -18
  2. package/dist/author/index.js +1151 -456
  3. package/dist/author/index.js.map +1 -1
  4. package/dist/browser/author/index.js +3764 -3612
  5. package/dist/browser/author/index.js.map +1 -1
  6. package/dist/browser/{client-BSTc3boO.js → client-e4ZTphpa.js} +874 -680
  7. package/dist/browser/client-e4ZTphpa.js.map +1 -0
  8. package/dist/browser/controller/index.js +2 -62
  9. package/dist/browser/controller-1IBOQFFL.js +69 -0
  10. package/dist/browser/controller-1IBOQFFL.js.map +1 -0
  11. package/dist/browser/delivery/index.js +82 -82
  12. package/dist/browser/delivery/index.js.map +1 -1
  13. package/dist/browser/editor-runtime/author/index.js +634 -0
  14. package/dist/browser/editor-runtime/author/index.js.map +1 -0
  15. package/dist/browser/editor-runtime/client-e4ZTphpa.js +2370 -0
  16. package/dist/browser/editor-runtime/client-e4ZTphpa.js.map +1 -0
  17. package/dist/browser/editor-runtime/controller/index.js +2 -0
  18. package/dist/browser/editor-runtime/controller-1IBOQFFL.js +69 -0
  19. package/dist/browser/editor-runtime/controller-1IBOQFFL.js.map +1 -0
  20. package/dist/browser/editor-runtime/delivery/index.js +243 -0
  21. package/dist/browser/editor-runtime/delivery/index.js.map +1 -0
  22. package/dist/browser/{i18n-BaF24Jzw.js → editor-runtime/i18n-BTsdzpBv.js} +397 -252
  23. package/dist/browser/editor-runtime/i18n-BTsdzpBv.js.map +1 -0
  24. package/dist/browser/editor-runtime/plain-text-iaQZhkGe.js +53 -0
  25. package/dist/browser/editor-runtime/plain-text-iaQZhkGe.js.map +1 -0
  26. package/dist/browser/editor-runtime/print/index.js +71 -0
  27. package/dist/browser/editor-runtime/print/index.js.map +1 -0
  28. package/dist/browser/i18n-BTsdzpBv.js +1755 -0
  29. package/dist/browser/i18n-BTsdzpBv.js.map +1 -0
  30. package/dist/browser/print/index.js +37 -52
  31. package/dist/browser/print/index.js.map +1 -1
  32. package/dist/controller/defaults.d.ts +5 -0
  33. package/dist/controller/index.js +11 -4
  34. package/dist/controller/index.js.map +1 -1
  35. package/dist/delivery/index.js +366 -200
  36. package/dist/delivery/index.js.map +1 -1
  37. package/dist/index.d.ts +1 -1
  38. package/dist/index.iife.d.ts +1 -1
  39. package/dist/index.iife.js +4 -4
  40. package/dist/print/index.d.ts +2 -10
  41. package/dist/print/index.js +366 -221
  42. package/dist/print/index.js.map +1 -1
  43. package/module/print.js +928 -783
  44. package/module/print.js.map +1 -1
  45. package/package.json +22 -4
  46. package/dist/browser/client-BSTc3boO.js.map +0 -1
  47. package/dist/browser/controller/index.js.map +0 -1
  48. package/dist/browser/i18n-BaF24Jzw.js.map +0 -1
@@ -6,10 +6,17 @@ var defaults_default = {
6
6
  correctAnswer: "",
7
7
  teacherInstructionsEnabled: true
8
8
  },
9
- configuration: { prompt: {
10
- label: "Prompt",
11
- settings: true
12
- } }
9
+ configuration: {
10
+ prompt: {
11
+ label: "Prompt",
12
+ settings: true
13
+ },
14
+ teacherInstructions: {
15
+ label: "Teacher Instructions",
16
+ settings: true
17
+ },
18
+ settingsPanelDisabled: false
19
+ }
13
20
  };
14
21
  //#endregion
15
22
  //#region src/controller/plain-text.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/controller/defaults.ts","../../src/controller/plain-text.ts","../../src/controller/index.ts"],"sourcesContent":["export default {\n model: {\n prompt: '<p>Enter your answer:</p>',\n promptEnabled: true,\n correctAnswer: '',\n teacherInstructionsEnabled: true,\n },\n configuration: {\n prompt: {\n label: 'Prompt',\n settings: true,\n },\n },\n};\n","/**\n * The text an HTML fragment reads as, computed without a DOM because\n * controllers also run server-side. An answer key authored in an editor is HTML\n * (`<p>Salt &amp; pepper</p>`) while the learner types plain text.\n */\n\n// HTML 4's Latin-1 entities name U+00A0 to U+00FF in code point order.\nconst LATIN_1 = [\n 'nbsp iexcl cent pound curren yen brvbar sect uml copy ordf laquo not shy reg macr',\n 'deg plusmn sup2 sup3 acute micro para middot cedil sup1 ordm raquo frac14 frac12 frac34 iquest',\n 'Agrave Aacute Acirc Atilde Auml Aring AElig Ccedil Egrave Eacute Ecirc Euml Igrave Iacute Icirc Iuml',\n 'ETH Ntilde Ograve Oacute Ocirc Otilde Ouml times Oslash Ugrave Uacute Ucirc Uuml Yacute THORN szlig',\n 'agrave aacute acirc atilde auml aring aelig ccedil egrave eacute ecirc euml igrave iacute icirc iuml',\n 'eth ntilde ograve oacute ocirc otilde ouml divide oslash ugrave uacute ucirc uuml yacute thorn yuml',\n].join(' ');\n\n// Small Greek letters from U+03B1. Each capital sits 32 below its small letter;\n// final sigma has none, and U+03A2 is unassigned.\nconst GREEK =\n 'alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi omicron pi rho ' +\n 'sigmaf sigma tau upsilon phi chi psi omega';\n\nconst SYMBOLS: Record<string, number> = {\n quot: 0x22,\n amp: 0x26,\n apos: 0x27,\n lt: 0x3c,\n gt: 0x3e,\n ensp: 0x2002,\n emsp: 0x2003,\n thinsp: 0x2009,\n ndash: 0x2013,\n mdash: 0x2014,\n lsquo: 0x2018,\n rsquo: 0x2019,\n sbquo: 0x201a,\n ldquo: 0x201c,\n rdquo: 0x201d,\n bdquo: 0x201e,\n bull: 0x2022,\n hellip: 0x2026,\n prime: 0x2032,\n Prime: 0x2033,\n euro: 0x20ac,\n trade: 0x2122,\n minus: 0x2212,\n radic: 0x221a,\n infin: 0x221e,\n asymp: 0x2248,\n ne: 0x2260,\n le: 0x2264,\n ge: 0x2265,\n};\n\nconst NAMED = new Map<string, number>(Object.entries(SYMBOLS));\nfor (const [i, name] of LATIN_1.split(' ').entries()) {\n NAMED.set(name, 0xa0 + i);\n}\nfor (const [i, name] of GREEK.split(' ').entries()) {\n NAMED.set(name, 0x3b1 + i);\n if (name !== 'sigmaf') NAMED.set(name[0].toUpperCase() + name.slice(1), 0x391 + i);\n}\n\n// As the HTML tokenizer reads markup: `<` opens a tag only before a letter or\n// `/`, so `3 < 5` stays text. Line breaks and block boundaries separate words;\n// inline tags do not (`<b>Par</b>is`).\nconst BLOCK_BOUNDARY = /<\\/?(?:br|p|div|li|h[1-6]|blockquote|pre|tr|td|th)\\b[^>]*>/gi;\nconst TAG = /<\\/?[a-z][^>]*>|<!--[\\s\\S]*?-->/gi;\nconst ENTITY = /&(#[0-9]+|#x[0-9a-f]+|[a-z][a-z0-9]*);/gi;\n\nconst decodeEntity = (match: string, body: string): string => {\n if (body[0] === '#') {\n const hex = body[1] === 'x' || body[1] === 'X';\n const code = Number.parseInt(body.slice(hex ? 2 : 1), hex ? 16 : 10);\n // HTML decodes NUL, a surrogate or an out-of-range number to U+FFFD.\n const valid = code > 0 && code <= 0x10ffff && (code < 0xd800 || code > 0xdfff);\n return String.fromCodePoint(valid ? code : 0xfffd);\n }\n const code = NAMED.get(body);\n return code === undefined ? match : String.fromCodePoint(code);\n};\n\n/**\n * Tags stripped, entities decoded (named and numeric), and each run of\n * whitespace, `&nbsp;` included, collapsed to one space and trimmed. An unknown\n * named entity stays as written.\n */\nexport const toPlainText = (value: unknown): string =>\n typeof value === 'string'\n ? value\n .replace(BLOCK_BOUNDARY, ' ')\n .replace(TAG, '')\n .replace(ENTITY, decodeEntity)\n .replace(/\\s+/g, ' ')\n .trim()\n : '';\n","import defaults from './defaults';\nimport { toPlainText } from './plain-text';\n\nexport type SimpleClozeQuestion = {\n id?: string;\n element?: string;\n prompt?: string;\n promptEnabled?: boolean;\n correctAnswer?: string;\n teacherInstructions?: string;\n teacherInstructionsEnabled?: boolean;\n /** The learner-facing strings' language, as `@pie-lib/translator` reads it (`en_US`, `es_ES`, …). */\n language?: string;\n [key: string]: unknown;\n};\n\nexport type SimpleClozeSession = {\n id?: string;\n element?: string;\n /** The learner's typed answer. */\n value?: unknown;\n [key: string]: unknown;\n};\n\nexport type SimpleClozeEnv = {\n mode?: string;\n role?: string;\n [key: string]: unknown;\n};\n\nexport type SimpleClozeCorrectness = 'correct' | 'incorrect' | 'unanswered';\n\nexport type SimpleClozeOutcome = {\n score: number;\n empty: boolean;\n traceLog: string[];\n};\n\ntype FieldConfig = { required?: boolean };\nexport type SimpleClozeValidationConfig = {\n prompt?: FieldConfig;\n [key: string]: unknown;\n};\n\n/**\n * Both sides compare as plain text, case-insensitively: an answer key authored\n * as HTML matches the text a learner types.\n */\nconst comparable = (value: unknown): string => toPlainText(value).toLowerCase();\n\n// Strips tags except img, iframe and source (media counts as content), as\n// multiple-choice's `getContent` does.\nconst hasContent = (html: unknown): boolean =>\n typeof html === 'string' &&\n html\n .replace(/(<(?!img|iframe|source)([^>]+)>)/gi, '')\n .replace(/&nbsp;/gi, ' ')\n .trim().length > 0;\n\n/**\n * A response with no text is unanswered; an answer key with no text matches\n * nothing, so a blank key never scores.\n */\nexport const getCorrectness = (\n question: SimpleClozeQuestion | null | undefined,\n session: SimpleClozeSession | null | undefined\n): SimpleClozeCorrectness => {\n const response = comparable(session?.value);\n if (!response) {\n return 'unanswered';\n }\n\n const key = comparable(question?.correctAnswer);\n if (key && response === key) {\n return 'correct';\n }\n\n return 'incorrect';\n};\n\n/** Single response, all-or-nothing: 1 when it matches the answer key, else 0. */\nexport const getPartialScore = (\n question: SimpleClozeQuestion | null | undefined,\n session: SimpleClozeSession | null | undefined\n): number => (getCorrectness(question, session) === 'correct' ? 1 : 0);\n\nconst getTraceLog = (\n question: SimpleClozeQuestion | null | undefined,\n correctness: SimpleClozeCorrectness,\n score: number\n): string[] => {\n const traceLog = [\n 'Response is compared with the answer key as plain text, ignoring case and extra spaces.',\n ];\n if (!comparable(question?.correctAnswer)) {\n traceLog.push('No correct answer is defined, so no response can score.');\n } else {\n traceLog.push(\n correctness === 'correct'\n ? 'Response matches the answer key.'\n : 'Response does not match the answer key.'\n );\n }\n traceLog.push(`Final score: ${score}.`);\n return traceLog;\n};\n\nexport const outcome = (\n question: SimpleClozeQuestion | null | undefined,\n session: SimpleClozeSession | null | undefined,\n _env: SimpleClozeEnv = {}\n): Promise<SimpleClozeOutcome> =>\n new Promise((resolve) => {\n const correctness = getCorrectness(question, session);\n\n if (correctness === 'unanswered') {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not enter a response. Score is 0.'],\n });\n return;\n }\n\n const score = correctness === 'correct' ? 1 : 0;\n resolve({ score, empty: false, traceLog: getTraceLog(question, correctness, score) });\n });\n\nexport const createDefaultModel = (model: SimpleClozeQuestion = {}): SimpleClozeQuestion => ({\n ...defaults.model,\n ...model,\n});\n\nexport const normalizeSession = (s: SimpleClozeSession | null | undefined): SimpleClozeSession => ({\n ...s,\n});\n\nexport const model = (\n question: SimpleClozeQuestion | null | undefined,\n session: SimpleClozeSession | null | undefined,\n env: SimpleClozeEnv | null | undefined\n): Promise<Record<string, unknown>> => {\n return new Promise((resolve) => {\n const safeSession = session || {};\n const safeEnv = env || {};\n const normalizedQuestion = createDefaultModel(question || {});\n\n const out: Record<string, unknown> = {\n prompt: normalizedQuestion.promptEnabled ? normalizedQuestion.prompt : null,\n disabled: safeEnv.mode !== 'gather',\n mode: safeEnv.mode,\n role: safeEnv.role,\n language: normalizedQuestion.language,\n };\n\n if (safeEnv.mode === 'evaluate') {\n out.correctness = getCorrectness(normalizedQuestion, safeSession);\n // The key is shown in the text input, so it goes out as plain text.\n out.correctAnswer = toPlainText(normalizedQuestion.correctAnswer);\n }\n\n if (safeEnv.role === 'instructor' && (safeEnv.mode === 'view' || safeEnv.mode === 'evaluate')) {\n out.teacherInstructions = normalizedQuestion.teacherInstructionsEnabled\n ? normalizedQuestion.teacherInstructions\n : null;\n } else {\n out.teacherInstructions = null;\n }\n\n resolve(out);\n });\n};\n\nexport const createCorrectResponseSession = (\n question: SimpleClozeQuestion | null | undefined,\n env: SimpleClozeEnv | null | undefined\n): Promise<SimpleClozeSession | null> => {\n return new Promise((resolve) => {\n if (env?.mode !== 'evaluate' && env?.role === 'instructor') {\n // The player sets `id` and `element` on the entry from the item config.\n resolve({ id: '1', value: toPlainText(question?.correctAnswer) });\n } else {\n resolve(null);\n }\n });\n};\n\nexport const validate = (\n model: SimpleClozeQuestion = {},\n config: SimpleClozeValidationConfig = {}\n): Record<string, string> => {\n const errors: Record<string, string> = {};\n\n if (config?.prompt?.required && !hasContent(model?.prompt)) {\n errors.prompt = 'This field is required.';\n }\n\n if (!comparable(model?.correctAnswer)) {\n errors.correctAnswer = 'No correct answer defined.';\n }\n\n return errors;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/controller/defaults.ts","../../src/controller/plain-text.ts","../../src/controller/index.ts"],"sourcesContent":["export default {\n model: {\n prompt: '<p>Enter your answer:</p>',\n promptEnabled: true,\n correctAnswer: '',\n teacherInstructionsEnabled: true,\n },\n configuration: {\n prompt: {\n label: 'Prompt',\n settings: true,\n },\n teacherInstructions: {\n label: 'Teacher Instructions',\n settings: true,\n },\n settingsPanelDisabled: false,\n },\n};\n","/**\n * The text an HTML fragment reads as, computed without a DOM because\n * controllers also run server-side. An answer key authored in an editor is HTML\n * (`<p>Salt &amp; pepper</p>`) while the learner types plain text.\n */\n\n// HTML 4's Latin-1 entities name U+00A0 to U+00FF in code point order.\nconst LATIN_1 = [\n 'nbsp iexcl cent pound curren yen brvbar sect uml copy ordf laquo not shy reg macr',\n 'deg plusmn sup2 sup3 acute micro para middot cedil sup1 ordm raquo frac14 frac12 frac34 iquest',\n 'Agrave Aacute Acirc Atilde Auml Aring AElig Ccedil Egrave Eacute Ecirc Euml Igrave Iacute Icirc Iuml',\n 'ETH Ntilde Ograve Oacute Ocirc Otilde Ouml times Oslash Ugrave Uacute Ucirc Uuml Yacute THORN szlig',\n 'agrave aacute acirc atilde auml aring aelig ccedil egrave eacute ecirc euml igrave iacute icirc iuml',\n 'eth ntilde ograve oacute ocirc otilde ouml divide oslash ugrave uacute ucirc uuml yacute thorn yuml',\n].join(' ');\n\n// Small Greek letters from U+03B1. Each capital sits 32 below its small letter;\n// final sigma has none, and U+03A2 is unassigned.\nconst GREEK =\n 'alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi omicron pi rho ' +\n 'sigmaf sigma tau upsilon phi chi psi omega';\n\nconst SYMBOLS: Record<string, number> = {\n quot: 0x22,\n amp: 0x26,\n apos: 0x27,\n lt: 0x3c,\n gt: 0x3e,\n ensp: 0x2002,\n emsp: 0x2003,\n thinsp: 0x2009,\n ndash: 0x2013,\n mdash: 0x2014,\n lsquo: 0x2018,\n rsquo: 0x2019,\n sbquo: 0x201a,\n ldquo: 0x201c,\n rdquo: 0x201d,\n bdquo: 0x201e,\n bull: 0x2022,\n hellip: 0x2026,\n prime: 0x2032,\n Prime: 0x2033,\n euro: 0x20ac,\n trade: 0x2122,\n minus: 0x2212,\n radic: 0x221a,\n infin: 0x221e,\n asymp: 0x2248,\n ne: 0x2260,\n le: 0x2264,\n ge: 0x2265,\n};\n\nconst NAMED = new Map<string, number>(Object.entries(SYMBOLS));\nfor (const [i, name] of LATIN_1.split(' ').entries()) {\n NAMED.set(name, 0xa0 + i);\n}\nfor (const [i, name] of GREEK.split(' ').entries()) {\n NAMED.set(name, 0x3b1 + i);\n if (name !== 'sigmaf') NAMED.set(name[0].toUpperCase() + name.slice(1), 0x391 + i);\n}\n\n// As the HTML tokenizer reads markup: `<` opens a tag only before a letter or\n// `/`, so `3 < 5` stays text. Line breaks and block boundaries separate words;\n// inline tags do not (`<b>Par</b>is`).\nconst BLOCK_BOUNDARY = /<\\/?(?:br|p|div|li|h[1-6]|blockquote|pre|tr|td|th)\\b[^>]*>/gi;\nconst TAG = /<\\/?[a-z][^>]*>|<!--[\\s\\S]*?-->/gi;\nconst ENTITY = /&(#[0-9]+|#x[0-9a-f]+|[a-z][a-z0-9]*);/gi;\n\nconst decodeEntity = (match: string, body: string): string => {\n if (body[0] === '#') {\n const hex = body[1] === 'x' || body[1] === 'X';\n const code = Number.parseInt(body.slice(hex ? 2 : 1), hex ? 16 : 10);\n // HTML decodes NUL, a surrogate or an out-of-range number to U+FFFD.\n const valid = code > 0 && code <= 0x10ffff && (code < 0xd800 || code > 0xdfff);\n return String.fromCodePoint(valid ? code : 0xfffd);\n }\n const code = NAMED.get(body);\n return code === undefined ? match : String.fromCodePoint(code);\n};\n\n/**\n * Tags stripped, entities decoded (named and numeric), and each run of\n * whitespace, `&nbsp;` included, collapsed to one space and trimmed. An unknown\n * named entity stays as written.\n */\nexport const toPlainText = (value: unknown): string =>\n typeof value === 'string'\n ? value\n .replace(BLOCK_BOUNDARY, ' ')\n .replace(TAG, '')\n .replace(ENTITY, decodeEntity)\n .replace(/\\s+/g, ' ')\n .trim()\n : '';\n","import defaults from './defaults.js';\nimport { toPlainText } from './plain-text.js';\n\nexport type SimpleClozeQuestion = {\n id?: string;\n element?: string;\n prompt?: string;\n promptEnabled?: boolean;\n correctAnswer?: string;\n teacherInstructions?: string;\n teacherInstructionsEnabled?: boolean;\n /** The learner-facing strings' language, as `@pie-lib/translator` reads it (`en_US`, `es_ES`, …). */\n language?: string;\n [key: string]: unknown;\n};\n\nexport type SimpleClozeSession = {\n id?: string;\n element?: string;\n /** The learner's typed answer. */\n value?: unknown;\n [key: string]: unknown;\n};\n\nexport type SimpleClozeEnv = {\n mode?: string;\n role?: string;\n [key: string]: unknown;\n};\n\nexport type SimpleClozeCorrectness = 'correct' | 'incorrect' | 'unanswered';\n\nexport type SimpleClozeOutcome = {\n score: number;\n empty: boolean;\n traceLog: string[];\n};\n\ntype FieldConfig = { required?: boolean };\nexport type SimpleClozeValidationConfig = {\n prompt?: FieldConfig;\n [key: string]: unknown;\n};\n\n/**\n * Both sides compare as plain text, case-insensitively: an answer key authored\n * as HTML matches the text a learner types.\n */\nconst comparable = (value: unknown): string => toPlainText(value).toLowerCase();\n\n// Strips tags except img, iframe and source (media counts as content), as\n// multiple-choice's `getContent` does.\nconst hasContent = (html: unknown): boolean =>\n typeof html === 'string' &&\n html\n .replace(/(<(?!img|iframe|source)([^>]+)>)/gi, '')\n .replace(/&nbsp;/gi, ' ')\n .trim().length > 0;\n\n/**\n * A response with no text is unanswered; an answer key with no text matches\n * nothing, so a blank key never scores.\n */\nexport const getCorrectness = (\n question: SimpleClozeQuestion | null | undefined,\n session: SimpleClozeSession | null | undefined\n): SimpleClozeCorrectness => {\n const response = comparable(session?.value);\n if (!response) {\n return 'unanswered';\n }\n\n const key = comparable(question?.correctAnswer);\n if (key && response === key) {\n return 'correct';\n }\n\n return 'incorrect';\n};\n\n/** Single response, all-or-nothing: 1 when it matches the answer key, else 0. */\nexport const getPartialScore = (\n question: SimpleClozeQuestion | null | undefined,\n session: SimpleClozeSession | null | undefined\n): number => (getCorrectness(question, session) === 'correct' ? 1 : 0);\n\nconst getTraceLog = (\n question: SimpleClozeQuestion | null | undefined,\n correctness: SimpleClozeCorrectness,\n score: number\n): string[] => {\n const traceLog = [\n 'Response is compared with the answer key as plain text, ignoring case and extra spaces.',\n ];\n if (!comparable(question?.correctAnswer)) {\n traceLog.push('No correct answer is defined, so no response can score.');\n } else {\n traceLog.push(\n correctness === 'correct'\n ? 'Response matches the answer key.'\n : 'Response does not match the answer key.'\n );\n }\n traceLog.push(`Final score: ${score}.`);\n return traceLog;\n};\n\nexport const outcome = (\n question: SimpleClozeQuestion | null | undefined,\n session: SimpleClozeSession | null | undefined,\n _env: SimpleClozeEnv = {}\n): Promise<SimpleClozeOutcome> =>\n new Promise((resolve) => {\n const correctness = getCorrectness(question, session);\n\n if (correctness === 'unanswered') {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not enter a response. Score is 0.'],\n });\n return;\n }\n\n const score = correctness === 'correct' ? 1 : 0;\n resolve({ score, empty: false, traceLog: getTraceLog(question, correctness, score) });\n });\n\nexport const createDefaultModel = (model: SimpleClozeQuestion = {}): SimpleClozeQuestion => ({\n ...defaults.model,\n ...model,\n});\n\nexport const normalizeSession = (s: SimpleClozeSession | null | undefined): SimpleClozeSession => ({\n ...s,\n});\n\nexport const model = (\n question: SimpleClozeQuestion | null | undefined,\n session: SimpleClozeSession | null | undefined,\n env: SimpleClozeEnv | null | undefined\n): Promise<Record<string, unknown>> => {\n return new Promise((resolve) => {\n const safeSession = session || {};\n const safeEnv = env || {};\n const normalizedQuestion = createDefaultModel(question || {});\n\n const out: Record<string, unknown> = {\n prompt: normalizedQuestion.promptEnabled ? normalizedQuestion.prompt : null,\n disabled: safeEnv.mode !== 'gather',\n mode: safeEnv.mode,\n role: safeEnv.role,\n language: normalizedQuestion.language,\n };\n\n if (safeEnv.mode === 'evaluate') {\n out.correctness = getCorrectness(normalizedQuestion, safeSession);\n // The key is shown in the text input, so it goes out as plain text.\n out.correctAnswer = toPlainText(normalizedQuestion.correctAnswer);\n }\n\n if (safeEnv.role === 'instructor' && (safeEnv.mode === 'view' || safeEnv.mode === 'evaluate')) {\n out.teacherInstructions = normalizedQuestion.teacherInstructionsEnabled\n ? normalizedQuestion.teacherInstructions\n : null;\n } else {\n out.teacherInstructions = null;\n }\n\n resolve(out);\n });\n};\n\nexport const createCorrectResponseSession = (\n question: SimpleClozeQuestion | null | undefined,\n env: SimpleClozeEnv | null | undefined\n): Promise<SimpleClozeSession | null> => {\n return new Promise((resolve) => {\n if (env?.mode !== 'evaluate' && env?.role === 'instructor') {\n // The player sets `id` and `element` on the entry from the item config.\n resolve({ id: '1', value: toPlainText(question?.correctAnswer) });\n } else {\n resolve(null);\n }\n });\n};\n\nexport const validate = (\n model: SimpleClozeQuestion = {},\n config: SimpleClozeValidationConfig = {}\n): Record<string, string> => {\n const errors: Record<string, string> = {};\n\n if (config?.prompt?.required && !hasContent(model?.prompt)) {\n errors.prompt = 'This field is required.';\n }\n\n if (!comparable(model?.correctAnswer)) {\n errors.correctAnswer = 'No correct answer defined.';\n }\n\n return errors;\n};\n"],"mappings":"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"}