mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat(www): markdown copy/ask affordances (#48475)
This commit is contained in:
1 parent
e605178a63
commit
10c425ad0b
14 files changed
+253
-100
No files matched your search
@@ -2,12 +2,11 @@
|
||||
|
||||
import { Feedback } from '~/components/Feedback'
|
||||
import { useSendTelemetryEvent } from '~/lib/telemetry'
|
||||
import { isFeatureEnabled } from 'common'
|
||||
import { askAiUrls, isFeatureEnabled, useCopyMarkdownFromUrl } from 'common'
|
||||
import { Chatgpt, Claude } from 'icons'
|
||||
import { Check, Copy, Sparkles } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useState } from 'react'
|
||||
import { cn } from 'ui'
|
||||
import { ExpandableVideo } from 'ui-patterns/ExpandableVideo'
|
||||
import { Toc, TOCItems, TOCScrollArea } from 'ui-patterns/Toc'
|
||||
@@ -22,38 +21,22 @@ interface TOCHeader {
|
||||
}
|
||||
|
||||
function AiTools({ className }: { className?: string }) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
const path = usePathname()
|
||||
const sendTelemetryEvent = useSendTelemetryEvent()
|
||||
const { copied, copyMarkdown } = useCopyMarkdownFromUrl()
|
||||
const urls = askAiUrls(`https://supabase.com/docs${path}`)
|
||||
|
||||
function handleAgentSetupClick() {
|
||||
sendTelemetryEvent({ action: 'agent_setup_clicked' })
|
||||
}
|
||||
|
||||
async function copyMarkdown() {
|
||||
const mdUrl = `/docs/${path}.md`
|
||||
|
||||
try {
|
||||
const res = await fetch(mdUrl)
|
||||
let text: string
|
||||
|
||||
if (res.ok) {
|
||||
text = await res.text()
|
||||
} else {
|
||||
// Default to HTML content within the article when no .md file is available.
|
||||
text = document.getElementById('sb-docs-guide-main-article')?.innerHTML ?? ''
|
||||
}
|
||||
|
||||
await navigator.clipboard.writeText(text)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
} catch (error) {
|
||||
console.error('Failed to copy markdown', error)
|
||||
}
|
||||
|
||||
sendTelemetryEvent({
|
||||
action: 'copy_as_markdown_clicked',
|
||||
async function handleCopy() {
|
||||
const ok = await copyMarkdown(`/docs${path}.md`, {
|
||||
fallback: () => document.getElementById('sb-docs-guide-main-article')?.innerHTML ?? '',
|
||||
})
|
||||
if (ok) {
|
||||
sendTelemetryEvent({ action: 'copy_as_markdown_clicked', properties: { pageType: 'guide' } })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -75,38 +58,47 @@ function AiTools({ className }: { className?: string }) {
|
||||
</Link>
|
||||
<button
|
||||
tabIndex={0}
|
||||
onClick={copyMarkdown}
|
||||
onClick={handleCopy}
|
||||
className="flex cursor-pointer items-center gap-1.5 text-xs text-foreground-lighter hover:text-foreground text-left transition-colors"
|
||||
>
|
||||
{copied ? (
|
||||
<Check size={14} strokeWidth={1.5} className="text-brand" />
|
||||
<Check size={14} strokeWidth={1.5} className="text-brand" aria-hidden />
|
||||
) : (
|
||||
<Copy size={14} strokeWidth={1.5} />
|
||||
<Copy size={14} strokeWidth={1.5} aria-hidden />
|
||||
)}
|
||||
{copied ? 'Copied!' : 'Copy as Markdown'}
|
||||
</button>
|
||||
<span className="sr-only" role="status">
|
||||
{copied ? 'Copied to clipboard' : ''}
|
||||
</span>
|
||||
<a
|
||||
href={`https://chatgpt.com/?hint=search&q=Read from https://supabase.com/docs${path} so I can ask questions about its contents`}
|
||||
href={urls.chatgpt}
|
||||
target="_blank"
|
||||
onClick={() =>
|
||||
sendTelemetryEvent({ action: 'ask_ai_clicked', properties: { agent: 'chatgpt' } })
|
||||
sendTelemetryEvent({
|
||||
action: 'ask_ai_clicked',
|
||||
properties: { agent: 'chatgpt', pageType: 'guide' },
|
||||
})
|
||||
}
|
||||
rel="noreferrer noopener"
|
||||
className="flex items-center gap-1.5 text-xs text-foreground-lighter hover:text-foreground transition-colors"
|
||||
>
|
||||
<Chatgpt size={14} />
|
||||
<Chatgpt size={14} aria-hidden />
|
||||
Ask ChatGPT
|
||||
</a>
|
||||
<a
|
||||
href={`https://claude.ai/new?q=Read from https://supabase.com/docs${path} so I can ask questions about its contents`}
|
||||
href={urls.claude}
|
||||
target="_blank"
|
||||
onClick={() =>
|
||||
sendTelemetryEvent({ action: 'ask_ai_clicked', properties: { agent: 'claude' } })
|
||||
sendTelemetryEvent({
|
||||
action: 'ask_ai_clicked',
|
||||
properties: { agent: 'claude', pageType: 'guide' },
|
||||
})
|
||||
}
|
||||
rel="noreferrer noopener"
|
||||
className="flex items-center gap-1.5 text-xs text-foreground-lighter hover:text-foreground transition-colors"
|
||||
>
|
||||
<Claude size={14} />
|
||||
<Claude size={14} aria-hidden />
|
||||
Ask Claude
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ArrowDownIcon } from '@heroicons/react/outline'
|
||||
import CTABanner from '~/components/CTABanner'
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import { MarkdownActions } from '~/components/MarkdownActions'
|
||||
import PricingAddons from '~/components/Pricing/PricingAddons'
|
||||
import PricingComputeSection from '~/components/Pricing/PricingComputeSection'
|
||||
import PricingDiskSection from '~/components/Pricing/PricingDiskSection'
|
||||
@@ -27,6 +28,12 @@ export default function PricingContent() {
|
||||
<p className="p text-lg leading-5">
|
||||
Start building for free, collaborate with your team, then scale to millions of users
|
||||
</p>
|
||||
<MarkdownActions
|
||||
orientation="horizontal"
|
||||
className="justify-center pt-4"
|
||||
pagePath="/pricing"
|
||||
pageType="pricing"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@ import LW13Summary from '@/components/LaunchWeek/13/Releases/LWSummary'
|
||||
import LW14Summary from '@/components/LaunchWeek/14/Releases/LWSummary'
|
||||
import LW15Summary from '@/components/LaunchWeek/15/LWSummary'
|
||||
import LWXSummary from '@/components/LaunchWeek/X/LWXSummary'
|
||||
import { MarkdownActions } from '@/components/MarkdownActions'
|
||||
import { getBlogThumbnailImage } from '@/lib/blog-images'
|
||||
import { compileBlogMdx } from '@/lib/mdx/compileBlogMdx'
|
||||
import mdxComponents from '@/lib/mdx/mdxComponents'
|
||||
@@ -225,7 +226,8 @@ const BlogPostRenderer = async ({
|
||||
{isLaunchWeek14 && <LW14Summary />}
|
||||
{isLaunchWeek15 && <LW15Summary />}
|
||||
|
||||
<div className="block lg:hidden py-8">
|
||||
<div className="lg:hidden py-8 flex flex-col gap-6">
|
||||
<MarkdownActions pagePath={`/blog/${blogMetaData.slug}`} pageType="blog" />
|
||||
<ShareArticleActions title={blogMetaData.title} slug={blogMetaData.slug} />
|
||||
</div>
|
||||
|
||||
@@ -291,7 +293,8 @@ const BlogPostRenderer = async ({
|
||||
On this page
|
||||
</p>
|
||||
<div className="overflow-y-auto min-h-0 flex-1">{toc}</div>
|
||||
<div className="shrink-0">
|
||||
<div className="shrink-0 flex flex-col gap-6">
|
||||
<MarkdownActions pagePath={`/blog/${blogMetaData.slug}`} pageType="blog" />
|
||||
<ShareArticleActions title={blogMetaData.title} slug={blogMetaData.slug} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import { useCopyMarkdownFromUrl } from 'common'
|
||||
import Link from 'next/link'
|
||||
import {
|
||||
cn,
|
||||
@@ -13,8 +12,6 @@ import {
|
||||
TooltipTrigger,
|
||||
} from 'ui'
|
||||
|
||||
import { SITE_ORIGIN } from '@/lib/constants'
|
||||
|
||||
const ShareArticleActions = ({
|
||||
title,
|
||||
slug,
|
||||
@@ -28,13 +25,8 @@ const ShareArticleActions = ({
|
||||
basePath?: string
|
||||
className?: string
|
||||
}) => {
|
||||
const { copied, copyMarkdown } = useCopyMarkdownFromUrl()
|
||||
|
||||
const permalink = encodeURIComponent(`${basePath}${slug}`)
|
||||
const encodedTitle = encodeURIComponent(title)
|
||||
const mdPath = `/blog/${slug}.md`
|
||||
const mdAbs = `${SITE_ORIGIN}${mdPath}`
|
||||
const aiPrompt = `Read from ${mdAbs} so I can ask questions about its contents`
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
'use client'
|
||||
|
||||
import { useCopyMarkdownFromUrl } from 'common'
|
||||
import { Chatgpt, Claude } from 'icons'
|
||||
import { Check, Copy, ExternalLink } from 'lucide-react'
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import { cn } from 'ui'
|
||||
|
||||
import { ChangeTypeBadge, ProductBadges } from '@/components/Changelog/ChangelogTimelineList'
|
||||
import { MarkdownActions } from '@/components/MarkdownActions'
|
||||
import type { ChangelogEntryFrontmatter } from '@/lib/changelog-repo'
|
||||
import { SITE_ORIGIN } from '@/lib/constants'
|
||||
|
||||
type Props = {
|
||||
slug: string
|
||||
@@ -16,10 +14,6 @@ type Props = {
|
||||
}
|
||||
|
||||
export function ChangelogDetailSidebar({ slug, frontmatter, className }: Props) {
|
||||
const { copied, copyMarkdown } = useCopyMarkdownFromUrl()
|
||||
const mdPath = `/changelog/${slug}.md`
|
||||
const mdAbs = `${SITE_ORIGIN}${mdPath}`
|
||||
const aiPrompt = `Read from ${mdAbs} so I can ask questions about its contents`
|
||||
const affectedProducts = frontmatter.affected_products ?? []
|
||||
|
||||
return (
|
||||
@@ -117,37 +111,7 @@ export function ChangelogDetailSidebar({ slug, frontmatter, className }: Props)
|
||||
View discussion on GitHub
|
||||
</a>
|
||||
)}
|
||||
<button
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
onClick={() => void copyMarkdown(mdPath)}
|
||||
className="text-foreground-lighter hover:text-foreground flex items-center gap-1.5 text-left text-xs transition-colors"
|
||||
>
|
||||
{copied ? (
|
||||
<Check size={14} strokeWidth={1.5} className="text-brand" />
|
||||
) : (
|
||||
<Copy size={14} strokeWidth={1.5} />
|
||||
)}
|
||||
{copied ? 'Copied as markdown' : 'Copy page as markdown'}
|
||||
</button>
|
||||
<a
|
||||
href={`https://chatgpt.com/?hint=search&q=${encodeURIComponent(`Read from ${mdAbs} so I can ask questions about its contents`)}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="text-foreground-lighter hover:text-foreground flex items-center gap-1.5 text-xs transition-colors"
|
||||
>
|
||||
<Chatgpt size={14} />
|
||||
Ask ChatGPT
|
||||
</a>
|
||||
<a
|
||||
href={`https://claude.ai/new?q=${encodeURIComponent(aiPrompt)}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="text-foreground-lighter hover:text-foreground flex items-center gap-1.5 text-xs transition-colors"
|
||||
>
|
||||
<Claude size={14} />
|
||||
Ask Claude
|
||||
</a>
|
||||
<MarkdownActions pagePath={`/changelog/${slug}`} pageType="changelog" />
|
||||
</nav>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useCopyMarkdownFromUrl } from 'common'
|
||||
import { useSendTelemetryEvent } from '~/lib/telemetry'
|
||||
import { askAiUrls, useCopyMarkdownFromUrl } from 'common'
|
||||
import { Chatgpt, Claude } from 'icons'
|
||||
import { Check, ChevronDown, Copy } from 'lucide-react'
|
||||
import {
|
||||
@@ -21,8 +22,19 @@ type Props = {
|
||||
|
||||
export function ChangelogLlmMarkdownButton({ className, markdownPath = '/changelog.md' }: Props) {
|
||||
const { copied, copyMarkdown } = useCopyMarkdownFromUrl()
|
||||
const mdAbs = `${SITE_ORIGIN}${markdownPath}`
|
||||
const aiPrompt = `Read from ${mdAbs} so I can ask questions about its contents`
|
||||
const sendTelemetryEvent = useSendTelemetryEvent()
|
||||
const pagePath = markdownPath.replace(/\.md$/, '')
|
||||
const urls = askAiUrls(`${SITE_ORIGIN}${pagePath}`)
|
||||
|
||||
async function handleCopy() {
|
||||
const ok = await copyMarkdown(markdownPath)
|
||||
if (ok) {
|
||||
sendTelemetryEvent({
|
||||
action: 'copy_as_markdown_clicked',
|
||||
properties: { pageType: 'changelog' },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('flex items-center', className)}>
|
||||
@@ -36,10 +48,13 @@ export function ChangelogLlmMarkdownButton({ className, markdownPath = '/changel
|
||||
<Copy className="h-4 w-4" strokeWidth={2} aria-hidden />
|
||||
)
|
||||
}
|
||||
onClick={() => void copyMarkdown(markdownPath)}
|
||||
onClick={handleCopy}
|
||||
>
|
||||
{copied ? 'Copied as Markdown' : 'Copy as Markdown'}
|
||||
{copied ? 'Copied!' : 'Copy as Markdown'}
|
||||
</Button>
|
||||
<span className="sr-only" role="status">
|
||||
{copied ? 'Copied to clipboard' : ''}
|
||||
</span>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -53,21 +68,33 @@ export function ChangelogLlmMarkdownButton({ className, markdownPath = '/changel
|
||||
<DropdownMenuContent align="end" className="w-40">
|
||||
<DropdownMenuItem asChild className="gap-2">
|
||||
<a
|
||||
href={`https://chatgpt.com/?hint=search&q=${encodeURIComponent(aiPrompt)}`}
|
||||
href={urls.chatgpt}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
onClick={() =>
|
||||
sendTelemetryEvent({
|
||||
action: 'ask_ai_clicked',
|
||||
properties: { agent: 'chatgpt', pageType: 'changelog' },
|
||||
})
|
||||
}
|
||||
>
|
||||
<Chatgpt className="h-4 w-4 shrink-0" />
|
||||
<Chatgpt className="h-4 w-4 shrink-0" aria-hidden />
|
||||
Ask ChatGPT
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild className="gap-2">
|
||||
<a
|
||||
href={`https://claude.ai/new?q=${encodeURIComponent(aiPrompt)}`}
|
||||
href={urls.claude}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
onClick={() =>
|
||||
sendTelemetryEvent({
|
||||
action: 'ask_ai_clicked',
|
||||
properties: { agent: 'claude', pageType: 'changelog' },
|
||||
})
|
||||
}
|
||||
>
|
||||
<Claude className="h-4 w-4 shrink-0" />
|
||||
<Claude className="h-4 w-4 shrink-0" aria-hidden />
|
||||
Ask Claude
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
'use client'
|
||||
|
||||
import { useSendTelemetryEvent } from '~/lib/telemetry'
|
||||
import { askAiUrls, useCopyMarkdownFromUrl } from 'common'
|
||||
import type { MarkdownAffordancePageType } from 'common/telemetry-constants'
|
||||
import { Chatgpt, Claude } from 'icons'
|
||||
import { Check, Copy } from 'lucide-react'
|
||||
import { cn } from 'ui'
|
||||
|
||||
import { SITE_ORIGIN } from '@/lib/constants'
|
||||
|
||||
type Props = {
|
||||
pagePath: string
|
||||
pageType: MarkdownAffordancePageType
|
||||
orientation?: 'vertical' | 'horizontal'
|
||||
className?: string
|
||||
}
|
||||
|
||||
const itemClass =
|
||||
'flex items-center gap-1.5 text-xs text-foreground-lighter hover:text-foreground transition-colors'
|
||||
|
||||
export function MarkdownActions({
|
||||
pagePath,
|
||||
pageType,
|
||||
orientation = 'vertical',
|
||||
className,
|
||||
}: Props) {
|
||||
const { copied, copyMarkdown } = useCopyMarkdownFromUrl()
|
||||
const sendTelemetryEvent = useSendTelemetryEvent()
|
||||
const mdPath = pagePath === '/' ? '/homepage.md' : `${pagePath}.md`
|
||||
const urls = askAiUrls(`${SITE_ORIGIN}${pagePath === '/' ? '' : pagePath}`)
|
||||
|
||||
async function handleCopy() {
|
||||
const ok = await copyMarkdown(mdPath)
|
||||
if (ok) {
|
||||
sendTelemetryEvent({ action: 'copy_as_markdown_clicked', properties: { pageType } })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex',
|
||||
orientation === 'vertical' ? 'flex-col gap-2' : 'flex-row flex-wrap items-center gap-4',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<button
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
className={cn(itemClass, 'cursor-pointer text-left')}
|
||||
>
|
||||
{copied ? (
|
||||
<Check size={14} strokeWidth={1.5} className="text-brand" aria-hidden />
|
||||
) : (
|
||||
<Copy size={14} strokeWidth={1.5} aria-hidden />
|
||||
)}
|
||||
{copied ? 'Copied!' : 'Copy as Markdown'}
|
||||
</button>
|
||||
<span className="sr-only" role="status">
|
||||
{copied ? 'Copied to clipboard' : ''}
|
||||
</span>
|
||||
<a
|
||||
href={urls.chatgpt}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
onClick={() =>
|
||||
sendTelemetryEvent({
|
||||
action: 'ask_ai_clicked',
|
||||
properties: { agent: 'chatgpt', pageType },
|
||||
})
|
||||
}
|
||||
className={itemClass}
|
||||
>
|
||||
<Chatgpt size={14} aria-hidden />
|
||||
Ask ChatGPT
|
||||
</a>
|
||||
<a
|
||||
href={urls.claude}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
onClick={() =>
|
||||
sendTelemetryEvent({
|
||||
action: 'ask_ai_clicked',
|
||||
properties: { agent: 'claude', pageType },
|
||||
})
|
||||
}
|
||||
className={itemClass}
|
||||
>
|
||||
<Claude size={14} aria-hidden />
|
||||
Ask Claude
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import Link from 'next/link'
|
||||
import { Button } from 'ui'
|
||||
|
||||
import SectionContainer from '@/components/Layouts/SectionContainer'
|
||||
import { MarkdownActions } from '@/components/MarkdownActions'
|
||||
|
||||
// table of contents extractor
|
||||
const toc = require('markdown-toc')
|
||||
@@ -209,6 +210,12 @@ function CaseStudyPage(props: any) {
|
||||
)
|
||||
})}
|
||||
|
||||
<MarkdownActions
|
||||
pagePath={`/customers/${slug}`}
|
||||
pageType="customers"
|
||||
className="not-prose"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<p>Ready to get started?</p>
|
||||
<div>
|
||||
|
||||
@@ -24,6 +24,7 @@ import { Image } from 'ui-patterns/Image'
|
||||
import ShareArticleActions from '@/components/Blog/ShareArticleActions'
|
||||
import DefaultLayout from '@/components/Layouts/Default'
|
||||
import SectionContainer from '@/components/Layouts/SectionContainer'
|
||||
import { MarkdownActions } from '@/components/MarkdownActions'
|
||||
import authors from '@/lib/authors.json'
|
||||
import { breadcrumbs } from '@/lib/breadcrumbs'
|
||||
import { capitalize, isNotNullOrUndefined } from '@/lib/helpers'
|
||||
@@ -324,6 +325,11 @@ const EventPage = ({ event }: InferGetStaticPropsType<typeof getStaticProps>) =>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<MarkdownActions
|
||||
pagePath={`/events/${event.slug}`}
|
||||
pageType="events"
|
||||
className="mb-4"
|
||||
/>
|
||||
<div className="flex flex-col text-sm">
|
||||
<span>Share on</span>
|
||||
<ShareArticleActions title={meta.title} slug={meta.url} basePath="" />
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
export type CopyMarkdownFromUrlOptions = {
|
||||
/** When the markdown URL is missing or not OK, use this HTML string instead (e.g. rendered article). */
|
||||
fallbackHtml?: () => string
|
||||
/** When the markdown URL is missing or not OK, use this string instead (e.g. rendered article HTML). */
|
||||
fallback?: () => string
|
||||
}
|
||||
|
||||
const COPIED_FEEDBACK_MS = 2000
|
||||
|
||||
/**
|
||||
* Fetches markdown from `mdUrl`, falls back to optional HTML when the response is not OK,
|
||||
* Fetches markdown from `mdUrl`, falls back to the optional fallback string when the response is not OK,
|
||||
* then writes the result to the clipboard.
|
||||
*/
|
||||
export async function copyMarkdownFromUrl(
|
||||
@@ -23,7 +23,7 @@ export async function copyMarkdownFromUrl(
|
||||
if (res.ok) {
|
||||
text = await res.text()
|
||||
} else {
|
||||
text = options?.fallbackHtml?.() ?? ''
|
||||
text = options?.fallback?.() ?? ''
|
||||
if (!text) return false
|
||||
}
|
||||
await navigator.clipboard.writeText(text)
|
||||
|
||||
@@ -8,6 +8,7 @@ export * from './feature-flags'
|
||||
export * from './gotrue'
|
||||
export * from './helpers'
|
||||
export * from './hooks'
|
||||
export * from './markdown-affordance'
|
||||
export * from './MetaFavicons/pages-router'
|
||||
export * from './Providers'
|
||||
export * from './first-referrer-cookie'
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { askAiPrompt, askAiUrls } from './markdown-affordance'
|
||||
|
||||
describe('askAiUrls', () => {
|
||||
it('embeds the encoded prompt for both agents', () => {
|
||||
const { chatgpt, claude } = askAiUrls('https://supabase.com/blog/some-post')
|
||||
const prompt = encodeURIComponent(
|
||||
'Read from https://supabase.com/blog/some-post so I can ask questions about its contents'
|
||||
)
|
||||
expect(chatgpt).toBe(`https://chatgpt.com/?hint=search&q=${prompt}`)
|
||||
expect(claude).toBe(`https://claude.ai/new?q=${prompt}`)
|
||||
})
|
||||
|
||||
it('encodes spaces and slashes so the prompt survives as a single query param', () => {
|
||||
const { chatgpt } = askAiUrls('https://supabase.com/changelog')
|
||||
expect(chatgpt).not.toContain(' ')
|
||||
expect(chatgpt.split('q=')[1]).not.toContain('/')
|
||||
})
|
||||
})
|
||||
|
||||
describe('askAiPrompt', () => {
|
||||
it('references the page URL verbatim', () => {
|
||||
expect(askAiPrompt('https://supabase.com/pricing')).toBe(
|
||||
'Read from https://supabase.com/pricing so I can ask questions about its contents'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
export function askAiPrompt(pageUrl: string) {
|
||||
return `Read from ${pageUrl} so I can ask questions about its contents`
|
||||
}
|
||||
|
||||
export function askAiUrls(pageUrl: string) {
|
||||
const prompt = encodeURIComponent(askAiPrompt(pageUrl))
|
||||
return {
|
||||
chatgpt: `https://chatgpt.com/?hint=search&q=${prompt}`,
|
||||
claude: `https://claude.ai/new?q=${prompt}`,
|
||||
}
|
||||
}
|
||||
@@ -920,14 +920,29 @@ export interface DocsFeedbackClickedEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export type MarkdownAffordancePageType =
|
||||
| 'blog'
|
||||
| 'customers'
|
||||
| 'events'
|
||||
| 'pricing'
|
||||
| 'changelog'
|
||||
| 'guide'
|
||||
|
||||
/**
|
||||
* User clicked 'Copy as Markdown' option on a page.
|
||||
* User clicked 'Copy as Markdown' on a page and the markdown was copied successfully.
|
||||
* Fires on success only; failed fetch/clipboard writes are not counted.
|
||||
*
|
||||
* @group Events
|
||||
* @source docs
|
||||
* @source www, docs
|
||||
*/
|
||||
export interface CopyAsMarkdownClickedEvent {
|
||||
action: 'copy_as_markdown_clicked'
|
||||
properties: {
|
||||
/**
|
||||
* Page class the affordance sits on.
|
||||
*/
|
||||
pageType: MarkdownAffordancePageType
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -944,12 +959,16 @@ export interface AgentSetupClickedEvent {
|
||||
* User clicked "Ask..." to open a new window to consult an agent about the current page.
|
||||
*
|
||||
* @group Events
|
||||
* @source docs
|
||||
* @source www, docs
|
||||
*/
|
||||
export interface AskAiClickedEvent {
|
||||
action: 'ask_ai_clicked'
|
||||
properties: {
|
||||
agent: 'chatgpt' | 'claude'
|
||||
/**
|
||||
* Page class the affordance sits on.
|
||||
*/
|
||||
pageType: MarkdownAffordancePageType
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user