How the Markdown Toggle Revert Mechanism and State Management Work in Markdown Here

The Markdown Toggle button detects rendered content by searching for a specific wrapper element in the DOM, then restores the original Markdown from a hidden data holder rather than rendering new content.

The open-source Markdown Here extension (adam-p/markdown-here) implements a robust toggle system that switches between raw Markdown and rendered HTML without relying on volatile JavaScript state. Instead, the extension embeds state directly into the document structure through specialized DOM wrappers and hidden data attributes, ensuring persistence across page reloads and different browser environments.

Detecting Rendered State via DOM Traversal

The core markdownHere function in src/common/markdown-here.js determines whether to render or revert by checking for the presence of a rendered wrapper around the current selection. It uses findMarkdownHereWrapper to walk up the DOM tree from the caret’s common ancestor until it locates an element satisfying isWrapperElem:

outerWrapper = findMarkdownHereWrapper(focusedElem);

This search specifically looks for a <div class="markdown-here-wrapper"> that contains a hidden raw-MD holder. If findMarkdownHereWrapper returns a valid element, the system treats the toggle action as a revert request; otherwise, it proceeds with Markdown rendering. This detection happens around lines 91-100 of the source file.

Reverting to Original Markdown

When a wrapper is detected, the code enters the reverting branch and calls unrenderMarkdown for each found wrapper:

unrenderMarkdown(wrappers[i]);

The unrenderMarkdown function retrieves the original Markdown source that was stored during the initial rendering process. It extracts the data from the hidden holder’s title attribute, which is prefixed with MDH: and Base64-encoded to handle special characters safely:

var rawHolder = findElemRawHolder(wrapperElem);
var originalMdHtml = rawHolder.getAttribute('title')
                            .slice(WRAPPER_TITLE_PREFIX.length)
                            .replace(/\n/g, '')
                            .replace(/\s/g, '');
originalMdHtml = Utils.base64ToUTF8String(originalMdHtml);
Utils.saferSetOuterHTML(wrapperElem, originalMdHtml);

The Utils.saferSetOuterHTML call replaces the entire wrapper element with the decoded original Markdown, effectively restoring the document to its pre-rendered state. This mechanism ensures that the exact Markdown source is preserved and recoverable at any time, as implemented in lines 19-34 of src/common/markdown-here.js.

State Tracking and Conflict Detection

The extension tracks whether users have modified the rendered HTML before toggling back to Markdown. A MutationObserver monitors the wrapper element and sets a specific DOM attribute when changes are detected:

  • markdown-here-wrapper-content-modified="true": Added to the wrapper when the rendered content has been altered by the user.

Before executing the revert, markdownHere checks for this attribute and can prompt the user to confirm the action, preventing accidental loss of HTML edits:

if (wrappers[i].getAttribute('markdown-here-wrapper-content-modified')) { … }

This state management approach, found in lines 98-107 of the core file, keeps all status information within the DOM itself rather than in JavaScript variables, allowing the toggle state to survive page refreshes as long as the wrapper persists.

Wiring the Toggle Button

The options page implementation in src/common/options.js provides a minimal UI layer that delegates to the core engine. The button handler simply calls markdownHere with the appropriate document context:

function markdownToggle() {
    markdownHere(rawMarkdownIframe.contentDocument, requestMarkdownConversion);
}
document.querySelector('#markdown-toggle-button')
        .addEventListener('click', markdownToggle, false);

This wiring at lines 24-27 demonstrates how the UI remains agnostic about whether it is rendering or reverting—the markdownHere function makes that determination automatically based on the current DOM state.

Practical Implementation Examples

Programmatically triggering a toggle:

// `doc` represents the compose document (Gmail, Thunderbird, etc.)
markdownHere(doc, requestMarkdownConversion);

Manually forcing a revert when you have a wrapper reference:

var wrapper = markdownHere.findFocusedElem(doc);
wrapper = findMarkdownHereWrapper(wrapper);
if (wrapper) {
    unrenderMarkdown(wrapper);
}

Checking if the current selection is in rendered state:

var focused = findFocusedElem(document);
var wrapper = findMarkdownHereWrapper(focused);
if (wrapper) {
    console.log('Already rendered – next click will revert');
}

Summary

  • State detection relies on the presence of .markdown-here-wrapper elements in the DOM, identified by findMarkdownHereWrapper walking up from the current selection.
  • Data persistence stores original Markdown in a hidden holder’s title attribute using Base64 encoding with the MDH: prefix, retrieved during revert by unrenderMarkdown.
  • Change tracking uses a MutationObserver to set markdown-here-wrapper-content-modified attributes, warning users before they lose HTML edits.
  • UI integration in src/common/options.js delegates all logic to the core markdownHere function, which automatically handles render versus revert decisions.

Frequently Asked Questions

How does Markdown Here decide whether to render or revert?

The system calls findMarkdownHereWrapper to check if the cursor sits inside a <div class="markdown-here-wrapper">. If found, it executes unrenderMarkdown to restore the source; otherwise, it proceeds with rendering the Markdown content. This decision logic resides in the markdownHere function within src/common/markdown-here.js.

Where is the original Markdown stored while content is rendered?

During rendering, the extension creates a hidden child element (the raw holder) inside the wrapper and stores the Base64-encoded original Markdown in its title attribute with an MDH: prefix. The unrenderMarkdown function decodes this using Utils.base64ToUTF8String when reverting.

What happens if I edit the rendered HTML before toggling back?

A MutationObserver monitors the wrapper element and sets markdown-here-wrapper-content-modified="true" if it detects manual changes. Before reverting, the code checks this attribute and can display a warning dialog, ensuring you do not accidentally discard HTML formatting that does not exist in the original Markdown source.

Can I trigger the revert mechanism from my own code?

Yes. Call markdownHere(doc, requestMarkdownConversion) where doc is your target document object. The function automatically handles the revert if a wrapper exists. Alternatively, manually obtain a wrapper reference via findMarkdownHereWrapper and pass it directly to unrenderMarkdown if you need to bypass the automatic detection logic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →