A lightweight Docusaurus plugin that exposes your Markdown files as raw .md
URLs and adds a button (or drop-down widget) to each page so users can copy the
Markdown file to their clipboard.
Note
This project is not packaged and distributed on npm. This is a fork of markdown_docusaurus_plugin by FlyNumber with some added functionality to support docs.modular.com.
Here are some of the things we added/changed:
- Supports
.mdxfiles, turning them into.md - Extracts page title from frontmatter before stripping the frontmatter
- Renders a single "Copy page" button instead of drop-down widget, by default (the drop-down widget is still available via configuration)
- Miscellaneous improvements to processing Docusaurus tags like tabs, plus other MDX components (most are specific to docs.modular.com)
- Adds several user-configuration properties:
- Specify the path to your docs (previously hard-coded to
/docs) - Select the widget type, either original drop-down or new button
- Specify the DOM element where you want to inject the button/drop-down as a CSS selector (default is the page header)
- Specify the button text
- Specify the button/widget icons
- Specify the path to your docs (previously hard-coded to
Although this fork is not distributed as a package, you can still add it
as a dependency in your package.json pointing to the GitHub repo:
{
"dependencies": {
"docusaurus-markdown-source-plugin": "github:modular/markdown_docusaurus_plugin"
}
}Then run npm install to generate package-lock.json, which pins the resolved
commit. To update to a specific commit later, update the lock file entry or
append a commit hash:
"docusaurus-markdown-source-plugin": "github:modular/markdown_docusaurus_plugin#abc1234"The plugin accepts several configuration options to customize the "copy page" behavior on your Docusaurus pages.
You don't need to set any of these, but here's a snippet you can copy paste
into your docusaurus.config.js file in case you want to customize:
module.exports = {
plugins: [
['docusaurus-markdown-source-plugin', {
// URL path prefix where docs are served; set to '/' if docs
// are at the site root (default: '/docs/')
docsPath: '/docs/',
// Filesystem directory name containing markdown source files,
// relative to the site root (default: 'docs')
docsDir: 'docs',
// 'button' for a simple copy button, 'dropdown' for a menu
// with multiple actions (default: 'button')
widgetType: 'button',
// CSS selector for the element the widget is injected into
// (default: 'article .markdown header')
containerSelector: 'article .markdown header',
// Label shown on the copy button (default: 'Copy page')
copyButtonText: 'Copy page',
// Label shown after a successful copy (default: 'Copied')
copiedButtonText: 'Copied',
// When true, trailing-slash URLs fetch intro.md
// (e.g., /foo/ -> /foo/intro.md). When false, the trailing slash
// is stripped (e.g., /foo/ -> /foo.md). (default: false)
supportDirectoryIndex: false,
// When true, rewrites internal markdown links to fully-qualified
// .md URLs using the site's configured URL. Makes the raw markdown
// self-contained for LLMs and external tools. (default: false)
fullyQualifiedLinks: false,
// Override the base URL used when fullyQualifiedLinks is enabled.
// By default the plugin uses context.siteConfig.url + baseUrl
// from your Docusaurus config. (default: undefined — auto-detected)
// siteUrl: 'https://example.com/docs',
// Markdown blockquote prepended to every generated .md file.
// Satisfies the llms-txt-directive check (afdocs). When set, the
// plugin also provides a <LlmsDirective /> theme component for
// placement on any page. (default: null — disabled)
// directive: '> For the full docs index, see [llms.txt](/llms.txt).\n> Markdown versions of all pages are available by appending .md to any URL.',
}],
],
};When fullyQualifiedLinks is enabled, the plugin rewrites all internal
hyperlinks in the generated .md files to absolute URLs with .md extensions.
This makes the raw markdown useful outside the browser — for example, when an
LLM fetches a .md file, every link it encounters will be a fetchable URL
rather than a relative path that only works on the website.
Generated URLs have the form <siteUrl>/<docPath>.md, where <siteUrl> is
your Docusaurus url + baseUrl (e.g. https://example.com/docs) and
<docPath> is the resolved path relative to the docs root. For example, a
relative link ./algorithm/ in std/index.md becomes
https://example.com/docs/std/algorithm.md.
The plugin handles two kinds of internal links:
- Relative (
./algorithm/,../types) — resolved against the current file's directory (usingdirnameof the source path), then converted to a fully-qualified.mdURL. - Site-root-absolute (
/docs/roadmap,/docs/manual/types#string-literals) — converted directly to a fully-qualified.mdURL with the fragment preserved.
External links (https://...), anchor-only links (#section), and image
references (both inline  and reference-style ![alt][ref]) are
left unchanged.
The base URL defaults to url + baseUrl from your Docusaurus config. You can
override it with the siteUrl option if your production URL differs from the
config.
When directive is set to a markdown blockquote string, the plugin:
- Prepends it to every generated
.mdfile — before fully-qualified link processing, so links in the directive (like/llms.txt) are also rewritten whenfullyQualifiedLinksis enabled. - Provides a
LlmsDirectivetheme component — a server-rendered<blockquote>you can place on any page, satisfying the llms-txt-directive check. For doc pages, swizzleDocItem/Content(wrapping mode) and render<LlmsDirective />before the original content.
Example configuration:
directive: '> For the full docs index, see [llms.txt](/llms.txt).\n'
+ '> Markdown versions of all pages are available by appending .md to any URL.',The rendered HTML blockquote has the class llms-directive so you can style or
visually hide it with CSS (the spec permits hiding it while keeping it in the
server-rendered HTML for agents).
Doc pages — swizzle DocItem/Content:
// src/theme/DocItem/Content/index.js
import React from 'react';
import Content from '@theme-original/DocItem/Content';
import LlmsDirective from '@theme/LlmsDirective';
export default function ContentWrapper(props) {
return (
<>
<LlmsDirective />
<Content {...props} />
</>
);
}Non-doc pages — import the component directly:
import LlmsDirective from '@theme/LlmsDirective';
export default function HomePage() {
return (
<Layout>
<LlmsDirective />
{/* ... */}
</Layout>
);
}When directive is not configured, the component renders nothing.
The plugin uses theme components for icons, which can be overridden in your site's src/theme/ directory:
Override the copy icon:
Create src/theme/MarkdownCopyIcon/index.js:
import React from 'react';
import IconCopy from '@theme/Icon/Copy';
export default function MarkdownCopyIcon({ size = 16, style }) {
return <IconCopy style={{ width: size, height: size, ...style }} />;
}Override the success/check icon:
Create src/theme/MarkdownCheckIcon/index.js:
import React from 'react';
import IconSuccess from '@theme/Icon/Success';
export default function MarkdownCheckIcon({ size = 16, style }) {
return <IconSuccess style={{ width: size, height: size, ...style }} />;
}This allows you to use your site's existing icon components or any custom SVG icons.
You'll want to customize this yourself, but here's some CSS to get the button or drop-down in the page header looking good.
Whether you're using the button or drop-down, add these styles to your
src/css/custom.css to set the layout position:
/* Style the article header as flexbox */
article .markdown header {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
overflow: visible;
}
/* Allow h1 to grow and take available space */
article .markdown header h1 {
flex: 1 1 auto;
margin: 0;
}
/* Container for the markdown actions dropdown */
.markdown-actions-container {
flex-shrink: 0;
margin-left: auto;
position: relative;
}
/* Ensure dropdown wrapper has proper positioning */
.markdown-actions-container .dropdown {
position: relative;
}Then, if you're using the default button, add this:
.markdown-actions-container {
display: inline-flex;
align-items: center;
}
.markdown-actions-container .markdown-copy-button {
display: inline-flex;
align-items: center;
gap: 8px;
background-color: transparent;
border: none;
padding: 0;
font-size: 0.875rem;
cursor: pointer;
color: var(--ifm-color-emphasis-600);
}
.markdown-actions-container .markdown-copy-button:hover {
color: var(--ifm-color-emphasis-900);
}
.markdown-actions-container .markdown-copy-button svg {
fill: currentColor;
}Or if you set widgetType: 'dropdown', add these styles:
/* Base dropdown menu styles */
.markdown-actions-container .dropdown__menu {
z-index: 1000;
min-width: 220px;
right: auto;
left: 0;
}
/* Add hover effect for dropdown items */
.dropdown__link:hover {
background-color: var(--ifm-hover-overlay);
}
/* Responsive adjustments for mobile */
@media (max-width: 768px) {
.markdown-actions-container {
margin-right: clamp(0px, 0.5rem, 1rem);
margin-bottom: 1rem;
}
.markdown-actions-container .button {
font-size: 0.875rem;
padding: 0.375rem 0.75rem;
}
/* Right-align menu on mobile to prevent cutoff */
.markdown-actions-container .dropdown__menu {
right: 0;
left: auto;
min-width: min(220px, calc(100vw - 2rem));
max-width: calc(100vw - 2rem);
padding-bottom: 0.75rem;
}
}
/* RTL language support */
[dir="rtl"] .markdown-actions-container {
margin-left: 0;
margin-right: auto;
}
[dir="rtl"] .markdown-actions-container .dropdown__menu {
right: auto;
left: 0;
}
@media (max-width: 768px) {
[dir="rtl"] .markdown-actions-container .dropdown__menu {
left: 0;
right: auto;
}
}For more information, see the original repo at FlyNumber/markdown_docusaurus_plugin