diff --git a/src/theme/DocActionsDropdown/index.js b/src/theme/DocActionsDropdown/index.js new file mode 100644 index 0000000000..f67a46ba3c --- /dev/null +++ b/src/theme/DocActionsDropdown/index.js @@ -0,0 +1,179 @@ +import React, { useState } from 'react'; +import clsx from 'clsx'; +import styles from './styles.module.css'; + +export default function DocActionsDropdown() { + const [isOpen, setIsOpen] = useState(false); + + const buildRawUrl = (path, isIndex) => { + if (isIndex) { + // For index files, use path/index.md + return `https://raw.githubusercontent.com/onflow/docs/main/docs/${path}/index.md`; + } else { + // For regular files, use path.md + return `https://raw.githubusercontent.com/onflow/docs/main/docs/${path}.md`; + } + }; + + const fetchMarkdown = async (path) => { + // First, try to determine if this is an index.md file by checking both paths + const directPath = `https://raw.githubusercontent.com/onflow/docs/main/docs/${path}.md`; + const indexPath = `https://raw.githubusercontent.com/onflow/docs/main/docs/${path}/index.md`; + + try { + // Try the index path first + const indexResponse = await fetch(indexPath); + if (indexResponse.ok) { + return { url: indexPath, text: await indexResponse.text() }; + } + + // If index path fails, try the direct path + const directResponse = await fetch(directPath); + if (directResponse.ok) { + return { url: directPath, text: await directResponse.text() }; + } + + // If both fail, return null + return null; + } catch (error) { + console.error('Error fetching markdown:', error); + return null; + } + }; + + const handleCopyMarkdown = async () => { + try { + const path = window.location.pathname.replace(/^\/docs\/?/, '').replace(/\/$/, ''); + const result = await fetchMarkdown(path); + + if (result) { + navigator.clipboard.writeText(result.text); + setIsOpen(false); + } else { + throw new Error('Could not fetch markdown'); + } + } catch (error) { + console.error('Error copying markdown:', error); + // Fallback to GitHub + const currentPath = window.location.pathname.replace(/^\/docs\/?/, ''); + window.open(`https://github.com/onflow/docs/tree/main/docs/${currentPath}`, '_blank'); + } + }; + + const handleViewMarkdown = async () => { + try { + const path = window.location.pathname.replace(/^\/docs\/?/, '').replace(/\/$/, ''); + const result = await fetchMarkdown(path); + + if (result) { + window.open(result.url, '_blank'); + setIsOpen(false); + } else { + // Fallback to GitHub + const currentPath = window.location.pathname.replace(/^\/docs\/?/, ''); + window.open(`https://github.com/onflow/docs/tree/main/docs/${currentPath}`, '_blank'); + } + } catch (error) { + console.error('Error viewing markdown:', error); + // Fallback to GitHub + const currentPath = window.location.pathname.replace(/^\/docs\/?/, ''); + window.open(`https://github.com/onflow/docs/tree/main/docs/${currentPath}`, '_blank'); + } + }; + + const handleOpenInChatGPT = async () => { + try { + const path = window.location.pathname.replace(/^\/docs\/?/, '').replace(/\/$/, ''); + const result = await fetchMarkdown(path); + + if (result) { + const prompt = `Analyze this documentation: ${result.url}. After reading, ask me what I'd like to know. Keep responses focused on the content.`; + const encodedPrompt = encodeURIComponent(prompt); + window.open(`https://chatgpt.com/?q=${encodedPrompt}`, '_blank'); + } else { + // Fallback to current URL + const currentUrl = window.location.href; + const prompt = `Analyze this documentation: ${currentUrl}. After reading, ask me what I'd like to know. Keep responses focused on the content.`; + const encodedPrompt = encodeURIComponent(prompt); + window.open(`https://chatgpt.com/?q=${encodedPrompt}`, '_blank'); + } + setIsOpen(false); + } catch (error) { + console.error('Error opening in ChatGPT:', error); + // Fallback to current URL + const currentUrl = window.location.href; + const prompt = `Analyze this documentation: ${currentUrl}. After reading, ask me what I'd like to know. Keep responses focused on the content.`; + const encodedPrompt = encodeURIComponent(prompt); + window.open(`https://chatgpt.com/?q=${encodedPrompt}`, '_blank'); + setIsOpen(false); + } + }; + + const handleOpenInClaude = async () => { + try { + const path = window.location.pathname.replace(/^\/docs\/?/, '').replace(/\/$/, ''); + const result = await fetchMarkdown(path); + + if (result) { + const prompt = `Review this documentation: ${result.url}. Once complete, ask me what questions I have. Stay focused on the provided content.`; + const encodedPrompt = encodeURIComponent(prompt); + window.open(`https://claude.ai/chat/new?prompt=${encodedPrompt}`, '_blank'); + } else { + // Fallback to current URL + const currentUrl = window.location.href; + const prompt = `Review this documentation: ${currentUrl}. Once complete, ask me what questions I have. Stay focused on the provided content.`; + const encodedPrompt = encodeURIComponent(prompt); + window.open(`https://claude.ai/chat/new?prompt=${encodedPrompt}`, '_blank'); + } + setIsOpen(false); + } catch (error) { + console.error('Error opening in Claude:', error); + // Fallback to current URL + const currentUrl = window.location.href; + const prompt = `Review this documentation: ${currentUrl}. Once complete, ask me what questions I have. Stay focused on the provided content.`; + const encodedPrompt = encodeURIComponent(prompt); + window.open(`https://claude.ai/chat/new?prompt=${encodedPrompt}`, '_blank'); + setIsOpen(false); + } + }; + + const handleOpenFlowKnowledge = () => { + window.open('https://github.com/onflow/Flow-Data-Sources/tree/main/merged_docs', '_blank'); + setIsOpen(false); + }; + + const handleArrowClick = (e) => { + e.stopPropagation(); + setIsOpen(!isOpen); + }; + + return ( +
+ + {isOpen && ( +
+ +
+ + +
+ +
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/theme/DocActionsDropdown/styles.module.css b/src/theme/DocActionsDropdown/styles.module.css new file mode 100644 index 0000000000..90ed151845 --- /dev/null +++ b/src/theme/DocActionsDropdown/styles.module.css @@ -0,0 +1,112 @@ +.dropdownContainer { + position: relative; + display: inline-block; + width: 100%; + margin-top: 1rem; +} + +.dropdownButton { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 8px 16px; + background-color: var(--ifm-color-primary); + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + position: relative; +} + +.dropdownButton:hover { + background: var(--ifm-color-primary-dark); +} + +.arrow { + width: 40%; + height: 100%; + position: absolute; + right: 0; + top: 0; + display: flex; + align-items: center; + justify-content: flex-end; + padding-right: 8px; + cursor: pointer; +} + +.arrow::after { + content: ''; + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid white; + transition: transform 0.2s ease; +} + +.dropdownButton:hover .arrow::after { + transform: translateY(2px); +} + +.dropdownButton[aria-expanded="true"] .arrow { + transform: rotate(180deg); +} + +.dropdownMenu { + position: fixed; + width: calc(100% - 30px); + max-width: 300px; + background: var(--ifm-background-color); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 4px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + z-index: 9999; + margin-top: 0.5rem; + max-height: 80vh; + overflow-y: auto; +} + +.menuItem { + display: block; + width: 100%; + padding: 0.75rem 1rem; + text-align: left; + border: none; + background: none; + color: var(--ifm-font-color-base); + cursor: pointer; + font-size: 0.9rem; + transition: background-color 0.2s ease; +} + +.menuItem:hover { + background: var(--ifm-color-emphasis-100); + color: var(--ifm-font-color-base); +} + +.divider { + height: 1px; + background: var(--ifm-color-emphasis-200); + margin: 0.5rem 0; +} + +[data-theme="dark"] .dropdownMenu { + background: var(--ifm-background-color); + border-color: var(--ifm-color-emphasis-300); +} + +[data-theme="dark"] .menuItem { + color: var(--ifm-font-color-base); +} + +[data-theme="dark"] .menuItem:hover { + background: var(--ifm-color-emphasis-200); + color: var(--ifm-font-color-base); +} + +[data-theme="dark"] .divider { + background: var(--ifm-color-emphasis-300); +} \ No newline at end of file diff --git a/src/theme/DocItem/index.tsx b/src/theme/DocItem/index.tsx new file mode 100644 index 0000000000..5fe02372a1 --- /dev/null +++ b/src/theme/DocItem/index.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import DocItem from '@theme-original/DocItem'; +import type { Props } from '@theme/DocItem'; + +export default function DocItemWrapper(props: Props): JSX.Element { + return ( +
+
+
+ +
+ ); +} \ No newline at end of file diff --git a/src/theme/DocItem/styles.module.css b/src/theme/DocItem/styles.module.css new file mode 100644 index 0000000000..8c3511cc3d --- /dev/null +++ b/src/theme/DocItem/styles.module.css @@ -0,0 +1,19 @@ +.doc-item-wrapper { + position: relative; +} + +.doc-actions-container { + position: absolute; + top: 1rem; + right: 1rem; + z-index: 10; +} + +@media (max-width: 996px) { + .doc-actions-container { + position: static; + margin-bottom: 1rem; + display: flex; + justify-content: flex-end; + } +} \ No newline at end of file diff --git a/src/theme/Layout/styles.module.css b/src/theme/Layout/styles.module.css new file mode 100644 index 0000000000..35dd5a6b11 --- /dev/null +++ b/src/theme/Layout/styles.module.css @@ -0,0 +1,19 @@ +.layoutWrapper { + position: relative; +} + +.docActionsContainer { + position: fixed; + top: 1rem; + right: 1rem; + z-index: 1000; +} + +@media (max-width: 996px) { + .docActionsContainer { + position: static; + margin: 1rem; + display: flex; + justify-content: flex-end; + } +} \ No newline at end of file diff --git a/src/theme/TOC/index.tsx b/src/theme/TOC/index.tsx index 5c68756552..ceee81593f 100644 --- a/src/theme/TOC/index.tsx +++ b/src/theme/TOC/index.tsx @@ -2,6 +2,7 @@ import React from 'react'; import clsx from 'clsx'; import TOCItems from '@theme/TOCItems'; import type { Props } from '@theme/TOC'; +import DocActionsDropdown from '../DocActionsDropdown'; import styles from './styles.module.css'; import FeedbackFaces from '@site/src/components/feedbackFaces'; @@ -17,6 +18,9 @@ export default function TOC({ className, ...props }: Props): JSX.Element {
Rate this page
+
+ +