@lernapps/app-templates 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +195 -2
  3. package/dist/appname-Bh23cUuz.mjs +12 -0
  4. package/dist/assessment-By2GGTJC.mjs +1696 -0
  5. package/dist/block-BK_ohsH1.mjs +82 -0
  6. package/dist/blocks-BmRrD7UY.mjs +149 -0
  7. package/dist/blocks.mjs +4 -0
  8. package/dist/check-BkEl-m-F.mjs +254 -0
  9. package/dist/content-cYBPYg_J.mjs +140 -0
  10. package/dist/contract-PONpxJtd.mjs +52 -0
  11. package/dist/e2e-BiMI-pCL.mjs +89 -0
  12. package/dist/engine-BqF2Vffn.mjs +136 -0
  13. package/dist/explainer-e2e.mjs +1297 -0
  14. package/dist/explainer-islands.mjs +197 -0
  15. package/dist/explainer-plugin.mjs +2131 -0
  16. package/dist/explainer.mjs +1246 -0
  17. package/dist/html-mffkHGHh.mjs +89 -0
  18. package/dist/image-CspJVfyQ.mjs +21 -0
  19. package/dist/interactive-e2e.mjs +140 -0
  20. package/dist/interactive-plugin.mjs +242 -0
  21. package/dist/interactive.mjs +75 -0
  22. package/dist/load-C-0hUaSC.mjs +16947 -0
  23. package/dist/needs-BzEahJ6X.mjs +97 -0
  24. package/dist/pages-9RaaomoO.mjs +81 -0
  25. package/dist/questions-e2e.mjs +1 -0
  26. package/dist/questions-engine.mjs +3 -0
  27. package/dist/questions.mjs +6 -0
  28. package/dist/quiz-BKc8JYtC.mjs +70 -0
  29. package/dist/quiz-e2e.mjs +174 -0
  30. package/dist/quiz-plugin.mjs +266 -0
  31. package/dist/quiz.mjs +246 -0
  32. package/dist/render-BJey0bwl.mjs +540 -0
  33. package/dist/render-BL6fV_NI.mjs +507 -0
  34. package/dist/render-DpRO_RKm.mjs +146 -0
  35. package/dist/result-_eQcamoz.mjs +180 -0
  36. package/dist/tasks-DADUYy52.mjs +74 -0
  37. package/dist/validate-COaZso7u.mjs +1072 -0
  38. package/dist/validate-CxtdeQ5S.mjs +146 -0
  39. package/dist/vue-DO1m6q1R.mjs +40 -0
  40. package/dist/vue-code-BkJjW_I_.mjs +320 -0
  41. package/docs/arc42/01-introduction-and-goals.arc42.md +82 -0
  42. package/docs/arc42/02-constraints.arc42.md +228 -0
  43. package/docs/arc42/03-system-context.arc42.md +118 -0
  44. package/docs/arc42/04-solution-strategy.arc42.md +47 -0
  45. package/docs/arc42/05-building-blocks.arc42.md +307 -0
  46. package/docs/arc42/06-runtime-view.arc42.md +98 -0
  47. package/docs/arc42/07-deployment-view.arc42.md +160 -0
  48. package/docs/arc42/08-concepts.arc42.md +123 -0
  49. package/docs/arc42/09-decisions.arc42.md +246 -0
  50. package/docs/arc42/10-quality-requirements.arc42.md +155 -0
  51. package/docs/arc42/11-risks.arc42.md +113 -0
  52. package/docs/arc42/12-glossary.arc42.md +162 -0
  53. package/docs/arc42/architecture-evidence.md +395 -0
  54. package/explainer/.arc42ignore +3 -0
  55. package/explainer/README.md +54 -0
  56. package/explainer/_gitignore +5 -0
  57. package/explainer/docs/arc42/03-explainer-system-context.arc42.md +35 -0
  58. package/explainer/docs/arc42/05-explainer-building-blocks.arc42.md +659 -0
  59. package/explainer/docs/arc42/06-explainer-runtime-view.arc42.md +105 -0
  60. package/explainer/docs/arc42/07-explainer-deployment-view.arc42.md +34 -0
  61. package/explainer/docs/arc42/08-explainer-concepts.arc42.md +17 -0
  62. package/explainer/docs/arc42/09-explainer-decisions.arc42.md +443 -0
  63. package/explainer/docs/arc42/10-explainer-quality-requirements.arc42.md +54 -0
  64. package/explainer/docs/arc42/11-explainer-risks.arc42.md +17 -0
  65. package/explainer/docs/arc42/12-explainer-glossary.arc42.md +185 -0
  66. package/explainer/e2e/explainer.e2e.ts +3 -0
  67. package/explainer/index.html +11 -0
  68. package/explainer/package.json +18 -0
  69. package/explainer/playwright.config.ts +1 -0
  70. package/explainer/src/app.yaml +20 -0
  71. package/explainer/src/components/ProzentFeld.test.ts +30 -0
  72. package/explainer/src/components/ProzentFeld.ts +121 -0
  73. package/explainer/src/components/ProzentFeld.vue +84 -0
  74. package/explainer/src/images/hunderterfeld-klammern.svg +105 -0
  75. package/explainer/src/main.ts +4 -0
  76. package/explainer/src/prozent/chapter.yaml +18 -0
  77. package/explainer/src/prozent/grundwert.yaml +115 -0
  78. package/explainer/src/prozent/prozentsatz.yaml +99 -0
  79. package/explainer/src/prozent/prozentwert.yaml +119 -0
  80. package/explainer/tsconfig.json +1 -0
  81. package/explainer/vite.config.ts +4 -0
  82. package/interactive/.arc42ignore +5 -0
  83. package/interactive/README.md +28 -0
  84. package/interactive/_gitignore +4 -0
  85. package/interactive/docs/arc42/05-interactive-building-blocks.arc42.md +518 -0
  86. package/interactive/docs/arc42/06-interactive-runtime-view.arc42.md +58 -0
  87. package/interactive/docs/arc42/07-interactive-deployment-view.arc42.md +35 -0
  88. package/interactive/docs/arc42/08-interactive-concepts.arc42.md +52 -0
  89. package/interactive/docs/arc42/09-interactive-decisions.arc42.md +130 -0
  90. package/interactive/docs/arc42/10-interactive-quality-requirements.arc42.md +45 -0
  91. package/interactive/docs/arc42/11-interactive-risks.arc42.md +20 -0
  92. package/interactive/docs/arc42/12-interactive-glossary.arc42.md +80 -0
  93. package/interactive/e2e/interactive.e2e.ts +3 -0
  94. package/interactive/index.html +11 -0
  95. package/interactive/package.json +18 -0
  96. package/interactive/playwright.config.ts +1 -0
  97. package/interactive/src/app.yaml +28 -0
  98. package/interactive/src/core/index.ts +101 -0
  99. package/interactive/src/data/lessons.json +4 -0
  100. package/interactive/src/env.d.ts +6 -0
  101. package/interactive/src/main.ts +6 -0
  102. package/interactive/src/messages/de.json +12 -0
  103. package/interactive/src/playthroughs/grundreihe.yaml +33 -0
  104. package/interactive/src/playthroughs/obere-reihe.yaml +14 -0
  105. package/interactive/src/ui/App.vue +85 -0
  106. package/interactive/tsconfig.json +1 -0
  107. package/interactive/vite.config.ts +4 -0
  108. package/package.json +142 -4
  109. package/quiz/README.md +31 -0
  110. package/quiz/_gitignore +5 -0
  111. package/quiz/docs/arc42/05-quiz-building-blocks.arc42.md +283 -0
  112. package/quiz/docs/arc42/06-quiz-runtime-view.arc42.md +21 -0
  113. package/quiz/docs/arc42/07-quiz-deployment-view.arc42.md +33 -0
  114. package/quiz/docs/arc42/09-quiz-decisions.arc42.md +36 -0
  115. package/quiz/docs/arc42/10-quiz-quality-requirements.arc42.md +53 -0
  116. package/quiz/docs/arc42/11-quiz-risks.arc42.md +19 -0
  117. package/quiz/docs/arc42/12-quiz-glossary.arc42.md +27 -0
  118. package/quiz/e2e/quiz.e2e.ts +3 -0
  119. package/quiz/index.html +11 -0
  120. package/quiz/package.json +18 -0
  121. package/quiz/playwright.config.ts +1 -0
  122. package/quiz/src/images/berge-ordnen.jpg +0 -0
  123. package/quiz/src/images/everest-hoehe.jpg +0 -0
  124. package/quiz/src/images/galapagos.jpg +0 -0
  125. package/quiz/src/images/grand-canyon.jpg +0 -0
  126. package/quiz/src/images/totes-meer.jpg +0 -0
  127. package/quiz/src/images/uluru.jpg +0 -0
  128. package/quiz/src/images/wasserfaelle-laender.jpg +0 -0
  129. package/quiz/src/main.ts +4 -0
  130. package/quiz/src/quiz.json +312 -0
  131. package/quiz/tsconfig.json +1 -0
  132. package/quiz/vite.config.ts +4 -0
  133. package/runtime/explainer/assessment-client.ts +478 -0
  134. package/runtime/explainer/assessment-page.ts +446 -0
  135. package/runtime/explainer/assessment.ts +393 -0
  136. package/runtime/explainer/check-page.ts +520 -0
  137. package/runtime/explainer/components/exercise.ts +409 -0
  138. package/runtime/explainer/components/props.ts +230 -0
  139. package/runtime/explainer/components/read.ts +428 -0
  140. package/runtime/explainer/content.ts +277 -0
  141. package/runtime/explainer/e2e.ts +1474 -0
  142. package/runtime/explainer/exercises.ts +324 -0
  143. package/runtime/explainer/explainer.css +1283 -0
  144. package/runtime/explainer/explainer.ts +838 -0
  145. package/runtime/explainer/explainer.v1.schema.json +1165 -0
  146. package/runtime/explainer/expression.ts +407 -0
  147. package/runtime/explainer/fraction.ts +107 -0
  148. package/runtime/explainer/generated/comparator.ts +31 -0
  149. package/runtime/explainer/generated/question.ts +165 -0
  150. package/runtime/explainer/generated/soundness.ts +280 -0
  151. package/runtime/explainer/generated/texts.ts +257 -0
  152. package/runtime/explainer/generated/types.ts +103 -0
  153. package/runtime/explainer/generated/values.ts +136 -0
  154. package/runtime/explainer/islands.ts +281 -0
  155. package/runtime/explainer/load.ts +457 -0
  156. package/runtime/explainer/messages/de.json +200 -0
  157. package/runtime/explainer/messages/en.json +200 -0
  158. package/runtime/explainer/messages.ts +13 -0
  159. package/runtime/explainer/needs.ts +122 -0
  160. package/runtime/explainer/open-task.ts +235 -0
  161. package/runtime/explainer/outline.ts +299 -0
  162. package/runtime/explainer/plugin.ts +488 -0
  163. package/runtime/explainer/position.ts +62 -0
  164. package/runtime/explainer/prompt.ts +75 -0
  165. package/runtime/explainer/render.ts +686 -0
  166. package/runtime/explainer/result.ts +238 -0
  167. package/runtime/explainer/routes.ts +119 -0
  168. package/runtime/explainer/schema.ts +140 -0
  169. package/runtime/explainer/scope.ts +87 -0
  170. package/runtime/explainer/stuck.ts +57 -0
  171. package/runtime/explainer/tasks.ts +109 -0
  172. package/runtime/explainer/tutor.ts +469 -0
  173. package/runtime/explainer/tutorpage.ts +669 -0
  174. package/runtime/explainer/video.ts +123 -0
  175. package/runtime/interactive/contract.ts +121 -0
  176. package/runtime/interactive/e2e.ts +190 -0
  177. package/runtime/interactive/interactive.css +82 -0
  178. package/runtime/interactive/interactive.ts +115 -0
  179. package/runtime/interactive/interactive.v1.schema.json +146 -0
  180. package/runtime/interactive/load.ts +188 -0
  181. package/runtime/interactive/messages/de.json +14 -0
  182. package/runtime/interactive/messages/en.json +14 -0
  183. package/runtime/interactive/messages.ts +11 -0
  184. package/runtime/interactive/playthrough.v1.schema.json +143 -0
  185. package/runtime/interactive/plugin.ts +266 -0
  186. package/runtime/interactive/render.ts +126 -0
  187. package/runtime/quiz/e2e.ts +233 -0
  188. package/runtime/quiz/engine.ts +242 -0
  189. package/runtime/quiz/messages/de.json +23 -0
  190. package/runtime/quiz/messages/en.json +23 -0
  191. package/runtime/quiz/plugin.ts +150 -0
  192. package/runtime/quiz/quiz.css +154 -0
  193. package/runtime/quiz/quiz.ts +56 -0
  194. package/runtime/quiz/quiz.v1.schema.json +72 -0
  195. package/runtime/quiz/render.ts +173 -0
  196. package/runtime/quiz/validate.ts +108 -0
  197. package/runtime/shared/appname.ts +16 -0
  198. package/runtime/shared/blocks/block.ts +162 -0
  199. package/runtime/shared/blocks/blocks.css +190 -0
  200. package/runtime/shared/blocks/blocks.v1.schema.json +432 -0
  201. package/runtime/shared/blocks/code.ts +26 -0
  202. package/runtime/shared/blocks/figures.ts +49 -0
  203. package/runtime/shared/blocks/image.ts +25 -0
  204. package/runtime/shared/blocks/index.ts +13 -0
  205. package/runtime/shared/blocks/messages/de.json +4 -0
  206. package/runtime/shared/blocks/messages/en.json +4 -0
  207. package/runtime/shared/blocks/render.ts +134 -0
  208. package/runtime/shared/blocks/validate.ts +165 -0
  209. package/runtime/shared/content.ts +197 -0
  210. package/runtime/shared/examples.ts +20 -0
  211. package/runtime/shared/html.ts +10 -0
  212. package/runtime/shared/languages.ts +94 -0
  213. package/runtime/shared/page.css +101 -0
  214. package/runtime/shared/pages.ts +114 -0
  215. package/runtime/shared/questions/e2e.ts +115 -0
  216. package/runtime/shared/questions/engine.ts +209 -0
  217. package/runtime/shared/questions/index.ts +39 -0
  218. package/runtime/shared/questions/messages/de.json +26 -0
  219. package/runtime/shared/questions/messages/en.json +26 -0
  220. package/runtime/shared/questions/question.ts +430 -0
  221. package/runtime/shared/questions/question.v1.schema.json +342 -0
  222. package/runtime/shared/questions/questions.css +192 -0
  223. package/runtime/shared/questions/render.ts +247 -0
  224. package/runtime/shared/questions/validate.ts +518 -0
  225. package/runtime/shared/schema.ts +44 -0
  226. package/runtime/shared/text/check.ts +68 -0
  227. package/runtime/shared/text/formula.ts +69 -0
  228. package/runtime/shared/text/markdown.ts +185 -0
  229. package/runtime/shared/text/text.v1.schema.json +34 -0
  230. package/runtime/shared/vue-code.ts +395 -0
  231. package/runtime/shared/vue.ts +38 -0
