@lernapps/app-templates 0.2.1 → 0.3.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 (56) hide show
  1. package/README.md +5 -11
  2. package/dist/{assessment-Dy359O1O.mjs → assessment-DaKsXUdj.mjs} +204 -16
  3. package/dist/{blocks-CxtCPAa5.mjs → blocks-D4SSGKuL.mjs} +2 -2
  4. package/dist/blocks.mjs +2 -2
  5. package/dist/{check-CteIFN96.mjs → check-BxZOZPic.mjs} +58 -4
  6. package/dist/{content-DO6oEJxj.mjs → content-CAL0Nz1a.mjs} +1 -1
  7. package/dist/explainer-e2e.mjs +5 -5
  8. package/dist/explainer-plugin.mjs +57 -13
  9. package/dist/explainer.mjs +2 -2
  10. package/dist/interactive-e2e.mjs +1 -1
  11. package/dist/interactive-plugin.mjs +2 -2
  12. package/dist/{load-RosvaPZf.mjs → load--ovXqbNE.mjs} +286 -33
  13. package/dist/questions.mjs +1 -1
  14. package/dist/quiz-e2e.mjs +1 -1
  15. package/dist/quiz-plugin.mjs +3 -3
  16. package/dist/{render-D5kwkNt1.mjs → render-D3LsJY04.mjs} +2 -2
  17. package/dist/{render-DqHOJyHK.mjs → render-mYtzN1DV.mjs} +1 -1
  18. package/dist/{result-D_mmCVwN.mjs → result-DphIWHDF.mjs} +1 -1
  19. package/dist/{validate-Bxy9NfB8.mjs → validate-CfFykN0M.mjs} +1 -1
  20. package/dist/{validate-BDwL7Su3.mjs → validate-j3GjVhNF.mjs} +2 -2
  21. package/explainer/README.md +7 -3
  22. package/explainer/docs/arc42/05-explainer-building-blocks.arc42.md +26 -8
  23. package/explainer/docs/arc42/09-explainer-decisions.arc42.md +42 -0
  24. package/explainer/package.json +1 -1
  25. package/explainer/src/app.yaml +3 -1
  26. package/explainer/src/prozent/chapter.yaml +1 -1
  27. package/explainer/src/prozent/grundwert.yaml +2 -1
  28. package/explainer/src/prozent/prozentsatz.yaml +2 -1
  29. package/explainer/src/prozent/prozentwert.yaml +2 -1
  30. package/interactive/package.json +1 -1
  31. package/package.json +3 -4
  32. package/quiz/package.json +1 -1
  33. package/runtime/explainer/assessment.ts +4 -2
  34. package/runtime/explainer/check-page.ts +6 -2
  35. package/runtime/explainer/content.ts +20 -1
  36. package/runtime/explainer/exercises.ts +71 -6
  37. package/runtime/explainer/explainer.css +21 -0
  38. package/runtime/explainer/explainer.v1.schema.json +2 -2
  39. package/runtime/explainer/explainer.v2.schema.json +152 -0
  40. package/runtime/explainer/expression.ts +127 -1
  41. package/runtime/explainer/generated/texts.ts +98 -9
  42. package/runtime/explainer/generated/types.ts +8 -0
  43. package/runtime/explainer/load.ts +70 -16
  44. package/runtime/explainer/messages/de.json +5 -1
  45. package/runtime/explainer/messages/en.json +5 -1
  46. package/runtime/explainer/outline.ts +1 -1
  47. package/runtime/explainer/page-list.ts +38 -0
  48. package/runtime/explainer/plugin.ts +13 -4
  49. package/runtime/explainer/render.ts +26 -2
  50. package/runtime/explainer/schema.ts +92 -9
  51. package/runtime/explainer/tutorpage.ts +9 -1
  52. package/runtime/shared/blocks/blocks.v1.schema.json +1 -1
  53. package/runtime/shared/text/formula.ts +61 -1
  54. package/scaffold/explainer/src/app.yaml +5 -1
  55. package/scaffold/explainer/src/thema/chapter.yaml +1 -1
  56. package/scaffold/explainer/src/thema/einstieg.yaml +1 -1
