Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: facebook/docusaurus
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v2.3.0
Choose a base ref
...
head repository: facebook/docusaurus
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v2.3.1
Choose a head ref
  • 12 commits
  • 175 files changed
  • 29 contributors

Commits on Jan 27, 2023

  1. chore: backport retro compatible commits for the Docusaurus v2.3 rele…

    …ase (#8585)
    
    Co-authored-by: stnor <stefan@selessia.com>
    Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
    Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
    Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    Co-authored-by: Matija Sirk <matija.sirk@kopit.si>
    Co-authored-by: AHMET BAYHAN BAYRAMOGLU <49499275+ABB65@users.noreply.github.com>
    Co-authored-by: Stefan Norberg <stefan@norberg.org>
    Co-authored-by: Josh Goldberg <git@joshuakgoldberg.com>
    Co-authored-by: Muhammad Hammad <33136628+mhnaeem@users.noreply.github.com>
    Co-authored-by: Denis Al-Khelali <denis.al-khelali@itechart-group.com>
    Co-authored-by: Balthasar Hofer <lebalz@outlook.com>
    Co-authored-by: Danny Kim <0916dhkim@gmail.com>
    Co-authored-by: Frieder Bluemle <frieder.bluemle@gmail.com>
    Co-authored-by: John Reilly <johnny_reilly@hotmail.com>
    Co-authored-by: Robert Lawrence <62929526+r-lawrence@users.noreply.github.com>
    Co-authored-by: Sadegh Karimi <sadegh.krmi@gmail.com>
    Co-authored-by: Lachlan Heywood <lachieh@users.noreply.github.com>
    Co-authored-by: mturoci <64769322+mturoci@users.noreply.github.com>
    Co-authored-by: 宋锦丰 <36468758+SJFCS@users.noreply.github.com>
    Co-authored-by: Nguyễn Thành Nam <namnguyenthanh.work@gmail.com>
    Co-authored-by: Dongjoon Lee <djunnni@gmail.com>
    Co-authored-by: Thomas.CA <44041651+Thomascogez@users.noreply.github.com>
    Co-authored-by: Riccardo <riccardo.odone@gmail.com>
    Co-authored-by: Lane Goolsby <lanegoolsby@yahoo.com>
    Co-authored-by: Mariusz Krzaczkowski <m.krzaczkowski@yetiforce.com>
    Co-authored-by: Matija Sirk <sirkmatija@gmail.com>
    Co-authored-by: Jiří <zmrhal.j@gmail.com>
    1

    Partially verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
    Copy the full SHA
    c84d779 View commit details

Commits on Feb 2, 2023

  1. chore(deps): bump eta from 1.12.3 to 2.0.0 (#8610)

    Bumps [eta](https://github.com/eta-dev/eta) from 1.12.3 to 2.0.0.
    - [Release notes](https://github.com/eta-dev/eta/releases)
    - [Commits](eta-dev/eta@v1.12.3...v2.0.0)
    
    ---
    updated-dependencies:
    - dependency-name: eta
      dependency-type: direct:production
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored and slorber committed Feb 2, 2023
    Copy the full SHA
    4cd2c65 View commit details
  2. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    990e553 View commit details
  3. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    4761c8c View commit details
  4. Copy the full SHA
    2bdd27a View commit details
  5. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    883983c View commit details
  6. Copy the full SHA
    ce8e55b View commit details
  7. fix(theme-common): localStorage utils dispatch too many storage event…

    …s leading to infinite loop (#8619)
    slorber committed Feb 2, 2023
    Copy the full SHA
    692bbda View commit details
  8. trigger ci

    slorber committed Feb 2, 2023
    Copy the full SHA
    484774c View commit details

Commits on Feb 3, 2023

  1. Copy the full SHA
    ed13d5c View commit details
  2. Copy the full SHA
    cc767ed View commit details
  3. v2.3.1

    slorber committed Feb 3, 2023
    Copy the full SHA
    cf12f21 View commit details
Showing with 2,734 additions and 1,209 deletions.
  1. +1 −1 admin/new.docusaurus.io/package.json
  2. +2 −1 lerna.json
  3. +3 −3 packages/create-docusaurus/package.json
  4. +4 −4 packages/create-docusaurus/templates/classic-typescript/package.json
  5. +10 −3 packages/create-docusaurus/templates/classic/docusaurus.config.js
  6. +4 −4 packages/create-docusaurus/templates/classic/package.json
  7. +9 −3 packages/create-docusaurus/templates/facebook/docusaurus.config.js
  8. +3 −3 packages/create-docusaurus/templates/facebook/package.json
  9. +5 −1 packages/create-docusaurus/templates/shared/docs/tutorial-basics/markdown-features.mdx
  10. BIN packages/create-docusaurus/templates/shared/static/img/docusaurus-social-card.jpg
  11. +1 −1 packages/docusaurus-cssnano-preset/package.json
  12. +1 −1 packages/docusaurus-logger/package.json
  13. +4 −5 packages/docusaurus-mdx-loader/package.json
  14. +10 −0 packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/nesting.md
  15. +5 −0 packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__snapshots__/index.test.ts.snap
  16. +5 −0 packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/index.test.ts
  17. +42 −6 packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts
  18. +3 −3 packages/docusaurus-migrate/package.json
  19. +3 −3 packages/docusaurus-migrate/src/__tests__/__snapshots__/index.test.ts.snap
  20. +2 −2 packages/docusaurus-module-type-aliases/package.json
  21. +8 −8 packages/docusaurus-plugin-client-redirects/package.json
  22. +8 −11 packages/docusaurus-plugin-content-blog/package.json
  23. +118 −21 packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap
  24. +52 −0 packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
  25. +2 −2 packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts
  26. +34 −9 packages/docusaurus-plugin-content-blog/src/feed.ts
  27. +1 −0 packages/docusaurus-plugin-content-blog/src/options.ts
  28. +29 −2 packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts
  29. +8 −9 packages/docusaurus-plugin-content-docs/package.json
  30. +2 −2 packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts
  31. +5 −4 packages/docusaurus-plugin-content-docs/src/index.ts
  32. +6 −6 packages/docusaurus-plugin-content-pages/package.json
  33. +4 −4 packages/docusaurus-plugin-debug/package.json
  34. +4 −4 packages/docusaurus-plugin-google-analytics/package.json
  35. +4 −4 packages/docusaurus-plugin-google-gtag/package.json
  36. +3 −0 packages/docusaurus-plugin-google-tag-manager/.npmignore
  37. +7 −0 packages/docusaurus-plugin-google-tag-manager/README.md
  38. +33 −0 packages/docusaurus-plugin-google-tag-manager/package.json
  39. +78 −0 packages/docusaurus-plugin-google-tag-manager/src/index.ts
  40. +12 −0 packages/docusaurus-plugin-google-tag-manager/src/options.ts
  41. +8 −0 packages/docusaurus-plugin-google-tag-manager/src/types.d.ts
  42. +15 −0 packages/docusaurus-plugin-google-tag-manager/tsconfig.client.json
  43. +13 −0 packages/docusaurus-plugin-google-tag-manager/tsconfig.json
  44. +7 −7 packages/docusaurus-plugin-ideal-image/package.json
  45. +8 −8 packages/docusaurus-plugin-pwa/package.json
  46. +7 −7 packages/docusaurus-plugin-sitemap/package.json
  47. +14 −13 packages/docusaurus-preset-classic/package.json
  48. +10 −1 packages/docusaurus-preset-classic/src/index.ts
  49. +2 −0 packages/docusaurus-preset-classic/src/options.ts
  50. +1 −1 packages/docusaurus-remark-plugin-npm2yarn/package.json
  51. +13 −13 packages/docusaurus-theme-classic/package.json
  52. +1 −1 packages/docusaurus-theme-classic/src/__tests__/options.test.ts
  53. +39 −1 packages/docusaurus-theme-classic/src/getSwizzleConfig.ts
  54. +8 −25 packages/docusaurus-theme-classic/src/theme-classic.d.ts
  55. +33 −0 packages/docusaurus-theme-classic/src/theme/DocBreadcrumbs/Items/Home/index.tsx
  56. +14 −0 packages/docusaurus-theme-classic/src/theme/DocBreadcrumbs/Items/Home/styles.module.css
  57. +1 −20 packages/docusaurus-theme-classic/src/theme/DocBreadcrumbs/index.tsx
  58. +0 −8 packages/docusaurus-theme-classic/src/theme/DocBreadcrumbs/styles.module.css
  59. +3 −2 packages/docusaurus-theme-classic/src/theme/DocPage/Layout/Sidebar/ExpandButton/styles.module.css
  60. +13 −8 packages/docusaurus-theme-classic/src/theme/DocPage/Layout/Sidebar/index.tsx
  61. +7 −0 packages/docusaurus-theme-classic/src/theme/DocPage/Layout/Sidebar/styles.module.css
  62. +1 −0 packages/docusaurus-theme-classic/src/theme/DocPage/Layout/styles.module.css
  63. +1 −0 packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/CollapseButton/styles.module.css
  64. +6 −0 packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/Content/index.tsx
  65. +0 −6 packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/styles.module.css
  66. +18 −8 packages/docusaurus-theme-classic/src/theme/Heading/index.tsx
  67. +0 −2 packages/docusaurus-theme-classic/src/theme/Layout/Provider/index.tsx
  68. +2 −0 packages/docusaurus-theme-classic/src/theme/Layout/styles.module.css
  69. +6 −0 packages/docusaurus-theme-classic/src/theme/Navbar/Layout/index.tsx
  70. +150 −84 packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx
  71. +81 −142 packages/docusaurus-theme-classic/src/theme/Tabs/index.tsx
  72. +10 −9 packages/docusaurus-theme-common/package.json
  73. +0 −85 packages/docusaurus-theme-common/src/contexts/tabGroupChoice.tsx
  74. +10 −5 packages/docusaurus-theme-common/src/hooks/useSearchPage.ts
  75. +5 −1 packages/docusaurus-theme-common/src/index.ts
  76. +7 −5 packages/docusaurus-theme-common/src/internal.ts
  77. +33 −1 packages/docusaurus-theme-common/src/utils/historyUtils.ts
  78. +4 −1 packages/docusaurus-theme-common/src/utils/scrollUtils.tsx
  79. +115 −7 packages/docusaurus-theme-common/src/utils/storageUtils.ts
  80. +270 −0 packages/docusaurus-theme-common/src/utils/tabsUtils.tsx
  81. +6 −6 packages/docusaurus-theme-live-codeblock/package.json
  82. +7 −8 packages/docusaurus-theme-mermaid/package.json
  83. +3 −4 packages/docusaurus-theme-mermaid/src/client/index.ts
  84. +2 −3 packages/docusaurus-theme-mermaid/src/validateThemeConfig.ts
  85. +1 −1 packages/docusaurus-theme-mermaid/tsconfig.client.json
  86. +1 −1 packages/docusaurus-theme-mermaid/tsconfig.json
  87. +10 −10 packages/docusaurus-theme-search-algolia/package.json
  88. +48 −1 packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts
  89. +2 −0 packages/docusaurus-theme-search-algolia/src/client/index.ts
  90. +15 −0 packages/docusaurus-theme-search-algolia/src/client/useAlgoliaThemeConfig.ts
  91. +54 −0 packages/docusaurus-theme-search-algolia/src/client/useSearchResultUrlProcessor.ts
  92. +10 −0 packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts
  93. +20 −24 packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx
  94. +15 −17 packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx
  95. +14 −0 packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts
  96. +3 −1 packages/docusaurus-theme-translations/locales/ar/theme-common.json
  97. +6 −2 packages/docusaurus-theme-translations/locales/base/theme-common.json
  98. +3 −1 packages/docusaurus-theme-translations/locales/bn/theme-common.json
  99. +3 −1 packages/docusaurus-theme-translations/locales/cs/theme-common.json
  100. +3 −1 packages/docusaurus-theme-translations/locales/da/theme-common.json
  101. +3 −1 packages/docusaurus-theme-translations/locales/de/theme-common.json
  102. +3 −1 packages/docusaurus-theme-translations/locales/es/theme-common.json
  103. +3 −1 packages/docusaurus-theme-translations/locales/fa/theme-common.json
  104. +23 −23 packages/docusaurus-theme-translations/locales/fa/theme-search-algolia.json
  105. +3 −1 packages/docusaurus-theme-translations/locales/fil/theme-common.json
  106. +3 −1 packages/docusaurus-theme-translations/locales/fr/theme-common.json
  107. +3 −1 packages/docusaurus-theme-translations/locales/he/theme-common.json
  108. +3 −1 packages/docusaurus-theme-translations/locales/hi/theme-common.json
  109. +3 −1 packages/docusaurus-theme-translations/locales/it/theme-common.json
  110. +3 −1 packages/docusaurus-theme-translations/locales/ja/theme-common.json
  111. +3 −1 packages/docusaurus-theme-translations/locales/ko/theme-common.json
  112. +3 −1 packages/docusaurus-theme-translations/locales/nl/theme-common.json
  113. +13 −11 packages/docusaurus-theme-translations/locales/pl/theme-common.json
  114. +23 −23 packages/docusaurus-theme-translations/locales/pl/theme-search-algolia.json
  115. +3 −1 packages/docusaurus-theme-translations/locales/pt-BR/theme-common.json
  116. +3 −1 packages/docusaurus-theme-translations/locales/pt-PT/theme-common.json
  117. +3 −1 packages/docusaurus-theme-translations/locales/ru/theme-common.json
  118. +7 −0 packages/docusaurus-theme-translations/locales/sl/plugin-ideal-image.json
  119. +5 −0 packages/docusaurus-theme-translations/locales/sl/plugin-pwa.json
  120. +72 −0 packages/docusaurus-theme-translations/locales/sl/theme-common.json
  121. +4 −0 packages/docusaurus-theme-translations/locales/sl/theme-live-codeblock.json
  122. +35 −0 packages/docusaurus-theme-translations/locales/sl/theme-search-algolia.json
  123. +3 −1 packages/docusaurus-theme-translations/locales/sr/theme-common.json
  124. +4 −2 packages/docusaurus-theme-translations/locales/sv/theme-common.json
  125. +24 −24 packages/docusaurus-theme-translations/locales/sv/theme-search-algolia.json
  126. +14 −10 packages/docusaurus-theme-translations/locales/tr/theme-common.json
  127. +3 −1 packages/docusaurus-theme-translations/locales/uk/theme-common.json
  128. +4 −2 packages/docusaurus-theme-translations/locales/vi/theme-common.json
  129. +1 −1 packages/docusaurus-theme-translations/locales/vi/theme-search-algolia.json
  130. +5 −3 packages/docusaurus-theme-translations/locales/zh-Hans/theme-common.json
  131. +3 −1 packages/docusaurus-theme-translations/locales/zh-Hant/theme-common.json
  132. +3 −3 packages/docusaurus-theme-translations/package.json
  133. +1 −1 packages/docusaurus-types/package.json
  134. +1 −1 packages/docusaurus-utils-common/package.json
  135. +3 −3 packages/docusaurus-utils-validation/package.json
  136. +4 −3 packages/docusaurus-utils/package.json
  137. +35 −1 packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts
  138. +1 −0 packages/docusaurus-utils/src/index.ts
  139. +6 −7 packages/docusaurus-utils/src/markdownUtils.ts
  140. +12 −0 packages/docusaurus-utils/src/regExpUtils.ts
  141. +18 −0 packages/docusaurus/bin/docusaurus.mjs
  142. +10 −10 packages/docusaurus/package.json
  143. +21 −4 packages/docusaurus/src/client/ClientLifecyclesDispatcher.tsx
  144. +9 −1 packages/docusaurus/src/client/exports/useBaseUrl.ts
  145. +5 −1 packages/docusaurus/src/commands/build.ts
  146. +1 −1 packages/docusaurus/src/commands/deploy.ts
  147. +60 −0 packages/docusaurus/src/commands/swizzle/__tests__/__snapshots__/index.test.ts.snap
  148. +9 −3 packages/docusaurus/src/commands/swizzle/__tests__/actions.test.ts
  149. +2 −1 packages/docusaurus/src/commands/swizzle/actions.ts
  150. +1 −1 packages/docusaurus/src/commands/swizzle/components.ts
  151. +34 −0 packages/docusaurus/src/server/__tests__/routes.test.ts
  152. +7 −1 packages/docusaurus/src/server/routes.ts
  153. +1 −1 packages/eslint-plugin/package.json
  154. +2 −0 packages/eslint-plugin/src/index.ts
  155. +2 −2 packages/lqip-loader/package.json
  156. +1 −1 packages/stylelint-copyright/package.json
  157. +28 −0 website/_dogfooding/_pages tests/markdownPageTests.md
  158. +26 −0 website/_dogfooding/_pages tests/tabs-tests.mdx
  159. +31 −17 website/_dogfooding/testSwizzleThemeClassic.mjs
  160. +10 −12 website/docs/api/misc/eslint-plugin/README.md
  161. +20 −0 website/docs/api/plugins/plugin-content-blog.md
  162. +10 −0 website/docs/api/plugins/plugin-google-analytics.md
  163. +71 −0 website/docs/api/plugins/plugin-google-tag-manager.md
  164. +19 −0 website/docs/blog.mdx
  165. +127 −2 website/docs/guides/markdown-features/markdown-features-admonitions.mdx
  166. +60 −0 website/docs/guides/markdown-features/markdown-features-tabs.mdx
  167. +6 −0 website/docs/search.md
  168. +5 −2 website/docs/using-plugins.md
  169. +1 −1 website/docusaurus.config-blog-only.js
  170. +8 −1 website/docusaurus.config.js
  171. +15 −15 website/package.json
  172. BIN website/static/docusaurus-social-card.jpg
  173. BIN website/static/docusaurus-social-card.png
  174. BIN website/static/img/docusaurus-soc.png
  175. +66 −315 yarn.lock
2 changes: 1 addition & 1 deletion admin/new.docusaurus.io/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "new.docusaurus.io",
"version": "2.2.0",
"version": "2.3.1",
"private": true,
"scripts": {
"start": "npx --package netlify-cli netlify dev"
3 changes: 2 additions & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "2.2.0",
"version": "2.3.1",
"npmClient": "yarn",
"useWorkspaces": true,
"changelog": {
@@ -11,6 +11,7 @@
"pr: performance": ":running_woman: Performance",
"pr: polish": ":nail_care: Polish",
"pr: documentation": ":memo: Documentation",
"pr: dependencies": ":robot: Dependencies",
"pr: maintenance": ":wrench: Maintenance"
},
"cacheDir": ".changelog"
6 changes: 3 additions & 3 deletions packages/create-docusaurus/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-docusaurus",
"version": "2.2.0",
"version": "2.3.1",
"description": "Create Docusaurus apps easily.",
"type": "module",
"repository": {
@@ -22,8 +22,8 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/logger": "2.2.0",
"@docusaurus/utils": "2.2.0",
"@docusaurus/logger": "2.3.1",
"@docusaurus/utils": "2.3.1",
"commander": "^5.1.0",
"fs-extra": "^10.1.0",
"lodash": "^4.17.21",
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "docusaurus-2-classic-typescript-template",
"version": "2.2.0",
"version": "2.3.1",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@@ -15,16 +15,16 @@
"typecheck": "tsc"
},
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/preset-classic": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/preset-classic": "2.3.1",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.2.0",
"@docusaurus/module-type-aliases": "2.3.1",
"@tsconfig/docusaurus": "^1.0.5",
"typescript": "^4.7.4"
},
13 changes: 10 additions & 3 deletions packages/create-docusaurus/templates/classic/docusaurus.config.js
Original file line number Diff line number Diff line change
@@ -8,17 +8,22 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula');
const config = {
title: 'My Site',
tagline: 'Dinosaurs are cool',
favicon: 'img/favicon.ico',

// Set the production url of your site here
url: 'https://your-docusaurus-test-site.com',
// Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.ico',

// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'facebook', // Usually your GitHub org/user name.
projectName: 'docusaurus', // Usually your repo name.

onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',

// Even if you don't use internalization, you can use this field to set useful
// metadata like html lang. For example, if your site is Chinese, you may want
// to replace "en" with "zh-Hans".
@@ -56,6 +61,8 @@ const config = {
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
// Replace with your project's social card
image: 'img/docusaurus-social-card.jpg',
navbar: {
title: 'My Site',
logo: {
8 changes: 4 additions & 4 deletions packages/create-docusaurus/templates/classic/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "docusaurus-2-classic-template",
"version": "2.2.0",
"version": "2.3.1",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@@ -14,16 +14,16 @@
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/preset-classic": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/preset-classic": "2.3.1",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.2.0"
"@docusaurus/module-type-aliases": "2.3.1"
},
"browserslist": {
"production": [
Original file line number Diff line number Diff line change
@@ -13,17 +13,22 @@
const config = {
title: 'My Site',
tagline: 'The tagline of my site',
favicon: 'img/favicon.ico',

// Set the production url of your site here
url: 'https://your-docusaurus-test-site.com',
// Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.ico',

// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'facebook', // Usually your GitHub org/user name.
projectName: 'docusaurus', // Usually your repo name.

onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',

presets: [
[
'classic',
@@ -53,6 +58,7 @@ const config = {
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
image: 'img/docusaurus-social-card.jpg',
navbar: {
title: 'My Meta Project',
logo: {
6 changes: 3 additions & 3 deletions packages/create-docusaurus/templates/facebook/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "docusaurus-2-facebook-template",
"version": "2.2.0",
"version": "2.3.1",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@@ -18,8 +18,8 @@
"format:diff": "prettier --config .prettierrc --list-different \"**/*.{js,jsx,ts,tsx,md,mdx}\""
},
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/preset-classic": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/preset-classic": "2.3.1",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"react": "^17.0.2",
Original file line number Diff line number Diff line change
@@ -51,7 +51,11 @@ You can use absolute paths to reference images in the static directory (`static/

![Docusaurus logo](/img/docusaurus.png)

You can reference images relative to the current file as well, as shown in [the extra guides](../tutorial-extras/manage-docs-versions.md).
You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them:

```md
![Docusaurus logo](./img/docusaurus.png)
```

## Code Blocks

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion packages/docusaurus-cssnano-preset/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/cssnano-preset",
"version": "2.2.0",
"version": "2.3.1",
"description": "Advanced cssnano preset for maximum optimization.",
"main": "lib/index.js",
"license": "MIT",
2 changes: 1 addition & 1 deletion packages/docusaurus-logger/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/logger",
"version": "2.2.0",
"version": "2.3.1",
"description": "An encapsulated logger for semantically formatting console messages.",
"main": "./lib/index.js",
"repository": {
9 changes: 4 additions & 5 deletions packages/docusaurus-mdx-loader/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/mdx-loader",
"version": "2.2.0",
"version": "2.3.1",
"description": "Docusaurus Loader for MDX",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@@ -20,8 +20,8 @@
"dependencies": {
"@babel/parser": "^7.18.8",
"@babel/traverse": "^7.18.8",
"@docusaurus/logger": "2.2.0",
"@docusaurus/utils": "2.2.0",
"@docusaurus/logger": "2.3.1",
"@docusaurus/utils": "2.3.1",
"@mdx-js/mdx": "^1.6.22",
"escape-html": "^1.0.3",
"file-loader": "^6.2.0",
@@ -37,10 +37,9 @@
"webpack": "^5.73.0"
},
"devDependencies": {
"@docusaurus/types": "2.2.0",
"@docusaurus/types": "2.3.1",
"@types/escape-html": "^1.0.2",
"@types/mdast": "^3.0.10",
"@types/mermaid": "^8.2.9",
"@types/stringify-object": "^3.3.1",
"@types/unist": "^2.0.6",
"rehype-stringify": "^8.0.0",

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -42,3 +42,8 @@ exports[`admonitions remark plugin interpolation 1`] = `
"<p>Test admonition with interpolated title/body</p>
<admonition type="tip"><mdxAdmonitionTitle>My <code>interpolated</code> <strong>title</strong> &#x3C;button style={{color: "red"}} onClick={() => alert("click")}>test</mdxAdmonitionTitle><p><code>body</code> <strong>interpolated</strong> content</p></admonition>"
`;

exports[`admonitions remark plugin nesting 1`] = `
"<p>Test nested Admonitions</p>
<admonition type="info"><mdxAdmonitionTitle><strong>Weather</strong></mdxAdmonitionTitle><p>On nice days, you can enjoy skiing in the mountains.</p><admonition type="danger"><mdxAdmonitionTitle><em>Storms</em></mdxAdmonitionTitle><p>Take care of snowstorms...</p></admonition></admonition>"
`;
Original file line number Diff line number Diff line change
@@ -50,4 +50,9 @@ describe('admonitions remark plugin', () => {
const result = await processFixture('interpolation');
expect(result).toMatchSnapshot();
});

it('nesting', async () => {
const result = await processFixture('nesting');
expect(result).toMatchSnapshot();
});
});
48 changes: 42 additions & 6 deletions packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts
Original file line number Diff line number Diff line change
@@ -52,9 +52,20 @@ const plugin: Plugin = function plugin(
const options = normalizeOptions(optionsInput);

const keywords = Object.values(options.keywords).map(escapeRegExp).join('|');
const nestingChar = escapeRegExp(options.tag.slice(0, 1));
const tag = escapeRegExp(options.tag);
const regex = new RegExp(`${tag}(${keywords})(?: *(.*))?\n`);
const escapeTag = new RegExp(escapeRegExp(`\\${options.tag}`), 'g');

// resolve th nesting level of an opening tag
// ::: -> 0, :::: -> 1, ::::: -> 2 ...
const nestingLevelRegex = new RegExp(
`^${tag}(?<nestingLevel>${nestingChar}*)`,
);

const regex = new RegExp(`${tag}${nestingChar}*(${keywords})(?: *(.*))?\n`);
const escapeTag = new RegExp(
escapeRegExp(`\\${options.tag}${options.tag.slice(0, 1)}*`),
'g',
);

// The tokenizer is called on blocks to determine if there is an admonition
// present and create tags for it
@@ -77,6 +88,11 @@ const plugin: Plugin = function plugin(
];
const food = [];
const content = [];
// get the nesting level of the opening tag
const openingLevel =
nestingLevelRegex.exec(opening)!.groups!.nestingLevel!.length;
// used as a stack to keep track of nested admonitions
const nestingLevels: number[] = [openingLevel];

let newValue = value;
// consume lines until a closing tag
@@ -88,12 +104,32 @@ const plugin: Plugin = function plugin(
next !== -1 ? newValue.slice(idx + 1, next) : newValue.slice(idx + 1);
food.push(line);
newValue = newValue.slice(idx + 1);
// the closing tag is NOT part of the content
if (line.startsWith(options.tag)) {
break;
const nesting = nestingLevelRegex.exec(line);
idx = newValue.indexOf(NEWLINE);
if (!nesting) {
content.push(line);
continue;
}
const tagLevel = nesting.groups!.nestingLevel!.length;
// first level
if (nestingLevels.length === 0) {
nestingLevels.push(tagLevel);
content.push(line);
continue;
}
const currentLevel = nestingLevels[nestingLevels.length - 1]!;
if (tagLevel < currentLevel) {
// entering a nested admonition block
nestingLevels.push(tagLevel);
} else if (tagLevel === currentLevel) {
// closing a nested admonition block
nestingLevels.pop();
// the closing tag is NOT part of the content
if (nestingLevels.length === 0) {
break;
}
}
content.push(line);
idx = newValue.indexOf(NEWLINE);
}

// consume the processed tag and replace escape sequences
6 changes: 3 additions & 3 deletions packages/docusaurus-migrate/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/migrate",
"version": "2.2.0",
"version": "2.3.1",
"description": "A CLI tool to migrate from older versions of Docusaurus.",
"license": "MIT",
"engines": {
@@ -24,8 +24,8 @@
"dependencies": {
"@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.6",
"@docusaurus/logger": "2.2.0",
"@docusaurus/utils": "2.2.0",
"@docusaurus/logger": "2.3.1",
"@docusaurus/utils": "2.3.1",
"@mapbox/hast-util-to-jsx": "^2.0.0",
"color": "^4.2.3",
"commander": "^5.1.0",
Original file line number Diff line number Diff line change
@@ -120,7 +120,7 @@ exports[`migration CLI migrates complex website: write 1`] = `
]
}
],
"copyright": "Copyright © 2022 Facebook Inc.",
"copyright": "Copyright © 2023 Facebook Inc.",
"logo": {
"src": "img/docusaurus_monochrome.svg"
}
@@ -303,7 +303,7 @@ exports[`migration CLI migrates missing versions: write 1`] = `
]
}
],
"copyright": "Copyright © 2022 Facebook Inc.",
"copyright": "Copyright © 2023 Facebook Inc.",
"logo": {
"src": "img/docusaurus_monochrome.svg"
}
@@ -483,7 +483,7 @@ exports[`migration CLI migrates simple website: write 1`] = `
]
}
],
"copyright": "Copyright © 2022 Facebook Inc.",
"copyright": "Copyright © 2023 Facebook Inc.",
"logo": {
"src": "img/docusaurus_monochrome.svg"
}
4 changes: 2 additions & 2 deletions packages/docusaurus-module-type-aliases/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/module-type-aliases",
"version": "2.2.0",
"version": "2.3.1",
"description": "Docusaurus module type aliases.",
"types": "./src/index.d.ts",
"publishConfig": {
@@ -13,7 +13,7 @@
},
"dependencies": {
"@docusaurus/react-loadable": "5.5.2",
"@docusaurus/types": "2.2.0",
"@docusaurus/types": "2.3.1",
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router-config": "*",
16 changes: 8 additions & 8 deletions packages/docusaurus-plugin-client-redirects/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-client-redirects",
"version": "2.2.0",
"version": "2.3.1",
"description": "Client redirects plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@@ -18,18 +18,18 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/logger": "2.2.0",
"@docusaurus/utils": "2.2.0",
"@docusaurus/utils-common": "2.2.0",
"@docusaurus/utils-validation": "2.2.0",
"eta": "^1.12.3",
"@docusaurus/core": "2.3.1",
"@docusaurus/logger": "2.3.1",
"@docusaurus/utils": "2.3.1",
"@docusaurus/utils-common": "2.3.1",
"@docusaurus/utils-validation": "2.3.1",
"eta": "^2.0.0",
"fs-extra": "^10.1.0",
"lodash": "^4.17.21",
"tslib": "^2.4.0"
},
"devDependencies": {
"@docusaurus/types": "2.2.0"
"@docusaurus/types": "2.3.1"
},
"peerDependencies": {
"react": "^16.8.4 || ^17.0.0",
19 changes: 8 additions & 11 deletions packages/docusaurus-plugin-content-blog/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-blog",
"version": "2.2.0",
"version": "2.3.1",
"description": "Blog plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-content-blog.d.ts",
@@ -18,13 +18,13 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/logger": "2.2.0",
"@docusaurus/mdx-loader": "2.2.0",
"@docusaurus/types": "2.2.0",
"@docusaurus/utils": "2.2.0",
"@docusaurus/utils-common": "2.2.0",
"@docusaurus/utils-validation": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/logger": "2.3.1",
"@docusaurus/mdx-loader": "2.3.1",
"@docusaurus/types": "2.3.1",
"@docusaurus/utils": "2.3.1",
"@docusaurus/utils-common": "2.3.1",
"@docusaurus/utils-validation": "2.3.1",
"cheerio": "^1.0.0-rc.12",
"feed": "^4.2.2",
"fs-extra": "^10.1.0",
@@ -35,9 +35,6 @@
"utility-types": "^3.10.0",
"webpack": "^5.73.0"
},
"devDependencies": {
"escape-string-regexp": "^4.0.0"
},
"peerDependencies": {
"react": "^16.8.4 || ^17.0.0",
"react-dom": "^16.8.4 || ^17.0.0"

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
Original file line number Diff line number Diff line change
@@ -143,4 +143,56 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
).toMatchSnapshot();
fsMock.mockClear();
});

it('filters to the first two entries', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const outDir = path.join(siteDir, 'build-snap');
const siteConfig = {
title: 'Hello',
baseUrl: '/myBaseUrl/',
url: 'https://docusaurus.io',
favicon: 'image/favicon.ico',
};

// Build is quite difficult to mock, so we built the blog beforehand and
// copied the output to the fixture...
await testGenerateFeeds(
{
siteDir,
siteConfig,
i18n: DefaultI18N,
outDir,
} as LoadContext,
{
path: 'blog',
routeBasePath: 'blog',
tagsBasePath: 'tags',
authorsMapPath: 'authors.yml',
include: DEFAULT_OPTIONS.include,
exclude: DEFAULT_OPTIONS.exclude,
feedOptions: {
type: [feedType],
copyright: 'Copyright',
createFeedItems: async (params) => {
const {blogPosts, defaultCreateFeedItems, ...rest} = params;
const blogPostsFiltered = blogPosts.filter(
(item, index) => index < 2,
);
return defaultCreateFeedItems({
blogPosts: blogPostsFiltered,
...rest,
});
},
},
readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}),
truncateMarker: /<!--\s*truncate\s*-->/,
} as PluginOptions,
);

expect(
fsMock.mock.calls.map((call) => call[1] as string),
).toMatchSnapshot();
fsMock.mockClear();
});
});
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import escapeStringRegexp from 'escape-string-regexp';
import {escapeRegexp} from '@docusaurus/utils';
import {validateBlogPostFrontMatter} from '../frontMatter';
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';

@@ -57,7 +57,7 @@ function testField(params: {
} catch (err) {
// eslint-disable-next-line jest/no-conditional-expect
expect((err as Error).message).toMatch(
new RegExp(escapeStringRegexp(message)),
new RegExp(escapeRegexp(message)),
);
}
});
43 changes: 34 additions & 9 deletions packages/docusaurus-plugin-content-blog/src/feed.ts
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
import path from 'path';
import fs from 'fs-extra';
import logger from '@docusaurus/logger';
import {Feed, type Author as FeedAuthor, type Item as FeedItem} from 'feed';
import {Feed, type Author as FeedAuthor} from 'feed';
import {normalizeUrl, readOutputHTMLFile} from '@docusaurus/utils';
import {blogPostContainerID} from '@docusaurus/utils-common';
import {load as cheerioLoad} from 'cheerio';
@@ -18,6 +18,7 @@ import type {
PluginOptions,
Author,
BlogPost,
BlogFeedItem,
} from '@docusaurus/plugin-content-blog';

async function generateBlogFeed({
@@ -54,14 +55,39 @@ async function generateBlogFeed({
copyright: feedOptions.copyright,
});

const createFeedItems =
options.feedOptions.createFeedItems ?? defaultCreateFeedItems;

const feedItems = await createFeedItems({
blogPosts,
siteConfig,
outDir,
defaultCreateFeedItems,
});

feedItems.forEach(feed.addItem);

return feed;
}

async function defaultCreateFeedItems({
blogPosts,
siteConfig,
outDir,
}: {
blogPosts: BlogPost[];
siteConfig: DocusaurusConfig;
outDir: string;
}): Promise<BlogFeedItem[]> {
const {url: siteUrl} = siteConfig;

function toFeedAuthor(author: Author): FeedAuthor {
return {name: author.name, link: author.url, email: author.email};
}

await Promise.all(
return Promise.all(
blogPosts.map(async (post) => {
const {
id,
metadata: {
title: metadataTitle,
permalink,
@@ -79,10 +105,11 @@ async function generateBlogFeed({
);
const $ = cheerioLoad(content);

const feedItem: FeedItem = {
const link = normalizeUrl([siteUrl, permalink]);
const feedItem: BlogFeedItem = {
title: metadataTitle,
id,
link: normalizeUrl([siteUrl, permalink]),
id: link,
link,
date,
description,
// Atom feed demands the "term", while other feeds use "name"
@@ -99,9 +126,7 @@ async function generateBlogFeed({

return feedItem;
}),
).then((items) => items.forEach(feed.addItem));

return feed;
);
}

async function createBlogFeedFile({
1 change: 1 addition & 0 deletions packages/docusaurus-plugin-content-blog/src/options.ts
Original file line number Diff line number Diff line change
@@ -124,6 +124,7 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
.default(DEFAULT_OPTIONS.feedOptions.copyright),
}),
language: Joi.string(),
createFeedItems: Joi.function(),
}).default(DEFAULT_OPTIONS.feedOptions),
authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),
Original file line number Diff line number Diff line change
@@ -9,12 +9,19 @@ declare module '@docusaurus/plugin-content-blog' {
import type {LoadedMDXContent} from '@docusaurus/mdx-loader';
import type {MDXOptions} from '@docusaurus/mdx-loader';
import type {FrontMatterTag, Tag} from '@docusaurus/utils';
import type {Plugin, LoadContext} from '@docusaurus/types';
import type {DocusaurusConfig, Plugin, LoadContext} from '@docusaurus/types';
import type {Item as FeedItem} from 'feed';
import type {Overwrite} from 'utility-types';

export type Assets = {
/**
* If `metadata.image` is a collocated image path, this entry will be the
* If `metadata.yarn workspace website typecheck
4
yarn workspace v1.22.19yarn workspace website typecheck
4
yarn workspace v1.22.19yarn workspace website typecheck
4
yarn workspace v1.22.19image` is a collocated image path, this entry will be the
* bundler-generated image path. Otherwise, it's empty, and the image URL
* should be accessed through `frontMatter.image`.
*/
@@ -255,6 +262,24 @@ declare module '@docusaurus/plugin-content-blog' {
copyright: string;
/** Language of the feed. */
language?: string;
/** Allow control over the construction of BlogFeedItems */
createFeedItems?: CreateFeedItemsFn;
};

type DefaultCreateFeedItemsParams = {
blogPosts: BlogPost[];
siteConfig: DocusaurusConfig;
outDir: string;
};

type CreateFeedItemsFn = (
params: CreateFeedItemsParams,
) => Promise<BlogFeedItem[]>;

type CreateFeedItemsParams = DefaultCreateFeedItemsParams & {
defaultCreateFeedItems: (
params: DefaultCreateFeedItemsParams,
) => Promise<BlogFeedItem[]>;
};

/**
@@ -436,6 +461,8 @@ declare module '@docusaurus/plugin-content-blog' {
content: string;
};

export type BlogFeedItem = FeedItem;

export type BlogPaginatedMetadata = {
/** Title of the entire blog. */
readonly blogTitle: string;
17 changes: 8 additions & 9 deletions packages/docusaurus-plugin-content-docs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-docs",
"version": "2.2.0",
"version": "2.3.1",
"description": "Docs plugin for Docusaurus.",
"main": "lib/index.js",
"sideEffects": false,
@@ -35,13 +35,13 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/logger": "2.2.0",
"@docusaurus/mdx-loader": "2.2.0",
"@docusaurus/module-type-aliases": "2.2.0",
"@docusaurus/types": "2.2.0",
"@docusaurus/utils": "2.2.0",
"@docusaurus/utils-validation": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/logger": "2.3.1",
"@docusaurus/mdx-loader": "2.3.1",
"@docusaurus/module-type-aliases": "2.3.1",
"@docusaurus/types": "2.3.1",
"@docusaurus/utils": "2.3.1",
"@docusaurus/utils-validation": "2.3.1",
"@types/react-router-config": "^5.0.6",
"combine-promises": "^1.1.0",
"fs-extra": "^10.1.0",
@@ -56,7 +56,6 @@
"@types/js-yaml": "^4.0.5",
"@types/picomatch": "^2.3.0",
"commander": "^5.1.0",
"escape-string-regexp": "^4.0.0",
"picomatch": "^2.3.1",
"shelljs": "^0.8.5"
},
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import escapeStringRegexp from 'escape-string-regexp';
import {escapeRegexp} from '@docusaurus/utils';
import {validateDocFrontMatter} from '../frontMatter';
import type {DocFrontMatter} from '@docusaurus/plugin-content-docs';

@@ -57,7 +57,7 @@ function testField(params: {
} catch (err) {
// eslint-disable-next-line jest/no-conditional-expect
expect((err as Error).message).toMatch(
new RegExp(escapeStringRegexp(message)),
new RegExp(escapeRegexp(message)),
);
}
});
9 changes: 5 additions & 4 deletions packages/docusaurus-plugin-content-docs/src/index.ts
Original file line number Diff line number Diff line change
@@ -336,12 +336,13 @@ export default async function pluginContentDocs(
};

function createMDXLoaderRule(): RuleSetRule {
const contentDirs = versionsMetadata.flatMap(getContentPathList);
const contentDirs = versionsMetadata
.flatMap(getContentPathList)
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
.map(addTrailingPathSeparator);
return {
test: /\.mdx?$/i,
include: contentDirs
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
.map(addTrailingPathSeparator),
include: contentDirs,
use: [
getJSLoader({isServer}),
{
12 changes: 6 additions & 6 deletions packages/docusaurus-plugin-content-pages/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-pages",
"version": "2.2.0",
"version": "2.3.1",
"description": "Pages plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-content-pages.d.ts",
@@ -18,11 +18,11 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/mdx-loader": "2.2.0",
"@docusaurus/types": "2.2.0",
"@docusaurus/utils": "2.2.0",
"@docusaurus/utils-validation": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/mdx-loader": "2.3.1",
"@docusaurus/types": "2.3.1",
"@docusaurus/utils": "2.3.1",
"@docusaurus/utils-validation": "2.3.1",
"fs-extra": "^10.1.0",
"tslib": "^2.4.0",
"webpack": "^5.73.0"
8 changes: 4 additions & 4 deletions packages/docusaurus-plugin-debug/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-debug",
"version": "2.2.0",
"version": "2.3.1",
"description": "Debug plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-debug.d.ts",
@@ -20,9 +20,9 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/types": "2.2.0",
"@docusaurus/utils": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/types": "2.3.1",
"@docusaurus/utils": "2.3.1",
"fs-extra": "^10.1.0",
"react-json-view": "^1.21.3",
"tslib": "^2.4.0"
8 changes: 4 additions & 4 deletions packages/docusaurus-plugin-google-analytics/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-google-analytics",
"version": "2.2.0",
"version": "2.3.1",
"description": "Global analytics (analytics.js) plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@@ -18,9 +18,9 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/types": "2.2.0",
"@docusaurus/utils-validation": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/types": "2.3.1",
"@docusaurus/utils-validation": "2.3.1",
"tslib": "^2.4.0"
},
"peerDependencies": {
8 changes: 4 additions & 4 deletions packages/docusaurus-plugin-google-gtag/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-google-gtag",
"version": "2.2.0",
"version": "2.3.1",
"description": "Global Site Tag (gtag.js) plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@@ -18,9 +18,9 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/types": "2.2.0",
"@docusaurus/utils-validation": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/types": "2.3.1",
"@docusaurus/utils-validation": "2.3.1",
"tslib": "^2.4.0"
},
"peerDependencies": {
3 changes: 3 additions & 0 deletions packages/docusaurus-plugin-google-tag-manager/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.tsbuildinfo*
tsconfig*
__tests__
7 changes: 7 additions & 0 deletions packages/docusaurus-plugin-google-tag-manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# `@docusaurus/plugin-google-tag-manager`

Google Tag Manager plugin for Docusaurus.

## Usage

See [plugin-google-tag-manager documentation](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-google-tag-manager).
33 changes: 33 additions & 0 deletions packages/docusaurus-plugin-google-tag-manager/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@docusaurus/plugin-google-tag-manager",
"version": "2.3.1",
"description": "Google Tag Manager (gtm.js) plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsc --build",
"watch": "tsc --build --watch"
},
"repository": {
"type": "git",
"url": "https://github.com/facebook/docusaurus.git",
"directory": "packages/docusaurus-plugin-google-tag-manager"
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.3.1",
"@docusaurus/types": "2.3.1",
"@docusaurus/utils-validation": "2.3.1",
"tslib": "^2.4.0"
},
"peerDependencies": {
"react": "^16.8.4 || ^17.0.0",
"react-dom": "^16.8.4 || ^17.0.0"
},
"engines": {
"node": ">=16.14"
}
}
78 changes: 78 additions & 0 deletions packages/docusaurus-plugin-google-tag-manager/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {Joi} from '@docusaurus/utils-validation';
import type {
LoadContext,
Plugin,
OptionValidationContext,
} from '@docusaurus/types';
import type {PluginOptions, Options} from './options';

export default function pluginGoogleAnalytics(
context: LoadContext,
options: PluginOptions,
): Plugin {
const {containerId} = options;
const isProd = process.env.NODE_ENV === 'production';

return {
name: 'docusaurus-plugin-google-tag-manager',

contentLoaded({actions}) {
actions.setGlobalData(options);
},

injectHtmlTags() {
if (!isProd) {
return {};
}
return {
preBodyTags: [
{
tagName: 'noscript',
innerHTML: `<iframe src="https://www.googletagmanager.com/ns.html?id=${containerId}" height="0" width="0" style="display:none;visibility:hidden"></iframe>`,
},
],
headTags: [
{
tagName: 'link',
attributes: {
rel: 'preconnect',
href: 'https://www.googletagmanager.com',
},
},
{
tagName: 'script',
innerHTML: `window.dataLayer = window.dataLayer || [];`,
},
{
tagName: 'script',
innerHTML: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','${containerId}');`,
},
],
};
},
};
}

const pluginOptionsSchema = Joi.object<PluginOptions>({
containerId: Joi.string().required(),
});

export function validateOptions({
validate,
options,
}: OptionValidationContext<Options, PluginOptions>): PluginOptions {
return validate(pluginOptionsSchema, options);
}

export type {PluginOptions, Options};
12 changes: 12 additions & 0 deletions packages/docusaurus-plugin-google-tag-manager/src/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

export type PluginOptions = {
containerId: string;
};

export type Options = Partial<PluginOptions>;
8 changes: 8 additions & 0 deletions packages/docusaurus-plugin-google-tag-manager/src/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/// <reference types="@docusaurus/module-type-aliases" />
15 changes: 15 additions & 0 deletions packages/docusaurus-plugin-google-tag-manager/tsconfig.client.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"noEmit": false,
"composite": true,
"incremental": true,
"tsBuildInfoFile": "./lib/.tsbuildinfo-client",
"module": "esnext",
"target": "esnext",
"rootDir": "src",
"outDir": "lib"
},
"include": ["src/*.d.ts"],
"exclude": ["**/__tests__/**"]
}
13 changes: 13 additions & 0 deletions packages/docusaurus-plugin-google-tag-manager/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.json",
"references": [{"path": "./tsconfig.client.json"}],
"compilerOptions": {
"noEmit": false,
"incremental": true,
"tsBuildInfoFile": "./lib/.tsbuildinfo",
"rootDir": "src",
"outDir": "lib"
},
"include": ["src"],
"exclude": ["**/__tests__/**"]
}
14 changes: 7 additions & 7 deletions packages/docusaurus-plugin-ideal-image/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-ideal-image",
"version": "2.2.0",
"version": "2.3.1",
"description": "Docusaurus Plugin to generate an almost ideal image (responsive, lazy-loading, and low quality placeholder).",
"main": "lib/index.js",
"types": "src/plugin-ideal-image.d.ts",
@@ -20,20 +20,20 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/lqip-loader": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/lqip-loader": "2.3.1",
"@docusaurus/responsive-loader": "^1.7.0",
"@docusaurus/theme-translations": "2.2.0",
"@docusaurus/types": "2.2.0",
"@docusaurus/utils-validation": "2.2.0",
"@docusaurus/theme-translations": "2.3.1",
"@docusaurus/types": "2.3.1",
"@docusaurus/utils-validation": "2.3.1",
"@endiliey/react-ideal-image": "^0.0.11",
"react-waypoint": "^10.3.0",
"sharp": "^0.30.7",
"tslib": "^2.4.0",
"webpack": "^5.73.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.2.0",
"@docusaurus/module-type-aliases": "2.3.1",
"fs-extra": "^10.1.0"
},
"peerDependencies": {
16 changes: 8 additions & 8 deletions packages/docusaurus-plugin-pwa/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-pwa",
"version": "2.2.0",
"version": "2.3.1",
"description": "Docusaurus Plugin to add PWA support.",
"main": "lib/index.js",
"types": "src/plugin-pwa.d.ts",
@@ -22,12 +22,12 @@
"dependencies": {
"@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.6",
"@docusaurus/core": "2.2.0",
"@docusaurus/theme-common": "2.2.0",
"@docusaurus/theme-translations": "2.2.0",
"@docusaurus/types": "2.2.0",
"@docusaurus/utils": "2.2.0",
"@docusaurus/utils-validation": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/theme-common": "2.3.1",
"@docusaurus/theme-translations": "2.3.1",
"@docusaurus/types": "2.3.1",
"@docusaurus/utils": "2.3.1",
"@docusaurus/utils-validation": "2.3.1",
"babel-loader": "^8.2.5",
"clsx": "^1.2.1",
"core-js": "^3.23.3",
@@ -40,7 +40,7 @@
"workbox-window": "^6.5.3"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.2.0",
"@docusaurus/module-type-aliases": "2.3.1",
"fs-extra": "^10.1.0"
},
"peerDependencies": {
14 changes: 7 additions & 7 deletions packages/docusaurus-plugin-sitemap/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-sitemap",
"version": "2.2.0",
"version": "2.3.1",
"description": "Simple sitemap generation plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@@ -18,12 +18,12 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/logger": "2.2.0",
"@docusaurus/types": "2.2.0",
"@docusaurus/utils": "2.2.0",
"@docusaurus/utils-common": "2.2.0",
"@docusaurus/utils-validation": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/logger": "2.3.1",
"@docusaurus/types": "2.3.1",
"@docusaurus/utils": "2.3.1",
"@docusaurus/utils-common": "2.3.1",
"@docusaurus/utils-validation": "2.3.1",
"fs-extra": "^10.1.0",
"sitemap": "^7.1.1",
"tslib": "^2.4.0"
27 changes: 14 additions & 13 deletions packages/docusaurus-preset-classic/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/preset-classic",
"version": "2.2.0",
"version": "2.3.1",
"description": "Classic preset for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@@ -18,18 +18,19 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/plugin-content-blog": "2.2.0",
"@docusaurus/plugin-content-docs": "2.2.0",
"@docusaurus/plugin-content-pages": "2.2.0",
"@docusaurus/plugin-debug": "2.2.0",
"@docusaurus/plugin-google-analytics": "2.2.0",
"@docusaurus/plugin-google-gtag": "2.2.0",
"@docusaurus/plugin-sitemap": "2.2.0",
"@docusaurus/theme-classic": "2.2.0",
"@docusaurus/theme-common": "2.2.0",
"@docusaurus/theme-search-algolia": "2.2.0",
"@docusaurus/types": "2.2.0"
"@docusaurus/core": "2.3.1",
"@docusaurus/plugin-content-blog": "2.3.1",
"@docusaurus/plugin-content-docs": "2.3.1",
"@docusaurus/plugin-content-pages": "2.3.1",
"@docusaurus/plugin-debug": "2.3.1",
"@docusaurus/plugin-google-analytics": "2.3.1",
"@docusaurus/plugin-google-gtag": "2.3.1",
"@docusaurus/plugin-google-tag-manager": "2.3.1",
"@docusaurus/plugin-sitemap": "2.3.1",
"@docusaurus/theme-classic": "2.3.1",
"@docusaurus/theme-common": "2.3.1",
"@docusaurus/theme-search-algolia": "2.3.1",
"@docusaurus/types": "2.3.1"
},
"peerDependencies": {
"react": "^16.8.4 || ^17.0.0",
11 changes: 10 additions & 1 deletion packages/docusaurus-preset-classic/src/index.ts
Original file line number Diff line number Diff line change
@@ -40,6 +40,7 @@ export default function preset(
theme,
googleAnalytics,
gtag,
googleTagManager,
...rest
} = opts;

@@ -80,14 +81,22 @@ export default function preset(
if (gtag) {
plugins.push(makePluginConfig('@docusaurus/plugin-google-gtag', gtag));
}
if (googleTagManager) {
plugins.push(
makePluginConfig(
'@docusaurus/plugin-google-tag-manager',
googleTagManager,
),
);
}
if (isProd && sitemap !== false) {
plugins.push(makePluginConfig('@docusaurus/plugin-sitemap', sitemap));
}
if (Object.keys(rest).length > 0) {
throw new Error(
`Unrecognized keys ${Object.keys(rest).join(
', ',
)} found in preset-classic configuration. The allowed keys are debug, docs, blog, pages, sitemap, theme, googleAnalytics, gtag. Check the documentation: https://docusaurus.io/docs/using-plugins#docusauruspreset-classic for more information on how to configure individual plugins.`,
)} found in preset-classic configuration. The allowed keys are debug, docs, blog, pages, sitemap, theme, googleAnalytics, gtag, and googleTagManager. Check the documentation: https://docusaurus.io/docs/using-plugins#docusauruspreset-classic for more information on how to configure individual plugins.`,
);
}

2 changes: 2 additions & 0 deletions packages/docusaurus-preset-classic/src/options.ts
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ import type {Options as PagesPluginOptions} from '@docusaurus/plugin-content-pag
import type {Options as SitemapPluginOptions} from '@docusaurus/plugin-sitemap';
import type {Options as GAPluginOptions} from '@docusaurus/plugin-google-analytics';
import type {Options as GtagPluginOptions} from '@docusaurus/plugin-google-gtag';
import type {Options as GTMPluginOptions} from '@docusaurus/plugin-google-tag-manager';
import type {Options as ThemeOptions} from '@docusaurus/theme-classic';
import type {ThemeConfig as BaseThemeConfig} from '@docusaurus/types';
import type {UserThemeConfig as ClassicThemeConfig} from '@docusaurus/theme-common';
@@ -42,6 +43,7 @@ export type Options = {
* is present.
*/
gtag?: GtagPluginOptions;
googleTagManager?: GTMPluginOptions;
};

export type ThemeConfig = BaseThemeConfig &
2 changes: 1 addition & 1 deletion packages/docusaurus-remark-plugin-npm2yarn/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/remark-plugin-npm2yarn",
"version": "2.2.0",
"version": "2.3.1",
"description": "Remark plugin for converting npm commands to Yarn commands as tabs.",
"main": "lib/index.js",
"publishConfig": {
26 changes: 13 additions & 13 deletions packages/docusaurus-theme-classic/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-classic",
"version": "2.2.0",
"version": "2.3.1",
"description": "Classic theme for Docusaurus",
"main": "lib/index.js",
"types": "src/theme-classic.d.ts",
@@ -20,18 +20,18 @@
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
},
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/mdx-loader": "2.2.0",
"@docusaurus/module-type-aliases": "2.2.0",
"@docusaurus/plugin-content-blog": "2.2.0",
"@docusaurus/plugin-content-docs": "2.2.0",
"@docusaurus/plugin-content-pages": "2.2.0",
"@docusaurus/theme-common": "2.2.0",
"@docusaurus/theme-translations": "2.2.0",
"@docusaurus/types": "2.2.0",
"@docusaurus/utils": "2.2.0",
"@docusaurus/utils-common": "2.2.0",
"@docusaurus/utils-validation": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/mdx-loader": "2.3.1",
"@docusaurus/module-type-aliases": "2.3.1",
"@docusaurus/plugin-content-blog": "2.3.1",
"@docusaurus/plugin-content-docs": "2.3.1",
"@docusaurus/plugin-content-pages": "2.3.1",
"@docusaurus/theme-common": "2.3.1",
"@docusaurus/theme-translations": "2.3.1",
"@docusaurus/types": "2.3.1",
"@docusaurus/utils": "2.3.1",
"@docusaurus/utils-common": "2.3.1",
"@docusaurus/utils-validation": "2.3.1",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"copy-text-to-clipboard": "^3.0.1",
Original file line number Diff line number Diff line change
@@ -62,7 +62,7 @@ describe('themeConfig', () => {
textColor: '#000',
isCloseable: true,
},
image: 'img/docusaurus-soc.png',
image: 'img/docusaurus-social-card.jpg',
navbar: {
style: 'primary',
hideOnScroll: true,
40 changes: 39 additions & 1 deletion packages/docusaurus-theme-classic/src/getSwizzleConfig.ts
Original file line number Diff line number Diff line change
@@ -20,6 +20,14 @@ export default function getSwizzleConfig(): SwizzleConfig {
description:
'The component used to render multi-line code blocks, generally used in Markdown files.',
},
'CodeBlock/Content': {
actions: {
eject: 'unsafe',
wrap: 'forbidden',
},
description:
'The folder containing components responsible for rendering different types of CodeBlock content.',
},
ColorModeToggle: {
actions: {
eject: 'safe',
@@ -28,6 +36,14 @@ export default function getSwizzleConfig(): SwizzleConfig {
description:
'The color mode toggle to switch between light and dark mode.',
},
'DocBreadcrumbs/Items': {
actions: {
eject: 'unsafe',
wrap: 'forbidden', // Can't wrap a folder
},
description:
'The components responsible for rendering the breadcrumb items',
},
DocCardList: {
actions: {
eject: 'safe',
@@ -36,6 +52,17 @@ export default function getSwizzleConfig(): SwizzleConfig {
description:
'The component responsible for rendering a list of sidebar items cards.\nNotable used on the category generated-index pages.',
},
'DocItem/TOC': {
actions: {
// Forbidden because it's a parent folder, makes the CLI crash atm
// TODO the CLI should rather support --eject
// Subfolders can be swizzled
eject: 'forbidden',
wrap: 'forbidden',
},
description:
'The DocItem TOC is not directly swizzle-able, but you can swizzle its sub-components.',
},
DocSidebar: {
actions: {
eject: 'unsafe', // Too much technical code in sidebar, not very safe atm
@@ -101,6 +128,17 @@ export default function getSwizzleConfig(): SwizzleConfig {
},
description: 'The footer logo',
},
Icon: {
actions: {
// Forbidden because it's a parent folder, makes the CLI crash atm
// TODO the CLI should rather support --eject
// Subfolders can be swizzled
eject: 'forbidden',
wrap: 'forbidden',
},
description:
'The Icon folder is not directly swizzle-able, but you can swizzle its sub-components.',
},
'Icon/Arrow': {
actions: {
eject: 'safe',
@@ -220,7 +258,7 @@ export default function getSwizzleConfig(): SwizzleConfig {
wrap: 'forbidden',
},
description:
'The Navbar item components mapping. Can be ejected to add custom navbar item types. See https://github.com/facebook/docusaurus/issues/7227.',
'The Navbar item components mapping. Can be ejected to add custom navbar item types.\nSee https://github.com/facebook/docusaurus/issues/7227.',
},
NotFound: {
actions: {
33 changes: 8 additions & 25 deletions packages/docusaurus-theme-classic/src/theme-classic.d.ts
Original file line number Diff line number Diff line change
@@ -1123,38 +1123,17 @@ declare module '@theme/Mermaid' {
}

declare module '@theme/TabItem' {
import type {ReactNode} from 'react';
import type {TabItemProps} from '@docusaurus/theme-common/internal';

export interface Props {
readonly children: ReactNode;
readonly value: string;
readonly default?: boolean;
readonly label?: string;
readonly hidden?: boolean;
readonly className?: string;
readonly attributes?: {[key: string]: unknown};
}
export interface Props extends TabItemProps {}

export default function TabItem(props: Props): JSX.Element;
}

declare module '@theme/Tabs' {
import type {ReactElement} from 'react';
import type {Props as TabItemProps} from '@theme/TabItem';
import type {TabsProps} from '@docusaurus/theme-common/internal';

export interface Props {
readonly lazy?: boolean;
readonly block?: boolean;
readonly children: readonly ReactElement<TabItemProps>[];
readonly defaultValue?: string | null;
readonly values?: readonly {
value: string;
label?: string;
attributes?: {[key: string]: unknown};
}[];
readonly groupId?: string;
readonly className?: string;
}
export interface Props extends TabsProps {}

export default function Tabs(props: Props): JSX.Element;
}
@@ -1392,3 +1371,7 @@ declare module '@theme/prism-include-languages' {
PrismObject: typeof PrismNamespace,
): void;
}

declare module '@theme/DocBreadcrumbs/Items/Home' {
export default function HomeBreadcrumbItem(): JSX.Element;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
import {translate} from '@docusaurus/Translate';
import IconHome from '@theme/Icon/Home';

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

export default function HomeBreadcrumbItem(): JSX.Element {
const homeHref = useBaseUrl('/');

return (
<li className="breadcrumbs__item">
<Link
aria-label={translate({
id: 'theme.docs.breadcrumbs.home',
message: 'Home page',
description: 'The ARIA label for the home page in the breadcrumbs',
})}
className="breadcrumbs__link"
href={homeHref}>
<IconHome className={styles.breadcrumbHomeIcon} />
</Link>
</li>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

.breadcrumbHomeIcon {
position: relative;
top: 1px;
vertical-align: top;
height: 1.1rem;
width: 1.1rem;
}
Original file line number Diff line number Diff line change
@@ -13,9 +13,8 @@ import {
useHomePageRoute,
} from '@docusaurus/theme-common/internal';
import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
import {translate} from '@docusaurus/Translate';
import IconHome from '@theme/Icon/Home';
import HomeBreadcrumbItem from '@theme/DocBreadcrumbs/Items/Home';

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

@@ -79,24 +78,6 @@ function BreadcrumbsItem({
);
}

function HomeBreadcrumbItem() {
const homeHref = useBaseUrl('/');
return (
<li className="breadcrumbs__item">
<Link
aria-label={translate({
id: 'theme.docs.breadcrumbs.home',
message: 'Home page',
description: 'The ARIA label for the home page in the breadcrumbs',
})}
className={clsx('breadcrumbs__link', styles.breadcrumbsItemLink)}
href={homeHref}>
<IconHome className={styles.breadcrumbHomeIcon} />
</Link>
</li>
);
}

export default function DocBreadcrumbs(): JSX.Element | null {
const breadcrumbs = useSidebarBreadcrumbs();
const homePageRoute = useHomePageRoute();
Original file line number Diff line number Diff line change
@@ -9,11 +9,3 @@
--ifm-breadcrumb-size-multiplier: 0.8;
margin-bottom: 0.8rem;
}

.breadcrumbHomeIcon {
position: relative;
top: 1px;
vertical-align: top;
height: 1.1rem;
width: 1.1rem;
}
Original file line number Diff line number Diff line change
@@ -7,10 +7,11 @@

@media (min-width: 997px) {
.expandButton {
position: sticky;
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 100%;
max-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
Original file line number Diff line number Diff line change
@@ -60,15 +60,20 @@ export default function DocPageLayoutSidebar({
}
}}>
<ResetOnSidebarChange>
<DocSidebar
sidebar={sidebar}
path={pathname}
onCollapse={toggleSidebar}
isHidden={hiddenSidebar}
/>
<div
className={clsx(
styles.sidebarViewport,
hiddenSidebar && styles.sidebarViewportHidden,
)}>
<DocSidebar
sidebar={sidebar}
path={pathname}
onCollapse={toggleSidebar}
isHidden={hiddenSidebar}
/>
{hiddenSidebar && <ExpandButton toggleSidebar={toggleSidebar} />}
</div>
</ResetOnSidebarChange>

{hiddenSidebar && <ExpandButton toggleSidebar={toggleSidebar} />}
</aside>
);
}
Original file line number Diff line number Diff line change
@@ -29,4 +29,11 @@
width: var(--doc-sidebar-hidden-width);
cursor: pointer;
}

.sidebarViewport {
top: 0;
position: sticky;
height: 100%;
max-height: 100vh;
}
}
Original file line number Diff line number Diff line change
@@ -12,4 +12,5 @@

.docsWrapper {
display: flex;
flex: 1 0 auto;
}
Original file line number Diff line number Diff line change
@@ -43,4 +43,5 @@

.collapseSidebarButton {
display: none;
margin: 0;
}
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import {
useAnnouncementBar,
useScrollPosition,
} from '@docusaurus/theme-common/internal';
import {translate} from '@docusaurus/Translate';
import DocSidebarItems from '@theme/DocSidebarItems';
import type {Props} from '@theme/DocSidebar/Desktop/Content';

@@ -41,6 +42,11 @@ export default function DocSidebarDesktopContent({

return (
<nav
aria-label={translate({
id: 'theme.docs.sidebar.navAriaLabel',
message: 'Docs sidebar',
description: 'The ARIA label for the sidebar navigation',
})}
className={clsx(
'menu thin-scrollbar',
styles.menu,
Original file line number Diff line number Diff line change
@@ -9,13 +9,9 @@
.sidebar {
display: flex;
flex-direction: column;
max-height: 100vh;
height: 100%;
position: sticky;
top: 0;
padding-top: var(--ifm-navbar-height);
width: var(--doc-sidebar-width);
transition: opacity 50ms ease;
}

.sidebarWithHideableNavbar {
@@ -24,8 +20,6 @@

.sidebarHidden {
opacity: 0;
height: 0;
overflow: hidden;
visibility: hidden;
}

26 changes: 18 additions & 8 deletions packages/docusaurus-theme-classic/src/theme/Heading/index.tsx
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import React from 'react';
import clsx from 'clsx';
import {translate} from '@docusaurus/Translate';
import {useThemeConfig} from '@docusaurus/theme-common';
import Link from '@docusaurus/Link';
import type {Props} from '@theme/Heading';

import styles from './styles.module.css';
@@ -22,6 +23,17 @@ export default function Heading({as: As, id, ...props}: Props): JSX.Element {
return <As {...props} id={undefined} />;
}

const anchorTitle = translate(
{
id: 'theme.common.headingLinkTitle',
message: 'Direct link to {heading}',
description: 'Title for link to heading',
},
{
heading: typeof props.children === 'string' ? props.children : id,
},
);

return (
<As
{...props}
@@ -30,19 +42,17 @@ export default function Heading({as: As, id, ...props}: Props): JSX.Element {
hideOnScroll
? styles.anchorWithHideOnScrollNavbar
: styles.anchorWithStickyNavbar,
props.className,
)}
id={id}>
{props.children}
<a
<Link
className="hash-link"
href={`#${id}`}
title={translate({
id: 'theme.common.headingLinkTitle',
message: 'Direct link to heading',
description: 'Title for link to heading',
})}>
to={`#${id}`}
aria-label={anchorTitle}
title={anchorTitle}>
&#8203;
</a>
</Link>
</As>
);
}
Original file line number Diff line number Diff line change
@@ -9,7 +9,6 @@ import React from 'react';
import {composeProviders} from '@docusaurus/theme-common';
import {
ColorModeProvider,
TabGroupChoiceProvider,
AnnouncementBarProvider,
DocsPreferredVersionContextProvider,
ScrollControllerProvider,
@@ -21,7 +20,6 @@ import type {Props} from '@theme/Layout/Provider';
const Provider = composeProviders([
ColorModeProvider,
AnnouncementBarProvider,
TabGroupChoiceProvider,
ScrollControllerProvider,
DocsPreferredVersionContextProvider,
PluginHtmlClassNameProvider,
Original file line number Diff line number Diff line change
@@ -12,6 +12,8 @@ body {

.mainWrapper {
flex: 1 0 auto;
display: flex;
flex-direction: column;
}

/* Docusaurus-specific utility class */
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import {
useHideableNavbar,
useNavbarMobileSidebar,
} from '@docusaurus/theme-common/internal';
import {translate} from '@docusaurus/Translate';
import NavbarMobileSidebar from '@theme/Navbar/MobileSidebar';
import type {Props} from '@theme/Navbar/Layout';

@@ -36,6 +37,11 @@ export default function NavbarLayout({children}: Props): JSX.Element {
return (
<nav
ref={navbarRef}
aria-label={translate({
id: 'theme.NavBar.navAriaLabel',
message: 'Main',
description: 'The ARIA label for the main navigation',
})}
className={clsx(
'navbar',
'navbar--fixed-top',
Original file line number Diff line number Diff line change
@@ -5,23 +5,37 @@
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import React, {type ReactNode} from 'react';
import renderer from 'react-test-renderer';
import {
TabGroupChoiceProvider,
ScrollControllerProvider,
} from '@docusaurus/theme-common/internal';
import {ScrollControllerProvider} from '@docusaurus/theme-common/internal';
import {StaticRouter} from 'react-router-dom';
import Tabs from '../index';
import TabItem from '../../TabItem';

function TestProviders({
children,
pathname = '/',
}: {
children: ReactNode;
pathname?: string;
}) {
return (
<StaticRouter location={{pathname}}>
<ScrollControllerProvider>{children}</ScrollControllerProvider>
</StaticRouter>
);
}

describe('Tabs', () => {
it('rejects bad Tabs child', () => {
expect(() => {
renderer.create(
<Tabs>
<div>Naughty</div>
<TabItem value="good">Good</TabItem>
</Tabs>,
<TestProviders>
<Tabs>
<div>Naughty</div>
<TabItem value="good">Good</TabItem>
</Tabs>
</TestProviders>,
);
}).toThrowErrorMatchingInlineSnapshot(
`"Docusaurus error: Bad <Tabs> child <div>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop."`,
@@ -30,10 +44,12 @@ describe('Tabs', () => {
it('rejects bad Tabs defaultValue', () => {
expect(() => {
renderer.create(
<Tabs defaultValue="bad">
<TabItem value="v1">Tab 1</TabItem>
<TabItem value="v2">Tab 2</TabItem>
</Tabs>,
<TestProviders>
<Tabs defaultValue="bad">
<TabItem value="v1">Tab 1</TabItem>
<TabItem value="v2">Tab 2</TabItem>
</Tabs>
</TestProviders>,
);
}).toThrowErrorMatchingInlineSnapshot(
`"Docusaurus error: The <Tabs> has a defaultValue "bad" but none of its children has the corresponding value. Available values are: v1, v2. If you intend to show no default tab, use defaultValue={null} instead."`,
@@ -42,14 +58,16 @@ describe('Tabs', () => {
it('rejects duplicate values', () => {
expect(() => {
renderer.create(
<Tabs>
<TabItem value="v1">Tab 1</TabItem>
<TabItem value="v2">Tab 2</TabItem>
<TabItem value="v3">Tab 3</TabItem>
<TabItem value="v4">Tab 4</TabItem>
<TabItem value="v1">Tab 5</TabItem>
<TabItem value="v2">Tab 6</TabItem>
</Tabs>,
<TestProviders>
<Tabs>
<TabItem value="v1">Tab 1</TabItem>
<TabItem value="v2">Tab 2</TabItem>
<TabItem value="v3">Tab 3</TabItem>
<TabItem value="v4">Tab 4</TabItem>
<TabItem value="v1">Tab 5</TabItem>
<TabItem value="v2">Tab 6</TabItem>
</Tabs>
</TestProviders>,
);
}).toThrowErrorMatchingInlineSnapshot(
`"Docusaurus error: Duplicate values "v1, v2" found in <Tabs>. Every value needs to be unique."`,
@@ -58,54 +76,52 @@ describe('Tabs', () => {
it('accepts valid Tabs config', () => {
expect(() => {
renderer.create(
<ScrollControllerProvider>
<TabGroupChoiceProvider>
<Tabs>
<TabItem value="v1">Tab 1</TabItem>
<TabItem value="v2">Tab 2</TabItem>
</Tabs>
<Tabs>
<TabItem value="v1">Tab 1</TabItem>
<TabItem value="v2" default>
Tab 2
</TabItem>
</Tabs>
<Tabs defaultValue="v1">
<TabItem value="v1" label="V1">
Tab 1
</TabItem>
<TabItem value="v2" label="V2">
Tab 2
</TabItem>
</Tabs>
<Tabs
defaultValue="v1"
values={[
{value: 'v1', label: 'V1'},
{value: 'v2', label: 'V2'},
]}>
<TabItem value="v1">Tab 1</TabItem>
<TabItem value="v2">Tab 2</TabItem>
</Tabs>
<Tabs
defaultValue={null}
values={[
{value: 'v1', label: 'V1'},
{value: 'v2', label: 'V2'},
]}>
<TabItem value="v1">Tab 1</TabItem>
<TabItem value="v2">Tab 2</TabItem>
</Tabs>
<Tabs defaultValue={null}>
<TabItem value="v1" label="V1">
Tab 1
</TabItem>
<TabItem value="v2" label="V2">
Tab 2
</TabItem>
</Tabs>
</TabGroupChoiceProvider>
</ScrollControllerProvider>,
<TestProviders>
<Tabs>
<TabItem value="v1">Tab 1</TabItem>
<TabItem value="v2">Tab 2</TabItem>
</Tabs>
<Tabs>
<TabItem value="v1">Tab 1</TabItem>
<TabItem value="v2" default>
Tab 2
</TabItem>
</Tabs>
<Tabs defaultValue="v1">
<TabItem value="v1" label="V1">
Tab 1
</TabItem>
<TabItem value="v2" label="V2">
Tab 2
</TabItem>
</Tabs>
<Tabs
defaultValue="v1"
values={[
{value: 'v1', label: 'V1'},
{value: 'v2', label: 'V2'},
]}>
<TabItem value="v1">Tab 1</TabItem>
<TabItem value="v2">Tab 2</TabItem>
</Tabs>
<Tabs
defaultValue={null}
values={[
{value: 'v1', label: 'V1'},
{value: 'v2', label: 'V2'},
]}>
<TabItem value="v1">Tab 1</TabItem>
<TabItem value="v2">Tab 2</TabItem>
</Tabs>
<Tabs defaultValue={null}>
<TabItem value="v1" label="V1">
Tab 1
</TabItem>
<TabItem value="v2" label="V2">
Tab 2
</TabItem>
</Tabs>
</TestProviders>,
);
}).not.toThrow(); // TODO Better Jest infrastructure to mock the Layout
});
@@ -114,22 +130,72 @@ describe('Tabs', () => {
expect(() => {
const tabs = ['Apple', 'Banana', 'Carrot'];
renderer.create(
<ScrollControllerProvider>
<TabGroupChoiceProvider>
<Tabs
<TestProviders>
<Tabs
// @ts-expect-error: for an edge-case that we didn't write types for
values={tabs.map((t, idx) => ({label: t, value: idx}))}
// @ts-expect-error: for an edge-case that we didn't write types for
defaultValue={0}>
{tabs.map((t, idx) => (
// @ts-expect-error: for an edge-case that we didn't write types for
values={tabs.map((t, idx) => ({label: t, value: idx}))}
// @ts-expect-error: for an edge-case that we didn't write types for
defaultValue={0}>
{tabs.map((t, idx) => (
// @ts-expect-error: for an edge-case that we didn't write types for
<TabItem key={idx} value={idx}>
{t}
</TabItem>
))}
</Tabs>
</TabGroupChoiceProvider>
</ScrollControllerProvider>,
<TabItem key={idx} value={idx}>
{t}
</TabItem>
))}
</Tabs>
</TestProviders>,
);
}).not.toThrow();
});
it('rejects if querystring is true, but groupId falsy', () => {
expect(() => {
renderer.create(
<TestProviders>
<Tabs queryString>
<TabItem value="val1">Val1</TabItem>
<TabItem value="val2">Val2</TabItem>
</Tabs>
</TestProviders>,
);
}).toThrow(
'Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".',
);
});

it('accept querystring=true when groupId is defined', () => {
expect(() => {
renderer.create(
<TestProviders>
<Tabs queryString groupId="my-group-id">
<TabItem value="val1">Val1</TabItem>
<TabItem value="val2">Val2</TabItem>
</Tabs>
</TestProviders>,
);
}).not.toThrow();
});

it('accept querystring as string, but groupId falsy', () => {
expect(() => {
renderer.create(
<TestProviders>
<Tabs queryString="qsKey">
<TabItem value="val1">Val1</TabItem>
<TabItem value="val2">Val2</TabItem>
</Tabs>
</TestProviders>,
);
}).not.toThrow();
});

it('accepts a single TabItem', () => {
expect(() => {
renderer.create(
<TestProviders>
<Tabs>
<TabItem value="val1">Val1</TabItem>
</Tabs>
</TestProviders>,
);
}).not.toThrow();
});
223 changes: 81 additions & 142 deletions packages/docusaurus-theme-classic/src/theme/Tabs/index.tsx
Original file line number Diff line number Diff line change
@@ -5,104 +5,27 @@
* LICENSE file in the root directory of this source tree.
*/

import React, {
useState,
cloneElement,
isValidElement,
type ReactElement,
} from 'react';
import React, {cloneElement} from 'react';
import clsx from 'clsx';
import useIsBrowser from '@docusaurus/useIsBrowser';
import {duplicates} from '@docusaurus/theme-common';
import {
useScrollPositionBlocker,
useTabGroupChoice,
useTabs,
} from '@docusaurus/theme-common/internal';
import useIsBrowser from '@docusaurus/useIsBrowser';
import type {Props} from '@theme/Tabs';
import type {Props as TabItemProps} from '@theme/TabItem';

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

// A very rough duck type, but good enough to guard against mistakes while
// allowing customization
function isTabItem(
comp: ReactElement<object>,
): comp is ReactElement<TabItemProps> {
return 'value' in comp.props;
}

function TabsComponent(props: Props): JSX.Element {
const {
lazy,
block,
defaultValue: defaultValueProp,
values: valuesProp,
groupId,
className,
} = props;
const children = React.Children.map(props.children, (child) => {
if (isValidElement(child) && isTabItem(child)) {
return child;
}
// child.type.name will give non-sensical values in prod because of
// minification, but we assume it won't throw in prod.
throw new Error(
`Docusaurus error: Bad <Tabs> child <${
// @ts-expect-error: guarding against unexpected cases
typeof child.type === 'string' ? child.type : child.type.name
}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.`,
);
});
const values =
valuesProp ??
// Only pick keys that we recognize. MDX would inject some keys by default
children.map(({props: {value, label, attributes}}) => ({
value,
label,
attributes,
}));
const dup = duplicates(values, (a, b) => a.value === b.value);
if (dup.length > 0) {
throw new Error(
`Docusaurus error: Duplicate values "${dup
.map((a) => a.value)
.join(', ')}" found in <Tabs>. Every value needs to be unique.`,
);
}
// When defaultValueProp is null, don't show a default tab
const defaultValue =
defaultValueProp === null
? defaultValueProp
: defaultValueProp ??
children.find((child) => child.props.default)?.props.value ??
children[0]!.props.value;
if (defaultValue !== null && !values.some((a) => a.value === defaultValue)) {
throw new Error(
`Docusaurus error: The <Tabs> has a defaultValue "${defaultValue}" but none of its children has the corresponding value. Available values are: ${values
.map((a) => a.value)
.join(
', ',
)}. If you intend to show no default tab, use defaultValue={null} instead.`,
);
}

const {tabGroupChoices, setTabGroupChoices} = useTabGroupChoice();
const [selectedValue, setSelectedValue] = useState(defaultValue);
function TabList({
className,
block,
selectedValue,
selectValue,
tabValues,
}: Props & ReturnType<typeof useTabs>) {
const tabRefs: (HTMLLIElement | null)[] = [];
const {blockElementScrollPositionUntilNextRender} =
useScrollPositionBlocker();

if (groupId != null) {
const relevantTabGroupChoice = tabGroupChoices[groupId];
if (
relevantTabGroupChoice != null &&
relevantTabGroupChoice !== selectedValue &&
values.some((value) => value.value === relevantTabGroupChoice)
) {
setSelectedValue(relevantTabGroupChoice);
}
}

const handleTabChange = (
event:
| React.FocusEvent<HTMLLIElement>
@@ -111,15 +34,11 @@ function TabsComponent(props: Props): JSX.Element {
) => {
const newTab = event.currentTarget;
const newTabIndex = tabRefs.indexOf(newTab);
const newTabValue = values[newTabIndex]!.value;
const newTabValue = tabValues[newTabIndex]!.value;

if (newTabValue !== selectedValue) {
blockElementScrollPositionUntilNextRender(newTab);
setSelectedValue(newTabValue);

if (groupId != null) {
setTabGroupChoices(groupId, String(newTabValue));
}
selectValue(newTabValue);
}
};

@@ -149,61 +68,81 @@ function TabsComponent(props: Props): JSX.Element {
};

return (
<div className={clsx('tabs-container', styles.tabList)}>
<ul
role="tablist"
aria-orientation="horizontal"
className={clsx(
'tabs',
{
'tabs--block': block,
},
className,
)}>
{values.map(({value, label, attributes}) => (
<li
role="tab"
tabIndex={selectedValue === value ? 0 : -1}
aria-selected={selectedValue === value}
key={value}
ref={(tabControl) => tabRefs.push(tabControl)}
onKeyDown={handleKeydown}
onClick={handleTabChange}
{...attributes}
className={clsx(
'tabs__item',
styles.tabItem,
attributes?.className as string,
{
'tabs__item--active': selectedValue === value,
},
)}>
{label ?? value}
</li>
))}
</ul>
<ul
role="tablist"
aria-orientation="horizontal"
className={clsx(
'tabs',
{
'tabs--block': block,
},
className,
)}>
{tabValues.map(({value, label, attributes}) => (
<li
// TODO extract TabListItem
role="tab"
tabIndex={selectedValue === value ? 0 : -1}
aria-selected={selectedValue === value}
key={value}
ref={(tabControl) => tabRefs.push(tabControl)}
onKeyDown={handleKeydown}
onClick={handleTabChange}
{...attributes}
className={clsx(
'tabs__item',
styles.tabItem,
attributes?.className as string,
{
'tabs__item--active': selectedValue === value,
},
)}>
{label ?? value}
</li>
))}
</ul>
);
}

{lazy ? (
cloneElement(
children.filter(
(tabItem) => tabItem.props.value === selectedValue,
)[0]!,
{className: 'margin-top--md'},
)
) : (
<div className="margin-top--md">
{children.map((tabItem, i) =>
cloneElement(tabItem, {
key: i,
hidden: tabItem.props.value !== selectedValue,
}),
)}
</div>
function TabContent({
lazy,
children,
selectedValue,
}: Props & ReturnType<typeof useTabs>) {
// eslint-disable-next-line no-param-reassign
children = Array.isArray(children) ? children : [children];
if (lazy) {
const selectedTabItem = children.find(
(tabItem) => tabItem.props.value === selectedValue,
);
if (!selectedTabItem) {
// fail-safe or fail-fast? not sure what's best here
return null;
}
return cloneElement(selectedTabItem, {className: 'margin-top--md'});
}
return (
<div className="margin-top--md">
{children.map((tabItem, i) =>
cloneElement(tabItem, {
key: i,
hidden: tabItem.props.value !== selectedValue,
}),
)}
</div>
);
}

function TabsComponent(props: Props): JSX.Element {
const tabs = useTabs(props);
return (
<div className={clsx('tabs-container', styles.tabList)}>
<TabList {...props} {...tabs} />
<TabContent {...props} {...tabs} />
</div>
);
}

export default function Tabs(props: Props): JSX.Element {
const isBrowser = useIsBrowser();
return (
19 changes: 10 additions & 9 deletions packages/docusaurus-theme-common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-common",
"version": "2.2.0",
"version": "2.3.1",
"description": "Common code for Docusaurus themes.",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
@@ -30,24 +30,25 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/mdx-loader": "2.2.0",
"@docusaurus/module-type-aliases": "2.2.0",
"@docusaurus/plugin-content-blog": "2.2.0",
"@docusaurus/plugin-content-docs": "2.2.0",
"@docusaurus/plugin-content-pages": "2.2.0",
"@docusaurus/utils": "2.2.0",
"@docusaurus/mdx-loader": "2.3.1",
"@docusaurus/module-type-aliases": "2.3.1",
"@docusaurus/plugin-content-blog": "2.3.1",
"@docusaurus/plugin-content-docs": "2.3.1",
"@docusaurus/plugin-content-pages": "2.3.1",
"@docusaurus/utils": "2.3.1",
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router-config": "*",
"clsx": "^1.2.1",
"parse-numeric-range": "^1.3.0",
"prism-react-renderer": "^1.3.5",
"tslib": "^2.4.0",
"use-sync-external-store": "^1.2.0",
"utility-types": "^3.10.0"
},
"devDependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/types": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/types": "2.3.1",
"fs-extra": "^10.1.0",
"lodash": "^4.17.21"
},
85 changes: 0 additions & 85 deletions packages/docusaurus-theme-common/src/contexts/tabGroupChoice.tsx

This file was deleted.

15 changes: 10 additions & 5 deletions packages/docusaurus-theme-common/src/hooks/useSearchPage.ts
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
import {useCallback, useEffect, useState} from 'react';
import {useHistory} from '@docusaurus/router';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import type {ThemeConfig as AlgoliaThemeConfig} from '@docusaurus/theme-search-algolia';

const SEARCH_PARAM_QUERY = 'q';

@@ -31,8 +32,11 @@ export function useSearchPage(): {
} {
const history = useHistory();
const {
siteConfig: {baseUrl},
siteConfig: {baseUrl, themeConfig},
} = useDocusaurusContext();
const {
algolia: {searchPagePath},
} = themeConfig as AlgoliaThemeConfig;

const [searchQuery, setSearchQueryState] = useState('');

@@ -65,10 +69,11 @@ export function useSearchPage(): {
const generateSearchPageLink = useCallback(
(targetSearchQuery: string) =>
// Refer to https://github.com/facebook/docusaurus/pull/2838
`${baseUrl}search?${SEARCH_PARAM_QUERY}=${encodeURIComponent(
targetSearchQuery,
)}`,
[baseUrl],
// Note: if searchPagePath is falsy, useSearchPage() will not be called
`${baseUrl}${
searchPagePath as string
}?${SEARCH_PARAM_QUERY}=${encodeURIComponent(targetSearchQuery)}`,
[baseUrl, searchPagePath],
);

return {
6 changes: 5 additions & 1 deletion packages/docusaurus-theme-common/src/index.ts
Original file line number Diff line number Diff line change
@@ -24,7 +24,11 @@ export {
type ColorModeConfig,
} from './utils/useThemeConfig';

export {createStorageSlot, listStorageKeys} from './utils/storageUtils';
export {
createStorageSlot,
useStorageSlot,
listStorageKeys,
} from './utils/storageUtils';

export {useContextualSearchFilters} from './utils/searchUtils';

12 changes: 7 additions & 5 deletions packages/docusaurus-theme-common/src/internal.ts
Original file line number Diff line number Diff line change
@@ -42,10 +42,8 @@ export {
useAnnouncementBar,
} from './contexts/announcementBar';

export {
useTabGroupChoice,
TabGroupChoiceProvider,
} from './contexts/tabGroupChoice';
export {useTabs} from './utils/tabsUtils';
export type {TabValue, TabsProps, TabItemProps} from './utils/tabsUtils';

export {useNavbarMobileSidebar} from './contexts/navbarMobileSidebar';
export {useNavbarSecondaryMenu} from './contexts/navbarSecondaryMenu/display';
@@ -82,7 +80,11 @@ export {useLocationChange} from './utils/useLocationChange';

export {useLocalPathname} from './utils/useLocalPathname';

export {useHistoryPopHandler} from './utils/historyUtils';
export {
useHistoryPopHandler,
useHistorySelector,
useQueryStringValue,
} from './utils/historyUtils';

export {
useFilteredAndTreeifiedTOC,
34 changes: 33 additions & 1 deletion packages/docusaurus-theme-common/src/utils/historyUtils.ts
Original file line number Diff line number Diff line change
@@ -7,8 +7,11 @@

import {useEffect} from 'react';
import {useHistory} from '@docusaurus/router';
// @ts-expect-error: TODO temporary until React 18 upgrade
import {useSyncExternalStore} from 'use-sync-external-store/shim';
import {useEvent} from './reactUtils';
import type {Location, Action} from 'history';

import type {History, Location, Action} from 'history';

type HistoryBlockHandler = (location: Location, action: Action) => void | false;

@@ -43,3 +46,32 @@ export function useHistoryPopHandler(handler: HistoryBlockHandler): void {
return undefined;
});
}

/**
* Permits to efficiently subscribe to a slice of the history
* See https://thisweekinreact.com/articles/useSyncExternalStore-the-underrated-react-api
* @param selector
*/
export function useHistorySelector<Value>(
selector: (history: History<unknown>) => Value,
): Value {
const history = useHistory();
return useSyncExternalStore(
history.listen,
() => selector(history),
() => selector(history),
);
}

/**
* Permits to efficiently subscribe to a specific querystring value
* @param key
*/
export function useQueryStringValue(key: string | null): string | null {
return useHistorySelector((history) => {
if (key === null) {
return null;
}
return new URLSearchParams(history.location.search).get(key);
});
}
5 changes: 4 additions & 1 deletion packages/docusaurus-theme-common/src/utils/scrollUtils.tsx
Original file line number Diff line number Diff line change
@@ -222,7 +222,10 @@ export function useScrollPositionBlocker(): {
);

useLayoutEffect(() => {
nextLayoutEffectCallbackRef.current?.();
// Queuing permits to restore scroll position after all useLayoutEffect
// have run, and yet preserve the sync nature of the scroll restoration
// See https://github.com/facebook/docusaurus/issues/8625
queueMicrotask(() => nextLayoutEffectCallbackRef.current?.());
});

return {
122 changes: 115 additions & 7 deletions packages/docusaurus-theme-common/src/utils/storageUtils.ts
Original file line number Diff line number Diff line change
@@ -5,12 +5,53 @@
* LICENSE file in the root directory of this source tree.
*/

import {useCallback, useRef} from 'react';
// @ts-expect-error: TODO temp error until React 18 upgrade
import {useSyncExternalStore} from 'use-sync-external-store/shim';

const StorageTypes = ['localStorage', 'sessionStorage', 'none'] as const;

export type StorageType = typeof StorageTypes[number];

const DefaultStorageType: StorageType = 'localStorage';

// window.addEventListener('storage') only works for different windows...
// so for current window we have to dispatch the event manually
// Now we can listen for both cross-window / current-window storage changes!
// see https://stackoverflow.com/a/71177640/82609
// see https://stackoverflow.com/questions/26974084/listen-for-changes-with-localstorage-on-the-same-window
function dispatchChangeEvent({
key,
oldValue,
newValue,
storage,
}: {
key: string;
oldValue: string | null;
newValue: string | null;
storage: Storage;
}) {
// If we set multiple times the same storage value, events should not be fired
// The native events behave this way, so our manual event dispatch should
// rather behave exactly the same. Not doing so might create infinite loops.
// See https://github.com/facebook/docusaurus/issues/8594
if (oldValue === newValue) {
return;
}
const event = document.createEvent('StorageEvent');
event.initStorageEvent(
'storage',
false,
false,
key,
oldValue,
newValue,
window.location.href,
storage,
);
window.dispatchEvent(event);
}

/**
* Will return `null` if browser storage is unavailable (like running Docusaurus
* in an iframe). This should NOT be called in SSR.
@@ -58,12 +99,14 @@ export type StorageSlot = {
get: () => string | null;
set: (value: string) => void;
del: () => void;
listen: (onChange: (event: StorageEvent) => void) => () => void;
};

const NoopStorageSlot: StorageSlot = {
get: () => null,
set: () => {},
del: () => {},
listen: () => () => {},
};

// Fail-fast, as storage APIs should not be used during the SSR process
@@ -78,6 +121,7 @@ Please only call storage APIs in effects and event handlers.`);
get: throwError,
set: throwError,
del: throwError,
listen: throwError,
};
}

@@ -98,39 +142,103 @@ export function createStorageSlot(
if (typeof window === 'undefined') {
return createServerStorageSlot(key);
}
const browserStorage = getBrowserStorage(options?.persistence);
if (browserStorage === null) {
const storage = getBrowserStorage(options?.persistence);
if (storage === null) {
return NoopStorageSlot;
}
return {
get: () => {
try {
return browserStorage.getItem(key);
return storage.getItem(key);
} catch (err) {
console.error(`Docusaurus storage error, can't get key=${key}`, err);
return null;
}
},
set: (value) => {
set: (newValue) => {
try {
browserStorage.setItem(key, value);
const oldValue = storage.getItem(key);
storage.setItem(key, newValue);
dispatchChangeEvent({
key,
oldValue,
newValue,
storage,
});
} catch (err) {
console.error(
`Docusaurus storage error, can't set ${key}=${value}`,
`Docusaurus storage error, can't set ${key}=${newValue}`,
err,
);
}
},
del: () => {
try {
browserStorage.removeItem(key);
const oldValue = storage.getItem(key);
storage.removeItem(key);
dispatchChangeEvent({key, oldValue, newValue: null, storage});
} catch (err) {
console.error(`Docusaurus storage error, can't delete key=${key}`, err);
}
},
listen: (onChange) => {
try {
const listener = (event: StorageEvent) => {
if (event.storageArea === storage && event.key === key) {
onChange(event);
}
};
window.addEventListener('storage', listener);
return () => window.removeEventListener('storage', listener);
} catch (err) {
console.error(
`Docusaurus storage error, can't listen for changes of key=${key}`,
err,
);
return () => {};
}
},
};
}

export function useStorageSlot(
key: string | null,
options?: {persistence?: StorageType},
): [string | null, StorageSlot] {
// Not ideal but good enough: assumes storage slot config is constant
const storageSlot = useRef(() => {
if (key === null) {
return NoopStorageSlot;
}
return createStorageSlot(key, options);
}).current();

const listen: StorageSlot['listen'] = useCallback(
(onChange) => {
// Do not try to add a listener during SSR
if (typeof window === 'undefined') {
return () => {};
}
return storageSlot.listen(onChange);
},
[storageSlot],
);

const currentValue = useSyncExternalStore(
listen,
() => {
// TODO this check should be useless after React 18
if (typeof window === 'undefined') {
return null;
}
return storageSlot.get();
},
() => null,
);

return [currentValue, storageSlot];
}

/**
* Returns a list of all the keys currently stored in browser storage,
* or an empty list if browser storage can't be accessed.
270 changes: 270 additions & 0 deletions packages/docusaurus-theme-common/src/utils/tabsUtils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import React, {
isValidElement,
useCallback,
useState,
useMemo,
type ReactNode,
type ReactElement,
useLayoutEffect,
} from 'react';
import {useHistory} from '@docusaurus/router';
import {useQueryStringValue} from '@docusaurus/theme-common/internal';
import {duplicates, useStorageSlot} from '../index';

/**
* TabValue is the "config" of a given Tab
* Provided through <Tabs> "values" prop or through the children <TabItem> props
*/
export interface TabValue {
readonly value: string;
readonly label?: string;
readonly attributes?: {[key: string]: unknown};
readonly default?: boolean;
}

export interface TabsProps {
readonly lazy?: boolean;
readonly block?: boolean;
readonly children:
| readonly ReactElement<TabItemProps>[]
| ReactElement<TabItemProps>;
readonly defaultValue?: string | null;
readonly values?: readonly TabValue[];
readonly groupId?: string;
readonly className?: string;
readonly queryString?: string | boolean;
}

export interface TabItemProps {
readonly children: ReactNode;
readonly value: string;
readonly default?: boolean;
readonly label?: string;
readonly hidden?: boolean;
readonly className?: string;
readonly attributes?: {[key: string]: unknown};
}

// A very rough duck type, but good enough to guard against mistakes while
// allowing customization
function isTabItem(
comp: ReactElement<object>,
): comp is ReactElement<TabItemProps> {
return 'value' in comp.props;
}

function ensureValidChildren(children: TabsProps['children']) {
return React.Children.map(children, (child) => {
if (isValidElement(child) && isTabItem(child)) {
return child;
}
// child.type.name will give non-sensical values in prod because of
// minification, but we assume it won't throw in prod.
throw new Error(
`Docusaurus error: Bad <Tabs> child <${
// @ts-expect-error: guarding against unexpected cases
typeof child.type === 'string' ? child.type : child.type.name
}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.`,
);
});
}

function extractChildrenTabValues(children: TabsProps['children']): TabValue[] {
return ensureValidChildren(children).map(
({props: {value, label, attributes, default: isDefault}}) => ({
value,
label,
attributes,
default: isDefault,
}),
);
}

function ensureNoDuplicateValue(values: readonly TabValue[]) {
const dup = duplicates(values, (a, b) => a.value === b.value);
if (dup.length > 0) {
throw new Error(
`Docusaurus error: Duplicate values "${dup
.map((a) => a.value)
.join(', ')}" found in <Tabs>. Every value needs to be unique.`,
);
}
}

function useTabValues(
props: Pick<TabsProps, 'values' | 'children'>,
): readonly TabValue[] {
const {values: valuesProp, children} = props;
return useMemo(() => {
const values = valuesProp ?? extractChildrenTabValues(children);
ensureNoDuplicateValue(values);
return values;
}, [valuesProp, children]);
}

function isValidValue({
value,
tabValues,
}: {
value: string | null | undefined;
tabValues: readonly TabValue[];
}) {
return tabValues.some((a) => a.value === value);
}

function getInitialStateValue({
defaultValue,
tabValues,
}: {
defaultValue: TabsProps['defaultValue'];
tabValues: readonly TabValue[];
}): string {
if (tabValues.length === 0) {
throw new Error(
'Docusaurus error: the <Tabs> component requires at least one <TabItem> children component',
);
}
if (defaultValue) {
// Warn user about passing incorrect defaultValue as prop.
if (!isValidValue({value: defaultValue, tabValues})) {
throw new Error(
`Docusaurus error: The <Tabs> has a defaultValue "${defaultValue}" but none of its children has the corresponding value. Available values are: ${tabValues
.map((a) => a.value)
.join(
', ',
)}. If you intend to show no default tab, use defaultValue={null} instead.`,
);
}
return defaultValue;
}
const defaultTabValue =
tabValues.find((tabValue) => tabValue.default) ?? tabValues[0];
if (!defaultTabValue) {
throw new Error('Unexpected error: 0 tabValues');
}
return defaultTabValue.value;
}

function getStorageKey(groupId: string | undefined) {
if (!groupId) {
return null;
}
return `docusaurus.tab.${groupId}`;
}

function getQueryStringKey({
queryString = false,
groupId,
}: Pick<TabsProps, 'queryString' | 'groupId'>) {
if (typeof queryString === 'string') {
return queryString;
}
if (queryString === false) {
return null;
}
if (queryString === true && !groupId) {
throw new Error(
`Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".`,
);
}
return groupId ?? null;
}

function useTabQueryString({
queryString = false,
groupId,
}: Pick<TabsProps, 'queryString' | 'groupId'>) {
const history = useHistory();
const key = getQueryStringKey({queryString, groupId});
const value = useQueryStringValue(key);

const setValue = useCallback(
(newValue: string) => {
if (!key) {
return; // no-op
}
const searchParams = new URLSearchParams(history.location.search);
searchParams.set(key, newValue);
history.replace({...history.location, search: searchParams.toString()});
},
[key, history],
);

return [value, setValue] as const;
}

function useTabStorage({groupId}: Pick<TabsProps, 'groupId'>) {
const key = getStorageKey(groupId);
const [value, storageSlot] = useStorageSlot(key);

const setValue = useCallback(
(newValue: string) => {
if (!key) {
return; // no-op
}
storageSlot.set(newValue);
},
[key, storageSlot],
);

return [value, setValue] as const;
}

export function useTabs(props: TabsProps): {
selectedValue: string;
selectValue: (value: string) => void;
tabValues: readonly TabValue[];
} {
const {defaultValue, queryString = false, groupId} = props;
const tabValues = useTabValues(props);

const [selectedValue, setSelectedValue] = useState(() =>
getInitialStateValue({defaultValue, tabValues}),
);

const [queryStringValue, setQueryString] = useTabQueryString({
queryString,
groupId,
});

const [storageValue, setStorageValue] = useTabStorage({
groupId,
});

// We sync valid querystring/storage value to state on change + hydration
const valueToSync = (() => {
const value = queryStringValue ?? storageValue;
if (!isValidValue({value, tabValues})) {
return null;
}
return value;
})();
// Sync in a layout/sync effect is important, for useScrollPositionBlocker
// See https://github.com/facebook/docusaurus/issues/8625
useLayoutEffect(() => {
if (valueToSync) {
setSelectedValue(valueToSync);
}
}, [valueToSync]);

const selectValue = useCallback(
(newValue: string) => {
if (!isValidValue({value: newValue, tabValues})) {
throw new Error(`Can't select invalid tab value=${newValue}`);
}
setSelectedValue(newValue);
setQueryString(newValue);
setStorageValue(newValue);
},
[setQueryString, setStorageValue, tabValues],
);

return {selectedValue, selectValue, tabValues};
}
12 changes: 6 additions & 6 deletions packages/docusaurus-theme-live-codeblock/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-live-codeblock",
"version": "2.2.0",
"version": "2.3.1",
"description": "Docusaurus live code block component.",
"main": "lib/index.js",
"types": "src/theme-live-codeblock.d.ts",
@@ -23,18 +23,18 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/theme-common": "2.2.0",
"@docusaurus/theme-translations": "2.2.0",
"@docusaurus/utils-validation": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/theme-common": "2.3.1",
"@docusaurus/theme-translations": "2.3.1",
"@docusaurus/utils-validation": "2.3.1",
"@philpl/buble": "^0.19.7",
"clsx": "^1.2.1",
"fs-extra": "^10.1.0",
"react-live": "2.2.3",
"tslib": "^2.4.0"
},
"devDependencies": {
"@docusaurus/types": "2.2.0",
"@docusaurus/types": "2.3.1",
"@types/buble": "^0.20.1"
},
"peerDependencies": {
15 changes: 7 additions & 8 deletions packages/docusaurus-theme-mermaid/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-mermaid",
"version": "2.2.0",
"version": "2.3.1",
"description": "Mermaid components for Docusaurus.",
"main": "lib/index.js",
"types": "src/theme-mermaid.d.ts",
@@ -33,18 +33,17 @@
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
},
"dependencies": {
"@docusaurus/core": "2.2.0",
"@docusaurus/module-type-aliases": "2.2.0",
"@docusaurus/theme-common": "2.2.0",
"@docusaurus/types": "2.2.0",
"@docusaurus/utils-validation": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/module-type-aliases": "2.3.1",
"@docusaurus/theme-common": "2.3.1",
"@docusaurus/types": "2.3.1",
"@docusaurus/utils-validation": "2.3.1",
"@mdx-js/react": "^1.6.22",
"mermaid": "^9.1.1",
"mermaid": "^9.2.2",
"tslib": "^2.4.0"
},
"devDependencies": {
"@types/mdx-js__react": "^1.5.5",
"@types/mermaid": "^8.2.9",
"react-test-renderer": "^17.0.2"
},
"peerDependencies": {
7 changes: 3 additions & 4 deletions packages/docusaurus-theme-mermaid/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -7,8 +7,7 @@

import {useMemo} from 'react';
import {useColorMode, useThemeConfig} from '@docusaurus/theme-common';
import mermaid from 'mermaid';
import type mermaidAPI from 'mermaid/mermaidAPI';
import mermaid, {type MermaidConfig} from 'mermaid';
import type {ThemeConfig} from '@docusaurus/theme-mermaid';

// Stable className to allow users to easily target with CSS
@@ -18,7 +17,7 @@ export function useMermaidThemeConfig(): ThemeConfig['mermaid'] {
return (useThemeConfig() as unknown as ThemeConfig).mermaid;
}

export function useMermaidConfig(): mermaidAPI.Config {
export function useMermaidConfig(): MermaidConfig {
const {colorMode} = useColorMode();
const mermaidThemeConfig = useMermaidThemeConfig();

@@ -33,7 +32,7 @@ export function useMermaidConfig(): mermaidAPI.Config {

export function useMermaidSvg(
txt: string,
mermaidConfigParam?: mermaidAPI.Config,
mermaidConfigParam?: MermaidConfig,
): string {
/*
For flexibility, we allow the hook to receive a custom Mermaid config
5 changes: 2 additions & 3 deletions packages/docusaurus-theme-mermaid/src/validateThemeConfig.ts
Original file line number Diff line number Diff line change
@@ -7,14 +7,13 @@

import {Joi} from '@docusaurus/utils-validation';
import type {ThemeConfig} from '@docusaurus/theme-mermaid';
import type mermaidAPI from 'mermaid/mermaidAPI';
import type {ThemeConfigValidationContext} from '@docusaurus/types';

export const DEFAULT_THEME_CONFIG: ThemeConfig = {
mermaid: {
theme: {
dark: 'dark' as mermaidAPI.Theme,
light: 'default' as mermaidAPI.Theme,
dark: 'dark',
light: 'default',
},
options: {},
},
2 changes: 1 addition & 1 deletion packages/docusaurus-theme-mermaid/tsconfig.client.json
Original file line number Diff line number Diff line change
@@ -10,6 +10,6 @@
"module": "esnext",
"target": "esnext"
},
"include": ["src/theme", "src/*.d.ts"],
"include": ["src/client", "src/theme", "src/*.d.ts"],
"exclude": ["**/__tests__/**"]
}
2 changes: 1 addition & 1 deletion packages/docusaurus-theme-mermaid/tsconfig.json
Original file line number Diff line number Diff line change
@@ -10,5 +10,5 @@
"outDir": "lib"
},
"include": ["src"],
"exclude": ["src/theme", "**/__tests__/**"]
"exclude": ["src/client", "src/theme", "**/__tests__/**"]
}
20 changes: 10 additions & 10 deletions packages/docusaurus-theme-search-algolia/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-search-algolia",
"version": "2.2.0",
"version": "2.3.1",
"description": "Algolia search component for Docusaurus.",
"main": "lib/index.js",
"sideEffects": [
@@ -34,24 +34,24 @@
},
"dependencies": {
"@docsearch/react": "^3.1.1",
"@docusaurus/core": "2.2.0",
"@docusaurus/logger": "2.2.0",
"@docusaurus/plugin-content-docs": "2.2.0",
"@docusaurus/theme-common": "2.2.0",
"@docusaurus/theme-translations": "2.2.0",
"@docusaurus/utils": "2.2.0",
"@docusaurus/utils-validation": "2.2.0",
"@docusaurus/core": "2.3.1",
"@docusaurus/logger": "2.3.1",
"@docusaurus/plugin-content-docs": "2.3.1",
"@docusaurus/theme-common": "2.3.1",
"@docusaurus/theme-translations": "2.3.1",
"@docusaurus/utils": "2.3.1",
"@docusaurus/utils-validation": "2.3.1",
"algoliasearch": "^4.13.1",
"algoliasearch-helper": "^3.10.0",
"clsx": "^1.2.1",
"eta": "^1.12.3",
"eta": "^2.0.0",
"fs-extra": "^10.1.0",
"lodash": "^4.17.21",
"tslib": "^2.4.0",
"utility-types": "^3.10.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.2.0"
"@docusaurus/module-type-aliases": "2.3.1"
},
"peerDependencies": {
"react": "^16.8.4 || ^17.0.0",
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import {validateThemeConfig, DEFAULT_CONFIG} from '../validateThemeConfig';
import {DEFAULT_CONFIG, validateThemeConfig} from '../validateThemeConfig';
import type {Joi} from '@docusaurus/utils-validation';

function testValidateThemeConfig(themeConfig: {[key: string]: unknown}) {
@@ -121,6 +121,53 @@ describe('validateThemeConfig', () => {
});
});

describe('replaceSearchResultPathname', () => {
it('escapes from string', () => {
const algolia = {
appId: 'BH4D9OD16A',
indexName: 'index',
apiKey: 'apiKey',
replaceSearchResultPathname: {
from: '/docs/some-\\special-.[regexp]{chars*}',
to: '/abc',
},
};
expect(testValidateThemeConfig({algolia})).toEqual({
algolia: {
...DEFAULT_CONFIG,
...algolia,
replaceSearchResultPathname: {
from: '/docs/some\\x2d\\\\special\\x2d\\.\\[regexp\\]\\{chars\\*\\}',
to: '/abc',
},
},
});
});

it('converts from regexp to string', () => {
const algolia = {
appId: 'BH4D9OD16A',
indexName: 'index',
apiKey: 'apiKey',
replaceSearchResultPathname: {
from: /^\/docs\/(?:1\.0|next)/,
to: '/abc',
},
};

expect(testValidateThemeConfig({algolia})).toEqual({
algolia: {
...DEFAULT_CONFIG,
...algolia,
replaceSearchResultPathname: {
from: '^\\/docs\\/(?:1\\.0|next)',
to: '/abc',
},
},
});
});
});

it('searchParameters.facetFilters search config', () => {
const algolia = {
appId: 'BH4D9OD16A',
2 changes: 2 additions & 0 deletions packages/docusaurus-theme-search-algolia/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -5,4 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/

export {useAlgoliaThemeConfig} from './useAlgoliaThemeConfig';
export {useAlgoliaContextualFacetFilters} from './useAlgoliaContextualFacetFilters';
export {useSearchResultUrlProcessor} from './useSearchResultUrlProcessor';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import type {ThemeConfig} from '@docusaurus/theme-search-algolia';

export function useAlgoliaThemeConfig(): ThemeConfig {
const {
siteConfig: {themeConfig},
} = useDocusaurusContext();
return themeConfig as ThemeConfig;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {useCallback} from 'react';
import {isRegexpStringMatch} from '@docusaurus/theme-common';
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
import {useAlgoliaThemeConfig} from './useAlgoliaThemeConfig';
import type {ThemeConfig} from '@docusaurus/theme-search-algolia';

function replacePathname(
pathname: string,
replaceSearchResultPathname: ThemeConfig['algolia']['replaceSearchResultPathname'],
): string {
return replaceSearchResultPathname
? pathname.replaceAll(
new RegExp(replaceSearchResultPathname.from, 'g'),
replaceSearchResultPathname.to,
)
: pathname;
}

/**
* Process the search result url from Algolia to its final form, ready to be
* navigated to or used as a link
*/
export function useSearchResultUrlProcessor(): (url: string) => string {
const {withBaseUrl} = useBaseUrlUtils();
const {
algolia: {externalUrlRegex, replaceSearchResultPathname},
} = useAlgoliaThemeConfig();

return useCallback(
(url: string) => {
const parsedURL = new URL(url);

// Algolia contains an external domain => navigate to URL
if (isRegexpStringMatch(externalUrlRegex, parsedURL.href)) {
return url;
}

// Otherwise => transform to relative URL for SPA navigation
const relativeUrl = `${parsedURL.pathname + parsedURL.hash}`;

return withBaseUrl(
replacePathname(relativeUrl, replaceSearchResultPathname),
);
},
[withBaseUrl, externalUrlRegex, replaceSearchResultPathname],
);
}
Original file line number Diff line number Diff line change
@@ -17,13 +17,23 @@ declare module '@docusaurus/theme-search-algolia' {
indexName: string;
searchParameters: {[key: string]: unknown};
searchPagePath: string | false | null;
replaceSearchResultPathname?: {
from: string;
to: string;
};
};
};
export type UserThemeConfig = DeepPartial<ThemeConfig>;
}

declare module '@docusaurus/theme-search-algolia/client' {
import type {ThemeConfig} from '@docusaurus/theme-search-algolia';

export function useAlgoliaThemeConfig(): ThemeConfig;

export function useAlgoliaContextualFacetFilters(): [string, string[]];

export function useSearchResultUrlProcessor(): (url: string) => string;
}

declare module '@theme/SearchPage' {
Loading