components
All repositories: gitoria
5.3 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-…, <base value>)`, so an app that declares the tokens (and its `--color-accent`) wins andan app without them shows the base look (black, white, greys, no frames — root README "Look").## 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` (the site's `lib/uploads.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
- 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