@lernapps/app-templates 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +195 -2
- package/dist/appname-Bh23cUuz.mjs +12 -0
- package/dist/assessment-By2GGTJC.mjs +1696 -0
- package/dist/block-BK_ohsH1.mjs +82 -0
- package/dist/blocks-BmRrD7UY.mjs +149 -0
- package/dist/blocks.mjs +4 -0
- package/dist/check-BkEl-m-F.mjs +254 -0
- package/dist/content-cYBPYg_J.mjs +140 -0
- package/dist/contract-PONpxJtd.mjs +52 -0
- package/dist/e2e-BiMI-pCL.mjs +89 -0
- package/dist/engine-BqF2Vffn.mjs +136 -0
- package/dist/explainer-e2e.mjs +1297 -0
- package/dist/explainer-islands.mjs +197 -0
- package/dist/explainer-plugin.mjs +2131 -0
- package/dist/explainer.mjs +1246 -0
- package/dist/html-mffkHGHh.mjs +89 -0
- package/dist/image-CspJVfyQ.mjs +21 -0
- package/dist/interactive-e2e.mjs +140 -0
- package/dist/interactive-plugin.mjs +242 -0
- package/dist/interactive.mjs +75 -0
- package/dist/load-C-0hUaSC.mjs +16947 -0
- package/dist/needs-BzEahJ6X.mjs +97 -0
- package/dist/pages-9RaaomoO.mjs +81 -0
- package/dist/questions-e2e.mjs +1 -0
- package/dist/questions-engine.mjs +3 -0
- package/dist/questions.mjs +6 -0
- package/dist/quiz-BKc8JYtC.mjs +70 -0
- package/dist/quiz-e2e.mjs +174 -0
- package/dist/quiz-plugin.mjs +266 -0
- package/dist/quiz.mjs +246 -0
- package/dist/render-BJey0bwl.mjs +540 -0
- package/dist/render-BL6fV_NI.mjs +507 -0
- package/dist/render-DpRO_RKm.mjs +146 -0
- package/dist/result-_eQcamoz.mjs +180 -0
- package/dist/tasks-DADUYy52.mjs +74 -0
- package/dist/validate-COaZso7u.mjs +1072 -0
- package/dist/validate-CxtdeQ5S.mjs +146 -0
- package/dist/vue-DO1m6q1R.mjs +40 -0
- package/dist/vue-code-BkJjW_I_.mjs +320 -0
- package/docs/arc42/01-introduction-and-goals.arc42.md +82 -0
- package/docs/arc42/02-constraints.arc42.md +228 -0
- package/docs/arc42/03-system-context.arc42.md +118 -0
- package/docs/arc42/04-solution-strategy.arc42.md +47 -0
- package/docs/arc42/05-building-blocks.arc42.md +307 -0
- package/docs/arc42/06-runtime-view.arc42.md +98 -0
- package/docs/arc42/07-deployment-view.arc42.md +160 -0
- package/docs/arc42/08-concepts.arc42.md +123 -0
- package/docs/arc42/09-decisions.arc42.md +246 -0
- package/docs/arc42/10-quality-requirements.arc42.md +155 -0
- package/docs/arc42/11-risks.arc42.md +113 -0
- package/docs/arc42/12-glossary.arc42.md +162 -0
- package/docs/arc42/architecture-evidence.md +395 -0
- package/explainer/.arc42ignore +3 -0
- package/explainer/README.md +54 -0
- package/explainer/_gitignore +5 -0
- package/explainer/docs/arc42/03-explainer-system-context.arc42.md +35 -0
- package/explainer/docs/arc42/05-explainer-building-blocks.arc42.md +659 -0
- package/explainer/docs/arc42/06-explainer-runtime-view.arc42.md +105 -0
- package/explainer/docs/arc42/07-explainer-deployment-view.arc42.md +34 -0
- package/explainer/docs/arc42/08-explainer-concepts.arc42.md +17 -0
- package/explainer/docs/arc42/09-explainer-decisions.arc42.md +443 -0
- package/explainer/docs/arc42/10-explainer-quality-requirements.arc42.md +54 -0
- package/explainer/docs/arc42/11-explainer-risks.arc42.md +17 -0
- package/explainer/docs/arc42/12-explainer-glossary.arc42.md +185 -0
- package/explainer/e2e/explainer.e2e.ts +3 -0
- package/explainer/index.html +11 -0
- package/explainer/package.json +18 -0
- package/explainer/playwright.config.ts +1 -0
- package/explainer/src/app.yaml +20 -0
- package/explainer/src/components/ProzentFeld.test.ts +30 -0
- package/explainer/src/components/ProzentFeld.ts +121 -0
- package/explainer/src/components/ProzentFeld.vue +84 -0
- package/explainer/src/images/hunderterfeld-klammern.svg +105 -0
- package/explainer/src/main.ts +4 -0
- package/explainer/src/prozent/chapter.yaml +18 -0
- package/explainer/src/prozent/grundwert.yaml +115 -0
- package/explainer/src/prozent/prozentsatz.yaml +99 -0
- package/explainer/src/prozent/prozentwert.yaml +119 -0
- package/explainer/tsconfig.json +1 -0
- package/explainer/vite.config.ts +4 -0
- package/interactive/.arc42ignore +5 -0
- package/interactive/README.md +28 -0
- package/interactive/_gitignore +4 -0
- package/interactive/docs/arc42/05-interactive-building-blocks.arc42.md +518 -0
- package/interactive/docs/arc42/06-interactive-runtime-view.arc42.md +58 -0
- package/interactive/docs/arc42/07-interactive-deployment-view.arc42.md +35 -0
- package/interactive/docs/arc42/08-interactive-concepts.arc42.md +52 -0
- package/interactive/docs/arc42/09-interactive-decisions.arc42.md +130 -0
- package/interactive/docs/arc42/10-interactive-quality-requirements.arc42.md +45 -0
- package/interactive/docs/arc42/11-interactive-risks.arc42.md +20 -0
- package/interactive/docs/arc42/12-interactive-glossary.arc42.md +80 -0
- package/interactive/e2e/interactive.e2e.ts +3 -0
- package/interactive/index.html +11 -0
- package/interactive/package.json +18 -0
- package/interactive/playwright.config.ts +1 -0
- package/interactive/src/app.yaml +28 -0
- package/interactive/src/core/index.ts +101 -0
- package/interactive/src/data/lessons.json +4 -0
- package/interactive/src/env.d.ts +6 -0
- package/interactive/src/main.ts +6 -0
- package/interactive/src/messages/de.json +12 -0
- package/interactive/src/playthroughs/grundreihe.yaml +33 -0
- package/interactive/src/playthroughs/obere-reihe.yaml +14 -0
- package/interactive/src/ui/App.vue +85 -0
- package/interactive/tsconfig.json +1 -0
- package/interactive/vite.config.ts +4 -0
- package/package.json +142 -4
- package/quiz/README.md +31 -0
- package/quiz/_gitignore +5 -0
- package/quiz/docs/arc42/05-quiz-building-blocks.arc42.md +283 -0
- package/quiz/docs/arc42/06-quiz-runtime-view.arc42.md +21 -0
- package/quiz/docs/arc42/07-quiz-deployment-view.arc42.md +33 -0
- package/quiz/docs/arc42/09-quiz-decisions.arc42.md +36 -0
- package/quiz/docs/arc42/10-quiz-quality-requirements.arc42.md +53 -0
- package/quiz/docs/arc42/11-quiz-risks.arc42.md +19 -0
- package/quiz/docs/arc42/12-quiz-glossary.arc42.md +27 -0
- package/quiz/e2e/quiz.e2e.ts +3 -0
- package/quiz/index.html +11 -0
- package/quiz/package.json +18 -0
- package/quiz/playwright.config.ts +1 -0
- package/quiz/src/images/berge-ordnen.jpg +0 -0
- package/quiz/src/images/everest-hoehe.jpg +0 -0
- package/quiz/src/images/galapagos.jpg +0 -0
- package/quiz/src/images/grand-canyon.jpg +0 -0
- package/quiz/src/images/totes-meer.jpg +0 -0
- package/quiz/src/images/uluru.jpg +0 -0
- package/quiz/src/images/wasserfaelle-laender.jpg +0 -0
- package/quiz/src/main.ts +4 -0
- package/quiz/src/quiz.json +312 -0
- package/quiz/tsconfig.json +1 -0
- package/quiz/vite.config.ts +4 -0
- package/runtime/explainer/assessment-client.ts +478 -0
- package/runtime/explainer/assessment-page.ts +446 -0
- package/runtime/explainer/assessment.ts +393 -0
- package/runtime/explainer/check-page.ts +520 -0
- package/runtime/explainer/components/exercise.ts +409 -0
- package/runtime/explainer/components/props.ts +230 -0
- package/runtime/explainer/components/read.ts +428 -0
- package/runtime/explainer/content.ts +277 -0
- package/runtime/explainer/e2e.ts +1474 -0
- package/runtime/explainer/exercises.ts +324 -0
- package/runtime/explainer/explainer.css +1283 -0
- package/runtime/explainer/explainer.ts +838 -0
- package/runtime/explainer/explainer.v1.schema.json +1165 -0
- package/runtime/explainer/expression.ts +407 -0
- package/runtime/explainer/fraction.ts +107 -0
- package/runtime/explainer/generated/comparator.ts +31 -0
- package/runtime/explainer/generated/question.ts +165 -0
- package/runtime/explainer/generated/soundness.ts +280 -0
- package/runtime/explainer/generated/texts.ts +257 -0
- package/runtime/explainer/generated/types.ts +103 -0
- package/runtime/explainer/generated/values.ts +136 -0
- package/runtime/explainer/islands.ts +281 -0
- package/runtime/explainer/load.ts +457 -0
- package/runtime/explainer/messages/de.json +200 -0
- package/runtime/explainer/messages/en.json +200 -0
- package/runtime/explainer/messages.ts +13 -0
- package/runtime/explainer/needs.ts +122 -0
- package/runtime/explainer/open-task.ts +235 -0
- package/runtime/explainer/outline.ts +299 -0
- package/runtime/explainer/plugin.ts +488 -0
- package/runtime/explainer/position.ts +62 -0
- package/runtime/explainer/prompt.ts +75 -0
- package/runtime/explainer/render.ts +686 -0
- package/runtime/explainer/result.ts +238 -0
- package/runtime/explainer/routes.ts +119 -0
- package/runtime/explainer/schema.ts +140 -0
- package/runtime/explainer/scope.ts +87 -0
- package/runtime/explainer/stuck.ts +57 -0
- package/runtime/explainer/tasks.ts +109 -0
- package/runtime/explainer/tutor.ts +469 -0
- package/runtime/explainer/tutorpage.ts +669 -0
- package/runtime/explainer/video.ts +123 -0
- package/runtime/interactive/contract.ts +121 -0
- package/runtime/interactive/e2e.ts +190 -0
- package/runtime/interactive/interactive.css +82 -0
- package/runtime/interactive/interactive.ts +115 -0
- package/runtime/interactive/interactive.v1.schema.json +146 -0
- package/runtime/interactive/load.ts +188 -0
- package/runtime/interactive/messages/de.json +14 -0
- package/runtime/interactive/messages/en.json +14 -0
- package/runtime/interactive/messages.ts +11 -0
- package/runtime/interactive/playthrough.v1.schema.json +143 -0
- package/runtime/interactive/plugin.ts +266 -0
- package/runtime/interactive/render.ts +126 -0
- package/runtime/quiz/e2e.ts +233 -0
- package/runtime/quiz/engine.ts +242 -0
- package/runtime/quiz/messages/de.json +23 -0
- package/runtime/quiz/messages/en.json +23 -0
- package/runtime/quiz/plugin.ts +150 -0
- package/runtime/quiz/quiz.css +154 -0
- package/runtime/quiz/quiz.ts +56 -0
- package/runtime/quiz/quiz.v1.schema.json +72 -0
- package/runtime/quiz/render.ts +173 -0
- package/runtime/quiz/validate.ts +108 -0
- package/runtime/shared/appname.ts +16 -0
- package/runtime/shared/blocks/block.ts +162 -0
- package/runtime/shared/blocks/blocks.css +190 -0
- package/runtime/shared/blocks/blocks.v1.schema.json +432 -0
- package/runtime/shared/blocks/code.ts +26 -0
- package/runtime/shared/blocks/figures.ts +49 -0
- package/runtime/shared/blocks/image.ts +25 -0
- package/runtime/shared/blocks/index.ts +13 -0
- package/runtime/shared/blocks/messages/de.json +4 -0
- package/runtime/shared/blocks/messages/en.json +4 -0
- package/runtime/shared/blocks/render.ts +134 -0
- package/runtime/shared/blocks/validate.ts +165 -0
- package/runtime/shared/content.ts +197 -0
- package/runtime/shared/examples.ts +20 -0
- package/runtime/shared/html.ts +10 -0
- package/runtime/shared/languages.ts +94 -0
- package/runtime/shared/page.css +101 -0
- package/runtime/shared/pages.ts +114 -0
- package/runtime/shared/questions/e2e.ts +115 -0
- package/runtime/shared/questions/engine.ts +209 -0
- package/runtime/shared/questions/index.ts +39 -0
- package/runtime/shared/questions/messages/de.json +26 -0
- package/runtime/shared/questions/messages/en.json +26 -0
- package/runtime/shared/questions/question.ts +430 -0
- package/runtime/shared/questions/question.v1.schema.json +342 -0
- package/runtime/shared/questions/questions.css +192 -0
- package/runtime/shared/questions/render.ts +247 -0
- package/runtime/shared/questions/validate.ts +518 -0
- package/runtime/shared/schema.ts +44 -0
- package/runtime/shared/text/check.ts +68 -0
- package/runtime/shared/text/formula.ts +69 -0
- package/runtime/shared/text/markdown.ts +185 -0
- package/runtime/shared/text/text.v1.schema.json +34 -0
- package/runtime/shared/vue-code.ts +395 -0
- package/runtime/shared/vue.ts +38 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// Renders one question at build time, for every archetype that has questions: its controls, the button to check
|
|
2
|
+
// it, the place of its feedback and, hidden until it is answered, its solution (the right answer, the background of
|
|
3
|
+
// every option, the explanation, further reading). Without JavaScript the button stays hidden and the page reads as a
|
|
4
|
+
// worksheet with its answer key; the engine (engine.ts) makes a question live. The engine and the end-to-end tests
|
|
5
|
+
// rely on the ids, classes and data attributes written here. A page embeds a question as
|
|
6
|
+
//
|
|
7
|
+
// renderQuestion(question) +
|
|
8
|
+
// `<details><summary>${messages.solution}</summary>${renderSolution(question)}</details>`
|
|
9
|
+
//
|
|
10
|
+
// Links to other sites are plain links with rel="noopener": nothing is loaded from another server before a click.
|
|
11
|
+
import { translator } from "@lernapps/tooling/i18n";
|
|
12
|
+
import de from "./messages/de.json" with { type: "json" };
|
|
13
|
+
import en from "./messages/en.json" with { type: "json" };
|
|
14
|
+
import { escape } from "../html.ts";
|
|
15
|
+
import { localeOf, textsIn, type Language } from "../languages.ts";
|
|
16
|
+
import { mixed, questionId, rightEntries, type Link, type Question } from "./question.ts";
|
|
17
|
+
|
|
18
|
+
export type QuestionMessages = typeof de;
|
|
19
|
+
/** The texts of a question for learners in each language it has them: an app may declare only these languages. */
|
|
20
|
+
export const QUESTION_MESSAGES: Readonly<Record<Language, QuestionMessages>> = { de, en };
|
|
21
|
+
|
|
22
|
+
/** The question's texts in `lang`; German for a language it does not have. */
|
|
23
|
+
export const questionMessagesIn = (lang: string): QuestionMessages => textsIn(QUESTION_MESSAGES, lang);
|
|
24
|
+
|
|
25
|
+
/** What rendering in one language needs: its texts and its numbers. */
|
|
26
|
+
export interface Words {
|
|
27
|
+
t: (key: keyof QuestionMessages, params?: Readonly<Record<string, string | number>>) => string;
|
|
28
|
+
numbers: Intl.NumberFormat;
|
|
29
|
+
}
|
|
30
|
+
const languages = new Map<string, Words>();
|
|
31
|
+
export function questionWords(lang = "de"): Words {
|
|
32
|
+
let found = languages.get(lang);
|
|
33
|
+
if (found === undefined) {
|
|
34
|
+
const locale = localeOf(lang);
|
|
35
|
+
found = {
|
|
36
|
+
t: translator(questionMessagesIn(lang), locale),
|
|
37
|
+
numbers: new Intl.NumberFormat(locale, { maximumFractionDigits: 10 }),
|
|
38
|
+
};
|
|
39
|
+
languages.set(lang, found);
|
|
40
|
+
}
|
|
41
|
+
return found;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** How a learner text becomes HTML. */
|
|
45
|
+
export type Format = (text: string) => string;
|
|
46
|
+
|
|
47
|
+
function controls(question: Question, { t }: Words, format: Format): string {
|
|
48
|
+
const name = questionId(question);
|
|
49
|
+
switch (question.type) {
|
|
50
|
+
case "single-choice":
|
|
51
|
+
case "multiple-choice": {
|
|
52
|
+
const type = question.type === "single-choice" ? "radio" : "checkbox";
|
|
53
|
+
return question.options
|
|
54
|
+
.map(
|
|
55
|
+
(option, index) =>
|
|
56
|
+
`<label class="question-option"><input type="${type}" name="${name}" value="${index}"> <span>${format(option.text)}</span></label>`,
|
|
57
|
+
)
|
|
58
|
+
.join("\n");
|
|
59
|
+
}
|
|
60
|
+
case "true-false":
|
|
61
|
+
return [true, false]
|
|
62
|
+
.map(
|
|
63
|
+
(value) =>
|
|
64
|
+
`<label class="question-option"><input type="radio" name="${name}" value="${value}"> <span>${t(value ? "true" : "false")}</span></label>`,
|
|
65
|
+
)
|
|
66
|
+
.join("\n");
|
|
67
|
+
case "number":
|
|
68
|
+
return [
|
|
69
|
+
`<label class="question-number">${t("yourAnswer")}`,
|
|
70
|
+
`<input type="text" inputmode="decimal" autocomplete="off" name="${name}"${question.unit === undefined ? "" : ` aria-describedby="${name}-unit"`}>`,
|
|
71
|
+
question.unit === undefined ? "" : `<span id="${name}-unit">${format(question.unit)}</span>`,
|
|
72
|
+
`</label>`,
|
|
73
|
+
].join(" ");
|
|
74
|
+
case "ordering": {
|
|
75
|
+
const places = question.items
|
|
76
|
+
.map((_item, place) => `<option value="${place}">${t("place", { place: place + 1 })}</option>`)
|
|
77
|
+
.join("");
|
|
78
|
+
return [
|
|
79
|
+
`<p class="question-hint">${t("orderHint")}</p>`,
|
|
80
|
+
`<ul class="question-pairs">`,
|
|
81
|
+
...mixed(question.items.length, question.id).map(
|
|
82
|
+
(index) =>
|
|
83
|
+
`<li><label><span>${format(question.items[index] ?? "")}</span> <select name="${name}-${index}" data-item="${index}"><option value="">${t("choose")}</option>${places}</select></label></li>`,
|
|
84
|
+
),
|
|
85
|
+
`</ul>`,
|
|
86
|
+
].join("\n");
|
|
87
|
+
}
|
|
88
|
+
case "matching": {
|
|
89
|
+
const entries = rightEntries(question);
|
|
90
|
+
const rights = mixed(entries.length, question.id)
|
|
91
|
+
.map((index) => `<option value="${index}">${escape(entries[index] ?? "")}</option>`)
|
|
92
|
+
.join("");
|
|
93
|
+
return [
|
|
94
|
+
`<p class="question-hint">${t("matchHint")}</p>`,
|
|
95
|
+
`<ul class="question-pairs">`,
|
|
96
|
+
...question.pairs.map(
|
|
97
|
+
(pair, index) =>
|
|
98
|
+
`<li><label><span>${format(pair.left)}</span> <select name="${name}-${index}" data-left="${index}"><option value="">${t("choose")}</option>${rights}</select></label></li>`,
|
|
99
|
+
),
|
|
100
|
+
`</ul>`,
|
|
101
|
+
].join("\n");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* How one question renders: in a `div` on its own or an `li` of a list, with or without a button to the next one;
|
|
108
|
+
* in which language (the question already in it, see questionIn); and, for a page that renders the question's text
|
|
109
|
+
* itself (with formulas), its text as HTML.
|
|
110
|
+
*/
|
|
111
|
+
export interface RenderQuestionOptions {
|
|
112
|
+
/** How a learner text becomes HTML, e.g. with its formulas; escaped as plain text by default. */
|
|
113
|
+
format?: (text: string) => string;
|
|
114
|
+
element?: "div" | "li";
|
|
115
|
+
next?: boolean;
|
|
116
|
+
lang?: string;
|
|
117
|
+
textHtml?: string;
|
|
118
|
+
/** The question's material as HTML, rendered by the page (renderMaterial of the blocks): shown after its text. */
|
|
119
|
+
materialHtml?: string;
|
|
120
|
+
explanationHtml?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** How a solution renders: in which language, and, for a page that renders it itself, the explanation as HTML. */
|
|
124
|
+
export interface RenderSolutionOptions {
|
|
125
|
+
format?: (text: string) => string;
|
|
126
|
+
lang?: string;
|
|
127
|
+
explanationHtml?: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* One question with its controls, the button to check it, the place of its feedback and, hidden until it is answered,
|
|
132
|
+
* what the learner learns about it (the same as its solution). The engine's startQuestion makes it live; without
|
|
133
|
+
* JavaScript the button stays hidden.
|
|
134
|
+
*/
|
|
135
|
+
export function renderQuestion(
|
|
136
|
+
question: Question,
|
|
137
|
+
{
|
|
138
|
+
element = "div",
|
|
139
|
+
next = false,
|
|
140
|
+
lang = "de",
|
|
141
|
+
textHtml,
|
|
142
|
+
materialHtml,
|
|
143
|
+
explanationHtml,
|
|
144
|
+
format = escape,
|
|
145
|
+
}: RenderQuestionOptions = {},
|
|
146
|
+
): string {
|
|
147
|
+
const words = questionWords(lang);
|
|
148
|
+
const { t } = words;
|
|
149
|
+
const id = questionId(question);
|
|
150
|
+
const nextButton = next ? ` <button type="button" class="question-next" hidden>${t("next")}</button>` : "";
|
|
151
|
+
return [
|
|
152
|
+
`<${element} class="question" id="${id}" data-id="${question.id}" data-type="${question.type}">`,
|
|
153
|
+
`<form class="question-form" novalidate>`,
|
|
154
|
+
`<fieldset>`,
|
|
155
|
+
`<legend class="question-text">${textHtml ?? format(question.text)}</legend>`,
|
|
156
|
+
...(materialHtml === undefined || materialHtml === ""
|
|
157
|
+
? []
|
|
158
|
+
: [`<div class="question-material">${materialHtml}</div>`]),
|
|
159
|
+
controls(question, words, format),
|
|
160
|
+
`</fieldset>`,
|
|
161
|
+
`<div class="question-feedback" role="status"></div>`,
|
|
162
|
+
`<div class="question-reveal" hidden>`,
|
|
163
|
+
renderSolution(question, { lang, format, ...(explanationHtml === undefined ? {} : { explanationHtml }) }),
|
|
164
|
+
`</div>`,
|
|
165
|
+
`<p class="question-actions" hidden><button type="submit" class="question-check">${t("check")}</button>${nextButton}</p>`,
|
|
166
|
+
`</form>`,
|
|
167
|
+
`</${element}>`,
|
|
168
|
+
].join("\n");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** The right answer as the solution states it, in `lang`: the right options, the number, the order, the pairs. */
|
|
172
|
+
export function answerText(question: Question, lang = "de", format: Format = escape): string {
|
|
173
|
+
return rightAnswerText(question, questionWords(lang), format);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function rightAnswerText(question: Question, { t, numbers }: Words, format: Format): string {
|
|
177
|
+
switch (question.type) {
|
|
178
|
+
case "single-choice":
|
|
179
|
+
case "multiple-choice":
|
|
180
|
+
return question.options
|
|
181
|
+
.filter((option) => option.correct)
|
|
182
|
+
.map((option) => format(option.text))
|
|
183
|
+
.join("; ");
|
|
184
|
+
case "true-false":
|
|
185
|
+
return t(question.answer ? "statementTrue" : "statementFalse");
|
|
186
|
+
case "number": {
|
|
187
|
+
const unit = question.unit === undefined ? "" : ` ${format(question.unit)}`;
|
|
188
|
+
const tolerance =
|
|
189
|
+
question.tolerance > 0
|
|
190
|
+
? ` ${t("tolerance", { tolerance: `${numbers.format(question.tolerance)}${unit}` })}`
|
|
191
|
+
: "";
|
|
192
|
+
return `${numbers.format(question.answer)}${unit}${tolerance}`;
|
|
193
|
+
}
|
|
194
|
+
case "ordering":
|
|
195
|
+
return question.items.map((item, index) => `${index + 1}. ${format(item)}`).join(", ");
|
|
196
|
+
case "matching":
|
|
197
|
+
return question.pairs.map((pair) => `${format(pair.left)} – ${format(pair.right)}`).join("; ");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** A link to another site: opened like any link, never loaded before a click, without access to this page. */
|
|
202
|
+
export const external = (link: Link) => `<a href="${escape(link.url)}" rel="noopener">${escape(link.title)}</a>`;
|
|
203
|
+
|
|
204
|
+
/** Every option with its background, the right ones marked; the engine marks the learner's choice. */
|
|
205
|
+
function optionInfo(question: Question, words: Words, format: Format): string {
|
|
206
|
+
const { t } = words;
|
|
207
|
+
if (question.type !== "single-choice" && question.type !== "multiple-choice") {
|
|
208
|
+
return `<p><strong>${t("rightAnswer")}</strong> ${rightAnswerText(question, words, format)}</p>`;
|
|
209
|
+
}
|
|
210
|
+
return [
|
|
211
|
+
`<ul class="question-infos">`,
|
|
212
|
+
...question.options.map((option, index) =>
|
|
213
|
+
[
|
|
214
|
+
`<li class="question-info${option.correct ? " question-info-right" : ""}" data-option="${index}">`,
|
|
215
|
+
`<p class="question-info-head"><strong>${format(option.text)}</strong>${option.correct ? ` <span class="question-badge question-badge-right">${t("rightOption")}</span>` : ""}</p>`,
|
|
216
|
+
option.info === "" ? "" : `<p>${format(option.info)}</p>`,
|
|
217
|
+
option.link === undefined ? "" : `<p>${external(option.link)}</p>`,
|
|
218
|
+
`</li>`,
|
|
219
|
+
].join(""),
|
|
220
|
+
),
|
|
221
|
+
`</ul>`,
|
|
222
|
+
].join("\n");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* What the learner learns about a question, its solution: the right answer (the right options marked), each option's
|
|
227
|
+
* background, the explanation, further reading. A fragment without ids, e.g. for a <details>.
|
|
228
|
+
*/
|
|
229
|
+
export function renderSolution(
|
|
230
|
+
question: Question,
|
|
231
|
+
{ lang = "de", explanationHtml, format = escape }: RenderSolutionOptions = {},
|
|
232
|
+
): string {
|
|
233
|
+
const words = questionWords(lang);
|
|
234
|
+
const links =
|
|
235
|
+
question.links.length === 0
|
|
236
|
+
? []
|
|
237
|
+
: [
|
|
238
|
+
`<p class="question-more">${words.t("moreReading")}</p>`,
|
|
239
|
+
`<ul class="question-links">`,
|
|
240
|
+
...question.links.map((link) => `<li>${external(link)}</li>`),
|
|
241
|
+
`</ul>`,
|
|
242
|
+
];
|
|
243
|
+
const explanation =
|
|
244
|
+
explanationHtml ??
|
|
245
|
+
(question.explanation === "" ? "" : `<p class="question-explanation">${format(question.explanation)}</p>`);
|
|
246
|
+
return [optionInfo(question, words, format), explanation, ...links].filter((part) => part !== "").join("\n");
|
|
247
|
+
}
|