Skip to content

Commit 099d799

Browse files
authored
Similar Posts (#6672)
1 parent dec6230 commit 099d799

File tree

10 files changed

+103
-22
lines changed

10 files changed

+103
-22
lines changed

packages/web/docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"@radix-ui/react-icons": "1.3.2",
1717
"@radix-ui/react-tabs": "1.1.2",
1818
"@radix-ui/react-tooltip": "1.1.6",
19-
"@theguild/components": "9.3.4",
19+
"@theguild/components": "9.5.0",
2020
"date-fns": "4.1.0",
2121
"next": "15.2.4",
2222
"react": "19.0.0",

packages/web/docs/src/app/blog/(posts)/hive-platform-achieves-soc2-certification/page.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Hive Platform Achieves SOC-2 Type II Certification
3-
tags: [security, cloud, hive, platform, compliance]
3+
tags: [security, cloud, graphql-hive, platform, compliance]
44
authors: dotan
55
date: 2025-03-25
66
description:

packages/web/docs/src/app/blog/(posts)/layout.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { cn, HiveLayoutConfig } from '@theguild/components';
1+
import { cn, GetYourAPIGameRightSection, HiveLayoutConfig } from '@theguild/components';
22
import { LandingPageContainer } from '../../../components/landing-page-container';
3-
import '../../hive-prose-styles.css';
43
import { BlogPostHeader } from '../components/blog-post-layout/blog-post-header';
4+
import { SimilarPosts } from '../components/blog-post-layout/similar-posts';
5+
import '../../hive-prose-styles.css';
56

67
const MAIN_CONTENT = 'main-content';
78

@@ -13,11 +14,13 @@ export default function BlogPostLayout({ children }: { children: React.ReactNode
1314
<div
1415
className={cn(
1516
MAIN_CONTENT,
16-
'mx-auto flex *:!pl-2 sm:*:!ml-auto sm:*:!pl-0 [&>div>:first-child]:hidden [&_main>p:first-of-type]:text-2xl/8',
17+
'mx-auto flex *:!pl-2 sm:*:!ml-auto sm:*:!pl-0 [&>div>:first-child]:hidden [&_main>p:first-of-type]:text-xl/8 md:[&_main>p:first-of-type]:text-2xl/8',
1718
)}
1819
>
1920
{children}
2021
</div>
22+
<SimilarPosts className="mx-4 md:mx-6" />
23+
<GetYourAPIGameRightSection className="mx-4 sm:mb-6 md:mx-6" />
2124
</LandingPageContainer>
2225
);
2326
}

packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-header.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function BlogPostHeader({ className }: { className?: string }) {
2020
{image && <BlogPostPicture image={image} />}
2121
<header
2222
className={cn(
23-
'flex flex-col items-center rounded-3xl bg-[rgb(var(--nextra-bg))] px-1 pb-6 pt-4 md:px-12 md:pb-12 md:pt-6 xl:w-[888px]',
23+
'flex flex-col rounded-3xl bg-[rgb(var(--nextra-bg))] px-1 pb-6 pt-4 sm:items-center md:px-12 md:pb-12 md:pt-6 xl:w-[888px]',
2424
image && '-mt-20 max-sm:mx-6',
2525
className,
2626
)}
@@ -38,7 +38,7 @@ export function BlogPostHeader({ className }: { className?: string }) {
3838
<Heading
3939
as="h1"
4040
size="md"
41-
className="mb-0 mt-4 w-[--article-max-width] text-pretty text-center"
41+
className="mb-0 mt-4 w-[--article-max-width] text-pretty sm:text-center"
4242
>
4343
{title}
4444
</Heading>
@@ -47,7 +47,7 @@ export function BlogPostHeader({ className }: { className?: string }) {
4747
authors: Array.isArray(authors) ? authors : [authors],
4848
date,
4949
}}
50-
className="mt-4"
50+
className="mt-4 max-sm:justify-start"
5151
/>
5252
</header>
5353
</>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use client';
2+
3+
import { useFrontmatter } from '#components/use-frontmatter';
4+
import { BlogFrontmatter, BlogPostFile } from '../../../blog-types';
5+
import { BlogCard } from '../../blog-card';
6+
7+
export function SimilarPostsClient({ sortedPosts }: { sortedPosts: BlogPostFile[] }) {
8+
const { frontmatter } = useFrontmatter<BlogFrontmatter>();
9+
const tags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [frontmatter.tags];
10+
11+
const postsToShow = [];
12+
const intersectedTags: string[] = [];
13+
14+
for (let i = 0; i < sortedPosts.length && postsToShow.length < 2; i++) {
15+
const post = sortedPosts[i];
16+
if (post.frontMatter.title !== frontmatter.title) {
17+
const postTags = Array.isArray(post.frontMatter.tags)
18+
? post.frontMatter.tags
19+
: [post.frontMatter.tags];
20+
21+
const tagsInCommon = postTags.filter(tag => tags.includes(tag));
22+
if (tagsInCommon.length > 0) {
23+
postsToShow.push(post);
24+
intersectedTags.push(tagsInCommon[0]);
25+
}
26+
}
27+
}
28+
29+
if (postsToShow.length === 0) {
30+
// We need the CSS in the server component to know we didn't find any similar posts.
31+
return <del />;
32+
}
33+
34+
return postsToShow.map((post, i) => (
35+
<BlogCard key={i} post={post} className="h-full" tag={intersectedTags[i]} />
36+
));
37+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { ArrowIcon, cn, Heading } from '@theguild/components';
2+
import { getPageMap } from '@theguild/components/server';
3+
import { isBlogPost } from '../../../blog-types';
4+
import { SimilarPostsClient } from './client';
5+
6+
export async function SimilarPosts({ className }: { className?: string }) {
7+
// We're overfetching all posts here, because Nextra doesn't allow us
8+
// to get the current post frontmatter on the server, and we need it to
9+
// filter the similar posts. This could be worked around by moving the
10+
// code to a remark/rehype plugin layer, but it seems like an overkill.
11+
// We can optimize this later.
12+
const [_meta, _indexPage, ...pageMap] = await getPageMap('/blog');
13+
const sortedPosts = pageMap
14+
.filter(isBlogPost)
15+
.sort((a, b) => new Date(b.frontMatter.date).getTime() - new Date(a.frontMatter.date).getTime())
16+
.slice(
17+
0,
18+
// This is an assumption that 100 posts is enough to find similar ones.
19+
// Worst case we'll not render the section if there's no similar post.
20+
100,
21+
);
22+
23+
return (
24+
<section
25+
className={cn(
26+
'flex items-stretch gap-4 py-6 *:flex-1 max-md:flex-col sm:gap-6 lg:p-24',
27+
'has-[del]:hidden',
28+
className,
29+
)}
30+
>
31+
<div className="text-green-1000 md:max-w-[36%] dark:text-neutral-100">
32+
<header className="flex items-center justify-between max-md:justify-center">
33+
<Heading size="md" as="h3">
34+
Explore
35+
</Heading>
36+
<ArrowIcon className="ml-2 size-12 shrink-0 max-md:hidden" />
37+
</header>
38+
<p className="mt-4 max-md:text-center">Dive deeper into related topics.</p>
39+
</div>
40+
<SimilarPostsClient sortedPosts={sortedPosts} />
41+
</section>
42+
);
43+
}

packages/web/docs/src/app/gateway/lets-get-advanced-section.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export function LetsGetAdvancedSection({ className, ...rest }: React.HTMLAttribu
1515
<div className="nextra-scrollbar overflow-auto max-sm:-m-4 max-sm:p-4">
1616
<ul className="mt-6 flex gap-6 *:flex *:flex-col *:rounded-3xl max-sm:*:w-80 max-sm:*:shrink-0 sm:grid sm:grid-cols-2 md:mt-8 md:*:p-8 lg:mt-12 xl:mt-16 xl:grid-cols-4 [&>*>:last-child]:contents [&>*>h3]:mb-4">
1717
<InfoCard
18+
as="li"
1819
icon={<ArrowRightWallIcon />}
1920
heading="GraphQL Subscriptions"
2021
// moved to the last place, because it's the shortest
@@ -33,7 +34,7 @@ export function LetsGetAdvancedSection({ className, ...rest }: React.HTMLAttribu
3334
Documentation
3435
</CallToAction>
3536
</InfoCard>
36-
<InfoCard icon={<CogIcon />} heading="@defer and @stream Support">
37+
<InfoCard as="li" icon={<CogIcon />} heading="@defer and @stream Support">
3738
Allows more efficient data loading patterns, improving user interface responsiveness and
3839
system performance.
3940
<div className="grow" />
@@ -45,7 +46,7 @@ export function LetsGetAdvancedSection({ className, ...rest }: React.HTMLAttribu
4546
Documentation
4647
</CallToAction>
4748
</InfoCard>
48-
<InfoCard icon={<StackIcon />} heading="Request Batching">
49+
<InfoCard as="li" icon={<StackIcon />} heading="Request Batching">
4950
Reduces network overhead by enabling multiple GraphQL operations in a single HTTP
5051
request, enhancing data retrieval efficiency.
5152
<div className="grow" />
@@ -57,7 +58,7 @@ export function LetsGetAdvancedSection({ className, ...rest }: React.HTMLAttribu
5758
Documentation
5859
</CallToAction>
5960
</InfoCard>
60-
<InfoCard icon={<TargetIcon />} heading="Demand Control">
61+
<InfoCard as="li" icon={<TargetIcon />} heading="Demand Control">
6162
Facilitates efficient management of API resources by setting limits on query complexity
6263
and execution depth, tailored for high-demand cloud environments.
6364
<div className="grow" />

packages/web/docs/src/app/layout.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,7 @@ export default async function HiveDocsLayout({ children }: { children: ReactNode
9797
icon: <AccountBox />,
9898
children: 'Case Studies',
9999
},
100-
{
101-
href: 'https://the-guild.dev/graphql/hive/blog',
102-
icon: <PencilIcon />,
103-
children: 'Blog',
104-
},
100+
{ href: '/blog', icon: <PencilIcon />, children: 'Blog' },
105101
{
106102
href: 'https://github.com/graphql-hive/console',
107103
icon: <GitHubIcon />,

packages/web/docs/src/components/lede.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ import { cn } from '@theguild/components';
33
export interface LedeProps extends React.HTMLAttributes<HTMLParagraphElement> {}
44

55
export function Lede(props: LedeProps) {
6-
return <div {...props} className={cn('sm:*:text-xl/6 md:*:text-2xl/8', props.className)} />;
6+
return <div {...props} className={cn('sm:*:text-xl/8 md:*:text-2xl/8', props.className)} />;
77
}

pnpm-lock.yaml

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)