From c5a28f884a107d8a75b8631cdba98a8b3d494dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Mon, 25 Mar 2024 10:31:40 +0000 Subject: [PATCH 01/19] Fix zoom on small images with custom zoom component (#2228) --- bun.lockb | Bin 469201 -> 468747 bytes package.json | 1 - src/components/Search/SearchModal.tsx | 2 - src/components/utils/Image.tsx | 26 +- src/components/utils/Zoom.css | 3 - src/components/utils/Zoom.tsx | 13 - src/components/utils/ZoomImage.module.css | 19 ++ src/components/utils/ZoomImage.tsx | 280 ++++++++++++++++++++++ tailwind.config.ts | 5 - 9 files changed, 313 insertions(+), 36 deletions(-) delete mode 100644 src/components/utils/Zoom.css delete mode 100644 src/components/utils/Zoom.tsx create mode 100644 src/components/utils/ZoomImage.module.css create mode 100644 src/components/utils/ZoomImage.tsx diff --git a/bun.lockb b/bun.lockb index f6d6c7c348c1c6f8015e787f9dfb708602e850d2..2d949319652fd5e48e534a65f9a6f8e2da097e1d 100755 GIT binary patch delta 85109 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¨#tLRbvHf%JoAxDswDNC#enyV^W%LK<>Kx|#EA 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 JBpCnU{{tZdRcHVJ delta 85226 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 + ); +} + +function startViewTransition(callback: () => void, onEnd?: () => void) { + // @ts-ignore + if (document.startViewTransition) { + // @ts-ignore + const transition = document.startViewTransition(() => { + ReactDOM.flushSync(() => callback()); + }); + transition.finished.then(() => { + if (onEnd) { + onEnd(); + } + }); + } else { + callback(); + onEnd?.(); + } +} + +function isTouchDevice(): boolean { + return ( + 'ontouchstart' in window || + navigator.maxTouchPoints > 0 || + // @ts-ignore + navigator.msMaxTouchPoints > 0 + ); +} diff --git a/tailwind.config.ts b/tailwind.config.ts index 525e4e743..9c6b8e92f 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -199,11 +199,6 @@ const config: Config = { */ addVariant('navigation-open', 'body.navigation-open &'); - /** - * Variant when the search overlay is open. - */ - addVariant('search-open', 'body.search-open &'); - /** * Variant when a header is displayed. */ From 4c286b503c9d3c333719874c878bfba634d6b8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Berg=C3=A9?= Date: Mon, 25 Mar 2024 12:43:50 +0100 Subject: [PATCH 02/19] Fix `fullPage` screenshots (#2230) --- e2e/pages.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/e2e/pages.spec.ts b/e2e/pages.spec.ts index e71b6f80c..4b1c70dcd 100644 --- a/e2e/pages.spec.ts +++ b/e2e/pages.spec.ts @@ -52,7 +52,6 @@ const testCases: TestsCase[] = [ { name: 'Search', url: '?q=', - fullPage: false, }, { name: 'Search Results', @@ -60,7 +59,6 @@ const testCases: TestsCase[] = [ run: async (page) => { await page.waitForSelector('[data-test="search-results"]'); }, - fullPage: false, }, { name: 'AI Search', @@ -482,7 +480,7 @@ for (const testCase of testCases) { display: none !important; } `, - fullPage: testEntry.fullPage, + fullPage: testEntry.fullPage ?? false, }); } }); From ad9e8f223230d6dd361c821091de7f2b98b8d845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Mon, 25 Mar 2024 12:15:19 +0000 Subject: [PATCH 03/19] Fix authentication displayed for images (#2231) --- src/components/utils/Image.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/utils/Image.tsx b/src/components/utils/Image.tsx index 6fad1371b..f7835af1c 100644 --- a/src/components/utils/Image.tsx +++ b/src/components/utils/Image.tsx @@ -280,6 +280,7 @@ async function ImagePicture( style, loading, fetchPriority, + crossOrigin: 'anonymous', ...rest, ...attrs, }; From 0c99a57b3c03176d56027db6088877e40f4c4bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Mon, 25 Mar 2024 12:43:19 +0000 Subject: [PATCH 04/19] Also fix crossOrigin for images when zooming it (#2232) --- src/components/utils/ZoomImage.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/utils/ZoomImage.tsx b/src/components/utils/ZoomImage.tsx index 73d0b26cd..674368c51 100644 --- a/src/components/utils/ZoomImage.tsx +++ b/src/components/utils/ZoomImage.tsx @@ -125,7 +125,12 @@ export function ZoomImage( ) : null} {ReactDOM.createPortal( - , + , document.body, )} @@ -166,8 +171,13 @@ export function ZoomImage( ); } -function ZoomImageModal(props: { src: string; alt: string; onClose: () => void }) { - const { src, alt, onClose } = props; +function ZoomImageModal(props: { + src: string; + alt: string; + crossOrigin: React.ComponentPropsWithoutRef<'img'>['crossOrigin']; + onClose: () => void; +}) { + const { src, alt, crossOrigin, onClose } = props; const buttonRef = React.useRef(null); @@ -210,6 +220,7 @@ function ZoomImageModal(props: { src: string; alt: string; onClose: () => void } {alt} Date: Mon, 25 Mar 2024 13:49:39 +0100 Subject: [PATCH 05/19] Add legacy references to readme (#2233) --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 20a5005fb..f011225fa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,3 @@ - -

GitBook

@@ -28,8 +24,10 @@ - [Getting Started](#getting-started) - [Contributing](#contributing) -- [Types of contributions](#types-of-contributions) + - [Types of contributions](#types-of-contributions) - [Licensing](#license) +- [Acknowledgements](#acknowledgements) +- [Legacy GitBook](#legacy-gitbook-deprecated) ## Getting Started @@ -120,3 +118,7 @@ GitBook wouldn't be possible without these projects: - [Bun](https://bun.sh/) - [Tailwind CSS](https://tailwindcss.com/) - [Framer Motion](https://www.npmjs.com/package/framer-motion) + +## Legacy GitBook (Deprecated) + +Our previous version of GitBook and it's CLI tool are now deprecated. You can still view the old repository and it's commits on this [branch](https://github.com/GitbookIO/gitbook/tree/legacy). From d3f9131af48e1d09ae8c379b65c579343550da9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Berg=C3=A9?= Date: Mon, 25 Mar 2024 21:53:24 +0100 Subject: [PATCH 06/19] Support images & files in synced blocks (#2234) --- bun.lockb | Bin 468747 -> 469147 bytes package.json | 2 +- .../DocumentView/BlockSyncedBlock.tsx | 23 +++++++++++---- src/components/DocumentView/Drawing.tsx | 2 +- src/components/DocumentView/File.tsx | 11 ++----- src/components/DocumentView/Images.tsx | 4 +-- src/components/DocumentView/InlineImage.tsx | 6 ++-- .../DocumentView/Table/RecordCard.tsx | 2 +- src/components/PageBody/PageCover.tsx | 3 +- src/lib/references.ts | 27 +++++++++++++++--- 10 files changed, 53 insertions(+), 27 deletions(-) diff --git a/bun.lockb b/bun.lockb index 2d949319652fd5e48e534a65f9a6f8e2da097e1d..447149b32e4dee8b665494e91ae5f2bb39bca4b4 100755 GIT binary patch delta 2806 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 K*SjNJ{`? { + if (!syncedBlock?.files) { + return context.resolveContentRef(ref, options); + } + const result = resolveContentRefWithFiles(syncedBlock.files, ref); + if (result !== undefined) { + return result; + } + return context.resolveContentRef(ref, options); + }, + }} style={style} /> ); diff --git a/src/components/DocumentView/Drawing.tsx b/src/components/DocumentView/Drawing.tsx index b4adf477a..1b0d83d40 100644 --- a/src/components/DocumentView/Drawing.tsx +++ b/src/components/DocumentView/Drawing.tsx @@ -19,7 +19,7 @@ export async function Drawing(props: BlockProps) { sources={{ light: { src: resolved.href, - size: resolved.fileDimensions, + size: resolved.file?.dimensions, }, }} alt="Drawing" diff --git a/src/components/DocumentView/File.tsx b/src/components/DocumentView/File.tsx index f86d063af..e4b99ed69 100644 --- a/src/components/DocumentView/File.tsx +++ b/src/components/DocumentView/File.tsx @@ -4,7 +4,6 @@ import IconImage from '@geist-ui/icons/image'; import IconPaperClip from '@geist-ui/icons/paperclip'; import { DocumentBlockFile } from '@gitbook/api'; -import { getRevisionFile } from '@/lib/api'; import { tcls } from '@/lib/tailwind'; import { BlockProps } from './Block'; @@ -12,13 +11,9 @@ import { BlockProps } from './Block'; export async function File(props: BlockProps) { const { block, context, style } = props; - const file = context.content - ? await getRevisionFile( - context.content.spaceId, - context.content.revisionId, - block.data.ref.file, - ) - : null; + const contentRef = await context.resolveContentRef(block.data.ref); + const file = contentRef?.file; + if (!file) { return null; } diff --git a/src/components/DocumentView/Images.tsx b/src/components/DocumentView/Images.tsx index 1bb7d67f3..a9595dd98 100644 --- a/src/components/DocumentView/Images.tsx +++ b/src/components/DocumentView/Images.tsx @@ -89,12 +89,12 @@ async function ImageBlock(props: { sources={{ light: { src: src.href, - size: src.fileDimensions, + size: src.file?.dimensions, }, dark: darkSrc ? { src: darkSrc.href, - size: darkSrc.fileDimensions, + size: darkSrc.file?.dimensions, } : null, }} diff --git a/src/components/DocumentView/InlineImage.tsx b/src/components/DocumentView/InlineImage.tsx index c3c35fbc4..799af9bcb 100644 --- a/src/components/DocumentView/InlineImage.tsx +++ b/src/components/DocumentView/InlineImage.tsx @@ -32,12 +32,12 @@ export async function InlineImage(props: InlineProps) { sources={{ light: { src: src.href, - size: src.fileDimensions, + size: src.file?.dimensions, }, dark: darkSrc ? { src: darkSrc.href, - size: darkSrc.fileDimensions, + size: darkSrc.file?.dimensions, } : null, }} @@ -55,7 +55,7 @@ async function getImageSizes(size: 'original' | 'line', src: ResolvedContentRef) switch (size) { case 'line': { const imageSize = - src.fileDimensions ?? + src.file?.dimensions ?? (await getImageSize(src.href, { dpr: 3, })); diff --git a/src/components/DocumentView/Table/RecordCard.tsx b/src/components/DocumentView/Table/RecordCard.tsx index c79f772b8..7658891d6 100644 --- a/src/components/DocumentView/Table/RecordCard.tsx +++ b/src/components/DocumentView/Table/RecordCard.tsx @@ -52,7 +52,7 @@ export async function RecordCard( sources={{ light: { src: cover.href, - size: cover.fileDimensions, + size: cover.file?.dimensions, }, }} sizes={[ diff --git a/src/components/PageBody/PageCover.tsx b/src/components/PageBody/PageCover.tsx index 3cca60f0a..108732639 100644 --- a/src/components/PageBody/PageCover.tsx +++ b/src/components/PageBody/PageCover.tsx @@ -5,7 +5,6 @@ import { ContentRefContext, resolveContentRef } from '@/lib/references'; import { tcls } from '@/lib/tailwind'; import defaultPageCover from './default-page-cover.svg'; -import { PAGE_COVER_HEIGHT } from '../layout'; const PAGE_COVER_SIZE: ImageSize = { width: 1990, height: 480 }; @@ -43,7 +42,7 @@ export async function PageCover(props: { light: resolved ? { src: resolved.href, - size: resolved.fileDimensions, + size: resolved.file?.dimensions, } : { src: defaultPageCover.src, diff --git a/src/lib/references.ts b/src/lib/references.ts index 8c5a8bdaf..b7f6771f9 100644 --- a/src/lib/references.ts +++ b/src/lib/references.ts @@ -1,4 +1,4 @@ -import { ContentRef, Revision, RevisionPageDocument, Space } from '@gitbook/api'; +import { ContentRef, Revision, RevisionFile, RevisionPageDocument, Space } from '@gitbook/api'; import assertNever from 'assert-never'; import { @@ -24,8 +24,8 @@ export interface ResolvedContentRef { href: string; /** True if the content ref is active */ active: boolean; - /** Image size, if the reference is a image file */ - fileDimensions?: { width: number; height: number }; + /** File, if the reference is a file */ + file?: RevisionFile; } export interface ContentRefContext extends PageHrefContext { @@ -90,7 +90,7 @@ export async function resolveContentRef( href: file.downloadURL, text: file.name, active: false, - fileDimensions: file.dimensions, + file, }; } else { return null; @@ -251,3 +251,22 @@ async function resolveContentRefInSpace(spaceId: string, contentRef: ContentRef) baseUrl, }); } + +export function resolveContentRefWithFiles( + files: RevisionFile[], + contentRef: ContentRef, +): ResolvedContentRef | null | undefined { + if (contentRef.kind === 'file') { + const file = files.find((file) => file.id === contentRef.file); + if (file) { + return { + href: file.downloadURL, + text: file.name, + active: false, + file, + }; + } + return null; + } + return undefined; +} From 0d999067743fa68d8138360fa4481ba204320486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Tue, 26 Mar 2024 10:30:38 +0000 Subject: [PATCH 07/19] Fix crash with concurrent loading of shiki syntaxes (#2235) --- .../DocumentView/CodeBlock/highlight.test.ts | 54 ++++----- .../DocumentView/CodeBlock/highlight.ts | 19 +-- src/lib/async.test.ts | 87 +++++++++++++- src/lib/async.ts | 109 ++++++++++++++++++ 4 files changed, 233 insertions(+), 36 deletions(-) diff --git a/src/components/DocumentView/CodeBlock/highlight.test.ts b/src/components/DocumentView/CodeBlock/highlight.test.ts index 2aef96b67..faed7f48d 100644 --- a/src/components/DocumentView/CodeBlock/highlight.test.ts +++ b/src/components/DocumentView/CodeBlock/highlight.test.ts @@ -35,6 +35,33 @@ it('should parse plain code', async () => { ]); }); +it('should parse different code in parallel', async () => { + await Promise.all( + ['shell', 'scss', 'markdown', 'less', 'scss', 'css', 'scss', 'yaml'].map(async (syntax) => + highlight({ + object: 'block', + type: 'code', + data: { + syntax: syntax, + }, + nodes: [ + { + object: 'block', + type: 'code-line', + data: {}, + nodes: [ + { + object: 'text', + leaves: [{ object: 'leaf', marks: [], text: 'Hello world' }], + }, + ], + }, + ], + }), + ), + ); +}); + it('should parse a multilines plain code', async () => { const tokens = await highlight({ object: 'block', @@ -561,45 +588,30 @@ it('should support multiple code tokens in an annotation', async () => { type: 'shiki', token: { content: 'const', - color: '#000007', - start: 0, - end: 5, }, }, { type: 'shiki', token: { content: ' ', - color: '#000001', - start: 5, - end: 6, }, }, { type: 'shiki', token: { content: 'a', - color: '#000004', - start: 6, - end: 7, }, }, { type: 'shiki', token: { content: ' ', - color: '#000001', - start: 7, - end: 8, }, }, { type: 'shiki', token: { content: '=', - color: '#000007', - start: 8, - end: 9, }, }, { @@ -613,27 +625,18 @@ it('should support multiple code tokens in an annotation', async () => { type: 'shiki', token: { content: 'hello', - color: '#000004', - start: 9, - end: 14, }, }, { type: 'shiki', token: { content: '.world', - color: '#000009', - start: 14, - end: 20, }, }, { type: 'shiki', token: { content: '(', - color: '#000001', - start: 20, - end: 21, }, }, ], @@ -642,9 +645,6 @@ it('should support multiple code tokens in an annotation', async () => { type: 'shiki', token: { content: ');', - color: '#000001', - start: 21, - end: 23, }, }, ], diff --git a/src/components/DocumentView/CodeBlock/highlight.ts b/src/components/DocumentView/CodeBlock/highlight.ts index db9adcdab..7525d4f27 100644 --- a/src/components/DocumentView/CodeBlock/highlight.ts +++ b/src/components/DocumentView/CodeBlock/highlight.ts @@ -9,7 +9,7 @@ import { // @ts-ignore - onigWasm is a Wasm module import onigWasm from 'shiki/onig.wasm?module'; -import { singleton, singletonMap } from '@/lib/async'; +import { asyncMutexFunction, singleton } from '@/lib/async'; import { getNodeText } from '@/lib/document'; import { trace } from '@/lib/tracing'; @@ -314,10 +314,13 @@ const loadHighlighter = singleton(async () => { }); }); -const loadHighlighterLanguage = singletonMap(async (lang: keyof typeof bundledLanguages) => { - const highlighter = await loadHighlighter(); - await trace( - `highlighting.loadLanguage(${lang})`, - async () => await highlighter.loadLanguage(lang), - ); -}); +const loadLanguagesMutex = asyncMutexFunction(); +async function loadHighlighterLanguage(lang: keyof typeof bundledLanguages) { + await loadLanguagesMutex.runBlocking(async () => { + const highlighter = await loadHighlighter(); + await trace( + `highlighting.loadLanguage(${lang})`, + async () => await highlighter.loadLanguage(lang), + ); + }); +} diff --git a/src/lib/async.test.ts b/src/lib/async.test.ts index ed7c1cd13..5891f69f9 100644 --- a/src/lib/async.test.ts +++ b/src/lib/async.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'bun:test'; -import { race } from './async'; +import { asyncMutexFunction, race } from './async'; import { flushWaitUntil } from './waitUntil'; describe('race', () => { @@ -380,3 +380,88 @@ describe('race', () => { }); }); }); + +describe('asyncMutexFunction', () => { + describe('self', () => { + it('should run only once', async () => { + let running = 0; + + const fn = async () => { + running += 1; + + await new Promise((resolve) => setTimeout(resolve, 10)); + }; + + const mutexFn = asyncMutexFunction(); + + await Promise.all([ + mutexFn(fn), + mutexFn(fn), + mutexFn(fn), + mutexFn(fn), + mutexFn(fn), + mutexFn(fn), + ]); + + expect(running).toBe(1); + }); + }); + + describe('runBlocking', () => { + it('should run only one function at a time', async () => { + let running = 0; + let maxRunning = 0; + + const fn = async () => { + running += 1; + maxRunning = Math.max(running, maxRunning); + + await new Promise((resolve) => setTimeout(resolve, 10)); + + running -= 1; + }; + + const mutexFn = asyncMutexFunction(); + + await Promise.all([ + mutexFn.runBlocking(fn), + mutexFn.runBlocking(fn), + mutexFn.runBlocking(fn), + mutexFn.runBlocking(fn), + mutexFn.runBlocking(fn), + mutexFn.runBlocking(fn), + ]); + + expect(maxRunning).toBe(1); + }); + }); + + describe('runAfter', () => { + it('should run only one function at a time', async () => { + let running = 0; + let maxRunning = 0; + + const fn = async () => { + running += 1; + maxRunning = Math.max(running, maxRunning); + + await new Promise((resolve) => setTimeout(resolve, 10)); + + running -= 1; + }; + + const mutexFn = asyncMutexFunction(); + + await Promise.all([ + mutexFn.runAfter(fn), + mutexFn.runAfter(fn), + mutexFn.runAfter(fn), + mutexFn.runAfter(fn), + mutexFn.runAfter(fn), + mutexFn.runAfter(fn), + ]); + + expect(maxRunning).toBe(1); + }); + }); +}); diff --git a/src/lib/async.ts b/src/lib/async.ts index 26b875f28..e99cb26f7 100644 --- a/src/lib/async.ts +++ b/src/lib/async.ts @@ -361,3 +361,112 @@ export function batch( }); }; } + +type MutexOperationOptions = { + /** + * If true, fail the operation if a pending operation on the mutex fails. + * Defaults to true. + */ + failOnMutexError?: boolean; +}; + +export type AsyncMutexFunction = ((fn: () => Promise) => Promise) & { + /** + * Wait for a pending operation to complete. + */ + wait: () => Promise; + + /** + * Execute a function after the previous operation completes. + */ + runAfter: (fn: () => Promise, options?: MutexOperationOptions) => Promise; + + /** + * Execute a function that blocks the mutex, but does not influence the return value of the mutex. + */ + runBlocking: ( + fn: () => Promise, + options?: MutexOperationOptions, + ) => Promise; +}; + +/** + * Creates a function that will only call the given function once at a time. + */ +export function asyncMutexFunction(): AsyncMutexFunction { + let pending: + | undefined + | { kind: 'value'; promise: Promise } + | { kind: 'blocking'; promise: Promise }; + + const mutex: AsyncMutexFunction = async (fn) => { + if (pending?.kind === 'value') { + return pending.promise; + } + + while (pending) { + await pending.promise; + } + + const promise = fn(); + pending = { kind: 'value', promise }; + try { + const result = await promise; + return result; + } finally { + pending = undefined; + } + }; + + mutex.wait = async () => { + return pending?.promise; + }; + + mutex.runBlocking = async (fn, options) => { + const failOnMutexError = options?.failOnMutexError ?? true; + + while (pending) { + try { + await pending.promise; + } catch (err) { + if (failOnMutexError) { + throw err; + } + } + } + + const promise = fn(); + pending = { kind: 'blocking', promise }; + try { + const result = await promise; + return result; + } finally { + pending = undefined; + } + }; + + mutex.runAfter = async (fn, options) => { + const failOnMutexError = options?.failOnMutexError ?? true; + + while (pending) { + try { + await pending.promise; + } catch (err) { + if (failOnMutexError) { + throw err; + } + } + } + + const promise = fn(); + pending = { kind: 'value', promise }; + try { + const result = await pending.promise; + return result; + } finally { + pending = undefined; + } + }; + + return mutex; +} From ee7a74fe433a2082422a983455cf6b8b39dc0575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Tue, 26 Mar 2024 10:53:32 +0000 Subject: [PATCH 08/19] Fix VA redirects for nested paths (#2236) --- src/middleware.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/middleware.ts b/src/middleware.ts index c9d6e5164..a3ba008fa 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -569,7 +569,23 @@ async function lookupSpaceByAPI( } if ('redirect' in data) { - if (alternative.url === url.toString()) { + if (alternative.primary) { + // Append the path to the redirect URL if it's a VA redirect + // because we might have matched a shorter path and the redirect is relative to it + if (alternative.extraPath) { + const redirect = new URL(data.redirect); + if (redirect.searchParams.has('location')) { + redirect.searchParams.set( + 'location', + joinPath( + redirect.searchParams.get('location') ?? '', + alternative.extraPath, + ), + ); + data.redirect = redirect.toString(); + } + } + return data; } From a6789513dcd73241484f5b2076bb771ce25eefb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Tue, 26 Mar 2024 12:45:23 +0000 Subject: [PATCH 09/19] Fix alignment and border around small images in a grid with captions (#2229) --- e2e/pages.spec.ts | 1 + src/components/DocumentView/Caption.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/e2e/pages.spec.ts b/e2e/pages.spec.ts index 4b1c70dcd..fc91608ab 100644 --- a/e2e/pages.spec.ts +++ b/e2e/pages.spec.ts @@ -125,6 +125,7 @@ const testCases: TestsCase[] = [ name: 'Images', url: 'blocks/block-images', run: waitForCookiesDialog, + fullPage: true, }, { name: 'Inline Images', diff --git a/src/components/DocumentView/Caption.tsx b/src/components/DocumentView/Caption.tsx index 61b0ffcbd..df6df549e 100644 --- a/src/components/DocumentView/Caption.tsx +++ b/src/components/DocumentView/Caption.tsx @@ -43,6 +43,7 @@ export function Caption( 'dark:after:border-light/1', 'dark:after:mix-blend-plus-lighter', 'after:pointer-events-none', + 'w-fit', ], style, } = props; @@ -60,7 +61,7 @@ export function Caption( return ( -

{children}
+
{children}
Date: Tue, 26 Mar 2024 13:02:17 +0000 Subject: [PATCH 10/19] Fix OpenAPI tag in aside when text is long (#2237) --- src/components/DocumentView/OpenAPI/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DocumentView/OpenAPI/style.css b/src/components/DocumentView/OpenAPI/style.css index 07ddabba2..1f7bcde36 100644 --- a/src/components/DocumentView/OpenAPI/style.css +++ b/src/components/DocumentView/OpenAPI/style.css @@ -23,7 +23,7 @@ /** Method tag */ .openapi-method { - @apply rounded font-mono font-bold text-[11.5px] px-1 py-0.5 mr-2 text-dark-1/8 leading-tight align-middle inline-flex ring-1 ring-inset ring-dark-1/1 dark:ring-light-1/1; + @apply rounded font-mono font-bold text-[11.5px] px-1 py-0.5 mr-2 text-dark-1/8 leading-tight align-middle inline-flex ring-1 ring-inset ring-dark-1/1 dark:ring-light-1/1 whitespace-nowrap; } .openapi-method-get { From 73d806a796955950ec6070b398ad7abe6c175343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Tue, 26 Mar 2024 15:46:19 +0000 Subject: [PATCH 11/19] Set z-index on annotation popover (#2238) --- e2e/pages.spec.ts | 8 ++++++++ .../DocumentView/Annotation/AnnotationPopover.tsx | 2 ++ 2 files changed, 10 insertions(+) diff --git a/e2e/pages.spec.ts b/e2e/pages.spec.ts index fc91608ab..5c329f7e3 100644 --- a/e2e/pages.spec.ts +++ b/e2e/pages.spec.ts @@ -196,6 +196,14 @@ const testCases: TestsCase[] = [ name: 'Math', url: 'blocks/math', }, + { + name: 'Annotations', + url: 'blocks/annotations', + run: async (page) => { + await page.waitForSelector('[data-testid="annotation-button"]'); + await page.click('[data-testid="annotation-button"]'); + }, + }, ], }, { diff --git a/src/components/DocumentView/Annotation/AnnotationPopover.tsx b/src/components/DocumentView/Annotation/AnnotationPopover.tsx index b2d17a7e3..3b6e776f0 100644 --- a/src/components/DocumentView/Annotation/AnnotationPopover.tsx +++ b/src/components/DocumentView/Annotation/AnnotationPopover.tsx @@ -14,6 +14,7 @@ export function AnnotationPopover(props: { children: React.ReactNode; body: Reac

JG9IA>= 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 { if (isSearchOpened) { - document.body.classList.add('search-open'); document.body.style.overflow = 'hidden'; } return () => { - document.body.classList.remove('search-open'); document.body.style.overflow = 'auto'; }; }, [isSearchOpened]); diff --git a/src/components/utils/Image.tsx b/src/components/utils/Image.tsx index 5575c57ce..6fad1371b 100644 --- a/src/components/utils/Image.tsx +++ b/src/components/utils/Image.tsx @@ -5,7 +5,7 @@ import { checkIsHttpURL, getImageSize, getResizedImageURL } from '@/lib/images'; import { ClassValue, tcls } from '@/lib/tailwind'; import { PolymorphicComponentProp } from './types'; -import { Zoom } from './Zoom'; +import { ZoomImage } from './ZoomImage'; export type ImageSize = { width: number; height: number }; @@ -87,6 +87,10 @@ interface ImageCommonProps { inlineStyle?: React.CSSProperties; } +interface ImgDOMPropsWithSrc extends React.ComponentPropsWithoutRef<'img'> { + src: string; +} + /** * Render an image that will be swapped depending on the theme. * We don't use the `next/image` component because we need to load images from external sources, @@ -271,16 +275,14 @@ async function ImagePicture( }); } - const img = ( - {alt} - ); + const imgProps: ImgDOMPropsWithSrc = { + alt, + style, + loading, + fetchPriority, + ...rest, + ...attrs, + }; - return zoom ? {img} : img; + return zoom ? : {imgProps.alt; } diff --git a/src/components/utils/Zoom.css b/src/components/utils/Zoom.css deleted file mode 100644 index 2d436e393..000000000 --- a/src/components/utils/Zoom.css +++ /dev/null @@ -1,3 +0,0 @@ -html.dark [data-rmiz-modal-overlay='visible'] { - background-color: rgb(var(--dark-base)); -} diff --git a/src/components/utils/Zoom.tsx b/src/components/utils/Zoom.tsx deleted file mode 100644 index 99fa40db8..000000000 --- a/src/components/utils/Zoom.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client'; - -import ReactZoom, { UncontrolledProps } from 'react-medium-image-zoom'; -import 'react-medium-image-zoom/dist/styles.css'; -import './Zoom.css'; - -/** - * Client component to zoom on an image. - * See https://github.com/rpearce/react-medium-image-zoom - */ -export function Zoom(props: UncontrolledProps) { - return ; -} diff --git a/src/components/utils/ZoomImage.module.css b/src/components/utils/ZoomImage.module.css new file mode 100644 index 000000000..d47fa51e4 --- /dev/null +++ b/src/components/utils/ZoomImage.module.css @@ -0,0 +1,19 @@ +html:has(.zoomModal) { + overflow: hidden; +} + +.zoomImg { + cursor: zoom-in; +} + +.zoomImageActive { + view-transition-name: zoom-image; +} + +.zoomModal { +} + +.zoomModal img { + view-transition-name: zoom-image; + cursor: zoom-out; +} diff --git a/src/components/utils/ZoomImage.tsx b/src/components/utils/ZoomImage.tsx new file mode 100644 index 000000000..73d0b26cd --- /dev/null +++ b/src/components/utils/ZoomImage.tsx @@ -0,0 +1,280 @@ +'use client'; + +import IconMinimize from '@geist-ui/icons/minimize'; +import classNames from 'classnames'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +import { tcls } from '@/lib/tailwind'; + +import styles from './ZoomImage.module.css'; + +/** + * Replacement for an tag that allows zooming. + * The implementation uses the experimental View Transition API in Chrome for a smooth transition. + */ +export function ZoomImage( + props: React.ComponentPropsWithoutRef<'img'> & { + src: string; + }, +) { + const { src, alt, width } = props; + + const imgRef = React.useRef(null); + const [zoomable, setZoomable] = React.useState(false); + const [active, setActive] = React.useState(false); + const [opened, setOpened] = React.useState(false); + const [placeholderRect, setPlaceholderRect] = React.useState(null); + + // Only allow zooming when image will not actually be larger and on mobile + React.useEffect(() => { + if (isTouchDevice()) { + return; + } + + const imageWidth = typeof width === 'number' ? width : 0; + let viewWidth = 0; + + const mediaQueryList = window.matchMedia('(min-width: 768px)'); + const resizeObserver = + typeof ResizeObserver !== 'undefined' + ? new ResizeObserver((entries) => { + const imgEntry = entries[0]; + + // Since the image is removed from the DOM when the modal is opened, + // We only care when the size is defined. + if (imgEntry && imgEntry.contentRect.width !== 0) { + viewWidth = entries[0]?.contentRect.width; + setPlaceholderRect(entries[0].contentRect); + onChange(); + } + }) + : null; + + const onChange = () => { + if (!mediaQueryList.matches) { + // Don't allow zooming on mobile + setZoomable(false); + } else if (resizeObserver && imageWidth && viewWidth && imageWidth <= viewWidth) { + // Image can't be zoomed if it's already rendered as it's largest size + setZoomable(false); + } else { + setZoomable(true); + } + }; + + mediaQueryList.addEventListener('change', onChange); + if (imgRef.current) { + resizeObserver?.observe(imgRef.current); + } + + if (!resizeObserver) { + // When resizeObserver is available, it'll take care of calling the changelog as soon as the element is observed + onChange(); + } + + return () => { + resizeObserver?.disconnect(); + mediaQueryList.removeEventListener('change', onChange); + }; + }, [imgRef, width]); + + // Preload the image that will be displayed in the modal + if (zoomable) { + ReactDOM.preload(src, { + as: 'image', + }); + } + const preloadImage = React.useCallback( + (onLoad?: () => void) => { + const image = new Image(); + image.src = src; + + image.onload = () => { + onLoad?.(); + }; + }, + [src], + ); + + // When closing the modal, animate the transition back to the original image + const onClose = React.useCallback(() => { + startViewTransition( + () => { + setOpened(false); + }, + () => { + setActive(false); + }, + ); + }, []); + + return ( + <> + {opened ? ( + <> + {placeholderRect ? ( + // Placeholder to keep the layout stable when the image is removed from the DOM + + ) : null} + + {ReactDOM.createPortal( + , + document.body, + )} + + ) : ( + // When zooming, remove the image from the DOM to let the browser animates it with View Transition. + {alt { + if (zoomable) { + preloadImage(); + } + }} + onClick={() => { + if (!zoomable) { + return; + } + + // Preload the image before opening the modal to ensure the animation is smooth + preloadImage(() => { + const change = () => { + setOpened(true); + }; + + ReactDOM.flushSync(() => setActive(true)); + startViewTransition(change); + }); + }} + className={classNames( + props.className, + zoomable ? styles.zoomImg : null, + active ? styles.zoomImageActive : null, + )} + /> + )} + + ); +} + +function ZoomImageModal(props: { src: string; alt: string; onClose: () => void }) { + const { src, alt, onClose } = props; + + const buttonRef = React.useRef(null); + + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + onClose(); + } + }; + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [onClose]); + + React.useEffect(() => { + buttonRef.current?.focus(); + }, []); + + return ( +