components
All repositories: gitoria
5.2 KB
# Markdown editorA Markdown field you edit **visually**: headings look like headings, **bold** is bold, lists are lists — while you type.The value stays **plain Markdown**. A port of `worldapi-components/md-editor.js` as a pure Hybriel component: no JavaScriptof its own. Demo: `/md-editor`.Use it in a page:```import MdEditor from './md-editor.hl'form { method = "post" action = "/save"MdEditor { name = "summary" value = "# Title" }}```## FilesCopy `md-editor.hl` (one file; it has its own `Style`). Nothing else. It needs the WorldAPI colour tokens only as an option:every colour is `var(--color-…, <palette value>)`, so an app that declares the tokens (and its `--color-accent`) wins andan app without them still looks right.## Attributes| Attribute | Meaning ||---|---|| `name` | the field name the Markdown posts under (default `markdown`) || `value` | the start Markdown || `placeholder` | the grey text of an empty editor (default `Write…`) || `rows` | the height of the source view (default 6) |## Reading the valueThe Markdown lives in a hidden `<input name="…">` inside `<md-editor>`. It posts with the form, and every change fires abubbling DOM `input` event on that input: a wrapper element catches it with `on input(e) { text = e.target.value }`(`e.target.name` says which editor, when there are several). A host cannot read a child member (hybriel#87), so the DOMevent is the way out.## What it makes (the Markdown tickets renders)Headings `#`…`######` (toolbar: H cycles paragraph → H1 → H2 → H3), paragraphs (a single line break is kept), `-` / `1.` lists(one level), fenced code blocks, `` `code` ``, links `[text](url)` / `<https://…>` / bare `https://…` (only `http(s)://`,`mailto:`, `/path`, `#anchor` become links), *em*, **strong**. Images `` (http(s) or a `/path`; no `data:` / `javascript:`). Nothing else (quotes, tables, nesting, HTML) can be made;pasted text is read as Markdown and pasted HTML is kept as plain text.## Keyboard and toolbar (the toolbar also works on a phone)| Action | Keys | Toolbar ||---|---|---|| bold / italic / inline code | Ctrl+B / Ctrl+I / Ctrl+E (Cmd on Mac) | B, I, `<>` || link (add / change / remove) | Ctrl+K | Link → small form (Enter applies, Esc closes) || heading 1/2/3, paragraph | Ctrl+Alt+1/2/3, Ctrl+Alt+0 | H || bulleted / numbered list | Ctrl+Shift+8 / Ctrl+Shift+7 | • List, 1. List || code block | Ctrl+Alt+C | Block || undo / redo | Ctrl+Z / Ctrl+Shift+Z | ↶ ↷ || send the form | Ctrl+Enter | || line break inside a paragraph | Shift+Enter | |**Typing Markdown formats it**: `# `, `- `, `* `, `1. ` at the start of a line make a heading / list; ```` ``` ```` + Enter a codeblock; a closed `**x**`, `*x*`, `_x_`, `` `x` `` becomes formatted (Ctrl+Z turns it back). Backspace at the start of a heading /list item / code block makes it a paragraph. Ctrl/Cmd+click opens a link.## Files and imagesDrop files on the editor, paste them, or press **File** in the toolbar (several at once). Each file is sent to the app with`emit server mdUpload(files)` — the framework uploads it over HTTP in parts and a progress bar per file shows under the toolbar(`on client uploadProgress`). The **app decides where files are stored**: it must answer that face, or the upload fails("The upload failed." under the toolbar). The face gets a list of `{ bytes, name, type, size }` and returns a list of addresses,one per file, in order. An image (`image/*`) lands in the text as `` and is drawn; any other file as `[name](address)`.Dropped files land where they were dropped, pasted / chosen ones at the caret (or at the end).```on server mdUpload(files) {let urls = []for (f of files) { writeFile('storage/files/' + f.name, f.bytes) urls.push('/files/' + f.name) }return urls}```The app also sets `uploadMax` on `WebFramework` (default 1 GiB) and serves the address (`directory` route). Demo: `demo.hl` keepsfiles 24 h in `storage/md-files` under a random name, with a known extension only, served with `nosniff` + `sandbox`.## Markdown sourceA "Markdown source" button in the footer switches the field to a plain textarea holding the raw Markdown (for copying itout or pasting a larger text in) and back to "Visual editor"; switching back reads the typed text again.## How it is built (and its limits)- The formatted surface is a `contenteditable` element; its content is built with `createElement` / text nodes only (never`innerHTML` of user text) and is never re-rendered by the framework. Hybriel has no mount event, so a 100 ms CSS animationon the surface fires `animationiteration` once the page is live and that hands the editor its first content.- An untouched value is returned byte for byte; a changed value is written in one canonical form (`*em*`, `**strong**`, `- item`,`1. item`, ```` ``` ```` fences, backslash escapes where needed). What Markdown cannot hold is dropped (empty paragraphs).- Not ported from the JS version: the `<textarea>` "enhance" mode, `disabled` / `readonly` / `toolbar="none"`, the `[text](url)`typing rule, per-block byte-for-byte spelling, and the `MdEditor.markdown` test API.- Undo / redo is the browser's own; typing rules and the toolbar use the browser's editing commands so they undo.
Branches
- mainmain branch
Latest commits
- 4b4dabe5State of 2026-09-27, before the move to gitoriamre