|
| 1 | +"use client"; |
| 2 | + |
| 3 | +import { useMemo } from "react"; |
| 4 | +import type { ReactNode } from "react"; |
| 5 | +import { usePathname } from "fumadocs-core/framework"; |
| 6 | +import { buttonVariants } from "fumadocs-ui/components/ui/button"; |
| 7 | +import { |
| 8 | + Popover, |
| 9 | + PopoverContent, |
| 10 | + PopoverTrigger, |
| 11 | +} from "fumadocs-ui/components/ui/popover"; |
| 12 | +import { MarkdownCopyButton } from "fumadocs-ui/layouts/docs/page"; |
| 13 | +import { ChevronDown, ExternalLinkIcon, TextIcon } from "lucide-react"; |
| 14 | + |
| 15 | +type ViewOptionsPopoverProps = { |
| 16 | + markdownUrl: string; |
| 17 | + githubUrl: string; |
| 18 | + className?: string; |
| 19 | +}; |
| 20 | + |
| 21 | +type ViewOption = { |
| 22 | + title: string; |
| 23 | + href: string; |
| 24 | + icon: ReactNode; |
| 25 | +}; |
| 26 | + |
| 27 | +const linkClassName = |
| 28 | + "text-sm p-2 rounded-lg inline-flex items-center gap-2 hover:text-fd-accent-foreground hover:bg-fd-accent [&_svg]:size-4"; |
| 29 | + |
| 30 | +function iconGitHub() { |
| 31 | + return ( |
| 32 | + <svg fill="currentColor" role="img" viewBox="0 0 24 24"> |
| 33 | + <title>GitHub</title> |
| 34 | + <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" /> |
| 35 | + </svg> |
| 36 | + ); |
| 37 | +} |
| 38 | + |
| 39 | +function iconOpenAI() { |
| 40 | + return ( |
| 41 | + <svg fill="currentColor" role="img" viewBox="0 0 24 24"> |
| 42 | + <title>OpenAI</title> |
| 43 | + <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" /> |
| 44 | + </svg> |
| 45 | + ); |
| 46 | +} |
| 47 | + |
| 48 | +function iconClaude() { |
| 49 | + return ( |
| 50 | + <svg fill="currentColor" role="img" viewBox="0 0 24 24"> |
| 51 | + <title>Anthropic</title> |
| 52 | + <path d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z" /> |
| 53 | + </svg> |
| 54 | + ); |
| 55 | +} |
| 56 | + |
| 57 | +function iconCursor() { |
| 58 | + return ( |
| 59 | + <svg fill="currentColor" role="img" viewBox="0 0 24 24"> |
| 60 | + <title>Cursor</title> |
| 61 | + <path d="M11.503.131 1.891 5.678a.84.84 0 0 0-.42.726v11.188c0 .3.162.575.42.724l9.609 5.55a1 1 0 0 0 .998 0l9.61-5.55a.84.84 0 0 0 .42-.724V6.404a.84.84 0 0 0-.42-.726L12.497.131a1.01 1.01 0 0 0-.996 0M2.657 6.338h18.55c.263 0 .43.287.297.515L12.23 22.918c-.062.107-.229.064-.229-.06V12.335a.59.59 0 0 0-.295-.51l-9.11-5.257c-.109-.063-.064-.23.061-.23" /> |
| 62 | + </svg> |
| 63 | + ); |
| 64 | +} |
| 65 | + |
| 66 | +export function PageActions({ |
| 67 | + markdownUrl, |
| 68 | + githubUrl, |
| 69 | +}: { |
| 70 | + markdownUrl: string; |
| 71 | + githubUrl: string; |
| 72 | +}) { |
| 73 | + return ( |
| 74 | + <div className="not-prose mb-6 flex items-center gap-2 border-b pb-6"> |
| 75 | + <MarkdownCopyButton markdownUrl={markdownUrl} /> |
| 76 | + <ViewOptionsPopover |
| 77 | + markdownUrl={markdownUrl} |
| 78 | + githubUrl={githubUrl} |
| 79 | + /> |
| 80 | + </div> |
| 81 | + ); |
| 82 | +} |
| 83 | + |
| 84 | +export function ViewOptionsPopover({ |
| 85 | + markdownUrl, |
| 86 | + githubUrl, |
| 87 | + className, |
| 88 | +}: ViewOptionsPopoverProps) { |
| 89 | + const pathname = usePathname(); |
| 90 | + |
| 91 | + const items = useMemo<ViewOption[]>(() => { |
| 92 | + const q = `Read ${typeof window === "undefined" ? pathname : new URL(pathname, window.location.origin)}, I want to ask questions about it.`; |
| 93 | + |
| 94 | + return [ |
| 95 | + { |
| 96 | + title: "Open in GitHub", |
| 97 | + href: githubUrl, |
| 98 | + icon: iconGitHub(), |
| 99 | + }, |
| 100 | + { |
| 101 | + title: "View as Markdown", |
| 102 | + href: markdownUrl, |
| 103 | + icon: <TextIcon />, |
| 104 | + }, |
| 105 | + { |
| 106 | + title: "Open in ChatGPT", |
| 107 | + href: `https://chatgpt.com/?${new URLSearchParams({ |
| 108 | + hints: "search", |
| 109 | + q, |
| 110 | + })}`, |
| 111 | + icon: iconOpenAI(), |
| 112 | + }, |
| 113 | + { |
| 114 | + title: "Open in Claude", |
| 115 | + href: `https://claude.ai/new?${new URLSearchParams({ q })}`, |
| 116 | + icon: iconClaude(), |
| 117 | + }, |
| 118 | + { |
| 119 | + title: "Open in Cursor", |
| 120 | + href: `https://cursor.com/link/prompt?${new URLSearchParams({ |
| 121 | + text: q, |
| 122 | + })}`, |
| 123 | + icon: iconCursor(), |
| 124 | + }, |
| 125 | + ]; |
| 126 | + }, [githubUrl, markdownUrl, pathname]); |
| 127 | + |
| 128 | + return ( |
| 129 | + <Popover> |
| 130 | + <PopoverTrigger |
| 131 | + className={[ |
| 132 | + buttonVariants({ color: "secondary", size: "sm" }), |
| 133 | + "gap-2 data-[state=open]:bg-fd-accent data-[state=open]:text-fd-accent-foreground", |
| 134 | + className, |
| 135 | + ] |
| 136 | + .filter(Boolean) |
| 137 | + .join(" ")} |
| 138 | + > |
| 139 | + Open |
| 140 | + <ChevronDown className="size-3.5 text-fd-muted-foreground" /> |
| 141 | + </PopoverTrigger> |
| 142 | + <PopoverContent className="flex flex-col"> |
| 143 | + {items.map((item) => ( |
| 144 | + <a |
| 145 | + key={item.href} |
| 146 | + href={item.href} |
| 147 | + rel="noreferrer noopener" |
| 148 | + target="_blank" |
| 149 | + className={linkClassName} |
| 150 | + > |
| 151 | + {item.icon} |
| 152 | + {item.title} |
| 153 | + <ExternalLinkIcon className="text-fd-muted-foreground size-3.5 ms-auto" /> |
| 154 | + </a> |
| 155 | + ))} |
| 156 | + </PopoverContent> |
| 157 | + </Popover> |
| 158 | + ); |
| 159 | +} |
0 commit comments