@lernapps/app-templates 0.0.1 → 0.1.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/LICENSE +22 -0
- package/README.md +195 -2
- package/dist/appname-Bh23cUuz.mjs +12 -0
- package/dist/assessment-Dy359O1O.mjs +1696 -0
- package/dist/block-BK_ohsH1.mjs +82 -0
- package/dist/blocks-CxtCPAa5.mjs +149 -0
- package/dist/blocks.mjs +4 -0
- package/dist/check-CteIFN96.mjs +254 -0
- package/dist/content-DO6oEJxj.mjs +140 -0
- package/dist/contract-PONpxJtd.mjs +52 -0
- package/dist/e2e-CU0n_unV.mjs +89 -0
- package/dist/engine-CArxqizW.mjs +136 -0
- package/dist/explainer-e2e.mjs +1311 -0
- package/dist/explainer-islands.mjs +197 -0
- package/dist/explainer-plugin.mjs +2132 -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-RosvaPZf.mjs +16976 -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-DnW-xtCh.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-D5kwkNt1.mjs +507 -0
- package/dist/render-DqHOJyHK.mjs +146 -0
- package/dist/render-GS1Htlu8.mjs +545 -0
- package/dist/result-D_mmCVwN.mjs +180 -0
- package/dist/tasks-DADUYy52.mjs +74 -0
- package/dist/validate-BDwL7Su3.mjs +1076 -0
- package/dist/validate-Bxy9NfB8.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 +143 -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 +448 -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 +1488 -0
- package/runtime/explainer/exercises.ts +326 -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 +306 -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 +433 -0
- package/runtime/shared/questions/question.v1.schema.json +346 -0
- package/runtime/shared/questions/questions.css +192 -0
- package/runtime/shared/questions/render.ts +255 -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/speech.ts +39 -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,82 @@
|
|
|
1
|
+
import { i as inLanguage } from "./html-mffkHGHh.mjs";
|
|
2
|
+
// runtime/shared/blocks/block.ts
|
|
3
|
+
/** The marker of the label at `index`: the one it names, else its number. */
|
|
4
|
+
const markerOf = (label, index) => label.marker ?? String(index + 1);
|
|
5
|
+
/** The types of the blocks that show content. */
|
|
6
|
+
const MATERIAL_TYPES = [
|
|
7
|
+
"text",
|
|
8
|
+
"formula",
|
|
9
|
+
"figure",
|
|
10
|
+
"labelled-figure",
|
|
11
|
+
"table",
|
|
12
|
+
"quote",
|
|
13
|
+
"code"
|
|
14
|
+
];
|
|
15
|
+
/** The blocks with each text in `lang`. */
|
|
16
|
+
function materialIn(blocks, lang) {
|
|
17
|
+
const t = (text) => inLanguage(text, lang);
|
|
18
|
+
const credit = (block) => ({
|
|
19
|
+
...block.source === void 0 ? {} : { source: t(block.source) },
|
|
20
|
+
...block.licence === void 0 ? {} : { licence: block.licence }
|
|
21
|
+
});
|
|
22
|
+
return blocks.map((block) => {
|
|
23
|
+
switch (block.type) {
|
|
24
|
+
case "text": return {
|
|
25
|
+
type: "text",
|
|
26
|
+
text: t(block.text)
|
|
27
|
+
};
|
|
28
|
+
case "formula": return {
|
|
29
|
+
type: "formula",
|
|
30
|
+
formula: t(block.formula)
|
|
31
|
+
};
|
|
32
|
+
case "figure": return {
|
|
33
|
+
type: "figure",
|
|
34
|
+
...block.draw === void 0 ? {} : { draw: block.draw },
|
|
35
|
+
...block.image === void 0 ? {} : { image: block.image },
|
|
36
|
+
alt: t(block.alt),
|
|
37
|
+
caption: t(block.caption),
|
|
38
|
+
...credit(block)
|
|
39
|
+
};
|
|
40
|
+
case "labelled-figure": return {
|
|
41
|
+
type: "labelled-figure",
|
|
42
|
+
image: block.image,
|
|
43
|
+
alt: t(block.alt),
|
|
44
|
+
...block.caption === void 0 ? {} : { caption: t(block.caption) },
|
|
45
|
+
...block.legend === void 0 ? {} : { legend: block.legend },
|
|
46
|
+
labels: block.labels.map((label) => ({
|
|
47
|
+
...label.marker === void 0 ? {} : { marker: label.marker },
|
|
48
|
+
x: label.x,
|
|
49
|
+
y: label.y,
|
|
50
|
+
text: t(label.text)
|
|
51
|
+
})),
|
|
52
|
+
...credit(block)
|
|
53
|
+
};
|
|
54
|
+
case "table": return {
|
|
55
|
+
type: "table",
|
|
56
|
+
caption: t(block.caption),
|
|
57
|
+
...block.header === void 0 ? {} : { header: block.header.map(t) },
|
|
58
|
+
...block.rowHeaders === void 0 ? {} : { rowHeaders: block.rowHeaders },
|
|
59
|
+
rows: block.rows.map((row) => row.map(t))
|
|
60
|
+
};
|
|
61
|
+
case "quote": return {
|
|
62
|
+
type: "quote",
|
|
63
|
+
text: t(block.text),
|
|
64
|
+
origin: {
|
|
65
|
+
author: t(block.origin.author),
|
|
66
|
+
...block.origin.work === void 0 ? {} : { work: t(block.origin.work) },
|
|
67
|
+
...block.origin.year === void 0 ? {} : { year: typeof block.origin.year === "number" ? block.origin.year : t(block.origin.year) }
|
|
68
|
+
},
|
|
69
|
+
...credit(block)
|
|
70
|
+
};
|
|
71
|
+
case "code": return {
|
|
72
|
+
type: "code",
|
|
73
|
+
language: block.language,
|
|
74
|
+
code: block.code,
|
|
75
|
+
...block.caption === void 0 ? {} : { caption: t(block.caption) }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** The bundled images `blocks` use, each once, as paths from src/. */
|
|
81
|
+
const materialImages = (blocks) => [...new Set(blocks.flatMap((block) => "image" in block && block.image !== void 0 ? [block.image] : []))];
|
|
82
|
+
export { materialIn as i, markerOf as n, materialImages as r, MATERIAL_TYPES as t };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { i as inLanguage, l as textsIn, n as escapeText, s as localeOf, t as escape } from "./html-mffkHGHh.mjs";
|
|
2
|
+
import { n as markerOf } from "./block-BK_ohsH1.mjs";
|
|
3
|
+
import { c as renderMarkdown, n as isUrl, s as renderInline, u as renderFormula } from "./check-CteIFN96.mjs";
|
|
4
|
+
import "./validate-Bxy9NfB8.mjs";
|
|
5
|
+
import hljs from "highlight.js/lib/core";
|
|
6
|
+
import bash from "highlight.js/lib/languages/bash";
|
|
7
|
+
import c from "highlight.js/lib/languages/c";
|
|
8
|
+
import css from "highlight.js/lib/languages/css";
|
|
9
|
+
import java from "highlight.js/lib/languages/java";
|
|
10
|
+
import javascript from "highlight.js/lib/languages/javascript";
|
|
11
|
+
import json from "highlight.js/lib/languages/json";
|
|
12
|
+
import python from "highlight.js/lib/languages/python";
|
|
13
|
+
import sql from "highlight.js/lib/languages/sql";
|
|
14
|
+
import typescript from "highlight.js/lib/languages/typescript";
|
|
15
|
+
import xml from "highlight.js/lib/languages/xml";
|
|
16
|
+
// runtime/shared/blocks/code.ts
|
|
17
|
+
const languages = {
|
|
18
|
+
bash,
|
|
19
|
+
c,
|
|
20
|
+
css,
|
|
21
|
+
html: xml,
|
|
22
|
+
java,
|
|
23
|
+
javascript,
|
|
24
|
+
json,
|
|
25
|
+
python,
|
|
26
|
+
sql,
|
|
27
|
+
typescript
|
|
28
|
+
};
|
|
29
|
+
for (const [name, language] of Object.entries(languages)) hljs.registerLanguage(name, language);
|
|
30
|
+
/** The code as highlighted HTML, for inside <code>. */
|
|
31
|
+
function highlight(code, language) {
|
|
32
|
+
if (language === "plaintext") return escapeText(code);
|
|
33
|
+
return hljs.highlight(code, {
|
|
34
|
+
language,
|
|
35
|
+
ignoreIllegals: true
|
|
36
|
+
}).value;
|
|
37
|
+
}
|
|
38
|
+
// runtime/shared/blocks/figures.ts
|
|
39
|
+
const ON = "#1d4ed8";
|
|
40
|
+
const OFF = "#ffffff";
|
|
41
|
+
const LINE = "#4a4a4a";
|
|
42
|
+
/** A field of 10 × 10 squares, the first `filled` shaded, row by row. */
|
|
43
|
+
function hundredGrid(filled, alt) {
|
|
44
|
+
const cells = [];
|
|
45
|
+
for (let index = 0; index < 100; index++) {
|
|
46
|
+
const x = 1 + index % 10 * 11;
|
|
47
|
+
const y = 1 + Math.floor(index / 10) * 11;
|
|
48
|
+
cells.push(`<rect x="${x}" y="${y}" width="10" height="10" class="${index < filled ? "fig-on" : "fig-off"} fig-line" fill="${index < filled ? ON : OFF}" stroke="${LINE}" stroke-width="0.5"/>`);
|
|
49
|
+
}
|
|
50
|
+
return `<svg class="block-drawing block-hundred-grid" role="img" aria-label="${escape(alt)}" viewBox="0 0 111 111" xmlns="http://www.w3.org/2000/svg">${cells.join("")}</svg>`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A strip for `whole`, `part` of it shaded; under it 0, part and whole, and their percentages (without them in a task
|
|
54
|
+
* that asks for the percentage: `percent` false).
|
|
55
|
+
*/
|
|
56
|
+
function bar(part, whole, alt, locale, percent = true) {
|
|
57
|
+
const numbers = new Intl.NumberFormat(locale, { maximumFractionDigits: 2 });
|
|
58
|
+
const share = Math.min(part / whole, 1);
|
|
59
|
+
const width = 298 * share;
|
|
60
|
+
const at = Math.min(Math.max(1 + width, 50), 250);
|
|
61
|
+
const label = (x, y, anchor, text) => `<text x="${x.toFixed(1)}" y="${y}" text-anchor="${anchor}" font-size="12" class="fig-text" fill="#1a1a1a">${escape(text)}</text>`;
|
|
62
|
+
return [
|
|
63
|
+
`<svg class="block-drawing block-bar" role="img" aria-label="${escape(alt)}" viewBox="0 0 300 ${percent ? 74 : 58}" xmlns="http://www.w3.org/2000/svg">`,
|
|
64
|
+
`<rect x="1" y="1" width="298" height="36" class="fig-off fig-line" fill="${OFF}" stroke="${LINE}"/>`,
|
|
65
|
+
`<rect x="1" y="1" width="${width.toFixed(1)}" height="36" class="fig-on" fill="${ON}"/>`,
|
|
66
|
+
label(1, 54, "start", "0"),
|
|
67
|
+
label(at, 54, "middle", numbers.format(part)),
|
|
68
|
+
label(299, 54, "end", numbers.format(whole)),
|
|
69
|
+
...percent ? [
|
|
70
|
+
label(1, 70, "start", "0 %"),
|
|
71
|
+
label(at, 70, "middle", `${numbers.format(share * 100)} %`),
|
|
72
|
+
label(299, 70, "end", "100 %")
|
|
73
|
+
] : [],
|
|
74
|
+
`</svg>`
|
|
75
|
+
].join("");
|
|
76
|
+
}
|
|
77
|
+
// runtime/shared/blocks/render.ts
|
|
78
|
+
const BLOCK_MESSAGES = {
|
|
79
|
+
de: {
|
|
80
|
+
credit: "Quelle: {source}. Lizenz: {licence}.",
|
|
81
|
+
code: "Code in {language}"
|
|
82
|
+
},
|
|
83
|
+
en: {
|
|
84
|
+
credit: "Source: {source}. Licence: {licence}.",
|
|
85
|
+
code: "Code in {language}"
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const external = (url) => `<a href="${escape(url)}" rel="noopener">${escape(url)}</a>`;
|
|
89
|
+
/** The credit of an image or a quotation: its source, as a link when it is a URL, and its licence. */
|
|
90
|
+
function renderCredit(item, lang) {
|
|
91
|
+
if (item.source === void 0 || item.licence === void 0) return "";
|
|
92
|
+
const given = inLanguage(item.source, lang);
|
|
93
|
+
const source = isUrl(given) ? external(given) : escape(given);
|
|
94
|
+
return ` <span class="block-credit">${textsIn(BLOCK_MESSAGES, lang).credit.replace("{licence}", () => escape(item.licence ?? "")).replace("{source}", () => source)}</span>`;
|
|
95
|
+
}
|
|
96
|
+
function renderBlock(block, context) {
|
|
97
|
+
const { lang } = context;
|
|
98
|
+
const resolve = context.resolve ?? ((href) => href);
|
|
99
|
+
const plain = (text) => escape(inLanguage(text, lang));
|
|
100
|
+
const markdown = (text) => renderMarkdown(inLanguage(text, lang), resolve);
|
|
101
|
+
const inline = (text) => renderInline(inLanguage(text, lang), resolve);
|
|
102
|
+
switch (block.type) {
|
|
103
|
+
case "text": return `<div class="block-text">${markdown(block.text)}</div>`;
|
|
104
|
+
case "formula": return `<div class="block-formula">${renderFormula(inLanguage(block.formula, lang), true)}</div>`;
|
|
105
|
+
case "figure": {
|
|
106
|
+
const alt = inLanguage(block.alt, lang);
|
|
107
|
+
let picture = "";
|
|
108
|
+
if (block.draw?.kind === "hundred-grid") picture = hundredGrid(block.draw.filled, alt);
|
|
109
|
+
else if (block.draw?.kind === "bar") picture = bar(block.draw.part, block.draw.whole, alt, localeOf(lang));
|
|
110
|
+
else if (block.image !== void 0) picture = `<img src="${escape(context.href(block.image))}" alt="${escape(alt)}" loading="lazy" decoding="async">`;
|
|
111
|
+
return `<figure class="block-figure">${picture}<figcaption>${markdown(block.caption)}${renderCredit(block, lang)}</figcaption></figure>`;
|
|
112
|
+
}
|
|
113
|
+
case "labelled-figure": {
|
|
114
|
+
const markers = block.labels.map((label, index) => `<span class="block-marker" style="left: ${label.x}%; top: ${label.y}%" aria-hidden="true">${escape(markerOf(label, index))}</span>`).join("");
|
|
115
|
+
const named = block.labels.some((label) => label.marker !== void 0);
|
|
116
|
+
const item = (label, index) => named ? `<li><b>${escape(markerOf(label, index))}</b> ${plain(label.text)}</li>` : `<li>${plain(label.text)}</li>`;
|
|
117
|
+
const legend = block.legend === false ? "" : `<ol class="block-legend${named ? " block-legend-named" : ""}">${block.labels.map(item).join("")}</ol>`;
|
|
118
|
+
const caption = block.caption === void 0 ? "" : markdown(block.caption);
|
|
119
|
+
return [
|
|
120
|
+
`<figure class="block-figure block-labelled">`,
|
|
121
|
+
`<div class="block-labelled-image${context.sizeless?.(block.image) === true ? " block-labelled-fill" : ""}"><img src="${escape(context.href(block.image))}" alt="${plain(block.alt)}" loading="lazy" decoding="async">${markers}</div>`,
|
|
122
|
+
legend,
|
|
123
|
+
`<figcaption>${caption}${renderCredit(block, lang)}</figcaption>`,
|
|
124
|
+
`</figure>`
|
|
125
|
+
].join("");
|
|
126
|
+
}
|
|
127
|
+
case "table": {
|
|
128
|
+
const caption = plain(block.caption);
|
|
129
|
+
return `<div class="block-table" role="region" aria-label="${caption}" tabindex="0"><table><caption>${caption}</caption>${block.header === void 0 ? "" : `<thead><tr>${block.header.map((cell) => `<th scope="col">${inline(cell)}</th>`).join("")}</tr></thead>`}<tbody>${block.rows.map((row) => `<tr>${row.map((cell, index) => index === 0 && block.rowHeaders === true ? `<th scope="row">${inline(cell)}</th>` : `<td>${inline(cell)}</td>`).join("")}</tr>`).join("")}</tbody></table></div>`;
|
|
130
|
+
}
|
|
131
|
+
case "quote": {
|
|
132
|
+
const { author, work, year } = block.origin;
|
|
133
|
+
const origin = [
|
|
134
|
+
plain(author),
|
|
135
|
+
...work === void 0 ? [] : [`<cite>${plain(work)}</cite>`],
|
|
136
|
+
...year === void 0 ? [] : [typeof year === "number" ? String(year) : plain(year)]
|
|
137
|
+
].join(", ");
|
|
138
|
+
return `<figure class="block-quote"><blockquote>${markdown(block.text)}</blockquote><figcaption>— ${origin}.${renderCredit(block, lang)}</figcaption></figure>`;
|
|
139
|
+
}
|
|
140
|
+
case "code": {
|
|
141
|
+
const label = textsIn(BLOCK_MESSAGES, lang).code.replace("{language}", () => block.language);
|
|
142
|
+
const caption = block.caption === void 0 ? "" : `<figcaption>${plain(block.caption)}</figcaption>`;
|
|
143
|
+
return `<figure class="block-code"><pre role="region" aria-label="${escape(label)}" tabindex="0"><code class="hljs language-${block.language}">${highlight(block.code, block.language)}</code></pre>${caption}</figure>`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/** The blocks as HTML, one after the other. */
|
|
148
|
+
const renderMaterial = (blocks, context) => blocks.map((block) => renderBlock(block, context)).join("");
|
|
149
|
+
export { highlight as a, hundredGrid as i, renderMaterial as n, bar as r, renderCredit as t };
|
package/dist/blocks.mjs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { i as materialIn, n as markerOf, r as materialImages, t as MATERIAL_TYPES } from "./block-BK_ohsH1.mjs";
|
|
2
|
+
import { a as highlight, i as hundredGrid, n as renderMaterial, r as bar, t as renderCredit } from "./blocks-CxtCPAa5.mjs";
|
|
3
|
+
import { n as checkMaterial, t as MATERIAL_DEFINITIONS } from "./validate-Bxy9NfB8.mjs";
|
|
4
|
+
export { MATERIAL_DEFINITIONS, MATERIAL_TYPES, bar, checkMaterial, highlight, hundredGrid, markerOf, materialImages, materialIn, renderCredit, renderMaterial };
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { c as textLanguages, t as escape } from "./html-mffkHGHh.mjs";
|
|
2
|
+
import temml from "temml";
|
|
3
|
+
import { Marked } from "marked";
|
|
4
|
+
// runtime/shared/text/formula.ts
|
|
5
|
+
globalThis.temml = temml;
|
|
6
|
+
await import("temml/contrib/mhchem/mhchem.js");
|
|
7
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
8
|
+
"\\frac",
|
|
9
|
+
"\\cdot",
|
|
10
|
+
"\\times",
|
|
11
|
+
"\\div",
|
|
12
|
+
"\\%",
|
|
13
|
+
"\\text",
|
|
14
|
+
"\\sqrt",
|
|
15
|
+
"\\approx",
|
|
16
|
+
"\\pm",
|
|
17
|
+
"\\le",
|
|
18
|
+
"\\ge",
|
|
19
|
+
"\\neq",
|
|
20
|
+
"\\,",
|
|
21
|
+
"\\{",
|
|
22
|
+
"\\}",
|
|
23
|
+
"\\ce",
|
|
24
|
+
"\\pu"
|
|
25
|
+
]);
|
|
26
|
+
/** The commands of `tex` outside the arguments of \ce and \pu, which mhchem reads in its own notation. */
|
|
27
|
+
function commands(tex) {
|
|
28
|
+
const found = [];
|
|
29
|
+
for (let at = 0; at < tex.length; at++) {
|
|
30
|
+
if (tex[at] !== "\\") continue;
|
|
31
|
+
const word = /^\\(?:[a-zA-Z]+|.)/.exec(tex.slice(at))?.[0] ?? "\\";
|
|
32
|
+
found.push(word);
|
|
33
|
+
at += word.length - 1;
|
|
34
|
+
if ((word === "\\ce" || word === "\\pu") && tex[at + 1] === "{") {
|
|
35
|
+
let depth = 0;
|
|
36
|
+
for (at++; at < tex.length; at++) if (tex[at] === "{") depth++;
|
|
37
|
+
else if (tex[at] === "}" && --depth === 0) break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return found;
|
|
41
|
+
}
|
|
42
|
+
/** What is wrong with the formula `tex`, or undefined when it is in the command list and parses. */
|
|
43
|
+
function formulaProblem(tex) {
|
|
44
|
+
const unknown = commands(tex).find((command) => !allowed.has(command));
|
|
45
|
+
if (unknown !== void 0) return `${unknown} is not in the formula command list; write the formula with the commands the schema lists ($defs/latex)`;
|
|
46
|
+
try {
|
|
47
|
+
temml.renderToString(tex, { throwOnError: true });
|
|
48
|
+
return;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return `the formula does not parse: ${(error instanceof Error ? error.message : String(error)).split("\n")[0]?.trim() ?? ""}`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** The formula as MathML: a <math> element, on its own line when `display`. */
|
|
54
|
+
function renderFormula(tex, display) {
|
|
55
|
+
return temml.renderToString(tex, {
|
|
56
|
+
displayMode: display,
|
|
57
|
+
throwOnError: true
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// runtime/shared/text/markdown.ts
|
|
61
|
+
/** Where a link leads from the page being rendered; set for each render. */
|
|
62
|
+
let resolveLink = (href) => href;
|
|
63
|
+
const marked = new Marked({
|
|
64
|
+
gfm: true,
|
|
65
|
+
breaks: false,
|
|
66
|
+
extensions: [{
|
|
67
|
+
name: "math",
|
|
68
|
+
level: "inline",
|
|
69
|
+
start(source) {
|
|
70
|
+
const at = source.indexOf("$");
|
|
71
|
+
return at < 0 ? void 0 : at;
|
|
72
|
+
},
|
|
73
|
+
tokenizer(source) {
|
|
74
|
+
const match = /^\$((?:\\.|[^\\$\n])+?)\$/.exec(source);
|
|
75
|
+
return match === null ? void 0 : {
|
|
76
|
+
type: "math",
|
|
77
|
+
raw: match[0],
|
|
78
|
+
tex: match[1] ?? ""
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
renderer(token) {
|
|
82
|
+
return renderFormula(String(token["tex"]), false);
|
|
83
|
+
}
|
|
84
|
+
}],
|
|
85
|
+
renderer: {
|
|
86
|
+
html(token) {
|
|
87
|
+
return escape(token.text);
|
|
88
|
+
},
|
|
89
|
+
link({ href, tokens }) {
|
|
90
|
+
const text = this.parser.parseInline(tokens);
|
|
91
|
+
const external = /^https?:\/\//.test(href);
|
|
92
|
+
return `<a href="${escape(external ? href : resolveLink(href))}"${external ? " rel=\"noopener\"" : ""}>${text}</a>`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
/** What each construct outside restricted Markdown is, and what to use instead. */
|
|
97
|
+
const NOT_ALLOWED = {
|
|
98
|
+
blockquote: "a quotation in Markdown; use a quote block",
|
|
99
|
+
code: "code in a text; use a code block",
|
|
100
|
+
codespan: "code in a text; use a code block, or $…$ for a formula",
|
|
101
|
+
def: "a link definition; write the link in place: [text](url)",
|
|
102
|
+
del: "strikethrough is not part of restricted Markdown",
|
|
103
|
+
hr: "a horizontal rule; structure the page with section blocks",
|
|
104
|
+
image: "an image in a text; use a figure block",
|
|
105
|
+
table: "a table in Markdown; use a table block",
|
|
106
|
+
checkbox: "a task list is not part of restricted Markdown"
|
|
107
|
+
};
|
|
108
|
+
const ALLOWED = /* @__PURE__ */ new Set([
|
|
109
|
+
"space",
|
|
110
|
+
"paragraph",
|
|
111
|
+
"text",
|
|
112
|
+
"list",
|
|
113
|
+
"list_item",
|
|
114
|
+
"strong",
|
|
115
|
+
"em",
|
|
116
|
+
"link",
|
|
117
|
+
"escape",
|
|
118
|
+
"br",
|
|
119
|
+
"math"
|
|
120
|
+
]);
|
|
121
|
+
function children(token) {
|
|
122
|
+
const nested = [];
|
|
123
|
+
if ("tokens" in token && Array.isArray(token.tokens)) nested.push(...token.tokens);
|
|
124
|
+
if (token.type === "list") nested.push(...token.items);
|
|
125
|
+
return nested;
|
|
126
|
+
}
|
|
127
|
+
/** The problems, formulas and links of a text in restricted Markdown. */
|
|
128
|
+
function inspect(source) {
|
|
129
|
+
const found = {
|
|
130
|
+
problems: [],
|
|
131
|
+
formulas: [],
|
|
132
|
+
links: []
|
|
133
|
+
};
|
|
134
|
+
const lineOf = (raw) => {
|
|
135
|
+
const at = source.indexOf(raw.trimStart());
|
|
136
|
+
return at < 0 ? 0 : source.slice(0, at).split("\n").length - 1;
|
|
137
|
+
};
|
|
138
|
+
const add = (raw, message) => {
|
|
139
|
+
if (!found.problems.some((problem) => problem.message === message)) found.problems.push({
|
|
140
|
+
line: lineOf(raw),
|
|
141
|
+
message
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
const visit = (tokens) => {
|
|
145
|
+
for (const token of tokens) {
|
|
146
|
+
if (token.type === "heading") {
|
|
147
|
+
const title = token.text.trim();
|
|
148
|
+
add(token.raw, `a Markdown heading ("${title}"); use a section block with the title "${title}"`);
|
|
149
|
+
} else if (token.type === "html") {
|
|
150
|
+
const tag = /^<\/?([a-zA-Z][\w-]*)/.exec(token.raw.trim())?.[1] ?? "";
|
|
151
|
+
if (!found.problems.some((problem) => problem.message.startsWith("raw HTML"))) add(token.raw, `raw HTML (<${tag}>) is not allowed; write restricted Markdown: **bold**, *italics*, lists, links`);
|
|
152
|
+
} else if (token.type === "math") {
|
|
153
|
+
const tex = String(token.tex);
|
|
154
|
+
found.formulas.push(tex);
|
|
155
|
+
const problem = formulaProblem(tex);
|
|
156
|
+
if (problem !== void 0) add(token.raw, `$${tex}$: ${problem}`);
|
|
157
|
+
} else if (token.type === "link") {
|
|
158
|
+
const href = token.href;
|
|
159
|
+
if (/^https?:\/\/\S+$/.test(href) || href.startsWith("/")) found.links.push(href);
|
|
160
|
+
else add(token.raw, `the link ${href} leads nowhere a learner can follow; link to https://… or to a page of the app: /<chapter>/<page>/`);
|
|
161
|
+
} else if (!ALLOWED.has(token.type)) add(token.raw, NOT_ALLOWED[token.type] ?? `${token.type} is not part of restricted Markdown`);
|
|
162
|
+
visit(children(token));
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
visit(marked.lexer(source));
|
|
166
|
+
return found;
|
|
167
|
+
}
|
|
168
|
+
/** A text in restricted Markdown as HTML; links to pages of the app go through `resolve`. */
|
|
169
|
+
function renderMarkdown(source, resolve) {
|
|
170
|
+
resolveLink = resolve;
|
|
171
|
+
return marked.parse(source, { async: false }).trim();
|
|
172
|
+
}
|
|
173
|
+
/** A short text in restricted Markdown as HTML without a paragraph around it, e.g. a table cell or a caption. */
|
|
174
|
+
function renderInline(source, resolve) {
|
|
175
|
+
resolveLink = resolve;
|
|
176
|
+
return marked.parseInline(source, { async: false }).trim();
|
|
177
|
+
}
|
|
178
|
+
/** A plain text as HTML. */
|
|
179
|
+
const plain = escape;
|
|
180
|
+
const FORMULA = /\$((?:\\.|[^\\$\n])+?)\$/g;
|
|
181
|
+
/** A plain text as HTML in which only $…$ is a formula (MathML); everything else stays text, as in a quiz. */
|
|
182
|
+
function renderWithFormulas(text) {
|
|
183
|
+
let html = "";
|
|
184
|
+
let from = 0;
|
|
185
|
+
for (const match of text.matchAll(FORMULA)) {
|
|
186
|
+
html += escape(text.slice(from, match.index)) + renderFormula(match[1] ?? "", false);
|
|
187
|
+
from = match.index + match[0].length;
|
|
188
|
+
}
|
|
189
|
+
return html + escape(text.slice(from));
|
|
190
|
+
}
|
|
191
|
+
/** What is wrong with the formulas $…$ of a plain text; none when each is in the command list and parses. */
|
|
192
|
+
function formulaProblems(text) {
|
|
193
|
+
return [...text.matchAll(FORMULA)].flatMap((match) => {
|
|
194
|
+
const problem = formulaProblem(match[1] ?? "");
|
|
195
|
+
return problem === void 0 ? [] : [`$${match[1]}$: ${problem}`];
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/** The first HTML tag in a plain text, if it has one. */
|
|
199
|
+
function htmlTag(text) {
|
|
200
|
+
return /<\/?([a-zA-Z][\w-]*)[^>]*>/.exec(text)?.[1];
|
|
201
|
+
}
|
|
202
|
+
// runtime/shared/text/check.ts
|
|
203
|
+
/** A URL as the source of a credit: the same in every language. */
|
|
204
|
+
const isUrl = (text) => /^https?:\/\/\S+$/.test(text);
|
|
205
|
+
/** The problems of the text `value` of the given kind in `languages`, declared in `declaredIn`, and its links. */
|
|
206
|
+
function checkText(value, kind, languages, declaredIn) {
|
|
207
|
+
const findings = [];
|
|
208
|
+
const links = [];
|
|
209
|
+
const neutral = kind === "latex" || kind === "name" || kind === "credit" && typeof value === "string" && isUrl(value);
|
|
210
|
+
const { texts, problems } = textLanguages(value, languages, neutral, declaredIn);
|
|
211
|
+
for (const problem of problems) findings.push({
|
|
212
|
+
path: [],
|
|
213
|
+
lang: problem.lang,
|
|
214
|
+
message: problem.message
|
|
215
|
+
});
|
|
216
|
+
for (const { lang, text, keyed } of texts) {
|
|
217
|
+
const at = keyed ? [lang] : [];
|
|
218
|
+
if (kind === "latex") {
|
|
219
|
+
const problem = formulaProblem(text);
|
|
220
|
+
if (problem !== void 0) findings.push({
|
|
221
|
+
path: at,
|
|
222
|
+
...keyed ? { lang } : {},
|
|
223
|
+
message: problem
|
|
224
|
+
});
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (kind === "plain" || kind === "name" || kind === "credit") {
|
|
228
|
+
const tag = htmlTag(text);
|
|
229
|
+
if (tag !== void 0) findings.push({
|
|
230
|
+
path: at,
|
|
231
|
+
lang,
|
|
232
|
+
message: `raw HTML (<${tag}>) is not allowed; write restricted Markdown: **bold**, *italics*, lists, links`
|
|
233
|
+
});
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const found = inspect(text);
|
|
237
|
+
for (const problem of found.problems) findings.push({
|
|
238
|
+
path: at,
|
|
239
|
+
lang,
|
|
240
|
+
line: problem.line,
|
|
241
|
+
message: problem.message
|
|
242
|
+
});
|
|
243
|
+
for (const href of found.links.filter((link) => link.startsWith("/"))) links.push({
|
|
244
|
+
path: at,
|
|
245
|
+
lang,
|
|
246
|
+
href
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
findings,
|
|
251
|
+
links
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
export { inspect as a, renderMarkdown as c, formulaProblems as i, renderWithFormulas as l, isUrl as n, plain as o, FORMULA as r, renderInline as s, checkText as t, renderFormula as u };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { t as checkText } from "./check-CteIFN96.mjs";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { LineCounter, isNode, parseDocument } from "yaml";
|
|
5
|
+
// runtime/shared/content.ts
|
|
6
|
+
/** `["blocks", 0, "text"]` as `blocks[0].text`. */
|
|
7
|
+
function pathText(path) {
|
|
8
|
+
return path.map((part, index) => typeof part === "number" ? `[${part}]` : index === 0 ? part : `.${part}`).join("");
|
|
9
|
+
}
|
|
10
|
+
const get = (value, path) => path.reduce((at, part) => typeof at === "object" && at !== null ? at[part] : void 0, value);
|
|
11
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12
|
+
/** Collects the problems, one line each, without repeating one. */
|
|
13
|
+
var Problems = class {
|
|
14
|
+
lines = [];
|
|
15
|
+
add(line) {
|
|
16
|
+
if (!this.lines.includes(line)) this.lines.push(line);
|
|
17
|
+
}
|
|
18
|
+
/** A problem at `path` in `yaml` (in `lang`), `offset` lines below the line of the value there. */
|
|
19
|
+
at(yaml, path, message, lang, offset = 0) {
|
|
20
|
+
const line = lineOf(yaml, path, offset);
|
|
21
|
+
const shown = lang !== void 0 && path.at(-1) === lang ? path.slice(0, -1) : path;
|
|
22
|
+
const where = shown.length === 0 ? "" : ` ${pathText(shown)}${lang === void 0 ? "" : ` (${lang})`}:`;
|
|
23
|
+
this.add(`${yaml.file}${line === void 0 ? "" : `:${line}`}:${where} ${message}`);
|
|
24
|
+
}
|
|
25
|
+
file(file, message) {
|
|
26
|
+
this.add(`${file}: ${message}`);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
/** The line of the value at `path`, or of the nearest value around it; a block scalar's text starts a line lower. */
|
|
30
|
+
function lineOf(yaml, path, offset) {
|
|
31
|
+
for (let length = path.length; length >= 0; length--) {
|
|
32
|
+
const node = yaml.doc.getIn(path.slice(0, length), true);
|
|
33
|
+
if (!isNode(node) || node.range === void 0 || node.range === null) continue;
|
|
34
|
+
const block = "type" in node && (node.type === "BLOCK_LITERAL" || node.type === "BLOCK_FOLDED");
|
|
35
|
+
return yaml.lines.linePos(node.range[0]).line + (length === path.length ? offset + (block && offset > 0 ? 1 : block ? 1 : 0) : 0);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Reads a YAML file of the content (`file` from the app's root); undefined, with its problems, when it is not valid YAML. */
|
|
39
|
+
function readYaml(root, file, problems) {
|
|
40
|
+
const lines = new LineCounter();
|
|
41
|
+
const doc = parseDocument(readFileSync(join(root, file), "utf8"), {
|
|
42
|
+
lineCounter: lines,
|
|
43
|
+
prettyErrors: false
|
|
44
|
+
});
|
|
45
|
+
if (doc.errors.length > 0) {
|
|
46
|
+
for (const error of doc.errors) {
|
|
47
|
+
const line = lines.linePos(error.pos[0]).line;
|
|
48
|
+
problems.add(`${file}:${line}: not valid YAML: ${error.message.split("\n")[0] ?? ""}`);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
file,
|
|
54
|
+
doc,
|
|
55
|
+
lines,
|
|
56
|
+
value: doc.toJS()
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** The schema's errors as problems: one per cause, the branches of texts and blocks folded. */
|
|
60
|
+
function schemaProblems(yaml, errors, problems, describe) {
|
|
61
|
+
const pathOf = (pointer) => pointer.split("/").slice(1).map((part) => part.replace(/~1/g, "/").replace(/~0/g, "~")).map((part) => /^\d+$/.test(part) ? Number(part) : part);
|
|
62
|
+
const texts = errors.filter((error) => error.keyword === "anyOf").map((error) => error.instancePath);
|
|
63
|
+
const underText = (error) => texts.some((text) => error.instancePath === text || error.instancePath.startsWith(`${text}/`));
|
|
64
|
+
for (const error of errors) {
|
|
65
|
+
const path = pathOf(error.instancePath);
|
|
66
|
+
const value = get(yaml.value, path);
|
|
67
|
+
const described = describe?.(error, path, value);
|
|
68
|
+
if (described !== void 0) {
|
|
69
|
+
if (described !== null) problems.at(yaml, described.path ?? path, described.message, described.lang);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
switch (error.keyword) {
|
|
73
|
+
case "if": break;
|
|
74
|
+
case "anyOf":
|
|
75
|
+
if (typeof value === "string") problems.at(yaml, path, "must not be empty");
|
|
76
|
+
else if (isObject(value)) {
|
|
77
|
+
for (const [lang, text] of Object.entries(value)) if (typeof text !== "string") problems.at(yaml, path, "must be a text", lang);
|
|
78
|
+
else if (text.trim() === "") problems.at(yaml, path, "must not be empty", lang);
|
|
79
|
+
else if (!/^[a-z]{2}$/.test(lang)) problems.at(yaml, path, `${lang} is not a language code; use two letters, e.g. de, en`);
|
|
80
|
+
} else problems.at(yaml, path, "must be a text, or a map of texts by language: { de: …, en: … }");
|
|
81
|
+
break;
|
|
82
|
+
case "required":
|
|
83
|
+
case "dependentRequired":
|
|
84
|
+
problems.at(yaml, path, `${String(error.params["missingProperty"])} is missing`);
|
|
85
|
+
break;
|
|
86
|
+
case "additionalProperties":
|
|
87
|
+
problems.at(yaml, path, `${String(error.params["additionalProperty"])} is not allowed here`);
|
|
88
|
+
break;
|
|
89
|
+
case "unevaluatedProperties":
|
|
90
|
+
problems.at(yaml, path, `${String(error.params["unevaluatedProperty"])} is not allowed here`);
|
|
91
|
+
break;
|
|
92
|
+
case "enum":
|
|
93
|
+
problems.at(yaml, path, `must be one of ${error.params["allowedValues"].join(", ")}`);
|
|
94
|
+
break;
|
|
95
|
+
case "pattern":
|
|
96
|
+
if (underText(error)) break;
|
|
97
|
+
if (error.schemaPath.includes("/id/") || error.schemaPath.startsWith("#/$defs/id")) problems.at(yaml, path, `use lower-case letters, digits and hyphens, e.g. "prozentwert"`);
|
|
98
|
+
else if (String(error.params["pattern"]) === "\\S") problems.at(yaml, path, "must not be empty");
|
|
99
|
+
else problems.at(yaml, path, `must match ${String(error.params["pattern"])}`);
|
|
100
|
+
break;
|
|
101
|
+
case "type":
|
|
102
|
+
case "minLength":
|
|
103
|
+
if (!underText(error)) problems.at(yaml, path, error.message ?? "is not valid");
|
|
104
|
+
break;
|
|
105
|
+
case "minItems":
|
|
106
|
+
problems.at(yaml, path, "must not be empty");
|
|
107
|
+
break;
|
|
108
|
+
default: if (!underText(error)) problems.at(yaml, path, error.message ?? "is not valid");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** The checks of the texts: languages, raw HTML, Markdown, formulas. */
|
|
113
|
+
var Texts = class {
|
|
114
|
+
links = [];
|
|
115
|
+
languages;
|
|
116
|
+
problems;
|
|
117
|
+
constructor(languages, problems) {
|
|
118
|
+
this.languages = languages;
|
|
119
|
+
this.problems = problems;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The text at `path` of `yaml`: a plain text, restricted Markdown, a formula, a name (a person, a work), or the source
|
|
123
|
+
* of a credit (a URL or a citation); a formula, a name or a URL given as a string is the same in every language, a
|
|
124
|
+
* citation is a plain text.
|
|
125
|
+
*/
|
|
126
|
+
check(yaml, path, kind) {
|
|
127
|
+
const value = get(yaml.value, path);
|
|
128
|
+
if (value === void 0) return;
|
|
129
|
+
const { findings, links } = checkText(value, kind, this.languages);
|
|
130
|
+
for (const finding of findings) this.problems.at(yaml, [...path, ...finding.path], finding.message, finding.lang, finding.line);
|
|
131
|
+
for (const link of links) this.links.push({
|
|
132
|
+
yaml,
|
|
133
|
+
path: [...path, ...link.path],
|
|
134
|
+
lang: link.lang,
|
|
135
|
+
offset: 0,
|
|
136
|
+
href: link.href
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
export { lineOf as a, schemaProblems as c, isObject as i, Texts as n, pathText as o, get as r, readYaml as s, Problems as t };
|