@@ -0,0 +1,507 @@
1
+ import { i as inLanguage, l as textsIn, o as languageListProblems, r as LANGUAGES } from "./html-mffkHGHh.mjs";
2
+ import { a as plain, s as renderMarkdown } from "./check-BkEl-m-F.mjs";
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-cYBPYg_J.mjs";
5
+ import { Ajv2020 } from "ajv/dist/2020.js";
6
+ import { existsSync, readdirSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ // runtime/interactive/interactive.v1.schema.json
9
+ var interactive_v1_schema_default = {
10
+ $schema: "https://json-schema.org/draft/2020-12/schema",
11
+ $id: "urn:lernapps:schema:interactive.v1",
12
+ title: "The start page of a lernapps interactive app",
13
+ description: "src/app.yaml of an app built from the archetype interactive (runtime @lernapps/app-templates): what the static start page says, readable without JavaScript, the app's languages and its routes, the modes or levels that each have an address app/#<id>. The build validates it against this schema, then what a schema cannot say (languages complete, restricted Markdown, unique ids), and fails with one line per problem. Every text is for learners, in plain language. A text is a string while German is the app's only language, else a map of texts by language: { de: …, en: … }.",
14
+ type: "object",
15
+ additionalProperties: false,
16
+ required: [
17
+ "title",
18
+ "description",
19
+ "howto",
20
+ "competencies",
21
+ "languages"
22
+ ],
23
+ properties: {
24
+ "title": {
25
+ "description": "The app's title: the h1 of the start page and of the app page, and the title of both.",
26
+ "$ref": "#/$defs/text"
27
+ },
28
+ "description": {
29
+ "description": "What the app does and for whom, under the title of the start page.",
30
+ "$ref": "#/$defs/markdown"
31
+ },
32
+ "howto": {
33
+ "description": "How to play or use the app: the keys, the goal, what counts. The start page shows it under \"So geht's\".",
34
+ "$ref": "#/$defs/markdown"
35
+ },
36
+ "competencies": {
37
+ "description": "What a learner can do after practising with the app, each an \"Ich kann …\" statement. The start page lists them under \"Das lernst du\".",
38
+ "type": "array",
39
+ "minItems": 1,
40
+ "items": { "$ref": "#/$defs/text" }
41
+ },
42
+ "languages": {
43
+ "description": "The languages of the app, German first. Every text is then complete in each of them; each language other than German gets its own pages below /<language>/.",
44
+ "type": "array",
45
+ "minItems": 1,
46
+ "items": {
47
+ "type": "string",
48
+ "pattern": "^[a-z]{2}$"
49
+ }
50
+ },
51
+ "routes": {
52
+ "description": "Optional: the modes or levels of the app, each with its address app/#<id>, linked from the start page and listed on the app page. The core starts with the route of the address (start(seed, route)), the first one when the address names none. A topic of the app's catalog entry may link one: app/#<id>.",
53
+ "type": "array",
54
+ "minItems": 1,
55
+ "items": { "$ref": "#/$defs/route" }
56
+ },
57
+ "sections": {
58
+ "description": "Optional: background sections at the end of the start page, each a heading with its text; its id is its anchor.",
59
+ "type": "array",
60
+ "minItems": 1,
61
+ "items": { "$ref": "#/$defs/section" }
62
+ }
63
+ },
64
+ $defs: {
65
+ "id": {
66
+ "description": "A stable name, part of the address: lower-case letters, digits and hyphens. Keep it once published, so that links to it keep working.",
67
+ "type": "string",
68
+ "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
69
+ },
70
+ "text": {
71
+ "description": "A text without formatting: a string while German is the only language of the app, else a map of texts by language ({ de: …, en: … }) with every declared language.",
72
+ "anyOf": [{
73
+ "type": "string",
74
+ "minLength": 1,
75
+ "pattern": "\\S"
76
+ }, {
77
+ "type": "object",
78
+ "minProperties": 1,
79
+ "propertyNames": { "pattern": "^[a-z]{2}$" },
80
+ "additionalProperties": {
81
+ "type": "string",
82
+ "minLength": 1,
83
+ "pattern": "\\S"
84
+ }
85
+ }]
86
+ },
87
+ "markdown": {
88
+ "description": "A text in restricted Markdown (paragraphs, **bold**, *italics*, lists, links, $…$ formulas; no headings, no raw HTML): a string while German is the only language of the app, else a map of texts by language.",
89
+ "anyOf": [{
90
+ "type": "string",
91
+ "minLength": 1,
92
+ "pattern": "\\S"
93
+ }, {
94
+ "type": "object",
95
+ "minProperties": 1,
96
+ "propertyNames": { "pattern": "^[a-z]{2}$" },
97
+ "additionalProperties": {
98
+ "type": "string",
99
+ "minLength": 1,
100
+ "pattern": "\\S"
101
+ }
102
+ }]
103
+ },
104
+ "route": {
105
+ "description": "A mode or level of the app.",
106
+ "type": "object",
107
+ "additionalProperties": false,
108
+ "required": ["id", "title"],
109
+ "properties": {
110
+ "id": {
111
+ "description": "Its anchor: the address app/#<id>.",
112
+ "$ref": "#/$defs/id"
113
+ },
114
+ "title": {
115
+ "description": "Its name, the text of its links.",
116
+ "$ref": "#/$defs/text"
117
+ },
118
+ "description": {
119
+ "description": "Optional: what it practises, next to its link on the start page.",
120
+ "$ref": "#/$defs/text"
121
+ }
122
+ }
123
+ },
124
+ "section": {
125
+ "description": "A background section of the start page.",
126
+ "type": "object",
127
+ "additionalProperties": false,
128
+ "required": [
129
+ "id",
130
+ "title",
131
+ "text"
132
+ ],
133
+ "properties": {
134
+ "id": {
135
+ "description": "Its anchor on the start page: #<id>.",
136
+ "$ref": "#/$defs/id"
137
+ },
138
+ "title": {
139
+ "description": "Its heading.",
140
+ "$ref": "#/$defs/text"
141
+ },
142
+ "text": { "$ref": "#/$defs/markdown" }
143
+ }
144
+ }
145
+ }
146
+ };
147
+ // runtime/interactive/playthrough.v1.schema.json
148
+ var playthrough_v1_schema_default = {
149
+ $schema: "https://json-schema.org/draft/2020-12/schema",
150
+ $id: "urn:lernapps:schema:playthrough.v1",
151
+ title: "A playthrough of a lernapps interactive app",
152
+ description: "A file src/playthroughs/<name>.yaml of an app built from the archetype interactive: what a learner does, from the start of a route with a seed: inputs (keys typed, actions of the app's root component) and time passing (ticks), and after each step what is expected: the state of the core (the fields named, compared with the state step(state, input) and tick(state, dt) give) or the announcement for screen readers (@lernapps/tooling/a11y), or both. The build plays every playthrough against the core and fails with one line per mismatch; the end-to-end tests play the same inputs and ticks in the browser. In both, no time passes but the ticks a step names.",
153
+ type: "object",
154
+ additionalProperties: false,
155
+ required: ["seed", "steps"],
156
+ properties: {
157
+ "route": {
158
+ "description": "The route it starts at, an id of routes in src/app.yaml; the first route when it names none.",
159
+ "type": "string",
160
+ "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
161
+ },
162
+ "seed": {
163
+ "description": "The seed it starts with: start(seed, route); in the browser the address app/?seed=<seed>#<route>.",
164
+ "type": "integer",
165
+ "minimum": 1,
166
+ "maximum": 2147483647
167
+ },
168
+ "steps": {
169
+ "description": "The steps, in order.",
170
+ "type": "array",
171
+ "minItems": 1,
172
+ "items": { "$ref": "#/$defs/step" }
173
+ }
174
+ },
175
+ $defs: {
176
+ "step": {
177
+ "description": "What happens: keys or inputs, then ticks, at least one of them; then what is expected after them: state, announce, or both.",
178
+ "type": "object",
179
+ "additionalProperties": false,
180
+ "properties": {
181
+ "keys": {
182
+ "description": "Keys, one after the other, each as the browser names it (KeyboardEvent.key): a, A, Enter, \" \" for the space bar, ArrowLeft. The short form of inputs that are all keys; a step has keys or inputs, not both.",
183
+ "type": "array",
184
+ "minItems": 1,
185
+ "items": {
186
+ "type": "string",
187
+ "minLength": 1
188
+ }
189
+ },
190
+ "inputs": {
191
+ "description": "Inputs, one after the other: a key ({ kind: key, key: a }), pressed on the keyboard in the browser, or an action of the app's root component ({ kind: action, name: again }, with a value if it has one), emitted by the root component as input in the browser.",
192
+ "type": "array",
193
+ "minItems": 1,
194
+ "items": { "$ref": "#/$defs/input" }
195
+ },
196
+ "ticks": {
197
+ "description": "The ticks that pass after the keys or inputs: tick(state, TICK) that many times, TICK = 20 ms, so 50 ticks are a second.",
198
+ "type": "integer",
199
+ "minimum": 1
200
+ },
201
+ "state": { "description": "The state expected after the step: the fields named, each compared in the same way; a list or any other value is compared as a whole." },
202
+ "announce": {
203
+ "description": "The announcement expected after the step, in German: what @lernapps/tooling/a11y's announce said last. Only the browser can see it.",
204
+ "type": "string",
205
+ "minLength": 1,
206
+ "pattern": "\\S"
207
+ }
208
+ }
209
+ },
210
+ "input": {
211
+ "description": "An input to the core: step(state, input). Told apart by kind.",
212
+ "type": "object",
213
+ "required": ["kind"],
214
+ "properties": { "kind": { "enum": ["key", "action"] } },
215
+ "allOf": [{
216
+ "if": {
217
+ "type": "object",
218
+ "properties": { "kind": { "const": "key" } }
219
+ },
220
+ "then": { "$ref": "#/$defs/key" }
221
+ }, {
222
+ "if": {
223
+ "type": "object",
224
+ "properties": { "kind": { "const": "action" } }
225
+ },
226
+ "then": { "$ref": "#/$defs/action" }
227
+ }]
228
+ },
229
+ "key": {
230
+ "description": "A key, as the browser names it (KeyboardEvent.key).",
231
+ "type": "object",
232
+ "additionalProperties": false,
233
+ "required": ["kind", "key"],
234
+ "properties": {
235
+ "kind": { "const": "key" },
236
+ "key": {
237
+ "type": "string",
238
+ "minLength": 1
239
+ }
240
+ }
241
+ },
242
+ "action": {
243
+ "description": "An action the app's root component emits as input, for what the learner does with the pointer or by touch.",
244
+ "type": "object",
245
+ "additionalProperties": false,
246
+ "required": ["kind", "name"],
247
+ "properties": {
248
+ "kind": { "const": "action" },
249
+ "name": {
250
+ "description": "The name the app gives the action.",
251
+ "type": "string",
252
+ "minLength": 1,
253
+ "pattern": "\\S"
254
+ },
255
+ "value": { "description": "The JSON value the action carries, if it needs one." }
256
+ }
257
+ }
258
+ }
259
+ };
260
+ // runtime/interactive/load.ts
261
+ /** Where an app finds the schemas of its start page and of its playthroughs, for the messages that name them. */
262
+ const SCHEMA_FILE = "node_modules/@lernapps/app-templates/runtime/interactive/interactive.v1.schema.json";
263
+ const PLAYTHROUGH_SCHEMA_FILE = "node_modules/@lernapps/app-templates/runtime/interactive/playthrough.v1.schema.json";
264
+ const RULE_URL = "https://lernapps.net/tooling/rules/#interactive-app-valid";
265
+ const APP = "src/app.yaml";
266
+ const PLAYTHROUGHS = "src/playthroughs";
267
+ /** The app is not valid: one problem per line in `problems`. */
268
+ var InteractiveError = class extends Error {
269
+ problems;
270
+ constructor(problems) {
271
+ super([
272
+ `the interactive app in src/ is not valid (${RULE_URL}):`,
273
+ ...problems.map((problem) => ` - ${problem}`),
274
+ `Fix it as the schemas say: ${SCHEMA_FILE}, ${PLAYTHROUGH_SCHEMA_FILE}`
275
+ ].join("\n"));
276
+ this.problems = problems;
277
+ }
278
+ };
279
+ const ajv = new Ajv2020({
280
+ allErrors: true,
281
+ strict: true,
282
+ allowUnionTypes: true
283
+ });
284
+ const validators = {
285
+ app: ajv.compile(interactive_v1_schema_default),
286
+ playthrough: ajv.compile(playthrough_v1_schema_default)
287
+ };
288
+ function validate(yaml, kind, problems) {
289
+ const check = validators[kind];
290
+ if (check(yaml.value)) return true;
291
+ const before = problems.lines.length;
292
+ schemaProblems(yaml, check.errors ?? [], problems);
293
+ if (problems.lines.length === before) problems.at(yaml, [], "does not match the schema");
294
+ return false;
295
+ }
296
+ /** The problems of a list of ids: each once. */
297
+ function uniqueIds(yaml, key, items, problems) {
298
+ items.forEach((item, index) => {
299
+ if (items.findIndex((other) => other.id === item.id) !== index) problems.at(yaml, [
300
+ key,
301
+ index,
302
+ "id"
303
+ ], `${item.id} is used twice in ${key}; give each its own id`);
304
+ });
305
+ }
306
+ /** The app in `root`, or the problems that keep it from building. */
307
+ function check(root) {
308
+ const problems = new Problems();
309
+ if (!existsSync(join(root, "src/app.yaml"))) {
310
+ problems.add(`${APP} is missing: the start page of the app comes from it (${SCHEMA_FILE})`);
311
+ return { problems: problems.lines };
312
+ }
313
+ const appYaml = readYaml(root, APP, problems);
314
+ if (appYaml === void 0 || !validate(appYaml, "app", problems)) return { problems: problems.lines };
315
+ const app = appYaml.value;
316
+ const languageList = languageListProblems(app.languages, LANGUAGES);
317
+ for (const problem of languageList) problems.at(appYaml, ["languages", Math.max(app.languages.lastIndexOf(problem.lang), 0)], problem.message);
318
+ if (languageList.length > 0) return { problems: problems.lines };
319
+ const texts = new Texts(app.languages, problems);
320
+ texts.check(appYaml, ["title"], "plain");
321
+ texts.check(appYaml, ["description"], "markdown");
322
+ texts.check(appYaml, ["howto"], "markdown");
323
+ app.competencies.forEach((_, index) => texts.check(appYaml, ["competencies", index], "plain"));
324
+ (app.routes ?? []).forEach((_, index) => {
325
+ texts.check(appYaml, [
326
+ "routes",
327
+ index,
328
+ "title"
329
+ ], "plain");
330
+ texts.check(appYaml, [
331
+ "routes",
332
+ index,
333
+ "description"
334
+ ], "plain");
335
+ });
336
+ (app.sections ?? []).forEach((_, index) => {
337
+ texts.check(appYaml, [
338
+ "sections",
339
+ index,
340
+ "title"
341
+ ], "plain");
342
+ texts.check(appYaml, [
343
+ "sections",
344
+ index,
345
+ "text"
346
+ ], "markdown");
347
+ });
348
+ uniqueIds(appYaml, "routes", app.routes ?? [], problems);
349
+ uniqueIds(appYaml, "sections", app.sections ?? [], problems);
350
+ if (problems.lines.length > 0) return { problems: problems.lines };
351
+ const routes = (app.routes ?? []).map((route) => route.id);
352
+ const files = existsSync(join(root, "src/playthroughs")) ? readdirSync(join(root, PLAYTHROUGHS)).filter((file) => file.endsWith(".yaml")).sort() : [];
353
+ if (files.length === 0) problems.add(`${PLAYTHROUGHS}/: no playthrough; write at least one, <name>.yaml: a seed, and steps of keys, inputs or ticks with the state or announcement expected after them (${PLAYTHROUGH_SCHEMA_FILE})`);
354
+ const playthroughs = [];
355
+ for (const file of files) {
356
+ const yaml = readYaml(root, `${PLAYTHROUGHS}/${file}`, problems);
357
+ if (yaml === void 0 || !validate(yaml, "playthrough", problems)) continue;
358
+ const playthrough = yaml.value;
359
+ if (playthrough.route !== void 0 && !routes.includes(playthrough.route)) problems.at(yaml, ["route"], routes.length === 0 ? `${playthrough.route}: ${APP} has no routes; leave route out` : `${playthrough.route} is not a route of ${APP}; use one of ${routes.join(", ")}`);
360
+ playthrough.steps.forEach((step, index) => {
361
+ if (step.keys !== void 0 && step.inputs !== void 0) problems.at(yaml, [
362
+ "steps",
363
+ index,
364
+ "inputs"
365
+ ], "keys and inputs in one step; write the keys as key inputs ({ kind: key, key: a }) in inputs, or give each its own step");
366
+ else if (step.keys === void 0 && step.inputs === void 0 && step.ticks === void 0) problems.at(yaml, ["steps", index], "does nothing; give the keys (keys) or inputs (inputs) of the step, the ticks that pass (ticks), or both");
367
+ else if (step.state === void 0 && step.announce === void 0) problems.at(yaml, ["steps", index], "expects nothing; give the state expected after the step (state), the announcement (announce), or both");
368
+ });
369
+ playthroughs.push({
370
+ yaml,
371
+ playthrough
372
+ });
373
+ }
374
+ if (problems.lines.length > 0) return { problems: problems.lines };
375
+ return {
376
+ interactive: {
377
+ app,
378
+ appYaml,
379
+ playthroughs
380
+ },
381
+ problems: []
382
+ };
383
+ }
384
+ /** The app in `root`; an InteractiveError with every problem when it is not valid. */
385
+ function loadInteractive(root) {
386
+ const { interactive, problems } = check(root);
387
+ if (interactive === void 0) throw new InteractiveError(problems);
388
+ return interactive;
389
+ }
390
+ // runtime/interactive/messages.ts
391
+ const MESSAGES = {
392
+ de: {
393
+ language: "Deutsch",
394
+ languages: "Sprache",
395
+ play: "Los geht's",
396
+ routes: "Auswahl",
397
+ howto: "So geht's",
398
+ competencies: "Das lernst du",
399
+ noscript: "Für die App brauchst du JavaScript. Schalte es in deinem Browser ein. Alles andere auf dieser Seite kannst du auch ohne lesen.",
400
+ noscriptApp: "Diese App braucht JavaScript. Schalte es in deinem Browser ein. Was die App macht, steht auf der Startseite.",
401
+ toStart: "Zur Startseite",
402
+ footer: "Eine interaktive App auf lernapps.net",
403
+ imprint: "Impressum",
404
+ privacy: "Datenschutz"
405
+ },
406
+ en: {
407
+ language: "English",
408
+ languages: "Language",
409
+ play: "Let's go",
410
+ routes: "Choose",
411
+ howto: "How it works",
412
+ competencies: "What you learn",
413
+ noscript: "The app needs JavaScript. Turn it on in your browser. You can read everything else on this page without it.",
414
+ noscriptApp: "This app needs JavaScript. Turn it on in your browser. The start page says what the app does.",
415
+ toStart: "To the start page",
416
+ footer: "An interactive app on lernapps.net",
417
+ imprint: "Imprint",
418
+ privacy: "Privacy"
419
+ }
420
+ };
421
+ /** The text of `key` in `lang`. */
422
+ const message = (lang, key) => textsIn(MESSAGES, lang)[key];
423
+ // runtime/interactive/render.ts
424
+ /** The address of the app page, from a language tree's root. */
425
+ const APP_PAGE = "app/";
426
+ /** Every page of the app, in every language. */
427
+ function pages(app) {
428
+ return app.languages.flatMap((lang) => ["start", "app"].map((kind) => {
429
+ const local = kind === "start" ? "" : APP_PAGE;
430
+ return {
431
+ kind,
432
+ lang,
433
+ path: `${languageRoot(lang)}${local}`,
434
+ local
435
+ };
436
+ }));
437
+ }
438
+ /** The ids of the routes, in order: the app page's anchors. */
439
+ const routeIds = (app) => (app.routes ?? []).map((route) => route.id);
440
+ /** One page of the app. */
441
+ function renderPage(app, page) {
442
+ const { lang, path } = page;
443
+ const t = (key) => plain(message(lang, key));
444
+ const text = (value) => plain(inLanguage(value, lang));
445
+ /** A link from this page to `to` (from the language tree's root). */
446
+ const to = (target) => relative(path, `${languageRoot(lang)}${target}`);
447
+ const markdown = (value) => renderMarkdown(inLanguage(value, lang), (href) => href.startsWith("/") ? to(href.slice(1)) : href);
448
+ const title = text(app.title);
449
+ const header = pageHeader({
450
+ ...page,
451
+ languages: app.languages,
452
+ label: t("languages"),
453
+ name: (other) => plain(message(other, "language"))
454
+ });
455
+ const footer = pageFooter({
456
+ footer: t("footer"),
457
+ imprint: t("imprint"),
458
+ privacy: t("privacy")
459
+ });
460
+ const routes = app.routes ?? [];
461
+ let main;
462
+ if (page.kind === "start") {
463
+ const routeList = routes.length === 0 ? "" : `<section class="interactive-section"><h2>${t("routes")}</h2><ul class="interactive-route-list">${routes.map((route) => {
464
+ const description = route.description === void 0 ? "" : ` <span class="interactive-description">${text(route.description)}</span>`;
465
+ return `<li><a href="${to(`${APP_PAGE}#${route.id}`)}">${text(route.title)}</a>${description}</li>`;
466
+ }).join("")}</ul></section>`;
467
+ main = [
468
+ `<h1>${title}</h1>`,
469
+ `<div class="interactive-intro">${markdown(app.description)}</div>`,
470
+ `<noscript><p class="interactive-noscript">${t("noscript")}</p></noscript>`,
471
+ `<p class="interactive-play"><a class="interactive-start" href="${to(APP_PAGE)}">${t("play")}</a></p>`,
472
+ routeList,
473
+ `<section class="interactive-section interactive-howto"><h2>${t("howto")}</h2>${markdown(app.howto)}</section>`,
474
+ `<section class="interactive-section"><h2>${t("competencies")}</h2><ul class="interactive-competencies">${app.competencies.map((competency) => `<li>${text(competency)}</li>`).join("")}</ul></section>`,
475
+ ...(app.sections ?? []).map((section) => `<section class="interactive-section"><h2 id="${section.id}">${text(section.title)}</h2>${markdown(section.text)}</section>`)
476
+ ].filter((part) => part !== "").join("\n");
477
+ } else {
478
+ const nav = routes.length === 0 ? "" : `<nav class="interactive-routes" aria-label="${t("routes")}"><ul>${routes.map((route) => `<li id="${route.id}"><a href="#${route.id}">${text(route.title)}</a></li>`).join("")}</ul></nav>`;
479
+ main = [
480
+ `<div class="interactive-title"><h1>${title}</h1><div class="interactive-title-slot" hidden></div></div>`,
481
+ `<noscript><p class="interactive-noscript">${t("noscriptApp")} <a href="${to("")}">${t("toStart")}</a></p></noscript>`,
482
+ nav,
483
+ `<div class="interactive-app" data-routes="${plain(JSON.stringify(routeIds(app)))}"></div>`
484
+ ].filter((part) => part !== "").join("\n");
485
+ }
486
+ const body = [
487
+ header,
488
+ `<main class="interactive" id="inhalt">`,
489
+ main,
490
+ `</main>`,
491
+ footer
492
+ ].filter((part) => part !== "").join("\n");
493
+ return {
494
+ page,
495
+ head: [
496
+ `<title>${page.kind === "start" ? title : `${t("play")} – ${title}`}</title>`,
497
+ `<meta name="description" content="${plain(inLanguage(app.description, lang).replace(/\s+/g, " ").trim())}" />`,
498
+ `<meta name="lernapps-archetype" content="interactive" />`,
499
+ ...alternateLinks({
500
+ ...page,
501
+ languages: app.languages
502
+ })
503
+ ].join("\n "),
504
+ body
505
+ };
506
+ }
507
+ export { InteractiveError as a, message as i, pages as n, loadInteractive as o, renderPage as r, APP_PAGE as t };
@@ -0,0 +1,146 @@
1
+ import { l as textsIn, s as localeOf, t as escape } from "./html-mffkHGHh.mjs";
2
+ import { n as renderMaterial } from "./blocks-BmRrD7UY.mjs";
3
+ import { a as renderQuestion, g as questionId, o as renderSolution, r as external, t as QUESTION_MESSAGES } from "./render-BJey0bwl.mjs";
4
+ import { a as de_default, i as en_default, r as solutionId } from "./quiz-BKc8JYtC.mjs";
5
+ import { a as pageFooter, o as pageHeader, r as languageRoot, s as relative, t as alternateLinks } from "./pages-9RaaomoO.mjs";
6
+ import { translator } from "@lernapps/tooling/i18n";
7
+ // runtime/quiz/render.ts
8
+ /** The quiz's texts for learners in each language it has them: a quiz may declare only these languages. */
9
+ const QUIZ_MESSAGES = {
10
+ de: {
11
+ ...QUESTION_MESSAGES.de,
12
+ ...de_default
13
+ },
14
+ en: {
15
+ ...QUESTION_MESSAGES.en,
16
+ ...en_default
17
+ }
18
+ };
19
+ /** The quiz's texts in `lang`; German for a language it does not have. */
20
+ const messagesIn = (lang) => textsIn(QUIZ_MESSAGES, lang);
21
+ const languages = /* @__PURE__ */ new Map();
22
+ function wordsIn(lang = "de") {
23
+ let found = languages.get(lang);
24
+ if (found === void 0) {
25
+ found = { t: translator(messagesIn(lang), localeOf(lang)) };
26
+ languages.set(lang, found);
27
+ }
28
+ return found;
29
+ }
30
+ function solutionItem(question, lang) {
31
+ const { t } = wordsIn(lang);
32
+ return [
33
+ `<li class="quiz-solution" id="${solutionId(question)}">`,
34
+ `<h3>${escape(question.text)}</h3>`,
35
+ renderSolution(question, { lang }),
36
+ `<p><a href="#${questionId(question)}">${t("toQuestion")}</a></p>`,
37
+ `</li>`
38
+ ].join("\n");
39
+ }
40
+ function renderSources(quiz, { t }) {
41
+ if (quiz.sources === void 0) return [];
42
+ return [
43
+ `<section class="quiz-sources" id="quellen">`,
44
+ `<h2>${t("sources")}</h2>`,
45
+ `<p>${t("sourcesIntro")}</p>`,
46
+ `<ul>`,
47
+ ...quiz.sources.map((source) => `<li>${source.url === void 0 ? escape(source.title) : external({
48
+ title: source.title,
49
+ url: source.url
50
+ })}</li>`),
51
+ `</ul>`,
52
+ `</section>`
53
+ ];
54
+ }
55
+ /** The form with which the learner chooses how many questions to answer; the engine shows it (none for one question). */
56
+ function renderStart(total, t) {
57
+ if (total < 2) return "";
58
+ return [
59
+ `<form class="quiz-start" hidden>`,
60
+ `<p><label for="quiz-count">${t("countLabel")}</label> `,
61
+ `<input id="quiz-count" type="number" inputmode="numeric" min="1" max="${total}" value="${total}" required> `,
62
+ `<span>${t("countOf", { total })}</span></p>`,
63
+ `<p><button type="submit">${t("start")}</button></p>`,
64
+ `</form>`
65
+ ].join("");
66
+ }
67
+ /**
68
+ * The page of the quiz in its data's language (German without one): its title and description, its alternates, and
69
+ * the quiz itself for the page's body, after a language switch when the quiz has several `languages`.
70
+ */
71
+ function renderQuiz(data, { languages = ["de"], sizeless } = {}) {
72
+ const { quiz } = data;
73
+ const lang = data.lang ?? "de";
74
+ const words = wordsIn(lang);
75
+ const { t } = words;
76
+ const where = {
77
+ path: languageRoot(lang),
78
+ local: "",
79
+ languages
80
+ };
81
+ const forEngine = {
82
+ ...data,
83
+ quiz: {
84
+ ...data.quiz,
85
+ questions: data.quiz.questions.map(({ material: _material, ...question }) => question)
86
+ }
87
+ };
88
+ const json = JSON.stringify(forEngine).replace(/</g, "\\u003c");
89
+ const material = {
90
+ lang,
91
+ href: (image) => relative(where.path, image),
92
+ ...sizeless === void 0 ? {} : { sizeless }
93
+ };
94
+ const body = [
95
+ pageHeader({
96
+ ...where,
97
+ lang,
98
+ label: escape(t("languages")),
99
+ name: (other) => escape(messagesIn(other).language)
100
+ }),
101
+ `<main class="quiz" id="quiz">`,
102
+ `<h1>${escape(quiz.title)}</h1>`,
103
+ `<p class="quiz-intro">${escape(quiz.intro)}</p>`,
104
+ `<p class="quiz-howto">${t("howto")}</p>`,
105
+ `<noscript><p>${t("noscript")}</p></noscript>`,
106
+ `<h2 class="quiz-heading">${t("questions")}</h2>`,
107
+ renderStart(quiz.questions.length, t),
108
+ `<p class="quiz-progress" hidden></p>`,
109
+ `<ol class="quiz-questions">`,
110
+ ...quiz.questions.map((question) => renderQuestion(question, {
111
+ element: "li",
112
+ next: true,
113
+ lang,
114
+ ...question.material === void 0 ? {} : { materialHtml: renderMaterial(question.material, material) }
115
+ })),
116
+ `</ol>`,
117
+ `<section class="quiz-result" id="ergebnis" hidden tabindex="-1">`,
118
+ `<h2>${t("result")}</h2>`,
119
+ `<p class="quiz-score"></p>`,
120
+ `<p class="quiz-last" hidden></p>`,
121
+ `<p><a class="quiz-restart" href="./">${t("restart")}</a></p>`,
122
+ `</section>`,
123
+ `<section class="quiz-solutions" id="loesungen">`,
124
+ `<h2>${t("solutions")}</h2>`,
125
+ `<ol>`,
126
+ ...quiz.questions.map((question) => solutionItem(question, lang)),
127
+ `</ol>`,
128
+ `</section>`,
129
+ ...renderSources(quiz, words),
130
+ `</main>`,
131
+ pageFooter({
132
+ footer: t("footer"),
133
+ imprint: t("imprint"),
134
+ privacy: t("privacy")
135
+ }),
136
+ `<script type="application/json" id="quiz-data">${json}<\/script>`
137
+ ].filter((part) => part !== "").join("\n");
138
+ const alternates = languages.length < 2 ? [] : alternateLinks(where);
139
+ return {
140
+ title: quiz.title,
141
+ description: quiz.intro,
142
+ alternates,
143
+ body
144
+ };
145
+ }
146
+ export { messagesIn as n, renderQuiz as r, QUIZ_MESSAGES as t };