+ )
+}
diff --git a/apps/www/pages/customers/[slug].tsx b/apps/www/pages/customers/[slug].tsx
index 4f6e26eff3a..ed3451e3936 100644
--- a/apps/www/pages/customers/[slug].tsx
+++ b/apps/www/pages/customers/[slug].tsx
@@ -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) {
)
})}
+
+
Ready to get started?
diff --git a/apps/www/pages/events/[slug].tsx b/apps/www/pages/events/[slug].tsx
index 029b30eae2f..106be659865 100644
--- a/apps/www/pages/events/[slug].tsx
+++ b/apps/www/pages/events/[slug].tsx
@@ -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) =>
+
Share on
diff --git a/packages/common/hooks/useCopyMarkdownFromUrl.ts b/packages/common/hooks/useCopyMarkdownFromUrl.ts
index 8174c6734b4..f3a85a43a2b 100644
--- a/packages/common/hooks/useCopyMarkdownFromUrl.ts
+++ b/packages/common/hooks/useCopyMarkdownFromUrl.ts
@@ -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)
diff --git a/packages/common/index.tsx b/packages/common/index.tsx
index 13233f82400..ddafa4401c8 100644
--- a/packages/common/index.tsx
+++ b/packages/common/index.tsx
@@ -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'
diff --git a/packages/common/markdown-affordance.test.ts b/packages/common/markdown-affordance.test.ts
new file mode 100644
index 00000000000..7a127b93cf5
--- /dev/null
+++ b/packages/common/markdown-affordance.test.ts
@@ -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'
+ )
+ })
+})
diff --git a/packages/common/markdown-affordance.ts b/packages/common/markdown-affordance.ts
new file mode 100644
index 00000000000..6405485cf2b
--- /dev/null
+++ b/packages/common/markdown-affordance.ts
@@ -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}`,
+ }
+}
diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts
index 14feba7fab0..688b44468ac 100644
--- a/packages/common/telemetry-constants.ts
+++ b/packages/common/telemetry-constants.ts
@@ -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
}
}