Skip to content

Commit 02cb46b

Browse files
authored
Merge pull request #268 from genlayerlabs/dxp-504-allow-users-to-copyexport-a-page-in-the-docs
feat: allow users to copyexport a page in the docs
2 parents 0e07678 + f272710 commit 02cb46b

14 files changed

Lines changed: 370 additions & 10 deletions

components/copy-page.module.css

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/* CopyPage Component Styles */
2+
3+
.container {
4+
position: relative;
5+
margin-right: 16px;
6+
margin-left: 10px;
7+
display: flex;
8+
}
9+
10+
.mainButton {
11+
display: flex;
12+
align-items: center;
13+
gap: 2px;
14+
padding: 6px 10px;
15+
font-size: 12px;
16+
font-weight: 500;
17+
color: #374151;
18+
background-color: white;
19+
border: 1px solid #d1d5db;
20+
border-right: none;
21+
border-top-left-radius: 6px;
22+
border-bottom-left-radius: 6px;
23+
border-top-right-radius: 0;
24+
border-bottom-right-radius: 0;
25+
cursor: pointer;
26+
transition: all 0.2s;
27+
}
28+
29+
.mainButton:hover {
30+
background-color: #f9fafb;
31+
}
32+
33+
.arrowButton {
34+
display: flex;
35+
align-items: center;
36+
justify-content: center;
37+
padding: 6px 6px;
38+
font-size: 12px;
39+
font-weight: 500;
40+
color: #374151;
41+
background-color: white;
42+
border: 1px solid #d1d5db;
43+
border-left: 1px solid #d1d5db;
44+
border-top-left-radius: 0;
45+
border-bottom-left-radius: 0;
46+
border-top-right-radius: 6px;
47+
border-bottom-right-radius: 6px;
48+
cursor: pointer;
49+
transition: all 0.2s;
50+
}
51+
52+
.arrowButton:hover {
53+
background-color: #f9fafb;
54+
}
55+
56+
.arrowIcon {
57+
width: 16px;
58+
height: 16px;
59+
transition: transform 0.2s;
60+
}
61+
62+
.arrowIconRotated {
63+
transform: rotate(180deg);
64+
}
65+
66+
.dropdown {
67+
position: absolute;
68+
right: 0;
69+
top: 100%;
70+
margin-top: 8px;
71+
width: 256px;
72+
background-color: white;
73+
border: 1px solid #e5e7eb;
74+
border-radius: 6px;
75+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
76+
z-index: 50;
77+
}
78+
79+
.dropdownContent {
80+
padding: 4px 0;
81+
}
82+
83+
.dropdownButton {
84+
display: flex;
85+
align-items: center;
86+
width: 100%;
87+
padding: 12px 16px;
88+
font-size: 14px;
89+
color: #374151;
90+
background-color: transparent;
91+
border: none;
92+
cursor: pointer;
93+
text-align: left;
94+
transition: background-color 0.2s;
95+
}
96+
97+
.dropdownButton:hover {
98+
background-color: #f3f4f6;
99+
}
100+
101+
.mainButtonIcon {
102+
width: 16px;
103+
height: 16px;
104+
margin-right: 6px;
105+
}
106+
107+
.dropdownIcon {
108+
width: 16px;
109+
height: 16px;
110+
margin-right: 12px;
111+
}
112+
113+
.dropdownText {
114+
display: flex;
115+
flex-direction: column;
116+
align-items: flex-start;
117+
flex: 1;
118+
}
119+
120+
.dropdownTitle {
121+
font-weight: 500;
122+
}
123+
124+
.dropdownDescription {
125+
font-size: 12px;
126+
color: #6b7280;
127+
margin-top: 2px;
128+
}
129+
130+
@media (max-width: 640px) {
131+
.container {
132+
display: none;
133+
}
134+
}