@@ -1,12 +1,12 @@
1
- import { _ as exampleProblems, a as routes, c as exerciseData, d as templatesOf, g as describedProblems, h as TABLE_CONTRACT, i as hiddenChapter, l as renderExercise, m as SIZE_BUDGET, n as loadSite, o as terms, p as formulaAlternative, r as contentPages, s as dataOf, t as ContentError, u as taskSeeds, v as propertyProblems, y as propsIn } from "./load-RosvaPZf.mjs";
1
+ import { _ as exampleProblems, a as routes, c as exerciseData, d as templatesOf, g as describedProblems, h as TABLE_CONTRACT, i as hiddenChapter, l as renderExercise, m as SIZE_BUDGET, n as loadSite, o as terms, p as formulaAlternative, r as contentPages, s as dataOf, t as ContentError, u as taskSeeds, v as propertyProblems, y as propsIn } from "./load--ovXqbNE.mjs";
2
2
  import { i as inLanguage, s as localeOf, t as escape } from "./html-mffkHGHh.mjs";
3
3
  import { n as markerOf } from "./block-BK_ohsH1.mjs";
4
- import { n as renderMaterial } from "./blocks-CxtCPAa5.mjs";
5
- import { c as renderMarkdown, l as renderWithFormulas, o as plain, s as renderInline } from "./check-CteIFN96.mjs";
4
+ import { n as renderMaterial } from "./blocks-D4SSGKuL.mjs";
5
+ import { c as renderMarkdown, l as renderWithFormulas, o as plain, s as renderInline } from "./check-BxZOZPic.mjs";
6
6
  import { _ as questionIn, a as renderQuestion, i as questionMessagesIn, n as answerText } from "./render-GS1Htlu8.mjs";
7
7
  import { n as isSizeless, t as imageType } from "./image-CspJVfyQ.mjs";
8
8
  import { a as pageFooter, c as withLanguage, i as notFoundPage, n as fromDepth, o as pageHeader, r as languageRoot, s as relative, t as alternateLinks } from "./pages-9RaaomoO.mjs";
9
- import { B as label, D as generate, F as questionFor, H as placeholdersIn, S as videoLine, U as plainText, V as placeholderValue, X as kindOf, _ as termId, a as assessments, b as renderVideo, h as allBlocks, m as titleOf, o as drawsTasks, p as stepHint, r as assessmentOf, v as timelineEvents, w as message } from "./assessment-Dy359O1O.mjs";
9
+ import { C as videoLine, G as placeholdersIn, K as plainText, L as questionFor, O as generate, T as message, U as label, W as placeholderValue, _ as mapUrl, a as assessments, et as kindOf, h as allBlocks, m as titleOf, o as drawsTasks, p as stepHint, r as assessmentOf, v as termId, x as renderVideo, y as timelineEvents } from "./assessment-DaKsXUdj.mjs";
10
10
  import { i as NEW_TASK_ICON } from "./tasks-DADUYy52.mjs";
11
11
  import { n as NEEDS, o as languageName, t as LANGUAGES } from "./needs-BzEahJ6X.mjs";
12
12
  import { n as vueAlias, r as withServer, t as VUE_OPTIONS } from "./vue-DO1m6q1R.mjs";
@@ -450,6 +450,7 @@ const WORDS$2 = {
450
450
  "rechnen-anschaulich": "Zeig Zahlen der Seite als Menge, z. B. „37 sind 3 Zehner und 7 Einer“, aber nie das Ergebnis einer Aufgabe. Ein Schritt pro Nachricht, nie Zeitdruck."
451
451
  },
452
452
  contextTitle: "Kontext: die Seite „{page}“",
