Skip to content

Add Copy Markdown and View Raw features to documentation docs#158

Open
thibaudcolas wants to merge 1 commit intomainfrom
feat/copy-view-markdown-docs-10322830930238481409
Open

Add Copy Markdown and View Raw features to documentation docs#158
thibaudcolas wants to merge 1 commit intomainfrom
feat/copy-view-markdown-docs-10322830930238481409

Conversation

@thibaudcolas
Copy link
Owner

@thibaudcolas thibaudcolas commented Jan 28, 2026

Added "Copy Markdown" and "View Raw" features to the documentation pages to streamline content extraction for LLM usage. The implementation includes a swizzled EditThisPage component with interactive copy functionality and direct links to raw GitHub source files.


PR created automatically by Jules for task 10322830930238481409 started by @thibaudcolas

Summary by CodeRabbit

  • New Features
    • Added "Copy Markdown" button to easily copy page content to your clipboard
    • Added "Raw" link to view the raw markdown source
    • Displays success confirmation when markdown is copied
    • Improved responsive layout for smaller screens

✏️ Tip: You can customize this high-level summary in your review settings.

This change adds a "Copy Markdown" button and a "Raw" link to the documentation
footer, making it easier for users to extract content for use with LLMs.

- Swizzled the EditThisPage component to add new actions.
- Implemented "Copy Markdown" functionality using the GitHub raw content API.
- Added a direct "Raw" link to the source file on GitHub.
- Added styling to ensure visual consistency with the existing theme.
- Included internationalization support for the new labels.

Co-authored-by: thibaudcolas <877585+thibaudcolas@users.noreply.github.com>
@google-labs-jules
Copy link

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

Introduces EditThisPageWrapper component that extends EditThisPage with copy-to-clipboard and raw link functionality. The wrapper adds a button to copy Markdown content from GitHub and a link to view the raw source, with appropriate error handling and UI state feedback.

Changes

Cohort / File(s) Summary
EditThisPage Component
src/theme/EditThisPage/index.tsx
New wrapper component that wraps EditThisPage and adds copy Markdown functionality. Transforms GitHub edit URLs to raw URLs, fetches content via HTTP, copies to clipboard with 2-second success feedback, handles fetch/clipboard errors, and renders a Raw link for raw source access.
EditThisPage Styling
src/theme/EditThisPage/styles.module.css
CSS module with styles for the wrapper: flexbox container with wrapping and centered alignment, separator styling, copy button with link-like appearance and hover states, icon sizing, and responsive rule for screens ≤996px.

Sequence Diagram

sequenceDiagram
    participant User
    participant Component as EditThisPageWrapper
    participant GitHub as GitHub API
    participant Clipboard as Clipboard API

    User->>Component: Click "Copy Markdown"
    activate Component
    Component->>Component: Transform edit URL to raw URL
    Component->>GitHub: Fetch raw Markdown content
    activate GitHub
    GitHub-->>Component: Return raw content
    deactivate GitHub
    Component->>Clipboard: Copy content to clipboard
    activate Clipboard
    Clipboard-->>Component: Success/Error
    deactivate Clipboard
    Component->>Component: Update UI state ("Copied!" or error)
    Component-->>User: Show feedback (2 sec timeout)
    deactivate Component
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • Copy/view docs page as Markdown #145: This PR implements the "Copy/View Markdown" feature by adding copy-to-clipboard and raw link functionality to the EditThisPage component as requested.

Poem

