diff --git a/.changeset/healthy-spoons-itch.md b/.changeset/healthy-spoons-itch.md new file mode 100644 index 000000000..5545d3e3d --- /dev/null +++ b/.changeset/healthy-spoons-itch.md @@ -0,0 +1,5 @@ +--- +'gitbook': minor +--- + +Replace all icons, previously imported from Geist, by new package `@gitbook/icons` diff --git a/.changeset/lovely-donkeys-deny.md b/.changeset/lovely-donkeys-deny.md new file mode 100644 index 000000000..cfd37a7ec --- /dev/null +++ b/.changeset/lovely-donkeys-deny.md @@ -0,0 +1,5 @@ +--- +'@gitbook/icons': minor +--- + +Initial release for `@gitbook/icons`, a package of content icons to be used for pages, documents, etc. diff --git a/.changeset/violet-beers-matter.md b/.changeset/violet-beers-matter.md new file mode 100644 index 000000000..568462c65 --- /dev/null +++ b/.changeset/violet-beers-matter.md @@ -0,0 +1,5 @@ +--- +'@gitbook/react-math': minor +--- + +Export binary `gitbook-math` to copy assets to a directory diff --git a/.github/actions/setup-playwright/action.yml b/.github/actions/setup-playwright/action.yml index 5796e8d63..3cbbd7ff5 100644 --- a/.github/actions/setup-playwright/action.yml +++ b/.github/actions/setup-playwright/action.yml @@ -7,7 +7,7 @@ runs: - name: 🏗 Prepare Playwright env shell: bash run: | - PLAYWRIGHT_VERSION=$(npm ls --json @playwright/test | jq --raw-output '.dependencies["@playwright/test"].version') + PLAYWRIGHT_VERSION=$(npm ls --json @playwright/test | jq --raw-output '.dependencies["gitbook"].dependencies["@playwright/test"].version') echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV # Cache browser binaries, cache key is based on Playwright version and OS @@ -24,6 +24,7 @@ runs: - name: 🏗 Install Playwright browser binaries & OS dependencies if: steps.playwright-cache.outputs.cache-hit != 'true' shell: bash + working-directory: packages/gitbook run: | bun x playwright install --with-deps chromium @@ -31,5 +32,6 @@ runs: - name: 🏗 Install Playwright OS dependencies if: steps.playwright-cache.outputs.cache-hit == 'true' shell: bash + working-directory: packages/gitbook run: | bun x playwright install-deps diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 363ad1239..b63c77b0b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,7 +4,8 @@ on: push: branches: - main - +env: + NPMRC_FONT_AWESOME_TOKEN: ${{ secrets.NPMRC_FONT_AWESOME_TOKEN }} jobs: deploy: name: Deploy to Cloudflare Pages diff --git a/.gitignore b/.gitignore index 72721b517..113a36d4e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ yarn-error.log* # Vercel .vercel +# Env files +.env.local diff --git a/.prettierignore b/.prettierignore index 40538f50d..a6fb41b14 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,3 +4,7 @@ # Generated packages/emoji-codepoints/index.ts packages/gitbook/public/~gitbook/static/ +packages/icons/src/data/*.json + +# Build files +dist/ diff --git a/bun.lockb b/bun.lockb index da66de451..d6a622ac6 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 000000000..88a0a88dd --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[install.scopes] +"awesome.me" = { token = "$NPMRC_FONT_AWESOME_TOKEN", url = "https://npm.fontawesome.com/" } diff --git a/package.json b/package.json index b37c6cd24..fee7a1bdd 100644 --- a/package.json +++ b/package.json @@ -8,13 +8,14 @@ "build": "turbo run build", "build:cloudflare": "turbo run build:cloudflare", "lint": "turbo run lint", + "lint:fix": "turbo run lint -- --fix", "typecheck": "turbo run typecheck", "format": "prettier ./ --ignore-unknown --write", "format:check": "prettier ./ --ignore-unknown --list-different", "unit": "turbo run unit", "e2e": "turbo run e2e", "changeset": "changeset", - "release": "turbo run build && changeset publish" + "release": "turbo run release && changeset publish" }, "workspaces": [ "packages/*" @@ -25,6 +26,6 @@ "turbo": "^2.0.9" }, "patchedDependencies": { - "@vercel/next@4.0.15": "patches/@vercel%2Fnext@4.0.15.patch" + "@vercel/next@4.3.6": "patches/@vercel%2Fnext@4.3.6.patch" } } diff --git a/packages/emoji-codepoints/.gitignore b/packages/emoji-codepoints/.gitignore index 1e21beece..849ddff3b 100644 --- a/packages/emoji-codepoints/.gitignore +++ b/packages/emoji-codepoints/.gitignore @@ -1 +1 @@ -index.ts +dist/ diff --git a/packages/emoji-codepoints/build.ts b/packages/emoji-codepoints/build.ts index 792978996..fa39e48b1 100644 --- a/packages/emoji-codepoints/build.ts +++ b/packages/emoji-codepoints/build.ts @@ -1,4 +1,5 @@ -import fs from 'fs'; +import fs from 'node:fs'; +import path from 'node:path'; import emojisRaws from 'emoji-assets/emoji.json'; interface EmojiData { @@ -20,7 +21,8 @@ Object.entries(emojis).forEach(([key, value]) => { } }); +fs.mkdirSync(path.resolve(__dirname, 'dist'), { recursive: true }); fs.writeFileSync( - 'index.ts', + path.resolve(__dirname, 'dist/index.ts'), `export const emojiCodepoints: Record = ${JSON.stringify(output, null, 4)};`, ); diff --git a/packages/emoji-codepoints/package.json b/packages/emoji-codepoints/package.json index eb4fa050f..b023a384e 100644 --- a/packages/emoji-codepoints/package.json +++ b/packages/emoji-codepoints/package.json @@ -3,12 +3,12 @@ "description": "Optimized mapping of codepoints to the fully qualified emoji codepoints", "version": "0.2.0", "private": true, - "exports": "./index.ts", + "exports": "./dist/index.ts", "dependencies": {}, "devDependencies": { "emoji-assets": "^8.0.0" }, "scripts": { - "postinstall": "bun ./build.ts" + "generate": "bun ./build.ts" } } diff --git a/packages/gitbook/e2e/pages.spec.ts b/packages/gitbook/e2e/pages.spec.ts index 01999095f..14c96bd44 100644 --- a/packages/gitbook/e2e/pages.spec.ts +++ b/packages/gitbook/e2e/pages.spec.ts @@ -349,6 +349,11 @@ const testCases: TestsCase[] = [ url: 'blocks/embeds', fullPage: true, }, + { + name: 'Page links', + url: 'blocks/page-links', + fullPage: true, + }, { name: 'Annotations', url: 'blocks/annotations', @@ -408,7 +413,6 @@ const testCases: TestsCase[] = [ { name: 'Valid link', url: 'TGs8PkF4GWVtbmPnWhYL/', - run: waitForCookiesDialog, }, { name: 'Invalid link', diff --git a/packages/gitbook/next.config.js b/packages/gitbook/next.config.js index cfcc05037..b5f1f42d6 100644 --- a/packages/gitbook/next.config.js +++ b/packages/gitbook/next.config.js @@ -7,6 +7,8 @@ module.exports = withSentryConfig( SENTRY_DSN: process.env.SENTRY_DSN ?? '', SENTRY_ENVIRONMENT: process.env.SENTRY_ENVIRONMENT ?? 'development', GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX, + GITBOOK_ICONS_URL: process.env.GITBOOK_ICONS_URL, + GITBOOK_ICONS_TOKEN: process.env.GITBOOK_ICONS_TOKEN, }, webpack(config, { dev, webpack }) { diff --git a/packages/gitbook/package.json b/packages/gitbook/package.json index 9d724d952..daca9b17c 100644 --- a/packages/gitbook/package.json +++ b/packages/gitbook/package.json @@ -3,7 +3,7 @@ "version": "0.3.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "env-cmd -f ../../.env.local next dev", "build": "next build", "build:cloudflare": "next-on-pages", "start": "next start", @@ -11,14 +11,16 @@ "typecheck": "tsc --noEmit", "e2e": "playwright test", "unit": "bun test {src,packages}", - "postinstall": "rm -rf ./public/~gitbook/static/mathjax@3.2.2 && mkdir -p ./public/~gitbook/static/ && cp -R ../../node_modules/mathjax/es5 ./public/~gitbook/static/mathjax@3.2.2" + "generate": "gitbook-icons ./public/~gitbook/static/icons custom-icons && gitbook-math ./public/~gitbook/static/math", + "copy:icons": "gitbook-icons ./public/~gitbook/static/icons" }, "dependencies": { - "@geist-ui/icons": "^1.0.2", "@gitbook/api": "^0.58.0", + "@gitbook/icons": "workspace:*", "@gitbook/react-math": "workspace:*", "@gitbook/react-openapi": "workspace:*", "@gitbook/react-contentkit": "workspace:*", + "@gitbook/emoji-codepoints": "workspace:*", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-popover": "^1.0.7", "@sentry/nextjs": "^7.94.1", @@ -37,7 +39,7 @@ "katex": "^0.16.9", "mathjax": "^3.2.2", "memoizee": "^0.4.15", - "next": "^14.1.3", + "next": "^14.2.5", "next-themes": "^0.2.1", "nuqs": "^1.17.4", "object-hash": "^3.0.0", @@ -64,8 +66,8 @@ "devDependencies": { "@argos-ci/playwright": "^2.0.0", "@playwright/test": "^1.42.1", - "@cloudflare/next-on-pages": "^1.11.3", - "@cloudflare/workers-types": "^4.20231218.0", + "@cloudflare/next-on-pages": "^1.12.1", + "@cloudflare/workers-types": "^4.20240725.0", "@types/js-cookie": "^3.0.6", "@types/jsontoxml": "^1.0.5", "@types/jsonwebtoken": "^9.0.6", @@ -84,6 +86,7 @@ "postcss": "^8", "psi": "^4.1.0", "tailwindcss": "^3.4.0", - "typescript": "^5.5.3" + "typescript": "^5.5.3", + "env-cmd": "^10.1.0" } } diff --git a/packages/gitbook/src/app/(space)/(core)/~gitbook/pdf/PageControlButtons.tsx b/packages/gitbook/src/app/(space)/(core)/~gitbook/pdf/PageControlButtons.tsx index 871687736..485c3ceb1 100644 --- a/packages/gitbook/src/app/(space)/(core)/~gitbook/pdf/PageControlButtons.tsx +++ b/packages/gitbook/src/app/(space)/(core)/~gitbook/pdf/PageControlButtons.tsx @@ -1,6 +1,6 @@ 'use client'; -import { AlertTriangle } from '@geist-ui/icons'; +import { Icon } from '@gitbook/icons'; import React from 'react'; import { useScrollActiveId } from '@/components/hooks'; @@ -105,7 +105,10 @@ export function PageControlButtons(props: { 'max-w-sm', )} > - {' '} + {' '}
{t(language, 'pdf_limit_reached', total, pageIds.length)}
diff --git a/packages/gitbook/src/app/(space)/(core)/~gitbook/pdf/page.tsx b/packages/gitbook/src/app/(space)/(core)/~gitbook/pdf/page.tsx index fdc4aa148..331cff3e0 100644 --- a/packages/gitbook/src/app/(space)/(core)/~gitbook/pdf/page.tsx +++ b/packages/gitbook/src/app/(space)/(core)/~gitbook/pdf/page.tsx @@ -1,13 +1,12 @@ -import { ArrowLeft, Printer } from '@geist-ui/icons'; import { CustomizationSettings, Revision, RevisionPageDocument, RevisionPageGroup, - RevisionPageLink, SiteCustomizationSettings, Space, } from '@gitbook/api'; +import { Icon } from '@gitbook/icons'; import { Metadata } from 'next'; import { notFound } from 'next/navigation'; import * as React from 'react'; @@ -116,7 +115,7 @@ export default async function PDFHTMLOutput(props: { searchParams: { [key: strin 'border', )} > - +
) : null} @@ -142,7 +141,7 @@ export default async function PDFHTMLOutput(props: { searchParams: { [key: strin 'border', )} > - +
diff --git a/packages/gitbook/src/app/(space)/layout.tsx b/packages/gitbook/src/app/(space)/layout.tsx index 9e2aba412..e9229ee69 100644 --- a/packages/gitbook/src/app/(space)/layout.tsx +++ b/packages/gitbook/src/app/(space)/layout.tsx @@ -5,6 +5,7 @@ import { CustomizationSettings, SiteCustomizationSettings, } from '@gitbook/api'; +import { IconsProvider, IconStyle } from '@gitbook/icons'; import assertNever from 'assert-never'; import colors from 'tailwindcss/colors'; @@ -12,11 +13,13 @@ import { emojiFontClassName } from '@/components/primitives'; import { fonts, ibmPlexMono } from '@/fonts'; import { getSpaceLanguage } from '@/intl/server'; import { getCurrentSiteLayoutData, getSpaceLayoutData } from '@/lib/api'; +import { getStaticFileURL } from '@/lib/assets'; import { hexToRgb, shadesOfColor } from '@/lib/colors'; import { tcls } from '@/lib/tailwind'; import { ClientContexts } from './ClientContexts'; import './globals.css'; +import '@gitbook/icons/style.css'; import { getContentPointer } from './fetch'; /** @@ -105,7 +108,18 @@ export default async function SpaceRootLayout(props: { children: React.ReactNode 'dark:bg-dark', )} > - {children} + + {children} + ); diff --git a/packages/gitbook/src/components/Ads/Ad.tsx b/packages/gitbook/src/components/Ads/Ad.tsx index ef64b4ecc..d6c336b5a 100644 --- a/packages/gitbook/src/components/Ads/Ad.tsx +++ b/packages/gitbook/src/components/Ads/Ad.tsx @@ -1,6 +1,6 @@ 'use client'; -import IconHeart from '@geist-ui/icons/heart'; +import { Icon } from '@gitbook/icons'; import * as React from 'react'; import { ClassValue, tcls } from '@/lib/tailwind'; @@ -188,7 +188,7 @@ function AdBlockerPlaceholder() { )} >
- +

Ad disabled

diff --git a/packages/gitbook/src/components/Cookies/CookiesToast.tsx b/packages/gitbook/src/components/Cookies/CookiesToast.tsx index 509174987..f3110fac9 100644 --- a/packages/gitbook/src/components/Cookies/CookiesToast.tsx +++ b/packages/gitbook/src/components/Cookies/CookiesToast.tsx @@ -1,6 +1,6 @@ 'use client'; -import IconX from '@geist-ui/icons/x'; +import { Icon } from '@gitbook/icons'; import * as React from 'react'; import { Button } from '@/components/primitives'; @@ -92,7 +92,7 @@ export function CookiesToast(props: { privacyPolicy?: string }) { )} title={tString(language, 'cookies_close')} > - +

); } diff --git a/packages/gitbook/src/components/PageAside/PageAside.tsx b/packages/gitbook/src/components/PageAside/PageAside.tsx index cae2de1af..19913c2a6 100644 --- a/packages/gitbook/src/components/PageAside/PageAside.tsx +++ b/packages/gitbook/src/components/PageAside/PageAside.tsx @@ -1,7 +1,3 @@ -import { Menu } from '@geist-ui/icons'; -import DownloadCloud from '@geist-ui/icons/downloadCloud'; -import Github from '@geist-ui/icons/github'; -import Gitlab from '@geist-ui/icons/gitlab'; import { CustomizationSettings, JSONDocument, @@ -10,6 +6,7 @@ import { SiteCustomizationSettings, Space, } from '@gitbook/api'; +import { Icon } from '@gitbook/icons'; import React from 'react'; import urlJoin from 'url-join'; @@ -97,7 +94,7 @@ export async function PageAside(props: { 'py-2', )} > - + {t(language, 'on_this_page')}
- {space.gitSync.installationProvider === 'gitlab' ? ( - - ) : ( - - )} + {t(language, 'edit_on_git', getGitSyncName(space))}
@@ -176,7 +176,7 @@ export async function PageAside(props: { 'dark:text-light/5', )} > - + {t(language, 'pdf_download')} diff --git a/packages/gitbook/src/components/PageBody/PageFooterNavigation.tsx b/packages/gitbook/src/components/PageBody/PageFooterNavigation.tsx index 5532bb135..7d7b33a84 100644 --- a/packages/gitbook/src/components/PageBody/PageFooterNavigation.tsx +++ b/packages/gitbook/src/components/PageBody/PageFooterNavigation.tsx @@ -1,5 +1,3 @@ -import ChevronLeft from '@geist-ui/icons/chevronLeft'; -import ChevronRight from '@geist-ui/icons/chevronRight'; import { CustomizationSettings, Revision, @@ -7,6 +5,7 @@ import { SiteCustomizationSettings, Space, } from '@gitbook/api'; +import { Icon, IconName } from '@gitbook/icons'; import React from 'react'; import { t, getSpaceLanguage } from '@/intl/server'; @@ -44,7 +43,7 @@ export function PageFooterNavigation(props: { > {previous ? ( ; + icon: IconName; label: React.ReactNode; title: string; href: string; reversed?: boolean; }) { - const { icon: IconCo, label, title, href, reversed } = props; + const { icon, label, title, href, reversed } = props; return ( - diff --git a/packages/gitbook/src/components/Search/SearchAskAnswer.tsx b/packages/gitbook/src/components/Search/SearchAskAnswer.tsx index 51499167b..962c42ff6 100644 --- a/packages/gitbook/src/components/Search/SearchAskAnswer.tsx +++ b/packages/gitbook/src/components/Search/SearchAskAnswer.tsx @@ -1,5 +1,4 @@ -import IconBox from '@geist-ui/icons/box'; -import IconSearch from '@geist-ui/icons/search'; +import { Icon } from '@gitbook/icons'; import React from 'react'; import { atom, useRecoilState } from 'recoil'; @@ -187,14 +186,9 @@ function AnswerFollowupQuestions(props: { followupQuestions: string[] }) { ask: true, })} > - path]:[stroke-opacity:0.64]', - )} + {question} @@ -242,15 +236,16 @@ function AnswerSources(props: { href={source.href} prefetch={false} > - {source.title} diff --git a/packages/gitbook/src/components/Search/SearchButton.tsx b/packages/gitbook/src/components/Search/SearchButton.tsx index 461e5ef31..11d23902f 100644 --- a/packages/gitbook/src/components/Search/SearchButton.tsx +++ b/packages/gitbook/src/components/Search/SearchButton.tsx @@ -1,6 +1,6 @@ 'use client'; -import IconSearch from '@geist-ui/icons/search'; +import { Icon } from '@gitbook/icons'; import { useEffect, useState } from 'react'; import { useLanguage, tString } from '@/intl/client'; @@ -65,7 +65,7 @@ export function SearchButton(props: { children?: React.ReactNode; style?: ClassV )} >
- +
{children} diff --git a/packages/gitbook/src/components/Search/SearchModal.tsx b/packages/gitbook/src/components/Search/SearchModal.tsx index 50f91f4ca..681b93890 100644 --- a/packages/gitbook/src/components/Search/SearchModal.tsx +++ b/packages/gitbook/src/components/Search/SearchModal.tsx @@ -1,7 +1,7 @@ 'use client'; -import IconSearch from '@geist-ui/icons/search'; import { Collection, Site } from '@gitbook/api'; +import { Icon } from '@gitbook/icons'; import { AnimatePresence, motion } from 'framer-motion'; import { useRouter } from 'next/navigation'; import React from 'react'; @@ -198,8 +198,11 @@ function SearchModalBody( }} >
-
- +
+
- {recommended ? question : t(language, 'search_ask', [question])} diff --git a/packages/gitbook/src/components/icons/LogoIcon.tsx b/packages/gitbook/src/components/Space/SpaceIcon.tsx similarity index 96% rename from packages/gitbook/src/components/icons/LogoIcon.tsx rename to packages/gitbook/src/components/Space/SpaceIcon.tsx index 9622344b0..3b2a3ebc5 100644 --- a/packages/gitbook/src/components/icons/LogoIcon.tsx +++ b/packages/gitbook/src/components/Space/SpaceIcon.tsx @@ -5,7 +5,10 @@ import { absoluteHref } from '@/lib/links'; import { Emoji } from '../primitives'; -export function LogoIcon( +/** + * Icon for a space. + */ +export function SpaceIcon( props: { icon?: CustomizationThemedURL; emoji?: string } & Omit< React.ComponentProps, 'sources' diff --git a/packages/gitbook/src/components/TableOfContents/PageLinkItem.tsx b/packages/gitbook/src/components/TableOfContents/PageLinkItem.tsx index ec4a61606..d6db5d091 100644 --- a/packages/gitbook/src/components/TableOfContents/PageLinkItem.tsx +++ b/packages/gitbook/src/components/TableOfContents/PageLinkItem.tsx @@ -1,5 +1,5 @@ -import IconExternal from '@geist-ui/icons/externalLink'; import { RevisionPageLink } from '@gitbook/api'; +import { Icon } from '@gitbook/icons'; import { Emoji, Link } from '@/components/primitives'; import { ContentRefContext, resolveContentRef } from '@/lib/references'; @@ -37,19 +37,19 @@ export async function PageLinkItem(props: { page: RevisionPageLink; context: Con > {page.emoji ? : null} {page.title} - path]:transition-[stroke-opacity]', - '[&>path]:[stroke-opacity:0.40]', - 'group-hover:[&>path]:[stroke-opacity:1]', + '[&>path]:transition-[opacity]', + '[&>path]:[opacity:0.40]', + 'group-hover:[&>path]:[opacity:1]', )} /> diff --git a/packages/gitbook/src/components/TableOfContents/ToggleableLinkItem.tsx b/packages/gitbook/src/components/TableOfContents/ToggleableLinkItem.tsx index d8483aedb..55943cc90 100644 --- a/packages/gitbook/src/components/TableOfContents/ToggleableLinkItem.tsx +++ b/packages/gitbook/src/components/TableOfContents/ToggleableLinkItem.tsx @@ -1,6 +1,6 @@ 'use client'; -import IconChevronRight from '@geist-ui/icons/chevronRight'; +import { Icon } from '@gitbook/icons'; import { motion, stagger, useAnimate } from 'framer-motion'; import { useSelectedLayoutSegment } from 'next/navigation'; import React from 'react'; @@ -170,18 +170,18 @@ export function ToggleableLinkItem(props: { setIsVisible((prev) => !prev); }} > - path]:transition-[stroke-opacity]', + 'size-3', + 'm-1', + 'transition-[opacity]', 'text-current', 'transition-transform', - '[&>path]:[stroke-opacity:0.40]', - 'group-hover:[&>path]:[stroke-opacity:1]', + '[opacity:0.40]', + 'group-hover:[opacity:1]', isVisible ? ['rotate-90'] : ['rotate-0'], )} diff --git a/packages/gitbook/src/components/TableOfContents/Trademark.tsx b/packages/gitbook/src/components/TableOfContents/Trademark.tsx index 67a922934..0bc7b0eee 100644 --- a/packages/gitbook/src/components/TableOfContents/Trademark.tsx +++ b/packages/gitbook/src/components/TableOfContents/Trademark.tsx @@ -1,10 +1,9 @@ import { CustomizationSettings, SiteCustomizationSettings, Space } from '@gitbook/api'; +import { Icon } from '@gitbook/icons'; import { t, getSpaceLanguage } from '@/intl/server'; import { tcls } from '@/lib/tailwind'; -import { IconLogo } from '../icons/IconLogo'; - /** * Trademark link to the GitBook. */ @@ -100,7 +99,7 @@ export function TrademarkLink(props: { 'dark:border-light/2', )} > - + {t(language, 'powered_by_gitbook')} ); diff --git a/packages/gitbook/src/components/ThemeToggler/ThemeToggler.tsx b/packages/gitbook/src/components/ThemeToggler/ThemeToggler.tsx index 4bf912107..7ef00a879 100644 --- a/packages/gitbook/src/components/ThemeToggler/ThemeToggler.tsx +++ b/packages/gitbook/src/components/ThemeToggler/ThemeToggler.tsx @@ -1,12 +1,9 @@ 'use client'; -import Monitor from '@geist-ui/icons/monitor'; -import Moon from '@geist-ui/icons/moon'; -import Sun from '@geist-ui/icons/sun'; +import { Icon, IconName } from '@gitbook/icons'; import { useTheme } from 'next-themes'; import React from 'react'; -import { IconComponent } from '@/components/icons'; import { tString, useLanguage } from '@/intl/client'; import { tcls } from '@/lib/tailwind'; @@ -44,19 +41,19 @@ export function ThemeToggler(props: {}) { > onSwitchMode('light')} title={tString(language, 'switch_to_light_theme')} /> onSwitchMode('system')} title={tString(language, 'switch_to_system_theme')} /> onSwitchMode('dark')} title={tString(language, 'switch_to_dark_theme')} /> @@ -65,12 +62,12 @@ export function ThemeToggler(props: {}) { } function ThemeButton(props: { - icon: IconComponent; + icon: IconName; onClick: () => void; title: string; active: boolean; }) { - const { icon: Icon, onClick, title, active } = props; + const { icon, onClick, title, active } = props; return ( diff --git a/packages/gitbook/src/components/icons/IconArrowLeft.tsx b/packages/gitbook/src/components/icons/IconArrowLeft.tsx deleted file mode 100644 index ec66fcfd1..000000000 --- a/packages/gitbook/src/components/icons/IconArrowLeft.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { SVGProps } from 'react'; - -export function IconArrowLeft(props: Partial>) { - return ( - - - - ); -} diff --git a/packages/gitbook/src/components/icons/IconArrowRight.tsx b/packages/gitbook/src/components/icons/IconArrowRight.tsx deleted file mode 100644 index 27adfa4a6..000000000 --- a/packages/gitbook/src/components/icons/IconArrowRight.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { SVGProps } from 'react'; - -export function IconArrowRight(props: Partial>) { - return ( - - - - ); -} diff --git a/packages/gitbook/src/components/icons/IconChevronDown.tsx b/packages/gitbook/src/components/icons/IconChevronDown.tsx deleted file mode 100644 index dfbb512ca..000000000 --- a/packages/gitbook/src/components/icons/IconChevronDown.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { SVGProps } from 'react'; - -export function IconChevronDown(props: Partial>) { - return ( - - - - ); -} diff --git a/packages/gitbook/src/components/icons/IconChevronRight.tsx b/packages/gitbook/src/components/icons/IconChevronRight.tsx deleted file mode 100644 index d6858a2bf..000000000 --- a/packages/gitbook/src/components/icons/IconChevronRight.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { SVGProps } from 'react'; - -export function IconChevronRight(props: Partial>) { - return ( - - - - ); -} diff --git a/packages/gitbook/src/components/icons/IconLogo.tsx b/packages/gitbook/src/components/icons/IconLogo.tsx deleted file mode 100644 index e3fd5fad4..000000000 --- a/packages/gitbook/src/components/icons/IconLogo.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { SVGProps } from 'react'; - -export function IconLogo(props: Partial>) { - return ( - - - - ); -} diff --git a/packages/gitbook/src/components/icons/IconMenu.tsx b/packages/gitbook/src/components/icons/IconMenu.tsx deleted file mode 100644 index 5c731b4c8..000000000 --- a/packages/gitbook/src/components/icons/IconMenu.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { SVGProps } from 'react'; - -export function IconMenu(props: Partial>) { - return ( - - - - ); -} diff --git a/packages/gitbook/src/components/icons/IconSearch.tsx b/packages/gitbook/src/components/icons/IconSearch.tsx deleted file mode 100644 index 87af7fa41..000000000 --- a/packages/gitbook/src/components/icons/IconSearch.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { SVGProps } from 'react'; - -export function IconSearch(props: Partial>) { - return ( - - - - - ); -} diff --git a/packages/gitbook/src/components/icons/index.ts b/packages/gitbook/src/components/icons/index.ts deleted file mode 100644 index eb69ca795..000000000 --- a/packages/gitbook/src/components/icons/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './types'; -export * from './LogoIcon'; diff --git a/packages/gitbook/src/components/icons/types.ts b/packages/gitbook/src/components/icons/types.ts deleted file mode 100644 index cca14f735..000000000 --- a/packages/gitbook/src/components/icons/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type IconProps = React.SVGProps; - -export type IconComponent = React.FunctionComponent; diff --git a/packages/gitbook/src/components/primitives/Card.tsx b/packages/gitbook/src/components/primitives/Card.tsx index cc1700907..f55867e5d 100644 --- a/packages/gitbook/src/components/primitives/Card.tsx +++ b/packages/gitbook/src/components/primitives/Card.tsx @@ -1,4 +1,4 @@ -import ChevronRight from '@geist-ui/icons/chevronRight'; +import { Icon } from '@gitbook/icons'; import { ClassValue, tcls } from '@/lib/tailwind'; @@ -61,15 +61,15 @@ export async function Card(props: { ) : null} - diff --git a/packages/gitbook/src/components/primitives/Checkbox.tsx b/packages/gitbook/src/components/primitives/Checkbox.tsx index 55a98d04f..23c0b1ba9 100644 --- a/packages/gitbook/src/components/primitives/Checkbox.tsx +++ b/packages/gitbook/src/components/primitives/Checkbox.tsx @@ -1,6 +1,6 @@ 'use client'; -import Check from '@geist-ui/icons/check'; +import { Icon } from '@gitbook/icons'; import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; import React from 'react'; @@ -19,7 +19,7 @@ export const Checkbox = React.forwardRef< 'shrink-0', 'rounded-sm', 'ring-1', - 'bg-primary-300/3', + 'bg-primary-300/1', 'ring-dark/3', 'ring-inset', 'grid', @@ -46,7 +46,7 @@ export const Checkbox = React.forwardRef< 'dark:text-primary-200', )} > - + {props.checked ? : null}
- + {props.checked ? : null}
)); diff --git a/packages/gitbook/src/components/utils/ZoomImage.tsx b/packages/gitbook/src/components/utils/ZoomImage.tsx index 674368c51..1fcddacad 100644 --- a/packages/gitbook/src/components/utils/ZoomImage.tsx +++ b/packages/gitbook/src/components/utils/ZoomImage.tsx @@ -1,6 +1,6 @@ 'use client'; -import IconMinimize from '@geist-ui/icons/minimize'; +import { Icon } from '@gitbook/icons'; import classNames from 'classnames'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -257,7 +257,7 @@ function ZoomImageModal(props: { )} onClick={onClose} > - +
); diff --git a/packages/gitbook/src/lib/csp.ts b/packages/gitbook/src/lib/csp.ts index 4f7d2ceff..465a197fe 100644 --- a/packages/gitbook/src/lib/csp.ts +++ b/packages/gitbook/src/lib/csp.ts @@ -29,6 +29,10 @@ export function createContentSecurityPolicyNonce(): string { * Generate a Content Security Policy header for a space. */ export function getContentSecurityPolicy(scripts: SpaceIntegrationScript[], nonce: string): string { + const iconsAssetsSrc = process.env.GITBOOK_ICONS_URL + ? new URL(process.env.GITBOOK_ICONS_URL).origin + : ''; + // We need to allow loading any image or download any file // to support image and OpenAPI blocks where the content reference could be external. // @@ -37,8 +41,8 @@ export function getContentSecurityPolicy(scripts: SpaceIntegrationScript[], nonc default-src 'self' ${assetsDomain}; script-src 'self' 'nonce-${nonce}' 'strict-dynamic' 'unsafe-inline' 'unsafe-eval' ${assetsDomain} https://integrations.gitbook.com https://cdn.iframe.ly; style-src 'self' ${assetsDomain} fonts.googleapis.com 'unsafe-inline'; - img-src * 'self' blob: data: files.gitbook.com ${assetsDomain}; - connect-src * 'self' integrations.gitbook.com app.gitbook.com api.gitbook.com srv.buysellads.com ${assetsDomain}; + img-src * 'self' blob: data: files.gitbook.com ${assetsDomain} ${iconsAssetsSrc}; + connect-src * 'self' integrations.gitbook.com app.gitbook.com api.gitbook.com srv.buysellads.com ${assetsDomain} ${iconsAssetsSrc}; font-src 'self' fonts.gstatic.com ${assetsDomain}; frame-src *; object-src 'none'; diff --git a/packages/gitbook/src/lib/utils.ts b/packages/gitbook/src/lib/utils.ts index ddf162fcc..450dab77c 100644 --- a/packages/gitbook/src/lib/utils.ts +++ b/packages/gitbook/src/lib/utils.ts @@ -21,5 +21,5 @@ export function getContentTitle( } // Otherwise the legacy behavior is not changed to avoid regressions - return parent ? parent.title : customization.title ?? space.title; + return parent ? parent.title : (customization.title ?? space.title); } diff --git a/packages/gitbook/src/middleware.ts b/packages/gitbook/src/middleware.ts index 20cb478e8..aeb368632 100644 --- a/packages/gitbook/src/middleware.ts +++ b/packages/gitbook/src/middleware.ts @@ -443,10 +443,10 @@ async function lookupSpaceInMultiIdMode(request: NextRequest, url: URL): Promise apiToken: url.searchParams.get(AUTH_TOKEN_QUERY) ?? '', apiEndpoint: url.searchParams.get(API_ENDPOINT_QUERY) ?? undefined, } - : decodeGitBookTokenCookie(spaceId, request.cookies.get(cookieName)?.value) ?? { + : (decodeGitBookTokenCookie(spaceId, request.cookies.get(cookieName)?.value) ?? { apiToken: undefined, apiEndpoint: undefined, - }; + }); if (!apiToken) { return { diff --git a/packages/icons/.gitignore b/packages/icons/.gitignore new file mode 100644 index 000000000..253839862 --- /dev/null +++ b/packages/icons/.gitignore @@ -0,0 +1,2 @@ +dist/ +src/data/*.json diff --git a/packages/icons/README.md b/packages/icons/README.md new file mode 100644 index 000000000..9f1f25145 --- /dev/null +++ b/packages/icons/README.md @@ -0,0 +1,11 @@ +# `@gitbook/icons` + +Icon sets useed in GitBook content. + +## Usage + +```tsx +import { Icon } from '@gitbook/icons'; + +; +``` diff --git a/packages/icons/bin/gen-list.js b/packages/icons/bin/gen-list.js new file mode 100644 index 000000000..f7736de7c --- /dev/null +++ b/packages/icons/bin/gen-list.js @@ -0,0 +1,72 @@ +import fs from 'fs/promises'; +import { existsSync } from 'fs'; +import path from 'path'; +import url from 'url'; + +import { getKitPath } from './kit.js'; + +/** + * Scripts to generate the list of all icons. + */ +async function main() { + const source = getKitPath(); + const icons = JSON.parse( + await fs.readFile(path.join(source, 'icons/metadata/icon-families.json'), 'utf8'), + ); + + // Only these families have exceptions + const potentialOnly = ['brands', 'custom-icons']; + + const keyRename = { + // Font Awesome stores custom icons under "custom-icons" + // but indicates them as svg.kit.custom in the JSON data + 'kit-custom': 'custom-icons', + }; + + const onlyStyles = {}; + + const result = []; + Object.entries(icons).forEach(([icon, iconSpec]) => { + const stylesCovered = []; + + Object.entries(iconSpec.svgs).forEach(([family, familyEntries]) => { + Object.keys(familyEntries).forEach((style) => { + let key = family !== 'classic' ? `${family}-${style}` : style; + key = keyRename[key] ?? key; + stylesCovered.push(key); + }); + }); + + potentialOnly.forEach((style) => { + if (stylesCovered.includes(style)) { + onlyStyles[style] = onlyStyles[style] || []; + onlyStyles[style].push(icon); + } + }); + + result.push({ + icon, + label: iconSpec.label, + search: iconSpec.search?.terms, + }); + }); + + await Promise.all([ + writeDataFile('styles-map', JSON.stringify(onlyStyles, null, 2)), + writeDataFile('icons', JSON.stringify(result, null, 2)), + ]); + console.log(`🎉 ${result.length} icons found`); +} + +async function writeDataFile(name, content) { + const dirname = path.dirname(url.fileURLToPath(import.meta.url)); + await Promise.all([ + fs.writeFile(path.resolve(dirname, `../src/data/${name}.json`), content), + // Write to dist folder if it exists + existsSync(path.resolve(dirname, '../dist/')) + ? fs.writeFile(path.resolve(dirname, `../dist/data/${name}.json`), content) + : null, + ]); +} + +main().catch(console.error); diff --git a/packages/icons/bin/gitbook-icons.js b/packages/icons/bin/gitbook-icons.js new file mode 100755 index 000000000..91100e1f2 --- /dev/null +++ b/packages/icons/bin/gitbook-icons.js @@ -0,0 +1,49 @@ +#!/usr/bin/env node +import fs from 'fs/promises'; +import path from 'path'; + +import { getKitPath } from './kit.js'; + +const allStyles = ['brands', 'duotone', 'solid', 'regular', 'light', 'thin', 'custom-icons']; + +/** + * Scripts to copy the assets to a public folder. + */ +async function main() { + const outputFolder = path.resolve(process.cwd(), process.argv[2] ?? 'public/icons'); + const stylesToCopy = (process.argv[3] ? process.argv[3].split(',') : allStyles).filter( + (style) => allStyles.includes(style), + ); + const source = getKitPath(); + + // Create the output folder if it doesn't exist + await fs.mkdir(outputFolder, { recursive: true }); + + const printOutputFolder = path.relative(process.cwd(), outputFolder); + console.log(`🚚 Copying icons of styles ${stylesToCopy.join(', ')} to ${printOutputFolder}`); + + // Copy the assets from + // source/sprites to outputFolder/sprites + // source/svgs to outputFolder/svgs + await Promise.all([ + ...stylesToCopy.map((style) => + fs.cp( + path.join(source, `icons/svgs/${style}`), + path.join(outputFolder, 'svgs', style), + { + recursive: true, + }, + ), + ), + ...stylesToCopy.map((style) => + fs.cp( + path.join(source, `icons/sprites/${style}.svg`), + path.join(outputFolder, 'sprites', style + '.svg'), + ), + ), + ]); + + console.log(`🎉 Icons copied to ${printOutputFolder}`); +} + +main().catch(console.error); diff --git a/packages/icons/bin/kit.js b/packages/icons/bin/kit.js new file mode 100644 index 000000000..06e0387c6 --- /dev/null +++ b/packages/icons/bin/kit.js @@ -0,0 +1,20 @@ +import path from 'path'; +import url from 'url'; + +/** + * Get the path to the kit, depending on the Pro or Free version. + */ +export function getKitPath() { + let source = path.dirname( + url.fileURLToPath(import.meta.resolve('@fortawesome/fontawesome-free/package.json')), + ); + try { + source = path.dirname( + url.fileURLToPath(import.meta.resolve('@awesome.me/kit-a463935e93/package.json')), + ); + } catch (error) { + console.warn('⚠️ Could not find the Pro kit, using the free kit instead'); + } + + return source; +} diff --git a/packages/icons/package.json b/packages/icons/package.json new file mode 100644 index 000000000..d5087b830 --- /dev/null +++ b/packages/icons/package.json @@ -0,0 +1,50 @@ +{ + "name": "@gitbook/icons", + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "development": "./src/index.ts", + "default": "./dist/index.js" + }, + "./icons": { + "types": "./dist/icons.d.ts", + "development": "./src/icons.ts", + "default": "./dist/icons.js" + }, + "./style.css": { + "default": "./src/style.css" + } + }, + "version": "0.0.3", + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.6.0", + "@fortawesome/fontawesome-free": "^6.6.0" + }, + "optionalDependencies": { + "@awesome.me/kit-a463935e93": "1.0.8" + }, + "devDependencies": { + "typescript": "^5.5.3" + }, + "peerDependencies": { + "react": "*" + }, + "scripts": { + "generate": "node ./bin/gen-list.js", + "build": "tsc", + "typecheck": "tsc --noEmit", + "dev": "tsc -w" + }, + "bin": { + "gitbook-icons": "./bin/gitbook-icons.js" + }, + "files": [ + "dist", + "src", + "bin", + "data", + "README.md", + "CHANGELOG.md" + ] +} diff --git a/packages/icons/src/Icon.tsx b/packages/icons/src/Icon.tsx new file mode 100644 index 000000000..242a7c2a0 --- /dev/null +++ b/packages/icons/src/Icon.tsx @@ -0,0 +1,70 @@ +'use client'; + +import * as React from 'react'; + +import { IconName, IconStyle } from './types'; +import { getIconStyle } from './getIconStyle'; +import { getIconAssetURL, useIcons } from './IconsProvider'; + +/** + * Props for the icon component. + */ +export type IconProps = React.SVGProps & { + /** + * Name of the icon to render. + */ + icon: IconName; + + /** + * Style of the icon to render. + * @default 'solid' + */ + iconStyle?: IconStyle; + + /** + * Class name to apply to the icon. + */ + className?: string; + + /** + * Size of the icon. + * If none is defined, the size should be defined using CSS / class name. + */ + size?: number; +}; + +/** + * Renders an icon component from the library. + * The icon is loaded as a standalone image. + */ +export const Icon = React.forwardRef(function Icon( + props: IconProps, + ref: React.Ref, +) { + const context = useIcons(); + const { + icon: propIcon, + iconStyle: propIconStyle = context.iconStyle, + className = '', + size, + ...rest + } = props; + + const [iconStyle, icon] = getIconStyle(propIconStyle, propIcon); + const url = getIconAssetURL(context, iconStyle, icon); + + return ( + + ); +}); diff --git a/packages/icons/src/IconsProvider.tsx b/packages/icons/src/IconsProvider.tsx new file mode 100644 index 000000000..9e6904852 --- /dev/null +++ b/packages/icons/src/IconsProvider.tsx @@ -0,0 +1,77 @@ +'use client'; + +import * as React from 'react'; +import { IconStyle } from './types'; + +const version = 1; + +export interface IconsAssetsLocation { + /** Rroot url where the icon assets are served */ + assetsURL: string; + /** Token to be passed in the URL for assets */ + assetsURLToken?: string; +} + +export type IconsContextType = Partial & { + /** Assets location for special styles */ + assetsByStyles?: Record; + /** Current default style for icons */ + iconStyle: IconStyle; +}; + +const IconsContext = React.createContext({ + iconStyle: IconStyle.Regular, +}); + +/** + * Provider to control the loading of icons. + */ +export function IconsProvider(props: React.PropsWithChildren>) { + const parent = React.useContext(IconsContext); + const { + children, + assetsURL = parent.assetsURL, + assetsURLToken = parent.assetsURLToken, + iconStyle = parent.iconStyle, + assetsByStyles = parent.assetsByStyles, + } = props; + const value = React.useMemo(() => { + return { assetsURL, assetsURLToken, iconStyle, assetsByStyles }; + }, [assetsURL, assetsURLToken, iconStyle, assetsByStyles]); + + return {children}; +} + +/** + * Hook to access the current icons context. + */ +export function useIcons(): IconsContextType { + return React.useContext(IconsContext); +} + +/** + * Get the URL for an asset. + */ +export function getAssetURL(location: Partial, path: string): string { + if (!location.assetsURL) { + throw new Error('You first need to pass a assetsURL to '); + } + const rawUrl = + location.assetsURL + (location.assetsURL.endsWith('/') ? '' : '/') + path + `?v=${version}`; + + if (location.assetsURLToken) { + const url = new URL(rawUrl); + url.searchParams.set('token', location.assetsURLToken); + return url.toString(); + } else { + return rawUrl; + } +} + +/** + * Get the URL for the SVG of an icon. + */ +export function getIconAssetURL(context: IconsContextType, style: string, icon: string): string { + const location = context.assetsByStyles?.[style] ?? context; + return getAssetURL(location, `svgs/${style}/${icon}.svg`); +} diff --git a/packages/icons/src/data/.gitkeep b/packages/icons/src/data/.gitkeep new file mode 100644 index 000000000..d7bce35f9 --- /dev/null +++ b/packages/icons/src/data/.gitkeep @@ -0,0 +1 @@ +Files in this folder are generated in postinstall script. \ No newline at end of file diff --git a/packages/icons/src/getIconStyle.ts b/packages/icons/src/getIconStyle.ts new file mode 100644 index 000000000..6511bf02c --- /dev/null +++ b/packages/icons/src/getIconStyle.ts @@ -0,0 +1,33 @@ +import stylesMap from './data/styles-map.json'; +import { IconName, IconStyle } from './types'; + +const cache = new Map>(); + +/** + * Return the style to load an icon from by its name. + * Some icons are only available for certain styles. + */ +export function getIconStyle(style: IconStyle, icon: IconName): [string, IconName] { + const cached = cache.get(icon)?.get(style); + + if (cached) { + return cached; + } + + // Check for exceptions + let result = [style, icon] as [string, IconName]; + + for (const [onlyStyle, icons] of Object.entries(stylesMap)) { + if (icons.includes(icon)) { + result = [onlyStyle, icon]; + break; + } + } + + if (!cache.has(icon)) { + cache.set(icon, new Map()); + } + cache.get(icon)!.set(style, result); + + return result; +} diff --git a/packages/icons/src/icons.ts b/packages/icons/src/icons.ts new file mode 100644 index 000000000..7e1feb6a3 --- /dev/null +++ b/packages/icons/src/icons.ts @@ -0,0 +1,58 @@ +import { IconName, IconStyle } from './types'; +import rawIcons from './data/icons.json'; + +export interface IconStyleDefinition { + title: string; + style: IconStyle; +} + +export interface IconDefinition { + icon: IconName; + label: string; + search?: string[]; +} + +/** + * List of all icon styles available in the library. + */ +export const iconStyles: IconStyleDefinition[] = [ + { + title: 'Regular', + style: IconStyle.Regular, + }, + { + title: 'Duotone', + style: IconStyle.Duotone, + }, + { + title: 'Light', + style: IconStyle.Light, + }, + { + title: 'Thin', + style: IconStyle.Thin, + }, + { + title: 'Solid', + style: IconStyle.Solid, + }, +]; + +/** + * List of all icons available in the library. + */ +// @ts-ignore +export const icons: IconDefinition[] = rawIcons; + +let iconNamesSet: Set | null = null; + +/** + * Validate that the icon name is valid. + */ +export function validateIconName(icon: IconName | string): icon is IconName { + if (!iconNamesSet) { + iconNamesSet = new Set(icons.map((icon) => icon.icon)); + } + + return iconNamesSet.has(icon); +} diff --git a/packages/icons/src/index.ts b/packages/icons/src/index.ts new file mode 100644 index 000000000..8148812a5 --- /dev/null +++ b/packages/icons/src/index.ts @@ -0,0 +1,3 @@ +export * from './Icon'; +export * from './types'; +export * from './IconsProvider'; diff --git a/packages/icons/src/style.css b/packages/icons/src/style.css new file mode 100644 index 000000000..bc88ed7fb --- /dev/null +++ b/packages/icons/src/style.css @@ -0,0 +1,11 @@ +/* Normal */ +svg.gb-icon { + background: currentColor; +} + +/* Sprite */ +svg.gb-icon-s path, +svg.gb-icon-s use { + fill: currentColor; + stroke: currentColor; +} diff --git a/packages/icons/src/types.ts b/packages/icons/src/types.ts new file mode 100644 index 000000000..5889a141d --- /dev/null +++ b/packages/icons/src/types.ts @@ -0,0 +1,23 @@ +import type { IconName as BasicIconName } from '@fortawesome/fontawesome-svg-core'; + +export enum IconStyle { + // Regular + Regular = 'regular', + SharpRegular = 'sharp-regular', + // Solid + Solid = 'solid', + SharpSolid = 'sharp-solid', + // Duotone + Duotone = 'duotone', + SharpDuotoneSolid = 'sharp-duotone-solid', + // Light + Light = 'light', + SharpLight = 'sharp-light', + // Thin + Thin = 'thin', + SharpThin = 'sharp-thin', +} + +type CustomIconName = 'gitbook'; + +export type IconName = BasicIconName | CustomIconName; diff --git a/packages/icons/tsconfig.json b/packages/icons/tsconfig.json new file mode 100644 index 000000000..2f0c8164b --- /dev/null +++ b/packages/icons/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": false, + "declaration": true, + "outDir": "dist", + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react", + "incremental": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/packages/react-math/bin/gitbook-math.js b/packages/react-math/bin/gitbook-math.js new file mode 100755 index 000000000..2a040f217 --- /dev/null +++ b/packages/react-math/bin/gitbook-math.js @@ -0,0 +1,28 @@ +#!/usr/bin/env node +import fs from 'fs/promises'; +import path from 'path'; +import url from 'url'; + +/** + * Scripts to copy the assets to a public folder. + */ +async function main() { + const outputFolder = path.resolve(process.cwd(), process.argv[2] ?? 'public/math'); + const source = path.dirname(url.fileURLToPath(import.meta.resolve('mathjax/package.json'))); + + const packageJson = JSON.parse(await fs.readFile(path.join(source, 'package.json'), 'utf8')); + + // Create the output folder if it doesn't exist + await fs.mkdir(outputFolder, { recursive: true }); + + // Copy the assets + await Promise.all([ + fs.cp(path.join(source, 'es5'), path.join(outputFolder, 'mathjax@' + packageJson.version), { + recursive: true, + }), + ]); + + console.log(`🎉 MathJaX assets copied to ${outputFolder}`); +} + +main().catch(console.error); diff --git a/packages/react-math/package.json b/packages/react-math/package.json index 2beee295e..a055cbf45 100644 --- a/packages/react-math/package.json +++ b/packages/react-math/package.json @@ -1,5 +1,6 @@ { "name": "@gitbook/react-math", + "type": "module", "exports": { ".": { "types": "./dist/index.d.ts", @@ -23,6 +24,9 @@ "typecheck": "tsc --noEmit", "dev": "tsc -w" }, + "bin": { + "gitbook-math": "./bin/gitbook-math.js" + }, "files": [ "dist", "src", diff --git a/packages/react-math/src/MathFormula.tsx b/packages/react-math/src/MathFormula.tsx index 011808df0..1f9ed8679 100644 --- a/packages/react-math/src/MathFormula.tsx +++ b/packages/react-math/src/MathFormula.tsx @@ -26,9 +26,9 @@ export interface MathFormulaProps { fallback?: React.ReactNode; /** - * URL to load MathJax from. + * URL to load assets from. */ - mathJaxUrl: string; + assetsUrl: string; } /** @@ -43,9 +43,11 @@ export function MathFormula(props: MathFormulaProps) { className: props.className, children: props.formula, }), - mathJaxUrl, + assetsUrl, } = props; + const mathJaxUrl = `${assetsUrl}/mathjax@3.2.2/tex-chtml.js`; + return (