# Editor and Markdown

## The storage rule

User-authored content is stored as **Markdown**. It is rendered to safe HTML **on the server**, on every render.

Consequences worth stating plainly:

- The preview and the published post cannot drift, because the same renderer produces both.
- Fixing a rendering bug fixes every existing post at once.
- Arbitrary user HTML is never stored, so there is no legacy HTML to sanitise forever.

## The editor

A lightweight WYSIWYG surface that produces Markdown. Supported formatting:

bold, italic, H3/H4/H5, unordered and ordered lists, blockquote, inline code, code block, link, and a source mode.

Deliberately absent: arbitrary font family, font size, text colour, background colour, embedded script, iframe, and raw HTML. Those are not "missing features" — they are the difference between a comment box and an injection surface.

### Behaviour

- `Ctrl/Cmd+B`, `Ctrl/Cmd+I`, `Ctrl/Cmd+K` shortcuts.
- A link dialog component instead of a browser prompt.
- Native undo/redo is preserved; the editor does not destructively rewrite the DOM out from under it.
- The toolbar is keyboard navigable and overflows rather than clipping on narrow screens.
- Selection and range handling use the Selection/Range APIs rather than depending on the deprecated `execCommand`.

### Paste handling

Paste from a word processor is reduced to clean text and allowed formatting. Inline styles, class attributes and editor-specific markup are dropped.

Pasting a URL over a selection creates a Markdown link around that selection.

### Surface sizing

Questions, answers and topics get the full lightweight toolbar plus preview. Comments get a compact toolbar — a comment box that looks like a word processor invites posts that should have been answers.

## The renderer

The server-side formatter converts Markdown to a restricted HTML subset. It refuses script and iframe elements, event-handler attributes, and unsafe URL schemes. External link policy is applied server-side, so a client-side change cannot alter it.

## Extending

Adding a formatting control means teaching **both** the editor and the server renderer. A control the renderer does not support produces Markdown that renders as literal text — which is at least safe, and visibly wrong, rather than silently unsafe.

## Images in content

A Markdown image whose source is `/uploads/content/name.png` renders as an `<img>` — but only when the source is one this site stores.

**What is accepted:** a root-relative path containing `/uploads/` and ending in `.png`, `.jpg`, `.jpeg`, `.gif` or `.webp`.

**What is refused, and degrades to a plain link instead:** any external host, a protocol-relative `//host` source, anything with a scheme (`https:`, `data:`, `javascript:`), a path containing `..`, a query string or fragment, `.svg`, and any path outside `/uploads/`.

The reasoning is narrow. An external image in member content leaks every reader's address and user agent to whoever owns that host, breaks under a strict Content-Security-Policy, and turns into mixed content on an https site. `.svg` is excluded because it can carry script.

Rendered images always carry `alt` (empty is valid for a decorative image), `loading="lazy"` and `decoding="async"`.

### Uploading

**Moderators and administrators only.** Pasting or dragging an image into the editor uploads it to `/content-image` and inserts the Markdown. The editor only advertises the endpoint to accounts allowed to use it, so an ordinary member's paste falls through to the normal text handling and never sees a control the server would refuse.

The endpoint requires POST, a CSRF token, and the moderator or administrator role, and is rate limited per account. The file is validated by the decoder rather than by its name — see [Themes overview](../04-themes/THEMES-OVERVIEW.md#logo-and-favicon) for the same rules applied to branding — and stored under a generated name in a directory that ships with the PHP engine disabled.

### Known limit

Markdown carries no dimensions, so an embedded image cannot reserve its exact height before it loads. The stylesheet constrains the box and sets a small minimum, which limits the shift without pretending to know the aspect ratio. Widening this would mean storing dimensions at upload time and extending the Markdown syntax; neither is done today.
