gitoriaLog in with ident

components

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commit3bc905c53bc905c5components #15 (mission 003) 2/2: docs (README Look, STATUS, LOG), reportmre3bc905c5/components/md-editor/README.md

5.3 KB

  1. # Markdown editor
  2. A Markdown field you edit **visually**: headings look like headings, **bold** is bold, lists are lists — while you type.
  3. The value stays **plain Markdown**. A port of `worldapi-components/md-editor.js` as a pure Hybriel component: no JavaScript
  4. of its own. Demo: `/md-editor`.
  5. Use it in a page:
  6. ```
  7. import MdEditor from './md-editor.hl'
  8. form { method = "post" action = "/save"
  9. MdEditor { name = "summary" value = "# Title" }
  10. }
  11. ```
  12. ## Files
  13. Copy `md-editor.hl` (one file; it has its own `Style`). Nothing else. It needs the WorldAPI colour tokens only as an option:
  14. every colour is `var(--color-…, <base value>)`, so an app that declares the tokens (and its `--color-accent`) wins and
  15. an app without them shows the base look (black, white, greys, no frames — root README "Look").
  16. ## Attributes
  17. | Attribute | Meaning |
  18. |---|---|
  19. | `name` | the field name the Markdown posts under (default `markdown`) |
  20. | `value` | the start Markdown |
  21. | `placeholder` | the grey text of an empty editor (default `Write…`) |
  22. | `rows` | the height of the source view (default 6) |
  23. ## Reading the value
  24. The Markdown lives in a hidden `<input name="…">` inside `<md-editor>`. It posts with the form, and every change fires a
  25. bubbling DOM `input` event on that input: a wrapper element catches it with `on input(e) { text = e.target.value }`
  26. (`e.target.name` says which editor, when there are several). A host cannot read a child member (hybriel#87), so the DOM
  27. event is the way out.
  28. ## What it makes (the Markdown tickets renders)
  29. Headings `#`…`######` (toolbar: H cycles paragraph → H1 → H2 → H3), paragraphs (a single line break is kept), `-` / `1.` lists
  30. (one level), fenced code blocks, `` `code` ``, links `[text](url)` / `<https://…>` / bare `https://…` (only `http(s)://`,
  31. `mailto:`, `/path`, `#anchor` become links), *em*, **strong**. Images `![alt](address)` (http(s) or a `/path`; no `data:` / `javascript:`). Nothing else (quotes, tables, nesting, HTML) can be made;
  32. pasted text is read as Markdown and pasted HTML is kept as plain text.
  33. ## Keyboard and toolbar (the toolbar also works on a phone)
  34. | Action | Keys | Toolbar |
  35. |---|---|---|
  36. | bold / italic / inline code | Ctrl+B / Ctrl+I / Ctrl+E (Cmd on Mac) | B, I, `<>` |
  37. | link (add / change / remove) | Ctrl+K | Link → small form (Enter applies, Esc closes) |
  38. | heading 1/2/3, paragraph | Ctrl+Alt+1/2/3, Ctrl+Alt+0 | H |
  39. | bulleted / numbered list | Ctrl+Shift+8 / Ctrl+Shift+7 | • List, 1. List |
  40. | code block | Ctrl+Alt+C | Block |
  41. | undo / redo | Ctrl+Z / Ctrl+Shift+Z | ↶ ↷ |
  42. | send the form | Ctrl+Enter | |
  43. | line break inside a paragraph | Shift+Enter | |
  44. **Typing Markdown formats it**: `# `, `- `, `* `, `1. ` at the start of a line make a heading / list; ```` ``` ```` + Enter a code
  45. block; a closed `**x**`, `*x*`, `_x_`, `` `x` `` becomes formatted (Ctrl+Z turns it back). Backspace at the start of a heading /
  46. list item / code block makes it a paragraph. Ctrl/Cmd+click opens a link.
  47. ## Files and images
  48. Drop files on the editor, paste them, or press **File** in the toolbar (several at once). Each file is sent to the app with
  49. `emit server mdUpload(files)` — the framework uploads it over HTTP in parts and a progress bar per file shows under the toolbar
  50. (`on client uploadProgress`). The **app decides where files are stored**: it must answer that face, or the upload fails
  51. ("The upload failed." under the toolbar). The face gets a list of `{ bytes, name, type, size }` and returns a list of addresses,
  52. one per file, in order. An image (`image/*`) lands in the text as `![name](address)` and is drawn; any other file as `[name](address)`.
  53. Dropped files land where they were dropped, pasted / chosen ones at the caret (or at the end).
  54. ```
  55. on server mdUpload(files) {
  56. let urls = []
  57. for (f of files) { writeFile('storage/files/' + f.name, f.bytes) urls.push('/files/' + f.name) }
  58. return urls
  59. }
  60. ```
  61. The app also sets `uploadMax` on `WebFramework` (default 1 GiB) and serves the address (`directory` route). Demo: `demo.hl` (the site's `lib/uploads.hl`) keeps
  62. files 24 h in `storage/md-files` under a random name, with a known extension only, served with `nosniff` + `sandbox`.
  63. ## Markdown source
  64. A "Markdown source" button in the footer switches the field to a plain textarea holding the raw Markdown (for copying it
  65. out or pasting a larger text in) and back to "Visual editor"; switching back reads the typed text again.
  66. ## How it is built (and its limits)
  67. - The formatted surface is a `contenteditable` element; its content is built with `createElement` / text nodes only (never
  68. `innerHTML` of user text) and is never re-rendered by the framework. Hybriel has no mount event, so a 100 ms CSS animation
  69. on the surface fires `animationiteration` once the page is live and that hands the editor its first content.
  70. - An untouched value is returned byte for byte; a changed value is written in one canonical form (`*em*`, `**strong**`, `- item`,
  71. `1. item`, ```` ``` ```` fences, backslash escapes where needed). What Markdown cannot hold is dropped (empty paragraphs).
  72. - Not ported from the JS version: the `<textarea>` "enhance" mode, `disabled` / `readonly` / `toolbar="none"`, the `[text](url)`
  73. typing rule, per-block byte-for-byte spelling, and the `MdEditor.markdown` test API.
  74. - Undo / redo is the browser's own; typing rules and the toolbar use the browser's editing commands so they undo.

Branches

Latest commits

  • 3bc905c5components #15 (mission 003) 2/2: docs (README Look, STATUS, LOG), reportmre
  • 5a98d77ecomponents #15 (mission 003) 1/2: black/white base look — greys for nuances, no WorldAPI fallbacks, no decorative frames; site + icon black/white; gate 230/0mre
  • 3f93d716components code order (mission 002) 4/4: docs, tests/letcount.py, tests/same-output*, reportmre
  • e747cfa0components code order (mission 002) 3/4: let only where reassigned (257 dropped, 0 never-reassigned left); gate 220/0, layouts gate 73/0, same outputmre
  • 172914a3components code order (mission 002) 2/4: lib/uploads.hl (demo upload storage), thin demo handlers, project.hl map; gate 220/0, same outputmre
  • d29f35b1components code order (mission 002) 1/4: styles.hl -> components/styles.hl; gate 220/0, same outputmre
  • 09945750components: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, http1 773de63e); gate 220/0mre
  • ede4d19acomponents: Hybriel master 190aa11d (fc838894 GC correctness, #126 closure scopes, #127); gate 220/0mre
  • 2bb9a935components: Hybriel master 8efba065 (re-vendor round 069: #126 memory, #48 lambda copy)mre
  • e4f01768antcolony#40: mission references point to the moved missionsmre
  • d32b44e2antcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
  • 0fb80b57components: Hybriel master ff51cf46 (re-vendor round)mre
  • 57408b6ecomponents#14: installable app (manifest, service worker, offline index), own iconmre
  • 317bd09fdeploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
  • becbd59bcomponents#13: modal dialog (<dialog> based, focus wrap, backdrop/Esc/x close with reason, scroll lock, footer buttons)mre
  • 367dd19adeploy.sh: never send .git or .gitignore to Byrodinmre
  • 4b4dabe5State of 2026-09-27, before the move to gitoriamre