Cross-browser extension (Firefox + Chrome) for bidirectional wiki markup ↔ Markdown conversion, with base64 image embedding, self-contained HTML export (with lightbox), and organized attachment downloads.
- Extract full page or selected content as clean Markdown
- Configurable image mode (via Settings):
- Base64 (default): images embedded as data URIs — single self-contained
.mdfile - Local path: images downloaded to
images/folder with relative path references
- Base64 (default): images embedded as data URIs — single self-contained
- Images at highest resolution (srcset 2x + Atlassian CDN 4096px)
- YAML front matter metadata (title, URL, author, labels, issue key, status)
- Download as
.md(filename = page title) or copy to clipboard - Export all images at original resolution
- Export as single self-contained HTML with click-to-zoom lightbox
- Side-by-side bidirectional text conversion
- Enhanced table handling (
||header||/|cell|) - Nested mixed lists, multi-line blockquotes (
{quote}), inline code ({{...}}) - Fenced code blocks with language syntax
- Paste or upload
.mdfiles - Auto-detect editor type (ProseMirror / TinyMCE / textarea)
- Preview converted wiki markup before inserting
- Scan page for all images and file attachments
- Checkbox selection — choose which items to download
- Select All / deselect individually
- Downloads into organized folder:
Downloads/{pageTitle}/images/and{pageTitle}/attachments/ - Automatic highest resolution selection for Atlassian media CDN
| Shortcut | Action |
|---|---|
Ctrl+Shift+M |
Copy current page as Markdown |
Ctrl+Shift+J |
Insert clipboard Markdown into page |
| Right-click → "Copy Page as Markdown" | Full page |
| Right-click → "Copy Selection as Markdown" | Selection only |
- Self-contained HTML: all CSS inlined, all images as base64
- Click any image to zoom (full-screen overlay)
- Keyboard accessible:
Escto close - Shows image dimensions and alt text
- Accessible via gear icon in popup header, or
about:addons→ Preferences - Embed images as base64 (default: ON) — controls Copy/Download MD behavior
- Inline images in HTML export (default: ON)
- Include metadata (default: ON) — YAML front matter
- Auto-save on change, Reset Defaults button
- Auto light/dark following system preference
- Manual toggle: Dark → Light → System
Modular design with isolated islands, composable plugins, and a custom AST pipeline.
| Pattern | Where | Purpose |
|---|---|---|
| Strategy | converter/strategies/ |
MarkdownStrategy, JiraStrategy |
| Island Architecture | src/islands/ |
7 self-contained islands + MessageBus |
| Plugin System | converter/plugins/ |
7 composable Turndown plugins |
| AST Pipeline | converter/pipeline/ |
Custom parser → Visitor transformer → emitter registry |
| Mediator | islands/message-bus.js |
Decoupled inter-island communication |
| Factory | plugins/base64-images.js |
Configurable plugin creation |
src/
├── converter/
│ ├── index.js # Facade — public API
│ ├── strategies/
│ │ ├── markdown.js # HTML→MD (Turndown + plugins)
│ │ └── jira.js # Jira↔MD (AST pipeline + jira2md)
│ ├── plugins/
│ │ ├── index.js # Plugin registry
│ │ ├── confluence-panels.js
│ │ ├── confluence-code.js # 5 rules for Server/Cloud code blocks
│ │ ├── confluence-tables.js # Cloud tables (th in tbody, p-wrapped cells)
│ │ ├── confluence-mentions.js
│ │ ├── jira-issues.js
│ │ └── base64-images.js
│ └── pipeline/
│ ├── parser.js # MD → AST (recursive-descent)
│ ├── transformer.js # AST visitors (Visitor Pattern)
│ └── emitter.js # AST → Jira markup
├── islands/
│ ├── index.js # Orchestrator
│ ├── message-bus.js # Message routing (Mediator)
│ ├── extractor.js # Content + metadata extraction
│ ├── inserter.js # Editor insertion strategies
│ ├── html-exporter.js # HTML export + lightbox
│ ├── attachment-collector.js # Attachment scanning
│ ├── shortcut-handler.js # Keyboard/context menu
│ └── notification.js # Toast notifications
extension/
├── manifest.json # MV3, gecko min 142.0
├── background.js # Image fetch, downloads, shortcuts, menus
├── content.js # Bundled islands (auto-generated)
├── popup/{html,css,js} # 4-tab UI
├── options/{html,css,js} # Settings page
├── lib/
│ ├── converter.bundle.js # Bundled converter
│ └── browser-polyfill.js # Chrome compat
└── icons/icon-{16..128}.png
| Library | Version | Purpose |
|---|---|---|
| Turndown | 7.2.2 | HTML → Markdown |
| @truto/turndown-plugin-gfm | 1.0.2 | GFM tables |
| jira2md | 3.0.1 | Jira → Markdown |
| webextension-polyfill | 0.12.0 | Chrome API compat |
| esbuild | 0.27.4 | Bundling |
npm installVersion is managed in package.json — all other files (manifest, UI, zip filenames) sync automatically on build.
npm run build # bundle only (for dev)
npm run package:firefox # build + package → dist/firefox/wiki_markdown-{version}.xpi
npm run package:chrome # build + package → dist/wiki_markdown-{version}-chrome.zip
npm run package # bothnpm version 1.1.0 --no-git-tag-version
npm run package # everything updates automatically| Command | Description |
|---|---|
npm run build |
Bundle converter + islands + polyfill, sync manifest version |
npm run start:firefox |
Launch Firefox with extension loaded |
npm run lint |
Validate with web-ext |
npm run package:firefox |
Build .xpi |
npm run package:chrome |
Build Chrome .zip |
npm run package |
Build both |
node test/converter.test.jsOption A — Permanent install (requires Firefox Developer Edition or Nightly):
The
.xpifrom this project is unsigned. Standard Firefox (Release/Beta) blocks unsigned extensions entirely — there is no workaround. You must use Developer Edition or Nightly.
npm run package:firefox- Open Firefox Developer Edition or Nightly
- Go to
about:config→ searchxpinstall.signatures.required→ set tofalse - Go to
about:addons→ gear icon → "Install Add-on From File..." - Select
dist/firefox/wiki_markdown-{version}.xpi
The extension persists across restarts.
Option B — Temporary (any Firefox edition, including Release):
npm run build- Go to
about:debugging#/runtime/this-firefox - Click "Load Temporary Add-on" → select
extension/manifest.json - Extension works until Firefox restarts
npm run package:chrome- Go to
chrome://extensions→ enable "Developer mode" - Click "Load unpacked" → select
dist/chrome/
To distribute a signed .xpi installable on standard Firefox:
- Get API credentials at addons.mozilla.org/developers/addon/api/key
- Sign:
npx web-ext sign -s extension --channel=unlisted \
--api-key=$AMO_JWT_ISSUER --api-secret=$AMO_JWT_SECRET- Signed
.xpiappears inweb-ext-artifacts/
- Fork & clone
npm install && npm run build- Load extension locally (see Install above)
- Make changes in
src/— follow existing patterns:- One responsibility per file; add new plugins/strategies instead of modifying existing ones
- Islands don't import each other — communicate only via
message-bus.js - Each Turndown plugin is a standalone file with
filter+replacementonly
npm run buildand test manually on a Confluence/Jira pagenode test/converter.test.jsto run unit tests- Submit a PR
MIT