A JSON editor on the build page, where the textarea only shows the source

Merged
AL
5h ago
+1037-32

Closes #26.

The source field of the build page is an editor, where it was a textarea that only showed the JSON. A person who gets a source from a chat is a teacher, not a developer: a missing comma or quote was invisible until Build said "The source cannot be read", with no place.

What changes

  • src/h5p/build/editor.ts, the editor: CodeMirror 6, loaded apart from the page, with JSON in the site's own token colours, line numbers, matching brackets and undo. The textarea stays under it, hidden, holding the same text, so the rest of the page reads the source as it always did. Where the editor cannot load, the textarea stays as the source and the page works.
  • A mistake in the JSON is marked as it is typed, and said under the source with its line and column and a Show where button: Not valid JSON at line 3, column 3: A comma is missing here, after the value before it.
  • src/h5p/build/json-scan.ts, which reads JSON text with no DOM for where a mistake is and where a path points. JSON.parse says where differently in each browser and Safari does not say at all, and the build names a problem by path (params.chapters[0].metadata.title), not by line. JSON.parse still decides whether text is JSON; this says where.
  • The problems the build finds are marked where they are, and a problem's name in the list is a button that goes there. What is missing is marked in the object it is missing from. The marks are of the text as it was built: a change to the text clears them.
  • Format lays the source out, and takes it out of the code fence a chat put it in. Text that is not JSON is left as it is, and the line under the source says where.
  • parseSource says where, in every browser: the problem text, and so the copy for the chat, now reads is not valid JSON at line 1, column 24: …, where it carried the browser's own words.
  • package.json and the lockfile: eight @codemirror/* and @lezer/* packages, pinned exactly; the lockfile change is their added entries.

What it respects

  • The page's policy. style-src 'self' refuses a <style> that an editor injects. The editor is mounted in a shadow root, where CodeMirror adopts its styles as a constructed stylesheet, which the policy does not govern. Its colours are the site's custom properties, which reach into the shadow root, so light and dark need no code.
  • Chrome's own editing. Text typed over a selection of coloured text gets a style attribute from Chrome, which the policy refuses and reports (I saw it with select all, then a letter; contenteditable="plaintext-only" did not stop it). The editor takes that typing itself, through its input handlers, so a bracket typed over a selection still wraps it and the policy is never asked.
  • The keyboard. Tab is not the editor's: it leaves it, so that focus is never trapped. The editor has a name, "The source, as JSON", and the label goes to it.
  • Size. The page's script is 395 kB (138 kB gzip), up 7 kB. The editor is its own chunk, 356 kB (116 kB gzip), loaded after the page.

Tested

pnpm build, pnpm typecheck and pnpm e2e: 29 pass, and they passed five runs in a row. The 16 that were there pass, and the build page's 10 of them fill the source through the editor now. New: 7 for the reader of JSON, with no browser (each kind of mistake at its offset, and that JSON.parse agrees which texts are JSON; paths found, and what is missing found in its object), and 6 for the editor (a mistake marked and put right; Format and the code fence; a problem marked and its name going there, a text that is not JSON as a problem with a place; the editor's name, its label and Tab; typing over a selection, which a bracket wraps, with no policy report; and the textarea where the editor cannot load). The tests that watch the page still fail on any policy report or page error. I looked at it in light and dark at desktop width and at 390 px.

The documentation of the build page, in the README as it is on main and in docs/h5p-build-page.md of #25, is not part of this: #25 moves it, so it is written there.