@@ -83,10 +72,10 @@ export function SkeletonCard(props: { id?: string; style?: ClassValue }) {
export function SkeletonSmall(props: { id?: string; style?: ClassValue }) {
const { id, style } = props;
return (
-
+
);
diff --git a/packages/gitbook/src/components/utils/Image.tsx b/packages/gitbook/src/components/utils/Image.tsx
index 7ce577e21c..e709af3756 100644
--- a/packages/gitbook/src/components/utils/Image.tsx
+++ b/packages/gitbook/src/components/utils/Image.tsx
@@ -15,6 +15,12 @@ type ImageSource = {
aspectRatio?: string;
};
+type ImageSourceSized = {
+ src: string;
+ size: ImageSize | null;
+ aspectRatio?: string;
+};
+
export type ImageResponsiveSize = {
/** Media query to apply this width for */
media?: string;
@@ -96,7 +102,7 @@ interface ImgDOMPropsWithSrc extends React.ComponentPropsWithoutRef<'img'> {
* We don't use the `next/image` component because we need to load images from external sources,
* and we want to avoid client components.
*/
-export async function Image(
+export function Image(
props: PolymorphicComponentProp<
'img',
{
@@ -145,26 +151,114 @@ export async function Image(
);
}
+function ImagePicture(
+ props: PolymorphicComponentProp<
+ 'img',
+ {
+ source: ImageSource;
+ } & ImageCommonProps
+ >,
+) {
+ const { source, ...rest } = props;
+ const { size } = source;
+
+ return size ? (
+
+ ) : (
+
+ );
+}
+
+async function ImagePictureUnsized(
+ props: PolymorphicComponentProp<
+ 'img',
+ {
+ source: ImageSource;
+ } & ImageCommonProps
+ >,
+) {
+ const { source, ...rest } = props;
+
+ const size = await getImageSize(source.src);
+ return
;
+}
+
+function ImagePictureSized(
+ props: PolymorphicComponentProp<
+ 'img',
+ {
+ source: ImageSourceSized;
+ } & ImageCommonProps
+ >,
+) {
+ const {
+ source,
+ sizes,
+ style: _style,
+ alt,
+ quality = 100,
+ priority = 'normal',
+ inline = false,
+ zoom = false,
+ resize = true,
+ preload = false,
+ inlineStyle,
+ ...rest
+ } = props;
+
+ if (process.env.NODE_ENV === 'development' && sizes.length === 0) {
+ throw new Error('You must provide at least one size for the image.');
+ }
+
+ const attrs = getImageAttributes({ sizes, source, quality, resize });
+ const canBeFetched = checkIsHttpURL(attrs.src);
+ const fetchPriority = canBeFetched ? getFetchPriority(priority) : undefined;
+ const loading = priority === 'lazy' ? 'lazy' : undefined;
+ const aspectRatioStyle = source.aspectRatio ? { aspectRatio: source.aspectRatio } : {};
+ const style = { ...aspectRatioStyle, ...inlineStyle };
+
+ // Preload the image if needed.
+ if (fetchPriority === 'high' || preload) {
+ ReactDOM.preload(attrs.src, {
+ as: 'image',
+ imageSrcSet: attrs.srcSet,
+ imageSizes: attrs.sizes,
+ fetchPriority,
+ });
+ }
+
+ const imgProps: ImgDOMPropsWithSrc = {
+ alt,
+ style,
+ loading,
+ fetchPriority,
+ ...rest,
+ ...attrs,
+ };
+
+ return zoom ?
:
![{imgProps.alt]()
;
+}
+
/**
* Get the attributes for an image.
* src, srcSet, sizes, width, height, etc.
*/
-async function getImageAttributes(params: {
+function getImageAttributes(params: {
sizes: ImageResponsiveSize[];
- source: ImageSource;
+ source: ImageSourceSized;
quality: number;
resize: boolean;
-}): Promise<{
+}): {
src: string;
srcSet?: string;
sizes?: string;
width?: number;
height?: number;
-}> {
+} {
const { sizes, source, quality, resize } = params;
let src = source.src;
- const getURL = resize ? await getResizedImageURLFactory(source.src) : null;
+ const getURL = resize ? getResizedImageURLFactory(source.src) : null;
if (!getURL) {
return {
@@ -204,11 +298,7 @@ async function getImageAttributes(params: {
sourceSizes.push(`${defaultSize.width}px`);
}
- // If we don't know the size of the image, we can try reading it from the image itself.
- const size =
- source.size ?? (await getImageSize(source.src, { width: defaultSize?.width, dpr: 3 }));
-
- if (!size) {
+ if (!source.size) {
return { src };
}
@@ -216,7 +306,7 @@ async function getImageAttributes(params: {
src,
srcSet: sources.join(', '),
sizes: sourceSizes.join(', '),
- ...size,
+ ...source.size,
};
}
@@ -230,59 +320,3 @@ function getFetchPriority(priority: ImageCommonProps['priority']) {
return undefined;
}
}
-
-async function ImagePicture(
- props: PolymorphicComponentProp<
- 'img',
- {
- source: ImageSource;
- } & ImageCommonProps
- >,
-) {
- const {
- source,
- sizes,
- style: _style,
- alt,
- quality = 100,
- priority = 'normal',
- inline = false,
- zoom = false,
- resize = true,
- preload = false,
- inlineStyle,
- ...rest
- } = props;
-
- if (process.env.NODE_ENV === 'development' && sizes.length === 0) {
- throw new Error('You must provide at least one size for the image.');
- }
-
- const attrs = await getImageAttributes({ sizes, source, quality, resize });
- const canBeFetched = checkIsHttpURL(attrs.src);
- const fetchPriority = canBeFetched ? getFetchPriority(priority) : undefined;
- const loading = priority === 'lazy' ? 'lazy' : undefined;
- const aspectRatioStyle = source.aspectRatio ? { aspectRatio: source.aspectRatio } : {};
- const style = { ...aspectRatioStyle, ...inlineStyle };
-
- // Preload the image if needed.
- if (fetchPriority === 'high' || preload) {
- ReactDOM.preload(attrs.src, {
- as: 'image',
- imageSrcSet: attrs.srcSet,
- imageSizes: attrs.sizes,
- fetchPriority,
- });
- }
-
- const imgProps: ImgDOMPropsWithSrc = {
- alt,
- style,
- loading,
- fetchPriority,
- ...rest,
- ...attrs,
- };
-
- return zoom ?
:
![{imgProps.alt]()
;
-}
diff --git a/packages/gitbook/src/lib/images.ts b/packages/gitbook/src/lib/images.ts
index 9c2030dc04..a9567523af 100644
--- a/packages/gitbook/src/lib/images.ts
+++ b/packages/gitbook/src/lib/images.ts
@@ -76,14 +76,14 @@ interface ResizeImageOptions {
/**
* Create a function to get resized image URLs for a given image URL.
*/
-export async function getResizedImageURLFactory(
+export function getResizedImageURLFactory(
input: string,
-): Promise<((options: ResizeImageOptions) => string) | null> {
+): ((options: ResizeImageOptions) => string) | null {
if (!checkIsSizableImageURL(input)) {
return null;
}
- const signature = await generateSignatureV1(input);
+ const signature = generateSignatureV1(input);
return (options) => {
const url = new URL('/~gitbook/image', rootUrl());
@@ -113,11 +113,8 @@ export async function getResizedImageURLFactory(
* Create a new URL for an image with resized parameters.
* The URL is signed and verified by the server.
*/
-export async function getResizedImageURL(
- input: string,
- options: ResizeImageOptions,
-): Promise
{
- const factory = await getResizedImageURLFactory(input);
+export function getResizedImageURL(input: string, options: ResizeImageOptions): string {
+ const factory = getResizedImageURLFactory(input);
return factory?.(options) ?? input;
}
@@ -129,7 +126,7 @@ export async function verifyImageSignature(
{ signature, version }: { signature: string; version: '1' | '0' },
): Promise {
const expectedSignature =
- version === '1' ? await generateSignatureV1(input) : await generateSignatureV0(input);
+ version === '1' ? generateSignatureV1(input) : await generateSignatureV0(input);
return expectedSignature === signature;
}
@@ -236,7 +233,7 @@ const fnv1aUtf8Buffer = new Uint8Array(512);
* When setting it in a URL, we use version '1' for the 'sv' querystring parameneter
* to know that it was the algorithm that was used.
*/
-async function generateSignatureV1(input: string): Promise {
+function generateSignatureV1(input: string): string {
const all = [input, process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY].filter(Boolean).join(':');
return fnv1a(all, { utf8Buffer: fnv1aUtf8Buffer }).toString(16);
}