Skip to content
69 changes: 0 additions & 69 deletions src/components/docs-page-actions.tsx

This file was deleted.

10 changes: 10 additions & 0 deletions src/components/landing-page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"use client";

import { LandingShardCanvas } from "@/components/landing-shard-canvas";
import { MarkdownActions } from "@/components/markdown-actions";
import { SiteNav } from "@/components/site-nav";
import { Button } from "@/components/ui/button";
import { posthogClient } from "@/lib/posthog-client";
import { siteUrl } from "@/lib/shared";
import { pageHeadingClassName } from "@/lib/typography";
import { cn } from "@/lib/utils";
import { GithubIcon } from "@/components/github-icon";
Expand Down Expand Up @@ -129,12 +132,19 @@ export default function Home() {
href="https://github.com/multigres/multigres"
target="_blank"
rel="noreferrer"
onClick={() => posthogClient.capture('multigres_github_clicked', { source: 'hero' })}
>
<GithubIcon className="size-4" />
GitHub
</a>
</Button>
</div>

<MarkdownActions
markdownUrl="/index.md"
pageUrl={siteUrl}
className="lg:col-start-1 lg:row-start-3"
/>
</div>
</div>

Expand Down
129 changes: 129 additions & 0 deletions src/components/markdown-actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
'use client';

import { Button, buttonVariants } from '@/components/ui/button';
import { useCopyButton } from '@/hooks/use-copy-button';
import { posthogClient } from '@/lib/posthog-client';
import { cn } from '@/lib/utils';
import { Check, Copy } from 'lucide-react';
import { useState, type ComponentProps, type SVGProps } from 'react';
import { useTranslations } from 'fumadocs-ui/contexts/i18n';

const markdownCache = new Map<string, Promise<string>>();

const actionButtonClass = cn(
buttonVariants({ variant: 'default', size: 'sm' }),
'gap-2 [&_svg]:size-3.5 [&_svg]:text-muted-foreground',
);

const askLinkClass = cn(
buttonVariants({ variant: 'secondary', size: 'sm' }),
'gap-2 [&_svg]:size-3.5',
);

export function MarkdownCopyButton({
markdownUrl,
className,
children,
...props
}: ComponentProps<'button'> & { markdownUrl: string }) {
const t = useTranslations();
const [isLoading, setLoading] = useState(false);
const [checked, onClick] = useCopyButton(async () => {
const cached = markdownCache.get(markdownUrl);
if (cached) {
await navigator.clipboard.writeText(await cached);
posthogClient.capture('copy_as_markdown_clicked');
return;
}

setLoading(true);
try {
const promise = fetch(markdownUrl).then((res) => res.text());
markdownCache.set(markdownUrl, promise);
await navigator.clipboard.write([
new ClipboardItem({ 'text/plain': promise }),
]);
posthogClient.capture('copy_as_markdown_clicked');
} finally {
setLoading(false);
}
});

return (
<Button
type="button"
disabled={isLoading}
onClick={onClick}
className={cn(actionButtonClass, className)}
{...props}
>
{checked ? <Check /> : <Copy />}
{children ?? t.pageActionsCopyMarkdown}
</Button>
);
}

function OpenAiIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
role="img"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<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" />
</svg>
);
}

function AnthropicIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
role="img"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<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" />
</svg>
);
}

export function MarkdownActions({
markdownUrl,
pageUrl,
className,
}: {
markdownUrl: string;
pageUrl: string;
className?: string;
}) {
const prompt = `Read ${pageUrl}, I want to ask questions about it.`;
return (
<div className={cn('flex flex-wrap items-center gap-2', className)}>
<MarkdownCopyButton markdownUrl={markdownUrl} />
<a
href={`https://chatgpt.com/?${new URLSearchParams({ hints: 'search', q: prompt })}`}
target="_blank"
rel="noreferrer noopener"
className={askLinkClass}
onClick={() => posthogClient.capture('ask_ai_clicked', { agent: 'chatgpt' })}
>
<OpenAiIcon />
Ask ChatGPT
</a>
<a
href={`https://claude.ai/new?${new URLSearchParams({ q: prompt })}`}
target="_blank"
rel="noreferrer noopener"
className={askLinkClass}
onClick={() => posthogClient.capture('ask_ai_clicked', { agent: 'claude' })}
>
<AnthropicIcon />
Ask Claude
</a>
</div>
);
}
6 changes: 6 additions & 0 deletions src/components/site-footer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Link } from '@tanstack/react-router';
import { GithubIcon } from '@/components/github-icon';
import { posthogClient } from '@/lib/posthog-client';
import { appName, blogRoute, docsRoute, gitConfig } from '@/lib/shared';
import { cn } from '@/lib/utils';

