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

> Discover how the Markdown Toggle revert mechanism in Markdown Here restores original Markdown from hidden data by finding a wrapper element. Learn about its DOM detection and state management.

- Repository: [Adam Pritchard/markdown-here](https://github.com/adam-p/markdown-here)
- Tags: internals
- Published: 2026-03-05

---

**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`](https://github.com/adam-p/markdown-here/blob/main/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`:

```javascript
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:

```javascript
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:

```javascript
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`](https://github.com/adam-p/markdown-here/blob/main/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:

```javascript
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`](https://github.com/adam-p/markdown-here/blob/main/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:

```javascript
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:**

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

```

**Manually forcing a revert when you have a wrapper reference:**

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

```

**Checking if the current selection is in rendered state:**

```javascript
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`](https://github.com/adam-p/markdown-here/blob/main/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`](https://github.com/adam-p/markdown-here/blob/main/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.