diff --git a/components/copy-page.module.css b/components/copy-page.module.css new file mode 100644 index 00000000..83b0057a --- /dev/null +++ b/components/copy-page.module.css @@ -0,0 +1,134 @@ +/* CopyPage Component Styles */ + +.container { + position: relative; + margin-right: 16px; + margin-left: 10px; + display: flex; +} + +.mainButton { + display: flex; + align-items: center; + gap: 2px; + padding: 6px 10px; + font-size: 12px; + font-weight: 500; + color: #374151; + background-color: white; + border: 1px solid #d1d5db; + border-right: none; + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + cursor: pointer; + transition: all 0.2s; +} + +.mainButton:hover { + background-color: #f9fafb; +} + +.arrowButton { + display: flex; + align-items: center; + justify-content: center; + padding: 6px 6px; + font-size: 12px; + font-weight: 500; + color: #374151; + background-color: white; + border: 1px solid #d1d5db; + border-left: 1px solid #d1d5db; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + cursor: pointer; + transition: all 0.2s; +} + +.arrowButton:hover { + background-color: #f9fafb; +} + +.arrowIcon { + width: 16px; + height: 16px; + transition: transform 0.2s; +} + +.arrowIconRotated { + transform: rotate(180deg); +} + +.dropdown { + position: absolute; + right: 0; + top: 100%; + margin-top: 8px; + width: 256px; + background-color: white; + border: 1px solid #e5e7eb; + border-radius: 6px; + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + z-index: 50; +} + +.dropdownContent { + padding: 4px 0; +} + +.dropdownButton { + display: flex; + align-items: center; + width: 100%; + padding: 12px 16px; + font-size: 14px; + color: #374151; + background-color: transparent; + border: none; + cursor: pointer; + text-align: left; + transition: background-color 0.2s; +} + +.dropdownButton:hover { + background-color: #f3f4f6; +} + +.mainButtonIcon { + width: 16px; + height: 16px; + margin-right: 6px; +} + +.dropdownIcon { + width: 16px; + height: 16px; + margin-right: 12px; +} + +.dropdownText { + display: flex; + flex-direction: column; + align-items: flex-start; + flex: 1; +} + +.dropdownTitle { + font-weight: 500; +} + +.dropdownDescription { + font-size: 12px; + color: #6b7280; + margin-top: 2px; +} + +@media (max-width: 640px) { + .container { + display: none; + } +} \ No newline at end of file diff --git a/components/copy-page.tsx b/components/copy-page.tsx new file mode 100644 index 00000000..a2a613ed --- /dev/null +++ b/components/copy-page.tsx @@ -0,0 +1,156 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { useRouter } from 'next/router'; +import styles from './copy-page.module.css'; +import CopyIcon from './icons/copy'; +import CheckIcon from './icons/check'; +import ChevronDownIcon from './icons/chevron-down'; +import DocumentIcon from './icons/document'; +import ChatGPTIcon from './icons/chatgpt'; +import AnthropicIcon from './icons/anthropic'; + +const CopyPage: React.FC = () => { + const [isOpen, setIsOpen] = useState(false); + const [isCopied, setIsCopied] = useState(false); + const dropdownRef = useRef(null); + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + const copyPageAsMarkdown = async () => { + try { + // Get the current page content + const pageContent = document.querySelector('main')?.innerText || ''; + const pageTitle = document.title; + + // Create markdown content + const markdownContent = `# ${pageTitle}\n\n${pageContent}`; + + await navigator.clipboard.writeText(markdownContent); + + // Show success feedback + setIsCopied(true); + setTimeout(() => { + setIsCopied(false); + }, 2000); + } catch (error) { + console.error('Failed to copy page:', error); + } + setIsOpen(false); + }; + + const viewAsMarkdown = () => { + const pageContent = document.querySelector('main')?.innerText || ''; + const pageTitle = document.title; + const markdownContent = `# ${pageTitle}\n\n${pageContent}`; + + // Open in new window/tab + const blob = new Blob([markdownContent], { type: 'text/markdown' }); + const url = URL.createObjectURL(blob); + window.open(url, '_blank'); + URL.revokeObjectURL(url); + setIsOpen(false); + }; + + const openInAI = (platform: 'chatgpt' | 'claude') => { + const currentUrl = window.location.href; + const prompt = `I'm building with GenLayer - can you read this docs page ${currentUrl} so I can ask you questions about it?`; + const encodedPrompt = encodeURIComponent(prompt); + + const urls = { + chatgpt: `https://chatgpt.com/?q=${encodedPrompt}`, + claude: `https://claude.ai/new?q=${encodedPrompt}` + }; + + window.open(urls[platform], '_blank'); + setIsOpen(false); + }; + + const openInChatGPT = () => openInAI('chatgpt'); + const openInClaude = () => openInAI('claude'); + + return ( +
+ + + + + {isOpen && ( +
+
+ + + + + + + +
+
+ )} +
+ ); +}; + +export default CopyPage; \ No newline at end of file diff --git a/components/icons/anthropic.js b/components/icons/anthropic.js new file mode 100644 index 00000000..c281018c --- /dev/null +++ b/components/icons/anthropic.js @@ -0,0 +1,7 @@ +export default function AnthropicIcon({ className }) { + return ( + + + + ); +} \ No newline at end of file diff --git a/components/icons/chatgpt.js b/components/icons/chatgpt.js new file mode 100644 index 00000000..580c8b62 --- /dev/null +++ b/components/icons/chatgpt.js @@ -0,0 +1,7 @@ +export default function ChatGPTIcon({ className }) { + return ( + + + + ); +} \ No newline at end of file diff --git a/components/icons/check.js b/components/icons/check.js new file mode 100644 index 00000000..e79a5541 --- /dev/null +++ b/components/icons/check.js @@ -0,0 +1,7 @@ +export default function CheckIcon({ className }) { + return ( + + + + ); +} \ No newline at end of file diff --git a/components/icons/chevron-down.js b/components/icons/chevron-down.js new file mode 100644 index 00000000..a6cd5d18 --- /dev/null +++ b/components/icons/chevron-down.js @@ -0,0 +1,7 @@ +export default function ChevronDownIcon({ className }) { + return ( + + + + ); +} \ No newline at end of file diff --git a/components/icons/copy.js b/components/icons/copy.js new file mode 100644 index 00000000..4e4d64d3 --- /dev/null +++ b/components/icons/copy.js @@ -0,0 +1,7 @@ +export default function CopyIcon({ className }) { + return ( + + + + ); +} \ No newline at end of file diff --git a/components/discord.js b/components/icons/discord.js similarity index 100% rename from components/discord.js rename to components/icons/discord.js diff --git a/components/icons/document.js b/components/icons/document.js new file mode 100644 index 00000000..6b23178b --- /dev/null +++ b/components/icons/document.js @@ -0,0 +1,7 @@ +export default function DocumentIcon({ className }) { + return ( + + + + ); +} \ No newline at end of file diff --git a/components/icons/github.js b/components/icons/github.js new file mode 100644 index 00000000..dc45337c --- /dev/null +++ b/components/icons/github.js @@ -0,0 +1,7 @@ +export default function GitHubIcon() { + return ( + + + + ); +} \ No newline at end of file diff --git a/components/telegram.js b/components/icons/telegram.js similarity index 100% rename from components/telegram.js rename to components/icons/telegram.js diff --git a/components/twitter.js b/components/icons/twitter.js similarity index 100% rename from components/twitter.js rename to components/icons/twitter.js diff --git a/theme.config.tsx b/theme.config.tsx index 8e01bde8..b5e36be3 100644 --- a/theme.config.tsx +++ b/theme.config.tsx @@ -1,16 +1,15 @@ import React from "react"; import { DocsThemeConfig } from "nextra-theme-docs"; import { useRouter } from "next/router"; -import TelegramIcon from "./components/telegram"; +import TelegramIcon from "./components/icons/telegram"; import Logo from "./components/icon"; -import TwitterLogo from "./components/twitter"; -import DiscordIcon from "./components/discord"; +import TwitterLogo from "./components/icons/twitter"; +import DiscordIcon from "./components/icons/discord"; +import GitHubIcon from "./components/icons/github"; +import CopyPage from "./components/copy-page"; const config: DocsThemeConfig = { logo: , - project: { - link: "https://github.com/genlayerlabs", - }, docsRepositoryBase: "https://github.com/genlayerlabs/genlayer-docs/tree/main", footer: { text: "GenLayer Documentation", @@ -27,6 +26,10 @@ const config: DocsThemeConfig = { navbar: { extraContent: (
+ + + + @@ -39,6 +42,7 @@ const config: DocsThemeConfig = {
), }, + useNextSeoProps: () => { const { asPath } = useRouter(); const isHomePage = asPath === "/"; diff --git a/tsconfig.json b/tsconfig.json index 1563f3e8..f70f7358 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": false, @@ -13,8 +17,21 @@ "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve" + "jsx": "preserve", + "plugins": [ + { + "name": "next" + } + ], + "strictNullChecks": true }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] + "include": [ + "**/*.ts", + "**/*.tsx", + "next-env.d.ts", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] }