All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
2.2.5 - 2026-05-03
intro.mdcollision onslug: '/'routes — sites with bothdocs/intro.mdand a doc routed to/docs/had their build output collide onintro.md, with the second route silently overwriting the first. Trailing-slash routes now resolve toindex.mdin both the build writer and the dropdown URL.- Code fences corrupted by MDX/Docusaurus transforms —
cleanMarkdownForDisplayran every regex over raw content with no fence awareness, so docs demonstrating Docusaurus syntax inside fenced code blocks had imports stripped,<Tabs>blocks rewritten, and components removed from their own examples. Transforms now skip backtick and tilde fences (length ≥ 3, ≤3 leading spaces, supports CRLF and unclosed fences) via a placeholder-based protection that also handles legitimate<Tabs>blocks containing fenced code. - Image directories only copied to the first route's destination — when sibling docs in the same source dir routed to different URL spaces (e.g. via
slug:),imgDirsToCopylocked the destination from the first route, leaving silent broken images on other routes. Switched toMap<src, Set<dest>>so each source dir is copied to every destination it serves. <TabItem>and YouTube iframe regexes rejected valid attribute orders —<TabItem>requiredvaluebeforelabel; reverse-order items silently emptied the entire<Tabs>block. YouTube iframe requiredsrcbeforetitle. Both now capture the attribute string and parse fields independently with single- or double-quote support. If no<TabItem>s parse, the original<Tabs>block is preserved instead of being silently deleted.- Multi-line and side-effect imports survived stripping — the import stripper used
.*?which cannot cross newlines, soimport {\n Foo\n} from './x';survived. Side-effect imports likeimport './x.css';weren't matched. Both are now removed. - Component scrubber left orphan close tags — the closing-tag alternation matched any uppercase tag, so siblings like
<Outer><Inner>x</Inner></Outer>left</Outer>orphaned. Added a backreference so paired tags must share a name. Deeply nested same-name components remain a known regex limitation. <details>regex was too strict and destroyed body whitespace — rejected attributes on the opening tag (<details open>), rejected mixed-content summaries, and the body cleanup trimmed every line and dropped blanks, breaking 4-space indents in code and intentional blank-line separators. Now allows attrs on<details>/<summary>, strips inline tags from the summary text, and only trims outer blank padding from the body.Root.jsdecodeURIComponentcrash on malformed hash — a URL with malformed percent encoding like#%foothrewURIErrorsynchronously and silently lost the anchor scroll. Now wrapped in adecodeHashSafelyhelper that returns null and short-circuits the scroll attempt.Root.jsscroll-to-anchor timer and listener leaks — foursetTimeouts and awindow 'load'listener were never cleaned up. Quickly clicking different hash links piled up timers, and stale timers could scroll the page out from under the user after navigation. The effect now returns a cleanup that clears every scheduled timer and removes the listener.- Dropdown copy-reset timer auto-closed the menu — the 2 s reset timer unconditionally called
setIsOpen(false), so a user who reopened the dropdown during the cooldown saw it close again with no input. The timer was also never cleared on unmount or before a new copy. Now tracked in auseRef, cleared on unmount and at the top of each new copy, and only resets the "Copied!" label.
- Added a
node --testbased test suite (zero new dependencies). Run withnpm test. - Extracted
cleanMarkdownForDisplay,getMarkdownUrl, fence-protection, image-mapping, and hash-decoding into focusedlib/modules for testability.
2.2.4 - 2026-03-19
- Dropdown chevron not vertically centered — added inline-flex alignment to the button so the SVG chevron centers with the text across all browsers (fixes #2)
- Increased chevron spacing from 4px to 6px for better visual balance
2.2.3 - 2026-03-12
- Dropdown not appearing on client-side navigation — the MutationObserver introduced in 2.2.1 short-circuited when it found stale DOM during React transitions, causing the button to vanish after page swaps. Observer now stays active to catch content swaps and re-inject reliably.
- Track exact container instance via ref instead of global querySelector to prevent cleanup collisions during page transitions
- Observe
document.bodyinstead of<main>element to survive layout swaps during navigation - Unmount stale React roots before creating new ones to prevent memory leaks
- Added note clarifying the dropdown only appears on doc content pages, not category/index pages
- Updated peer dependencies to support React 19 (
^18.0.0 || ^19.0.0) — re-applied from #5
2.2.1 - 2026-03-11
- Faster dropdown appearance on cold page load — replaced fixed retry timeouts with MutationObserver that injects the button the instant the article header appears after hydration
- Properly unmount React root on cleanup to prevent memory leaks
2.2.0 - 2026-03-11
- Configurable
docsPathoption for sites that don't host docs under/docs/(fixes #3)- Default:
'/docs/'(no behavior change for existing users) - Example:
['docusaurus-markdown-source-plugin', { docsPath: '/' }] - Supports docs-only subdomains, custom route base paths, etc.
- Default:
contentLoadedhook exposing plugin options to theme components via Docusaurus global data
- Runtime components now read
docsPathfrom plugin global data instead of hardcoding/docs/ - Updated README: replaced swizzle instructions with
docsPathconfiguration docs
2.1.0 - 2026-03-03
- Refactored build processing to use Docusaurus route metadata instead of filesystem scanning
- Plugin now reads
route.metadata.sourceFilePathfrompostBuildroutes prop - Automatically handles custom
routeBasePath, versioned docs, and i18n configurations - Image path rewriting now uses actual route URLs instead of hardcoded
/docs/prefix - Removed
findMarkdownFiles()andcopyImageDirectories()internal functions
- Image paths in cleaned markdown now correctly reflect the site's URL structure
- Plugin no longer assumes docs live at the
/docs/URL path
2.0.1 - 2025-11-24
- Added button screenshot to README showing the dropdown UI
- Removed unnecessary v1.x migration guide (no users existed before v2.0.0)
- Simplified Advanced Configuration section
- Removed complex swizzling instructions for blog support
- Added honest note about customization trade-offs
- Screenshot now displays at 400px width for better README viewing
- Cleaner, more focused documentation
- More transparent about current limitations and future plans
2.0.0 - 2025-11-24
- Eliminated manual file copying requirement - Plugin now uses Docusaurus native APIs
- Users upgrading from v1.x must remove manually copied theme files (
src/theme/Root.jsandsrc/components/MarkdownActionsDropdown/) - Component directory structure changed:
src/→theme/andcomponents/ - Components now bundled with plugin instead of user's project
getThemePath()plugin API to automatically provide theme components.gitignorefile for cleaner development experience- Comprehensive migration guide in README for v1.x users
- Zero-config installation - just add plugin to docusaurus.config.js
- Plugin now provides components via Docusaurus plugin APIs instead of requiring manual copying
- Updated README with simplified installation instructions
- Component imports now use relative paths instead of
@sitealias - Updated troubleshooting guide to reflect new architecture
- Updated advanced configuration examples to use swizzling
- Much better developer experience - no manual file management needed
- Components automatically update when plugin updates (no stale copied files)
- Cleaner project structure - plugin consumers don't need theme overrides
- Standard Docusaurus plugin pattern - follows best practices
- Uses Docusaurus
getThemePath()lifecycle method - Components bundled at:
theme/Root.jsandcomponents/MarkdownActionsDropdown/ - Tested in production with 58 markdown files and 10 image directories
- Compatible with Docusaurus v3.x
- Remove manually copied files:
src/theme/Root.jsandsrc/components/MarkdownActionsDropdown/ - Update the plugin:
npm update docusaurus-markdown-source-plugin - Rebuild:
npm run build - CSS in
custom.cssremains unchanged
1.0.0 - 2025-11-24
- Initial release of docusaurus-markdown-source-plugin
- Build-time plugin that copies markdown files to build output
- Automatic cleaning of Docusaurus-specific syntax (front matter, imports, MDX components)
- Conversion of HTML elements back to markdown equivalents
- Conversion of relative image paths to absolute paths from /docs/ root
- Automatic copying of image directories to build output
- React dropdown component for viewing and copying markdown
- "View as Markdown" feature - opens raw markdown in new tab
- "Copy Page as Markdown" feature - copies markdown to clipboard
- Dynamic injection into article headers via Root.js theme override
- Click-outside-to-close dropdown behavior
- Mobile-responsive dropdown positioning
- RTL language support for dropdown menu
- Comprehensive deployment guides for:
- Vercel
- Netlify
- Cloudflare Pages
- Apache
- Nginx
- SEO-safe HTTP headers configuration examples
- CSS customization support via custom.css
- Support for Tabs/TabItem component conversion
- Support for details/summary component conversion
- YouTube iframe to text link conversion
- HTML5 video tag handling
- Zero-config setup with sensible defaults
- Comprehensive README with installation instructions
- Quick start guide
- Deployment configuration for all major platforms
- Troubleshooting guide
- Advanced configuration examples (blog support, custom URL patterns)
- CSS customization guide
- Live example at flynumber.com/docs
- Dependencies: fs-extra ^11.0.0
- Peer dependencies: @docusaurus/core ^3.0.0, react ^18.0.0
- Requires Node.js >=18.0.0
- Compatible with Docusaurus v3.x
- Uses React 18's createRoot API for component injection