Yuriy Vereshchagin36 downloadsCaptures and wraps HTML tags in pasted text so they don't break the note's formatting.
Captures and wraps HTML tags in pasted text so they don't break the note's formatting.
Paste a paragraph that contains <name> somewhere in the middle, and everything below it stops rendering. Headings go flat, lists lose their bullets, callouts unroll. The source still holds every character, but the note no longer displays it.
Markdown allows raw HTML, and a tag name can be any identifier — it does not have to be a real element. So <name> is read as an opening HTML tag. The renderer passes it to the browser, the browser opens an element that is never closed, and each sibling after it is pulled into that element.
This is not a fault in Obsidian. It is Markdown following its specification, applied to text that was never meant to be markup.
On paste, it finds tag-shaped text and moves it where the renderer will not read it as markup. The characters are left unchanged.
| Situation | Result |
|---|---|
| A tag inside a sentence | Wrapped in a code span: `<name>` |
| A tag on its own line | Placed in a code block |
| Several lines of HTML | The whole run is placed in one code block |
| A full HTML document | One code block, not one edit per tag |
It leaves the following alone:
< in prose, as in a < b.<https://example.com> and <[email protected]>.\<name>.br, img, hr, sup, sub, u, mark, kbd by default.Where the text is copied from changes what lands on the clipboard.
Copying from a Markdown code block inside Obsidian puts the literal <name> on the clipboard as plain text, so it is caught before it is inserted. Copying from a rendered HTML preview — a web page, the reading view, a browser — does not. There, <name> renders as an empty element with no visible text, so the plain-text copy drops it and only the clipboard's HTML flavor still carries it. Obsidian then pastes its own conversion of that HTML, which puts <name> back into the note after the plugin has already looked at the paste.
The plugin handles this by recording the note at paste time and comparing against it once the paste settles, then protecting whatever was inserted. The check retries across a few frames, so it does not depend on the paste being synchronous.
| Command | What it does |
|---|---|
| Protect raw HTML in current note | Repairs a note that is already broken |
| Protect raw HTML in selection | The same, for a selection. Also in the right-click menu |
| Toggle protection on paste | Turns protection on or off. Also the status bar icon |
Protect on paste is the master switch, on after install. Ask first turns silent protection into a confirmation prompt. Tell me what was fixed shows a short note after each change. Status bar shortcut adds the </> icon, which is struck through when protection is off.
How much to catch sets the scope. Anything that looks like a tag is the default. Only unknown or unclosed tags leaves correctly paired HTML such as <b>bold</b> as written. Tags to leave alone is your own ignore list. Check pastes from the web covers the HTML-preview case above.
Tags in a sentence chooses between a code span and a backslash escape. Where a line already contains an unpaired backtick, the plugin escapes regardless, because a code span would not survive there. Blocks of HTML puts standalone or multi-line HTML in a code block. Code block label is the language written after the opening fence.
npm install
npm run build # type-checks, then writes main.js
npm run lint # the official Obsidian plugin review rules
npm test # 44 unit tests, plus a runtime smoke test
npm run dev # rebuild on change
src/protect.ts holds the protection logic and has no Obsidian dependency, so it is tested on its own. The plugin bundles no third-party code at runtime; it uses only the Obsidian API.
manifest.json / versions.json conventions follow the Obsidian sample plugin (MIT).src/protect.ts implements the raw-HTML rules from the CommonMark specification.MIT.