@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,324 @@
|
|
|
1
|
+
// The exercises of an explainer page at build time: a block of one of the question types, rendered through
|
|
2
|
+
// the per-question exports of the shared runtime (shared/questions) (its controls, and a <details> "Lösung" with the right answer, the
|
|
3
|
+
// background of every option, the explanation and the links); and a generated exercise: its templates rendered once
|
|
4
|
+
// per language (restricted Markdown, formulas as MathML, the placeholders as tokens), the place of the live task, and
|
|
5
|
+
// three worked tasks, the task numbers 1 to 3 (taskSeeds), each with its task number and a <details> "Lösung" (with JavaScript they show
|
|
6
|
+
// once the live task is solved). Each exercise carries the data its live part
|
|
7
|
+
// needs in the browser (explainer.ts): the question, or the exercise with its templates and data files.
|
|
8
|
+
import { questionIn, type Question } from "../shared/questions/question.ts";
|
|
9
|
+
import { renderMaterial, type MaterialContext } from "../shared/blocks/index.ts";
|
|
10
|
+
import { answerText, questionMessagesIn, renderQuestion, renderSolution } from "../shared/questions/render.ts";
|
|
11
|
+
import { inLanguage, type Text } from "../shared/languages.ts";
|
|
12
|
+
import type { DataRecord, Exercise, OpenTask, QuestionBlock } from "./content.ts";
|
|
13
|
+
import { hintOf, stepsOf, taskText, tokenize } from "./generated/texts.ts";
|
|
14
|
+
import { generate } from "./generated/values.ts";
|
|
15
|
+
import { questionFor, taskKey } from "./generated/question.ts";
|
|
16
|
+
import { WORKED_TASKS } from "./tasks.ts";
|
|
17
|
+
import { type Data, type Generated, type LanguageTemplates, type Templates } from "./generated/types.ts";
|
|
18
|
+
import { plain, renderInline, renderMarkdown, renderWithFormulas } from "../shared/text/markdown.ts";
|
|
19
|
+
import { distinctSeeds } from "./tasks.ts";
|
|
20
|
+
import { message } from "./messages.ts";
|
|
21
|
+
|
|
22
|
+
/** What the browser reads from an exercise (script.explainer-exercise-data). */
|
|
23
|
+
export type ExerciseData =
|
|
24
|
+
| {
|
|
25
|
+
kind: "question";
|
|
26
|
+
lang: string;
|
|
27
|
+
question: Question;
|
|
28
|
+
/** The texts of the question that hold a formula, as HTML (the feedback quotes them); the others are plain. */
|
|
29
|
+
rendered: Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
| {
|
|
32
|
+
kind: "generated";
|
|
33
|
+
lang: string;
|
|
34
|
+
spec: Generated;
|
|
35
|
+
templates: Templates;
|
|
36
|
+
data: Data;
|
|
37
|
+
/** The seed of each task number, from 1 (taskSeeds). */
|
|
38
|
+
seeds: number[];
|
|
39
|
+
texts: { newTask: string; practice: string; hint: string; solution: string; taskNumber: string };
|
|
40
|
+
}
|
|
41
|
+
| OpenTaskData;
|
|
42
|
+
|
|
43
|
+
/** What the browser reads from an open task: its texts as HTML, its points, how the learner may answer. */
|
|
44
|
+
export interface OpenTaskData {
|
|
45
|
+
kind: "open";
|
|
46
|
+
lang: string;
|
|
47
|
+
id: string;
|
|
48
|
+
/** The task as HTML. */
|
|
49
|
+
task: string;
|
|
50
|
+
answer: readonly ("text" | "photo")[];
|
|
51
|
+
/** Each point as plain text, its kind, and the address of its source on this page's tree. */
|
|
52
|
+
points: { text: string; kind: "content" | "form"; href?: string }[];
|
|
53
|
+
/** The model answer as HTML. */
|
|
54
|
+
solution: string;
|
|
55
|
+
/** The hint as HTML. */
|
|
56
|
+
hint?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The templates of a generated exercise in each of `languages`; links inside them through `resolve`. */
|
|
60
|
+
export function templatesOf(
|
|
61
|
+
spec: Generated,
|
|
62
|
+
languages: readonly string[],
|
|
63
|
+
resolve: (href: string) => string = (href) => href,
|
|
64
|
+
): Templates {
|
|
65
|
+
const templates: Record<string, LanguageTemplates> = {};
|
|
66
|
+
for (const lang of languages) {
|
|
67
|
+
const inline = (text: Text) => renderInline(tokenize(inLanguage(text, lang)), resolve);
|
|
68
|
+
templates[lang] = {
|
|
69
|
+
task: inline(spec.task.text),
|
|
70
|
+
steps: (spec.steps ?? []).map(inline),
|
|
71
|
+
hint: spec.hint === undefined ? undefined : inline(spec.hint),
|
|
72
|
+
messages: (spec.mistakes ?? []).map((mistake) => tokenize(inLanguage(mistake.message, lang))),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return templates;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The data files a generated exercise draws from, by name. */
|
|
79
|
+
export function dataOf(spec: Generated, all: Readonly<Record<string, readonly DataRecord[]>>): Data {
|
|
80
|
+
const names = Object.values(spec.given).flatMap((arbitrary) =>
|
|
81
|
+
arbitrary.from !== undefined && !Array.isArray(arbitrary.from) ? [(arbitrary.from as { data: string }).data] : [],
|
|
82
|
+
);
|
|
83
|
+
return Object.fromEntries(names.filter((name) => all[name] !== undefined).map((name) => [name, all[name] ?? []]));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const seedCache = new WeakMap<Generated, number[]>();
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The seed of each task number of a generated exercise, from 1: the seeds of its distinct tasks (what the learner sees
|
|
90
|
+
* of a task, in German), up to MAX_TASKS (tasks.ts); once per exercise.
|
|
91
|
+
*/
|
|
92
|
+
export function taskSeeds(spec: Generated, data: Data): number[] {
|
|
93
|
+
let seeds = seedCache.get(spec);
|
|
94
|
+
if (seeds === undefined) {
|
|
95
|
+
const templates = templatesOf(spec, ["de"]);
|
|
96
|
+
seeds = distinctSeeds((seed) => {
|
|
97
|
+
try {
|
|
98
|
+
return taskKey(spec, generate(spec, seed, data), "de", templates);
|
|
99
|
+
} catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
seedCache.set(spec, seeds);
|
|
104
|
+
}
|
|
105
|
+
return seeds;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** One worked task of a generated exercise: what the page shows without JavaScript, and what the tests expect. */
|
|
109
|
+
export interface Worked {
|
|
110
|
+
/** Its task number. */
|
|
111
|
+
number: number;
|
|
112
|
+
seed: number;
|
|
113
|
+
/** The task as HTML. */
|
|
114
|
+
task: string;
|
|
115
|
+
question: Question;
|
|
116
|
+
hint?: string;
|
|
117
|
+
steps: string[];
|
|
118
|
+
/** The right answer as the solution states it. */
|
|
119
|
+
answer: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The worked tasks of a generated exercise in `lang`. */
|
|
123
|
+
export function workedTasks(spec: Generated, data: Data, templates: Templates, lang: string): Worked[] {
|
|
124
|
+
return taskSeeds(spec, data)
|
|
125
|
+
.slice(0, WORKED_TASKS)
|
|
126
|
+
.map((seed, index) => {
|
|
127
|
+
const task = generate(spec, seed, data);
|
|
128
|
+
const question = questionFor(spec, task, lang, templates);
|
|
129
|
+
const hint = hintOf(spec, task, lang, templates);
|
|
130
|
+
return {
|
|
131
|
+
number: index + 1,
|
|
132
|
+
seed,
|
|
133
|
+
task: taskText(spec, task, lang, templates),
|
|
134
|
+
question,
|
|
135
|
+
steps: stepsOf(spec, task, lang, templates),
|
|
136
|
+
answer: answerText(question, lang),
|
|
137
|
+
...(hint === undefined ? {} : { hint }),
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const json = (value: unknown) => JSON.stringify(value).replace(/</g, "\\u003c");
|
|
143
|
+
|
|
144
|
+
/** The texts of a question that hold a formula, $…$. */
|
|
145
|
+
function formulaTexts(value: unknown): string[] {
|
|
146
|
+
if (typeof value === "string") return value.includes("$") && /\$[^$\n]+\$/.test(value) ? [value] : [];
|
|
147
|
+
if (Array.isArray(value)) return value.flatMap(formulaTexts);
|
|
148
|
+
if (typeof value === "object" && value !== null) return Object.values(value).flatMap(formulaTexts);
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** What the browser reads from a question of the question types in `lang`: the question, its formulas as HTML. */
|
|
153
|
+
function questionData(block: QuestionBlock, lang: string): Extract<ExerciseData, { kind: "question" }> {
|
|
154
|
+
const question = questionIn(block, lang);
|
|
155
|
+
const rendered: Record<string, string> = {};
|
|
156
|
+
for (const text of formulaTexts(question)) rendered[text] = renderWithFormulas(text);
|
|
157
|
+
return { kind: "question", lang, question, rendered };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function questionExercise(block: QuestionBlock, lang: string, material: MaterialContext): string {
|
|
161
|
+
const data = questionData(block, lang);
|
|
162
|
+
const { question } = data;
|
|
163
|
+
return [
|
|
164
|
+
`<div class="explainer-exercise explainer-question" id="${block.id}">`,
|
|
165
|
+
`<p class="explainer-exercise-label">${plain(message(lang, "exerciseLabel"))}</p>`,
|
|
166
|
+
renderQuestion(question, {
|
|
167
|
+
lang,
|
|
168
|
+
format: renderWithFormulas,
|
|
169
|
+
...(question.material === undefined ? {} : { materialHtml: renderMaterial(question.material, material) }),
|
|
170
|
+
}),
|
|
171
|
+
`<details class="explainer-solution"><summary>${plain(questionMessagesIn(lang).solution)}</summary>`,
|
|
172
|
+
renderSolution(question, { lang, format: renderWithFormulas }),
|
|
173
|
+
`</details>`,
|
|
174
|
+
`<script type="application/json" class="explainer-exercise-data">${json(data)}</script>`,
|
|
175
|
+
`</div>`,
|
|
176
|
+
].join("\n");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** What the browser reads from a generated exercise in `lang`: the exercise, its templates, data files and seeds. */
|
|
180
|
+
function generatedData(
|
|
181
|
+
spec: Generated,
|
|
182
|
+
lang: string,
|
|
183
|
+
all: Readonly<Record<string, readonly DataRecord[]>>,
|
|
184
|
+
resolve: (href: string) => string,
|
|
185
|
+
): Extract<ExerciseData, { kind: "generated" }> {
|
|
186
|
+
const data = dataOf(spec, all);
|
|
187
|
+
// the examples are test cases for the build: not shipped
|
|
188
|
+
const { examples: _examples, ...shipped } = spec;
|
|
189
|
+
return {
|
|
190
|
+
kind: "generated",
|
|
191
|
+
lang,
|
|
192
|
+
spec: shipped,
|
|
193
|
+
templates: templatesOf(spec, [lang], resolve),
|
|
194
|
+
data,
|
|
195
|
+
seeds: taskSeeds(spec, data),
|
|
196
|
+
texts: {
|
|
197
|
+
newTask: message(lang, "newTask"),
|
|
198
|
+
practice: spec.practice === undefined ? message(lang, "practice") : inLanguage(spec.practice, lang),
|
|
199
|
+
hint: message(lang, "hint"),
|
|
200
|
+
solution: questionMessagesIn(lang).solution,
|
|
201
|
+
// with {n} kept for the client module
|
|
202
|
+
taskNumber: message(lang, "taskNumber"),
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** What the browser reads from an open task in `lang`; links and sources through `resolve`. */
|
|
208
|
+
export function openTaskData(block: OpenTask, lang: string, resolve: (href: string) => string): OpenTaskData {
|
|
209
|
+
return {
|
|
210
|
+
kind: "open",
|
|
211
|
+
lang,
|
|
212
|
+
id: block.id,
|
|
213
|
+
task: renderMarkdown(inLanguage(block.task, lang), resolve),
|
|
214
|
+
answer: block.answer ?? ["text", "photo"],
|
|
215
|
+
points: block.points.map((point) => ({
|
|
216
|
+
text: inLanguage(point.text, lang),
|
|
217
|
+
kind: point.kind ?? "content",
|
|
218
|
+
...(point.source === undefined ? {} : { href: resolve(point.source) }),
|
|
219
|
+
})),
|
|
220
|
+
solution: renderMarkdown(inLanguage(block.solution, lang), resolve),
|
|
221
|
+
...(block.hint === undefined ? {} : { hint: renderMarkdown(inLanguage(block.hint, lang), resolve) }),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** What the browser reads from an exercise in `lang` (script.explainer-exercise-data). */
|
|
226
|
+
export function exerciseData(
|
|
227
|
+
block: Exercise,
|
|
228
|
+
lang: string,
|
|
229
|
+
all: Readonly<Record<string, readonly DataRecord[]>>,
|
|
230
|
+
resolve: (href: string) => string,
|
|
231
|
+
): ExerciseData {
|
|
232
|
+
if (block.type === "generated") return generatedData(block, lang, all, resolve);
|
|
233
|
+
if (block.type === "open-task") return openTaskData(block, lang, resolve);
|
|
234
|
+
return questionData(block, lang);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* An open task in `lang`: the task, the points of a good answer with a link to each one's source, and the model answer
|
|
239
|
+
* under "Lösung", which is all the page shows without JavaScript; with JavaScript the learner answers first (explainer.ts).
|
|
240
|
+
*/
|
|
241
|
+
function openTaskExercise(block: OpenTask, lang: string, resolve: (href: string) => string, storage: string): string {
|
|
242
|
+
const data = openTaskData(block, lang, resolve);
|
|
243
|
+
const points = data.points.map((point) => {
|
|
244
|
+
const where =
|
|
245
|
+
point.href === undefined
|
|
246
|
+
? ` <span class="explainer-open-kind">(${plain(message(lang, "openTaskForm"))})</span>`
|
|
247
|
+
: ` <a class="explainer-open-source" href="${plain(point.href)}">${plain(message(lang, "openTaskSource"))}</a>`;
|
|
248
|
+
return `<li>${plain(point.text)}${where}</li>`;
|
|
249
|
+
});
|
|
250
|
+
return [
|
|
251
|
+
`<div class="explainer-exercise explainer-open-task" id="${block.id}" data-storage="${plain(storage)}">`,
|
|
252
|
+
`<p class="explainer-exercise-label">${plain(message(lang, "openTaskLabel"))}</p>`,
|
|
253
|
+
`<div class="explainer-open-task-text">${data.task}</div>`,
|
|
254
|
+
data.hint === undefined
|
|
255
|
+
? ""
|
|
256
|
+
: `<details class="explainer-hint"><summary>${plain(message(lang, "hint"))}</summary>${data.hint}</details>`,
|
|
257
|
+
`<div class="explainer-open-live" hidden></div>`,
|
|
258
|
+
`<div class="explainer-open-points">`,
|
|
259
|
+
`<p class="explainer-open-points-intro">${plain(message(lang, "openTaskPoints"))}</p>`,
|
|
260
|
+
`<ul>${points.join("")}</ul>`,
|
|
261
|
+
`</div>`,
|
|
262
|
+
`<details class="explainer-solution"><summary>${plain(questionMessagesIn(lang).solution)}</summary>`,
|
|
263
|
+
`<div class="explainer-open-solution">${data.solution}</div>`,
|
|
264
|
+
`</details>`,
|
|
265
|
+
`<script type="application/json" class="explainer-exercise-data">${json(data)}</script>`,
|
|
266
|
+
`</div>`,
|
|
267
|
+
]
|
|
268
|
+
.filter((part) => part !== "")
|
|
269
|
+
.join("\n");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function generatedExercise(
|
|
273
|
+
spec: Generated,
|
|
274
|
+
lang: string,
|
|
275
|
+
all: Readonly<Record<string, readonly DataRecord[]>>,
|
|
276
|
+
resolve: (href: string) => string,
|
|
277
|
+
): string {
|
|
278
|
+
const templates = templatesOf(spec, [lang], resolve);
|
|
279
|
+
const data = dataOf(spec, all);
|
|
280
|
+
const solution = questionMessagesIn(lang).solution;
|
|
281
|
+
const worked = workedTasks(spec, data, templates, lang).map((item) =>
|
|
282
|
+
[
|
|
283
|
+
`<li class="explainer-worked-task">`,
|
|
284
|
+
`<p class="explainer-task-number">${plain(message(lang, "taskNumber", { n: String(item.number) }))}</p>`,
|
|
285
|
+
`<p class="explainer-task">${item.task}</p>`,
|
|
286
|
+
item.hint === undefined
|
|
287
|
+
? ""
|
|
288
|
+
: `<details class="explainer-hint"><summary>${plain(message(lang, "hint"))}</summary><p>${item.hint}</p></details>`,
|
|
289
|
+
`<details class="explainer-solution"><summary>${plain(solution)}</summary>`,
|
|
290
|
+
item.steps.length === 0
|
|
291
|
+
? ""
|
|
292
|
+
: `<ol class="explainer-steps">${item.steps.map((step) => `<li>${step}</li>`).join("")}</ol>`,
|
|
293
|
+
`<p><strong>${plain(questionMessagesIn(lang).rightAnswer)}</strong> ${item.answer}</p>`,
|
|
294
|
+
`</details>`,
|
|
295
|
+
`</li>`,
|
|
296
|
+
]
|
|
297
|
+
.filter((part) => part !== "")
|
|
298
|
+
.join(""),
|
|
299
|
+
);
|
|
300
|
+
const exerciseData = generatedData(spec, lang, all, resolve);
|
|
301
|
+
return [
|
|
302
|
+
`<div class="explainer-exercise explainer-generated" id="${spec.id}">`,
|
|
303
|
+
`<p class="explainer-exercise-label">${plain(message(lang, "exerciseLabel"))}</p>`,
|
|
304
|
+
`<div class="explainer-live" hidden></div>`,
|
|
305
|
+
`<p class="explainer-worked-intro">${plain(message(lang, "workedTasks"))}</p>`,
|
|
306
|
+
`<ol class="explainer-worked">${worked.join("\n")}</ol>`,
|
|
307
|
+
`<script type="application/json" class="explainer-exercise-data">${json(exerciseData)}</script>`,
|
|
308
|
+
`</div>`,
|
|
309
|
+
].join("\n");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** An exercise of a page in `lang`; an open task keeps its answer while the tab is open under the app's `storage`. */
|
|
313
|
+
export function renderExercise(
|
|
314
|
+
block: Exercise,
|
|
315
|
+
lang: string,
|
|
316
|
+
data: Readonly<Record<string, readonly DataRecord[]>>,
|
|
317
|
+
resolve: (href: string) => string,
|
|
318
|
+
material: MaterialContext,
|
|
319
|
+
storage = "explainer",
|
|
320
|
+
): string {
|
|
321
|
+
if (block.type === "generated") return generatedExercise(block, lang, data, resolve);
|
|
322
|
+
if (block.type === "open-task") return openTaskExercise(block, lang, resolve, storage);
|
|
323
|
+
return questionExercise(block, lang, material);
|
|
324
|
+
}
|