Expand Down Expand Up @@ -39,6 +40,11 @@ export function SiteFooter({ className }: SiteFooterProps) {
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-1.5 hover:text-foreground"
onClick={
item.label === 'GitHub'
? () => posthogClient.capture('multigres_github_clicked', { source: 'footer' })
: undefined
}
>
{item.label === 'GitHub' ? <GithubIcon className="size-4" /> : null}
{item.label}
Expand Down
6 changes: 6 additions & 0 deletions src/components/site-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { GithubIcon } from '@/components/github-icon';
import { MultigresLogo } from '@/components/multigres-logo';
import { posthogClient } from '@/lib/posthog-client';
import { cn } from '@/lib/utils';
import { appName } from '@/lib/shared';
import { siteNavLinks } from '@/lib/site-nav';
Expand Down Expand Up @@ -56,6 +57,11 @@ export function SiteNav({ trailing, className }: SiteNavProps) {
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-1.5 text-sm font-semibold text-foreground"
onClick={
item.label === 'GitHub'
? () => posthogClient.capture('multigres_github_clicked', { source: 'nav' })
: undefined
}
>
{item.label === 'GitHub' ? (
<GithubIcon className="size-4" />
Expand Down
25 changes: 21 additions & 4 deletions src/routes/blog/$slug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { BlogLayout } from '@/components/blog-layout';
import { BlogShardPlaceholder } from '@/components/blog-shard-placeholder';
import { useMDXComponents } from '@/components/mdx';
import { parseAuthorKeys, resolveAuthors } from '@/lib/authors';
import { MarkdownActions } from '@/components/markdown-actions';
import { blogSource } from '@/lib/blog-source.server';
import { siteUrl } from '@/lib/shared';
import { docPageHeadingClassName } from '@/lib/typography';
import { useFumadocsLoader } from 'fumadocs-core/source/client';
import { Suspense } from 'react';
Expand Down Expand Up @@ -60,7 +62,10 @@ export const Route = createFileRoute('/blog/$slug')({
{ name: 'twitter:description', content: description },
{ name: 'twitter:image', content: image },
],
links: [{ rel: 'canonical', href: canonicalUrl }],
links: [
{ rel: 'canonical', href: canonicalUrl },
{ rel: 'alternate', type: 'text/markdown', href: `${canonicalUrl}.md` },
],
scripts: [
{
type: 'application/ld+json',
Expand Down Expand Up @@ -91,11 +96,16 @@ const serverLoader = createServerFn({ method: 'GET' })
image: page.data.image,
datePublished: page.data.date ? page.data.date.toISOString() : undefined,
authorName: firstAuthor?.name,
markdownUrl: `${page.url}.md`,
pageUrl: page.url,
};
});

const clientLoader = browserCollections.blog.createClientLoader({
component({ default: MDX, frontmatter }) {
component(
{ default: MDX, frontmatter },
{ markdownUrl, pageUrl }: { markdownUrl: string; pageUrl: string },
) {
const postAuthors = resolveAuthors(
parseAuthorKeys(frontmatter.authors ?? frontmatter.author),
);
Expand Down Expand Up @@ -127,6 +137,11 @@ const clientLoader = browserCollections.blog.createClientLoader({
series={frontmatter.series}
seriesPart={frontmatter.seriesPart}
/>
<MarkdownActions
markdownUrl={markdownUrl}
pageUrl={`${siteUrl}${pageUrl}`}
className="mt-6"
/>
</header>
<div className="prose prose-invert max-w-none prose-headings:font-heading">
<MDX components={useMDXComponents()} />
Expand All @@ -137,11 +152,13 @@ const clientLoader = browserCollections.blog.createClientLoader({
});

function BlogPostPage() {
const { path } = useFumadocsLoader(Route.useLoaderData());
const { path, markdownUrl, pageUrl } = useFumadocsLoader(
Route.useLoaderData(),
);

return (
<BlogLayout className="pt-0" contentClassName="py-0 md:py-0">
<Suspense>{clientLoader.useContent(path)}</Suspense>
<Suspense>{clientLoader.useContent(path, { markdownUrl, pageUrl })}</Suspense>
</BlogLayout>
);
}
Loading
Loading