Skip to content

Commit b484634

Browse files
authored
Merge pull request #1231 from onflow/ai-dropdow-
Add sidebar in docs for better integration with LLMs
2 parents 1cb2819 + 4d61ca0 commit b484634

File tree

6 files changed

+346
-0
lines changed

6 files changed

+346
-0
lines changed

src/theme/DocActionsDropdown/index.js

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import React, { useState } from 'react';
2+
import clsx from 'clsx';
3+
import styles from './styles.module.css';
4+
5+
export default function DocActionsDropdown() {
6+
const [isOpen, setIsOpen] = useState(false);
7+
8+
const buildRawUrl = (path, isIndex) => {
9+
if (isIndex) {
10+
// For index files, use path/index.md
11+
return `https://raw.githubusercontent.com/onflow/docs/main/docs/${path}/index.md`;
12+
} else {
13+
// For regular files, use path.md
14+
return `https://raw.githubusercontent.com/onflow/docs/main/docs/${path}.md`;
15+
}
16+
};
17+
18+
const fetchMarkdown = async (path) => {
19+
// First, try to determine if this is an index.md file by checking both paths
20+
const directPath = `https://raw.githubusercontent.com/onflow/docs/main/docs/${path}.md`;
21+
const indexPath = `https://raw.githubusercontent.com/onflow/docs/main/docs/${path}/index.md`;
22+
23+
try {
24+
// Try the index path first
25+
const indexResponse = await fetch(indexPath);
26+
if (indexResponse.ok) {
27+
return { url: indexPath, text: await indexResponse.text() };
28+
}
29+
30+
// If index path fails, try the direct path
31+
const directResponse = await fetch(directPath);
32+
if (directResponse.ok) {
33+
return { url: directPath, text: await directResponse.text() };
34+
}
35+
36+
// If both fail, return null
37+
return null;
38+
} catch (error) {
39+
console.error('Error fetching markdown:', error);
40+
return null;
41+
}
42+
};
43+
44+
const handleCopyMarkdown = async () => {
45+
try {
46+
const path = window.location.pathname.replace(/^\/docs\/?/, '').replace(/\/$/, '');
47+
const result = await fetchMarkdown(path);
48+
49+
if (result) {
50+
navigator.clipboard.writeText(result.text);
51+
setIsOpen(false);
52+
} else {
53+
throw new Error('Could not fetch markdown');
54+
}
55+
} catch (error) {
56+
console.error('Error copying markdown:', error);
57+
// Fallback to GitHub
58+
const currentPath = window.location.pathname.replace(/^\/docs\/?/, '');
59+
window.open(`https://github.com/onflow/docs/tree/main/docs/${currentPath}`, '_blank');
60+
}
61+
};
62+
63+
const handleViewMarkdown = async () => {
64+
try {
65+
const path = window.location.pathname.replace(/^\/docs\/?/, '').replace(/\/$/, '');
66+
const result = await fetchMarkdown(path);
67+
68+
if (result) {
69+
window.open(result.url, '_blank');
70+
setIsOpen(false);
71+
} else {
72+
// Fallback to GitHub
73+
const currentPath = window.location.pathname.replace(/^\/docs\/?/, '');
74+
window.open(`https://github.com/onflow/docs/tree/main/docs/${currentPath}`, '_blank');
75+
}
76+
} catch (error) {
77+
console.error('Error viewing markdown:', error);
78+
// Fallback to GitHub
79+
const currentPath = window.location.pathname.replace(/^\/docs\/?/, '');
80+
window.open(`https://github.com/onflow/docs/tree/main/docs/${currentPath}`, '_blank');
81+
}
82+
};
83+
84+
const handleOpenInChatGPT = async () => {
85+
try {
86+
const path = window.location.pathname.replace(/^\/docs\/?/, '').replace(/\/$/, '');
87+
const result = await fetchMarkdown(path);
88+
89+
if (result) {
90+
const prompt = `Analyze this documentation: ${result.url}. After reading, ask me what I'd like to know. Keep responses focused on the content.`;
91+
const encodedPrompt = encodeURIComponent(prompt);
92+
window.open(`https://chatgpt.com/?q=${encodedPrompt}`, '_blank');
93+
} else {
94+
// Fallback to current URL
95+
const currentUrl = window.location.href;
96+
const prompt = `Analyze this documentation: ${currentUrl}. After reading, ask me what I'd like to know. Keep responses focused on the content.`;
97+
const encodedPrompt = encodeURIComponent(prompt);
98+
window.open(`https://chatgpt.com/?q=${encodedPrompt}`, '_blank');
99+
}
100+
setIsOpen(false);
101+
} catch (error) {
102+
console.error('Error opening in ChatGPT:', error);
103+
// Fallback to current URL
104+
const currentUrl = window.location.href;
105+
const prompt = `Analyze this documentation: ${currentUrl}. After reading, ask me what I'd like to know. Keep responses focused on the content.`;
106+
const encodedPrompt = encodeURIComponent(prompt);
107+
window.open(`https://chatgpt.com/?q=${encodedPrompt}`, '_blank');
108+
setIsOpen(false);
109+
}
110+
};
111+
112+
const handleOpenInClaude = async () => {
113+
try {
114+
const path = window.location.pathname.replace(/^\/docs\/?/, '').replace(/\/$/, '');
115+
const result = await fetchMarkdown(path);
116+
117+
if (result) {
118+
const prompt = `Review this documentation: ${result.url}. Once complete, ask me what questions I have. Stay focused on the provided content.`;
119+
const encodedPrompt = encodeURIComponent(prompt);
120+
window.open(`https://claude.ai/chat/new?prompt=${encodedPrompt}`, '_blank');
121+
} else {
122+
// Fallback to current URL
123+
const currentUrl = window.location.href;
124+
const prompt = `Review this documentation: ${currentUrl}. Once complete, ask me what questions I have. Stay focused on the provided content.`;
125+
const encodedPrompt = encodeURIComponent(prompt);
126+
window.open(`https://claude.ai/chat/new?prompt=${encodedPrompt}`, '_blank');
127+
}
128+
setIsOpen(false);
129+
} catch (error) {
130+
console.error('Error opening in Claude:', error);
131+
// Fallback to current URL
132+
const currentUrl = window.location.href;
133+
const prompt = `Review this documentation: ${currentUrl}. Once complete, ask me what questions I have. Stay focused on the provided content.`;
134+
const encodedPrompt = encodeURIComponent(prompt);
135+
window.open(`https://claude.ai/chat/new?prompt=${encodedPrompt}`, '_blank');
136+
setIsOpen(false);
137+
}
138+
};
139+
140+
const handleOpenFlowKnowledge = () => {
141+
window.open('https://github.com/onflow/Flow-Data-Sources/tree/main/merged_docs', '_blank');
142+
setIsOpen(false);
143+
};
144+
145+
const handleArrowClick = (e) => {
146+
e.stopPropagation();
147+
setIsOpen(!isOpen);
148+
};
149+
150+
return (
151+
<div className={styles.dropdownContainer}>
152+
<button
153+
className={styles.dropdownButton}
154+
onClick={handleOpenInChatGPT}
155+
>
156+
Open in ChatGPT
157+
<span className={styles.arrow} onClick={handleArrowClick} />
158+
</button>
159+
{isOpen && (
160+
<div className={styles.dropdownMenu}>
161+
<button onClick={handleOpenInClaude} className={styles.menuItem}>
162+
Open in Claude
163+
</button>
164+
<div className={styles.divider} />
165+
<button onClick={handleCopyMarkdown} className={styles.menuItem}>
166+
Copy as Markdown
167+
</button>
168+
<button onClick={handleViewMarkdown} className={styles.menuItem}>
169+
View Source Markdown
170+
</button>
171+
<div className={styles.divider} />
172+
<button onClick={handleOpenFlowKnowledge} className={styles.menuItem}>
173+
Full Flow Knowledge Source
174+
</button>
175+
</div>
176+
)}
177+
</div>
178+
);
179+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
.dropdownContainer {
2+
position: relative;
3+
display: inline-block;
4+
width: 100%;
5+
margin-top: 1rem;
6+
}
7+
8+
.dropdownButton {
9+
display: flex;
10+
align-items: center;
11+
justify-content: space-between;
12+
width: 100%;
13+
padding: 8px 16px;
14+
background-color: var(--ifm-color-primary);
15+
color: white;
16+
border: none;
17+
border-radius: 4px;
18+
cursor: pointer;
19+
font-size: 14px;
20+
position: relative;
21+
}
22+
23+
.dropdownButton:hover {
24+
background: var(--ifm-color-primary-dark);
25+
}
26+
27+
.arrow {
28+
width: 40%;
29+
height: 100%;
30+
position: absolute;
31+
right: 0;
32+
top: 0;
33+
display: flex;
34+
align-items: center;
35+
justify-content: flex-end;
36+
padding-right: 8px;
37+
cursor: pointer;
38+
}
39+
40+
.arrow::after {
41+
content: '';
42+
width: 0;
43+
height: 0;
44+
border-left: 5px solid transparent;
45+
border-right: 5px solid transparent;
46+
border-top: 5px solid white;
47+
transition: transform 0.2s ease;
48+
}
49+
50+
.dropdownButton:hover .arrow::after {
51+
transform: translateY(2px);
52+
}
53+
54+
.dropdownButton[aria-expanded="true"] .arrow {
55+
transform: rotate(180deg);
56+
}
57+
58+
.dropdownMenu {
59+
position: fixed;
60+
width: calc(100% - 30px);
61+
max-width: 300px;
62+
background: var(--ifm-background-color);
63+
border: 1px solid var(--ifm-color-emphasis-200);
64+
border-radius: 4px;
65+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
66+
z-index: 9999;
67+
margin-top: 0.5rem;
68+
max-height: 80vh;
69+
overflow-y: auto;
70+
}
71+
72+
.menuItem {
73+
display: block;
74+
width: 100%;
75+
padding: 0.75rem 1rem;
76+
text-align: left;
77+
border: none;
78+
background: none;
79+
color: var(--ifm-font-color-base);
80+
cursor: pointer;
81+
font-size: 0.9rem;
82+
transition: background-color 0.2s ease;
83+
}
84+
85+
.menuItem:hover {
86+
background: var(--ifm-color-emphasis-100);
87+
color: var(--ifm-font-color-base);
88+
}
89+
90+
.divider {
91+
height: 1px;
92+
background: var(--ifm-color-emphasis-200);
93+
margin: 0.5rem 0;
94+
}
95+
96+
[data-theme="dark"] .dropdownMenu {
97+
background: var(--ifm-background-color);
98+
border-color: var(--ifm-color-emphasis-300);
99+
}
100+
101+
[data-theme="dark"] .menuItem {
102+
color: var(--ifm-font-color-base);
103+
}
104+
105+
[data-theme="dark"] .menuItem:hover {
106+
background: var(--ifm-color-emphasis-200);
107+
color: var(--ifm-font-color-base);
108+
}
109+
110+
[data-theme="dark"] .divider {
111+
background: var(--ifm-color-emphasis-300);
112+
}

src/theme/DocItem/index.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react';
2+
import DocItem from '@theme-original/DocItem';
3+
import type { Props } from '@theme/DocItem';
4+
5+
export default function DocItemWrapper(props: Props): JSX.Element {
6+
return (
7+
<div className="doc-item-wrapper">
8+
<div className="doc-actions-container">
9+
</div>
10+
<DocItem {...props} />
11+
</div>
12+
);
13+
}

src/theme/DocItem/styles.module.css

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.doc-item-wrapper {
2+
position: relative;
3+
}
4+
5+
.doc-actions-container {
6+
position: absolute;
7+
top: 1rem;
8+
right: 1rem;
9+
z-index: 10;
10+
}
11+
12+
@media (max-width: 996px) {
13+
.doc-actions-container {
14+
position: static;
15+
margin-bottom: 1rem;
16+
display: flex;
17+
justify-content: flex-end;
18+
}
19+
}

src/theme/Layout/styles.module.css

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.layoutWrapper {
2+
position: relative;
3+
}
4+
5+
.docActionsContainer {
6+
position: fixed;
7+
top: 1rem;
8+
right: 1rem;
9+
z-index: 1000;
10+
}
11+
12+
@media (max-width: 996px) {
13+
.docActionsContainer {
14+
position: static;
15+
margin: 1rem;
16+
display: flex;
17+
justify-content: flex-end;
18+
}
19+
}

src/theme/TOC/index.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import clsx from 'clsx';
33
import TOCItems from '@theme/TOCItems';
44
import type { Props } from '@theme/TOC';
5+
import DocActionsDropdown from '../DocActionsDropdown';
56

67
import styles from './styles.module.css';
78
import FeedbackFaces from '@site/src/components/feedbackFaces';
@@ -17,6 +18,9 @@ export default function TOC({ className, ...props }: Props): JSX.Element {
1718
<div className="p-1">
1819
<h6 className="mb-0 p-1">Rate this page</h6>
1920
<FeedbackFaces />
21+
<div className={styles.docActionsContainer}>
22+
<DocActionsDropdown />
23+
</div>
2024
</div>
2125
<TOCItems
2226
{...props}

0 commit comments

Comments
 (0)