Compare commits

...

17 Commits
main ... v2.4.3

Author SHA1 Message Date
sebastienlorber 56410aa946 v2.4.3 2023-09-20 17:41:27 +02:00
Sébastien Lorber 4ab5a93262
chore: backport retro compatible commits for the Docusaurus v2.4.2 release (#9324)
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: Ori Shalom <ori-shalom@users.noreply.github.com>
Co-authored-by: Mikey O'Toole <mikey@homotechsual.dev>
Co-authored-by: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
fix flaky screenshots, add html data-has-hydrated attribute (#9256)
fix(theme-common): ThemedComponent should display something when JS is disabled (#9243)
fix(theme): canonical url should be not change after hydration if url accessed with/without trailing slash (#9130)
fix(theme): only set classname on ul elements if they have an existing class (#9099)
fix(content-docs): sidebar generator should return customProps for doc items (#9107)
2023-09-20 17:29:41 +02:00
Sébastien Lorber 4a2200ace4
chore: backport retro compatible commits for the Docusaurus v2.4.1 release (#8979)
Co-authored-by: Tarun Chauhan <tarun.chauhan@tripjack.com>
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
Co-authored-by: Sachin Nanayakkara <sachinnanayakkara21@gmail.com>
Co-authored-by: Tarun Chauhan <tarunrajput1337@gmail.com>
Co-authored-by: Armano <armano2@users.noreply.github.com>
Co-authored-by: Aleksandr Vladykin <morsko1@yandex.ru>
Co-authored-by: Andrew Lyons <andrew@nicols.co.uk>
Co-authored-by: MetuMortis <78408599+LiberaTeMetuMortis@users.noreply.github.com>
fix(create): add missing await (#8831)
fix(theme-translations): remove redundant navigation text in aria label (#8842)
fix(utils): handle Markdown links with spaces to route correctly (#8874)
fix(theme-common): fix confusing theme error message: bad sidebar id suggestions (#8873)
fix(theme-common): fix collapsible component with prefers-reduced-motion (#8906)
fix(theme): add __ prefix to technical anchors, search crawlers (Algolia) should ignore them (#8909)
fix(core): Correct yarn upgrade command for yarn 2.x (#8908)
fix(theme-translations): fix Turkish translation for aria label "Enter key" (#8933)
fix(theme): fix collapsible sidebar behavior when prefers-reduced-motion (#8971)
2023-05-15 15:41:07 +02:00
Sébastien Lorber 4fb67ef11b
chore: backport retro compatible commits for the Docusaurus v2.4 release (#8809)
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: Ben Gubler <nebrelbug@gmail.com>
Co-authored-by: Davide Donadio <davide.donadio@it.clara.net>
Co-authored-by: Petter Drønnen <36735557+dr0nn1@users.noreply.github.com>
Co-authored-by: Moritz Stückler <moritz@bitbetter.de>
Co-authored-by: Mysterious_Dev <40738104+Mysterious-Dev@users.noreply.github.com>
Co-authored-by: TrueQAP <32407751+trueqap@users.noreply.github.com>
Co-authored-by: Kagan <34136752+kagankan@users.noreply.github.com>
Co-authored-by: Dewansh Thakur <71703033+dewanshDT@users.noreply.github.com>
Co-authored-by: Armano <armano2@users.noreply.github.com>
Co-authored-by: Anas <60762285+Anasqx@users.noreply.github.com>
Co-authored-by: Tanner Dolby <tannercdolby@gmail.com>
Co-authored-by: Davide Donadio <davide.donadio94@gmail.com>
Co-authored-by: biplavmz <68702055+biplavmz@users.noreply.github.com>
Co-authored-by: Vishruta Patil <72292532+Vishruta-Patil@users.noreply.github.com>
fix(theme-classic): fix tab focus bug in dropdown (#8697) (#8699)
fix(theme): improve color toggle when using dark navbar (#8615)
fix(theme-translations): fix wrong arabic words (tip/next) (#8744)
fix(core): baseUrl error banner link anchor case (#8746)
fix(search): search page should react to querystring changes + cleanup/refactor (#8757)
fix(theme): allow tabs children to be falsy (#8801)
fix(theme): codeblock buttons should be kept on the right when using RTL locale (#8803)
2023-03-24 09:23:58 +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
389 changed files with 7619 additions and 1846 deletions

View File

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

View File

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

View File

@ -8,7 +8,7 @@
set -euo pipefail set -euo pipefail
CUSTOM_REGISTRY_URL="http://localhost:4873" 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" CONTAINER_NAME="verdaccio"
EXTRA_OPTS="" EXTRA_OPTS=""

View File

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

View File

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

View File

@ -241,7 +241,7 @@ async function getSiteName(
return true; return true;
} }
if (reqName) { if (reqName) {
const res = validateSiteName(reqName); const res = await validateSiteName(reqName);
if (typeof res === 'string') { if (typeof res === 'string') {
throw new Error(res); throw new Error(res);
} }
@ -456,8 +456,10 @@ export default async function init(
reqTemplate?: string, reqTemplate?: string,
cliOptions: CLIOptions = {}, cliOptions: CLIOptions = {},
): Promise<void> { ): Promise<void> {
const templates = await readTemplates(); const [templates, siteName] = await Promise.all([
const siteName = await getSiteName(reqName, rootDir); readTemplates(),
getSiteName(reqName, rootDir),
]);
const dest = path.resolve(rootDir, siteName); const dest = path.resolve(rootDir, siteName);
const source = await getSource(reqTemplate, templates, cliOptions); const source = await getSource(reqTemplate, templates, cliOptions);

View File

@ -1,6 +1,6 @@
{ {
"name": "docusaurus-2-classic-typescript-template", "name": "docusaurus-2-classic-typescript-template",
"version": "2.0.0-rc.1", "version": "2.4.3",
"private": true, "private": true,
"scripts": { "scripts": {
"docusaurus": "docusaurus", "docusaurus": "docusaurus",
@ -15,8 +15,8 @@
"typecheck": "tsc" "typecheck": "tsc"
}, },
"dependencies": { "dependencies": {
"@docusaurus/core": "2.0.0-rc.1", "@docusaurus/core": "2.4.3",
"@docusaurus/preset-classic": "2.0.0-rc.1", "@docusaurus/preset-classic": "2.4.3",
"@mdx-js/react": "^1.6.22", "@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"prism-react-renderer": "^1.3.5", "prism-react-renderer": "^1.3.5",
@ -24,7 +24,7 @@
"react-dom": "^17.0.2" "react-dom": "^17.0.2"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-rc.1", "@docusaurus/module-type-aliases": "2.4.3",
"@tsconfig/docusaurus": "^1.0.5", "@tsconfig/docusaurus": "^1.0.5",
"typescript": "^4.7.4" "typescript": "^4.7.4"
}, },

View File

@ -8,17 +8,22 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula');
const config = { const config = {
title: 'My Site', title: 'My Site',
tagline: 'Dinosaurs are cool', tagline: 'Dinosaurs are cool',
url: 'https://your-docusaurus-test-site.com',
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.ico', 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. // GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these. // If you aren't using GitHub pages, you don't need these.
organizationName: 'facebook', // Usually your GitHub org/user name. organizationName: 'facebook', // Usually your GitHub org/user name.
projectName: 'docusaurus', // Usually your repo 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 // 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 // metadata like html lang. For example, if your site is Chinese, you may want
// to replace "en" with "zh-Hans". // to replace "en" with "zh-Hans".
@ -56,6 +61,8 @@ const config = {
themeConfig: themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */ /** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({ ({
// Replace with your project's social card
image: 'img/docusaurus-social-card.jpg',
navbar: { navbar: {
title: 'My Site', title: 'My Site',
logo: { logo: {
@ -64,8 +71,8 @@ const config = {
}, },
items: [ items: [
{ {
type: 'doc', type: 'docSidebar',
docId: 'intro', sidebarId: 'tutorialSidebar',
position: 'left', position: 'left',
label: 'Tutorial', label: 'Tutorial',
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "docusaurus-2-classic-template", "name": "docusaurus-2-classic-template",
"version": "2.0.0-rc.1", "version": "2.4.3",
"private": true, "private": true,
"scripts": { "scripts": {
"docusaurus": "docusaurus", "docusaurus": "docusaurus",
@ -14,8 +14,8 @@
"write-heading-ids": "docusaurus write-heading-ids" "write-heading-ids": "docusaurus write-heading-ids"
}, },
"dependencies": { "dependencies": {
"@docusaurus/core": "2.0.0-rc.1", "@docusaurus/core": "2.4.3",
"@docusaurus/preset-classic": "2.0.0-rc.1", "@docusaurus/preset-classic": "2.4.3",
"@mdx-js/react": "^1.6.22", "@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"prism-react-renderer": "^1.3.5", "prism-react-renderer": "^1.3.5",
@ -23,7 +23,7 @@
"react-dom": "^17.0.2" "react-dom": "^17.0.2"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-rc.1" "@docusaurus/module-type-aliases": "2.4.3"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [

View File

@ -13,17 +13,22 @@
const config = { const config = {
title: 'My Site', title: 'My Site',
tagline: 'The tagline of my site', tagline: 'The tagline of my site',
url: 'https://your-docusaurus-test-site.com',
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.ico', 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. // GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these. // If you aren't using GitHub pages, you don't need these.
organizationName: 'facebook', // Usually your GitHub org/user name. organizationName: 'facebook', // Usually your GitHub org/user name.
projectName: 'docusaurus', // Usually your repo name. projectName: 'docusaurus', // Usually your repo name.
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
presets: [ presets: [
[ [
'classic', 'classic',
@ -53,6 +58,7 @@ const config = {
themeConfig: themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */ /** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({ ({
image: 'img/docusaurus-social-card.jpg',
navbar: { navbar: {
title: 'My Meta Project', title: 'My Meta Project',
logo: { logo: {
@ -61,8 +67,8 @@ const config = {
}, },
items: [ items: [
{ {
type: 'doc', type: 'docSidebar',
docId: 'intro', sidebarId: 'tutorialSidebar',
position: 'left', position: 'left',
label: 'Tutorial', label: 'Tutorial',
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "docusaurus-2-facebook-template", "name": "docusaurus-2-facebook-template",
"version": "2.0.0-rc.1", "version": "2.4.3",
"private": true, "private": true,
"scripts": { "scripts": {
"docusaurus": "docusaurus", "docusaurus": "docusaurus",
@ -18,8 +18,8 @@
"format:diff": "prettier --config .prettierrc --list-different \"**/*.{js,jsx,ts,tsx,md,mdx}\"" "format:diff": "prettier --config .prettierrc --list-different \"**/*.{js,jsx,ts,tsx,md,mdx}\""
}, },
"dependencies": { "dependencies": {
"@docusaurus/core": "2.0.0-rc.1", "@docusaurus/core": "2.4.3",
"@docusaurus/preset-classic": "2.0.0-rc.1", "@docusaurus/preset-classic": "2.4.3",
"@mdx-js/react": "^1.6.22", "@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"react": "^17.0.2", "react": "^17.0.2",

View File

@ -25,10 +25,12 @@ module.exports = {
// But you can create a sidebar manually // But you can create a sidebar manually
/* /*
tutorialSidebar: [ tutorialSidebar: [
'intro',
'hello',
{ {
type: 'category', type: 'category',
label: 'Tutorial', 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? ## 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 custom [Design and Layout](https://docusaurus.io/docs/styling-layout)
- Add a [search bar](https://docusaurus.io/docs/search) - Add a [search bar](https://docusaurus.io/docs/search)
- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase) - 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" ```js title="sidebars.js"
module.exports = { module.exports = {
tutorialSidebar: [ tutorialSidebar: [
'intro',
// highlight-next-line
'hello',
{ {
type: 'category', type: 'category',
label: 'Tutorial', label: 'Tutorial',
// highlight-next-line items: ['tutorial-basics/create-a-document'],
items: ['hello'],
}, },
], ],
}; };

View File

@ -51,7 +51,11 @@ You can use absolute paths to reference images in the static directory (`static/
![Docusaurus logo](/img/docusaurus.png) ![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 ## Code Blocks

View File

@ -19,10 +19,12 @@ const sidebars = {
// But you can create a sidebar manually // But you can create a sidebar manually
/* /*
tutorialSidebar: [ tutorialSidebar: [
'intro',
'hello',
{ {
type: 'category', type: 'category',
label: 'Tutorial', 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", "name": "@docusaurus/cssnano-preset",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "Advanced cssnano preset for maximum optimization.", "description": "Advanced cssnano preset for maximum optimization.",
"main": "lib/index.js", "main": "lib/index.js",
"license": "MIT", "license": "MIT",

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/mdx-loader", "name": "@docusaurus/mdx-loader",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "Docusaurus Loader for MDX", "description": "Docusaurus Loader for MDX",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
@ -20,8 +20,8 @@
"dependencies": { "dependencies": {
"@babel/parser": "^7.18.8", "@babel/parser": "^7.18.8",
"@babel/traverse": "^7.18.8", "@babel/traverse": "^7.18.8",
"@docusaurus/logger": "2.0.0-rc.1", "@docusaurus/logger": "2.4.3",
"@docusaurus/utils": "2.0.0-rc.1", "@docusaurus/utils": "2.4.3",
"@mdx-js/mdx": "^1.6.22", "@mdx-js/mdx": "^1.6.22",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
@ -37,7 +37,7 @@
"webpack": "^5.73.0" "webpack": "^5.73.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/types": "2.0.0-rc.1", "@docusaurus/types": "2.4.3",
"@types/escape-html": "^1.0.2", "@types/escape-html": "^1.0.2",
"@types/mdast": "^3.0.10", "@types/mdast": "^3.0.10",
"@types/stringify-object": "^3.3.1", "@types/stringify-object": "^3.3.1",

View File

@ -22,8 +22,10 @@ import toc from './remark/toc';
import unwrapMdxCodeBlocks from './remark/unwrapMdxCodeBlocks'; import unwrapMdxCodeBlocks from './remark/unwrapMdxCodeBlocks';
import transformImage from './remark/transformImage'; import transformImage from './remark/transformImage';
import transformLinks from './remark/transformLinks'; import transformLinks from './remark/transformLinks';
import mermaid from './remark/mermaid';
import transformAdmonitions from './remark/admonitions'; import transformAdmonitions from './remark/admonitions';
import type {MarkdownConfig} from '@docusaurus/types';
import type {LoaderContext} from 'webpack'; import type {LoaderContext} from 'webpack';
import type {Processor, Plugin} from 'unified'; import type {Processor, Plugin} from 'unified';
import type {AdmonitionOptions} from './remark/admonitions'; import type {AdmonitionOptions} from './remark/admonitions';
@ -61,6 +63,7 @@ export type MDXOptions = {
}; };
export type Options = Partial<MDXOptions> & { export type Options = Partial<MDXOptions> & {
markdownConfig: MarkdownConfig;
staticDirs: string[]; staticDirs: string[];
siteDir: string; siteDir: string;
isMDXPartial?: (filePath: string) => boolean; isMDXPartial?: (filePath: string) => boolean;
@ -71,7 +74,6 @@ export type Options = Partial<MDXOptions> & {
frontMatter: {[key: string]: unknown}; frontMatter: {[key: string]: unknown};
metadata: {[key: string]: unknown}; metadata: {[key: string]: unknown};
}) => {[key: string]: unknown}; }) => {[key: string]: unknown};
filepath: string;
}; };
/** /**
@ -171,6 +173,7 @@ export async function mdxLoader(
...(reqOptions.beforeDefaultRemarkPlugins ?? []), ...(reqOptions.beforeDefaultRemarkPlugins ?? []),
...getAdmonitionsPlugins(reqOptions.admonitions ?? false), ...getAdmonitionsPlugins(reqOptions.admonitions ?? false),
...DEFAULT_OPTIONS.remarkPlugins, ...DEFAULT_OPTIONS.remarkPlugins,
...(reqOptions.markdownConfig.mermaid ? [mermaid] : []),
[ [
transformImage, 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> "<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>" <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'); const result = await processFixture('interpolation');
expect(result).toMatchSnapshot(); 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 options = normalizeOptions(optionsInput);
const keywords = Object.values(options.keywords).map(escapeRegExp).join('|'); const keywords = Object.values(options.keywords).map(escapeRegExp).join('|');
const nestingChar = escapeRegExp(options.tag.slice(0, 1));
const tag = escapeRegExp(options.tag); 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 // The tokenizer is called on blocks to determine if there is an admonition
// present and create tags for it // present and create tags for it
@ -77,6 +88,11 @@ const plugin: Plugin = function plugin(
]; ];
const food = []; const food = [];
const content = []; 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; let newValue = value;
// consume lines until a closing tag // 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); next !== -1 ? newValue.slice(idx + 1, next) : newValue.slice(idx + 1);
food.push(line); food.push(line);
newValue = newValue.slice(idx + 1); newValue = newValue.slice(idx + 1);
// the closing tag is NOT part of the content const nesting = nestingLevelRegex.exec(line);
if (line.startsWith(options.tag)) { idx = newValue.indexOf(NEWLINE);
break; 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); content.push(line);
idx = newValue.indexOf(NEWLINE);
} }
// consume the processed tag and replace escape sequences // 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", "name": "@docusaurus/migrate",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "A CLI tool to migrate from older versions of Docusaurus.", "description": "A CLI tool to migrate from older versions of Docusaurus.",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -24,8 +24,8 @@
"dependencies": { "dependencies": {
"@babel/core": "^7.18.6", "@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.6", "@babel/preset-env": "^7.18.6",
"@docusaurus/logger": "2.0.0-rc.1", "@docusaurus/logger": "2.4.3",
"@docusaurus/utils": "2.0.0-rc.1", "@docusaurus/utils": "2.4.3",
"@mapbox/hast-util-to-jsx": "^2.0.0", "@mapbox/hast-util-to-jsx": "^2.0.0",
"color": "^4.2.3", "color": "^4.2.3",
"commander": "^5.1.0", "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": { "logo": {
"src": "img/docusaurus_monochrome.svg" "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": { "logo": {
"src": "img/docusaurus_monochrome.svg" "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": { "logo": {
"src": "img/docusaurus_monochrome.svg" "src": "img/docusaurus_monochrome.svg"
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/module-type-aliases", "name": "@docusaurus/module-type-aliases",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "Docusaurus module type aliases.", "description": "Docusaurus module type aliases.",
"types": "./src/index.d.ts", "types": "./src/index.d.ts",
"publishConfig": { "publishConfig": {
@ -13,7 +13,7 @@
}, },
"dependencies": { "dependencies": {
"@docusaurus/react-loadable": "5.5.2", "@docusaurus/react-loadable": "5.5.2",
"@docusaurus/types": "2.0.0-rc.1", "@docusaurus/types": "2.4.3",
"@types/history": "^4.7.11", "@types/history": "^4.7.11",
"@types/react": "*", "@types/react": "*",
"@types/react-router-config": "*", "@types/react-router-config": "*",

View File

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/plugin-client-redirects", "name": "@docusaurus/plugin-client-redirects",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "Client redirects plugin for Docusaurus.", "description": "Client redirects plugin for Docusaurus.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
@ -18,18 +18,18 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.0.0-rc.1", "@docusaurus/core": "2.4.3",
"@docusaurus/logger": "2.0.0-rc.1", "@docusaurus/logger": "2.4.3",
"@docusaurus/utils": "2.0.0-rc.1", "@docusaurus/utils": "2.4.3",
"@docusaurus/utils-common": "2.0.0-rc.1", "@docusaurus/utils-common": "2.4.3",
"@docusaurus/utils-validation": "2.0.0-rc.1", "@docusaurus/utils-validation": "2.4.3",
"eta": "^1.12.3", "eta": "^2.0.0",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/types": "2.0.0-rc.1" "@docusaurus/types": "2.4.3"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",

View File

@ -1,8 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`collectRedirects throw if plugin option redirects contain invalid to paths 1`] = ` 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: "You are trying to create client-side redirections to invalid paths.
- /this/path/does/not/exist2
These paths are redirected to but do not exist:
- /this/path/does/not/exist2 - /this/path/does/not/exist2
Valid paths you can redirect to: 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`] = ` exports[`collectRedirects throws if redirect creator creates array of array redirect 1`] = `
"Some created redirects are invalid: "Some created redirects are invalid:
- {"from":["/fromPath"],"to":"/"} => Validation error: "from" must be a string - {"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/" /> <link rel="canonical" href="https://docusaurus.io/gr/%CF%83%CE%B5%CE%BB%CE%B9%CE%B4%CE%B1%CF%82/" />
</head> </head>
<script> <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> </script>
</html>" </html>"
`; `;
@ -23,7 +23,7 @@ exports[`createRedirectPageContent works 1`] = `
<link rel="canonical" href="https://docusaurus.io/" /> <link rel="canonical" href="https://docusaurus.io/" />
</head> </head>
<script> <script>
window.location.href = 'https://docusaurus.io/'; window.location.href = 'https://docusaurus.io/' + window.location.search + window.location.hash;
</script> </script>
</html>" </html>"
`; `;

View File

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

View File

@ -220,6 +220,69 @@ describe('collectRedirects', () => {
).toThrowErrorMatchingSnapshot(); ).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', () => { it('collects redirects with custom redirect creator', () => {
expect( expect(
collectRedirects( collectRedirects(

View File

@ -7,6 +7,7 @@
import _ from 'lodash'; import _ from 'lodash';
import logger from '@docusaurus/logger'; import logger from '@docusaurus/logger';
import {addTrailingSlash, removeTrailingSlash} from '@docusaurus/utils';
import {applyTrailingSlash} from '@docusaurus/utils-common'; import {applyTrailingSlash} from '@docusaurus/utils-common';
import { import {
createFromExtensionsRedirects, createFromExtensionsRedirects,
@ -80,16 +81,59 @@ function validateCollectedRedirects(
const allowedToPaths = pluginContext.relativeRoutesPaths; const allowedToPaths = pluginContext.relativeRoutesPaths;
const toPaths = redirects.map((redirect) => redirect.to); const toPaths = redirects.map((redirect) => redirect.to);
const illegalToPaths = _.difference(toPaths, allowedToPaths); const trailingSlashConfig = pluginContext.siteConfig.trailingSlash;
if (illegalToPaths.length > 0) { // Key is the path, value is whether a valid toPath with a different trailing
throw new Error( // slash exists; if the key doesn't exist it means it's valid
`You are trying to create client-side redirections to paths that do not exist: const differByTrailSlash = new Map(toPaths.map((path) => [path, false]));
- ${illegalToPaths.join('\n- ')} 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: Valid paths you can redirect to:
- ${allowedToPaths.join('\n- ')} - ${allowedToPaths.join('\n- ')}
`, `;
); }
throw new Error(message);
} }
} }

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/plugin-content-blog", "name": "@docusaurus/plugin-content-blog",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "Blog plugin for Docusaurus.", "description": "Blog plugin for Docusaurus.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "src/plugin-content-blog.d.ts", "types": "src/plugin-content-blog.d.ts",
@ -18,13 +18,13 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.0.0-rc.1", "@docusaurus/core": "2.4.3",
"@docusaurus/logger": "2.0.0-rc.1", "@docusaurus/logger": "2.4.3",
"@docusaurus/mdx-loader": "2.0.0-rc.1", "@docusaurus/mdx-loader": "2.4.3",
"@docusaurus/types": "2.0.0-rc.1", "@docusaurus/types": "2.4.3",
"@docusaurus/utils": "2.0.0-rc.1", "@docusaurus/utils": "2.4.3",
"@docusaurus/utils-common": "2.0.0-rc.1", "@docusaurus/utils-common": "2.4.3",
"@docusaurus/utils-validation": "2.0.0-rc.1", "@docusaurus/utils-validation": "2.4.3",
"cheerio": "^1.0.0-rc.12", "cheerio": "^1.0.0-rc.12",
"feed": "^4.2.2", "feed": "^4.2.2",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
@ -35,10 +35,6 @@
"utility-types": "^3.10.0", "utility-types": "^3.10.0",
"webpack": "^5.73.0" "webpack": "^5.73.0"
}, },
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21",
"escape-string-regexp": "^4.0.0"
},
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",
"react-dom": "^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(); ).toMatchSnapshot();
fsMock.mockClear(); 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. * 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 {validateBlogPostFrontMatter} from '../frontMatter';
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog'; import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
@ -57,7 +57,7 @@ function testField(params: {
} catch (err) { } catch (err) {
// eslint-disable-next-line jest/no-conditional-expect // eslint-disable-next-line jest/no-conditional-expect
expect((err as Error).message).toMatch( 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 path from 'path';
import fs from 'fs-extra'; import fs from 'fs-extra';
import logger from '@docusaurus/logger'; 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 {normalizeUrl, readOutputHTMLFile} from '@docusaurus/utils';
import {blogPostContainerID} from '@docusaurus/utils-common'; import {blogPostContainerID} from '@docusaurus/utils-common';
import {load as cheerioLoad} from 'cheerio'; import {load as cheerioLoad} from 'cheerio';
@ -18,6 +18,7 @@ import type {
PluginOptions, PluginOptions,
Author, Author,
BlogPost, BlogPost,
BlogFeedItem,
} from '@docusaurus/plugin-content-blog'; } from '@docusaurus/plugin-content-blog';
async function generateBlogFeed({ async function generateBlogFeed({
@ -54,14 +55,39 @@ async function generateBlogFeed({
copyright: feedOptions.copyright, 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 { function toFeedAuthor(author: Author): FeedAuthor {
return {name: author.name, link: author.url, email: author.email}; return {name: author.name, link: author.url, email: author.email};
} }
await Promise.all( return Promise.all(
blogPosts.map(async (post) => { blogPosts.map(async (post) => {
const { const {
id,
metadata: { metadata: {
title: metadataTitle, title: metadataTitle,
permalink, permalink,
@ -79,10 +105,11 @@ async function generateBlogFeed({
); );
const $ = cheerioLoad(content); const $ = cheerioLoad(content);
const feedItem: FeedItem = { const link = normalizeUrl([siteUrl, permalink]);
const feedItem: BlogFeedItem = {
title: metadataTitle, title: metadataTitle,
id, id: link,
link: normalizeUrl([siteUrl, permalink]), link,
date, date,
description, description,
// Atom feed demands the "term", while other feeds use "name" // Atom feed demands the "term", while other feeds use "name"
@ -99,9 +126,7 @@ async function generateBlogFeed({
return feedItem; return feedItem;
}), }),
).then((items) => items.forEach(feed.addItem)); );
return feed;
} }
async function createBlogFeedFile({ async function createBlogFeedFile({

View File

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

View File

@ -124,6 +124,7 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
.default(DEFAULT_OPTIONS.feedOptions.copyright), .default(DEFAULT_OPTIONS.feedOptions.copyright),
}), }),
language: Joi.string(), language: Joi.string(),
createFeedItems: Joi.function(),
}).default(DEFAULT_OPTIONS.feedOptions), }).default(DEFAULT_OPTIONS.feedOptions),
authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath), authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime), 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 {LoadedMDXContent} from '@docusaurus/mdx-loader';
import type {MDXOptions} from '@docusaurus/mdx-loader'; import type {MDXOptions} from '@docusaurus/mdx-loader';
import type {FrontMatterTag, Tag} from '@docusaurus/utils'; 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'; import type {Overwrite} from 'utility-types';
export type Assets = { 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 * bundler-generated image path. Otherwise, it's empty, and the image URL
* should be accessed through `frontMatter.image`. * should be accessed through `frontMatter.image`.
*/ */
@ -255,6 +262,24 @@ declare module '@docusaurus/plugin-content-blog' {
copyright: string; copyright: string;
/** Language of the feed. */ /** Language of the feed. */
language?: string; 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; content: string;
}; };
export type BlogFeedItem = FeedItem;
export type BlogPaginatedMetadata = { export type BlogPaginatedMetadata = {
/** Title of the entire blog. */ /** Title of the entire blog. */
readonly blogTitle: string; readonly blogTitle: string;

View File

@ -1,10 +1,11 @@
{ {
"name": "@docusaurus/plugin-content-docs", "name": "@docusaurus/plugin-content-docs",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "Docs plugin for Docusaurus.", "description": "Docs plugin for Docusaurus.",
"main": "lib/index.js", "main": "lib/index.js",
"sideEffects": false, "sideEffects": false,
"exports": { "exports": {
"./lib/*": "./lib/*",
"./src/*": "./src/*", "./src/*": "./src/*",
"./client": { "./client": {
"type": "./lib/client/index.d.ts", "type": "./lib/client/index.d.ts",
@ -34,13 +35,13 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.0.0-rc.1", "@docusaurus/core": "2.4.3",
"@docusaurus/logger": "2.0.0-rc.1", "@docusaurus/logger": "2.4.3",
"@docusaurus/mdx-loader": "2.0.0-rc.1", "@docusaurus/mdx-loader": "2.4.3",
"@docusaurus/module-type-aliases": "2.0.0-rc.1", "@docusaurus/module-type-aliases": "2.4.3",
"@docusaurus/types": "2.0.0-rc.1", "@docusaurus/types": "2.4.3",
"@docusaurus/utils": "2.0.0-rc.1", "@docusaurus/utils": "2.4.3",
"@docusaurus/utils-validation": "2.0.0-rc.1", "@docusaurus/utils-validation": "2.4.3",
"@types/react-router-config": "^5.0.6", "@types/react-router-config": "^5.0.6",
"combine-promises": "^1.1.0", "combine-promises": "^1.1.0",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
@ -52,11 +53,9 @@
"webpack": "^5.73.0" "webpack": "^5.73.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/types": "2.0.0-beta.21",
"@types/js-yaml": "^4.0.5", "@types/js-yaml": "^4.0.5",
"@types/picomatch": "^2.3.0", "@types/picomatch": "^2.3.0",
"commander": "^5.1.0", "commander": "^5.1.0",
"escape-string-regexp": "^4.0.0",
"picomatch": "^2.3.1", "picomatch": "^2.3.1",
"shelljs": "^0.8.5" "shelljs": "^0.8.5"
}, },

View File

@ -901,6 +901,7 @@ exports[`simple website content: data 1`] = `
"label": "Next", "label": "Next",
"banner": null, "banner": null,
"badge": false, "badge": false,
"noIndex": false,
"className": "docs-version-current", "className": "docs-version-current",
"isLast": true, "isLast": true,
"docsSidebars": { "docsSidebars": {
@ -2608,6 +2609,7 @@ exports[`versioned website (community) content: data 1`] = `
"label": "1.0.0", "label": "1.0.0",
"banner": null, "banner": null,
"badge": true, "badge": true,
"noIndex": false,
"className": "docs-version-1.0.0", "className": "docs-version-1.0.0",
"isLast": true, "isLast": true,
"docsSidebars": { "docsSidebars": {
@ -2635,6 +2637,7 @@ exports[`versioned website (community) content: data 1`] = `
"label": "Next", "label": "Next",
"banner": "unreleased", "banner": "unreleased",
"badge": true, "badge": true,
"noIndex": false,
"className": "docs-version-current", "className": "docs-version-current",
"isLast": false, "isLast": false,
"docsSidebars": { "docsSidebars": {
@ -3477,6 +3480,7 @@ exports[`versioned website content: data 1`] = `
"label": "1.0.0", "label": "1.0.0",
"banner": "unmaintained", "banner": "unmaintained",
"badge": true, "badge": true,
"noIndex": false,
"className": "docs-version-1.0.0", "className": "docs-version-1.0.0",
"isLast": false, "isLast": false,
"docsSidebars": { "docsSidebars": {
@ -3544,6 +3548,7 @@ exports[`versioned website content: data 1`] = `
"label": "1.0.1", "label": "1.0.1",
"banner": null, "banner": null,
"badge": true, "badge": true,
"noIndex": true,
"className": "docs-version-1.0.1", "className": "docs-version-1.0.1",
"isLast": true, "isLast": true,
"docsSidebars": { "docsSidebars": {
@ -3599,6 +3604,7 @@ exports[`versioned website content: data 1`] = `
"label": "Next", "label": "Next",
"banner": "unreleased", "banner": "unreleased",
"badge": true, "badge": true,
"noIndex": false,
"className": "docs-version-current", "className": "docs-version-current",
"isLast": false, "isLast": false,
"docsSidebars": { "docsSidebars": {
@ -3674,6 +3680,7 @@ exports[`versioned website content: data 1`] = `
"label": "withSlugs", "label": "withSlugs",
"banner": "unmaintained", "banner": "unmaintained",
"badge": true, "badge": true,
"noIndex": false,
"className": "docs-version-withSlugs", "className": "docs-version-withSlugs",
"isLast": false, "isLast": false,
"docsSidebars": { "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( async function testMeta(
docFileSource: string, docFileSource: string,
expectedMetadata: Optional< expectedMetadata: Optional<
@ -172,7 +184,13 @@ function createTestUtils({
}; };
} }
return {processDocFile, testMeta, testSlug, generateNavigation}; return {
processDocFile,
getProcessDocFileError,
testMeta,
testSlug,
generateNavigation,
};
} }
describe('simple site', () => { describe('simple site', () => {
@ -683,16 +701,21 @@ describe('simple site', () => {
it('docs with invalid id', async () => { it('docs with invalid id', async () => {
const {defaultTestUtils} = await loadSite(); const {defaultTestUtils} = await loadSite();
await expect(async () =>
defaultTestUtils.processDocFile( const error = await defaultTestUtils.getProcessDocFileError(
createFakeDocFile({ createFakeDocFile({
source: 'some/fake/path', source: 'some/fake/path',
frontMatter: { frontMatter: {
id: 'Hello/world', id: 'Hello/world',
}, },
}), }),
), );
).rejects.toThrowErrorMatchingInlineSnapshot(
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."`, `"Document id "Hello/world" cannot include slash."`,
); );
}); });

View File

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * 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 {validateDocFrontMatter} from '../frontMatter';
import type {DocFrontMatter} from '@docusaurus/plugin-content-docs'; import type {DocFrontMatter} from '@docusaurus/plugin-content-docs';
@ -57,7 +57,7 @@ function testField(params: {
} catch (err) { } catch (err) {
// eslint-disable-next-line jest/no-conditional-expect // eslint-disable-next-line jest/no-conditional-expect
expect((err as Error).message).toMatch( expect((err as Error).message).toMatch(
new RegExp(escapeStringRegexp(message)), new RegExp(escapeRegexp(message)),
); );
} }
}); });

View File

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

View File

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

View File

@ -316,7 +316,7 @@ async function doProcessDocMetadata({
}; };
} }
export function processDocMetadata(args: { export async function processDocMetadata(args: {
docFile: DocFile; docFile: DocFile;
versionMetadata: VersionMetadata; versionMetadata: VersionMetadata;
context: LoadContext; context: LoadContext;
@ -324,10 +324,12 @@ export function processDocMetadata(args: {
env: DocEnv; env: DocEnv;
}): Promise<DocMetadataBase> { }): Promise<DocMetadataBase> {
try { try {
return doProcessDocMetadata(args); return await doProcessDocMetadata(args);
} catch (err) { } catch (err) {
logger.error`Can't process doc metadata for doc at path path=${args.docFile.filePath} in version name=${args.versionMetadata.versionName}`; throw new Error(
throw err; `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 { 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 { return {
test: /\.mdx?$/i, test: /\.mdx?$/i,
include: contentDirs include: contentDirs,
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
.map(addTrailingPathSeparator),
use: [ use: [
getJSLoader({isServer}), getJSLoader({isServer}),
{ {
@ -375,6 +376,7 @@ export default async function pluginContentDocs(
}) => ({ }) => ({
image: frontMatter.image, image: frontMatter.image,
}), }),
markdownConfig: siteConfig.markdown,
}, },
}, },
{ {

View File

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

View File

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

View File

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

View File

@ -86,6 +86,9 @@ exports[`DefaultSidebarItemsGenerator generates simple flat sidebar 1`] = `
"type": "doc", "type": "doc",
}, },
{ {
"customProps": {
"custom": "prop",
},
"id": "doc1", "id": "doc1",
"label": "doc1 sidebar label", "label": "doc1 sidebar label",
"type": "doc", "type": "doc",

View File

@ -64,6 +64,7 @@ describe('DefaultSidebarItemsGenerator', () => {
sidebarPosition: 2, sidebarPosition: 2,
frontMatter: { frontMatter: {
sidebar_label: 'doc1 sidebar label', sidebar_label: 'doc1 sidebar label',
sidebar_custom_props: {custom: 'prop'},
}, },
title: '', title: '',
unversionedId: 'doc1', unversionedId: 'doc1',

View File

@ -138,7 +138,11 @@ Available doc IDs:
): WithPosition<SidebarItemDoc> { ): WithPosition<SidebarItemDoc> {
const { const {
sidebarPosition: position, sidebarPosition: position,
frontMatter: {sidebar_label: label, sidebar_class_name: className}, frontMatter: {
sidebar_label: label,
sidebar_class_name: className,
sidebar_custom_props: customProps,
},
} = getDoc(id); } = getDoc(id);
return { return {
type: 'doc', type: 'doc',
@ -149,6 +153,7 @@ Available doc IDs:
// sidebar // sidebar
...(label !== undefined && {label}), ...(label !== undefined && {label}),
...(className !== undefined && {className}), ...(className !== undefined && {className}),
...(customProps !== undefined && {customProps}),
}; };
} }
function createCategoryItem( function createCategoryItem(

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/plugin-content-pages", "name": "@docusaurus/plugin-content-pages",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "Pages plugin for Docusaurus.", "description": "Pages plugin for Docusaurus.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "src/plugin-content-pages.d.ts", "types": "src/plugin-content-pages.d.ts",
@ -18,18 +18,15 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.0.0-rc.1", "@docusaurus/core": "2.4.3",
"@docusaurus/mdx-loader": "2.0.0-rc.1", "@docusaurus/mdx-loader": "2.4.3",
"@docusaurus/types": "2.0.0-rc.1", "@docusaurus/types": "2.4.3",
"@docusaurus/utils": "2.0.0-rc.1", "@docusaurus/utils": "2.4.3",
"@docusaurus/utils-validation": "2.0.0-rc.1", "@docusaurus/utils-validation": "2.4.3",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"tslib": "^2.4.0", "tslib": "^2.4.0",
"webpack": "^5.73.0" "webpack": "^5.73.0"
}, },
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",
"react-dom": "^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`, `${docuHash(aliasedSource)}.json`,
); );
}, },
markdownConfig: siteConfig.markdown,
}, },
}, },
{ {

View File

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/plugin-debug", "name": "@docusaurus/plugin-debug",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "Debug plugin for Docusaurus.", "description": "Debug plugin for Docusaurus.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "src/plugin-debug.d.ts", "types": "src/plugin-debug.d.ts",
@ -20,16 +20,13 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.0.0-rc.1", "@docusaurus/core": "2.4.3",
"@docusaurus/types": "2.0.0-rc.1", "@docusaurus/types": "2.4.3",
"@docusaurus/utils": "2.0.0-rc.1", "@docusaurus/utils": "2.4.3",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"react-json-view": "^1.21.3", "react-json-view": "^1.21.3",
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",
"react-dom": "^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", "name": "@docusaurus/plugin-google-analytics",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "Global analytics (analytics.js) plugin for Docusaurus.", "description": "Global analytics (analytics.js) plugin for Docusaurus.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
@ -18,14 +18,11 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.0.0-rc.1", "@docusaurus/core": "2.4.3",
"@docusaurus/types": "2.0.0-rc.1", "@docusaurus/types": "2.4.3",
"@docusaurus/utils-validation": "2.0.0-rc.1", "@docusaurus/utils-validation": "2.4.3",
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",
"react-dom": "^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", "name": "@docusaurus/plugin-google-gtag",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "Global Site Tag (gtag.js) plugin for Docusaurus.", "description": "Global Site Tag (gtag.js) plugin for Docusaurus.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
@ -18,14 +18,11 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.0.0-rc.1", "@docusaurus/core": "2.4.3",
"@docusaurus/types": "2.0.0-rc.1", "@docusaurus/types": "2.4.3",
"@docusaurus/utils-validation": "2.0.0-rc.1", "@docusaurus/utils-validation": "2.4.3",
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",
"react-dom": "^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. * LICENSE file in the root directory of this source tree.
*/ */
import {Joi} from '@docusaurus/utils-validation'; import type {LoadContext, Plugin} from '@docusaurus/types';
import type {
LoadContext,
Plugin,
OptionValidationContext,
ThemeConfig,
ThemeConfigValidationContext,
} from '@docusaurus/types';
import type {PluginOptions, Options} from './options'; 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( export default function pluginGoogleGtag(
context: LoadContext, context: LoadContext,
options: PluginOptions, options: PluginOptions,
): Plugin { ): Plugin {
const {anonymizeIP, trackingID} = options;
const isProd = process.env.NODE_ENV === 'production'; const isProd = process.env.NODE_ENV === 'production';
const firstTrackingId = options.trackingID[0];
return { return {
name: 'docusaurus-plugin-google-gtag', name: 'docusaurus-plugin-google-gtag',
@ -60,7 +75,11 @@ export default function pluginGoogleGtag(
tagName: 'script', tagName: 'script',
attributes: { attributes: {
async: true, 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 || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} function gtag(){dataLayer.push(arguments);}
gtag('js', new Date()); gtag('js', new Date());
gtag('config', '${trackingID}', { ${ ${createConfigSnippets(options)};
anonymizeIP ? "'anonymize_ip': true" : '' `,
} });`,
}, },
], ],
}; };
@ -79,27 +97,6 @@ export default function pluginGoogleGtag(
}; };
} }
const pluginOptionsSchema = Joi.object<PluginOptions>({ export {validateThemeConfig, validateOptions} from './options';
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 type {PluginOptions, Options}; export type {PluginOptions, Options};

View File

@ -4,10 +4,58 @@
* This source code is licensed under the MIT license found in the * This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree. * 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 = { 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; 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", "rootDir": "src",
"outDir": "lib" "outDir": "lib"
}, },
"include": ["src/gtag.ts", "src/options.ts", "src/*.d.ts"], "include": ["src/gtag.ts", "src/*.d.ts"],
"exclude": ["**/__tests__/**"] "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.3",
"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.3",
"@docusaurus/types": "2.4.3",
"@docusaurus/utils-validation": "2.4.3",
"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", "name": "@docusaurus/plugin-ideal-image",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "Docusaurus Plugin to generate an almost ideal image (responsive, lazy-loading, and low quality placeholder).", "description": "Docusaurus Plugin to generate an almost ideal image (responsive, lazy-loading, and low quality placeholder).",
"main": "lib/index.js", "main": "lib/index.js",
"types": "src/plugin-ideal-image.d.ts", "types": "src/plugin-ideal-image.d.ts",
@ -20,12 +20,12 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.0.0-rc.1", "@docusaurus/core": "2.4.3",
"@docusaurus/lqip-loader": "2.0.0-rc.1", "@docusaurus/lqip-loader": "2.4.3",
"@docusaurus/responsive-loader": "^1.7.0", "@docusaurus/responsive-loader": "^1.7.0",
"@docusaurus/theme-translations": "2.0.0-rc.1", "@docusaurus/theme-translations": "2.4.3",
"@docusaurus/types": "2.0.0-rc.1", "@docusaurus/types": "2.4.3",
"@docusaurus/utils-validation": "2.0.0-rc.1", "@docusaurus/utils-validation": "2.4.3",
"@endiliey/react-ideal-image": "^0.0.11", "@endiliey/react-ideal-image": "^0.0.11",
"react-waypoint": "^10.3.0", "react-waypoint": "^10.3.0",
"sharp": "^0.30.7", "sharp": "^0.30.7",
@ -33,8 +33,7 @@
"webpack": "^5.73.0" "webpack": "^5.73.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-rc.1", "@docusaurus/module-type-aliases": "2.4.3",
"@docusaurus/types": "2.0.0-beta.21",
"fs-extra": "^10.1.0" "fs-extra": "^10.1.0"
}, },
"peerDependencies": { "peerDependencies": {

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/plugin-pwa", "name": "@docusaurus/plugin-pwa",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "Docusaurus Plugin to add PWA support.", "description": "Docusaurus Plugin to add PWA support.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "src/plugin-pwa.d.ts", "types": "src/plugin-pwa.d.ts",
@ -22,12 +22,12 @@
"dependencies": { "dependencies": {
"@babel/core": "^7.18.6", "@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.6", "@babel/preset-env": "^7.18.6",
"@docusaurus/core": "2.0.0-rc.1", "@docusaurus/core": "2.4.3",
"@docusaurus/theme-common": "2.0.0-rc.1", "@docusaurus/theme-common": "2.4.3",
"@docusaurus/theme-translations": "2.0.0-rc.1", "@docusaurus/theme-translations": "2.4.3",
"@docusaurus/types": "2.0.0-rc.1", "@docusaurus/types": "2.4.3",
"@docusaurus/utils": "2.0.0-rc.1", "@docusaurus/utils": "2.4.3",
"@docusaurus/utils-validation": "2.0.0-rc.1", "@docusaurus/utils-validation": "2.4.3",
"babel-loader": "^8.2.5", "babel-loader": "^8.2.5",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"core-js": "^3.23.3", "core-js": "^3.23.3",
@ -40,7 +40,7 @@
"workbox-window": "^6.5.3" "workbox-window": "^6.5.3"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-rc.1", "@docusaurus/module-type-aliases": "2.4.3",
"fs-extra": "^10.1.0" "fs-extra": "^10.1.0"
}, },
"peerDependencies": { "peerDependencies": {

View File

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

View File

@ -158,7 +158,10 @@ describe('createSitemap', () => {
meta: { meta: {
// @ts-expect-error: bad lib def // @ts-expect-error: bad lib def
toComponent: () => [ 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 {HelmetServerState} from 'react-helmet-async';
import type {PluginOptions} from './options'; 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( export default async function createSitemap(
siteConfig: DocusaurusConfig, siteConfig: DocusaurusConfig,
routesPaths: string[], routesPaths: string[],
@ -27,18 +61,15 @@ export default async function createSitemap(
const ignoreMatcher = createMatcher(ignorePatterns); const ignoreMatcher = createMatcher(ignorePatterns);
const includedRoutes = routesPaths.filter((route) => { function isRouteExcluded(route: string) {
if (route.endsWith('404.html') || ignoreMatcher(route)) { return (
return false; route.endsWith('404.html') ||
} ignoreMatcher(route) ||
// https://github.com/staylor/react-helmet-async/pull/167 isNoIndexMetaRoute({head, route})
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',
); );
}); }
const includedRoutes = routesPaths.filter((route) => !isRouteExcluded(route));
if (includedRoutes.length === 0) { if (includedRoutes.length === 0) {
return null; return null;

View File

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/preset-classic", "name": "@docusaurus/preset-classic",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "Classic preset for Docusaurus.", "description": "Classic preset for Docusaurus.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
@ -18,18 +18,19 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.0.0-rc.1", "@docusaurus/core": "2.4.3",
"@docusaurus/plugin-content-blog": "2.0.0-rc.1", "@docusaurus/plugin-content-blog": "2.4.3",
"@docusaurus/plugin-content-docs": "2.0.0-rc.1", "@docusaurus/plugin-content-docs": "2.4.3",
"@docusaurus/plugin-content-pages": "2.0.0-rc.1", "@docusaurus/plugin-content-pages": "2.4.3",
"@docusaurus/plugin-debug": "2.0.0-rc.1", "@docusaurus/plugin-debug": "2.4.3",
"@docusaurus/plugin-google-analytics": "2.0.0-rc.1", "@docusaurus/plugin-google-analytics": "2.4.3",
"@docusaurus/plugin-google-gtag": "2.0.0-rc.1", "@docusaurus/plugin-google-gtag": "2.4.3",
"@docusaurus/plugin-sitemap": "2.0.0-rc.1", "@docusaurus/plugin-google-tag-manager": "2.4.3",
"@docusaurus/theme-classic": "2.0.0-rc.1", "@docusaurus/plugin-sitemap": "2.4.3",
"@docusaurus/theme-common": "2.0.0-rc.1", "@docusaurus/theme-classic": "2.4.3",
"@docusaurus/theme-search-algolia": "2.0.0-rc.1", "@docusaurus/theme-common": "2.4.3",
"@docusaurus/types": "2.0.0-rc.1" "@docusaurus/theme-search-algolia": "2.4.3",
"@docusaurus/types": "2.4.3"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",

View File

@ -40,6 +40,7 @@ export default function preset(
theme, theme,
googleAnalytics, googleAnalytics,
gtag, gtag,
googleTagManager,
...rest ...rest
} = opts; } = opts;
@ -80,6 +81,14 @@ export default function preset(
if (gtag) { if (gtag) {
plugins.push(makePluginConfig('@docusaurus/plugin-google-gtag', gtag)); plugins.push(makePluginConfig('@docusaurus/plugin-google-gtag', gtag));
} }
if (googleTagManager) {
plugins.push(
makePluginConfig(
'@docusaurus/plugin-google-tag-manager',
googleTagManager,
),
);
}
if (isProd && sitemap !== false) { if (isProd && sitemap !== false) {
plugins.push(makePluginConfig('@docusaurus/plugin-sitemap', sitemap)); plugins.push(makePluginConfig('@docusaurus/plugin-sitemap', sitemap));
} }
@ -87,7 +96,7 @@ export default function preset(
throw new Error( throw new Error(
`Unrecognized keys ${Object.keys(rest).join( `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 SitemapPluginOptions} from '@docusaurus/plugin-sitemap';
import type {Options as GAPluginOptions} from '@docusaurus/plugin-google-analytics'; import type {Options as GAPluginOptions} from '@docusaurus/plugin-google-analytics';
import type {Options as GtagPluginOptions} from '@docusaurus/plugin-google-gtag'; 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 {Options as ThemeOptions} from '@docusaurus/theme-classic';
import type {ThemeConfig as BaseThemeConfig} from '@docusaurus/types'; import type {ThemeConfig as BaseThemeConfig} from '@docusaurus/types';
import type {UserThemeConfig as ClassicThemeConfig} from '@docusaurus/theme-common'; import type {UserThemeConfig as ClassicThemeConfig} from '@docusaurus/theme-common';
@ -42,6 +43,7 @@ export type Options = {
* is present. * is present.
*/ */
gtag?: GtagPluginOptions; gtag?: GtagPluginOptions;
googleTagManager?: GTMPluginOptions;
}; };
export type ThemeConfig = BaseThemeConfig & export type ThemeConfig = BaseThemeConfig &

View File

@ -63,3 +63,30 @@ module.exports = {
| Property | Type | Default | Description | | 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. | | `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", "name": "@docusaurus/remark-plugin-npm2yarn",
"version": "2.0.0-rc.1", "version": "2.4.3",
"description": "Remark plugin for converting npm commands to Yarn commands as tabs.", "description": "Remark plugin for converting npm commands to Yarn commands as tabs.",
"main": "lib/index.js", "main": "lib/index.js",
"publishConfig": { "publishConfig": {
@ -17,8 +17,8 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"npm-to-yarn": "^1.0.1", "npm-to-yarn": "^2.0.0",
"tslib": "^2.4.0", "tslib": "^2.4.1",
"unist-util-visit": "^2.0.3" "unist-util-visit": "^2.0.3"
}, },
"devDependencies": { "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
```

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