@lernapps/app-templates 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -11
- package/dist/{assessment-Dy359O1O.mjs → assessment-DaKsXUdj.mjs} +204 -16
- package/dist/{blocks-CxtCPAa5.mjs → blocks-D4SSGKuL.mjs} +2 -2
- package/dist/blocks.mjs +2 -2
- package/dist/{check-CteIFN96.mjs → check-BxZOZPic.mjs} +58 -4
- package/dist/{content-DO6oEJxj.mjs → content-CAL0Nz1a.mjs} +1 -1
- package/dist/explainer-e2e.mjs +36 -5
- package/dist/explainer-plugin.mjs +57 -13
- package/dist/explainer.mjs +2 -2
- package/dist/interactive-e2e.mjs +1 -1
- package/dist/interactive-plugin.mjs +2 -2
- package/dist/{load-RosvaPZf.mjs → load--ovXqbNE.mjs} +286 -33
- package/dist/questions.mjs +1 -1
- package/dist/quiz-e2e.mjs +1 -1
- package/dist/quiz-plugin.mjs +3 -3
- package/dist/{render-D5kwkNt1.mjs → render-D3LsJY04.mjs} +2 -2
- package/dist/{render-DqHOJyHK.mjs → render-mYtzN1DV.mjs} +1 -1
- package/dist/{result-D_mmCVwN.mjs → result-DphIWHDF.mjs} +1 -1
- package/dist/{validate-Bxy9NfB8.mjs → validate-CfFykN0M.mjs} +1 -1
- package/dist/{validate-BDwL7Su3.mjs → validate-j3GjVhNF.mjs} +2 -2
- package/explainer/README.md +7 -3
- package/explainer/docs/arc42/05-explainer-building-blocks.arc42.md +26 -8
- package/explainer/docs/arc42/09-explainer-decisions.arc42.md +42 -0
- package/explainer/package.json +1 -1
- package/explainer/src/app.yaml +3 -1
- package/explainer/src/prozent/chapter.yaml +1 -1
- package/explainer/src/prozent/grundwert.yaml +2 -1
- package/explainer/src/prozent/prozentsatz.yaml +2 -1
- package/explainer/src/prozent/prozentwert.yaml +2 -1
- package/interactive/package.json +1 -1
- package/package.json +3 -4
- package/quiz/package.json +1 -1
- package/runtime/explainer/assessment.ts +4 -2
- package/runtime/explainer/check-page.ts +6 -2
- package/runtime/explainer/content.ts +20 -1
- package/runtime/explainer/e2e.ts +40 -0
- package/runtime/explainer/exercises.ts +71 -6
- package/runtime/explainer/explainer.css +21 -16
- package/runtime/explainer/explainer.v1.schema.json +2 -2
- package/runtime/explainer/explainer.v2.schema.json +152 -0
- package/runtime/explainer/expression.ts +127 -1
- package/runtime/explainer/generated/texts.ts +98 -9
- package/runtime/explainer/generated/types.ts +8 -0
- package/runtime/explainer/load.ts +70 -16
- package/runtime/explainer/messages/de.json +5 -1
- package/runtime/explainer/messages/en.json +5 -1
- package/runtime/explainer/outline.ts +1 -1
- package/runtime/explainer/page-list.ts +38 -0
- package/runtime/explainer/plugin.ts +13 -4
- package/runtime/explainer/render.ts +26 -2
- package/runtime/explainer/schema.ts +92 -9
- package/runtime/explainer/tutorpage.ts +9 -1
- package/runtime/shared/blocks/blocks.css +18 -0
- package/runtime/shared/blocks/blocks.v1.schema.json +1 -1
- package/runtime/shared/text/formula.ts +61 -1
- package/scaffold/explainer/src/app.yaml +5 -1
- package/scaffold/explainer/src/thema/chapter.yaml +1 -1
- package/scaffold/explainer/src/thema/einstieg.yaml +1 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { a as isLanguage, c as textLanguages, i as inLanguage, o as languageListProblems, r as LANGUAGES } from "./html-mffkHGHh.mjs";
|
|
2
|
-
import { n as renderMaterial } from "./blocks-
|
|
3
|
-
import { a as inspect, c as renderMarkdown, i as formulaProblems, l as renderWithFormulas, o as plain, r as FORMULA, s as renderInline, u as renderFormula } from "./check-
|
|
4
|
-
import { n as checkMaterial } from "./validate-
|
|
2
|
+
import { n as renderMaterial } from "./blocks-D4SSGKuL.mjs";
|
|
3
|
+
import { a as inspect, c as renderMarkdown, i as formulaProblems, l as renderWithFormulas, o as plain, r as FORMULA, s as renderInline, u as renderFormula } from "./check-BxZOZPic.mjs";
|
|
4
|
+
import { n as checkMaterial } from "./validate-CfFykN0M.mjs";
|
|
5
5
|
import { _ as questionIn, a as renderQuestion, b as rightAnswer, i as questionMessagesIn, n as answerText, o as renderSolution, u as isCorrect, v as questionTexts } from "./render-GS1Htlu8.mjs";
|
|
6
|
-
import { _ as verdict, g as schemasById, h as knownProperties, i as addressProblem, m as question_v1_schema_default, s as questionDetails, t as QUESTION_BRANCH, u as questionSchemas } from "./validate-
|
|
6
|
+
import { _ as verdict, g as schemasById, h as knownProperties, i as addressProblem, m as question_v1_schema_default, s as questionDetails, t as QUESTION_BRANCH, u as questionSchemas } from "./validate-j3GjVhNF.mjs";
|
|
7
7
|
import { t as appName } from "./appname-Bh23cUuz.mjs";
|
|
8
8
|
import { r as languageRoot } from "./pages-9RaaomoO.mjs";
|
|
9
|
-
import { $ as
|
|
9
|
+
import { $ as writtenTemplates, A as items, B as chemPlaceholdersIn, D as compiled, E as StarvedError, H as hintOf, I as pairValues, J as taskText, L as questionFor, M as answerFor, O as generate, P as exampleGiving, Q as unusedVariables, R as taskKey, T as message$1, U as label, V as fill, X as templatesIn, Y as templateProblems, Z as tokenize, a as assessments, at as variables, d as parseRef, et as kindOf, g as isExercise, h as allBlocks, it as same, j as values, k as holds, nt as evaluate, o as drawsTasks, ot as Fraction, q as stepsOf, rt as parse, s as exerciseOf, t as ASSESSMENT, tt as ExpressionError, v as termId, z as answerLabel } from "./assessment-DaKsXUdj.mjs";
|
|
10
10
|
import { a as RESERVED_PARAMS, l as distinctSeeds, o as SEED_TRIES } from "./tasks-DADUYy52.mjs";
|
|
11
11
|
import { a as parse$1, i as nodes, o as readSfc, r as line, s as sfc, t as codeChecker } from "./vue-code-BkJjW_I_.mjs";
|
|
12
|
-
import { a as lineOf, c as schemaProblems, i as isObject, n as Texts, o as pathText, r as get, s as readYaml, t as Problems } from "./content-
|
|
12
|
+
import { a as lineOf, c as schemaProblems, i as isObject, n as Texts, o as pathText, r as get, s as readYaml, t as Problems } from "./content-CAL0Nz1a.mjs";
|
|
13
13
|
import "node:module";
|
|
14
14
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
15
15
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
@@ -14738,16 +14738,67 @@ function formulaAlternative(text, lang) {
|
|
|
14738
14738
|
return formulas.map((formula) => speakFormula(formula[1] ?? "", lang)).join(" ");
|
|
14739
14739
|
}
|
|
14740
14740
|
// runtime/explainer/exercises.ts
|
|
14741
|
-
/**
|
|
14742
|
-
|
|
14741
|
+
/** A value of a placeholder in \ce{{{…}}} whose formula does not parse; the check reports its message as it is. */
|
|
14742
|
+
var ChemError = class extends Error {};
|
|
14743
|
+
/**
|
|
14744
|
+
* The chemical formula of each value of the placeholders in \ce{{{…}}} of an exercise's templates in `lang`, as
|
|
14745
|
+
* MathML without <math>, by the value as the learner reads it: each value of the list the placeholder's root is
|
|
14746
|
+
* drawn from (oneOf, or pick: 1 from a list or a data file), set by mhchem as \ce{value}. Throws a ChemError for a
|
|
14747
|
+
* value that does not parse.
|
|
14748
|
+
*/
|
|
14749
|
+
function chemFormulas(spec, data, lang) {
|
|
14750
|
+
const formulas = {};
|
|
14751
|
+
const paths = new Set(writtenTemplates(spec).filter((written) => written.lang === void 0 || written.lang === lang).flatMap((written) => chemPlaceholdersIn(written.text)));
|
|
14752
|
+
for (const path of paths) {
|
|
14753
|
+
const root = path.split(".")[0] ?? "";
|
|
14754
|
+
const arbitrary = spec.given[root];
|
|
14755
|
+
if (arbitrary === void 0) continue;
|
|
14756
|
+
const source = arbitrary.oneOf ?? (arbitrary.pick === 1 ? arbitrary.from : void 0);
|
|
14757
|
+
if (source === void 0) continue;
|
|
14758
|
+
let candidates;
|
|
14759
|
+
try {
|
|
14760
|
+
candidates = items(source, data);
|
|
14761
|
+
} catch {
|
|
14762
|
+
continue;
|
|
14763
|
+
}
|
|
14764
|
+
for (const candidate of candidates) {
|
|
14765
|
+
let text;
|
|
14766
|
+
try {
|
|
14767
|
+
text = label(evaluate(parse(path), { [root]: candidate }), lang);
|
|
14768
|
+
} catch {
|
|
14769
|
+
continue;
|
|
14770
|
+
}
|
|
14771
|
+
if (text in formulas) continue;
|
|
14772
|
+
try {
|
|
14773
|
+
let depth = 0;
|
|
14774
|
+
for (const char of text) if (char === "{") depth++;
|
|
14775
|
+
else if (char === "}" && --depth < 0) break;
|
|
14776
|
+
if (depth !== 0) throw new Error("its braces are not balanced");
|
|
14777
|
+
const math = renderFormula(`\\ce{${text}}`, false);
|
|
14778
|
+
formulas[text] = /^<math[^>]*>([\s\S]*)<\/math>$/.exec(math)?.[1] ?? math;
|
|
14779
|
+
} catch (error) {
|
|
14780
|
+
const reason = (error instanceof Error ? error.message : String(error)).split("\n")[0]?.trim() ?? "";
|
|
14781
|
+
throw new ChemError(`\\ce{{{${path}}}}: the value "${text}" is no chemical formula mhchem can set (${reason}); write it in mhchem's notation, e.g. 2H2 + O2 -> 2H2O`);
|
|
14782
|
+
}
|
|
14783
|
+
}
|
|
14784
|
+
}
|
|
14785
|
+
return formulas;
|
|
14786
|
+
}
|
|
14787
|
+
/**
|
|
14788
|
+
* The templates of a generated exercise in each of `languages`, with the formulas of the values in \ce{{{…}}} drawn
|
|
14789
|
+
* from `data`; links inside them through `resolve`.
|
|
14790
|
+
*/
|
|
14791
|
+
function templatesOf(spec, languages, data, resolve = (href) => href) {
|
|
14743
14792
|
const templates = {};
|
|
14744
14793
|
for (const lang of languages) {
|
|
14745
14794
|
const inline = (text) => renderInline(tokenize(inLanguage(text, lang)), resolve);
|
|
14795
|
+
const chem = chemFormulas(spec, data, lang);
|
|
14746
14796
|
templates[lang] = {
|
|
14747
14797
|
task: inline(spec.task.text),
|
|
14748
14798
|
steps: (spec.steps ?? []).map(inline),
|
|
14749
14799
|
hint: spec.hint === void 0 ? void 0 : inline(spec.hint),
|
|
14750
|
-
messages: (spec.mistakes ?? []).map((mistake) => tokenize(inLanguage(mistake.message, lang)))
|
|
14800
|
+
messages: (spec.mistakes ?? []).map((mistake) => tokenize(inLanguage(mistake.message, lang))),
|
|
14801
|
+
...Object.keys(chem).length === 0 ? {} : { chem }
|
|
14751
14802
|
};
|
|
14752
14803
|
}
|
|
14753
14804
|
return templates;
|
|
@@ -14765,7 +14816,7 @@ const seedCache = /* @__PURE__ */ new WeakMap();
|
|
|
14765
14816
|
function taskSeeds(spec, data) {
|
|
14766
14817
|
let seeds = seedCache.get(spec);
|
|
14767
14818
|
if (seeds === void 0) {
|
|
14768
|
-
const templates = templatesOf(spec, ["de"]);
|
|
14819
|
+
const templates = templatesOf(spec, ["de"], data);
|
|
14769
14820
|
seeds = distinctSeeds((seed) => {
|
|
14770
14821
|
try {
|
|
14771
14822
|
return taskKey(spec, generate(spec, seed, data), "de", templates);
|
|
@@ -14844,7 +14895,7 @@ function generatedData(spec, lang, all, resolve) {
|
|
|
14844
14895
|
kind: "generated",
|
|
14845
14896
|
lang,
|
|
14846
14897
|
spec: shipped,
|
|
14847
|
-
templates: templatesOf(spec, [lang], resolve),
|
|
14898
|
+
templates: templatesOf(spec, [lang], data, resolve),
|
|
14848
14899
|
data,
|
|
14849
14900
|
seeds: taskSeeds(spec, data),
|
|
14850
14901
|
texts: {
|
|
@@ -14907,8 +14958,8 @@ function openTaskExercise(block, lang, resolve, storage) {
|
|
|
14907
14958
|
].filter((part) => part !== "").join("\n");
|
|
14908
14959
|
}
|
|
14909
14960
|
function generatedExercise(spec, lang, all, resolve) {
|
|
14910
|
-
const templates = templatesOf(spec, [lang], resolve);
|
|
14911
14961
|
const data = dataOf(spec, all);
|
|
14962
|
+
const templates = templatesOf(spec, [lang], data, resolve);
|
|
14912
14963
|
const solution = questionMessagesIn(lang).solution;
|
|
14913
14964
|
const worked = workedTasks(spec, data, templates, lang).map((item) => [
|
|
14914
14965
|
`<li class="explainer-worked-task">`,
|
|
@@ -15736,7 +15787,7 @@ var explainer_v1_schema_default = {
|
|
|
15736
15787
|
"type": "object"
|
|
15737
15788
|
},
|
|
15738
15789
|
"expression": {
|
|
15739
|
-
"description": "An expression of the expression language, e.g. \"G * p / 100\": numbers (exact fractions), + - * /, parentheses, == != < <= > >=, and, or, not, strings in quotes, record fields (e.year), list index (xs[0]), lists ([1, 2]), and the functions round(x, n), decimals, map(xs, field), sortBy(xs, field), reverse. It may use the variables of given.",
|
|
15790
|
+
"description": "An expression of the expression language, e.g. \"G * p / 100\": numbers (exact fractions), + - * /, parentheses, == != < <= > >=, and, or, not, strings in quotes, record fields (e.year), list index (xs[0]), lists ([1, 2]), and the functions round(x, n), decimals, map(xs, field), sortBy(xs, field), reverse, sqrt(x), pow(x, n), sin(x), cos(x), tan(x), asin(x), acos(x), atan(x). Angles are in degrees: sin(30) is 1/2. Round a result that is not rational (sqrt(2)) with round(x, n). It may use the variables of given.",
|
|
15740
15791
|
"type": "string",
|
|
15741
15792
|
"minLength": 1,
|
|
15742
15793
|
"pattern": "\\S"
|
|
@@ -15854,7 +15905,7 @@ var explainer_v1_schema_default = {
|
|
|
15854
15905
|
}
|
|
15855
15906
|
},
|
|
15856
15907
|
"generated": {
|
|
15857
|
-
"description": "An exercise drawn from a seed, property-based: given draws the values, where filters them, task is the text with placeholders ({{G}}, {{e.name}}; {{answer}} in the steps), answer is an expression with its comparator, mistakes are typical wrong answers each with a message saying what went wrong, hint and steps help. A placeholder is always two braces around a declared name or its field, nothing else inside, in prose and in formulas alike ($\\frac{{{W}}}{{{G}}}$); an index is no field, so {{opts[0].x}} is no placeholder and the build reports it: draw the record on its own (given: { e: { pick: 1, from: { data: name } } }) and write {{e.x}}; one brace is never a placeholder, so in a formula G and {G} are the letter. The build checks that every placeholder names a variable of given (or answer), that no text has double braces around anything but a name or its field, that no prose has a placeholder with one brace or spaces inside, and that every variable of given is used in a placeholder or in an expression of the answer or mistakes; then it checks it over 200 fixed seeds: where holds in at least 1 of 10 draws; the answer is defined, in its format and passes its comparator; no mistake equals it; the task is the same each time and keeps no placeholder. Its tasks are numbered: the build walks the seeds 1, 2, 3, … and numbers only the distinct tasks, Aufgabe Nr. 1 to N (N at most 67; fewer when the exercise has fewer distinct tasks, noted in the build output). The number is what the page shows, what a learner names to a tutor, and what the address carries: ?seed=<n>#<id> opens task number n (a number above N wraps around). Without JavaScript the page shows the tasks 1, 2 and 3 as worked tasks with their solution; with JavaScript a live task of a random number, Neue Aufgabe, which moves to the next number (after N back to 1) and writes it into the address, and the deep link: a wrong answer gets the message of the mistake it matches (else a line to look again, with the hint above) and may be tried again; the solution and the steps show after the right answer or Lösung zeigen.",
|
|
15908
|
+
"description": "An exercise drawn from a seed, property-based: given draws the values, where filters them, task is the text with placeholders ({{G}}, {{e.name}}; {{answer}} in the steps), answer is an expression with its comparator, mistakes are typical wrong answers each with a message saying what went wrong, hint and steps help. A placeholder is always two braces around a declared name or its field, nothing else inside, in prose and in formulas alike ($\\frac{{{W}}}{{{G}}}$); an index is no field, so {{opts[0].x}} is no placeholder and the build reports it: draw the record on its own (given: { e: { pick: 1, from: { data: name } } }) and write {{e.x}}; one brace is never a placeholder, so in a formula G and {G} are the letter. A chemical formula from a record is $\\ce{{{r.gl}}}$: the whole formula in one field (gl: \"2H2 + O2 -> 2H2O\"), r drawn with pick: 1 or oneOf; a placeholder inside a longer \\ce or in \\pu is not possible. The build checks that every placeholder names a variable of given (or answer), that no text has double braces around anything but a name or its field, that no prose has a placeholder with one brace or spaces inside, and that every variable of given is used in a placeholder or in an expression of the answer or mistakes; then it checks it over 200 fixed seeds: where holds in at least 1 of 10 draws; the answer is defined, in its format and passes its comparator; no mistake equals it; the task is the same each time and keeps no placeholder. Its tasks are numbered: the build walks the seeds 1, 2, 3, … and numbers only the distinct tasks, Aufgabe Nr. 1 to N (N at most 67; fewer when the exercise has fewer distinct tasks, noted in the build output). The number is what the page shows, what a learner names to a tutor, and what the address carries: ?seed=<n>#<id> opens task number n (a number above N wraps around). Without JavaScript the page shows the tasks 1, 2 and 3 as worked tasks with their solution; with JavaScript a live task of a random number, Neue Aufgabe, which moves to the next number (after N back to 1) and writes it into the address, and the deep link: a wrong answer gets the message of the mistake it matches (else a line to look again, with the hint above) and may be tried again; the solution and the steps show after the right answer or Lösung zeigen.",
|
|
15858
15909
|
"required": [
|
|
15859
15910
|
"id",
|
|
15860
15911
|
"given",
|
|
@@ -16096,10 +16147,125 @@ var explainer_v1_schema_default = {
|
|
|
16096
16147
|
}
|
|
16097
16148
|
}
|
|
16098
16149
|
};
|
|
16150
|
+
// runtime/explainer/explainer.v2.schema.json
|
|
16151
|
+
var explainer_v2_schema_default = {
|
|
16152
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
16153
|
+
$id: "urn:lernapps:schema:explainer.v2",
|
|
16154
|
+
title: "Content of a lernapps explainer, format 2",
|
|
16155
|
+
description: "Format 2 of an explainer's content, as YAML in src/: src/app.yaml, src/<chapter>/chapter.yaml, src/<chapter>/<page>.yaml and src/data/<name>.yaml. It is format 1 (explainer.v1.schema.json) plus the app's place on the learning map: format: 2 and strand in src/app.yaml, and on each page the competencies it practises, or recap on a page that repeats a competency from elsewhere. An app.yaml without format is format 1.",
|
|
16156
|
+
allOf: [
|
|
16157
|
+
{
|
|
16158
|
+
"if": {
|
|
16159
|
+
"type": "object",
|
|
16160
|
+
"required": ["chapters"],
|
|
16161
|
+
"properties": { "chapters": {} }
|
|
16162
|
+
},
|
|
16163
|
+
"then": { "$ref": "#/$defs/app" }
|
|
16164
|
+
},
|
|
16165
|
+
{
|
|
16166
|
+
"if": {
|
|
16167
|
+
"type": "object",
|
|
16168
|
+
"required": ["pages"],
|
|
16169
|
+
"properties": { "pages": {} }
|
|
16170
|
+
},
|
|
16171
|
+
"then": { "$ref": "#/$defs/chapter" }
|
|
16172
|
+
},
|
|
16173
|
+
{
|
|
16174
|
+
"if": {
|
|
16175
|
+
"type": "object",
|
|
16176
|
+
"required": ["blocks"],
|
|
16177
|
+
"properties": { "blocks": {} }
|
|
16178
|
+
},
|
|
16179
|
+
"then": { "$ref": "#/$defs/page" }
|
|
16180
|
+
},
|
|
16181
|
+
{
|
|
16182
|
+
"if": { "type": "array" },
|
|
16183
|
+
"then": { "$ref": "#/$defs/data" }
|
|
16184
|
+
}
|
|
16185
|
+
],
|
|
16186
|
+
$defs: {
|
|
16187
|
+
"id": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/id" },
|
|
16188
|
+
"strand": {
|
|
16189
|
+
"description": "The one strand (Strang) of the learning map the app belongs to, <subject>/<strand>, e.g. mathematik/stochastik; or none if it belongs to none (then it is not on the map).",
|
|
16190
|
+
"type": "string",
|
|
16191
|
+
"pattern": "^(none|[a-z0-9]+(-[a-z0-9]+)*/[a-z0-9]+(-[a-z0-9]+)*)$"
|
|
16192
|
+
},
|
|
16193
|
+
"competency": {
|
|
16194
|
+
"description": "A competency of the learning map, <subject>/<competency>, e.g. physik/gewichtskraft-berechnen.",
|
|
16195
|
+
"type": "string",
|
|
16196
|
+
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*/[a-z0-9]+(-[a-z0-9]+)*$"
|
|
16197
|
+
},
|
|
16198
|
+
"app": {
|
|
16199
|
+
"description": "src/app.yaml in format 2: format: 2 and strand, everything else as in format 1.",
|
|
16200
|
+
"type": "object",
|
|
16201
|
+
"additionalProperties": false,
|
|
16202
|
+
"required": [
|
|
16203
|
+
"format",
|
|
16204
|
+
"strand",
|
|
16205
|
+
"title",
|
|
16206
|
+
"intro",
|
|
16207
|
+
"languages",
|
|
16208
|
+
"chapters"
|
|
16209
|
+
],
|
|
16210
|
+
"properties": {
|
|
16211
|
+
"format": {
|
|
16212
|
+
"description": "Always 2. Without it, the content is format 1 (explainer.v1.schema.json).",
|
|
16213
|
+
"const": 2
|
|
16214
|
+
},
|
|
16215
|
+
"strand": { "$ref": "#/$defs/strand" },
|
|
16216
|
+
"title": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/app/properties/title" },
|
|
16217
|
+
"intro": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/app/properties/intro" },
|
|
16218
|
+
"languages": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/app/properties/languages" },
|
|
16219
|
+
"chapters": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/app/properties/chapters" },
|
|
16220
|
+
"sources": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/app/properties/sources" },
|
|
16221
|
+
"tutor": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/app/properties/tutor" },
|
|
16222
|
+
"scope": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/app/properties/scope" }
|
|
16223
|
+
}
|
|
16224
|
+
},
|
|
16225
|
+
"chapter": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/chapter" },
|
|
16226
|
+
"page": {
|
|
16227
|
+
"description": "src/<chapter>/<page>.yaml in format 2: a page as in format 1, plus competencies, or recap on a recap page.",
|
|
16228
|
+
"type": "object",
|
|
16229
|
+
"additionalProperties": false,
|
|
16230
|
+
"required": ["title", "blocks"],
|
|
16231
|
+
"properties": {
|
|
16232
|
+
"title": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/page/properties/title" },
|
|
16233
|
+
"description": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/page/properties/description" },
|
|
16234
|
+
"competencies": {
|
|
16235
|
+
"description": "The competencies (Bausteine) the page practises, ids of the subject of the app's strand; as a rule one. Required when the app has a strand; leave it out with strand: none and on a recap page. The catalog checks the ids when it lists the app.",
|
|
16236
|
+
"type": "array",
|
|
16237
|
+
"minItems": 1,
|
|
16238
|
+
"uniqueItems": true,
|
|
16239
|
+
"items": { "$ref": "#/$defs/id" }
|
|
16240
|
+
},
|
|
16241
|
+
"recap": {
|
|
16242
|
+
"description": "Makes the page a recap page: it repeats a competency that the app's competencies require but the app does not practise, e.g. physik/gewichtskraft-berechnen. Use it instead of competencies, also with strand: none. A recap page has descriptive blocks only, no exercise; the link to the competency on the learning map is added for you.",
|
|
16243
|
+
"$ref": "#/$defs/competency"
|
|
16244
|
+
},
|
|
16245
|
+
"blocks": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/page/properties/blocks" },
|
|
16246
|
+
"scope": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/page/properties/scope" }
|
|
16247
|
+
},
|
|
16248
|
+
"not": {
|
|
16249
|
+
"required": ["competencies", "recap"],
|
|
16250
|
+
"properties": {
|
|
16251
|
+
"competencies": {},
|
|
16252
|
+
"recap": {}
|
|
16253
|
+
}
|
|
16254
|
+
}
|
|
16255
|
+
},
|
|
16256
|
+
"data": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/data" },
|
|
16257
|
+
"event": { "$ref": "urn:lernapps:schema:explainer.v1#/$defs/event" }
|
|
16258
|
+
}
|
|
16259
|
+
};
|
|
16099
16260
|
// runtime/explainer/schema.ts
|
|
16100
16261
|
const SCHEMA_ID = explainer_v1_schema_default.$id;
|
|
16101
|
-
|
|
16102
|
-
|
|
16262
|
+
const SCHEMA_ID_V2 = explainer_v2_schema_default.$id;
|
|
16263
|
+
/** Where an app finds the schema of its content, by format, for the message that names it. */
|
|
16264
|
+
const SCHEMA_FILES = {
|
|
16265
|
+
1: "node_modules/@lernapps/app-templates/runtime/explainer/explainer.v1.schema.json",
|
|
16266
|
+
2: "node_modules/@lernapps/app-templates/runtime/explainer/explainer.v2.schema.json"
|
|
16267
|
+
};
|
|
16268
|
+
SCHEMA_FILES[1];
|
|
16103
16269
|
const ajv = new Ajv2020({
|
|
16104
16270
|
allErrors: true,
|
|
16105
16271
|
strict: true,
|
|
@@ -16107,9 +16273,10 @@ const ajv = new Ajv2020({
|
|
|
16107
16273
|
});
|
|
16108
16274
|
for (const each of questionSchemas) ajv.addSchema(each);
|
|
16109
16275
|
ajv.addSchema(explainer_v1_schema_default);
|
|
16110
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16276
|
+
ajv.addSchema(explainer_v2_schema_default);
|
|
16277
|
+
const validator = (name, id = SCHEMA_ID) => {
|
|
16278
|
+
const validate = ajv.getSchema(`${id}#/$defs/${name}`);
|
|
16279
|
+
if (validate === void 0) throw new Error(`the schema ${id} has no $defs/${name}`);
|
|
16113
16280
|
return validate;
|
|
16114
16281
|
};
|
|
16115
16282
|
const validators = {
|
|
@@ -16119,6 +16286,23 @@ const validators = {
|
|
|
16119
16286
|
data: validator("data"),
|
|
16120
16287
|
event: validator("event")
|
|
16121
16288
|
};
|
|
16289
|
+
/** The validators of format 2: its own app and page; chapters, data files and events are those of format 1. */
|
|
16290
|
+
const validatorsV2 = {
|
|
16291
|
+
...validators,
|
|
16292
|
+
app: validator("app", SCHEMA_ID_V2),
|
|
16293
|
+
page: validator("page", SCHEMA_ID_V2)
|
|
16294
|
+
};
|
|
16295
|
+
/** The format of the app `app` (src/app.yaml as read): 2 when it names any format, which its schema then checks. */
|
|
16296
|
+
const formatOf = (app) => isObject(app) && app["format"] !== void 0 ? 2 : 1;
|
|
16297
|
+
/** The fields of format 2, which format 1 does not know. */
|
|
16298
|
+
const FORMAT_2_FIELDS = /* @__PURE__ */ new Set([
|
|
16299
|
+
"format",
|
|
16300
|
+
"strand",
|
|
16301
|
+
"competencies",
|
|
16302
|
+
"recap"
|
|
16303
|
+
]);
|
|
16304
|
+
/** The fields of format 2 on a page. */
|
|
16305
|
+
const PAGE_FIELDS = /* @__PURE__ */ new Set(["competencies", "recap"]);
|
|
16122
16306
|
const schemas = schemasById(...questionSchemas, explainer_v1_schema_default);
|
|
16123
16307
|
const BLOCK_DEFINITIONS = {
|
|
16124
16308
|
text: "textBlock",
|
|
@@ -16147,6 +16331,7 @@ const questionFields = (type) => {
|
|
|
16147
16331
|
/** What `required` names as missing, with why it is needed. */
|
|
16148
16332
|
function missing(property, object) {
|
|
16149
16333
|
const type = isObject(object) ? object["type"] : void 0;
|
|
16334
|
+
if (property === "strand") return "strand is missing; name the strand of the learning map the app stays in, <subject>/<strand> (e.g. mathematik/stochastik), or none when the app is on no strand";
|
|
16150
16335
|
if (property === "origin") return "origin is missing; a quote names who said or wrote it, where and when";
|
|
16151
16336
|
if ((property === "licence" || property === "source") && type === "quote") return `${property} is missing; a quote names its source and its licence`;
|
|
16152
16337
|
if (property === "licence" || property === "source") return `${property} is missing; a bundled image names its source and its licence`;
|
|
@@ -16163,7 +16348,35 @@ const describe = (error, path, value) => {
|
|
|
16163
16348
|
...lang === void 0 ? {} : { lang }
|
|
16164
16349
|
};
|
|
16165
16350
|
}
|
|
16351
|
+
case "not":
|
|
16352
|
+
if (path.length !== 0 || !isObject(value) || !("recap" in value) || !("competencies" in value)) return void 0;
|
|
16353
|
+
return {
|
|
16354
|
+
path: ["recap"],
|
|
16355
|
+
message: "a page has competencies or recap, never both: a page practises competencies of the app's strand, a recap page only repeats a competency the app does not practise; remove one of them"
|
|
16356
|
+
};
|
|
16357
|
+
case "const":
|
|
16358
|
+
if (path.length !== 1 || path[0] !== "format") return void 0;
|
|
16359
|
+
return { message: `${String(value)} is not a format; write format: 2, or leave format out for format 1` };
|
|
16360
|
+
case "uniqueItems":
|
|
16361
|
+
if (path.at(-1) !== "competencies") return void 0;
|
|
16362
|
+
return { message: "names a competency twice; name each competency once" };
|
|
16363
|
+
case "additionalProperties": {
|
|
16364
|
+
const property = String(error.params["additionalProperty"]);
|
|
16365
|
+
if (path.length !== 0 || !FORMAT_2_FIELDS.has(property) || !isObject(value)) return void 0;
|
|
16366
|
+
if (PAGE_FIELDS.has(property) && "blocks" in value) return {
|
|
16367
|
+
path: [property],
|
|
16368
|
+
message: `${property} is a field of format 2; add format: 2 and the app's strand to src/app.yaml, or remove it`
|
|
16369
|
+
};
|
|
16370
|
+
if (!PAGE_FIELDS.has(property) && "chapters" in value) return {
|
|
16371
|
+
path: [property],
|
|
16372
|
+
message: `${property} is a field of format 2; add format: 2 to src/app.yaml, with the app's strand`
|
|
16373
|
+
};
|
|
16374
|
+
return;
|
|
16375
|
+
}
|
|
16166
16376
|
case "pattern":
|
|
16377
|
+
if (path.length === 1 && path[0] === "strand") return { message: `${String(value)} is no strand; write <subject>/<strand> with the ids of the learning map, e.g. mathematik/stochastik, or none` };
|
|
16378
|
+
if (path.length === 1 && path[0] === "recap") return { message: `${String(value)} is no competency of the learning map; write <subject>/<competency> with the ids of the learning map, e.g. physik/gewichtskraft-berechnen` };
|
|
16379
|
+
if (path.at(-2) === "competencies") return { message: `${String(value)} is no competency id; use the competency's id in the learning map: lower-case letters, digits and hyphens, e.g. laplace` };
|
|
16167
16380
|
if (path.at(-2) === "exercises" && path[0] === "selfAssessment") return { message: `${String(value)} is no exercise's address; write /<chapter>/<page>/#<exercise>` };
|
|
16168
16381
|
if (path.at(-1) !== "youtube") return void 0;
|
|
16169
16382
|
return { message: `${String(value)} is no YouTube id; give the 11 characters after watch?v= in the video's address, e.g. rkLmLaFHodo` };
|
|
@@ -16186,8 +16399,8 @@ const describe = (error, path, value) => {
|
|
|
16186
16399
|
};
|
|
16187
16400
|
}
|
|
16188
16401
|
};
|
|
16189
|
-
function validate(yaml, kind, problems) {
|
|
16190
|
-
const check = validators[kind];
|
|
16402
|
+
function validate(yaml, kind, problems, format = 1) {
|
|
16403
|
+
const check = (format === 2 ? validatorsV2 : validators)[kind];
|
|
16191
16404
|
if (check(yaml.value)) return true;
|
|
16192
16405
|
const before = problems.lines.length;
|
|
16193
16406
|
schemaProblems(yaml, check.errors ?? [], problems, describe);
|
|
@@ -16604,8 +16817,12 @@ function checkGenerated(check, block, at) {
|
|
|
16604
16817
|
for (const [name, file] of check.dataFiles) records[name] = file.value;
|
|
16605
16818
|
let templates;
|
|
16606
16819
|
try {
|
|
16607
|
-
templates = templatesOf(block, texts.languages);
|
|
16820
|
+
templates = templatesOf(block, texts.languages, dataOf(block, records));
|
|
16608
16821
|
} catch (error) {
|
|
16822
|
+
if (error instanceof ChemError) {
|
|
16823
|
+
problems.at(yaml, at, error.message);
|
|
16824
|
+
return;
|
|
16825
|
+
}
|
|
16609
16826
|
const reason = (error instanceof Error ? error.message : String(error)).split("\n")[0] ?? "";
|
|
16610
16827
|
problems.at(yaml, at, `a formula with its placeholders filled in does not parse (${reason.trim()}); check its braces: a placeholder is {{G}}, a letter is G`);
|
|
16611
16828
|
return;
|
|
@@ -16708,11 +16925,11 @@ const IMAGE = /^[a-z0-9]+(-[a-z0-9]+)*\.(svg|png|jpg|webp)$/;
|
|
|
16708
16925
|
/** The content could not be read: one problem per line in `problems`. */
|
|
16709
16926
|
var ContentError = class extends Error {
|
|
16710
16927
|
problems;
|
|
16711
|
-
constructor(problems) {
|
|
16928
|
+
constructor(problems, format = 1) {
|
|
16712
16929
|
super([
|
|
16713
16930
|
`the explainer's content in src/ is not valid (${RULE_URL}):`,
|
|
16714
16931
|
...problems.map((problem) => ` - ${problem}`),
|
|
16715
|
-
`Fix the content as its schema says: ${
|
|
16932
|
+
`Fix the content as its schema says: ${SCHEMA_FILES[format]}`
|
|
16716
16933
|
].join("\n"));
|
|
16717
16934
|
this.problems = problems;
|
|
16718
16935
|
}
|
|
@@ -16767,6 +16984,10 @@ function checkAssessment(yaml, assessment, chapters, chapter, components, proble
|
|
|
16767
16984
|
problems.at(yaml, path, `${ref} names no page of the app; write /<chapter>/<page>/#<exercise>`);
|
|
16768
16985
|
return;
|
|
16769
16986
|
}
|
|
16987
|
+
if (page.recap !== void 0) {
|
|
16988
|
+
problems.at(yaml, path, `${ref} is on a recap page, which has no exercise and is left out of the self-assessment; choose an exercise of a page of the app's own competencies`);
|
|
16989
|
+
return;
|
|
16990
|
+
}
|
|
16770
16991
|
const exercise = exerciseOf(components, page, parts.exercise);
|
|
16771
16992
|
if (twice && exercise !== void 0 && !drawsTasks(exercise)) {
|
|
16772
16993
|
problems.at(yaml, path, `${ref} is listed twice, and a question shows the same task each time; list it once (a generated exercise or an exercise component may be listed again: each listing draws a task of its own)`);
|
|
@@ -16778,13 +16999,27 @@ function checkAssessment(yaml, assessment, chapters, chapter, components, proble
|
|
|
16778
16999
|
});
|
|
16779
17000
|
});
|
|
16780
17001
|
}
|
|
17002
|
+
/** Each block of `blocks` and the blocks inside them, with its path in the page's file. */
|
|
17003
|
+
function* blockPaths(blocks, path) {
|
|
17004
|
+
for (const [index, block] of blocks.entries()) {
|
|
17005
|
+
yield [block, [...path, index]];
|
|
17006
|
+
if (block.type === "section" || block.type === "example" || block.type === "note") yield* blockPaths(block.blocks, [
|
|
17007
|
+
...path,
|
|
17008
|
+
index,
|
|
17009
|
+
"blocks"
|
|
17010
|
+
]);
|
|
17011
|
+
}
|
|
17012
|
+
}
|
|
16781
17013
|
/** The problems of the content in `root`/src, and the content when there are none. */
|
|
16782
17014
|
function check(root, seeds = 200) {
|
|
16783
17015
|
const problems = new Problems();
|
|
16784
17016
|
const src = join(root, "src");
|
|
16785
17017
|
if (!existsSync(join(src, "app.yaml"))) {
|
|
16786
17018
|
problems.file("src/app.yaml", "is missing: the app's title, intro, languages and the order of its chapters belong there");
|
|
16787
|
-
return {
|
|
17019
|
+
return {
|
|
17020
|
+
problems: problems.lines,
|
|
17021
|
+
format: 1
|
|
17022
|
+
};
|
|
16788
17023
|
}
|
|
16789
17024
|
const chapterIds = [];
|
|
16790
17025
|
const dataFiles = /* @__PURE__ */ new Map();
|
|
@@ -16811,11 +17046,18 @@ function check(root, seeds = 200) {
|
|
|
16811
17046
|
const components = existsSync(join(src, "components")) ? readComponents(root) : void 0;
|
|
16812
17047
|
for (const problem of components?.problems ?? []) problems.add(problem);
|
|
16813
17048
|
const appYaml = readYaml(root, "src/app.yaml", problems);
|
|
16814
|
-
|
|
17049
|
+
const format = formatOf(appYaml?.value);
|
|
17050
|
+
if (appYaml === void 0 || !validate(appYaml, "app", problems, format)) return {
|
|
17051
|
+
problems: problems.lines,
|
|
17052
|
+
format
|
|
17053
|
+
};
|
|
16815
17054
|
const app = appYaml.value;
|
|
16816
17055
|
const languageList = languageListProblems(app.languages, LANGUAGES);
|
|
16817
17056
|
for (const problem of languageList) problems.at(appYaml, ["languages", Math.max(app.languages.lastIndexOf(problem.lang), 0)], problem.message);
|
|
16818
|
-
if (languageList.length > 0) return {
|
|
17057
|
+
if (languageList.length > 0) return {
|
|
17058
|
+
problems: problems.lines,
|
|
17059
|
+
format
|
|
17060
|
+
};
|
|
16819
17061
|
const texts = new Texts(app.languages, problems);
|
|
16820
17062
|
texts.check(appYaml, ["title"], "plain");
|
|
16821
17063
|
texts.check(appYaml, ["intro"], "markdown");
|
|
@@ -16885,11 +17127,13 @@ function check(root, seeds = 200) {
|
|
|
16885
17127
|
for (const pageId of pagesOk ? chapter.pages : pageIds) {
|
|
16886
17128
|
const file = `src/${chapterId}/${pageId}.yaml`;
|
|
16887
17129
|
const yaml = readYaml(root, file, problems);
|
|
16888
|
-
if (yaml === void 0 || !validate(yaml, "page", problems)) {
|
|
17130
|
+
if (yaml === void 0 || !validate(yaml, "page", problems, format)) {
|
|
16889
17131
|
complete = false;
|
|
16890
17132
|
continue;
|
|
16891
17133
|
}
|
|
16892
17134
|
const page = yaml.value;
|
|
17135
|
+
if (app.strand === "none" && page.competencies !== void 0) problems.at(yaml, ["competencies"], "competencies is not allowed: src/app.yaml has strand: none, so the app is on no strand; remove it, or name the app's strand in src/app.yaml");
|
|
17136
|
+
else if (app.strand !== void 0 && app.strand !== "none" && page.competencies === void 0 && page.recap === void 0) problems.at(yaml, [], `competencies is missing; src/app.yaml names the strand ${app.strand}, so each page names the competencies of its subject that it practises, e.g. competencies: [laplace]; a page that only repeats a competency the app does not practise says recap: <subject>/<competency> instead`);
|
|
16893
17137
|
texts.check(yaml, ["title"], "plain");
|
|
16894
17138
|
texts.check(yaml, ["description"], "plain");
|
|
16895
17139
|
const pageAnchors = /* @__PURE__ */ new Set();
|
|
@@ -16909,7 +17153,12 @@ function check(root, seeds = 200) {
|
|
|
16909
17153
|
uses,
|
|
16910
17154
|
dataUsed
|
|
16911
17155
|
}, page.blocks, ["blocks"], 0);
|
|
16912
|
-
if (
|
|
17156
|
+
if (page.recap !== void 0) for (const [block, path] of blockPaths(page.blocks, ["blocks"])) {
|
|
17157
|
+
const component = uses.find((use) => use.block === block && use.component.exercise);
|
|
17158
|
+
if (!isExercise(block) && component === void 0) continue;
|
|
17159
|
+
problems.at(yaml, path, `${component === void 0 ? `the exercise ${block.type}` : `the exercise component ${component.component.name}`} is not allowed on a recap page: it repeats ${page.recap} with descriptive blocks only, and the apps of that competency's strand practise it; remove the exercise, or make it a page of the app's own competencies`);
|
|
17160
|
+
}
|
|
17161
|
+
else if (![...allBlocks(page.blocks)].some(isExercise)) problems.at(yaml, ["blocks"], "the page has no exercise; give it a section with an exercise: one of the question types, or generated");
|
|
16913
17162
|
for (const [id, path] of exercises) {
|
|
16914
17163
|
if (pageAnchors.has(id)) problems.at(yaml, [...path, "id"], `${id} is also the anchor of a section of this page; give the exercise its own id`);
|
|
16915
17164
|
pageAnchors.add(id);
|
|
@@ -16950,7 +17199,10 @@ function check(root, seeds = 200) {
|
|
|
16950
17199
|
}
|
|
16951
17200
|
for (const chapter of chapters) for (const page of chapter.pages) if (app.scope !== void 0 && page.scope === void 0) problems.file(page.file, "has no scope; src/app.yaml has one, so each page says in scope: what it teaches, in your own words from the material");
|
|
16952
17201
|
else if (app.scope === void 0 && page.scope !== void 0) problems.file(page.file, "has a scope, but src/app.yaml has none; add scope: { out: [...] } to src/app.yaml, what does not belong to the scope");
|
|
16953
|
-
if (problems.lines.length > 0) return {
|
|
17202
|
+
if (problems.lines.length > 0) return {
|
|
17203
|
+
problems: problems.lines,
|
|
17204
|
+
format
|
|
17205
|
+
};
|
|
16954
17206
|
const data = {};
|
|
16955
17207
|
for (const [id, yaml] of dataFiles) data[id] = yaml.value;
|
|
16956
17208
|
return {
|
|
@@ -16962,15 +17214,16 @@ function check(root, seeds = 200) {
|
|
|
16962
17214
|
components: uses,
|
|
16963
17215
|
storage: appName(root, "explainer")
|
|
16964
17216
|
},
|
|
16965
|
-
problems: []
|
|
17217
|
+
problems: [],
|
|
17218
|
+
format
|
|
16966
17219
|
};
|
|
16967
17220
|
}
|
|
16968
17221
|
/**
|
|
16969
17222
|
* The content in `root`/src, its generated exercises tested over `seeds` seeds (0: not at all, for a reader of content
|
|
16970
17223
|
* the build has checked); throws a ContentError with every problem when it is not valid. */
|
|
16971
17224
|
function loadSite(root, seeds = 200) {
|
|
16972
|
-
const { site, problems } = check(root, seeds);
|
|
16973
|
-
if (site === void 0) throw new ContentError(problems);
|
|
17225
|
+
const { site, problems, format } = check(root, seeds);
|
|
17226
|
+
if (site === void 0) throw new ContentError(problems, format);
|
|
16974
17227
|
return site;
|
|
16975
17228
|
}
|
|
16976
17229
|
export { exampleProblems$1 as _, routes as a, exerciseData as c, templatesOf as d, workedTasks as f, describedProblems as g, TABLE_CONTRACT as h, hiddenChapter as i, renderExercise as l, SIZE_BUDGET as m, loadSite as n, terms as o, formulaAlternative as p, contentPages as r, dataOf as s, ContentError as t, taskSeeds as u, propertyProblems as v, propsIn as y };
|
package/dist/questions.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { C as wrongAnswer, S as shuffled, T as de_default, _ as questionIn, a as renderQuestion, b as rightAnswer, c as hash, d as materialPairs, f as missesRight, g as questionId, h as questionAddresses, i as questionMessagesIn, l as isComplete, m as parseNumber, n as answerText, o as renderSolution, p as mixed, s as QUESTION_TYPES, t as QUESTION_MESSAGES, u as isCorrect, v as questionTexts, w as wrongReasons, x as rightEntries, y as random } from "./render-GS1Htlu8.mjs";
|
|
2
|
-
import { c as questionSchema, l as questionSchemaProblems, n as QUESTION_REF, p as validateQuestion, r as QUESTION_SCHEMA_ID, s as questionDetails, u as questionSchemas } from "./validate-
|
|
2
|
+
import { c as questionSchema, l as questionSchemaProblems, n as QUESTION_REF, p as validateQuestion, r as QUESTION_SCHEMA_ID, s as questionDetails, u as questionSchemas } from "./validate-j3GjVhNF.mjs";
|
|
3
3
|
// runtime/shared/questions/index.ts
|
|
4
4
|
/** The texts of a question for learners in German, by their key (messages/de.json); questionMessagesIn(lang) for another. */
|
|
5
5
|
const messages = de_default;
|
package/dist/quiz-e2e.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { C as wrongAnswer, b as rightAnswer, g as questionId } from "./render-GS1Htlu8.mjs";
|
|
2
2
|
import { i as expectMaterial, n as expectBackground, o as question, r as expectFeedbackInView, t as answerQuestion } from "./e2e-CU0n_unV.mjs";
|
|
3
3
|
import { n as quizLanguages, t as quizIn } from "./quiz-DnW-xtCh.mjs";
|
|
4
|
-
import { n as messagesIn, t as QUIZ_MESSAGES } from "./render-
|
|
4
|
+
import { n as messagesIn, t as QUIZ_MESSAGES } from "./render-mYtzN1DV.mjs";
|
|
5
5
|
import { expect, test } from "playwright/test";
|
|
6
6
|
import { readFileSync } from "node:fs";
|
|
7
7
|
// runtime/quiz/e2e.ts
|
package/dist/quiz-plugin.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { c as textLanguages, o as languageListProblems, r as LANGUAGES, t as escape } from "./html-mffkHGHh.mjs";
|
|
2
2
|
import { r as materialImages } from "./block-BK_ohsH1.mjs";
|
|
3
|
-
import "./blocks-
|
|
4
|
-
import { a as duplicates, d as readable, f as schemaMessages, o as inQuestion, p as validateQuestion, u as questionSchemas } from "./validate-
|
|
3
|
+
import "./blocks-D4SSGKuL.mjs";
|
|
4
|
+
import { a as duplicates, d as readable, f as schemaMessages, o as inQuestion, p as validateQuestion, u as questionSchemas } from "./validate-j3GjVhNF.mjs";
|
|
5
5
|
import { n as quizLanguages, t as quizIn } from "./quiz-DnW-xtCh.mjs";
|
|
6
6
|
import { t as appName } from "./appname-Bh23cUuz.mjs";
|
|
7
7
|
import { n as isSizeless, t as imageType } from "./image-CspJVfyQ.mjs";
|
|
8
8
|
import { c as withLanguage, i as notFoundPage, n as fromDepth, r as languageRoot } from "./pages-9RaaomoO.mjs";
|
|
9
|
-
import { r as renderQuiz } from "./render-
|
|
9
|
+
import { r as renderQuiz } from "./render-mYtzN1DV.mjs";
|
|
10
10
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
11
11
|
import { existsSync, readFileSync } from "node:fs";
|
|
12
12
|
import { join } from "node:path";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { i as inLanguage, l as textsIn, o as languageListProblems, r as LANGUAGES } from "./html-mffkHGHh.mjs";
|
|
2
|
-
import { c as renderMarkdown, o as plain } from "./check-
|
|
2
|
+
import { c as renderMarkdown, o as plain } from "./check-BxZOZPic.mjs";
|
|
3
3
|
import { a as pageFooter, o as pageHeader, r as languageRoot, s as relative, t as alternateLinks } from "./pages-9RaaomoO.mjs";
|
|
4
|
-
import { c as schemaProblems, n as Texts, s as readYaml, t as Problems } from "./content-
|
|
4
|
+
import { c as schemaProblems, n as Texts, s as readYaml, t as Problems } from "./content-CAL0Nz1a.mjs";
|
|
5
5
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
6
6
|
import { existsSync, readdirSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { l as textsIn, s as localeOf, t as escape } from "./html-mffkHGHh.mjs";
|
|
2
|
-
import { n as renderMaterial } from "./blocks-
|
|
2
|
+
import { n as renderMaterial } from "./blocks-D4SSGKuL.mjs";
|
|
3
3
|
import { a as renderQuestion, g as questionId, o as renderSolution, r as external, t as QUESTION_MESSAGES } from "./render-GS1Htlu8.mjs";
|
|
4
4
|
import { a as de_default, i as en_default, r as solutionId } from "./quiz-DnW-xtCh.mjs";
|
|
5
5
|
import { a as pageFooter, o as pageHeader, r as languageRoot, s as relative, t as alternateLinks } from "./pages-9RaaomoO.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { x as rightEntries } from "./render-GS1Htlu8.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { T as message } from "./assessment-DaKsXUdj.mjs";
|
|
3
3
|
// runtime/explainer/result.ts
|
|
4
4
|
const key = (id, n) => n === void 0 ? id : `${id}=${n}`;
|
|
5
5
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as inLanguage } from "./html-mffkHGHh.mjs";
|
|
2
2
|
import { n as markerOf } from "./block-BK_ohsH1.mjs";
|
|
3
|
-
import { t as checkText } from "./check-
|
|
3
|
+
import { t as checkText } from "./check-BxZOZPic.mjs";
|
|
4
4
|
// runtime/shared/blocks/validate.ts
|
|
5
5
|
/** The definition of each type of block in the schema of the blocks (blocks.v1.schema.json). */
|
|
6
6
|
const MATERIAL_DEFINITIONS = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as textLanguages } from "./html-mffkHGHh.mjs";
|
|
2
|
-
import { n as checkMaterial, t as MATERIAL_DEFINITIONS } from "./validate-
|
|
2
|
+
import { n as checkMaterial, t as MATERIAL_DEFINITIONS } from "./validate-CfFykN0M.mjs";
|
|
3
3
|
import { _ as questionIn, h as questionAddresses, l as isComplete, m as parseNumber, u as isCorrect, v as questionTexts, x as rightEntries } from "./render-GS1Htlu8.mjs";
|
|
4
4
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
5
5
|
// runtime/shared/examples.ts
|
|
@@ -298,7 +298,7 @@ var blocks_v1_schema_default = {
|
|
|
298
298
|
"type": "object"
|
|
299
299
|
},
|
|
300
300
|
"latex": {
|
|
301
|
-
"description": "One formula in the LaTeX subset of the formula command list: \\frac, \\cdot, \\times, \\div, \\%, \\text, ^, _, \\sqrt, \\approx, \\pm, \\le, \\ge, \\neq, \\, (thin space), parentheses and braces; \\ce{…} for chemical equations and \\pu{…} for units (mhchem). Written without the $ signs. A map by language only when it has \\text{…} that differs by language.",
|
|
301
|
+
"description": "One formula in the LaTeX subset of the formula command list: \\frac, \\cdot, \\times, \\div, \\%, \\text, ^, _, \\sqrt, \\approx, \\pm, \\le, \\ge, \\neq, \\, (thin space), \\ (space), \\quad, \\qquad, \\Rightarrow, \\Leftrightarrow, \\left( … \\right), parentheses and braces; \\sin, \\cos, \\tan, \\arcsin, \\arccos, \\arctan (or \\sin^{-1}), \\circ for degrees (30^\\circ); the Greek letters \\alpha to \\omega (also \\varepsilon, \\vartheta, \\varphi) and the capitals \\Gamma, \\Delta, \\Theta, \\Lambda, \\Xi, \\Pi, \\Sigma, \\Phi, \\Psi, \\Omega; \\ce{…} for chemical equations and \\pu{…} for units (mhchem). The build names any other command and lists these. Written without the $ signs. A map by language only when it has \\text{…} that differs by language.",
|
|
302
302
|
"$ref": "urn:lernapps:schema:text.v1#/$defs/text"
|
|
303
303
|
},
|
|
304
304
|
"table": {
|
package/explainer/README.md
CHANGED
|
@@ -11,7 +11,8 @@ Chapters of pages that explain a topic, with exercises on every page. Every page
|
|
|
11
11
|
videos directly from then on. The catalog entry of an app with a video says `fitness.thirdParty: on-click`.
|
|
12
12
|
- Exercises of the six question types, with feedback, hints and typical mistakes; a question can show content (`material`: the figures, tables, quotes and code of the page's blocks) between its text and its answers.
|
|
13
13
|
- Generated exercises: each task draws new numbers or records, up to 67 numbered tasks per exercise, with typical
|
|
14
|
-
mistakes, a hint and the way to the solution
|
|
14
|
+
mistakes, a hint and the way to the solution, also with roots, powers and trigonometry, and with chemical formulas
|
|
15
|
+
from a data file. The build tests them over fixed seeds.
|
|
15
16
|
- Vue components for what no block can show or check, such as reading a scale, labelling a figure or dragging into
|
|
16
17
|
place; an exercise component draws its own tasks. A component needs its logic module, a test and a text for readers
|
|
17
18
|
without JavaScript. An exercise component also declares its exercise for the tutor (`template`, `params`,
|
|
@@ -41,7 +42,7 @@ Chapters of pages that explain a topic, with exercises on every page. Every page
|
|
|
41
42
|
- No timer, no game.
|
|
42
43
|
- At most two section levels in a page.
|
|
43
44
|
- An exercise component does not count as a page's required exercise: each page also needs a question or a generated
|
|
44
|
-
exercise.
|
|
45
|
+
exercise. A recap page has none at all.
|
|
45
46
|
- Stores nothing but the learner's choice of AI tutor, if they make it, the choice to load videos directly, and the
|
|
46
47
|
last result of each self-assessment they do.
|
|
47
48
|
- A self-assessment needs JavaScript (without it, it is not offered).
|
|
@@ -50,5 +51,8 @@ Chapters of pages that explain a topic, with exercises on every page. Every page
|
|
|
50
51
|
## What the app supplies
|
|
51
52
|
|
|
52
53
|
- The content as YAML in `src/`: the app, a folder per chapter with its pages, data files and images
|
|
53
|
-
([schema](../runtime/explainer/explainer.
|
|
54
|
+
([schema](../runtime/explainer/explainer.v2.schema.json), format 2); Vue components in `src/components/` where
|
|
54
55
|
no block fits.
|
|
56
|
+
- Its place on the catalog's learning map (format 2): the app's strand, and the competencies each page practises. A
|
|
57
|
+
recap page repeats a competency from elsewhere that the app needs, without exercises. Format 1
|
|
58
|
+
([schema](../runtime/explainer/explainer.v1.schema.json)) stays valid, without a place on the map.
|