# Markdown editor

A 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 JavaScript
of 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" }
}
```

## Files
Copy `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 and
an 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 value
The Markdown lives in a hidden `<input name="…">` inside `<md-editor>`. It posts with the form, and every change fires a
bubbling 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 DOM
event 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 `![alt](address)` (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 code
block; 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 images
Drop 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 `![name](address)` 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`) keeps
files 24 h in `storage/md-files` under a random name, with a known extension only, served with `nosniff` + `sandbox`.

## Markdown source
A "Markdown source" button in the footer switches the field to a plain textarea holding the raw Markdown (for copying it
out 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 animation
  on 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.
