diff --git a/CHANGELOG.md b/CHANGELOG.md index f76554e..a0011af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.3.0] - Unreleased + +### Changed +- Exposed `MarkdownActionsDropdown` as a first-class Docusaurus theme component at `theme/MarkdownActionsDropdown`, so consumers can swizzle it directly with `npm run swizzle docusaurus-markdown-source-plugin MarkdownActionsDropdown -- --eject`. +- Updated `Root` to render `@theme/MarkdownActionsDropdown`, allowing swizzled dropdown overrides to be picked up automatically while keeping the existing injection behavior internal. + +### Fixed +- Made `theme/MarkdownActionsDropdown` self-contained so swizzled copies no longer fail to build by trying to resolve plugin-local imports like `../../lib/markdown-path` inside the consumer site's `src/theme/` directory. + ## [2.2.5] - 2026-05-03 ### Fixed diff --git a/README.md b/README.md index 15b370b..852db37 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ article .markdown header h1 { /* Add hover effect for dropdown items */ .dropdown__link:hover { - background-color: var(--ifm-hover-overlay); + background-color: rgba(0, 0, 0, 0.05); } /* Responsive adjustments for mobile */ @@ -296,7 +296,9 @@ location ~* ^/docs/.*/img/.* { | `X-Robots-Tag: noindex, nofollow` | Prevents search engines from indexing (avoids duplicate content SEO issues) while allowing AI assistants to access | | `Cache-Control` | Balances performance (caching) with content freshness | -## CSS Customization +## Customization + +### CSS Customization You can customize the dropdown appearance by overriding these CSS classes in your `custom.css`: @@ -317,6 +319,18 @@ You can customize the dropdown appearance by overriding these CSS classes in you } ``` +### Component Customization with Swizzling + +For deeper changes such as button labels, icons, menu items, analytics, or custom copy/open behavior, swizzle the dropdown component itself: + +```bash +npm run swizzle docusaurus-markdown-source-plugin MarkdownActionsDropdown -- --eject +``` + +This creates a local theme override at `src/theme/MarkdownActionsDropdown/index.js` in your Docusaurus site. The plugin's internal `Root` component will keep handling page injection, but it renders `@theme/MarkdownActionsDropdown`, so your swizzled component is used automatically. + +If you only need to wrap the default dropdown, import the original component from `@theme-original/MarkdownActionsDropdown` inside the swizzled file and compose around it. + ## Troubleshooting ### Dropdown Not Appearing diff --git a/components/MarkdownActionsDropdown/index.js b/components/MarkdownActionsDropdown/index.js index c0622ad..8f9538b 100644 --- a/components/MarkdownActionsDropdown/index.js +++ b/components/MarkdownActionsDropdown/index.js @@ -1,141 +1 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { getMarkdownUrl } from '../../lib/markdown-path'; - -export default function MarkdownActionsDropdown() { - const [copied, setCopied] = useState(false); - const [isOpen, setIsOpen] = useState(false); - const dropdownRef = useRef(null); - const copyResetTimerRef = useRef(null); - - // Clear any pending copy-reset timer on unmount so it cannot fire setState - // on a torn-down component or flip UI state after the user has navigated. - useEffect(() => () => { - if (copyResetTimerRef.current) { - clearTimeout(copyResetTimerRef.current); - copyResetTimerRef.current = null; - } - }, []); - - // Get pathname from window.location for URL construction - const currentPath = typeof window !== 'undefined' ? window.location.pathname : ''; - - // Handle click outside to close dropdown - useEffect(() => { - // Only add listener if dropdown is open - if (!isOpen) return; - - const handleClickOutside = (event) => { - // If the click is outside the dropdown, close it - if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { - setIsOpen(false); - } - }; - - // Add event listener to document - // Use mousedown instead of click for better UX (fires before click) - document.addEventListener('mousedown', handleClickOutside); - - // Cleanup function to remove event listener - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [isOpen]); - - const markdownUrl = getMarkdownUrl(currentPath); - - // Handle opening markdown in new tab - const handleOpenMarkdown = () => { - window.open(markdownUrl, '_blank'); - setIsOpen(false); - }; - - // Handle copying markdown to clipboard - const handleCopyMarkdown = async () => { - // Cancel any in-flight reset timer up front so a stale timer can't flip - // state during a slow fetch or after a rapid second click. - if (copyResetTimerRef.current) { - clearTimeout(copyResetTimerRef.current); - copyResetTimerRef.current = null; - } - - try { - const response = await fetch(markdownUrl); - if (!response.ok) { - throw new Error('Failed to fetch markdown'); - } - const markdown = await response.text(); - await navigator.clipboard.writeText(markdown); - - setCopied(true); - copyResetTimerRef.current = setTimeout(() => { - setCopied(false); - copyResetTimerRef.current = null; - }, 2000); - } catch (error) { - console.error('Failed to copy markdown:', error); - alert('Failed to copy markdown. Please try again.'); - } - }; - - return ( -