🐰 A button to copy, a link to view raw,
The editor's markdown, without any flaw,
With one hoppy click, the content takes flight,
To clipboard it hops—what a marvelous sight! ✨📋

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adding Copy Markdown and View Raw features to documentation. It directly matches the primary objectives and the implemented functionality across both modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/theme/EditThisPage/index.tsx`:
- Around line 63-68: The current Translate usage embeds a ternary inside its
children which prevents both variants from being extracted; update the JSX in
EditThisPage/index.tsx to render two distinct Translate components keyed to the
different states of the copied variable (e.g., use separate IDs like
"theme.common.copyMarkdown.copied" for "Copied!" and
"theme.common.copyMarkdown.copy" for "Copy Markdown") and switch between them
with the copied conditional (copied ? <Translate id="...copied">...</Translate>
: <Translate id="...copy">...</Translate>), so each string is statically
extractable for i18n.
🧹 Nitpick comments (4)
src/theme/EditThisPage/index.tsx (4)

35-36: Consider cleanup for setTimeout on unmount.

If the component unmounts before the 2-second timeout completes, setCopied(false) will be called on an unmounted component. While React 19 handles this gracefully, it's cleaner to use useEffect cleanup or store the timeout ID for cancellation.

♻️ Suggested improvement using useRef
-import { type ReactNode, useState } from "react"
+import { type ReactNode, useState, useRef, useEffect } from "react"
...

 export default function EditThisPageWrapper(props: Props): ReactNode {
   const { editUrl } = props
   const [copied, setCopied] = useState(false)
+  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
+
+  useEffect(() => {
+    return () => {
+      if (timeoutRef.current) {
+        clearTimeout(timeoutRef.current)
+      }
+    }
+  }, [])

   const handleCopy = async () => {
     ...
       setCopied(true)
-      setTimeout(() => setCopied(false), 2000)
+      timeoutRef.current = setTimeout(() => setCopied(false), 2000)
     ...
   }

24-26: DRY: URL transformation logic is duplicated.

The same URL transformation appears in both handleCopy (lines 24-26) and in the render scope (lines 43-45). Consider extracting to a helper function.

♻️ Suggested extraction
+const toRawGitHubUrl = (editUrl: string): string =>
+  editUrl
+    .replace("github.com", "raw.githubusercontent.com")
+    .replace("/edit/", "/")
+
 export default function EditThisPageWrapper(props: Props): ReactNode {
   const { editUrl } = props
   const [copied, setCopied] = useState(false)
+  const rawUrl = editUrl ? toRawGitHubUrl(editUrl) : undefined

   const handleCopy = async () => {
-    if (!editUrl) {
+    if (!rawUrl) {
       return
     }

-    // Convert GitHub edit URL to raw URL
-    // From: https://github.com/thibaudcolas/draftail.org/edit/main/docs/introduction/getting-started.md
-    // To: https://raw.githubusercontent.com/thibaudcolas/draftail.org/main/docs/introduction/getting-started.md
-    const rawUrl = editUrl
-      .replace("github.com", "raw.githubusercontent.com")
-      .replace("/edit/", "/")
-
     try {
       const response = await fetch(rawUrl)
       ...
   }

-  const rawUrl = editUrl
-    ?.replace("github.com", "raw.githubusercontent.com")
-    .replace("/edit/", "/")

Also applies to: 43-45


37-40: Consider providing user feedback on copy failure.

Currently, errors are only logged to the console. Users won't know the copy failed. Consider showing a brief error state or toast notification.

💡 Example approach with error state
   const [copied, setCopied] = useState(false)
+  const [error, setError] = useState(false)

   const handleCopy = async () => {
     ...
     } catch (err) {
       // eslint-disable-next-line no-console
       console.error("Failed to copy markdown", err)
+      setError(true)
+      setTimeout(() => setError(false), 2000)
     }
   }

Then render an error indicator in the UI when error is true.


73-77: Consider adding rel="noreferrer" for security.

While noopener prevents the new page from accessing window.opener, adding noreferrer also prevents the Referer header from being sent, which is a common best practice for external links.

♻️ Suggested change
               <a
                 href={rawUrl}
                 target="_blank"
-                rel="noopener noreferrer"
+                rel="noreferrer"

Note: noreferrer implies noopener in modern browsers, so you can simplify to just noreferrer.

Comment on lines +63 to +68
<Translate
id="theme.common.copyMarkdown"
description="The link label to copy the Markdown source of the current page"
>
{copied ? "Copied!" : "Copy Markdown"}
</Translate>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

i18n issue: Translate component used with dynamic content.

The Translate component is designed for static strings that get extracted at build time. Using a ternary inside the children means only one variant will be extracted, breaking translations.

Use separate Translate components for each state:

🛠️ Proposed fix
-            <Translate
-              id="theme.common.copyMarkdown"
-              description="The link label to copy the Markdown source of the current page"
-            >
-              {copied ? "Copied!" : "Copy Markdown"}
-            </Translate>
+            {copied ? (
+              <Translate
+                id="theme.common.copiedMarkdown"
+                description="The label shown after successfully copying the Markdown source"
+              >
+                Copied!
+              </Translate>
+            ) : (
+              <Translate
+                id="theme.common.copyMarkdown"
+                description="The link label to copy the Markdown source of the current page"
+              >
+                Copy Markdown
+              </Translate>
+            )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Translate
id="theme.common.copyMarkdown"
description="The link label to copy the Markdown source of the current page"
>
{copied ? "Copied!" : "Copy Markdown"}
</Translate>
{copied ? (
<Translate
id="theme.common.copiedMarkdown"
description="The label shown after successfully copying the Markdown source"
>
Copied!
</Translate>
) : (
<Translate
id="theme.common.copyMarkdown"
description="The link label to copy the Markdown source of the current page"
>
Copy Markdown
</Translate>
)}
🤖 Prompt for AI Agents
In `@src/theme/EditThisPage/index.tsx` around lines 63 - 68, The current Translate
usage embeds a ternary inside its children which prevents both variants from
being extracted; update the JSX in EditThisPage/index.tsx to render two distinct
Translate components keyed to the different states of the copied variable (e.g.,
use separate IDs like "theme.common.copyMarkdown.copied" for "Copied!" and
"theme.common.copyMarkdown.copy" for "Copy Markdown") and switch between them
with the copied conditional (copied ? <Translate id="...copied">...</Translate>
: <Translate id="...copy">...</Translate>), so each string is statically
extractable for i18n.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant