mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-29 10:17:55 +02:00
chore: backport retro compatible commits for the Docusaurus v2.3 release (#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>
This commit is contained in:
parent
de972142a8
commit
c84d779627
173 changed files with 2640 additions and 1199 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "new.docusaurus.io",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "npx --package netlify-cli netlify dev"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"changelog": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "create-docusaurus",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/utils": "2.3.0",
|
||||
"commander": "^5.1.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "docusaurus-2-classic-typescript-template",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
|
@ -15,8 +15,8 @@
|
|||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.2.0",
|
||||
"@docusaurus/preset-classic": "2.2.0",
|
||||
"@docusaurus/core": "2.3.0",
|
||||
"@docusaurus/preset-classic": "2.3.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
|
@ -24,7 +24,7 @@
|
|||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.2.0",
|
||||
"@docusaurus/module-type-aliases": "2.3.0",
|
||||
"@tsconfig/docusaurus": "^1.0.5",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
|
|
|
@ -8,17 +8,22 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula');
|
|||
const config = {
|
||||
title: 'My Site',
|
||||
tagline: 'Dinosaurs are cool',
|
||||
url: 'https://your-docusaurus-test-site.com',
|
||||
baseUrl: '/',
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
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: '/',
|
||||
|
||||
// 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: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "docusaurus-2-classic-template",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
|
@ -14,8 +14,8 @@
|
|||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.2.0",
|
||||
"@docusaurus/preset-classic": "2.2.0",
|
||||
"@docusaurus/core": "2.3.0",
|
||||
"@docusaurus/preset-classic": "2.3.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
|
@ -23,7 +23,7 @@
|
|||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.2.0"
|
||||
"@docusaurus/module-type-aliases": "2.3.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
|
|
@ -13,17 +13,22 @@
|
|||
const config = {
|
||||
title: 'My Site',
|
||||
tagline: 'The tagline of my site',
|
||||
url: 'https://your-docusaurus-test-site.com',
|
||||
baseUrl: '/',
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
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: '/',
|
||||
|
||||
// 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: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "docusaurus-2-facebook-template",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/preset-classic": "2.3.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"react": "^17.0.2",
|
||||
|
|
|
@ -51,7 +51,11 @@ You can use absolute paths to reference images in the static directory (`static/
|
|||
|
||||

|
||||
|
||||
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
|
||||

|
||||
```
|
||||
|
||||
## Code Blocks
|
||||
|
||||
|
|
BIN
packages/create-docusaurus/templates/shared/static/img/docusaurus-social-card.jpg
vendored
Normal file
BIN
packages/create-docusaurus/templates/shared/static/img/docusaurus-social-card.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/cssnano-preset",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"description": "Advanced cssnano preset for maximum optimization.",
|
||||
"main": "lib/index.js",
|
||||
"license": "MIT",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/logger",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"description": "An encapsulated logger for semantically formatting console messages.",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/mdx-loader",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/utils": "2.3.0",
|
||||
"@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.0",
|
||||
"@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",
|
||||
|
|
10
packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/nesting.md
generated
Normal file
10
packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/nesting.md
generated
Normal file
|
@ -0,0 +1,10 @@
|
|||
Test nested Admonitions
|
||||
|
||||
::::info **Weather**
|
||||
On nice days, you can enjoy skiing in the mountains.
|
||||
|
||||
:::danger *Storms*
|
||||
Take care of snowstorms...
|
||||
:::
|
||||
|
||||
::::
|
|
@ -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> <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>"
|
||||
`;
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/migrate",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/utils": "2.3.0",
|
||||
"@mapbox/hast-util-to-jsx": "^2.0.0",
|
||||
"color": "^4.2.3",
|
||||
"commander": "^5.1.0",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/module-type-aliases",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*",
|
||||
"@types/react-router-config": "*",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-client-redirects",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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",
|
||||
"@docusaurus/core": "2.3.0",
|
||||
"@docusaurus/logger": "2.3.0",
|
||||
"@docusaurus/utils": "2.3.0",
|
||||
"@docusaurus/utils-common": "2.3.0",
|
||||
"@docusaurus/utils-validation": "2.3.0",
|
||||
"eta": "^1.12.3",
|
||||
"fs-extra": "^10.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/types": "2.2.0"
|
||||
"@docusaurus/types": "2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.4 || ^17.0.0",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-content-blog",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/logger": "2.3.0",
|
||||
"@docusaurus/mdx-loader": "2.3.0",
|
||||
"@docusaurus/types": "2.3.0",
|
||||
"@docusaurus/utils": "2.3.0",
|
||||
"@docusaurus/utils-common": "2.3.0",
|
||||
"@docusaurus/utils-validation": "2.3.0",
|
||||
"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"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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)),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-content-docs",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/logger": "2.3.0",
|
||||
"@docusaurus/mdx-loader": "2.3.0",
|
||||
"@docusaurus/module-type-aliases": "2.3.0",
|
||||
"@docusaurus/types": "2.3.0",
|
||||
"@docusaurus/utils": "2.3.0",
|
||||
"@docusaurus/utils-validation": "2.3.0",
|
||||
"@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"
|
||||
},
|
||||
|
|
|
@ -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)),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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}),
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-content-pages",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/mdx-loader": "2.3.0",
|
||||
"@docusaurus/types": "2.3.0",
|
||||
"@docusaurus/utils": "2.3.0",
|
||||
"@docusaurus/utils-validation": "2.3.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"tslib": "^2.4.0",
|
||||
"webpack": "^5.73.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-debug",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/types": "2.3.0",
|
||||
"@docusaurus/utils": "2.3.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"react-json-view": "^1.21.3",
|
||||
"tslib": "^2.4.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-google-analytics",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/types": "2.3.0",
|
||||
"@docusaurus/utils-validation": "2.3.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-google-gtag",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/types": "2.3.0",
|
||||
"@docusaurus/utils-validation": "2.3.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
3
packages/docusaurus-plugin-google-tag-manager/.npmignore
Normal file
3
packages/docusaurus-plugin-google-tag-manager/.npmignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
.tsbuildinfo*
|
||||
tsconfig*
|
||||
__tests__
|
7
packages/docusaurus-plugin-google-tag-manager/README.md
Normal file
7
packages/docusaurus-plugin-google-tag-manager/README.md
Normal file
|
@ -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
packages/docusaurus-plugin-google-tag-manager/package.json
Normal file
33
packages/docusaurus-plugin-google-tag-manager/package.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-google-tag-manager",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/types": "2.3.0",
|
||||
"@docusaurus/utils-validation": "2.3.0",
|
||||
"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
packages/docusaurus-plugin-google-tag-manager/src/index.ts
Normal file
78
packages/docusaurus-plugin-google-tag-manager/src/index.ts
Normal file
|
@ -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
packages/docusaurus-plugin-google-tag-manager/src/options.ts
Normal file
12
packages/docusaurus-plugin-google-tag-manager/src/options.ts
Normal file
|
@ -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
packages/docusaurus-plugin-google-tag-manager/src/types.d.ts
vendored
Normal file
8
packages/docusaurus-plugin-google-tag-manager/src/types.d.ts
vendored
Normal file
|
@ -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" />
|
|
@ -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
packages/docusaurus-plugin-google-tag-manager/tsconfig.json
Normal file
13
packages/docusaurus-plugin-google-tag-manager/tsconfig.json
Normal file
|
@ -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__/**"]
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-ideal-image",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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,12 +20,12 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.2.0",
|
||||
"@docusaurus/lqip-loader": "2.2.0",
|
||||
"@docusaurus/core": "2.3.0",
|
||||
"@docusaurus/lqip-loader": "2.3.0",
|
||||
"@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.0",
|
||||
"@docusaurus/types": "2.3.0",
|
||||
"@docusaurus/utils-validation": "2.3.0",
|
||||
"@endiliey/react-ideal-image": "^0.0.11",
|
||||
"react-waypoint": "^10.3.0",
|
||||
"sharp": "^0.30.7",
|
||||
|
@ -33,7 +33,7 @@
|
|||
"webpack": "^5.73.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.2.0",
|
||||
"@docusaurus/module-type-aliases": "2.3.0",
|
||||
"fs-extra": "^10.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-pwa",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/theme-common": "2.3.0",
|
||||
"@docusaurus/theme-translations": "2.3.0",
|
||||
"@docusaurus/types": "2.3.0",
|
||||
"@docusaurus/utils": "2.3.0",
|
||||
"@docusaurus/utils-validation": "2.3.0",
|
||||
"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.0",
|
||||
"fs-extra": "^10.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-sitemap",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/logger": "2.3.0",
|
||||
"@docusaurus/types": "2.3.0",
|
||||
"@docusaurus/utils": "2.3.0",
|
||||
"@docusaurus/utils-common": "2.3.0",
|
||||
"@docusaurus/utils-validation": "2.3.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"sitemap": "^7.1.1",
|
||||
"tslib": "^2.4.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/preset-classic",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/plugin-content-blog": "2.3.0",
|
||||
"@docusaurus/plugin-content-docs": "2.3.0",
|
||||
"@docusaurus/plugin-content-pages": "2.3.0",
|
||||
"@docusaurus/plugin-debug": "2.3.0",
|
||||
"@docusaurus/plugin-google-analytics": "2.3.0",
|
||||
"@docusaurus/plugin-google-gtag": "2.3.0",
|
||||
"@docusaurus/plugin-google-tag-manager": "2.3.0",
|
||||
"@docusaurus/plugin-sitemap": "2.3.0",
|
||||
"@docusaurus/theme-classic": "2.3.0",
|
||||
"@docusaurus/theme-common": "2.3.0",
|
||||
"@docusaurus/theme-search-algolia": "2.3.0",
|
||||
"@docusaurus/types": "2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.4 || ^17.0.0",
|
||||
|
|
|
@ -40,6 +40,7 @@ export default function preset(
|
|||
theme,
|
||||
googleAnalytics,
|
||||
gtag,
|
||||
googleTagManager,
|
||||
...rest
|
||||
} = opts;
|
||||
|
||||
|
@ -80,6 +81,11 @@ export default function preset(
|
|||
if (gtag) {
|
||||
plugins.push(makePluginConfig('@docusaurus/plugin-google-gtag', gtag));
|
||||
}
|
||||
if (googleTagManager) {
|
||||
plugins.push(
|
||||
makePluginConfig('@docusaurus/plugin-google-gtag', googleTagManager),
|
||||
);
|
||||
}
|
||||
if (isProd && sitemap !== false) {
|
||||
plugins.push(makePluginConfig('@docusaurus/plugin-sitemap', sitemap));
|
||||
}
|
||||
|
|
|
@ -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 &
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/remark-plugin-npm2yarn",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"description": "Remark plugin for converting npm commands to Yarn commands as tabs.",
|
||||
"main": "lib/index.js",
|
||||
"publishConfig": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/theme-classic",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/mdx-loader": "2.3.0",
|
||||
"@docusaurus/module-type-aliases": "2.3.0",
|
||||
"@docusaurus/plugin-content-blog": "2.3.0",
|
||||
"@docusaurus/plugin-content-docs": "2.3.0",
|
||||
"@docusaurus/plugin-content-pages": "2.3.0",
|
||||
"@docusaurus/theme-common": "2.3.0",
|
||||
"@docusaurus/theme-translations": "2.3.0",
|
||||
"@docusaurus/types": "2.3.0",
|
||||
"@docusaurus/utils": "2.3.0",
|
||||
"@docusaurus/utils-common": "2.3.0",
|
||||
"@docusaurus/utils-validation": "2.3.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"copy-text-to-clipboard": "^3.0.1",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -29,4 +29,11 @@
|
|||
width: var(--doc-sidebar-hidden-width);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebarViewport {
|
||||
top: 0;
|
||||
position: sticky;
|
||||
height: 100%;
|
||||
max-height: 100vh;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,4 +12,5 @@
|
|||
|
||||
.docsWrapper {
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
|
|
@ -43,4 +43,5 @@
|
|||
|
||||
.collapseSidebarButton {
|
||||
display: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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}>
|
||||
​
|
||||
</a>
|
||||
</Link>
|
||||
</As>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -12,6 +12,8 @@ body {
|
|||
|
||||
.mainWrapper {
|
||||
flex: 1 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Docusaurus-specific utility class */
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,60 @@ 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();
|
||||
});
|
||||
|
|
|
@ -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,79 @@ 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>) {
|
||||
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 (
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/theme-common",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"description": "Common code for Docusaurus themes.",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
|
@ -30,12 +30,12 @@
|
|||
},
|
||||
"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.0",
|
||||
"@docusaurus/module-type-aliases": "2.3.0",
|
||||
"@docusaurus/plugin-content-blog": "2.3.0",
|
||||
"@docusaurus/plugin-content-docs": "2.3.0",
|
||||
"@docusaurus/plugin-content-pages": "2.3.0",
|
||||
"@docusaurus/utils": "2.3.0",
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*",
|
||||
"@types/react-router-config": "*",
|
||||
|
@ -43,11 +43,12 @@
|
|||
"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.0",
|
||||
"@docusaurus/types": "2.3.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
/**
|
||||
* 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, {
|
||||
useState,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useContext,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
import {createStorageSlot, listStorageKeys} from '../utils/storageUtils';
|
||||
import {ReactContextError} from '../utils/reactUtils';
|
||||
|
||||
const TAB_CHOICE_PREFIX = 'docusaurus.tab.';
|
||||
|
||||
type ContextValue = {
|
||||
/** A map from `groupId` to the `value` of the saved choice. */
|
||||
readonly tabGroupChoices: {readonly [groupId: string]: string};
|
||||
/** Set the new choice value of a group. */
|
||||
readonly setTabGroupChoices: (groupId: string, newChoice: string) => void;
|
||||
};
|
||||
|
||||
const Context = React.createContext<ContextValue | undefined>(undefined);
|
||||
|
||||
function useContextValue(): ContextValue {
|
||||
const [tabGroupChoices, setChoices] = useState<{
|
||||
readonly [groupId: string]: string;
|
||||
}>({});
|
||||
const setChoiceSyncWithLocalStorage = useCallback(
|
||||
(groupId: string, newChoice: string) => {
|
||||
createStorageSlot(`${TAB_CHOICE_PREFIX}${groupId}`).set(newChoice);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const localStorageChoices: {[groupId: string]: string} = {};
|
||||
listStorageKeys().forEach((storageKey) => {
|
||||
if (storageKey.startsWith(TAB_CHOICE_PREFIX)) {
|
||||
const groupId = storageKey.substring(TAB_CHOICE_PREFIX.length);
|
||||
localStorageChoices[groupId] = createStorageSlot(storageKey).get()!;
|
||||
}
|
||||
});
|
||||
setChoices(localStorageChoices);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setTabGroupChoices = useCallback(
|
||||
(groupId: string, newChoice: string) => {
|
||||
setChoices((oldChoices) => ({...oldChoices, [groupId]: newChoice}));
|
||||
setChoiceSyncWithLocalStorage(groupId, newChoice);
|
||||
},
|
||||
[setChoiceSyncWithLocalStorage],
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() => ({tabGroupChoices, setTabGroupChoices}),
|
||||
[tabGroupChoices, setTabGroupChoices],
|
||||
);
|
||||
}
|
||||
|
||||
export function TabGroupChoiceProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}): JSX.Element {
|
||||
const value = useContextValue();
|
||||
return <Context.Provider value={value}>{children}</Context.Provider>;
|
||||
}
|
||||
|
||||
export function useTabGroupChoice(): ContextValue {
|
||||
const context = useContext(Context);
|
||||
if (context == null) {
|
||||
throw new ReactContextError('TabGroupChoiceProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,28 @@ 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,12 +5,46 @@
|
|||
* 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;
|
||||
}) {
|
||||
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 +92,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 +114,7 @@ Please only call storage APIs in effects and event handlers.`);
|
|||
get: throwError,
|
||||
set: throwError,
|
||||
del: throwError,
|
||||
listen: throwError,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -98,39 +135,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.
|
||||
|
|
266
packages/docusaurus-theme-common/src/utils/tabsUtils.tsx
Normal file
266
packages/docusaurus-theme-common/src/utils/tabsUtils.tsx
Normal file
|
@ -0,0 +1,266 @@
|
|||
/**
|
||||
* 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,
|
||||
useEffect,
|
||||
useState,
|
||||
useMemo,
|
||||
type ReactNode,
|
||||
type ReactElement,
|
||||
} 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>[];
|
||||
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;
|
||||
})();
|
||||
useEffect(() => {
|
||||
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};
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/theme-live-codeblock",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"description": "Docusaurus live code block component.",
|
||||
"main": "lib/index.js",
|
||||
"types": "src/theme-live-codeblock.d.ts",
|
||||
|
@ -23,10 +23,10 @@
|
|||
},
|
||||
"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.0",
|
||||
"@docusaurus/theme-common": "2.3.0",
|
||||
"@docusaurus/theme-translations": "2.3.0",
|
||||
"@docusaurus/utils-validation": "2.3.0",
|
||||
"@philpl/buble": "^0.19.7",
|
||||
"clsx": "^1.2.1",
|
||||
"fs-extra": "^10.1.0",
|
||||
|
@ -34,7 +34,7 @@
|
|||
"tslib": "^2.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/types": "2.2.0",
|
||||
"@docusaurus/types": "2.3.0",
|
||||
"@types/buble": "^0.20.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/theme-mermaid",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"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.0",
|
||||
"@docusaurus/module-type-aliases": "2.3.0",
|
||||
"@docusaurus/theme-common": "2.3.0",
|
||||
"@docusaurus/types": "2.3.0",
|
||||
"@docusaurus/utils-validation": "2.3.0",
|
||||
"@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,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
|
||||
|
|
|
@ -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: {},
|
||||
},
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
"module": "esnext",
|
||||
"target": "esnext"
|
||||
},
|
||||
"include": ["src/theme", "src/*.d.ts"],
|
||||
"include": ["src/client", "src/theme", "src/*.d.ts"],
|
||||
"exclude": ["**/__tests__/**"]
|
||||
}
|
||||
|
|
|
@ -10,5 +10,5 @@
|
|||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["src/theme", "**/__tests__/**"]
|
||||
"exclude": ["src/client", "src/theme", "**/__tests__/**"]
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/theme-search-algolia",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"description": "Algolia search component for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"sideEffects": [
|
||||
|
@ -34,13 +34,13 @@
|
|||
},
|
||||
"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.0",
|
||||
"@docusaurus/logger": "2.3.0",
|
||||
"@docusaurus/plugin-content-docs": "2.3.0",
|
||||
"@docusaurus/theme-common": "2.3.0",
|
||||
"@docusaurus/theme-translations": "2.3.0",
|
||||
"@docusaurus/utils": "2.3.0",
|
||||
"@docusaurus/utils-validation": "2.3.0",
|
||||
"algoliasearch": "^4.13.1",
|
||||
"algoliasearch-helper": "^3.10.0",
|
||||
"clsx": "^1.2.1",
|
||||
|
@ -51,7 +51,7 @@
|
|||
"utility-types": "^3.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.2.0"
|
||||
"@docusaurus/module-type-aliases": "2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.4 || ^17.0.0",
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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],
|
||||
);
|
||||
}
|
|
@ -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' {
|
||||
|
|
|
@ -5,20 +5,23 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {useState, useRef, useCallback, useMemo} from 'react';
|
||||
import {createPortal} from 'react-dom';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import {useHistory} from '@docusaurus/router';
|
||||
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
|
||||
import Link from '@docusaurus/Link';
|
||||
import React, {useCallback, useMemo, useRef, useState} from 'react';
|
||||
import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
|
||||
import Head from '@docusaurus/Head';
|
||||
import Link from '@docusaurus/Link';
|
||||
import {useHistory} from '@docusaurus/router';
|
||||
import {isRegexpStringMatch} from '@docusaurus/theme-common';
|
||||
import {useSearchPage} from '@docusaurus/theme-common/internal';
|
||||
import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
|
||||
import {useAlgoliaContextualFacetFilters} from '@docusaurus/theme-search-algolia/client';
|
||||
import {
|
||||
useAlgoliaContextualFacetFilters,
|
||||
useSearchResultUrlProcessor,
|
||||
} from '@docusaurus/theme-search-algolia/client';
|
||||
import Translate from '@docusaurus/Translate';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import {createPortal} from 'react-dom';
|
||||
import translations from '@theme/SearchTranslations';
|
||||
|
||||
import type {AutocompleteState} from '@algolia/autocomplete-core';
|
||||
import type {
|
||||
DocSearchModal as DocSearchModalType,
|
||||
DocSearchModalProps,
|
||||
|
@ -28,7 +31,6 @@ import type {
|
|||
StoredDocSearchHit,
|
||||
} from '@docsearch/react/dist/esm/types';
|
||||
import type {SearchClient} from 'algoliasearch/lite';
|
||||
import type {AutocompleteState} from '@algolia/autocomplete-core';
|
||||
|
||||
type DocSearchProps = Omit<
|
||||
DocSearchModalProps,
|
||||
|
@ -88,6 +90,7 @@ function DocSearch({
|
|||
...props
|
||||
}: DocSearchProps) {
|
||||
const {siteMetadata} = useDocusaurusContext();
|
||||
const processSearchResultUrl = useSearchResultUrlProcessor();
|
||||
|
||||
const contextualSearchFacetFilters =
|
||||
useAlgoliaContextualFacetFilters() as FacetFilters;
|
||||
|
@ -107,7 +110,6 @@ function DocSearch({
|
|||
facetFilters,
|
||||
};
|
||||
|
||||
const {withBaseUrl} = useBaseUrlUtils();
|
||||
const history = useHistory();
|
||||
const searchContainer = useRef<HTMLDivElement | null>(null);
|
||||
const searchButtonRef = useRef<HTMLButtonElement>(null);
|
||||
|
@ -172,20 +174,14 @@ function DocSearch({
|
|||
|
||||
const transformItems = useRef<DocSearchModalProps['transformItems']>(
|
||||
(items) =>
|
||||
items.map((item) => {
|
||||
// If Algolia contains a external domain, we should navigate without
|
||||
// relative URL
|
||||
if (isRegexpStringMatch(externalUrlRegex, item.url)) {
|
||||
return item;
|
||||
}
|
||||
|
||||
// We transform the absolute URL into a relative URL.
|
||||
const url = new URL(item.url);
|
||||
return {
|
||||
...item,
|
||||
url: withBaseUrl(`${url.pathname}${url.hash}`),
|
||||
};
|
||||
}),
|
||||
props.transformItems
|
||||
? // Custom transformItems
|
||||
props.transformItems(items)
|
||||
: // Default transformItems
|
||||
items.map((item) => ({
|
||||
...item,
|
||||
url: processSearchResultUrl(item.url),
|
||||
})),
|
||||
).current;
|
||||
|
||||
const resultsFooterComponent: DocSearchProps['resultsFooterComponent'] =
|
||||
|
|
|
@ -7,32 +7,34 @@
|
|||
|
||||
/* eslint-disable jsx-a11y/no-autofocus */
|
||||
|
||||
import React, {useEffect, useState, useReducer, useRef} from 'react';
|
||||
import React, {useEffect, useReducer, useRef, useState} from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import algoliaSearch from 'algoliasearch/lite';
|
||||
import algoliaSearchHelper from 'algoliasearch-helper';
|
||||
import algoliaSearch from 'algoliasearch/lite';
|
||||
|
||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||
import Head from '@docusaurus/Head';
|
||||
import Link from '@docusaurus/Link';
|
||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||
import {useAllDocsData} from '@docusaurus/plugin-content-docs/client';
|
||||
import {
|
||||
HtmlClassNameProvider,
|
||||
usePluralForm,
|
||||
isRegexpStringMatch,
|
||||
useEvent,
|
||||
usePluralForm,
|
||||
} from '@docusaurus/theme-common';
|
||||
import {
|
||||
useTitleFormatter,
|
||||
useSearchPage,
|
||||
useTitleFormatter,
|
||||
} from '@docusaurus/theme-common/internal';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import {useAllDocsData} from '@docusaurus/plugin-content-docs/client';
|
||||
import Translate, {translate} from '@docusaurus/Translate';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import {
|
||||
useAlgoliaThemeConfig,
|
||||
useSearchResultUrlProcessor,
|
||||
} from '@docusaurus/theme-search-algolia/client';
|
||||
import Layout from '@theme/Layout';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
import type {ThemeConfig} from '@docusaurus/theme-search-algolia';
|
||||
|
||||
// Very simple pluralization: probably good enough for now
|
||||
function useDocumentsFoundPlural() {
|
||||
|
@ -156,12 +158,12 @@ type ResultDispatcher =
|
|||
|
||||
function SearchPageContent(): JSX.Element {
|
||||
const {
|
||||
siteConfig: {themeConfig},
|
||||
i18n: {currentLocale},
|
||||
} = useDocusaurusContext();
|
||||
const {
|
||||
algolia: {appId, apiKey, indexName, externalUrlRegex},
|
||||
} = themeConfig as ThemeConfig;
|
||||
algolia: {appId, apiKey, indexName},
|
||||
} = useAlgoliaThemeConfig();
|
||||
const processSearchResultUrl = useSearchResultUrlProcessor();
|
||||
const documentsFoundPlural = useDocumentsFoundPlural();
|
||||
|
||||
const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers();
|
||||
|
@ -244,16 +246,12 @@ function SearchPageContent(): JSX.Element {
|
|||
_highlightResult: {hierarchy: {[key: string]: {value: string}}};
|
||||
_snippetResult: {content?: {value: string}};
|
||||
}) => {
|
||||
const parsedURL = new URL(url);
|
||||
const titles = Object.keys(hierarchy).map((key) =>
|
||||
sanitizeValue(hierarchy[key]!.value),
|
||||
);
|
||||
|
||||
return {
|
||||
title: titles.pop()!,
|
||||
url: isRegexpStringMatch(externalUrlRegex, parsedURL.href)
|
||||
? parsedURL.href
|
||||
: parsedURL.pathname + parsedURL.hash,
|
||||
url: processSearchResultUrl(url),
|
||||
summary: snippet.content
|
||||
? `${sanitizeValue(snippet.content.value)}...`
|
||||
: '',
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {escapeRegexp} from '@docusaurus/utils';
|
||||
import {Joi} from '@docusaurus/utils-validation';
|
||||
import type {
|
||||
ThemeConfig,
|
||||
|
@ -39,6 +40,19 @@ export const Schema = Joi.object<ThemeConfig>({
|
|||
.try(Joi.boolean().invalid(true), Joi.string())
|
||||
.allow(null)
|
||||
.default(DEFAULT_CONFIG.searchPagePath),
|
||||
replaceSearchResultPathname: Joi.object({
|
||||
from: Joi.custom((from) => {
|
||||
if (typeof from === 'string') {
|
||||
return escapeRegexp(from);
|
||||
} else if (from instanceof RegExp) {
|
||||
return from.source;
|
||||
}
|
||||
throw new Error(
|
||||
`it should be a RegExp or a string, but received ${from}`,
|
||||
);
|
||||
}).required(),
|
||||
to: Joi.string().required(),
|
||||
}).optional(),
|
||||
})
|
||||
.label('themeConfig.algolia')
|
||||
.required()
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "تبديل فئة الشريط الجاني القابل للاغلاق '{label}'",
|
||||
"theme.ErrorPageContent.title": "هذه الصفحة لا تستجيب.",
|
||||
"theme.ErrorPageContent.tryAgain": "المحاولة مجددا",
|
||||
"theme.NavBar.navAriaLabel": "Main",
|
||||
"theme.NotFound.p1": "لم نتمكن من العثور على ما كنت تبحث عنه.",
|
||||
"theme.NotFound.p2": "يرجى الاتصال بمالك الموقع الذي ربطك بعنوان URL الأصلي وإخباره بأن الارتباط الخاص به معطل.",
|
||||
"theme.NotFound.title": "الصفحة غير موجودة",
|
||||
|
@ -35,7 +36,7 @@
|
|||
"theme.colorToggle.ariaLabel.mode.dark": "الوضع الداكن",
|
||||
"theme.colorToggle.ariaLabel.mode.light": "الوضع الفاتح",
|
||||
"theme.common.editThisPage": "تعديل هذه الصفحة",
|
||||
"theme.common.headingLinkTitle": "ارتباط مباشر بالعنوان",
|
||||
"theme.common.headingLinkTitle": "ارتباط مباشر بالعنوان {heading}",
|
||||
"theme.common.skipToMainContent": "انتقل إلى المحتوى الرئيسي",
|
||||
"theme.docs.DocCard.categoryDescription": "{count} مواد",
|
||||
"theme.docs.breadcrumbs.home": "الرئيسية",
|
||||
|
@ -48,6 +49,7 @@
|
|||
"theme.docs.sidebar.collapseButtonTitle": "طي الشريط الجانبي",
|
||||
"theme.docs.sidebar.expandButtonAriaLabel": "توسيع الشريط الجانبي",
|
||||
"theme.docs.sidebar.expandButtonTitle": "توسيع الشريط الجانبي",
|
||||
"theme.docs.sidebar.navAriaLabel": "Docs sidebar",
|
||||
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": "Toggle navigation bar",
|
||||
"theme.docs.tagDocListPageTitle": "{nDocsTagged} مستند موسوم بـ \"{tagName}\"",
|
||||
"theme.docs.tagDocListPageTitle.nDocsTagged": "مستند موسوم واحد|{count} مستندات موسومة",
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
"theme.ErrorPageContent.title": "This page crashed.",
|
||||
"theme.ErrorPageContent.title___DESCRIPTION": "The title of the fallback page when the page crashed",
|
||||
"theme.ErrorPageContent.tryAgain": "Try again",
|
||||
"theme.ErrorPageContent.tryAgain___DESCRIPTION": "The label of the button to try again when the page crashed",
|
||||
"theme.ErrorPageContent.tryAgain___DESCRIPTION": "The label of the button to try again rendering when the React error boundary captures an error",
|
||||
"theme.NavBar.navAriaLabel": "Main",
|
||||
"theme.NavBar.navAriaLabel___DESCRIPTION": "The ARIA label for the main navigation",
|
||||
"theme.NotFound.p1": "We could not find what you were looking for.",
|
||||
"theme.NotFound.p1___DESCRIPTION": "The first paragraph of the 404 page",
|
||||
"theme.NotFound.p2": "Please contact the owner of the site that linked you to the original URL and let them know their link is broken.",
|
||||
|
@ -71,7 +73,7 @@
|
|||
"theme.colorToggle.ariaLabel.mode.light___DESCRIPTION": "The name for the light color mode",
|
||||
"theme.common.editThisPage": "Edit this page",
|
||||
"theme.common.editThisPage___DESCRIPTION": "The link label to edit the current page",
|
||||
"theme.common.headingLinkTitle": "Direct link to heading",
|
||||
"theme.common.headingLinkTitle": "Direct link to {heading}",
|
||||
"theme.common.headingLinkTitle___DESCRIPTION": "Title for link to heading",
|
||||
"theme.common.skipToMainContent": "Skip to main content",
|
||||
"theme.common.skipToMainContent___DESCRIPTION": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation",
|
||||
|
@ -97,6 +99,8 @@
|
|||
"theme.docs.sidebar.expandButtonAriaLabel___DESCRIPTION": "The ARIA label and title attribute for expand button of doc sidebar",
|
||||
"theme.docs.sidebar.expandButtonTitle": "Expand sidebar",
|
||||
"theme.docs.sidebar.expandButtonTitle___DESCRIPTION": "The ARIA label and title attribute for expand button of doc sidebar",
|
||||
"theme.docs.sidebar.navAriaLabel": "Docs sidebar",
|
||||
"theme.docs.sidebar.navAriaLabel___DESCRIPTION": "The ARIA label for the sidebar navigation",
|
||||
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": "Toggle navigation bar",
|
||||
"theme.docs.sidebar.toggleSidebarButtonAriaLabel___DESCRIPTION": "The ARIA label for hamburger menu button of mobile navigation",
|
||||
"theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"",
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "Toggle the collapsible sidebar category '{label}'",
|
||||
"theme.ErrorPageContent.title": "This page crashed.",
|
||||
"theme.ErrorPageContent.tryAgain": "Try again",
|
||||
"theme.NavBar.navAriaLabel": "Main",
|
||||
"theme.NotFound.p1": "আপনি যা খুঁজছিলেন তা আমরা খুঁজে পাইনি।",
|
||||
"theme.NotFound.p2": "দয়া করে সাইটের মালিকের সাথে যোগাযোগ করুন যা আপনাকে মূল URL এর সাথে যুক্ত করেছে এবং তাদের লিঙ্কটি ভাঙ্গা রয়েছে তা তাদের জানান।",
|
||||
"theme.NotFound.title": "পেজটি খুঁজে পাওয়া যায়নি",
|
||||
|
@ -35,7 +36,7 @@
|
|||
"theme.colorToggle.ariaLabel.mode.dark": "dark mode",
|
||||
"theme.colorToggle.ariaLabel.mode.light": "light mode",
|
||||
"theme.common.editThisPage": "এই পেজটি এডিট করুন",
|
||||
"theme.common.headingLinkTitle": "হেডিং এর সঙ্গে সরাসরি লিংকড",
|
||||
"theme.common.headingLinkTitle": "{heading} এর সঙ্গে সরাসরি লিংকড",
|
||||
"theme.common.skipToMainContent": "স্কিপ করে মূল কন্টেন্ট এ যান",
|
||||
"theme.docs.DocCard.categoryDescription": "{count} items",
|
||||
"theme.docs.breadcrumbs.home": "Home page",
|
||||
|
@ -48,6 +49,7 @@
|
|||
"theme.docs.sidebar.collapseButtonTitle": "সাইডবারটি সঙ্কুচিত করুন",
|
||||
"theme.docs.sidebar.expandButtonAriaLabel": "সাইডবারটি প্রসারিত করুন",
|
||||
"theme.docs.sidebar.expandButtonTitle": "সাইডবারটি প্রসারিত করুন",
|
||||
"theme.docs.sidebar.navAriaLabel": "Docs sidebar",
|
||||
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": "Toggle navigation bar",
|
||||
"theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"",
|
||||
"theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged",
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "Toggle the collapsible sidebar category '{label}'",
|
||||
"theme.ErrorPageContent.title": "This page crashed.",
|
||||
"theme.ErrorPageContent.tryAgain": "Try again",
|
||||
"theme.NavBar.navAriaLabel": "Main",
|
||||
"theme.NotFound.p1": "Nepodařilo se nám najít co jste hledal(a).",
|
||||
"theme.NotFound.p2": "Kontaktujte prosím vlastníka webu, který vás odkázal na původní URL a upozorněte ho, že jejich odkaz nefunguje.",
|
||||
"theme.NotFound.title": "Stránka nenalezena",
|
||||
|
@ -35,7 +36,7 @@
|
|||
"theme.colorToggle.ariaLabel.mode.dark": "dark mode",
|
||||
"theme.colorToggle.ariaLabel.mode.light": "light mode",
|
||||
"theme.common.editThisPage": "Upravit tuto stránku",
|
||||
"theme.common.headingLinkTitle": "Přímý odkaz na nadpis",
|
||||
"theme.common.headingLinkTitle": "Přímý odkaz na {heading}",
|
||||
"theme.common.skipToMainContent": "Přeskočit na hlavní obsah",
|
||||
"theme.docs.DocCard.categoryDescription": "{count} items",
|
||||
"theme.docs.breadcrumbs.home": "Home page",
|
||||
|
@ -48,6 +49,7 @@
|
|||
"theme.docs.sidebar.collapseButtonTitle": "Zavřít postranní lištu",
|
||||
"theme.docs.sidebar.expandButtonAriaLabel": "Otevřít postranní lištu",
|
||||
"theme.docs.sidebar.expandButtonTitle": "Otevřít postranní lištu",
|
||||
"theme.docs.sidebar.navAriaLabel": "Docs sidebar",
|
||||
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": "Toggle navigation bar",
|
||||
"theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"",
|
||||
"theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged",
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "Toggle the collapsible sidebar category '{label}'",
|
||||
"theme.ErrorPageContent.title": "This page crashed.",
|
||||
"theme.ErrorPageContent.tryAgain": "Try again",
|
||||
"theme.NavBar.navAriaLabel": "Main",
|
||||
"theme.NotFound.p1": "Vi kunne ikke finde det, du søgte.",
|
||||
"theme.NotFound.p2": "Venligst kontakt ejeren til webstedet, som førte dig frem denne URL, og informer dem om at linket ikke virker.",
|
||||
"theme.NotFound.title": "Siden blev ikke fundet",
|
||||
|
@ -35,7 +36,7 @@
|
|||
"theme.colorToggle.ariaLabel.mode.dark": "dark mode",
|
||||
"theme.colorToggle.ariaLabel.mode.light": "light mode",
|
||||
"theme.common.editThisPage": "Rediger denne side",
|
||||
"theme.common.headingLinkTitle": "Direkte link til overskrift",
|
||||
"theme.common.headingLinkTitle": "Direkte link til {heading}",
|
||||
"theme.common.skipToMainContent": "Hop til hovedindhold",
|
||||
"theme.docs.DocCard.categoryDescription": "{count} items",
|
||||
"theme.docs.breadcrumbs.home": "Home page",
|
||||
|
@ -48,6 +49,7 @@
|
|||
"theme.docs.sidebar.collapseButtonTitle": "Sammenlæg sidenavigation",
|
||||
"theme.docs.sidebar.expandButtonAriaLabel": "Udvid sidenavigation",
|
||||
"theme.docs.sidebar.expandButtonTitle": "Udvid sidenavigation",
|
||||
"theme.docs.sidebar.navAriaLabel": "Docs sidebar",
|
||||
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": "Toggle navigation bar",
|
||||
"theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"",
|
||||
"theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged",
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "Umschalten der Seitenleiste mit einklappbarer Kategorie '{label}'",
|
||||
"theme.ErrorPageContent.title": "Die Seite ist abgestürzt.",
|
||||
"theme.ErrorPageContent.tryAgain": "Nochmal versuchen",
|
||||
"theme.NavBar.navAriaLabel": "Main",
|
||||
"theme.NotFound.p1": "Wir konnten nicht finden, wonach Sie gesucht haben.",
|
||||
"theme.NotFound.p2": "Bitte kontaktieren Sie den Besitzer der Seite, die Sie mit der ursprünglichen URL verlinkt hat, und teilen Sie ihm mit, dass der Link nicht mehr funktioniert.",
|
||||
"theme.NotFound.title": "Seite nicht gefunden",
|
||||
|
@ -35,7 +36,7 @@
|
|||
"theme.colorToggle.ariaLabel.mode.dark": "dunkler Modus",
|
||||
"theme.colorToggle.ariaLabel.mode.light": "heller Modus",
|
||||
"theme.common.editThisPage": "Diese Seite bearbeiten",
|
||||
"theme.common.headingLinkTitle": "Direkter Link zur Überschrift",
|
||||
"theme.common.headingLinkTitle": "Direkter Link zur {heading}",
|
||||
"theme.common.skipToMainContent": "Zum Hauptinhalt springen",
|
||||
"theme.docs.DocCard.categoryDescription": "{count} Einträge",
|
||||
"theme.docs.breadcrumbs.home": "Home page",
|
||||
|
@ -48,6 +49,7 @@
|
|||
"theme.docs.sidebar.collapseButtonTitle": "Seitenleiste einklappen",
|
||||
"theme.docs.sidebar.expandButtonAriaLabel": "Seitenleiste ausklappen",
|
||||
"theme.docs.sidebar.expandButtonTitle": "Seitenleiste ausklappen",
|
||||
"theme.docs.sidebar.navAriaLabel": "Docs sidebar",
|
||||
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": "Toggle navigation bar",
|
||||
"theme.docs.tagDocListPageTitle": "{nDocsTagged} mit \"{tagName}\"",
|
||||
"theme.docs.tagDocListPageTitle.nDocsTagged": "Ein doc getaggt|{count} docs getaggt",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue