How Context Menu Integration for the Markdown Toggle Feature Works in Markdown Here

The Markdown Toggle context menu integration registers a browser context menu item restricted to editable fields, which triggers the same markdownToggle() function used by the toolbar button by injecting content scripts and dispatching a button-click runtime message.

The adam-p/markdown-here extension provides a seamless way to convert Markdown syntax to rendered HTML within web-based text editors. Understanding how the context menu integration for the Markdown Toggle feature functions requires examining the interaction between background scripts, content scripts, and the core toggle logic distributed across the repository.

Registering the Context Menu in backgroundscript.js

The context menu entry is established when the extension initializes. In src/chrome/backgroundscript.js, the registration occurs within the chrome.runtime.onInstalled listener to ensure the menu item is created immediately after installation or updates.

Creating the Editable Context Menu Item

The extension creates a menu item specifically targeting editable web content:

chrome.contextMenus.create({
  id: 'markdown-here-context-menu',
  contexts: ['editable'],
  title: Utils.getMessage('context_menu_item')
});

The contexts: ['editable'] parameter restricts the menu to appear only when users right-click inside text input areas, such as Gmail compose windows, GitHub comment boxes, or generic HTML textareas. This prevents the menu from cluttering non-editable page contexts.

Handling Context Menu Clicks

When a user selects the Markdown Here entry from the context menu, the chrome.contextMenus.onClicked listener invokes the handleActionClick function to manage the execution flow.

The handleActionClick Function

Located in src/chrome/backgroundscript.js (lines 77-106), handleActionClick performs three critical operations:

  1. Detects the active tab context to determine if the user is on the extension's options page
  2. Injects content scripts using Injector.injectScripts(tab.id) if the scripts are not already present in the target tab
  3. Dispatches the activation message with the payload { action: 'button-click', info: info }
chrome.contextMenus.onClicked.addListener(async function(info, tab) {
  await handleActionClick(tab, info);
});

This architecture ensures that the content scripts—including src/chrome/contentscript.js and the core markdown-here.js bundle—are loaded before attempting to trigger the toggle functionality.

Processing the Message in contentscript.js

The content script acts as the intermediary between the background script's message and the actual toggle implementation.

Listening for the button-click Action

In src/chrome/contentscript.js, a runtime message listener waits for the specific action type:

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  if (request && request.action === 'button-click') {
    markdownToggle();
  }
});

By checking for request.action === 'button-click', the content script ensures that only legitimate toggle commands from the toolbar button or context menu trigger the conversion logic.

Executing the Markdown Toggle Logic

The actual conversion between Markdown source and rendered HTML resides in src/common/options.js within the markdownToggle() function (lines 324-340).

State Detection and Rendering

The markdownToggle() function determines the current editor state by checking for the presence of the .mdh-mirror CSS class. Based on this detection, it either:

  • Calls MarkdownRender.markdownRender to convert Markdown syntax to formatted HTML
  • Restores the original Markdown source from the mirror element

The function also synchronizes the toolbar button state and persists the current view preference to OptionsStore, ensuring consistency across multiple toggle operations.

Complete Execution Flow Example

The following code illustrates the full path from context menu interaction to Markdown conversion:

// Step 1: Background script receives the context menu click
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
  await handleActionClick(tab, info);
});

// Step 2: Background script sends activation message to content script
chrome.tabs.sendMessage(tab.id, { action: 'button-click', info: info });

// Step 3: Content script receives message and triggers the toggle
chrome.runtime.onMessage.addListener((request) => {
  if (request.action === 'button-click') {
    markdownToggle(); // Defined in src/common/options.js
  }
});

This unified message-passing approach ensures that both the browser action toolbar button and the right-click context menu execute identical conversion logic.

Summary

  • Menu Registration: src/chrome/backgroundscript.js creates a context menu item with ID markdown-here-context-menu restricted to editable contexts using chrome.contextMenus.create()
  • Script Injection: The handleActionClick function ensures content scripts are injected via Injector.injectScripts() before dispatching messages
  • Message Passing: Both toolbar and context menu triggers send a button-click action to the content script
  • Unified Logic: The markdownToggle() function in src/common/options.js handles the actual Markdown rendering and state management for both entry points

Frequently Asked Questions

Where is the context menu item for Markdown Toggle defined?

The context menu item is defined in src/chrome/backgroundscript.js using the chrome.contextMenus.create() API during the chrome.runtime.onInstalled event. It uses the ID markdown-here-context-menu and is restricted to editable contexts so it only appears in text input fields.

Why does the context menu only appear on certain web pages?

The menu item specifies contexts: ['editable'] in its creation options, which limits its appearance to editable HTML elements such as textareas and rich text editors. This prevents the menu from displaying when right-clicking on static text or images.

How does the context menu trigger the same conversion as the toolbar button?

Both activation methods send an identical runtime message with the action button-click to the content script. The listener in src/chrome/contentscript.js receives this message and calls markdownToggle() from src/common/options.js, ensuring the same rendering logic executes regardless of how the feature is invoked.

What happens if content scripts are not loaded when the context menu is used?

The handleActionClick function in src/chrome/backgroundscript.js checks the tab's script injection state and calls Injector.injectScripts(tab.id) to load contentscript.js and its dependencies before sending the activation message. This guarantees the toggle functionality works even on freshly loaded pages.

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 →