2026-10-22 · 8 min read
- markdown
- preview
- tooling
Flashman's Markdown editor and viewer call marked with GFM and breaks enabled, then sanitize the HTML in the browser before display. Copy HTML on the editor copies that sanitized string. The viewer copies the source. The counters above the panes call markdownStats on the source and do not look at the HTML.
A box that trims to nothing produces an empty HTML string, and the preview shows Nothing to preview yet. The counters can still be non-zero. Three spaces, a newline, and a tab are 0 words, 2 lines, and 5 characters, and the preview is the empty state.
Choose a break or a paragraph on purpose
hello and world on consecutive lines become one paragraph with a br between the words. A blank line between them becomes two paragraphs. a and b separated by CRLF also become one br. flashman followed by a single newline stays one paragraph. The extra newline is visible only in the line count, which becomes 2, and in the character count, which becomes 9.
An indented continuation of a list item is a br inside the item, not a code block. A paragraph, a blank line, then a hyphen item and a two-space continuation, renders the continuation after a br. A backslash at the end of a line is also a br. Two spaces between words in the middle of a line remain two spaces in the HTML, and the browser collapses them when it paints.
- Leave a blank line between paragraphs.
- Expect one newline to insert a br.
- Expect CRLF to render like LF.
- Read the line count when a trailing newline is the question.
Include the markers in the word count
Words are the trimmed source split on whitespace. # Title is 2 words. **bold** is 1 word. - a and - b is 4 words. A three-line fence that starts with ```js, contains const x = 1;, and ends with a fence is 6 words. one two and three on the next line is 3 words, 2 lines, and 13 characters.
Lines split on the newline character only. a, a carriage return, a newline, and b is 2 lines and 4 characters. A thematic break written as *** or --- is 1 word and renders as a horizontal rule. A task list counts the brackets too: - [ ] task and - [x] done is 7 words, and the HTML is two disabled checkboxes, the second of them checked.
Trust the sanitized HTML, not the raw markup
Inline code and fenced code escape brackets. `<tag>` becomes a code element whose text is the escaped tag. A fence containing a div does the same. Outside code, AT&T <tag> becomes a paragraph with AT&T and then the tag is dropped by the sanitizer, so the copied HTML is a paragraph that ends after the escaped ampersand and a space.
A script element is removed. A javascript: link keeps the anchor text and loses the href. An image onerror attribute is removed and the src is quoted. An ordinary https image and a target=_blank link are kept. Strikethrough becomes del. http://flashman.in becomes a link to itself. A table with a header row becomes thead and tbody.
Compare source, counters, and copied HTML
Paste the smallest sample that shows the break, read the three counters, then copy the HTML. Diff that HTML against the source if a reviewer expected a paragraph where the preview shows a br. For an entity inside the HTML, the HTML entities tool will show the amp that marked inserted.
Use the Markdown editor for source and HTML, the viewer when the text came from a file, diff for the two HTML strings, and the HTML entities tool for the escaped text. The counters describe the box. The preview describes the sanitized render.