open-standards/wai/quantum-studio/src/md.js
Transaction Science f090e8b6d1 quantum-studio: the course in the Studio and the desktop app, and the interface in Simplified Chinese
The Course pane lists the 21 lessons, shows a lesson's text and exercise,
loads its starter, and checks the program in the editor with the engine
(engine_course_check in the page, the same commands in the desktop app):
each case's verdict, each rule, What went wrong? from the diagnostics, the
verdict and the critique, the hints one at a time with the engine's pointer,
and a hint from the assistant when a model is connected, any program in its
answer held back. Progress is kept only in this browser and says so.

The interface's words are data, locales/<locale>/<area>.json for every part,
English the source and Simplified Chinese marked machine-assisted; the Studio
follows the browser's language until the reader picks one with the switch.
test/locales.test.mjs holds every locale to every key and placeholder. The
e2e checks the course in English and in Chinese. wai-web's engine tests pin
23 native and 16 wasm32, a course check the same bytes in both.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-28 08:43:06 -04:00

54 lines
2.2 KiB
JavaScript

// A lesson's Markdown as nodes, never as HTML: paragraphs, headings, lists,
// fenced code (its info string's first word as the language), inline code,
// **bold** and *emphasis*. HTML comments, such as a translation's note, are
// left out.
import { el } from './dom.js';
// **bold**, *emphasis* and `code` within a line.
export function inline(text) {
return text.split(/(\*\*[^*]+\*\*|`[^`]+`|\*[^*\s][^*]*\*)/).filter(Boolean).map((p) => {
if (p.startsWith('**') && p.endsWith('**')) return el('strong', { text: p.slice(2, -2) });
if (p.startsWith('`') && p.endsWith('`')) return el('code', { text: p.slice(1, -1) });
if (p.length > 2 && p.startsWith('*') && p.endsWith('*')) return el('em', { text: p.slice(1, -1) });
return document.createTextNode(p);
});
}
export function markdown(src) {
const out = [];
const lines = String(src || '').replace(/<!--[\s\S]*?-->/g, '').split('\n');
let para = [];
let list = null;
const flush = () => {
if (para.length) out.push(el('p', {}, ...inline(para.join(' '))));
para = [];
};
const endList = () => { if (list) out.push(list); list = null; };
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const fence = line.match(/^```(\S*)/);
if (fence) {
flush(); endList();
const body = [];
for (i++; i < lines.length && !lines[i].startsWith('```'); i++) body.push(lines[i]);
out.push(el('pre', { class: 'qs-code', 'data-lang': fence[1] || 'text' }, el('code', { text: body.join('\n') })));
continue;
}
const h = line.match(/^(#{1,4})\s+(.*)$/);
if (h) { flush(); endList(); out.push(el(`h${Math.min(6, h[1].length + 2)}`, {}, ...inline(h[2]))); continue; }
const item = line.match(/^\s*(?:[-*]|\d+\.)\s+(.*)$/);
if (item) {
flush();
const ordered = /^\s*\d+\./.test(line);
if (!list || (list.tagName === 'OL') !== ordered) { endList(); list = el(ordered ? 'ol' : 'ul'); }
list.append(el('li', {}, ...inline(item[1])));
continue;
}
if (!line.trim()) { flush(); endList(); continue; }
if (list && /^\s{2,}\S/.test(line)) { list.lastChild.append(' ', ...inline(line.trim())); continue; }
endList();
para.push(line.trim());
}
flush(); endList();
return out;
}