← Back to all posts

How do I write Markdown online? Live preview and XSS safety

A hand-written Markdown parser renders React elements, and a URL allow-list blocks javascript: — the TwoThink online editor uploads nothing.

When you write a README, an API description or a blog draft, Markdown on the left and a live preview on the right is close to a given. The tricky part is that the preview has to be safe — a malicious [click](javascript:alert(1)) must not become a clickable link.

What the MVP supports

The TwoThink Markdown editor currently supports:

  • Headings # ~ ######
  • Bold, italic and inline `code`
  • Fenced code blocks ```lang
  • Unordered - and ordered 1. lists
  • Links [text](url) and images ![alt](url)
  • Blockquotes > and horizontal rules ---

GFM tables (| a | b |) are planned for phase 2, and the MVP toolbar has no table button either.

Why React elements instead of innerHTML

The tool page's parser does not pull in marked / DOMPurify: block and inline syntax is parsed into an AST and then rendered with createElement. Text nodes are escaped by React by default; link and image URLs are restricted to http://, https:// and mailto:, and javascript: and data: are rejected.

Export options

  • Copy HTML: gives you the rendered HTML source, ready to paste into a CMS draft
  • Download .md: saves the original Markdown file

A live character, word and line count is shown at the bottom.

Is anything uploaded?

No. Parsing and previewing both happen inside your browser tab, which makes it a good fit for documents that contain internal links or draft wording.

How this differs from the blog infrastructure

The site's blog posts still use react-markdown to render the article pages; the tool page has its own parser so the tool keeps zero business npm dependencies. The two do not affect each other.