Compare commits

...

36 Commits
main ... v2.4.0

Author SHA1 Message Date
sebastienlorber 898b85ef13 v2.4.0 2023-03-23 19:16:05 +01:00
sebastienlorber ac61ed0a7b fix doc link 2023-03-23 16:06:21 +01:00
sebastienlorber 5509d57e5c chore: fix translations consistency test after bad translation backport 2023-03-23 15:35:47 +01:00
Vishruta Patil 1f4251154a fix(theme): codeblock buttons should be kept on the right when using RTL locale (#8803)
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
2023-03-23 15:26:12 +01:00
Joshua Chen 002b0eee27 fix(theme): allow tabs children to be falsy (#8801) 2023-03-23 15:25:59 +01:00
biplavmz 4694dd2c45 polish(create-docusaurus): the starter template should use a navbar item "docSidebar" instead of "doc" (less fragile on updates) (#8712)
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
2023-03-23 15:25:27 +01:00
Davide Donadio ba688bceec feat(content-docs): add support for sidebar item category/link descriptions in generated index page (#8236)
Co-authored-by: Davide Donadio <davide.donadio@it.clara.net>
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
2023-03-23 15:24:29 +01:00
Sébastien Lorber a3eef990ec fix(search): search page should react to querystring changes + cleanup/refactor (#8757) 2023-03-23 15:23:53 +01:00
sebastienlorber 4b37c01a18 polish(theme): better error messages on navbar item rendering failures + ErrorCauseBoundary API (#8735)
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
2023-03-23 15:22:46 +01:00
Tanner Dolby 6deecd7bf1 polish(core): better styling for error screens (#8736)
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
2023-03-23 15:19:31 +01:00
Sébastien Lorber 8e2cfac10a fix(core): baseUrl error banner link anchor case (#8746) 2023-03-23 15:09:58 +01:00
Anas 7bca0e5c7e fix(theme-translations): fix wrong arabic words (tip/next) (#8744) 2023-03-23 15:09:33 +01:00
Sébastien Lorber 186d670200 feat(theme): allow to load a Docusaurus page with theme from query-string: ?docusaurus-theme=dark (#8708) 2023-03-23 15:08:54 +01:00
Armano a8ab309ec8 feat(npm-to-yarn): add support for PnPm and custom converters (#8690)
Co-authored-by: Ben Gubler <nebrelbug@gmail.com>
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
2023-03-23 15:06:22 +01:00
sebastienlorber 3a73ce5ee1 feat(core): add script env variables: NODE_ENV + BABEL_ENV + DOCUSAURUS_CURRENT_LOCALE (temporary i18n workaround) (#8677) 2023-03-23 14:58:53 +01:00
Dewansh Thakur e8d971d2d6 fix(theme): improve color toggle when using dark navbar (#8615)
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
2023-03-23 14:52:28 +01:00
Kagan a12c6f8119 fix(theme-classic): fix tab focus bug in dropdown (#8697) (#8699)
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
2023-03-23 14:51:51 +01:00
Sébastien Lorber 147e0a822b feat(theme-classic): respect `prefers-reduced-motion: reduce` mediaquery, bump Infima to alpha.43 (#8674) 2023-03-23 14:50:33 +01:00
TrueQAP 37216590ec feat(theme-translations): add Hungarian theme translations (#8668)
Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
2023-03-23 14:49:36 +01:00
Mysterious_Dev c0f3675d32 feat(theme): add ability to translate navbar+footer logo alt text (#8616)
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
2023-03-23 14:49:08 +01:00
Moritz Stückler f54e3057fe feat(theme-common): allow passing a string for details summary (#8656)
Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
2023-03-23 14:48:35 +01:00
Petter Drønnen 3bd6010f3f feat(theme-translations): add Norwegian (Bokmål) theme translation (#8631) 2023-03-23 14:47:37 +01:00
Sébastien Lorber e5dcd4e4cf feat(gtag-plugin): gtag should support multiple tracking ids, notably for the UA => GA4 transition (#8620) 2023-03-23 14:39:48 +01:00
Sébastien Lorber 985a64ad22
fix(theme-classic): fix docs layout issue (#8707)
fix issue reported in https://github.com/facebook/docusaurus/issues/8369#issuecomment-1426838764
2023-02-24 15:20:30 +01:00
Sébastien Lorber c60387dbe8
chore: backport retro compatible commits for the Docusaurus v2.3.1 release (#8621)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
Co-authored-by: Sébastien Castiel <sebastien@castiel.me>
2023-02-03 15:04:03 +01:00
Sébastien Lorber c84d779627
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>
2023-01-27 17:02:15 +01:00
Sébastien Lorber de972142a8
chore: backport retro compatible commits for the Docusaurus v2.2 release (#8264)
Co-authored-by: Jan Peer Stoecklmair <jan.peer.stoecklmair@dynatrace.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: LittleboyHarry <littleboyharry@qq.com>
Co-authored-by: Mikey O'Toole <mikey@homotechsual.dev>
Co-authored-by: Jan Peer Stöcklmair <jan.oster94@gmail.com>
Co-authored-by: Nguyễn Thành Nam <namnguyenthanh.work@gmail.com>
Co-authored-by: Sanjaiyan Parthipan <parthipankalayini@gmail.com>
Co-authored-by: Ramazan SANCAR <ramazansancar4545@gmail.com>
Co-authored-by: mturoci <64769322+mturoci@users.noreply.github.com>
Co-authored-by: Adnan Hashmi <56730784+adnanhashmi09@users.noreply.github.com>
Co-authored-by: Pranav Joglekar <pranav2000joglekar@gmail.com>
Co-authored-by: forgeRW <20483211+forgeRW@users.noreply.github.com>
Co-authored-by: Masahiko Hara <pasora@sfc.wide.ad.jp>
Co-authored-by: Johan Fagerberg <johanringmann@gmail.com>
Co-authored-by: John Reilly <johnny_reilly@hotmail.com>
Co-authored-by: Sam Wall <oss@samuelwall.co.uk>
Co-authored-by: Jeferson S. Brito <30840709+jeferson-sb@users.noreply.github.com>
Co-authored-by: evan <evanmccarthy@outlook.com>
Co-authored-by: Xabier Lahuerta Vazquez <xlahuerta@protonmail.com>
Co-authored-by: Forresst <forresst17@gmail.com>
Co-authored-by: Shanmughapriyan S <priyanshan03@gmail.com>
Co-authored-by: Alexey Pyltsyn <lex61rus@gmail.com>
2022-10-29 15:13:42 +02:00
Sébastien Lorber 7743aa6307
chore: release Docusaurus v2.1.0 (#8040) 2022-09-02 12:41:55 +02:00
Sébastien Lorber 26d2b9a018
chore: backport retro compatible commits for the Docusaurus v2.1 release (#8033)
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
Co-authored-by: whiteand <andrewbeletskiy@gmail.com>
Co-authored-by: yzhe819 <68207314+yzhe819@users.noreply.github.com>
Co-authored-by: Ngô Quốc Đạt <56961917+datlechin@users.noreply.github.com>
Co-authored-by: Kevin Østerkilde <kevin@oesterkilde.dk>
Co-authored-by: Bagdasar Ovsepyan <66012777+b-ovsepian@users.noreply.github.com>
Co-authored-by: Yoni Chechik <chechik.yoni@gmail.com>
Co-authored-by: adventure-yunfei <adventure.yunfei@gmail.com>
Co-authored-by: Morgane Dubus <30866152+mdubus@users.noreply.github.com>
2022-09-02 12:20:33 +02:00
Sébastien Lorber bb65b5c578
chore: release v2.0.1 (#7919) 2022-08-08 16:23:27 +02:00
Sébastien Lorber 2ef40c2598
chore: Netlify branch deploys should only deploy default locale "en" (#7788) 2022-07-15 12:45:08 +02:00
Sébastien Lorber d88f248180
chore: add Netlify config for major version branch deploys (docusaurus-v2 branch) (#7787) 2022-07-15 12:10:36 +02:00
sebastienlorber e4fc47bec2 Merge branch 'main' into docusaurus-v2 2022-07-14 19:51:47 +02:00
Sébastien Lorber e78a15eeba
chore: ci tests should run on version branches "docusaurus-vX" (#7783) 2022-07-14 19:18:46 +02:00
Sébastien Lorber c751bc64ea
chore: regen v2.0.0-rc.1 examples (#7780) 2022-07-14 18:46:12 +02:00
Sébastien Lorber d255389e48
chore: prepare v2.0.0-rc.1 release (#7778) 2022-07-14 18:17:25 +02:00
360 changed files with 7323 additions and 1773 deletions

View File

@ -28,6 +28,7 @@
"__snapshots__",
"website/src/data/users.tsx",
"website/src/data/tweets.tsx",
"website/docusaurus.config.localized.json",
"*.xyz",
"*.docx",
"versioned_docs",

View File

@ -1,6 +1,6 @@
{
"name": "new.docusaurus.io",
"version": "2.0.0-rc.1",
"version": "2.4.0",
"private": true,
"scripts": {
"start": "npx --package netlify-cli netlify dev"

View File

@ -8,7 +8,7 @@
set -euo pipefail
CUSTOM_REGISTRY_URL="http://localhost:4873"
NEW_VERSION="$(node -p "require('./packages/docusaurus/package.json').version").NEW"
NEW_VERSION="$(node -p "require('./packages/docusaurus/package.json').version")-NEW"
CONTAINER_NAME="verdaccio"
EXTRA_OPTS=""

View File

@ -1,5 +1,5 @@
{
"version": "2.0.0-rc.1",
"version": "2.4.0",
"npmClient": "yarn",
"useWorkspaces": true,
"changelog": {
@ -11,6 +11,7 @@
"pr: performance": ":running_woman: Performance",
"pr: polish": ":nail_care: Polish",
"pr: documentation": ":memo: Documentation",
"pr: dependencies": ":robot: Dependencies",
"pr: maintenance": ":wrench: Maintenance"
},
"cacheDir": ".changelog"

View File

@ -1,6 +1,6 @@
{
"name": "create-docusaurus",
"version": "2.0.0-rc.1",
"version": "2.4.0",
"description": "Create Docusaurus apps easily.",
"type": "module",
"repository": {
@ -22,8 +22,8 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/logger": "2.0.0-rc.1",
"@docusaurus/utils": "2.0.0-rc.1",
"@docusaurus/logger": "2.4.0",
"@docusaurus/utils": "2.4.0",
"commander": "^5.1.0",
"fs-extra": "^10.1.0",
"lodash": "^4.17.21",

View File

@ -456,8 +456,10 @@ export default async function init(
reqTemplate?: string,
cliOptions: CLIOptions = {},
): Promise<void> {
const templates = await readTemplates();
const siteName = await getSiteName(reqName, rootDir);
const [templates, siteName] = await Promise.all([
readTemplates(),
getSiteName(reqName, rootDir),
]);
const dest = path.resolve(rootDir, siteName);
const source = await getSource(reqTemplate, templates, cliOptions);

View File

@ -1,6 +1,6 @@
{
"name": "docusaurus-2-classic-typescript-template",
"version": "2.0.0-rc.1",
"version": "2.4.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@ -15,8 +15,8 @@
"typecheck": "tsc"
},
"dependencies": {
"@docusaurus/core": "2.0.0-rc.1",
"@docusaurus/preset-classic": "2.0.0-rc.1",
"@docusaurus/core": "2.4.0",
"@docusaurus/preset-classic": "2.4.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.0.0-rc.1",
"@docusaurus/module-type-aliases": "2.4.0",
"@tsconfig/docusaurus": "^1.0.5",
"typescript": "^4.7.4"
},

View File

@ -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: {
@ -64,8 +71,8 @@ const config = {
},
items: [
{
type: 'doc',
docId: 'intro',
type: 'docSidebar',
sidebarId: 'tutorialSidebar',
position: 'left',
label: 'Tutorial',
},

View File

@ -1,6 +1,6 @@
{
"name": "docusaurus-2-classic-template",
"version": "2.0.0-rc.1",
"version": "2.4.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@ -14,8 +14,8 @@
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "2.0.0-rc.1",
"@docusaurus/preset-classic": "2.0.0-rc.1",
"@docusaurus/core": "2.4.0",
"@docusaurus/preset-classic": "2.4.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.0.0-rc.1"
"@docusaurus/module-type-aliases": "2.4.0"
},
"browserslist": {
"production": [

View File

@ -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: {
@ -61,8 +67,8 @@ const config = {
},
items: [
{
type: 'doc',
docId: 'intro',
type: 'docSidebar',
sidebarId: 'tutorialSidebar',
position: 'left',
label: 'Tutorial',
},

View File

@ -1,6 +1,6 @@
{
"name": "docusaurus-2-facebook-template",
"version": "2.0.0-rc.1",
"version": "2.4.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.0.0-rc.1",
"@docusaurus/preset-classic": "2.0.0-rc.1",
"@docusaurus/core": "2.4.0",
"@docusaurus/preset-classic": "2.4.0",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"react": "^17.0.2",

View File

@ -25,10 +25,12 @@ module.exports = {
// But you can create a sidebar manually
/*
tutorialSidebar: [
'intro',
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['hello'],
items: ['tutorial-basics/create-a-document'],
},
],
*/

View File

@ -14,7 +14,9 @@ Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://
## What's next?
- Read the [official documentation](https://docusaurus.io/).
- Read the [official documentation](https://docusaurus.io/)
- Modify your site configuration with [`docusaurus.config.js`](https://docusaurus.io/docs/api/docusaurus-config)
- Add navbar and footer items with [`themeConfig`](https://docusaurus.io/docs/api/themes/configuration)
- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout)
- Add a [search bar](https://docusaurus.io/docs/search)
- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase)

View File

@ -44,11 +44,13 @@ It is also possible to create your sidebar explicitly in `sidebars.js`:
```js title="sidebars.js"
module.exports = {
tutorialSidebar: [
'intro',
// highlight-next-line
'hello',
{
type: 'category',
label: 'Tutorial',
// highlight-next-line
items: ['hello'],
items: ['tutorial-basics/create-a-document'],
},
],
};

View File

@ -51,7 +51,11 @@ You can use absolute paths to reference images in the static directory (`static/
![Docusaurus logo](/img/docusaurus.png)
You can reference images relative to the current file as well, as shown in [the extra guides](../tutorial-extras/manage-docs-versions.md).
You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them:
```md
![Docusaurus logo](./img/docusaurus.png)
```
## Code Blocks

View File

@ -19,10 +19,12 @@ const sidebars = {
// But you can create a sidebar manually
/*
tutorialSidebar: [
'intro',
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['hello'],
items: ['tutorial-basics/create-a-document'],
},
],
*/

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/cssnano-preset",
"version": "2.0.0-rc.1",
"version": "2.4.0",
"description": "Advanced cssnano preset for maximum optimization.",
"main": "lib/index.js",
"license": "MIT",

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/logger",
"version": "2.0.0-rc.1",
"version": "2.4.0",
"description": "An encapsulated logger for semantically formatting console messages.",
"main": "./lib/index.js",
"repository": {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/mdx-loader",
"version": "2.0.0-rc.1",
"version": "2.4.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.0.0-rc.1",
"@docusaurus/utils": "2.0.0-rc.1",
"@docusaurus/logger": "2.4.0",
"@docusaurus/utils": "2.4.0",
"@mdx-js/mdx": "^1.6.22",
"escape-html": "^1.0.3",
"file-loader": "^6.2.0",
@ -37,7 +37,7 @@
"webpack": "^5.73.0"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-rc.1",
"@docusaurus/types": "2.4.0",
"@types/escape-html": "^1.0.2",
"@types/mdast": "^3.0.10",
"@types/stringify-object": "^3.3.1",

View File

@ -22,8 +22,10 @@ import toc from './remark/toc';
import unwrapMdxCodeBlocks from './remark/unwrapMdxCodeBlocks';
import transformImage from './remark/transformImage';
import transformLinks from './remark/transformLinks';
import mermaid from './remark/mermaid';
import transformAdmonitions from './remark/admonitions';
import type {MarkdownConfig} from '@docusaurus/types';
import type {LoaderContext} from 'webpack';
import type {Processor, Plugin} from 'unified';
import type {AdmonitionOptions} from './remark/admonitions';
@ -61,6 +63,7 @@ export type MDXOptions = {
};
export type Options = Partial<MDXOptions> & {
markdownConfig: MarkdownConfig;
staticDirs: string[];
siteDir: string;
isMDXPartial?: (filePath: string) => boolean;
@ -71,7 +74,6 @@ export type Options = Partial<MDXOptions> & {
frontMatter: {[key: string]: unknown};
metadata: {[key: string]: unknown};
}) => {[key: string]: unknown};
filepath: string;
};
/**
@ -171,6 +173,7 @@ export async function mdxLoader(
...(reqOptions.beforeDefaultRemarkPlugins ?? []),
...getAdmonitionsPlugins(reqOptions.admonitions ?? false),
...DEFAULT_OPTIONS.remarkPlugins,
...(reqOptions.markdownConfig.mermaid ? [mermaid] : []),
[
transformImage,
{

View 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...
:::
::::

View File

@ -42,3 +42,8 @@ exports[`admonitions remark plugin interpolation 1`] = `
"<p>Test admonition with interpolated title/body</p>
<admonition type="tip"><mdxAdmonitionTitle>My <code>interpolated</code> <strong>title</strong> &#x3C;button style={{color: "red"}} onClick={() => alert("click")}>test</mdxAdmonitionTitle><p><code>body</code> <strong>interpolated</strong> content</p></admonition>"
`;
exports[`admonitions remark plugin nesting 1`] = `
"<p>Test nested Admonitions</p>
<admonition type="info"><mdxAdmonitionTitle><strong>Weather</strong></mdxAdmonitionTitle><p>On nice days, you can enjoy skiing in the mountains.</p><admonition type="danger"><mdxAdmonitionTitle><em>Storms</em></mdxAdmonitionTitle><p>Take care of snowstorms...</p></admonition></admonition>"
`;

View File

@ -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();
});
});

View File

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

View File

@ -0,0 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`mermaid remark plugin does nothing if there's no mermaid code block 1`] = `
"
const layoutProps = {
};
const MDXLayout = "wrapper"
export default function MDXContent({
components,
...props
}) {
return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
<h1>{\`Heading 1\`}</h1>
<p>{\`No Mermaid diagram :(\`}</p>
<pre><code parentName="pre" {...{
"className": "language-js"
}}>{\`this is not mermaid
\`}</code></pre>
</MDXLayout>;
}
;
MDXContent.isMDXComponent = true;"
`;
exports[`mermaid remark plugin works for basic mermaid code blocks 1`] = `
"
const layoutProps = {
};
const MDXLayout = "wrapper"
export default function MDXContent({
components,
...props
}) {
return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
<h1>{\`Heading 1\`}</h1>
<mermaid {...{
"value": "graph TD;/n A-->B;/n A-->C;/n B-->D;/n C-->D;"
}}></mermaid>
</MDXLayout>;
}
;
MDXContent.isMDXComponent = true;"
`;

View File

@ -0,0 +1,46 @@
/**
* 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 {createCompiler} from '@mdx-js/mdx';
import mermaid from '..';
describe('mermaid remark plugin', () => {
function createTestCompiler() {
return createCompiler({
remarkPlugins: [mermaid],
});
}
it("does nothing if there's no mermaid code block", async () => {
const mdxCompiler = createTestCompiler();
const result = await mdxCompiler.process(
`# Heading 1
No Mermaid diagram :(
\`\`\`js
this is not mermaid
\`\`\`
`,
);
expect(result.contents).toMatchSnapshot();
});
it('works for basic mermaid code blocks', async () => {
const mdxCompiler = createTestCompiler();
const result = await mdxCompiler.process(`# Heading 1
\`\`\`mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
\`\`\``);
expect(result.contents).toMatchSnapshot();
});
});

View File

@ -0,0 +1,32 @@
/**
* 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 visit from 'unist-util-visit';
import type {Transformer} from 'unified';
import type {Code} from 'mdast';
// TODO: this plugin shouldn't be in the core MDX loader
// After we allow plugins to provide Remark/Rehype plugins (see
// https://github.com/facebook/docusaurus/issues/6370), this should be provided
// by theme-mermaid itself
export default function plugin(): Transformer {
return (root) => {
visit(root, 'code', (node: Code, index, parent) => {
if (node.lang === 'mermaid') {
parent!.children.splice(index, 1, {
type: 'mermaidCodeBlock',
data: {
hName: 'mermaid',
hProperties: {
value: node.value,
},
},
});
}
});
};
}

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/migrate",
"version": "2.0.0-rc.1",
"version": "2.4.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.0.0-rc.1",
"@docusaurus/utils": "2.0.0-rc.1",
"@docusaurus/logger": "2.4.0",
"@docusaurus/utils": "2.4.0",
"@mapbox/hast-util-to-jsx": "^2.0.0",
"color": "^4.2.3",
"commander": "^5.1.0",

View File

@ -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"
}

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/module-type-aliases",
"version": "2.0.0-rc.1",
"version": "2.4.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.0.0-rc.1",
"@docusaurus/types": "2.4.0",
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router-config": "*",

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-client-redirects",
"version": "2.0.0-rc.1",
"version": "2.4.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.0.0-rc.1",
"@docusaurus/logger": "2.0.0-rc.1",
"@docusaurus/utils": "2.0.0-rc.1",
"@docusaurus/utils-common": "2.0.0-rc.1",
"@docusaurus/utils-validation": "2.0.0-rc.1",
"eta": "^1.12.3",
"@docusaurus/core": "2.4.0",
"@docusaurus/logger": "2.4.0",
"@docusaurus/utils": "2.4.0",
"@docusaurus/utils-common": "2.4.0",
"@docusaurus/utils-validation": "2.4.0",
"eta": "^2.0.0",
"fs-extra": "^10.1.0",
"lodash": "^4.17.21",
"tslib": "^2.4.0"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-rc.1"
"@docusaurus/types": "2.4.0"
},
"peerDependencies": {
"react": "^16.8.4 || ^17.0.0",

View File

@ -1,8 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`collectRedirects throw if plugin option redirects contain invalid to paths 1`] = `
"You are trying to create client-side redirections to paths that do not exist:
- /this/path/does/not/exist2
"You are trying to create client-side redirections to invalid paths.
These paths are redirected to but do not exist:
- /this/path/does/not/exist2
Valid paths you can redirect to:
@ -12,6 +13,22 @@ Valid paths you can redirect to:
"
`;
exports[`collectRedirects throw if plugin option redirects contain to paths with mismatching trailing slash 1`] = `
"You are trying to create client-side redirections to invalid paths.
These paths do exist, but because you have explicitly set trailingSlash=false, you need to write the path without trailing slash:
- /someExistingPath/
"
`;
exports[`collectRedirects throw if plugin option redirects contain to paths with mismatching trailing slash 2`] = `
"You are trying to create client-side redirections to invalid paths.
These paths do exist, but because you have explicitly set trailingSlash=true, you need to write the path with trailing slash:
- /someExistingPath
"
`;
exports[`collectRedirects throws if redirect creator creates array of array redirect 1`] = `
"Some created redirects are invalid:
- {"from":["/fromPath"],"to":"/"} => Validation error: "from" must be a string

View File

@ -9,7 +9,7 @@ exports[`createRedirectPageContent encodes uri special chars 1`] = `
<link rel="canonical" href="https://docusaurus.io/gr/%CF%83%CE%B5%CE%BB%CE%B9%CE%B4%CE%B1%CF%82/" />
</head>
<script>
window.location.href = 'https://docusaurus.io/gr/%CF%83%CE%B5%CE%BB%CE%B9%CE%B4%CE%B1%CF%82/';
window.location.href = 'https://docusaurus.io/gr/%CF%83%CE%B5%CE%BB%CE%B9%CE%B4%CE%B1%CF%82/' + window.location.search + window.location.hash;
</script>
</html>"
`;
@ -23,7 +23,7 @@ exports[`createRedirectPageContent works 1`] = `
<link rel="canonical" href="https://docusaurus.io/" />
</head>
<script>
window.location.href = 'https://docusaurus.io/';
window.location.href = 'https://docusaurus.io/' + window.location.search + window.location.hash;
</script>
</html>"
`;

View File

@ -10,7 +10,7 @@ exports[`toRedirectFiles creates appropriate metadata for empty baseUrl: fileCon
<link rel="canonical" href="/abc" />
</head>
<script>
window.location.href = '/abc';
window.location.href = '/abc' + window.location.search + window.location.hash;
</script>
</html>",
]
@ -26,7 +26,7 @@ exports[`toRedirectFiles creates appropriate metadata for root baseUrl: fileCont
<link rel="canonical" href="/abc" />
</head>
<script>
window.location.href = '/abc';
window.location.href = '/abc' + window.location.search + window.location.hash;
</script>
</html>",
]
@ -42,7 +42,7 @@ exports[`toRedirectFiles creates appropriate metadata trailingSlash=false: fileC
<link rel="canonical" href="https://docusaurus.io/abc" />
</head>
<script>
window.location.href = 'https://docusaurus.io/abc';
window.location.href = 'https://docusaurus.io/abc' + window.location.search + window.location.hash;
</script>
</html>",
"<!DOCTYPE html>
@ -53,7 +53,7 @@ exports[`toRedirectFiles creates appropriate metadata trailingSlash=false: fileC
<link rel="canonical" href="https://docusaurus.io/def.html" />
</head>
<script>
window.location.href = 'https://docusaurus.io/def.html';
window.location.href = 'https://docusaurus.io/def.html' + window.location.search + window.location.hash;
</script>
</html>",
"<!DOCTYPE html>
@ -64,7 +64,7 @@ exports[`toRedirectFiles creates appropriate metadata trailingSlash=false: fileC
<link rel="canonical" href="https://docusaurus.io/" />
</head>
<script>
window.location.href = 'https://docusaurus.io/';
window.location.href = 'https://docusaurus.io/' + window.location.search + window.location.hash;
</script>
</html>",
]
@ -80,7 +80,7 @@ exports[`toRedirectFiles creates appropriate metadata trailingSlash=true: fileCo
<link rel="canonical" href="https://docusaurus.io/abc" />
</head>
<script>
window.location.href = 'https://docusaurus.io/abc';
window.location.href = 'https://docusaurus.io/abc' + window.location.search + window.location.hash;
</script>
</html>",
"<!DOCTYPE html>
@ -91,7 +91,7 @@ exports[`toRedirectFiles creates appropriate metadata trailingSlash=true: fileCo
<link rel="canonical" href="https://docusaurus.io/def.html" />
</head>
<script>
window.location.href = 'https://docusaurus.io/def.html';
window.location.href = 'https://docusaurus.io/def.html' + window.location.search + window.location.hash;
</script>
</html>",
"<!DOCTYPE html>
@ -102,7 +102,7 @@ exports[`toRedirectFiles creates appropriate metadata trailingSlash=true: fileCo
<link rel="canonical" href="https://docusaurus.io/" />
</head>
<script>
window.location.href = 'https://docusaurus.io/';
window.location.href = 'https://docusaurus.io/' + window.location.search + window.location.hash;
</script>
</html>",
]
@ -118,7 +118,7 @@ exports[`toRedirectFiles creates appropriate metadata trailingSlash=undefined: f
<link rel="canonical" href="https://docusaurus.io/abc" />
</head>
<script>
window.location.href = 'https://docusaurus.io/abc';
window.location.href = 'https://docusaurus.io/abc' + window.location.search + window.location.hash;
</script>
</html>",
"<!DOCTYPE html>
@ -129,7 +129,7 @@ exports[`toRedirectFiles creates appropriate metadata trailingSlash=undefined: f
<link rel="canonical" href="https://docusaurus.io/def.html" />
</head>
<script>
window.location.href = 'https://docusaurus.io/def.html';
window.location.href = 'https://docusaurus.io/def.html' + window.location.search + window.location.hash;
</script>
</html>",
"<!DOCTYPE html>
@ -140,7 +140,7 @@ exports[`toRedirectFiles creates appropriate metadata trailingSlash=undefined: f
<link rel="canonical" href="https://docusaurus.io/" />
</head>
<script>
window.location.href = 'https://docusaurus.io/';
window.location.href = 'https://docusaurus.io/' + window.location.search + window.location.hash;
</script>
</html>",
]

View File

@ -220,6 +220,69 @@ describe('collectRedirects', () => {
).toThrowErrorMatchingSnapshot();
});
it('tolerates mismatched trailing slash if option is undefined', () => {
expect(
collectRedirects(
createTestPluginContext(
{
redirects: [
{
from: '/someLegacyPath',
to: '/somePath',
},
],
},
['/', '/somePath/'],
{trailingSlash: undefined},
),
undefined,
),
).toEqual([
{
from: '/someLegacyPath',
to: '/somePath',
},
]);
});
it('throw if plugin option redirects contain to paths with mismatching trailing slash', () => {
expect(() =>
collectRedirects(
createTestPluginContext(
{
redirects: [
{
from: '/someLegacyPath',
to: '/someExistingPath/',
},
],
},
['/', '/someExistingPath', '/anotherExistingPath'],
{trailingSlash: false},
),
undefined,
),
).toThrowErrorMatchingSnapshot();
expect(() =>
collectRedirects(
createTestPluginContext(
{
redirects: [
{
from: '/someLegacyPath',
to: '/someExistingPath',
},
],
},
['/', '/someExistingPath/', '/anotherExistingPath/'],
{trailingSlash: true},
),
undefined,
),
).toThrowErrorMatchingSnapshot();
});
it('collects redirects with custom redirect creator', () => {
expect(
collectRedirects(

View File

@ -7,6 +7,7 @@
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {addTrailingSlash, removeTrailingSlash} from '@docusaurus/utils';
import {applyTrailingSlash} from '@docusaurus/utils-common';
import {
createFromExtensionsRedirects,
@ -80,16 +81,59 @@ function validateCollectedRedirects(
const allowedToPaths = pluginContext.relativeRoutesPaths;
const toPaths = redirects.map((redirect) => redirect.to);
const illegalToPaths = _.difference(toPaths, allowedToPaths);
if (illegalToPaths.length > 0) {
throw new Error(
`You are trying to create client-side redirections to paths that do not exist:
- ${illegalToPaths.join('\n- ')}
const trailingSlashConfig = pluginContext.siteConfig.trailingSlash;
// Key is the path, value is whether a valid toPath with a different trailing
// slash exists; if the key doesn't exist it means it's valid
const differByTrailSlash = new Map(toPaths.map((path) => [path, false]));
allowedToPaths.forEach((toPath) => {
if (differByTrailSlash.has(toPath)) {
differByTrailSlash.delete(toPath);
} else if (differByTrailSlash.has(removeTrailingSlash(toPath))) {
if (trailingSlashConfig === true) {
differByTrailSlash.set(removeTrailingSlash(toPath), true);
} else {
differByTrailSlash.delete(removeTrailingSlash(toPath));
}
} else if (differByTrailSlash.has(addTrailingSlash(toPath))) {
if (trailingSlashConfig === false) {
differByTrailSlash.set(addTrailingSlash(toPath), true);
} else {
differByTrailSlash.delete(addTrailingSlash(toPath));
}
}
});
if (differByTrailSlash.size > 0) {
console.log(differByTrailSlash);
const errors = Array.from(differByTrailSlash.entries());
let message =
'You are trying to create client-side redirections to invalid paths.\n';
const [trailingSlashIssues, invalidPaths] = _.partition(
errors,
([, differ]) => differ,
);
if (trailingSlashIssues.length) {
message += `
These paths do exist, but because you have explicitly set trailingSlash=${trailingSlashConfig}, you need to write the path ${
trailingSlashConfig ? 'with trailing slash' : 'without trailing slash'
}:
- ${trailingSlashIssues.map(([p]) => p).join('\n- ')}
`;
}
if (invalidPaths.length) {
message += `
These paths are redirected to but do not exist:
- ${invalidPaths.map(([p]) => p).join('\n- ')}
Valid paths you can redirect to:
- ${allowedToPaths.join('\n- ')}
`,
);
`;
}
throw new Error(message);
}
}

View File

@ -14,7 +14,7 @@ export default `
<link rel="canonical" href="<%= it.toUrl %>" />
</head>
<script>
window.location.href = '<%= it.toUrl %>';
window.location.href = '<%= it.toUrl %>' + window.location.search + window.location.hash;
</script>
</html>
`;

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-blog",
"version": "2.0.0-rc.1",
"version": "2.4.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.0.0-rc.1",
"@docusaurus/logger": "2.0.0-rc.1",
"@docusaurus/mdx-loader": "2.0.0-rc.1",
"@docusaurus/types": "2.0.0-rc.1",
"@docusaurus/utils": "2.0.0-rc.1",
"@docusaurus/utils-common": "2.0.0-rc.1",
"@docusaurus/utils-validation": "2.0.0-rc.1",
"@docusaurus/core": "2.4.0",
"@docusaurus/logger": "2.4.0",
"@docusaurus/mdx-loader": "2.4.0",
"@docusaurus/types": "2.4.0",
"@docusaurus/utils": "2.4.0",
"@docusaurus/utils-common": "2.4.0",
"@docusaurus/utils-validation": "2.4.0",
"cheerio": "^1.0.0-rc.12",
"feed": "^4.2.2",
"fs-extra": "^10.1.0",
@ -35,10 +35,6 @@
"utility-types": "^3.10.0",
"webpack": "^5.73.0"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21",
"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

View File

@ -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();
});
});

View File

@ -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)),
);
}
});

View File

@ -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({

View File

@ -455,6 +455,7 @@ export default async function pluginContentBlog(
(author) => author.imageURL,
),
}),
markdownConfig: siteConfig.markdown,
},
},
{

View File

@ -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),

View File

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

View File

@ -1,10 +1,11 @@
{
"name": "@docusaurus/plugin-content-docs",
"version": "2.0.0-rc.1",
"version": "2.4.0",
"description": "Docs plugin for Docusaurus.",
"main": "lib/index.js",
"sideEffects": false,
"exports": {
"./lib/*": "./lib/*",
"./src/*": "./src/*",
"./client": {
"type": "./lib/client/index.d.ts",
@ -34,13 +35,13 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.0.0-rc.1",
"@docusaurus/logger": "2.0.0-rc.1",
"@docusaurus/mdx-loader": "2.0.0-rc.1",
"@docusaurus/module-type-aliases": "2.0.0-rc.1",
"@docusaurus/types": "2.0.0-rc.1",
"@docusaurus/utils": "2.0.0-rc.1",
"@docusaurus/utils-validation": "2.0.0-rc.1",
"@docusaurus/core": "2.4.0",
"@docusaurus/logger": "2.4.0",
"@docusaurus/mdx-loader": "2.4.0",
"@docusaurus/module-type-aliases": "2.4.0",
"@docusaurus/types": "2.4.0",
"@docusaurus/utils": "2.4.0",
"@docusaurus/utils-validation": "2.4.0",
"@types/react-router-config": "^5.0.6",
"combine-promises": "^1.1.0",
"fs-extra": "^10.1.0",
@ -52,11 +53,9 @@
"webpack": "^5.73.0"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21",
"@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"
},

View File

@ -901,6 +901,7 @@ exports[`simple website content: data 1`] = `
"label": "Next",
"banner": null,
"badge": false,
"noIndex": false,
"className": "docs-version-current",
"isLast": true,
"docsSidebars": {
@ -2608,6 +2609,7 @@ exports[`versioned website (community) content: data 1`] = `
"label": "1.0.0",
"banner": null,
"badge": true,
"noIndex": false,
"className": "docs-version-1.0.0",
"isLast": true,
"docsSidebars": {
@ -2635,6 +2637,7 @@ exports[`versioned website (community) content: data 1`] = `
"label": "Next",
"banner": "unreleased",
"badge": true,
"noIndex": false,
"className": "docs-version-current",
"isLast": false,
"docsSidebars": {
@ -3477,6 +3480,7 @@ exports[`versioned website content: data 1`] = `
"label": "1.0.0",
"banner": "unmaintained",
"badge": true,
"noIndex": false,
"className": "docs-version-1.0.0",
"isLast": false,
"docsSidebars": {
@ -3544,6 +3548,7 @@ exports[`versioned website content: data 1`] = `
"label": "1.0.1",
"banner": null,
"badge": true,
"noIndex": true,
"className": "docs-version-1.0.1",
"isLast": true,
"docsSidebars": {
@ -3599,6 +3604,7 @@ exports[`versioned website content: data 1`] = `
"label": "Next",
"banner": "unreleased",
"badge": true,
"noIndex": false,
"className": "docs-version-current",
"isLast": false,
"docsSidebars": {
@ -3674,6 +3680,7 @@ exports[`versioned website content: data 1`] = `
"label": "withSlugs",
"banner": "unmaintained",
"badge": true,
"noIndex": false,
"className": "docs-version-withSlugs",
"isLast": false,
"docsSidebars": {

View File

@ -93,6 +93,18 @@ function createTestUtils({
});
}
// Makes it easier to assert failure cases
async function getProcessDocFileError(
docFileArg: DocFile | string,
): Promise<Error> {
try {
await processDocFile(docFileArg);
return new Error("unexpected: getProcessDocFileError didn't crash");
} catch (e) {
return e as Error;
}
}
async function testMeta(
docFileSource: string,
expectedMetadata: Optional<
@ -172,7 +184,13 @@ function createTestUtils({
};
}
return {processDocFile, testMeta, testSlug, generateNavigation};
return {
processDocFile,
getProcessDocFileError,
testMeta,
testSlug,
generateNavigation,
};
}
describe('simple site', () => {
@ -683,16 +701,21 @@ describe('simple site', () => {
it('docs with invalid id', async () => {
const {defaultTestUtils} = await loadSite();
await expect(async () =>
defaultTestUtils.processDocFile(
createFakeDocFile({
source: 'some/fake/path',
frontMatter: {
id: 'Hello/world',
},
}),
),
).rejects.toThrowErrorMatchingInlineSnapshot(
const error = await defaultTestUtils.getProcessDocFileError(
createFakeDocFile({
source: 'some/fake/path',
frontMatter: {
id: 'Hello/world',
},
}),
);
expect(error.message).toMatchInlineSnapshot(
`"Can't process doc metadata for doc at path path=some/fake/path in version name=current"`,
);
expect(error.cause).toBeDefined();
expect(error.cause!.message).toMatchInlineSnapshot(
`"Document id "Hello/world" cannot include slash."`,
);
});

View File

@ -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)),
);
}
});

View File

@ -362,6 +362,11 @@ describe('versioned website', () => {
options: {
routeBasePath,
sidebarPath,
versions: {
'1.0.1': {
noIndex: true,
},
},
},
});
const plugin = await pluginContentDocs(context, options);

View File

@ -76,6 +76,7 @@ describe('normalizeDocsPluginOptions', () => {
version1: {
path: 'hello',
label: 'world',
noIndex: true,
},
},
sidebarCollapsible: false,

View File

@ -316,7 +316,7 @@ async function doProcessDocMetadata({
};
}
export function processDocMetadata(args: {
export async function processDocMetadata(args: {
docFile: DocFile;
versionMetadata: VersionMetadata;
context: LoadContext;
@ -324,10 +324,12 @@ export function processDocMetadata(args: {
env: DocEnv;
}): Promise<DocMetadataBase> {
try {
return doProcessDocMetadata(args);
return await doProcessDocMetadata(args);
} catch (err) {
logger.error`Can't process doc metadata for doc at path path=${args.docFile.filePath} in version name=${args.versionMetadata.versionName}`;
throw err;
throw new Error(
`Can't process doc metadata for doc at path path=${args.docFile.filePath} in version name=${args.versionMetadata.versionName}`,
{cause: err as Error},
);
}
}

View File

@ -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}),
{
@ -375,6 +376,7 @@ export default async function pluginContentDocs(
}) => ({
image: frontMatter.image,
}),
markdownConfig: siteConfig.markdown,
},
},
{

View File

@ -59,6 +59,7 @@ const VersionOptionsSchema = Joi.object({
banner: Joi.string().equal('none', 'unreleased', 'unmaintained').optional(),
badge: Joi.boolean().optional(),
className: Joi.string().optional(),
noIndex: Joi.boolean().optional(),
});
const VersionsOptionsSchema = Joi.object()

View File

@ -125,6 +125,25 @@ declare module '@docusaurus/plugin-content-docs' {
// TODO support custom version banner?
// {type: "error", content: "html content"}
export type VersionBanner = 'unreleased' | 'unmaintained';
export type VersionOptions = {
/**
* The base path of the version, will be appended to `baseUrl` +
* `routeBasePath`.
*/
path?: string;
/** The label of the version to be used in badges, dropdowns, etc. */
label?: string;
/** The banner to show at the top of a doc of that version. */
banner?: 'none' | VersionBanner;
/** Show a badge with the version label at the top of each doc. */
badge?: boolean;
/** Prevents search engines from indexing this version */
noIndex?: boolean;
/** Add a custom class name to the <html> element of each doc. */
className?: string;
};
export type VersionsOptions = {
/**
* The version navigated to in priority and displayed by default for docs
@ -144,23 +163,7 @@ declare module '@docusaurus/plugin-content-docs' {
/** Include the current version of your docs. */
includeCurrentVersion: boolean;
/** Independent customization of each version's properties. */
versions: {
[versionName: string]: {
/**
* The base path of the version, will be appended to `baseUrl` +
* `routeBasePath`.
*/
path?: string;
/** The label of the version to be used in badges, dropdowns, etc. */
label?: string;
/** The banner to show at the top of a doc of that version. */
banner?: 'none' | VersionBanner;
/** Show a badge with the version label at the top of each doc. */
badge?: boolean;
/** Add a custom class name to the <html> element of each doc. */
className?: string;
};
};
versions: {[versionName: string]: VersionOptions};
};
export type SidebarOptions = {
/**
@ -263,6 +266,8 @@ declare module '@docusaurus/plugin-content-docs' {
banner: VersionBanner | null;
/** Show a badge with the version label at the top of each doc. */
badge: boolean;
/** Prevents search engines from indexing this version */
noIndex: boolean;
/** Add a custom class name to the <html> element of each doc. */
className: string;
/**
@ -500,7 +505,7 @@ declare module '@docusaurus/plugin-content-docs' {
export type PropVersionMetadata = Pick<
VersionMetadata,
'label' | 'banner' | 'badge' | 'className' | 'isLast'
'label' | 'banner' | 'badge' | 'className' | 'isLast' | 'noIndex'
> & {
/** ID of the docs plugin this version belongs to. */
pluginId: string;

View File

@ -142,6 +142,7 @@ export function toVersionMetadataProp(
label: loadedVersion.label,
banner: loadedVersion.banner,
badge: loadedVersion.badge,
noIndex: loadedVersion.noIndex,
className: loadedVersion.className,
isLast: loadedVersion.isLast,
docsSidebars: toSidebarsProp(loadedVersion),

View File

@ -44,6 +44,8 @@ export type SidebarItemLink = SidebarItemBase & {
type: 'link';
href: string;
label: string;
autoAddBaseUrl?: boolean;
description?: string;
};
export type SidebarItemAutogenerated = SidebarItemBase & {
@ -56,6 +58,7 @@ type SidebarItemCategoryBase = SidebarItemBase & {
label: string;
collapsed: boolean;
collapsible: boolean;
description?: string;
};
export type SidebarItemCategoryLinkDoc = {type: 'doc'; id: string};

View File

@ -59,9 +59,13 @@ const sidebarItemHtmlSchema = sidebarItemBaseSchema.append<SidebarItemHtml>({
const sidebarItemLinkSchema = sidebarItemBaseSchema.append<SidebarItemLink>({
type: 'link',
href: URISchema.required(),
autoAddBaseUrl: Joi.boolean(),
label: Joi.string()
.required()
.messages({'any.unknown': '"label" must be a string'}),
description: Joi.string().optional().messages({
'any.unknown': '"description" must be a string',
}),
});
const sidebarItemCategoryLinkSchema = Joi.object<SidebarItemCategoryLink>()
@ -115,6 +119,9 @@ const sidebarItemCategorySchema =
collapsible: Joi.boolean().messages({
'any.unknown': '"collapsible" must be a boolean',
}),
description: Joi.string().optional().messages({
'any.unknown': '"description" must be a string',
}),
});
const sidebarItemSchema = Joi.object<SidebarItemConfig>().when('.type', {

View File

@ -56,6 +56,7 @@ describe('readVersionsMetadata', () => {
path: '/docs',
banner: null,
badge: false,
noIndex: false,
className: 'docs-version-current',
};
return {simpleSiteDir, defaultOptions, defaultContext, vCurrent};
@ -218,6 +219,7 @@ describe('readVersionsMetadata', () => {
path: '/docs/next',
banner: 'unreleased',
badge: true,
noIndex: false,
className: 'docs-version-current',
};
@ -242,6 +244,7 @@ describe('readVersionsMetadata', () => {
path: '/docs',
banner: null,
badge: true,
noIndex: false,
className: 'docs-version-1.0.1',
};
@ -266,6 +269,7 @@ describe('readVersionsMetadata', () => {
path: '/docs/1.0.0',
banner: 'unmaintained',
badge: true,
noIndex: false,
className: 'docs-version-1.0.0',
};
@ -290,6 +294,7 @@ describe('readVersionsMetadata', () => {
path: '/docs/withSlugs',
banner: 'unmaintained',
badge: true,
noIndex: false,
className: 'docs-version-withSlugs',
};
@ -657,6 +662,7 @@ describe('readVersionsMetadata', () => {
path: '/communityBasePath/next',
banner: 'unreleased',
badge: true,
noIndex: false,
className: 'docs-version-current',
};
@ -681,6 +687,7 @@ describe('readVersionsMetadata', () => {
path: '/communityBasePath',
banner: null,
badge: true,
noIndex: false,
className: 'docs-version-1.0.0',
};

View File

@ -122,6 +122,13 @@ export function getVersionBadge({
return options.versions[versionName]?.badge ?? defaultVersionBadge;
}
export function getVersionNoIndex({
versionName,
options,
}: VersionContext): VersionMetadata['noIndex'] {
return options.versions[versionName]?.noIndex ?? false;
}
function getVersionClassName({
versionName,
options,
@ -179,6 +186,7 @@ async function createVersionMetadata(
label: getVersionLabel(context),
banner: getVersionBanner(context),
badge: getVersionBadge(context),
noIndex: getVersionNoIndex(context),
className: getVersionClassName(context),
path: routePath,
tagsPath: normalizeUrl([routePath, options.tagsBasePath]),

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-pages",
"version": "2.0.0-rc.1",
"version": "2.4.0",
"description": "Pages plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-content-pages.d.ts",
@ -18,18 +18,15 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.0.0-rc.1",
"@docusaurus/mdx-loader": "2.0.0-rc.1",
"@docusaurus/types": "2.0.0-rc.1",
"@docusaurus/utils": "2.0.0-rc.1",
"@docusaurus/utils-validation": "2.0.0-rc.1",
"@docusaurus/core": "2.4.0",
"@docusaurus/mdx-loader": "2.4.0",
"@docusaurus/types": "2.4.0",
"@docusaurus/utils": "2.4.0",
"@docusaurus/utils-validation": "2.4.0",
"fs-extra": "^10.1.0",
"tslib": "^2.4.0",
"webpack": "^5.73.0"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": {
"react": "^16.8.4 || ^17.0.0",
"react-dom": "^16.8.4 || ^17.0.0"

View File

@ -211,6 +211,7 @@ export default function pluginContentPages(
`${docuHash(aliasedSource)}.json`,
);
},
markdownConfig: siteConfig.markdown,
},
},
{

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-debug",
"version": "2.0.0-rc.1",
"version": "2.4.0",
"description": "Debug plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-debug.d.ts",
@ -20,16 +20,13 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.0.0-rc.1",
"@docusaurus/types": "2.0.0-rc.1",
"@docusaurus/utils": "2.0.0-rc.1",
"@docusaurus/core": "2.4.0",
"@docusaurus/types": "2.4.0",
"@docusaurus/utils": "2.4.0",
"fs-extra": "^10.1.0",
"react-json-view": "^1.21.3",
"tslib": "^2.4.0"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": {
"react": "^16.8.4 || ^17.0.0",
"react-dom": "^16.8.4 || ^17.0.0"

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-google-analytics",
"version": "2.0.0-rc.1",
"version": "2.4.0",
"description": "Global analytics (analytics.js) plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,14 +18,11 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.0.0-rc.1",
"@docusaurus/types": "2.0.0-rc.1",
"@docusaurus/utils-validation": "2.0.0-rc.1",
"@docusaurus/core": "2.4.0",
"@docusaurus/types": "2.4.0",
"@docusaurus/utils-validation": "2.4.0",
"tslib": "^2.4.0"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": {
"react": "^16.8.4 || ^17.0.0",
"react-dom": "^16.8.4 || ^17.0.0"

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-google-gtag",
"version": "2.0.0-rc.1",
"version": "2.4.0",
"description": "Global Site Tag (gtag.js) plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,14 +18,11 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.0.0-rc.1",
"@docusaurus/types": "2.0.0-rc.1",
"@docusaurus/utils-validation": "2.0.0-rc.1",
"@docusaurus/core": "2.4.0",
"@docusaurus/types": "2.4.0",
"@docusaurus/utils-validation": "2.4.0",
"tslib": "^2.4.0"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": {
"react": "^16.8.4 || ^17.0.0",
"react-dom": "^16.8.4 || ^17.0.0"

View File

@ -0,0 +1,156 @@
/**
* 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 {normalizePluginOptions} from '@docusaurus/utils-validation';
import {
validateOptions,
type PluginOptions,
type Options,
DEFAULT_OPTIONS,
} from '../options';
import type {Validate} from '@docusaurus/types';
function testValidateOptions(options: Options) {
return validateOptions({
validate: normalizePluginOptions as Validate<Options, PluginOptions>,
options,
});
}
function validationResult(options: Options) {
return {
id: 'default',
...DEFAULT_OPTIONS,
...options,
trackingID:
typeof options.trackingID === 'string'
? [options.trackingID]
: options.trackingID,
};
}
const MinimalConfig: Options = {
trackingID: 'G-XYZ12345',
};
describe('validateOptions', () => {
it('throws for undefined options', () => {
expect(
// @ts-expect-error: TS should error
() => testValidateOptions(undefined),
).toThrowErrorMatchingInlineSnapshot(`""trackingID" is required"`);
});
it('throws for null options', () => {
expect(
// @ts-expect-error: TS should error
() => testValidateOptions(null),
).toThrowErrorMatchingInlineSnapshot(`""value" must be of type object"`);
});
it('throws for empty object options', () => {
expect(
// @ts-expect-error: TS should error
() => testValidateOptions({}),
).toThrowErrorMatchingInlineSnapshot(`""trackingID" is required"`);
});
it('throws for number options', () => {
expect(
// @ts-expect-error: TS should error
() => testValidateOptions(42),
).toThrowErrorMatchingInlineSnapshot(`""value" must be of type object"`);
});
it('throws for null trackingID', () => {
expect(
// @ts-expect-error: TS should error
() => testValidateOptions({trackingID: null}),
).toThrowErrorMatchingInlineSnapshot(
`""trackingID" does not match any of the allowed types"`,
);
});
it('throws for number trackingID', () => {
expect(
// @ts-expect-error: TS should error
() => testValidateOptions({trackingID: 42}),
).toThrowErrorMatchingInlineSnapshot(
`""trackingID" does not match any of the allowed types"`,
);
});
it('throws for empty trackingID', () => {
expect(() =>
testValidateOptions({trackingID: ''}),
).toThrowErrorMatchingInlineSnapshot(
`""trackingID" does not match any of the allowed types"`,
);
});
it('accepts minimal config', () => {
expect(testValidateOptions(MinimalConfig)).toEqual(
validationResult(MinimalConfig),
);
});
it('accepts anonymizeIP', () => {
const config: Options = {
...MinimalConfig,
anonymizeIP: true,
};
expect(testValidateOptions(config)).toEqual(validationResult(config));
});
it('accepts single trackingID', () => {
const config: Options = {
trackingID: 'G-ABCDEF123',
};
expect(testValidateOptions(config)).toEqual(validationResult(config));
});
it('accepts multiple trackingIDs', () => {
const config: Options = {
trackingID: ['G-ABCDEF123', 'UA-XYZ456789'],
};
expect(testValidateOptions(config)).toEqual(validationResult(config));
});
it('throws for empty trackingID arrays', () => {
const config: Options = {
// @ts-expect-error: TS should error
trackingID: [],
};
expect(() =>
testValidateOptions(config),
).toThrowErrorMatchingInlineSnapshot(
`""trackingID" does not match any of the allowed types"`,
);
});
it('throws for sparse trackingID arrays', () => {
const config: Options = {
// @ts-expect-error: TS should error
trackingID: ['G-ABCDEF123', null, 'UA-XYZ456789'],
};
expect(() =>
testValidateOptions(config),
).toThrowErrorMatchingInlineSnapshot(
`""trackingID" does not match any of the allowed types"`,
);
});
it('throws for bad trackingID arrays', () => {
const config: Options = {
// @ts-expect-error: TS should error
trackingID: ['G-ABCDEF123', 42],
};
expect(() =>
testValidateOptions(config),
).toThrowErrorMatchingInlineSnapshot(
`""trackingID" does not match any of the allowed types"`,
);
});
});

View File

@ -5,23 +5,38 @@
* LICENSE file in the root directory of this source tree.
*/
import {Joi} from '@docusaurus/utils-validation';
import type {
LoadContext,
Plugin,
OptionValidationContext,
ThemeConfig,
ThemeConfigValidationContext,
} from '@docusaurus/types';
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {PluginOptions, Options} from './options';
function createConfigSnippet({
trackingID,
anonymizeIP,
}: {
trackingID: string;
anonymizeIP: boolean;
}): string {
return `gtag('config', '${trackingID}', { ${
anonymizeIP ? "'anonymize_ip': true" : ''
} });`;
}
function createConfigSnippets({
trackingID: trackingIDArray,
anonymizeIP,
}: PluginOptions): string {
return trackingIDArray
.map((trackingID) => createConfigSnippet({trackingID, anonymizeIP}))
.join('\n');
}
export default function pluginGoogleGtag(
context: LoadContext,
options: PluginOptions,
): Plugin {
const {anonymizeIP, trackingID} = options;
const isProd = process.env.NODE_ENV === 'production';
const firstTrackingId = options.trackingID[0];
return {
name: 'docusaurus-plugin-google-gtag',
@ -60,7 +75,11 @@ export default function pluginGoogleGtag(
tagName: 'script',
attributes: {
async: true,
src: `https://www.googletagmanager.com/gtag/js?id=${trackingID}`,
// We only include the first tracking id here because google says
// we shouldn't install multiple tags/scripts on the same page
// Instead we should load one script and use n * gtag("config",id)
// See https://developers.google.com/tag-platform/gtagjs/install#add-products
src: `https://www.googletagmanager.com/gtag/js?id=${firstTrackingId}`,
},
},
{
@ -69,9 +88,8 @@ export default function pluginGoogleGtag(
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${trackingID}', { ${
anonymizeIP ? "'anonymize_ip': true" : ''
} });`,
${createConfigSnippets(options)};
`,
},
],
};
@ -79,27 +97,6 @@ export default function pluginGoogleGtag(
};
}
const pluginOptionsSchema = Joi.object<PluginOptions>({
trackingID: Joi.string().required(),
anonymizeIP: Joi.boolean().default(false),
});
export function validateOptions({
validate,
options,
}: OptionValidationContext<Options, PluginOptions>): PluginOptions {
return validate(pluginOptionsSchema, options);
}
export function validateThemeConfig({
themeConfig,
}: ThemeConfigValidationContext<ThemeConfig>): ThemeConfig {
if ('gtag' in themeConfig) {
throw new Error(
'The "gtag" field in themeConfig should now be specified as option for plugin-google-gtag. More information at https://github.com/facebook/docusaurus/pull/5832.',
);
}
return themeConfig;
}
export {validateThemeConfig, validateOptions} from './options';
export type {PluginOptions, Options};

View File

@ -4,10 +4,58 @@
* 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 {
OptionValidationContext,
ThemeConfig,
ThemeConfigValidationContext,
} from '@docusaurus/types';
export type PluginOptions = {
trackingID: string;
trackingID: [string, ...string[]];
// TODO deprecate anonymizeIP after June 2023
// "In Google Analytics 4, IP masking is not necessary
// since IP addresses are not logged or stored."
// https://support.google.com/analytics/answer/2763052?hl=en
anonymizeIP: boolean;
};
export type Options = Partial<PluginOptions>;
export type Options = {
trackingID: string | [string, ...string[]];
anonymizeIP?: boolean;
};
export const DEFAULT_OPTIONS: Partial<PluginOptions> = {
anonymizeIP: false,
};
const pluginOptionsSchema = Joi.object<PluginOptions>({
// We normalize trackingID as a string[]
trackingID: Joi.alternatives()
.try(
Joi.alternatives().conditional(Joi.string().required(), {
then: Joi.custom((val: boolean) => [val]),
}),
Joi.array().items(Joi.string().required()),
)
.required(),
anonymizeIP: Joi.boolean().default(DEFAULT_OPTIONS.anonymizeIP),
});
export function validateOptions({
validate,
options,
}: OptionValidationContext<Options, PluginOptions>): PluginOptions {
return validate(pluginOptionsSchema, options);
}
export function validateThemeConfig({
themeConfig,
}: ThemeConfigValidationContext<ThemeConfig>): ThemeConfig {
if ('gtag' in themeConfig) {
throw new Error(
'The "gtag" field in themeConfig should now be specified as option for plugin-google-gtag. More information at https://github.com/facebook/docusaurus/pull/5832.',
);
}
return themeConfig;
}

View File

@ -10,6 +10,6 @@
"rootDir": "src",
"outDir": "lib"
},
"include": ["src/gtag.ts", "src/options.ts", "src/*.d.ts"],
"include": ["src/gtag.ts", "src/*.d.ts"],
"exclude": ["**/__tests__/**"]
}

View File

@ -0,0 +1,3 @@
.tsbuildinfo*
tsconfig*
__tests__

View 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).

View File

@ -0,0 +1,33 @@
{
"name": "@docusaurus/plugin-google-tag-manager",
"version": "2.4.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.4.0",
"@docusaurus/types": "2.4.0",
"@docusaurus/utils-validation": "2.4.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"
}
}

View 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};

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

View 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" />

View File

@ -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__/**"]
}

View 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__/**"]
}

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-ideal-image",
"version": "2.0.0-rc.1",
"version": "2.4.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.0.0-rc.1",
"@docusaurus/lqip-loader": "2.0.0-rc.1",
"@docusaurus/core": "2.4.0",
"@docusaurus/lqip-loader": "2.4.0",
"@docusaurus/responsive-loader": "^1.7.0",
"@docusaurus/theme-translations": "2.0.0-rc.1",
"@docusaurus/types": "2.0.0-rc.1",
"@docusaurus/utils-validation": "2.0.0-rc.1",
"@docusaurus/theme-translations": "2.4.0",
"@docusaurus/types": "2.4.0",
"@docusaurus/utils-validation": "2.4.0",
"@endiliey/react-ideal-image": "^0.0.11",
"react-waypoint": "^10.3.0",
"sharp": "^0.30.7",
@ -33,8 +33,7 @@
"webpack": "^5.73.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-rc.1",
"@docusaurus/types": "2.0.0-beta.21",
"@docusaurus/module-type-aliases": "2.4.0",
"fs-extra": "^10.1.0"
},
"peerDependencies": {

View File

@ -81,25 +81,19 @@ function getMessage(icon: IconKey, state: State) {
}
export default function IdealImage(props: Props): JSX.Element {
const {alt, className, img} = props;
const {img, ...propsRest} = props;
// In dev env just use regular img with original file
if (typeof img === 'string' || 'default' in img) {
return (
<img
src={typeof img === 'string' ? img : img.default}
className={className}
alt={alt}
{...props}
/>
// eslint-disable-next-line jsx-a11y/alt-text
<img src={typeof img === 'string' ? img : img.default} {...propsRest} />
);
}
return (
<ReactIdealImage
{...props}
alt={alt}
className={className}
{...propsRest}
height={img.src.height ?? 100}
width={img.src.width ?? 100}
placeholder={{lqip: img.preSrc}}

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-pwa",
"version": "2.0.0-rc.1",
"version": "2.4.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.0.0-rc.1",
"@docusaurus/theme-common": "2.0.0-rc.1",
"@docusaurus/theme-translations": "2.0.0-rc.1",
"@docusaurus/types": "2.0.0-rc.1",
"@docusaurus/utils": "2.0.0-rc.1",
"@docusaurus/utils-validation": "2.0.0-rc.1",
"@docusaurus/core": "2.4.0",
"@docusaurus/theme-common": "2.4.0",
"@docusaurus/theme-translations": "2.4.0",
"@docusaurus/types": "2.4.0",
"@docusaurus/utils": "2.4.0",
"@docusaurus/utils-validation": "2.4.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.0.0-rc.1",
"@docusaurus/module-type-aliases": "2.4.0",
"fs-extra": "^10.1.0"
},
"peerDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-sitemap",
"version": "2.0.0-rc.1",
"version": "2.4.0",
"description": "Simple sitemap generation plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,19 +18,16 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.0.0-rc.1",
"@docusaurus/logger": "2.0.0-rc.1",
"@docusaurus/types": "2.0.0-rc.1",
"@docusaurus/utils": "2.0.0-rc.1",
"@docusaurus/utils-common": "2.0.0-rc.1",
"@docusaurus/utils-validation": "2.0.0-rc.1",
"@docusaurus/core": "2.4.0",
"@docusaurus/logger": "2.4.0",
"@docusaurus/types": "2.4.0",
"@docusaurus/utils": "2.4.0",
"@docusaurus/utils-common": "2.4.0",
"@docusaurus/utils-validation": "2.4.0",
"fs-extra": "^10.1.0",
"sitemap": "^7.1.1",
"tslib": "^2.4.0"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": {
"react": "^16.8.4 || ^17.0.0",
"react-dom": "^16.8.4 || ^17.0.0"

View File

@ -158,7 +158,10 @@ describe('createSitemap', () => {
meta: {
// @ts-expect-error: bad lib def
toComponent: () => [
React.createElement('meta', {name: 'robots', content: 'noindex'}),
React.createElement('meta', {
name: 'robots',
content: 'NoFolloW, NoiNDeX',
}),
],
},
},

View File

@ -13,6 +13,40 @@ import type {DocusaurusConfig} from '@docusaurus/types';
import type {HelmetServerState} from 'react-helmet-async';
import type {PluginOptions} from './options';
function isNoIndexMetaRoute({
head,
route,
}: {
head: {[location: string]: HelmetServerState};
route: string;
}) {
const isNoIndexMetaTag = ({
name,
content,
}: {
name?: string;
content?: string;
}): boolean => {
if (!name || !content) {
return false;
}
return (
// meta name is not case-sensitive
name.toLowerCase() === 'robots' &&
// Robots directives are not case-sensitive
content.toLowerCase().includes('noindex')
);
};
// https://github.com/staylor/react-helmet-async/pull/167
const meta = head[route]?.meta.toComponent() as unknown as
| ReactElement<{name?: string; content?: string}>[]
| undefined;
return meta?.some((tag) =>
isNoIndexMetaTag({name: tag.props.name, content: tag.props.content}),
);
}
export default async function createSitemap(
siteConfig: DocusaurusConfig,
routesPaths: string[],
@ -27,18 +61,15 @@ export default async function createSitemap(
const ignoreMatcher = createMatcher(ignorePatterns);
const includedRoutes = routesPaths.filter((route) => {
if (route.endsWith('404.html') || ignoreMatcher(route)) {
return false;
}
// https://github.com/staylor/react-helmet-async/pull/167
const meta = head[route]?.meta.toComponent() as unknown as
| ReactElement<{name?: string; content?: string}>[]
| undefined;
return !meta?.some(
(tag) => tag.props.name === 'robots' && tag.props.content === 'noindex',
function isRouteExcluded(route: string) {
return (
route.endsWith('404.html') ||
ignoreMatcher(route) ||
isNoIndexMetaRoute({head, route})
);
});
}
const includedRoutes = routesPaths.filter((route) => !isRouteExcluded(route));
if (includedRoutes.length === 0) {
return null;

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/preset-classic",
"version": "2.0.0-rc.1",
"version": "2.4.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.0.0-rc.1",
"@docusaurus/plugin-content-blog": "2.0.0-rc.1",
"@docusaurus/plugin-content-docs": "2.0.0-rc.1",
"@docusaurus/plugin-content-pages": "2.0.0-rc.1",
"@docusaurus/plugin-debug": "2.0.0-rc.1",
"@docusaurus/plugin-google-analytics": "2.0.0-rc.1",
"@docusaurus/plugin-google-gtag": "2.0.0-rc.1",
"@docusaurus/plugin-sitemap": "2.0.0-rc.1",
"@docusaurus/theme-classic": "2.0.0-rc.1",
"@docusaurus/theme-common": "2.0.0-rc.1",
"@docusaurus/theme-search-algolia": "2.0.0-rc.1",
"@docusaurus/types": "2.0.0-rc.1"
"@docusaurus/core": "2.4.0",
"@docusaurus/plugin-content-blog": "2.4.0",
"@docusaurus/plugin-content-docs": "2.4.0",
"@docusaurus/plugin-content-pages": "2.4.0",
"@docusaurus/plugin-debug": "2.4.0",
"@docusaurus/plugin-google-analytics": "2.4.0",
"@docusaurus/plugin-google-gtag": "2.4.0",
"@docusaurus/plugin-google-tag-manager": "2.4.0",
"@docusaurus/plugin-sitemap": "2.4.0",
"@docusaurus/theme-classic": "2.4.0",
"@docusaurus/theme-common": "2.4.0",
"@docusaurus/theme-search-algolia": "2.4.0",
"@docusaurus/types": "2.4.0"
},
"peerDependencies": {
"react": "^16.8.4 || ^17.0.0",

View File

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

View File

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

View File

@ -63,3 +63,30 @@ module.exports = {
| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `sync` | `boolean` | `false` | Syncing tab choices (Yarn and npm). See https://docusaurus.io/docs/markdown-features/#syncing-tab-choices for details. |
| `converters` | `array` | `'yarn'`, `'pnpm'` | The list of converters to use. The order of the converters is important, as the first converter will be used as the default choice. |
## Custom converters
In case you want to convert npm commands to something else than `yarn` or `pnpm`, you can use custom converters:
```ts
type CustomConverter = [name: string, cb: (npmCode: string) => string];
```
```ts
{
remarkPlugins: [
[
require('@docusaurus/remark-plugin-npm2yarn'),
{
sync: true,
converters: [
'yarn',
'pnpm',
['Turbo', (code) => code.replace(/npm/g, 'turbo')],
],
},
],
];
}
```

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/remark-plugin-npm2yarn",
"version": "2.0.0-rc.1",
"version": "2.4.0",
"description": "Remark plugin for converting npm commands to Yarn commands as tabs.",
"main": "lib/index.js",
"publishConfig": {
@ -17,8 +17,8 @@
},
"license": "MIT",
"dependencies": {
"npm-to-yarn": "^1.0.1",
"tslib": "^2.4.0",
"npm-to-yarn": "^2.0.0",
"tslib": "^2.4.1",
"unist-util-visit": "^2.0.3"
},
"devDependencies": {

View File

@ -0,0 +1,16 @@
```bash npm2yarn
npm run xxx -- --arg
```
```bash npm2yarn
npm install package
```
```bash npm2yarn
npm remove package-name
```
```bash npm2yarn
npm init docusaurus
npm init docusaurus@latest my-website classic
```

View File

@ -6,6 +6,7 @@ exports[`npm2yarn plugin does not re-import tabs components when already importe
import TabItem from '@theme/TabItem';
<Tabs>
<TabItem value="npm">
\`\`\`bash
@ -13,19 +14,30 @@ import TabItem from '@theme/TabItem';
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
$ yarn add --global docusaurus
$ yarn global add docusaurus
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
$ pnpm add --global docusaurus
\`\`\`
</TabItem>
</Tabs>
"
`;
exports[`npm2yarn plugin does not re-import tabs components when already imported below 1`] = `
"<Tabs>
<TabItem value="npm">
\`\`\`bash
@ -33,13 +45,23 @@ exports[`npm2yarn plugin does not re-import tabs components when already importe
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
$ yarn add --global docusaurus
$ yarn global add docusaurus
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
$ pnpm add --global docusaurus
\`\`\`
</TabItem>
</Tabs>
import Tabs from '@theme/Tabs';
@ -63,11 +85,102 @@ npm install --save docusaurus-plugin-name
"
`;
exports[`npm2yarn plugin work with custom converter 1`] = `
"import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Installing a plugin
A plugin is usually a npm package, so you install them like other npm packages using npm.
<Tabs>
<TabItem value="npm">
\`\`\`bash
npm install --save docusaurus-plugin-name
\`\`\`
</TabItem>
<TabItem value="Turbo">
\`\`\`bash
turbo install --save docusaurus-plugin-name
\`\`\`
</TabItem>
</Tabs>
"
`;
exports[`npm2yarn plugin work with pnpm converter 1`] = `
"import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Installing a plugin
A plugin is usually a npm package, so you install them like other npm packages using npm.
<Tabs>
<TabItem value="npm">
\`\`\`bash
npm install --save docusaurus-plugin-name
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm add docusaurus-plugin-name
\`\`\`
</TabItem>
</Tabs>
"
`;
exports[`npm2yarn plugin work with yarn converter 1`] = `
"import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Installing a plugin
A plugin is usually a npm package, so you install them like other npm packages using npm.
<Tabs>
<TabItem value="npm">
\`\`\`bash
npm install --save docusaurus-plugin-name
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
yarn add docusaurus-plugin-name
\`\`\`
</TabItem>
</Tabs>
"
`;
exports[`npm2yarn plugin works on installation file 1`] = `
"import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs>
<TabItem value="npm">
\`\`\`bash
@ -75,13 +188,23 @@ import TabItem from '@theme/TabItem';
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
$ yarn add --global docusaurus
$ yarn global add docusaurus
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
$ pnpm add --global docusaurus
\`\`\`
</TabItem>
</Tabs>
"
`;
@ -95,6 +218,7 @@ import TabItem from '@theme/TabItem';
A plugin is usually a npm package, so you install them like other npm packages using npm.
<Tabs>
<TabItem value="npm">
\`\`\`bash
@ -102,6 +226,7 @@ npm install --save docusaurus-plugin-name
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
@ -109,6 +234,136 @@ yarn add docusaurus-plugin-name
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm add docusaurus-plugin-name
\`\`\`
</TabItem>
</Tabs>
"
`;
exports[`npm2yarn plugin works with common commands 1`] = `
"import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs groupId="npm2yarn">
<TabItem value="npm">
\`\`\`bash
npm run xxx -- --arg
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
yarn xxx --arg
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm run xxx -- --arg
\`\`\`
</TabItem>
</Tabs>
<Tabs groupId="npm2yarn">
<TabItem value="npm">
\`\`\`bash
npm install package
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
yarn add package
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm add package
\`\`\`
</TabItem>
</Tabs>
<Tabs groupId="npm2yarn">
<TabItem value="npm">
\`\`\`bash
npm remove package-name
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
yarn remove package-name
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm remove package-name
\`\`\`
</TabItem>
</Tabs>
<Tabs groupId="npm2yarn">
<TabItem value="npm">
\`\`\`bash
npm init docusaurus
npm init docusaurus@latest my-website classic
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
yarn create docusaurus
yarn create docusaurus@latest my-website classic
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm create docusaurus
pnpm create docusaurus@latest my-website classic
\`\`\`
</TabItem>
</Tabs>
"
`;
@ -122,6 +377,7 @@ import TabItem from '@theme/TabItem';
A plugin is usually a npm package, so you install them like other npm packages using npm.
<Tabs groupId="npm2yarn">
<TabItem value="npm">
\`\`\`bash
@ -129,6 +385,7 @@ npm install --save docusaurus-plugin-name
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
@ -136,6 +393,15 @@ yarn add docusaurus-plugin-name
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm add docusaurus-plugin-name
\`\`\`
</TabItem>
</Tabs>
"
`;

View File

@ -11,7 +11,10 @@ import mdx from 'remark-mdx';
import remark from 'remark';
import npm2yarn from '../index';
const processFixture = async (name: string, options?: {sync?: boolean}) => {
const processFixture = async (
name: string,
options?: Parameters<typeof npm2yarn>[0],
) => {
const filePath = path.join(__dirname, '__fixtures__', `${name}.md`);
const file = await vfile.read(filePath);
const result = await remark().use(mdx).use(npm2yarn, options).process(file);
@ -32,6 +35,12 @@ describe('npm2yarn plugin', () => {
expect(result).toMatchSnapshot();
});
it('works with common commands', async () => {
const result = await processFixture('conversion-test', {sync: true});
expect(result).toMatchSnapshot();
});
it('works with sync option', async () => {
const result = await processFixture('plugin', {sync: true});
@ -55,4 +64,24 @@ describe('npm2yarn plugin', () => {
expect(result).toMatchSnapshot();
});
it('work with yarn converter', async () => {
const result = await processFixture('plugin', {converters: ['yarn']});
expect(result).toMatchSnapshot();
});
it('work with pnpm converter', async () => {
const result = await processFixture('plugin', {converters: ['pnpm']});
expect(result).toMatchSnapshot();
});
it('work with custom converter', async () => {
const result = await processFixture('plugin', {
converters: [['Turbo', (code) => code.replace(/npm/g, 'turbo')]],
});
expect(result).toMatchSnapshot();
});
});

View File

@ -11,39 +11,62 @@ import type {Code, Content, Literal} from 'mdast';
import type {Plugin} from 'unified';
import type {Node, Parent} from 'unist';
type CustomConverter = [name: string, cb: (npmCode: string) => string];
type PluginOptions = {
sync?: boolean;
converters?: (CustomConverter | 'yarn' | 'pnpm')[];
};
// E.g. global install: 'npm i' -> 'yarn'
const convertNpmToYarn = (npmCode: string) => npmToYarn(npmCode, 'yarn');
const transformNode = (node: Code, isSync: boolean) => {
const groupIdProp = isSync ? ' groupId="npm2yarn"' : '';
const npmCode = node.value;
const yarnCode = convertNpmToYarn(node.value);
function createTabItem(
code: string,
node: Code,
value: string,
label?: string,
) {
return [
{
type: 'jsx',
value: `<Tabs${groupIdProp}>\n<TabItem value="npm">`,
value: `<TabItem value="${value}"${label ? ` label="${label}"` : ''}>`,
},
{
type: node.type,
lang: node.lang,
value: npmCode,
value: code,
},
{
type: 'jsx',
value: '</TabItem>\n<TabItem value="yarn" label="Yarn">',
},
{
type: node.type,
lang: node.lang,
value: yarnCode,
value: '</TabItem>',
},
] as Content[];
}
const transformNode = (
node: Code,
isSync: boolean,
converters: (CustomConverter | 'yarn' | 'pnpm')[],
) => {
const groupIdProp = isSync ? ' groupId="npm2yarn"' : '';
const npmCode = node.value;
return [
{
type: 'jsx',
value: '</TabItem>\n</Tabs>',
value: `<Tabs${groupIdProp}>`,
},
...createTabItem(npmCode, node, 'npm'),
...converters.flatMap((converter) =>
typeof converter === 'string'
? createTabItem(
npmToYarn(npmCode, converter),
node,
converter,
converter === 'yarn' ? 'Yarn' : converter,
)
: createTabItem(converter[1](npmCode), node, converter[0]),
),
{
type: 'jsx',
value: '</Tabs>',
},
] as Content[];
};
@ -60,7 +83,7 @@ const nodeForImport: Literal = {
};
const plugin: Plugin<[PluginOptions?]> = (options = {}) => {
const {sync = false} = options;
const {sync = false, converters = ['yarn', 'pnpm']} = options;
return (root) => {
let transformed = false as boolean;
let alreadyImported = false as boolean;
@ -73,7 +96,7 @@ const plugin: Plugin<[PluginOptions?]> = (options = {}) => {
while (index < node.children.length) {
const child = node.children[index]!;
if (matchNode(child)) {
const result = transformNode(child, sync);
const result = transformNode(child, sync, converters);
node.children.splice(index, 1, ...result);
index += result.length;
transformed = true;

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-classic",
"version": "2.0.0-rc.1",
"version": "2.4.0",
"description": "Classic theme for Docusaurus",
"main": "lib/index.js",
"types": "src/theme-classic.d.ts",
@ -20,22 +20,22 @@
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
},
"dependencies": {
"@docusaurus/core": "2.0.0-rc.1",
"@docusaurus/mdx-loader": "2.0.0-rc.1",
"@docusaurus/module-type-aliases": "2.0.0-rc.1",
"@docusaurus/plugin-content-blog": "2.0.0-rc.1",
"@docusaurus/plugin-content-docs": "2.0.0-rc.1",
"@docusaurus/plugin-content-pages": "2.0.0-rc.1",
"@docusaurus/theme-common": "2.0.0-rc.1",
"@docusaurus/theme-translations": "2.0.0-rc.1",
"@docusaurus/types": "2.0.0-rc.1",
"@docusaurus/utils": "2.0.0-rc.1",
"@docusaurus/utils-common": "2.0.0-rc.1",
"@docusaurus/utils-validation": "2.0.0-rc.1",
"@docusaurus/core": "2.4.0",
"@docusaurus/mdx-loader": "2.4.0",
"@docusaurus/module-type-aliases": "2.4.0",
"@docusaurus/plugin-content-blog": "2.4.0",
"@docusaurus/plugin-content-docs": "2.4.0",
"@docusaurus/plugin-content-pages": "2.4.0",
"@docusaurus/theme-common": "2.4.0",
"@docusaurus/theme-translations": "2.4.0",
"@docusaurus/types": "2.4.0",
"@docusaurus/utils": "2.4.0",
"@docusaurus/utils-common": "2.4.0",
"@docusaurus/utils-validation": "2.4.0",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"copy-text-to-clipboard": "^3.0.1",
"infima": "0.2.0-alpha.42",
"infima": "0.2.0-alpha.43",
"lodash": "^4.17.21",
"nprogress": "^0.2.0",
"postcss": "^8.4.14",
@ -47,8 +47,6 @@
"utility-types": "^3.10.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-beta.21",
"@docusaurus/types": "2.0.0-beta.21",
"@types/mdx-js__react": "^1.5.5",
"@types/nprogress": "^0.2.0",
"@types/prismjs": "^1.26.0",

View File

@ -16,6 +16,10 @@ exports[`getTranslationFiles returns translation files matching snapshot 1`] = `
"description": "Navbar item with label Dropdown item 2",
"message": "Dropdown item 2",
},
"logo.alt": {
"description": "The alt text of navbar logo",
"message": "navbar alt text logo",
},
"title": {
"description": "The title in the navbar",
"message": "navbar title",
@ -49,6 +53,10 @@ exports[`getTranslationFiles returns translation files matching snapshot 1`] = `
"description": "The title of the footer links column with title=Footer link column 2 in the footer",
"message": "Footer link column 2",
},
"logo.alt": {
"description": "The alt text of footer logo",
"message": "footer alt text logo",
},
},
"path": "footer",
},
@ -71,6 +79,10 @@ exports[`getTranslationFiles returns translation files matching snapshot 2`] = `
"description": "Navbar item with label Dropdown item 2",
"message": "Dropdown item 2",
},
"logo.alt": {
"description": "The alt text of navbar logo",
"message": "navbar alt text logo",
},
"title": {
"description": "The title in the navbar",
"message": "navbar title",
@ -92,6 +104,10 @@ exports[`getTranslationFiles returns translation files matching snapshot 2`] = `
"description": "The label of footer link with label=Link 2 linking to https://facebook.com",
"message": "Link 2",
},
"logo.alt": {
"description": "The alt text of footer logo",
"message": "footer alt text logo",
},
},
"path": "footer",
},
@ -131,6 +147,10 @@ exports[`translateThemeConfig returns translated themeConfig 1`] = `
"title": "Footer link column 2 (translated)",
},
],
"logo": {
"alt": "footer alt text logo (translated)",
"src": "img/docusaurus.svg",
},
"style": "light",
},
"navbar": {
@ -150,6 +170,10 @@ exports[`translateThemeConfig returns translated themeConfig 1`] = `
"label": "Dropdown (translated)",
},
],
"logo": {
"alt": "navbar alt text logo (translated)",
"src": "img/docusaurus.svg",
},
"style": "dark",
"title": "navbar title (translated)",
},

View File

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

View File

@ -18,6 +18,10 @@ const ThemeConfigSample = {
},
navbar: {
title: 'navbar title',
logo: {
alt: 'navbar alt text logo',
src: 'img/docusaurus.svg',
},
style: 'dark',
hideOnScroll: false,
items: [
@ -31,6 +35,10 @@ const ThemeConfigSample = {
],
},
footer: {
logo: {
alt: 'footer alt text logo',
src: 'img/docusaurus.svg',
},
copyright: 'Copyright FB',
style: 'light',
links: [
@ -52,6 +60,10 @@ const ThemeConfigSample = {
const ThemeConfigSampleSimpleFooter: ThemeConfig = {
...ThemeConfigSample,
footer: {
logo: {
alt: 'footer alt text logo',
src: 'img/docusaurus.svg',
},
copyright: 'Copyright FB',
style: 'light',
links: [

View File

@ -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,33 @@ 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',
wrap: 'safe',
},
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
@ -93,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',
@ -212,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: {
@ -232,6 +278,14 @@ export default function getSwizzleConfig(): SwizzleConfig {
description:
'The search bar component of your site, appearing in the navbar.',
},
SkipToContent: {
actions: {
eject: 'safe',
wrap: 'safe',
},
description:
'The component responsible for implementing the accessibility "skip to content" link (https://www.w3.org/TR/WCAG20-TECHS/G1.html)',
},
'prism-include-languages': {
actions: {
eject: 'safe',

View File

@ -26,6 +26,8 @@ const ContextReplacementPlugin = requireFromDocusaurusCore(
// Need to be inlined to prevent dark mode FOUC
// Make sure the key is the same as the one in `/theme/hooks/useTheme.js`
const ThemeStorageKey = 'theme';
const ThemeQueryStringKey = 'docusaurus-theme';
const noFlashColorMode = ({
defaultMode,
respectPrefersColorScheme,
@ -39,6 +41,14 @@ const noFlashColorMode = ({
document.documentElement.setAttribute('data-theme', theme);
}
function getQueryStringTheme() {
var theme = null;
try {
theme = new URLSearchParams(window.location.search).get('${ThemeQueryStringKey}')
} catch(e) {}
return theme;
}
function getStoredTheme() {
var theme = null;
try {
@ -47,9 +57,9 @@ const noFlashColorMode = ({
return theme;
}
var storedTheme = getStoredTheme();
if (storedTheme !== null) {
setDataThemeAttribute(storedTheme);
var initialTheme = getQueryStringTheme() || getStoredTheme();
if (initialTheme !== null) {
setDataThemeAttribute(initialTheme);
} else {
if (
respectPrefersColorScheme &&

View File

@ -55,6 +55,22 @@ declare module '@theme/AnnouncementBar' {
export default function AnnouncementBar(): JSX.Element | null;
}
declare module '@theme/AnnouncementBar/Content' {
import type {ComponentProps} from 'react';
export interface Props extends ComponentProps<'div'> {}
export default function AnnouncementBarContent(props: Props): JSX.Element;
}
declare module '@theme/AnnouncementBar/CloseButton' {
import type {ComponentProps} from 'react';
export interface Props extends ComponentProps<'button'> {}
export default function AnnouncementBarCloseButton(props: Props): JSX.Element;
}
declare module '@theme/BackToTopButton' {
export default function BackToTopButton(): JSX.Element;
}
@ -320,7 +336,7 @@ declare module '@theme/DocCardList' {
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
export interface Props {
readonly items: PropSidebarItem[];
readonly items?: PropSidebarItem[];
readonly className?: string;
}
@ -772,6 +788,7 @@ declare module '@theme/MDXComponents' {
import type MDXUl from '@theme/MDXComponents/Ul';
import type MDXImg from '@theme/MDXComponents/Img';
import type Admonition from '@theme/Admonition';
import type Mermaid from '@theme/Mermaid';
export type MDXComponentsObject = {
readonly head: typeof MDXHead;
@ -788,6 +805,7 @@ declare module '@theme/MDXComponents' {
readonly h5: (props: ComponentProps<'h5'>) => JSX.Element;
readonly h6: (props: ComponentProps<'h6'>) => JSX.Element;
readonly admonition: typeof Admonition;
readonly mermaid: typeof Mermaid;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[tagName: string]: ComponentType<any>;
};
@ -1096,39 +1114,26 @@ declare module '@theme/SearchBar' {
export default function SearchBar(): JSX.Element;
}
declare module '@theme/TabItem' {
import type {ReactNode} from 'react';
declare module '@theme/Mermaid' {
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};
value: string;
}
export default function Mermaid(props: Props): JSX.Element;
}
declare module '@theme/TabItem' {
import type {TabItemProps} from '@docusaurus/theme-common/internal';
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;
}
@ -1238,6 +1243,7 @@ declare module '@theme/ColorModeToggle' {
export interface Props {
readonly className?: string;
readonly buttonClassName?: string;
readonly value: ColorMode;
/**
* The parameter represents the "to-be" value. For example, if currently in
@ -1366,3 +1372,7 @@ declare module '@theme/prism-include-languages' {
PrismObject: typeof PrismNamespace,
): void;
}
declare module '@theme/DocBreadcrumbs/Items/Home' {
export default function HomeBreadcrumbItem(): JSX.Element;
}

Some files were not shown because too many files have changed in this diff Show More