# Editor

> Markdown in, a live preview while you write.

`<WriterEditor>` edits Markdown, held in its `v-model`. In plain Vue, import `WriterEditor`.

## Examples

### Basic

```vue
<script setup lang="ts">
const body = ref(`## A heading

Some **bold**, some _italic_, a [link](https://example.com) and \`code\`.

- A list
- [x] A done task

> A quote.`);
const editor = useTemplateRef("editor");
</script>

<template>
  <div class="writer-demo">
    <WriterToolbar
      :editor="editor"
      :items="['heading2', 'bold', 'italic', 'link', 'quote', 'bulletList', 'image']"
    />
    <WriterEditor ref="editor" v-model="body" label="Body" placeholder="Start writing…" />
  </div>
</template>
```

### Source mode

Plain Markdown throughout, with the source highlighted.

```vue
<script setup lang="ts">
const body = ref(`## Plain Markdown

Every **mark** stays where you can see it.

::callout
Blocks too.
::`);
</script>

<template>
  <div class="writer-demo">
    <WriterEditor v-model="body" mode="source" label="Body" />
  </div>
</template>
```

### Uploads

Pass `upload` and files can be pasted, dropped or picked. It gets the `File` and resolves to its URL.

```vue
<script setup lang="ts">
const body = ref("Drop, paste or choose an image:\n\n![]()\n");
const editor = useTemplateRef("editor");
const error = ref("");

// Your endpoint stores the file and answers with its URL. Here: the image inlined,
// after a moment, so you can see it upload.
async function upload(file: File) {
  await new Promise((resolve) => setTimeout(resolve, 1500));
  return new Promise<string>((resolve) => {
    const reader = new FileReader();
    reader.addEventListener("load", () => resolve(String(reader.result)));
    reader.readAsDataURL(file);
  });
}
</script>

<template>
  <div class="writer-demo">
    <WriterToolbar :editor="editor" :items="['bold', 'italic', 'image']" />
    <WriterEditor
      ref="editor"
      v-model="body"
      label="Body"
      :upload="upload"
      @upload-error="(_, file) => (error = `Couldn't upload ${file.name}.`)"
    />
    <p class="writer-demo-stats">
      {{ error || (editor?.uploading ? "Uploading…" : "Ready to save") }}
    </p>
  </div>
</template>
```

### Link suggestions

`links` suggests targets as the author types `](`, e.g. your site's pages.

```vue
<script setup lang="ts">
const body = ref("Type `](` after some text in brackets: [the council](");

const pages = [
  { label: "City council", url: "/topics/council" },
  { label: "Winter", url: "/topics/winter" },
  { label: "Dale", url: "/people/dale" },
];
const links = (query: string) => pages.filter((page) => page.url.includes(query));
</script>

<template>
  <div class="writer-demo">
    <WriterEditor v-model="body" label="Body" :links="links" />
  </div>
</template>
```

## Live preview

The document is always Markdown, but it reads like the page:

- **Formatting:** marks hide except in the element being edited. `**bold**` shows its asterisks only while the cursor is in it. Heading, quote and list marks show on the cursor's line.
- **Blocks** render with your components. Click or arrow into one and its source opens, framed, in the same spot. Set `--writer-block-inset` and `--writer-block-background` to your blocks' padding and background, and the text doesn't jump.
- **Images** on their own line are cards: the published figure, with caption and alt text typed in place. Drop, paste or pick a file to fill or replace one.
- **Leaving the editor** shows everything as published. The cursor stays put for when you come back.

`mode="source"` turns it off.

## Props

| Prop            | Type                                         | Description                                                                         |
| --------------- | -------------------------------------------- | ----------------------------------------------------------------------------------- |
| `v-model`       | `string`                                     | The Markdown.                                                                       |
| `label`         | `string`                                     | Accessible name. Default: "Markdown editor".                                        |
| `labelledby`    | `string`                                     | Id of the element naming it, instead of `label`.                                    |
| `describedby`   | `string`                                     | Id of a hint or error.                                                              |
| `name`          | `string`                                     | Submits the Markdown with its form.                                                 |
| `placeholder`   | `string`                                     |                                                                                     |
| `readonly`      | `boolean`                                    |                                                                                     |
| `mode`          | `"live" \| "source"`                         | Live preview (default) or plain Markdown.                                           |
| `upload`        | `(file: File) => Promise<string>`            | Enables pasting, dropping and picking files. Resolve to the URL.                    |
| `links`         | `(query) => { label, url }[]` (or a promise) | Suggests link targets after `](`.                                                   |
| `codeLanguages` | `LanguageDescription[]`                      | Highlighting inside fenced code, e.g. `languages` from `@codemirror/language-data`. |
| `extensions`    | `Extension`                                  | Extra CodeMirror extensions. Read once, on mount.                                   |

## Events

| Event          | Payload                        | When                                                                            |
| -------------- | ------------------------------ | ------------------------------------------------------------------------------- |
| `focus`        |                                | The editor gets focus.                                                          |
| `blur`         |                                | It loses it.                                                                    |
| `upload-error` | `(error: unknown, file: File)` | An `upload` rejected. Its placeholder is gone, or the card's old image is back. |

## Exposed

Through a template ref:

| Member               | Description                                                                                                                 |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `ready`              | CodeMirror has loaded.                                                                                                      |
| `active`             | Formatting at the cursor, a `Set` of command names.                                                                         |
| `canUpload`          | `upload` is set.                                                                                                            |
| `canUndo`, `canRedo` |                                                                                                                             |
| `uploading`          | Uploads in progress. Save when it's `0`.                                                                                    |
| `run(command)`       | Applies a [command](/components/toolbar#commands). `image` opens the file picker with `upload`, else inserts an empty card. |
| `insert(text)`       | Replaces the selection.                                                                                                     |
| `insertFiles(files)` | Uploads and inserts files at the cursor.                                                                                    |
| `focus()`            |                                                                                                                             |
| `view()`             | The CodeMirror `EditorView`, for anything else.                                                                             |

## Keyboard

`Mod` is Ctrl, or ⌘ on a Mac.

| Keys                            | Description                                                  |
| ------------------------------- | ------------------------------------------------------------ |
| `Mod+B` `Mod+I` `Mod+E` `Mod+K` | Bold, italic, code, link                                     |
| `Mod+Shift+X`                   | Strikethrough                                                |
| `Mod+Alt+1` … `3`               | Heading 1–3                                                  |
| `Mod+Shift+7` `8` `9`           | Numbered, bulleted, task list                                |
| `Mod+Alt+F`                     | Footnote                                                     |
| `Mod+Z` `Mod+Shift+Z`           | Undo, redo                                                   |
| `Enter`                         | New paragraph (in a quote, inside it); in a list, a new item |
| `Shift+Enter`                   | Line break inside the paragraph (`\` at the line end)        |
| `/` on an empty line            | Block menu                                                   |
| `Mod+]` `Mod+[`                 | Indent, outdent                                              |

`Tab` leaves the editor, so keyboard users aren't trapped. Pasting a URL over a selection links it.

## Uploads

While a file uploads, the Markdown holds an `#upload-…` placeholder where its URL will go, and `uploading` counts what's in flight. Wait for `0` before saving: the URL landing changes the Markdown, so a save-on-change runs again by itself. A placeholder saved anyway renders nothing, and in the editor it's an empty card to fill again.

## Loading another document

Change the editor's `key`. An outside `v-model` change is applied as an edit, keeping undo history; a new document should start fresh.

## Server rendering

The server renders the content as it will read (a `<textarea>` in source mode). CodeMirror loads in the browser and takes over, keeping anything typed before it did. Pages without an editor never load CodeMirror.
