humanonlyweb/writer

Images

Images, figures and uploads.

Markdown

In Markdown

Plain Markdown, no block needed: ![Alt text](/cat.png). Alone in its paragraph with a title, it becomes a figure:

![A cat on a sofa](/cat.png "Our cat, 2026")
Copy code
<figure>
  <img src="/cat.png" alt="A cat on a sofa" loading="lazy" decoding="async" />
  <figcaption>Our cat, 2026</figcaption>
</figure>
Copy code

Size it with attributes so the page doesn't shift: ![Alt](/cat.png){width="800" height="600"}. Uploads add them for you.

  • data:image/… URLs work, for images only.

  • An image without a URL (![]()) doesn't render. In the editor it's an empty card to fill.

  • For responsive images or a CDN, replace img through components, e.g. { img: "NuxtImg" }.

In the editor

Images on their own line are cards: the picture, its caption and its alt text, edited in place. Arrowing onto a card selects it; Backspace or Delete removes it, Enter adds a paragraph after it. A card never turns back into Markdown; switch to source mode for that.

Uploads

Give the editor an upload function: it gets the File and resolves to its URL.

async function upload(file: File) {
  const body = new FormData();
  body.append("file", file);
  const { url } = await $fetch<{ url: string }>("/api/uploads", { method: "POST", body });
  return url;
}
Copy code

While it uploads, the card shows the local file at its real size, so nothing moves when the URL arrives. Several files upload side by side, in order. A failed upload fires upload-error and removes its placeholder, or puts a replaced card back the way it was.

On the server, check what you're given: the file type by its first bytes, not its name, and a size limit. Don't accept SVG on your own origin: it can run script.

Drop, paste or choose an image:

Ready to save

Copy code