Skip to content

Commit

Permalink
feat: download snippet generation (#7351)
Browse files Browse the repository at this point in the history
* feat: download snippet generation

* feat: changed approach

* chore: simplify and reduce bundle size

* chore: reduce bundle

* chore: minor code fixes

* chore: more code review

* feat: introduced skeleton and animation

* chore: synchronous shiki

* chore: prevent internal errors

* chore: attempt to handle null situations

* fix: attempt to fix vercel env

* fix: erroneous env usage for fetch

* chore: fix select styles

* chore: updated allowlist for env vars

* feat: streamline detectos (only run query once)

* fix: avatar rendering and avatar group margins

* fix: pass ref

* chore: optimized release provider memo

* chore: next-image mock

* Update crowdin.yml (#7353)

* chore: improved loading state

* Apply suggestions from code review

Co-authored-by: Caner Akdas <canerakdas@gmail.com>
Signed-off-by: Claudio W <cwunder@gnome.org>

* chore: correct fixes

* fix: unit tests

* chore: final code review

* fix: server component test

* fix: rsc still risky for storybook

---------

Signed-off-by: Claudio W <cwunder@gnome.org>
Co-authored-by: Andriy Poznakhovskyy <andriy@crowdin.com>
Co-authored-by: Caner Akdas <canerakdas@gmail.com>
  • Loading branch information
3 people authored Dec 24, 2024
1 parent e56edcc commit 8b643f6
Show file tree
Hide file tree
Showing 85 changed files with 5,170 additions and 2,394 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/translations-pr-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ on:
- '!apps/site/pages/en/**/*.mdx'
- 'packages/i18n/locales/*.json'
- '!packages/i18n/locales/en.json'
- 'apps/site/snippets/**/*.bash'
- '!apps/site/snippets/en/**/*.bash'

# Cancel any runs on the same branch
concurrency:
Expand Down
1 change: 1 addition & 0 deletions apps/site/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const config: StorybookConfig = {
resolve: {
...config.resolve,
alias: {
'next/image': join(mocksFolder, './next-image.mjs'),
'next-intl/navigation': join(mocksFolder, './next-intl.mjs'),
'@/client-context': join(mocksFolder, './client-context.mjs'),
'@': join(__dirname, '../'),
Expand Down
13 changes: 7 additions & 6 deletions apps/site/app/[locale]/feed/[feed]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import provideWebsiteFeeds from '@/next-data/providers/websiteFeeds';
import { siteConfig } from '@/next.json.mjs';
import { defaultLocale } from '@/next.locales.mjs';

// We only support fetching these pages from the /en/ locale code
const locale = defaultLocale.code;

type StaticParams = { params: Promise<{ feed: string; locale: string }> };
type DynamicStaticPaths = { locale: string; feed: string };
type StaticParams = { params: Promise<DynamicStaticPaths> };

// This is the Route Handler for the `GET` method which handles the request
// for the Node.js Website Blog Feeds (RSS)
Expand All @@ -20,15 +18,18 @@ export const GET = async (_: Request, props: StaticParams) => {

return new NextResponse(websiteFeed, {
headers: { 'Content-Type': 'application/xml' },
status: websiteFeed ? 200 : 404,
status: websiteFeed !== undefined ? 200 : 404,
});
};

// This function generates the static paths that come from the dynamic segments
// `[locale]/feeds/[feed]` and returns an array of all available static paths
// This is used for ISR static validation and generation
export const generateStaticParams = async () =>
siteConfig.rssFeeds.map(feed => ({ feed: feed.file, locale }));
siteConfig.rssFeeds.map(feed => ({
locale: defaultLocale.code,
feed: feed.file,
}));

// Enforces that only the paths from `generateStaticParams` are allowed, giving 404 on the contrary
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import {
} from '@/next-data/providers/blogData';
import { defaultLocale } from '@/next.locales.mjs';

type StaticParams = {
params: Promise<{ locale: string; category: string; page: string }>;
};
type DynamicStaticPaths = { locale: string; category: string; page: string };
type StaticParams = { params: Promise<DynamicStaticPaths> };

// This is the Route Handler for the `GET` method which handles the request
// for providing Blog Posts for Blog Categories and Pagination Metadata
Expand Down
37 changes: 37 additions & 0 deletions apps/site/app/[locale]/next-data/download-snippets/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import provideDownloadSnippets from '@/next-data/providers/downloadSnippets';
import { defaultLocale } from '@/next.locales.mjs';

type DynamicStaticPaths = { locale: string };
type StaticParams = { params: Promise<DynamicStaticPaths> };

// This is the Route Handler for the `GET` method which handles the request
// for generating JSON data for Download Snippets
// @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers
export const GET = async (_: Request, props: StaticParams) => {
const params = await props.params;

// Retrieve all available Download snippets for a given locale if available
const snippets = provideDownloadSnippets(params.locale);

// We append always the default/fallback snippets when a result is found
return Response.json(snippets, {
status: snippets !== undefined ? 200 : 404,
});
};

// This function generates the static paths that come from the dynamic segments
// `[locale]/next-data/download-snippets/` this will return a default value as we don't want to
// statically generate this route as it is compute-expensive.
// Hence we generate a fake route just to satisfy Next.js requirements.
export const generateStaticParams = async () => [
{ locale: defaultLocale.code },
];

// Enforces that this route is cached and static as much as possible
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
export const dynamic = 'force-static';

// Ensures that this endpoint is invalidated and re-executed every X minutes
// so that when new deployments happen, the data is refreshed
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate
export const revalidate = 300;
11 changes: 4 additions & 7 deletions apps/site/app/[locale]/next-data/og/[category]/[title]/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,10 @@ const CATEGORY_TO_THEME_COLOUR_MAP = {
vulnerability: tailwindConfig.theme.colors.warning['600'],
};

type StaticParams = {
params: Promise<{
locale: string;
category: keyof typeof CATEGORY_TO_THEME_COLOUR_MAP;
title: string;
}>;
};
type Category = keyof typeof CATEGORY_TO_THEME_COLOUR_MAP;

type DynamicStaticPaths = { locale: string; category: Category; title: string };
type StaticParams = { params: Promise<DynamicStaticPaths> };

// This is the Route Handler for the `GET` method which handles the request
// for generating OpenGraph images for Blog Posts and Pages
Expand Down
12 changes: 4 additions & 8 deletions apps/site/components/Blog/BlogHeader/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
'use client';

import { RssIcon } from '@heroicons/react/24/solid';
import { useTranslations } from 'next-intl';
import type { FC } from 'react';
Expand All @@ -9,15 +7,13 @@ import { siteConfig } from '@/next.json.mjs';

import styles from './index.module.css';

type BlogHeaderProps = {
category: string;
};
type BlogHeaderProps = { category: string };

const BlogHeader: FC<BlogHeaderProps> = ({ category }) => {
const t = useTranslations();
const currentFile =
siteConfig.rssFeeds.find(item => item.category === category)?.file ??
'blog.xml';

const feed = siteConfig.rssFeeds.find(item => item.category === category);
const currentFile = feed ? feed.file : 'blog.xml';

return (
<header className={styles.blogHeader}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
justify-center
rounded-full
border-2
border-white
border-transparent
bg-neutral-100
object-cover
text-xs
text-neutral-800
dark:border-neutral-950
dark:bg-neutral-900
dark:text-neutral-300;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,4 @@ export const NoSquare: Story = {
},
};

export const FallBack: Story = {
args: {
image: 'https://avatars.githubusercontent.com/u/',
nickname: 'John Doe',
fallback: 'JD',
},
};

export default { component: Avatar } as Meta;
46 changes: 27 additions & 19 deletions apps/site/components/Common/AvatarGroup/Avatar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as RadixAvatar from '@radix-ui/react-avatar';
import classNames from 'classnames';
import type { ComponentPropsWithoutRef, ElementRef } from 'react';
import Image from 'next/image';
import type { HTMLAttributes } from 'react';
import { forwardRef } from 'react';

import Link from '@/components/Link';
Expand All @@ -16,37 +16,45 @@ export type AvatarProps = {
url?: string;
};

// @TODO: We temporarily removed the Avatar Radix UI primitive, since it was causing flashing
// during initial load and not being able to render nicely when images are already cached.
// @see https://github.com/radix-ui/primitives/pull/3008
const Avatar = forwardRef<
ElementRef<typeof RadixAvatar.Root>,
ComponentPropsWithoutRef<typeof RadixAvatar.Root> & AvatarProps
HTMLSpanElement,
HTMLAttributes<HTMLSpanElement> & AvatarProps
>(({ image, nickname, name, fallback, url, size = 'small', ...props }, ref) => {
const Wrapper = url ? Link : 'div';

return (
<RadixAvatar.Root
<span
{...props}
className={classNames(styles.avatar, styles[size], props.className)}
ref={ref}
className={classNames(styles.avatar, styles[size], props.className)}
>
<Wrapper
href={url || undefined}
target={url ? '_blank' : undefined}
className={styles.wrapper}
>
<RadixAvatar.Image
loading="lazy"
src={image}
alt={name || nickname}
className={styles.item}
/>
<RadixAvatar.Fallback
delayMs={500}
className={classNames(styles.item, styles[size])}
>
{fallback}
</RadixAvatar.Fallback>
{image && (
<Image
width={40}
height={40}
loading="lazy"
decoding="async"
src={image}
alt={name || nickname}
className={styles.item}
/>
)}

{!image && (
<span className={classNames(styles.item, styles[size])}>
{fallback}
</span>
)}
</Wrapper>
</RadixAvatar.Root>
</span>
);
});

Expand Down
1 change: 1 addition & 0 deletions apps/site/components/Common/AvatarGroup/Overlay/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const AvatarOverlay: FC<AvatarOverlayProps> = ({
fallback={fallback}
size="medium"
/>

<div className={styles.user}>
{name && <div className={styles.name}>{name}</div>}
{nickname && <div className={styles.nickname}>{nickname}</div>}
Expand Down
15 changes: 12 additions & 3 deletions apps/site/components/Common/AvatarGroup/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@
@apply flex
flex-wrap
items-center;

&:not(.expandable) {
> span {
@apply ml-0;
}
}
}

.small {
@apply xs:-space-x-2
space-x-0.5;
> span {
@apply -ml-2;
}
}

.medium {
@apply -space-x-2.5;
> span {
@apply -ml-2.5;
}
}
8 changes: 1 addition & 7 deletions apps/site/components/Common/AvatarGroup/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,8 @@ const names = [
'araujogui',
];

const unknownAvatar = {
image: 'https://avatars.githubusercontent.com/u/',
nickname: 'unknown-avatar',
fallback: 'UA',
};

const defaultProps = {
avatars: [unknownAvatar, ...getAuthorWithId(names, true)],
avatars: getAuthorWithId(names, true),
};

export const Default: Story = {
Expand Down
7 changes: 6 additions & 1 deletion apps/site/components/Common/AvatarGroup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ const AvatarGroup: FC<AvatarGroupProps> = ({
);

return (
<div className={classNames(styles.avatarGroup, styles[size])}>
<div
className={classNames(styles.avatarGroup, styles[size], {
[styles.expandable]: avatars.length > limit,
})}
>
{renderAvatars.map(({ ...avatar }) => (
<Fragment key={avatar.nickname}>
<Tooltip
Expand All @@ -54,6 +58,7 @@ const AvatarGroup: FC<AvatarGroupProps> = ({
</Tooltip>
</Fragment>
))}

{avatars.length > limit && (
<span
onClick={isExpandable ? () => setShowMore(prev => !prev) : undefined}
Expand Down
2 changes: 2 additions & 0 deletions apps/site/components/Common/ProgressionSidebar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client';

import { useTranslations } from 'next-intl';
import type { ComponentProps, FC } from 'react';
import { useRef } from 'react';
Expand Down
6 changes: 2 additions & 4 deletions apps/site/components/Common/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { OramaSearchBox, OramaSearchButton } from '@orama/react-components';
import { useTranslations, useLocale } from 'next-intl';
import { useTheme } from 'next-themes';
import { type FC } from 'react';
import type { FC } from 'react';

import { useRouter } from '@/navigation.mjs';
import {
Expand Down Expand Up @@ -132,9 +132,7 @@ const SearchButton: FC = () => {
const fullURLObject = new URL(event.detail.result.path, BASE_URL);

// result.path already contains LOCALE. Locale is set to undefined here so router does not add it once again.
router.push(fullURLObject.href, {
locale: undefined,
});
router.push(fullURLObject.href, { locale: undefined });
}}
/>
</>
Expand Down
Loading

0 comments on commit 8b643f6

Please sign in to comment.