diff --git a/website/src/components/CopyMarkdownButton/index.js b/website/src/components/CopyMarkdownButton/index.js
new file mode 100644
index 00000000000..39464a5903a
--- /dev/null
+++ b/website/src/components/CopyMarkdownButton/index.js
@@ -0,0 +1,254 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { useLocation } from '@docusaurus/router';
+import styles from './styles.module.css';
+
+const CHATGPT_PROMPT = `Read from {{document}} so I can ask you questions about it`
+const CLAUDE_PROMPT = `Read from {{document}} so I can answer questions about it`
+
+export default function CopyMarkdownButton({docId}) {
+ const [copied, setCopied] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [isOpen, setIsOpen] = useState(false);
+ const [content, setContent] = useState(null);
+ const location = useLocation();
+ const dropdownRef = useRef(null);
+
+ useEffect(() => {
+ const handleClickOutside = (event) => {
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
+ setIsOpen(false);
+ }
+ };
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => document.removeEventListener('mousedown', handleClickOutside);
+ }, []);
+
+ const fetchMarkdown = async () => {
+ const mdUrl = `${window.location.origin}/${docId}.md`;
+
+ if (content) return content;
+ try {
+ const response = await fetch(mdUrl);
+ if (!response.ok) {
+ throw new Error('Failed to fetch markdown content');
+ }
+ const text = await response.text();
+ setContent(text);
+ return text;
+ } catch (error) {
+ console.error('Failed to fetch markdown:', error);
+ throw error;
+ }
+ };
+
+ const handleCopy = async () => {
+ try {
+ setLoading(true);
+ const text = await fetchMarkdown();
+ await navigator.clipboard.writeText(text);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch (error) {
+ alert('Failed to copy markdown content. The .md file may not be available.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleOpenInChatGPT = async () => {
+ const browserUrl = `${window.location.origin}${location.pathname}`;
+ const prompt = CHATGPT_PROMPT.replace('{{document}}', browserUrl);
+ window.open(`https://chatgpt.com/?prompt=${prompt}`, '_blank');
+ };
+
+ const handleOpenInClaude = async () => {
+ const browserUrl = `${window.location.origin}${location.pathname}`;
+ const prompt = CLAUDE_PROMPT.replace('{{document}}', browserUrl);
+ window.open(`https://claude.ai/new?q=${prompt}`, '_blank');
+ };
+
+ const handleConnectMCPCursor = () => {
+ window.open(`cursor:mcp/install?%7B%22name%22%3A%22NEAR%20Docs%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fdocs.near.org%2Fmcp%22%7D`, '_blank');
+ setIsOpen(false);
+ };
+
+ const handleConnectMCPVSCode = () => {
+ window.open(`vscode:mcp/install?%7B%22name%22%3A%22NEAR%20Docs%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fdocs.near.org%2Fmcp%22%7D`, '_blank');
+ setIsOpen(false);
+ };
+
+ return (
+
+
+
+
+
+
+ {isOpen && (
+
+
+
+
+
+
+ )}
+
+ );
+}
+
+function MenuIcon() {
+ return (
+
+ );
+}
+
+function CopyIcon() {
+ return (
+
+ );
+}
+
+function CheckIcon() {
+ return (
+
+ );
+}
+
+function ChevronIcon({ isOpen }) {
+ return (
+
+ );
+}
+
+function ExternalLinkIcon() {
+ return (
+
+ );
+}
+
+function LinkIcon() {
+ return (
+
+ );
+}
diff --git a/website/src/components/CopyMarkdownButton/styles.module.css b/website/src/components/CopyMarkdownButton/styles.module.css
new file mode 100644
index 00000000000..2f66c562bc7
--- /dev/null
+++ b/website/src/components/CopyMarkdownButton/styles.module.css
@@ -0,0 +1,123 @@
+.container {
+ position: relative;
+ display: inline-block;
+}
+
+.buttonGroup {
+ display: inline-flex;
+ border: 1px solid var(--ifm-color-emphasis-300);
+ border-radius: 6px;
+ overflow: hidden;
+ margin: 8px 0;
+}
+
+.copyButton {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 12px;
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--ifm-color-emphasis-700);
+ background-color: var(--ifm-color-emphasis-100);
+ border: none;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ border-radius: 0;
+}
+
+.copyButton:hover:not(:disabled) {
+ background-color: var(--ifm-color-emphasis-200);
+}
+
+.copyButton:disabled {
+ cursor: not-allowed;
+ opacity: 0.7;
+}
+
+.dropdownButton {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 6px 8px;
+ color: var(--ifm-color-emphasis-700);
+ background-color: var(--ifm-color-emphasis-100);
+ border: none;
+ border-left: 1px solid var(--ifm-color-emphasis-300);
+ cursor: pointer;
+ transition: all 0.2s ease;
+ border-radius: 0;
+}
+
+.dropdownButton:hover:not(:disabled) {
+ background-color: var(--ifm-color-emphasis-200);
+}
+
+.dropdownButton:disabled {
+ cursor: not-allowed;
+ opacity: 0.7;
+}
+
+.dropdown {
+ position: absolute;
+ right: 0;
+ border: 1px solid var(--ifm-color-emphasis-300);
+ border-radius: 6px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ z-index: 1000;
+ min-width: 200px;
+ overflow: hidden;
+}
+
+.menuItem {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 14px;
+ font-size: 13px;
+ color: var(--ifm-color-emphasis-700);
+ border: none;
+ cursor: pointer;
+ text-align: left;
+ transition: all 0.2s ease;
+ background-color: var(--ifm-color-emphasis-100);
+}
+
+.menuItem:hover:not(:disabled) {
+ background-color: var(--ifm-color-emphasis-200);
+}
+
+.menuItem:disabled {
+ cursor: not-allowed;
+ opacity: 0.6;
+}
+
+.icon {
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+}
+
+.chevron {
+ transition: transform 0.2s ease;
+}
+
+.chevron.open {
+ transform: rotate(180deg);
+}
+
+.spinner {
+ width: 14px;
+ height: 14px;
+ border: 2px solid var(--ifm-color-emphasis-300);
+ border-top-color: var(--ifm-color-emphasis-700);
+ border-radius: 50%;
+ animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/website/src/theme/DocItem/Content/index.js b/website/src/theme/DocItem/Content/index.js
new file mode 100644
index 00000000000..43dcbdc77ca
--- /dev/null
+++ b/website/src/theme/DocItem/Content/index.js
@@ -0,0 +1,48 @@
+import React from 'react';
+import clsx from 'clsx';
+import {ThemeClassNames} from '@docusaurus/theme-common';
+import {useDoc} from '@docusaurus/plugin-content-docs/client';
+import Heading from '@theme/Heading';
+import MDXContent from '@theme/MDXContent';
+import CopyMarkdownButton from '@site/src/components/CopyMarkdownButton';
+import styles from './styles.module.css';
+/**
+ Title can be declared inside md content or declared through
+ front matter and added manually. To make both cases consistent,
+ the added title is added under the same div.markdown block
+ See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120
+
+ We render a "synthetic title" if:
+ - user doesn't ask to hide it with front matter
+ - the markdown content does not already contain a top-level h1 heading
+*/
+function useSyntheticTitle() {
+ const {metadata, frontMatter, contentTitle} = useDoc();
+ const shouldRender =
+ !frontMatter.hide_title && typeof contentTitle === 'undefined';
+ if (!shouldRender) {
+ return null;
+ }
+ return metadata.title;
+}
+export default function DocItemContent({children}) {
+ const syntheticTitle = useSyntheticTitle();
+ const {metadata} = useDoc();
+ return (
+
+ {syntheticTitle && (
+
+
+
+ {syntheticTitle}
+
+
+
+
+
+
+ )}
+
{children}
+
+ );
+}
diff --git a/website/src/theme/DocItem/Content/styles.module.css b/website/src/theme/DocItem/Content/styles.module.css
new file mode 100644
index 00000000000..26085cd47da
--- /dev/null
+++ b/website/src/theme/DocItem/Content/styles.module.css
@@ -0,0 +1,35 @@
+.headerRow {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ width: 100%;
+}
+
+.titleCol {
+ flex: 1 1 auto;
+ min-width: 0;
+}
+
+.buttonCol {
+ flex: 0 0 auto;
+ display: flex;
+ justify-content: flex-end;
+ margin-bottom: calc( var(--ifm-h1-vertical-rhythm-bottom) * var(--ifm-leading) );
+}
+
+@media (max-width: 640px) {
+ .headerRow {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+ }
+
+ .buttonCol {
+ width: 100%;
+ justify-content: flex-start;
+ }
+
+ .titleCol h1 {
+ margin-bottom: 0rem !important;
+ }
+}
\ No newline at end of file