Merge branch 'main' into claire/fix-infinite-reload-search-ai

* main:
  Fix server actions stability (#2728)
  temporarily add back cloudflare KV cache backend (#2726)
  Fix padding of ads box (#2724)
  Try to fix error on og image generation (#2722)
This commit is contained in:
Claire Chabas
2025-01-13 11:49:43 +01:00
9 changed files with 139 additions and 101 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'gitbook': patch
---
Try to fix error on og image generation
+5
View File
@@ -0,0 +1,5 @@
---
'gitbook': patch
---
Fix server actions stability leading to no results found sometimes on search
+1
View File
@@ -41,6 +41,7 @@ jobs:
run: bun run build:cloudflare
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ secrets.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
SENTRY_DSN: ${{ vars.SENTRY_DSN }}
+1
View File
@@ -10,6 +10,7 @@ module.exports = withSentryConfig(
GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX,
GITBOOK_ICONS_URL: process.env.GITBOOK_ICONS_URL,
GITBOOK_ICONS_TOKEN: process.env.GITBOOK_ICONS_TOKEN,
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY,
},
webpack(config, { dev, webpack }) {
@@ -5,15 +5,31 @@ import { NextRequest } from 'next/server';
import colorContrast from 'postcss-color-contrast/js';
import React from 'react';
import { googleFontsMap } from '@/fonts';
import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { getAbsoluteHref } from '@/lib/links';
import { tcls } from '@/lib/tailwind';
import { filterOutNullable } from '@/lib/typescript';
import { getContentTitle } from '@/lib/utils';
import { PageIdParams, fetchPageData } from '../../../../fetch';
export const runtime = 'edge';
async function loadGoogleFont(font: string, text: string) {
const url = `https://fonts.googleapis.com/css2?family=${font}&text=${encodeURIComponent(text)}`;
const css = await (await fetch(url)).text();
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/);
if (resource) {
const response = await fetch(resource[1]);
if (response.status == 200) {
return await response.arrayBuffer();
}
}
throw new Error('failed to load font data');
}
/**
* Render the OpenGraph image for a space.
*/
@@ -21,30 +37,60 @@ export async function GET(req: NextRequest, { params }: { params: Promise<PageId
const ctx = getGitBookContextFromHeaders(req.headers);
const { space, page, customization, site } = await fetchPageData(ctx, await params);
// If user configured a custom social preview, we redirect to it.
if (customization.socialPreview.url) {
// If user configured a custom social preview, we redirect to it.
redirect(customization.socialPreview.url);
}
// TODO: Support all fonts available in GitBook
// Right now this is impossible since next/font/google does not expose the cached font file
// Another option would be to use the Satori prop `loadAdditionalAsset` [example](https://github.com/vercel/satori/blob/main/playground/pages/index.tsx),
// but this prop isn't (yet) exposed through `ImageResponse`.
const interRegular = await fetch(
new URL('../../../../../../fonts/Inter/Inter-Regular.ttf', import.meta.url),
).then((res) => res.arrayBuffer());
const interBold = await fetch(
new URL('../../../../../../fonts/Inter/Inter-Bold.ttf', import.meta.url),
).then((res) => res.arrayBuffer());
// Compute all text to load only the necessary fonts
const contentTitle = customization.header.logo
? ''
: getContentTitle(space, customization, site ?? null);
const pageTitle = page
? page.title.length > 64
? page.title.slice(0, 64) + '...'
: page.title
: 'Not found';
const pageDescription =
page?.description && page?.title.length <= 64
? page.description.length > 164
? page.description.slice(0, 164) + '...'
: page.description
: '';
const fontFamily = googleFontsMap[customization.styling.font] ?? 'Inter';
const regularText = pageDescription;
const boldText = `${contentTitle}${pageTitle}`;
const fonts = (
await Promise.all([
regularText
? loadGoogleFont(`${fontFamily}:wght@400`, regularText).then((data) => ({
name: fontFamily,
data,
style: 'normal' as const,
weight: 400 as const,
}))
: null,
boldText
? loadGoogleFont(`${fontFamily}:wght@700`, `${contentTitle}${pageTitle}`).then(
(data) => ({
name: fontFamily,
data,
style: 'normal' as const,
weight: 700 as const,
}),
)
: null,
])
).filter(filterOutNullable);
const theme = customization.themes.default;
const useLightTheme = theme === 'light';
// We have no access to CSS variables, so we'll have to hardcode some values
const baseColors = {
light: '#ffffff',
dark: '#111827',
};
const baseColors = { light: '#ffffff', dark: '#111827' };
let colors = {
background: baseColors[theme],
@@ -92,49 +138,17 @@ export async function GET(req: NextRequest, { params }: { params: Promise<PageId
break;
}
const favicon = (() => {
if ('icon' in customization.favicon)
return (
<img
src={customization.favicon.icon[theme]}
width={40}
height={40}
tw={tcls('mr-4')}
alt="Icon"
/>
);
if ('emoji' in customization.favicon)
return (
<span tw={tcls('text-4xl', 'mr-4')}>
{String.fromCodePoint(parseInt('0x' + customization.favicon.emoji))}
</span>
);
const src = getAbsoluteHref(
ctx,
`~gitbook/icon?size=medium&theme=${customization.themes.default}`,
true,
);
return <img src={src} alt="Icon" width={40} height={40} tw={tcls('mr-4')} />;
})();
return new ImageResponse(
(
<div
tw={tcls(
'justify-between',
'p-20',
'relative',
'w-full',
'h-full',
'flex',
'flex-col',
`bg-[${colors.background}]`,
`text-[${colors.body}]`,
)}
tw={`justify-between p-20 relative w-full h-full flex flex-col bg-[${colors.background}] text-[${colors.body}]`}
style={{
fontFamily,
}}
>
{/* Gradient */}
<div
tw={tcls('absolute', 'inset-0')}
tw="absolute inset-0"
style={{
backgroundImage: `radial-gradient(ellipse 100% 100% at top right , ${colors.gradient}, ${colors.gradient}00)`,
opacity: 0.5,
@@ -142,11 +156,7 @@ export async function GET(req: NextRequest, { params }: { params: Promise<PageId
></div>
{/* Grid */}
<img
tw={tcls('absolute', 'inset-0', 'w-[100vw]', 'h-[100vh]')}
src={gridAsset}
alt="Grid"
/>
<img tw="absolute inset-0 w-[100vw] h-[100vh]" src={gridAsset} alt="Grid" />
{/* Logo */}
{customization.header.logo ? (
@@ -160,39 +170,46 @@ export async function GET(req: NextRequest, { params }: { params: Promise<PageId
}
/>
) : (
<div tw={tcls('flex')}>
{favicon}
<h3 tw={tcls('text-4xl', 'my-0')}>
{getContentTitle(space, customization, site ?? null)}
</h3>
<div tw="flex">
{(() => {
if ('icon' in customization.favicon)
return (
<img
src={customization.favicon.icon[theme]}
width={40}
height={40}
tw="mr-4"
alt="Icon"
/>
);
if ('emoji' in customization.favicon)
return (
<span tw="text-4xl mr-4">
{String.fromCodePoint(
parseInt('0x' + customization.favicon.emoji),
)}
</span>
);
const src = getAbsoluteHref(
ctx,
`~gitbook/icon?size=medium&theme=${customization.themes.default}`,
true,
);
return <img src={src} alt="Icon" width={40} height={40} tw="mr-4" />;
})()}
<h3 tw="text-4xl my-0 font-bold">{contentTitle}</h3>
</div>
)}
{/* Title and description */}
<div tw={tcls('flex', 'flex-col')}>
<div tw="flex flex-col">
<h1
tw={tcls(
'text-8xl',
'my-0',
'tracking-tight',
'leading-none',
'text-left',
`text-[${colors.title}]`,
'font-bold',
)}
tw={`text-8xl my-0 tracking-light leading-none text-left text-[${colors.title}] font-bold`}
>
{page
? page.title.length > 64
? page.title.slice(0, 64) + '...'
: page.title
: 'Not found'}
{pageTitle}
</h1>
{page?.description && page?.title.length <= 64 ? (
<h2 tw={tcls('text-4xl', 'mb-0', 'mt-8', 'w-[75%]', 'font-normal')}>
{page.description.length > 164
? page.description.slice(0, 164) + '...'
: page.description}
</h2>
{pageDescription ? (
<h2 tw="text-4xl mb-0 mt-8 w-[75%] font-normal">{pageDescription}</h2>
) : null}
</div>
</div>
@@ -200,20 +217,7 @@ export async function GET(req: NextRequest, { params }: { params: Promise<PageId
{
width: 1200,
height: 630,
fonts: [
{
name: 'Inter',
data: interRegular,
weight: 400,
style: 'normal',
},
{
name: 'Inter',
data: interBold,
weight: 700,
style: 'normal',
},
],
fonts,
},
);
}
@@ -30,7 +30,7 @@ function getTopOffset(props: { sectionsHeader: boolean; topHeader: boolean }) {
}
if (props.topHeader) {
return 'lg:top-16 lg:max-h-[calc(100vh_-_3rem)]';
return 'lg:top-16 lg:max-h-[calc(100vh_-_4rem)]';
}
return 'lg:top-0 lg:max-h-screen';
@@ -237,7 +237,7 @@ export async function PageAside(props: {
spaceId={space.id}
siteAdsStatus={site?.ads && site.ads.status ? site.ads.status : undefined}
ignore={process.env.NODE_ENV !== 'production'}
style={tcls(site?.ads && site.ads.status === SiteAdsStatus.Live && ['mt-4'])}
style={tcls(site?.ads && site.ads.status === SiteAdsStatus.Live && ['mb-4'])}
/>
</aside>
);
+18
View File
@@ -203,3 +203,21 @@ export const fonts: { [fontName in CustomizationFont]: { className: string } } =
[CustomizationFont.Ubuntu]: ubuntu,
[CustomizationFont.ABCFavorit]: abcFavorit,
};
export const googleFontsMap: { [fontName in CustomizationFont]: string } = {
[CustomizationFont.Inter]: 'Inter',
[CustomizationFont.FiraSans]: 'Fira Sans Extra Condensed',
[CustomizationFont.IBMPlexSerif]: 'IBM Plex Serif',
[CustomizationFont.Lato]: 'Lato',
[CustomizationFont.Merriweather]: 'Merriweather',
[CustomizationFont.NotoSans]: 'Noto Sans',
[CustomizationFont.OpenSans]: 'Open Sans',
[CustomizationFont.Overpass]: 'Overpass',
[CustomizationFont.Poppins]: 'Poppins',
[CustomizationFont.Raleway]: 'Raleway',
[CustomizationFont.Roboto]: 'Roboto',
[CustomizationFont.RobotoSlab]: 'Roboto Slab',
[CustomizationFont.SourceSansPro]: 'Source Sans 3',
[CustomizationFont.Ubuntu]: 'Ubuntu',
[CustomizationFont.ABCFavorit]: 'Inter',
};
+3
View File
@@ -1,5 +1,6 @@
import { cloudflareCache } from './cloudflare-cache';
import { cloudflareDOCache } from './cloudflare-do';
import { cloudflareKVCache } from './cloudflare-kv';
import { memoryCache } from './memory';
export const cacheBackends = [
@@ -9,6 +10,8 @@ export const cacheBackends = [
// Cache local to the datacenter
// It can't be purged globally but it's faster
cloudflareCache,
// Cache global, but with slow replication
cloudflareKVCache,
// Global cache with slower performances
cloudflareDOCache,
];
+2 -1
View File
@@ -19,7 +19,8 @@
},
// Build the package for Cloudflare Pages
"build:cloudflare": {
"dependsOn": ["^build", "generate"]
"dependsOn": ["^build", "generate"],
"env": ["NEXT_SERVER_ACTIONS_ENCRYPTION_KEY"]
},
// Check the package for type errors
"typecheck": {