components/copy-page.tsx

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import React, { useState, useRef, useEffect } from 'react';
2+
import { useRouter } from 'next/router';
3+
import styles from './copy-page.module.css';
4+
import CopyIcon from './icons/copy';
5+
import CheckIcon from './icons/check';
6+
import ChevronDownIcon from './icons/chevron-down';
7+
import DocumentIcon from './icons/document';
8+
import ChatGPTIcon from './icons/chatgpt';
9+
import AnthropicIcon from './icons/anthropic';
10+
11+
const CopyPage: React.FC = () => {
12+
const [isOpen, setIsOpen] = useState(false);
13+
const [isCopied, setIsCopied] = useState(false);
14+
const dropdownRef = useRef<HTMLDivElement>(null);
15+
16+
// Close dropdown when clicking outside
17+
useEffect(() => {
18+
const handleClickOutside = (event: MouseEvent) => {
19+
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
20+
setIsOpen(false);
21+
}
22+
};
23+
24+
document.addEventListener('mousedown', handleClickOutside);
25+
return () => document.removeEventListener('mousedown', handleClickOutside);
26+
}, []);
27+
28+
const copyPageAsMarkdown = async () => {
29+
try {
30+
// Get the current page content
31+
const pageContent = document.querySelector('main')?.innerText || '';
32+
const pageTitle = document.title;
33+
34+
// Create markdown content
35+
const markdownContent = `# ${pageTitle}\n\n${pageContent}`;
36+
37+
await navigator.clipboard.writeText(markdownContent);
38+
39+
// Show success feedback
40+
setIsCopied(true);
41+
setTimeout(() => {
42+
setIsCopied(false);
43+
}, 2000);
44+
} catch (error) {
45+
console.error('Failed to copy page:', error);
46+
}
47+
setIsOpen(false);
48+
};
49+
50+
const viewAsMarkdown = () => {
51+
const pageContent = document.querySelector('main')?.innerText || '';
52+
const pageTitle = document.title;
53+
const markdownContent = `# ${pageTitle}\n\n${pageContent}`;
54+
55+
// Open in new window/tab
56+
const blob = new Blob([markdownContent], { type: 'text/markdown' });
57+
const url = URL.createObjectURL(blob);
58+
window.open(url, '_blank');
59+
URL.revokeObjectURL(url);
60+
setIsOpen(false);
61+
};
62+
63+
const openInAI = (platform: 'chatgpt' | 'claude') => {
64+
const currentUrl = window.location.href;
65+
const prompt = `I'm building with GenLayer - can you read this docs page ${currentUrl} so I can ask you questions about it?`;
66+
const encodedPrompt = encodeURIComponent(prompt);
67+
68+
const urls = {
69+
chatgpt: `https://chatgpt.com/?q=${encodedPrompt}`,
70+
claude: `https://claude.ai/new?q=${encodedPrompt}`
71+
};
72+
73+
window.open(urls[platform], '_blank');
74+
setIsOpen(false);
75+
};
76+
77+
const openInChatGPT = () => openInAI('chatgpt');
78+
const openInClaude = () => openInAI('claude');
79+
80+
return (
81+
<div className={styles.container} ref={dropdownRef}>
82+
<button
83+
onClick={copyPageAsMarkdown}
84+
className={styles.mainButton}
85+
>
86+
{isCopied ? (
87+
<CheckIcon className={styles.mainButtonIcon} />
88+
) : (
89+
<CopyIcon className={styles.mainButtonIcon} />
90+
)}
91+
Copy page
92+
</button>
93+
94+
<button
95+
onClick={() => setIsOpen(!isOpen)}
96+
className={styles.arrowButton}
97+
>
98+
<ChevronDownIcon
99+
className={`${styles.arrowIcon} ${isOpen ? styles.arrowIconRotated : ''}`}
100+
/>
101+
</button>
102+
103+
{isOpen && (
104+
<div className={styles.dropdown}>
105+
<div className={styles.dropdownContent}>
106+
<button
107+
onClick={copyPageAsMarkdown}
108+
className={styles.dropdownButton}
109+
>
110+
<CopyIcon className={styles.dropdownIcon} />
111+
<div className={styles.dropdownText}>
112+
<span className={styles.dropdownTitle}>Copy page</span>
113+
<span className={styles.dropdownDescription}>Copy the page as Markdown for LLMs</span>
114+
</div>
115+
</button>
116+
117+
<button
118+
onClick={viewAsMarkdown}
119+
className={styles.dropdownButton}
120+
>
121+
<DocumentIcon className={styles.dropdownIcon} />
122+
<div className={styles.dropdownText}>
123+
<span className={styles.dropdownTitle}>View as MarkDown</span>
124+
<span className={styles.dropdownDescription}>View this page as plain text</span>
125+
</div>
126+
</button>
127+
128+
<button
129+
onClick={openInChatGPT}
130+
className={styles.dropdownButton}
131+
>
132+
<ChatGPTIcon className={styles.dropdownIcon} />
133+
<div className={styles.dropdownText}>
134+
<span className={styles.dropdownTitle}>Open in ChatGPT</span>
135+
<span className={styles.dropdownDescription}>Ask questions about this page</span>
136+
</div>
137+
</button>
138+
139+
<button
140+
onClick={openInClaude}
141+
className={styles.dropdownButton}
142+
>
143+
<AnthropicIcon className={styles.dropdownIcon} />
144+
<div className={styles.dropdownText}>
145+
<span className={styles.dropdownTitle}>Open in Claude</span>
146+
<span className={styles.dropdownDescription}>Ask questions about this page</span>
147+
</div>
148+
</button>
149+
</div>
150+
</div>
151+
)}
152+
</div>
153+
);
154+
};
155+
156+
export default CopyPage;

components/icons/anthropic.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function AnthropicIcon({ className }) {
2+
return (
3+
<svg className={className} width="16" height="16" fill="currentColor" viewBox="0 0 92.2 65">
4+
<path d="M66.5,0H52.4l25.7,65h14.1L66.5,0z M25.7,0L0,65h14.4l5.3-13.6h26.9L51.8,65h14.4L40.5,0C40.5,0,25.7,0,25.7,0z M24.3,39.3l8.8-22.8l8.8,22.8H24.3z"/>
5+
</svg>
6+
);
7+
}

components/icons/chatgpt.js

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/icons/check.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function CheckIcon({ className }) {
2+
return (
3+
<svg className={className} width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
4+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
5+
</svg>
6+
);
7+
}

components/icons/chevron-down.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function ChevronDownIcon({ className }) {
2+
return (
3+
<svg className={className} width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
4+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
5+
</svg>
6+
);
7+
}

components/icons/copy.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function CopyIcon({ className }) {
2+
return (
3+
<svg className={className} width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
4+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
5+
</svg>
6+
);
7+
}

components/icons/document.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function DocumentIcon({ className }) {
2+
return (
3+
<svg className={className} width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
4+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
5+
</svg>
6+
);
7+
}

components/icons/github.js

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)