453
+ recap: "Diese Seite ist eine Wiederholung: Sie wiederholt etwas, das das Kind schon können sollte, und hat keine Übung. Erklär mit dem Inhalt der Seite und frag nach, ob das Kind ihn verstanden hat. Üben kann es das in anderen Apps; welche das sind, steht hier: {url}",
453
454
  exercise: "Übung",
454
455
  steps: "Lösungsweg",
455
456
  link: "Link",
@@ -502,6 +503,7 @@ const WORDS$2 = {
502
503
  "rechnen-anschaulich": "Show numbers of the page as amounts, e.g. “37 is 3 tens and 7 ones”, but never the result of a task. One step per message, never time pressure."
503
504
  },
504
505
  contextTitle: "Context: the page “{page}”",
506
+ recap: "This page is a recap: it goes over something the child should already know, and has no exercise. Explain with the content of the page and ask whether the child has understood it. It can practise it in other apps; which ones, it says here: {url}",
505
507
  exercise: "Exercise",
506
508
  steps: "Steps of the solution",
507
509
  link: "Link",
@@ -617,7 +619,7 @@ function generatedLines(spec, site, lang, format, page = "../") {
617
619
  lines.push(`- ${words.linkNumber}: \`${page}?${spec.id}=n&via=tutor#${spec.id}\``);
618
620
  const { paths, answer } = placeholdersOf(spec, lang);
619
621
  const data = dataOf(spec, site.data);
620
- const templates = templatesOf(spec, [lang]);
622
+ const templates = templatesOf(spec, [lang], data);
621
623
  let choices = false;
622
624
  const rows = taskSeeds(spec, data).map((seed, index) => {
623
625
  const task = generate(spec, seed, data);
@@ -797,6 +799,7 @@ function pageMarkdown(site, page, lang, descriptions, exercises, rows, shown) {
797
799
  link: `…/${page.chapter}/${page.id}/?via=tutor#${first.id}`
798
800
  };
799
801
  lines.push(`## ${words.whereTitle}`, "", ...words.where.flatMap((text) => !text.includes("{example}") && !text.includes("{link}") ? [`- ${text}`] : example === void 0 ? [] : [`- ${text.replace("{example}", example.query).replace("{link}", example.link)}`]), "", ...needsLines(lang), `## ${fill(words.contextTitle)}`, "");
802
+ if (page.recap !== void 0) lines.push(words.recap.replace("{url}", mapUrl(page.recap)), "");
800
803
  if (page.description !== void 0) lines.push(own(page.description), "");
801
804
  lines.push(...contentLines(site, page.blocks, lang, formulas, (heading, _anchor, depth) => `${"#".repeat(Math.min(3 + depth, 6))} ${heading}`));
802
805
  for (const exercise of exercises) {
@@ -1402,10 +1405,11 @@ function pager(context) {
1402
1405
  };
1403
1406
  return `<nav class="explainer-pager" aria-label="${plain(context.t("pager"))}">${link("prev", "previous", -1)}${link("next", "next", 1)}</nav>`;
1404
1407
  }
1405
- function pageList(context, pages) {
1408
+ function pageList$1(context, pages) {
1406
1409
  return `<ol class="explainer-pages">${pages.map((page) => {
1407
1410
  const description = page.description === void 0 ? "" : ` <span class="explainer-description">${context.text(page.description)}</span>`;
1408
- return `<li><a href="${context.to(`${page.chapter}/${page.id}/`)}">${context.text(page.title)}</a>${description}</li>`;
1411
+ const recap = page.recap === void 0 ? "" : ` <span class="explainer-recap-note">(${plain(context.t("recapNote"))})</span>`;
1412
+ return `<li><a href="${context.to(`${page.chapter}/${page.id}/`)}">${context.text(page.title)}</a>${recap}${description}</li>`;
1409
1413
  }).join("")}</ol>`;
1410
1414
  }
1411
1415
  function main(context) {
@@ -1413,9 +1417,9 @@ function main(context) {
1413
1417
  const title = `<h1>${plain(route.title)}</h1>`;
1414
1418
  switch (route.kind) {
1415
1419
  case "start": {
1416
- const contents = hiddenChapter(site) ? pageList(context, site.chapters[0]?.pages ?? []) : `<ol class="explainer-chapters">${site.chapters.map((chapter) => {
1420
+ const contents = hiddenChapter(site) ? pageList$1(context, site.chapters[0]?.pages ?? []) : `<ol class="explainer-chapters">${site.chapters.map((chapter) => {
1417
1421
  const summary = chapter.summary === void 0 ? "" : context.markdown(chapter.summary);
1418
- return `<li><a class="explainer-chapter-link" href="${context.to(`${chapter.id}/`)}">${context.text(chapter.title)}</a>${summary}${pageList(context, chapter.pages)}</li>`;
1422
+ return `<li><a class="explainer-chapter-link" href="${context.to(`${chapter.id}/`)}">${context.text(chapter.title)}</a>${summary}${pageList$1(context, chapter.pages)}</li>`;
1419
1423
  }).join("")}</ol>`;
1420
1424
  const glossary = terms(site).length === 0 ? "" : `<p><a href="${context.to("glossar/")}">${plain(context.t("glossary"))}</a></p>`;
1421
1425
  const sources = site.app.sources === void 0 ? "" : `<section class="explainer-sources"><h2>${plain(context.t("sources"))}</h2><p>${plain(context.t("sourcesIntro"))}</p><ul>${site.app.sources.map((source) => {
@@ -1442,7 +1446,7 @@ function main(context) {
1442
1446
  title,
1443
1447
  summary,
1444
1448
  assessment === void 0 ? "" : assessmentOffer(assessmentContext(context), assessment),
1445
- `<nav class="explainer-contents" aria-label="${plain(context.t("pages"))}">${pageList(context, chapter.pages)}</nav>`
1449
+ `<nav class="explainer-contents" aria-label="${plain(context.t("pages"))}">${pageList$1(context, chapter.pages)}</nav>`
1446
1450
  ].filter((part) => part !== "").join("\n");
1447
1451
  }
1448
1452
  case "assessment": return route.assessment === void 0 ? title : assessmentMain(assessmentContext(context), route.assessment);
@@ -1467,12 +1471,29 @@ function main(context) {
1467
1471
  title,
1468
1472
  tutorSection(context),
1469
1473
  `</div>`,
1474
+ page.recap === void 0 ? "" : recapNotice(context, page.recap),
1470
1475
  pageContents(context, page.blocks),
1471
1476
  renderBlocks(context, page.blocks, 1)
1472
1477
  ].join("\n");
1473
1478
  }
1474
1479
  }
1475
1480
  }
1481
+ /**
1482
+ * At the top of a recap page: that it is a recap, and the link to the competency's page on the learning map, where the
1483
+ * apps are that practise it. The runtime writes it, so that it cannot be missing; a plain link, nothing loaded before
1484
+ * a click.
1485
+ */
1486
+ function recapNotice(context, recap) {
1487
+ const link = external(mapUrl(recap), context.t("recapLink"));
1488
+ const [before = "", after = ""] = context.t("recapText").split("{link}");
1489
+ const label = plain(context.t("recap"));
1490
+ return [
1491
+ `<aside class="explainer-recap" aria-label="${label}">`,
1492
+ `<p class="explainer-recap-label">${label}</p>`,
1493
+ `<p>${plain(before)}${link}${plain(after)}</p>`,
1494
+ `</aside>`
1495
+ ].join("\n");
1496
+ }
1476
1497
  /** What a part of a self-assessment needs of the page it is on (assessment-page.ts). */
1477
1498
  const assessmentContext = (context) => ({
1478
1499
  site: context.site,
@@ -1767,6 +1788,23 @@ function scopeDocument(markdown, title, lang, folder = "") {
1767
1788
  "</html>"
1768
1789
  ].join("\n");
1769
1790
  }
1791
+ // runtime/explainer/page-list.ts
1792
+ /** The file's name, at the root of the bundle. */
1793
+ const PAGE_LIST = "lernapps-pages.json";
1794
+ /** The list of the pages of `site`; undefined for an app of format 1, which has none. */
1795
+ function pageList(site) {
1796
+ const { format, strand } = site.app;
1797
+ if (format !== 2 || strand === void 0) return void 0;
1798
+ return {
1799
+ format: 1,
1800
+ strand,
1801
+ pages: contentPages(site).map((page) => ({
1802
+ path: `${page.chapter}/${page.id}/`,
1803
+ title: page.title,
1804
+ ...page.recap !== void 0 ? { recap: page.recap } : strand === "none" || page.competencies === void 0 ? {} : { competencies: page.competencies }
1805
+ }))
1806
+ };
1807
+ }
1770
1808
  // runtime/explainer/plugin.ts
1771
1809
  // @vitejs/plugin-vue and resolves vue to the copy this runtime depends on, so that the components and the islands
1772
1810
  const MARKER = "<!-- explainer -->";
@@ -1950,7 +1988,7 @@ function explainer() {
1950
1988
  }
1951
1989
  if (command !== "build" || content.components.length === 0) return;
1952
1990
  const rendered = await withServer(root, (loader) => renderComponents(content, loader, true));
1953
- if (rendered.problems.length > 0) this.error(new ContentError(rendered.problems).message);
1991
+ if (rendered.problems.length > 0) this.error(new ContentError(rendered.problems, content.app.format ?? 1).message);
1954
1992
  islands = rendered.islands;
1955
1993
  descriptions = rendered.descriptions;
1956
1994
  islandsChunk = this.emitFile({
@@ -1985,6 +2023,12 @@ function explainer() {
1985
2023
  fileName: "404.html",
1986
2024
  source: notFoundPage()
1987
2025
  });
2026
+ const list = pageList(content);
2027
+ if (list !== void 0) this.emitFile({
2028
+ type: "asset",
2029
+ fileName: PAGE_LIST,
2030
+ source: `${JSON.stringify(list, null, 2)}\n`
2031
+ });
1988
2032
  for (const [image, file] of content.images) this.emitFile({
1989
2033
  type: "asset",
1990
2034
  fileName: image,
@@ -2055,7 +2099,7 @@ function explainer() {
2055
2099
  const size = gzipSync(own).length;
2056
2100
  if (size > 2e4) problems.push(`${component.file}: ${(size / 1e3).toFixed(1)} kB gzip of its own code, more than the budget of ${SIZE_BUDGET / 1e3} kB; split it into smaller components, or ask for a built-in block (vue-component-size)`);
2057
2101
  }
2058
- if (problems.length > 0) this.error(new ContentError(problems).message);
2102
+ if (problems.length > 0) this.error(new ContentError(problems, content.app.format ?? 1).message);
2059
2103
  },
2060
2104
  configureServer(server) {
2061
2105
  devServer = server;
@@ -2091,7 +2135,7 @@ function explainer() {
2091
2135
  const dev = devServer;
2092
2136
  if (dev === void 0) return next();
2093
2137
  renderComponents(content, (id) => dev.ssrLoadModule(id), false).then(({ islands: rendered, descriptions: described, problems }) => {
2094
- if (problems.length > 0) throw new ContentError(problems);
2138
+ if (problems.length > 0) throw new ContentError(problems, content.app.format ?? 1);
2095
2139
  if (forScope) {
2096
2140
  const markdown = path.endsWith(TUTOR_MARKDOWN);
2097
2141
  const lang = content.app.languages.find((one) => languageRoot(one) !== "" && path.startsWith(languageRoot(one))) ?? content.app.languages[0] ?? "de";
@@ -1,8 +1,8 @@
1
1
  import { t as escape } from "./html-mffkHGHh.mjs";
2
2
  import { S as shuffled, a as renderQuestion, g as questionId, l as isComplete, u as isCorrect } from "./render-GS1Htlu8.mjs";
3
3
  import { n as startQuestion, t as readAnswer } from "./engine-CArxqizW.mjs";
4
- import { D as generate, F as questionFor, G as taskText, N as mistakeOf, W as stepsOf, c as isStoredResult, f as resultQuery, i as assessmentPrompt, j as diagnose, l as linkLines, n as BAND_MESSAGES, u as pageResults, w as message, x as startVideos, z as hintOf } from "./assessment-Dy359O1O.mjs";
5
- import { i as sessionLines, n as answerShown, r as resultLine, t as Progress } from "./result-D_mmCVwN.mjs";
4
+ import { F as mistakeOf, H as hintOf, J as taskText, L as questionFor, N as diagnose, O as generate, S as startVideos, T as message, c as isStoredResult, f as resultQuery, i as assessmentPrompt, l as linkLines, n as BAND_MESSAGES, q as stepsOf, u as pageResults } from "./assessment-DaKsXUdj.mjs";
5
+ import { i as sessionLines, n as answerShown, r as resultLine, t as Progress } from "./result-DphIWHDF.mjs";
6
6
  import { c as addressOf, d as numberFromAddress, f as randomTaskNumber, i as NEW_TASK_ICON, n as ASSESSMENT_TASK_EVENT, p as taskNumber, r as ASSESSMENT_UNPACKED_EVENT, s as SHOWN_EVENT, t as ASSESSMENT_ANSWER_EVENT, u as nextTaskNumber } from "./tasks-DADUYy52.mjs";
7
7
  import { a as isNeedsChoice, c as withNeeds, r as NEEDS_KEY, s as withLanguage } from "./needs-BzEahJ6X.mjs";
8
8
  import { moveFocus } from "@lernapps/tooling/a11y";
@@ -1,7 +1,7 @@
1
1
  import { i as inLanguage } from "./html-mffkHGHh.mjs";
2
2
  import { r as languageRoot } from "./pages-9RaaomoO.mjs";
3
3
  import { i as doingText, n as STATE_PROPERTY, o as inputsOf, s as mismatch, t as CLOCK_PROPERTY } from "./contract-PONpxJtd.mjs";
4
- import { i as message, o as loadInteractive, t as APP_PAGE } from "./render-D5kwkNt1.mjs";
4
+ import { i as message, o as loadInteractive, t as APP_PAGE } from "./render-D3LsJY04.mjs";
5
5
  import { expect, test } from "playwright/test";
6
6
  // runtime/interactive/e2e.ts
7
7
  /** The page's language, its alternates and a switch to each other language, the archetype marker. */
@@ -1,9 +1,9 @@
1
1
  import { c as withLanguage, i as notFoundPage, n as fromDepth } from "./pages-9RaaomoO.mjs";
2
2
  import { c as sourceScope, n as codeProblems } from "./vue-code-BkJjW_I_.mjs";
3
- import { o as pathText, t as Problems } from "./content-DO6oEJxj.mjs";
3
+ import { o as pathText, t as Problems } from "./content-CAL0Nz1a.mjs";
4
4
  import { n as vueAlias, r as withServer, t as VUE_OPTIONS } from "./vue-DO1m6q1R.mjs";
5
5
  import { a as inputText, i as doingText, o as inputsOf, s as mismatch } from "./contract-PONpxJtd.mjs";
6
- import { a as InteractiveError, n as pages, o as loadInteractive, r as renderPage } from "./render-D5kwkNt1.mjs";
6
+ import { a as InteractiveError, n as pages, o as loadInteractive, r as renderPage } from "./render-D3LsJY04.mjs";
7
7
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
8
8
  import { join } from "node:path";
9
9
  import vue from "@vitejs/plugin-vue";