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)
This commit is contained in:
Sébastien Lorber 2023-03-24 09:23:58 +01:00 committed by GitHub
parent 985a64ad22
commit 4fb67ef11b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
122 changed files with 1732 additions and 494 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.3.1", "version": "2.4.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "npx --package netlify-cli netlify dev" "start": "npx --package netlify-cli netlify dev"

View file

@ -1,5 +1,5 @@
{ {
"version": "2.3.1", "version": "2.4.0",
"npmClient": "yarn", "npmClient": "yarn",
"useWorkspaces": true, "useWorkspaces": true,
"changelog": { "changelog": {

View file

@ -1,6 +1,6 @@
{ {
"name": "create-docusaurus", "name": "create-docusaurus",
"version": "2.3.1", "version": "2.4.0",
"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.3.1", "@docusaurus/logger": "2.4.0",
"@docusaurus/utils": "2.3.1", "@docusaurus/utils": "2.4.0",
"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

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

View file

@ -71,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.3.1", "version": "2.4.0",
"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.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/preset-classic": "2.3.1", "@docusaurus/preset-classic": "2.4.0",
"@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.3.1" "@docusaurus/module-type-aliases": "2.4.0"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [

View file

@ -67,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.3.1", "version": "2.4.0",
"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.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/preset-classic": "2.3.1", "@docusaurus/preset-classic": "2.4.0",
"@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

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/cssnano-preset", "name": "@docusaurus/cssnano-preset",
"version": "2.3.1", "version": "2.4.0",
"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.3.1", "version": "2.4.0",
"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.3.1", "version": "2.4.0",
"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.3.1", "@docusaurus/logger": "2.4.0",
"@docusaurus/utils": "2.3.1", "@docusaurus/utils": "2.4.0",
"@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.3.1", "@docusaurus/types": "2.4.0",
"@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

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/migrate", "name": "@docusaurus/migrate",
"version": "2.3.1", "version": "2.4.0",
"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.3.1", "@docusaurus/logger": "2.4.0",
"@docusaurus/utils": "2.3.1", "@docusaurus/utils": "2.4.0",
"@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

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/module-type-aliases", "name": "@docusaurus/module-type-aliases",
"version": "2.3.1", "version": "2.4.0",
"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.3.1", "@docusaurus/types": "2.4.0",
"@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.3.1", "version": "2.4.0",
"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.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/logger": "2.3.1", "@docusaurus/logger": "2.4.0",
"@docusaurus/utils": "2.3.1", "@docusaurus/utils": "2.4.0",
"@docusaurus/utils-common": "2.3.1", "@docusaurus/utils-common": "2.4.0",
"@docusaurus/utils-validation": "2.3.1", "@docusaurus/utils-validation": "2.4.0",
"eta": "^2.0.0", "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.3.1" "@docusaurus/types": "2.4.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/plugin-content-blog", "name": "@docusaurus/plugin-content-blog",
"version": "2.3.1", "version": "2.4.0",
"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.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/logger": "2.3.1", "@docusaurus/logger": "2.4.0",
"@docusaurus/mdx-loader": "2.3.1", "@docusaurus/mdx-loader": "2.4.0",
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"@docusaurus/utils": "2.3.1", "@docusaurus/utils": "2.4.0",
"@docusaurus/utils-common": "2.3.1", "@docusaurus/utils-common": "2.4.0",
"@docusaurus/utils-validation": "2.3.1", "@docusaurus/utils-validation": "2.4.0",
"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",

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/plugin-content-docs", "name": "@docusaurus/plugin-content-docs",
"version": "2.3.1", "version": "2.4.0",
"description": "Docs plugin for Docusaurus.", "description": "Docs plugin for Docusaurus.",
"main": "lib/index.js", "main": "lib/index.js",
"sideEffects": false, "sideEffects": false,
@ -35,13 +35,13 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/logger": "2.3.1", "@docusaurus/logger": "2.4.0",
"@docusaurus/mdx-loader": "2.3.1", "@docusaurus/mdx-loader": "2.4.0",
"@docusaurus/module-type-aliases": "2.3.1", "@docusaurus/module-type-aliases": "2.4.0",
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"@docusaurus/utils": "2.3.1", "@docusaurus/utils": "2.4.0",
"@docusaurus/utils-validation": "2.3.1", "@docusaurus/utils-validation": "2.4.0",
"@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",

View file

@ -45,6 +45,7 @@ export type SidebarItemLink = SidebarItemBase & {
href: string; href: string;
label: string; label: string;
autoAddBaseUrl?: boolean; autoAddBaseUrl?: boolean;
description?: string;
}; };
export type SidebarItemAutogenerated = SidebarItemBase & { export type SidebarItemAutogenerated = SidebarItemBase & {
@ -57,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

@ -63,6 +63,9 @@ const sidebarItemLinkSchema = sidebarItemBaseSchema.append<SidebarItemLink>({
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>()
@ -116,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

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/plugin-content-pages", "name": "@docusaurus/plugin-content-pages",
"version": "2.3.1", "version": "2.4.0",
"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,11 +18,11 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/mdx-loader": "2.3.1", "@docusaurus/mdx-loader": "2.4.0",
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"@docusaurus/utils": "2.3.1", "@docusaurus/utils": "2.4.0",
"@docusaurus/utils-validation": "2.3.1", "@docusaurus/utils-validation": "2.4.0",
"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"

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/plugin-debug", "name": "@docusaurus/plugin-debug",
"version": "2.3.1", "version": "2.4.0",
"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,9 +20,9 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"@docusaurus/utils": "2.3.1", "@docusaurus/utils": "2.4.0",
"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"

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/plugin-google-analytics", "name": "@docusaurus/plugin-google-analytics",
"version": "2.3.1", "version": "2.4.0",
"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,9 +18,9 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"@docusaurus/utils-validation": "2.3.1", "@docusaurus/utils-validation": "2.4.0",
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
"peerDependencies": { "peerDependencies": {

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/plugin-google-gtag", "name": "@docusaurus/plugin-google-gtag",
"version": "2.3.1", "version": "2.4.0",
"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,9 +18,9 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"@docusaurus/utils-validation": "2.3.1", "@docusaurus/utils-validation": "2.4.0",
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
"peerDependencies": { "peerDependencies": {

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

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/plugin-google-tag-manager", "name": "@docusaurus/plugin-google-tag-manager",
"version": "2.3.1", "version": "2.4.0",
"description": "Google Tag Manager (gtm.js) plugin for Docusaurus.", "description": "Google Tag Manager (gtm.js) plugin for Docusaurus.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
@ -18,9 +18,9 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"@docusaurus/utils-validation": "2.3.1", "@docusaurus/utils-validation": "2.4.0",
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
"peerDependencies": { "peerDependencies": {

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/plugin-ideal-image", "name": "@docusaurus/plugin-ideal-image",
"version": "2.3.1", "version": "2.4.0",
"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.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/lqip-loader": "2.3.1", "@docusaurus/lqip-loader": "2.4.0",
"@docusaurus/responsive-loader": "^1.7.0", "@docusaurus/responsive-loader": "^1.7.0",
"@docusaurus/theme-translations": "2.3.1", "@docusaurus/theme-translations": "2.4.0",
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"@docusaurus/utils-validation": "2.3.1", "@docusaurus/utils-validation": "2.4.0",
"@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,7 +33,7 @@
"webpack": "^5.73.0" "webpack": "^5.73.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "2.3.1", "@docusaurus/module-type-aliases": "2.4.0",
"fs-extra": "^10.1.0" "fs-extra": "^10.1.0"
}, },
"peerDependencies": { "peerDependencies": {

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/plugin-pwa", "name": "@docusaurus/plugin-pwa",
"version": "2.3.1", "version": "2.4.0",
"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.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/theme-common": "2.3.1", "@docusaurus/theme-common": "2.4.0",
"@docusaurus/theme-translations": "2.3.1", "@docusaurus/theme-translations": "2.4.0",
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"@docusaurus/utils": "2.3.1", "@docusaurus/utils": "2.4.0",
"@docusaurus/utils-validation": "2.3.1", "@docusaurus/utils-validation": "2.4.0",
"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.3.1", "@docusaurus/module-type-aliases": "2.4.0",
"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.3.1", "version": "2.4.0",
"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,12 +18,12 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/logger": "2.3.1", "@docusaurus/logger": "2.4.0",
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"@docusaurus/utils": "2.3.1", "@docusaurus/utils": "2.4.0",
"@docusaurus/utils-common": "2.3.1", "@docusaurus/utils-common": "2.4.0",
"@docusaurus/utils-validation": "2.3.1", "@docusaurus/utils-validation": "2.4.0",
"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"

View file

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

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.3.1", "version": "2.4.0",
"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
```

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1243,6 +1243,7 @@ declare module '@theme/ColorModeToggle' {
export interface Props { export interface Props {
readonly className?: string; readonly className?: string;
readonly buttonClassName?: string;
readonly value: ColorMode; readonly value: ColorMode;
/** /**
* The parameter represents the "to-be" value. For example, if currently in * The parameter represents the "to-be" value. For example, if currently in

View file

@ -59,6 +59,7 @@
display: flex; display: flex;
column-gap: 0.2rem; column-gap: 0.2rem;
position: absolute; position: absolute;
/* rtl:ignore */
right: calc(var(--ifm-pre-padding) / 2); right: calc(var(--ifm-pre-padding) / 2);
top: calc(var(--ifm-pre-padding) / 2); top: calc(var(--ifm-pre-padding) / 2);
} }
@ -72,7 +73,7 @@
border-radius: var(--ifm-global-radius); border-radius: var(--ifm-global-radius);
padding: 0.4rem; padding: 0.4rem;
line-height: 0; line-height: 0;
transition: opacity 200ms ease-in-out; transition: opacity var(--ifm-transition-fast) ease-in-out;
opacity: 0; opacity: 0;
} }

View file

@ -24,7 +24,7 @@
opacity: inherit; opacity: inherit;
width: inherit; width: inherit;
height: inherit; height: inherit;
transition: all 0.15s ease; transition: all var(--ifm-transition-fast) ease;
} }
.copyButtonSuccessIcon { .copyButtonSuccessIcon {

View file

@ -15,7 +15,12 @@ import type {Props} from '@theme/ColorModeToggle';
import styles from './styles.module.css'; import styles from './styles.module.css';
function ColorModeToggle({className, value, onChange}: Props): JSX.Element { function ColorModeToggle({
className,
buttonClassName,
value,
onChange,
}: Props): JSX.Element {
const isBrowser = useIsBrowser(); const isBrowser = useIsBrowser();
const title = translate( const title = translate(
@ -47,6 +52,7 @@ function ColorModeToggle({className, value, onChange}: Props): JSX.Element {
'clean-btn', 'clean-btn',
styles.toggleButton, styles.toggleButton,
!isBrowser && styles.toggleButtonDisabled, !isBrowser && styles.toggleButtonDisabled,
buttonClassName,
)} )}
type="button" type="button"
onClick={() => onChange(value === 'dark' ? 'light' : 'dark')} onClick={() => onChange(value === 'dark' ? 'light' : 'dark')}

View file

@ -82,7 +82,9 @@ function CardCategory({
href={href} href={href}
icon="🗃️" icon="🗃️"
title={item.label} title={item.label}
description={translate( description={
item.description ??
translate(
{ {
message: '{count} items', message: '{count} items',
id: 'theme.docs.DocCard.categoryDescription', id: 'theme.docs.DocCard.categoryDescription',
@ -90,7 +92,8 @@ function CardCategory({
'The default description for a category card in the generated index about how many items this category includes', 'The default description for a category card in the generated index about how many items this category includes',
}, },
{count: item.items.length}, {count: item.items.length},
)} )
}
/> />
); );
} }
@ -103,7 +106,7 @@ function CardLink({item}: {item: PropSidebarItemLink}): JSX.Element {
href={item.href} href={item.href}
icon={icon} icon={icon}
title={item.label} title={item.label}
description={doc?.description} description={item.description ?? doc?.description}
/> />
); );
} }

View file

@ -7,6 +7,10 @@
import React from 'react'; import React from 'react';
import Translate from '@docusaurus/Translate'; import Translate from '@docusaurus/Translate';
import {
ErrorBoundaryError,
ErrorBoundaryTryAgainButton,
} from '@docusaurus/theme-common';
import type {Props} from '@theme/Error'; import type {Props} from '@theme/Error';
export default function ErrorPageContent({ export default function ErrorPageContent({
@ -24,15 +28,15 @@ export default function ErrorPageContent({
This page crashed. This page crashed.
</Translate> </Translate>
</h1> </h1>
<p>{error.message}</p> <div className="margin-vert--lg">
<div> <ErrorBoundaryTryAgainButton
<button type="button" onClick={tryAgain}> onClick={tryAgain}
<Translate className="button button--primary shadow--lw"
id="theme.ErrorPageContent.tryAgain" />
description="The label of the button to try again when the page crashed"> </div>
Try again <hr />
</Translate> <div className="margin-vert--md">
</button> <ErrorBoundaryError error={error} />
</div> </div>
</div> </div>
</div> </div>

View file

@ -9,10 +9,12 @@ import React from 'react';
import {useColorMode, useThemeConfig} from '@docusaurus/theme-common'; import {useColorMode, useThemeConfig} from '@docusaurus/theme-common';
import ColorModeToggle from '@theme/ColorModeToggle'; import ColorModeToggle from '@theme/ColorModeToggle';
import type {Props} from '@theme/Navbar/ColorModeToggle'; import type {Props} from '@theme/Navbar/ColorModeToggle';
import styles from './styles.module.css';
export default function NavbarColorModeToggle({ export default function NavbarColorModeToggle({
className, className,
}: Props): JSX.Element | null { }: Props): JSX.Element | null {
const navbarStyle = useThemeConfig().navbar.style;
const disabled = useThemeConfig().colorMode.disableSwitch; const disabled = useThemeConfig().colorMode.disableSwitch;
const {colorMode, setColorMode} = useColorMode(); const {colorMode, setColorMode} = useColorMode();
@ -23,6 +25,9 @@ export default function NavbarColorModeToggle({
return ( return (
<ColorModeToggle <ColorModeToggle
className={className} className={className}
buttonClassName={
navbarStyle === 'dark' ? styles.darkNavbarColorModeToggle : undefined
}
value={colorMode} value={colorMode}
onChange={setColorMode} onChange={setColorMode}
/> />

View file

@ -0,0 +1,10 @@
/**
* 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.
*/
.darkNavbarColorModeToggle:hover {
background: var(--ifm-color-gray-800);
}

View file

@ -6,7 +6,7 @@
*/ */
import React, {type ReactNode} from 'react'; import React, {type ReactNode} from 'react';
import {useThemeConfig} from '@docusaurus/theme-common'; import {useThemeConfig, ErrorCauseBoundary} from '@docusaurus/theme-common';
import { import {
splitNavbarItems, splitNavbarItems,
useNavbarMobileSidebar, useNavbarMobileSidebar,
@ -29,7 +29,18 @@ function NavbarItems({items}: {items: NavbarItemConfig[]}): JSX.Element {
return ( return (
<> <>
{items.map((item, i) => ( {items.map((item, i) => (
<NavbarItem {...item} key={i} /> <ErrorCauseBoundary
key={i}
onError={(error) =>
new Error(
`A theme navbar item failed to render.
Please double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config:
${JSON.stringify(item, null, 2)}`,
{cause: error},
)
}>
<NavbarItem {...item} />
</ErrorCauseBoundary>
))} ))}
</> </>
); );

View file

@ -54,7 +54,9 @@ function DropdownNavbarItemDesktop({
const [showDropdown, setShowDropdown] = useState(false); const [showDropdown, setShowDropdown] = useState(false);
useEffect(() => { useEffect(() => {
const handleClickOutside = (event: MouseEvent | TouchEvent) => { const handleClickOutside = (
event: MouseEvent | TouchEvent | FocusEvent,
) => {
if ( if (
!dropdownRef.current || !dropdownRef.current ||
dropdownRef.current.contains(event.target as Node) dropdownRef.current.contains(event.target as Node)
@ -66,10 +68,12 @@ function DropdownNavbarItemDesktop({
document.addEventListener('mousedown', handleClickOutside); document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('touchstart', handleClickOutside); document.addEventListener('touchstart', handleClickOutside);
document.addEventListener('focusin', handleClickOutside);
return () => { return () => {
document.removeEventListener('mousedown', handleClickOutside); document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('touchstart', handleClickOutside); document.removeEventListener('touchstart', handleClickOutside);
document.removeEventListener('focusin', handleClickOutside);
}; };
}, [dropdownRef]); }, [dropdownRef]);
@ -100,22 +104,6 @@ function DropdownNavbarItemDesktop({
{items.map((childItemProps, i) => ( {items.map((childItemProps, i) => (
<NavbarItem <NavbarItem
isDropdownItem isDropdownItem
onKeyDown={(e) => {
if (i === items.length - 1 && e.key === 'Tab') {
e.preventDefault();
setShowDropdown(false);
const nextNavbarItem = dropdownRef.current!.nextElementSibling;
if (nextNavbarItem) {
const targetItem =
nextNavbarItem instanceof HTMLAnchorElement
? nextNavbarItem
: // Next item is another dropdown; focus on the inner
// anchor element instead so there's outline
nextNavbarItem.querySelector('a')!;
targetItem.focus();
}
}
}}
activeClassName="dropdown__link--active" activeClassName="dropdown__link--active"
{...childItemProps} {...childItemProps}
key={i} key={i}

View file

@ -132,12 +132,9 @@ describe('Tabs', () => {
renderer.create( renderer.create(
<TestProviders> <TestProviders>
<Tabs <Tabs
// @ts-expect-error: for an edge-case that we didn't write types for
values={tabs.map((t, idx) => ({label: t, value: idx}))} values={tabs.map((t, idx) => ({label: t, value: idx}))}
// @ts-expect-error: for an edge-case that we didn't write types for
defaultValue={0}> defaultValue={0}>
{tabs.map((t, idx) => ( {tabs.map((t, idx) => (
// @ts-expect-error: for an edge-case that we didn't write types for
<TabItem key={idx} value={idx}> <TabItem key={idx} value={idx}>
{t} {t}
</TabItem> </TabItem>
@ -199,4 +196,19 @@ describe('Tabs', () => {
); );
}).not.toThrow(); }).not.toThrow();
}); });
it('allows a tab to be falsy', () => {
expect(() => {
renderer.create(
<TestProviders>
<Tabs>
<TabItem value="val1">Val1</TabItem>
{null}
{false}
{undefined}
</Tabs>
</TestProviders>,
);
}).not.toThrow();
});
}); });

View file

@ -5,11 +5,12 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import React, {cloneElement} from 'react'; import React, {cloneElement, type ReactElement} from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import { import {
useScrollPositionBlocker, useScrollPositionBlocker,
useTabs, useTabs,
type TabItemProps,
} from '@docusaurus/theme-common/internal'; } from '@docusaurus/theme-common/internal';
import useIsBrowser from '@docusaurus/useIsBrowser'; import useIsBrowser from '@docusaurus/useIsBrowser';
import type {Props} from '@theme/Tabs'; import type {Props} from '@theme/Tabs';
@ -109,10 +110,11 @@ function TabContent({
children, children,
selectedValue, selectedValue,
}: Props & ReturnType<typeof useTabs>) { }: Props & ReturnType<typeof useTabs>) {
// eslint-disable-next-line no-param-reassign const childTabs = (Array.isArray(children) ? children : [children]).filter(
children = Array.isArray(children) ? children : [children]; Boolean,
) as ReactElement<TabItemProps>[];
if (lazy) { if (lazy) {
const selectedTabItem = children.find( const selectedTabItem = childTabs.find(
(tabItem) => tabItem.props.value === selectedValue, (tabItem) => tabItem.props.value === selectedValue,
); );
if (!selectedTabItem) { if (!selectedTabItem) {
@ -123,7 +125,7 @@ function TabContent({
} }
return ( return (
<div className="margin-top--md"> <div className="margin-top--md">
{children.map((tabItem, i) => {childTabs.map((tabItem, i) =>
cloneElement(tabItem, { cloneElement(tabItem, {
key: i, key: i,
hidden: tabItem.props.value !== selectedValue, hidden: tabItem.props.value !== selectedValue,

View file

@ -45,7 +45,20 @@ function getNavbarTranslationFile(navbar: Navbar): TranslationFileContent {
? {title: {message: navbar.title, description: 'The title in the navbar'}} ? {title: {message: navbar.title, description: 'The title in the navbar'}}
: {}; : {};
return mergeTranslations([titleTranslations, navbarItemsTranslations]); const logoAlt: TranslationFileContent = navbar.logo?.alt
? {
'logo.alt': {
message: navbar.logo.alt,
description: 'The alt text of navbar logo',
},
}
: {};
return mergeTranslations([
titleTranslations,
logoAlt,
navbarItemsTranslations,
]);
} }
function translateNavbar( function translateNavbar(
navbar: Navbar, navbar: Navbar,
@ -54,9 +67,18 @@ function translateNavbar(
if (!navbarTranslations) { if (!navbarTranslations) {
return navbar; return navbar;
} }
const logo = navbar.logo
? {
...navbar.logo,
alt: navbarTranslations[`logo.alt`]?.message ?? navbar.logo?.alt,
}
: undefined;
return { return {
...navbar, ...navbar,
title: navbarTranslations.title?.message ?? navbar.title, title: navbarTranslations.title?.message ?? navbar.title,
logo,
// TODO handle properly all the navbar item types here! // TODO handle properly all the navbar item types here!
items: navbar.items.map((item) => { items: navbar.items.map((item) => {
const subItems = item.items?.map((subItem) => ({ const subItems = item.items?.map((subItem) => ({
@ -119,7 +141,21 @@ function getFooterTranslationFile(footer: Footer): TranslationFileContent {
} }
: {}; : {};
return mergeTranslations([footerLinkTitles, footerLinkLabels, copyright]); const logoAlt: TranslationFileContent = footer.logo?.alt
? {
'logo.alt': {
message: footer.logo.alt,
description: 'The alt text of footer logo',
},
}
: {};
return mergeTranslations([
footerLinkTitles,
footerLinkLabels,
copyright,
logoAlt,
]);
} }
function translateFooter( function translateFooter(
footer: Footer, footer: Footer,
@ -149,10 +185,18 @@ function translateFooter(
const copyright = footerTranslations.copyright?.message ?? footer.copyright; const copyright = footerTranslations.copyright?.message ?? footer.copyright;
const logo = footer.logo
? {
...footer.logo,
alt: footerTranslations[`logo.alt`]?.message ?? footer.logo?.alt,
}
: undefined;
return { return {
...footer, ...footer,
links, links,
copyright, copyright,
logo,
}; };
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/theme-common", "name": "@docusaurus/theme-common",
"version": "2.3.1", "version": "2.4.0",
"description": "Common code for Docusaurus themes.", "description": "Common code for Docusaurus themes.",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
@ -30,12 +30,13 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/mdx-loader": "2.3.1", "@docusaurus/mdx-loader": "2.4.0",
"@docusaurus/module-type-aliases": "2.3.1", "@docusaurus/module-type-aliases": "2.4.0",
"@docusaurus/plugin-content-blog": "2.3.1", "@docusaurus/plugin-content-blog": "2.4.0",
"@docusaurus/plugin-content-docs": "2.3.1", "@docusaurus/plugin-content-docs": "2.4.0",
"@docusaurus/plugin-content-pages": "2.3.1", "@docusaurus/plugin-content-pages": "2.4.0",
"@docusaurus/utils": "2.3.1", "@docusaurus/utils": "2.4.0",
"@docusaurus/utils-common": "2.4.0",
"@types/history": "^4.7.11", "@types/history": "^4.7.11",
"@types/react": "*", "@types/react": "*",
"@types/react-router-config": "*", "@types/react-router-config": "*",
@ -47,8 +48,8 @@
"utility-types": "^3.10.0" "utility-types": "^3.10.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/core": "2.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"lodash": "^4.17.21" "lodash": "^4.17.21"
}, },

View file

@ -65,6 +65,10 @@ function applyCollapsedStyle(el: HTMLElement, collapsed: boolean) {
el.style.height = collapsedStyles.height; el.style.height = collapsedStyles.height;
} }
function userPrefersReducedMotion(): boolean {
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
/* /*
Lex111: Dynamic transition duration is used in Material design, this technique Lex111: Dynamic transition duration is used in Material design, this technique
is good for a large number of items. is good for a large number of items.
@ -72,6 +76,9 @@ https://material.io/archive/guidelines/motion/duration-easing.html#duration-easi
https://github.com/mui-org/material-ui/blob/e724d98eba018e55e1a684236a2037e24bcf050c/packages/material-ui/src/styles/createTransitions.js#L40-L43 https://github.com/mui-org/material-ui/blob/e724d98eba018e55e1a684236a2037e24bcf050c/packages/material-ui/src/styles/createTransitions.js#L40-L43
*/ */
function getAutoHeightDuration(height: number) { function getAutoHeightDuration(height: number) {
if (userPrefersReducedMotion()) {
return 0;
}
const constant = height / 36; const constant = height / 36;
return Math.round((4 + 15 * constant ** 0.25 + constant / 5) * 10); return Math.round((4 + 15 * constant ** 0.25 + constant / 5) * 10);
} }

View file

@ -31,8 +31,11 @@ function hasParent(node: HTMLElement | null, parent: HTMLElement): boolean {
} }
export type DetailsProps = { export type DetailsProps = {
/** Summary is provided as props, including the wrapping `<summary>` tag */ /**
summary?: ReactElement; * Summary is provided as props, optionally including the wrapping
* `<summary>` tag
*/
summary?: ReactElement | string;
} & ComponentProps<'details'>; } & ComponentProps<'details'>;
/** /**
@ -54,6 +57,12 @@ export function Details({
// only after animation completes, otherwise close animations won't work // only after animation completes, otherwise close animations won't work
const [open, setOpen] = useState(props.open); const [open, setOpen] = useState(props.open);
const summaryElement = React.isValidElement(summary) ? (
summary
) : (
<summary>{summary ?? 'Details'}</summary>
);
return ( return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
<details <details
@ -91,8 +100,7 @@ export function Details({
// setOpen(false); // setOpen(false);
} }
}}> }}>
{/* eslint-disable-next-line @docusaurus/no-untranslated-text */} {summaryElement}
{summary ?? <summary>Details</summary>}
<Collapsible <Collapsible
lazy={false} // Content might matter for SEO in this case lazy={false} // Content might matter for SEO in this case

View file

@ -5,32 +5,24 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {useCallback, useEffect, useState} from 'react'; import {useCallback} from 'react';
import {useHistory} from '@docusaurus/router';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import {useQueryString} from '../utils/historyUtils';
import type {ThemeConfig as AlgoliaThemeConfig} from '@docusaurus/theme-search-algolia'; import type {ThemeConfig as AlgoliaThemeConfig} from '@docusaurus/theme-search-algolia';
const SEARCH_PARAM_QUERY = 'q'; const SEARCH_PARAM_QUERY = 'q';
/** Some utility functions around search queries. */ /**
export function useSearchPage(): { * Permits to read/write the current search query string
/**
* Works hand-in-hand with `setSearchQuery`; whatever the user has inputted
* into the search box.
*/ */
searchQuery: string; export function useSearchQueryString(): [string, (newValue: string) => void] {
/** return useQueryString(SEARCH_PARAM_QUERY);
* Set a new search query. In addition to updating `searchQuery`, this handle }
* also mutates the location and appends the query.
/**
* Permits to create links to the search page with the appropriate query string
*/ */
setSearchQuery: (newSearchQuery: string) => void; export function useSearchLinkCreator(): (searchValue: string) => string {
/**
* Given a query, this handle generates the corresponding search page link,
* with base URL prepended.
*/
generateSearchPageLink: (targetSearchQuery: string) => string;
} {
const history = useHistory();
const { const {
siteConfig: {baseUrl, themeConfig}, siteConfig: {baseUrl, themeConfig},
} = useDocusaurusContext(); } = useDocusaurusContext();
@ -38,47 +30,13 @@ export function useSearchPage(): {
algolia: {searchPagePath}, algolia: {searchPagePath},
} = themeConfig as AlgoliaThemeConfig; } = themeConfig as AlgoliaThemeConfig;
const [searchQuery, setSearchQueryState] = useState(''); return useCallback(
(searchValue: string) =>
// Init search query just after React hydration
useEffect(() => {
const searchQueryStringValue =
new URLSearchParams(window.location.search).get(SEARCH_PARAM_QUERY) ?? '';
setSearchQueryState(searchQueryStringValue);
}, []);
const setSearchQuery = useCallback(
(newSearchQuery: string) => {
const searchParams = new URLSearchParams(window.location.search);
if (newSearchQuery) {
searchParams.set(SEARCH_PARAM_QUERY, newSearchQuery);
} else {
searchParams.delete(SEARCH_PARAM_QUERY);
}
history.replace({
search: searchParams.toString(),
});
setSearchQueryState(newSearchQuery);
},
[history],
);
const generateSearchPageLink = useCallback(
(targetSearchQuery: string) =>
// Refer to https://github.com/facebook/docusaurus/pull/2838 // Refer to https://github.com/facebook/docusaurus/pull/2838
// Note: if searchPagePath is falsy, useSearchPage() will not be called // Note: if searchPagePath is falsy, useSearchPage() will not be called
`${baseUrl}${ `${baseUrl}${
searchPagePath as string searchPagePath as string
}?${SEARCH_PARAM_QUERY}=${encodeURIComponent(targetSearchQuery)}`, }?${SEARCH_PARAM_QUERY}=${encodeURIComponent(searchValue)}`,
[baseUrl, searchPagePath], [baseUrl, searchPagePath],
); );
return {
searchQuery,
setSearchQuery,
generateSearchPageLink,
};
} }

View file

@ -73,6 +73,11 @@ export {
type TagLetterEntry, type TagLetterEntry,
} from './utils/tagsUtils'; } from './utils/tagsUtils';
export {
useSearchQueryString,
useSearchLinkCreator,
} from './hooks/useSearchPage';
export {isMultiColumnFooterLinks} from './utils/footerUtils'; export {isMultiColumnFooterLinks} from './utils/footerUtils';
export {isRegexpStringMatch} from './utils/regexpUtils'; export {isRegexpStringMatch} from './utils/regexpUtils';
@ -89,3 +94,9 @@ export {
SkipToContentFallbackId, SkipToContentFallbackId,
SkipToContentLink, SkipToContentLink,
} from './utils/skipToContentUtils'; } from './utils/skipToContentUtils';
export {
ErrorBoundaryTryAgainButton,
ErrorBoundaryError,
ErrorCauseBoundary,
} from './utils/errorBoundaryUtils';

View file

@ -117,7 +117,6 @@ export {
keyboardFocusedClassName, keyboardFocusedClassName,
} from './hooks/useKeyboardNavigation'; } from './hooks/useKeyboardNavigation';
export {useLockBodyScroll} from './hooks/useLockBodyScroll'; export {useLockBodyScroll} from './hooks/useLockBodyScroll';
export {useSearchPage} from './hooks/useSearchPage';
export {useCodeWordWrap} from './hooks/useCodeWordWrap'; export {useCodeWordWrap} from './hooks/useCodeWordWrap';
export {getPrismCssVariables} from './utils/codeBlockUtils'; export {getPrismCssVariables} from './utils/codeBlockUtils';
export {useBackToTopButton} from './hooks/useBackToTopButton'; export {useBackToTopButton} from './hooks/useBackToTopButton';

View file

@ -271,8 +271,8 @@ export function useLayoutDocsSidebar(
`Can't find any sidebar with id "${sidebarId}" in version${ `Can't find any sidebar with id "${sidebarId}" in version${
versions.length > 1 ? 's' : '' versions.length > 1 ? 's' : ''
} ${versions.map((version) => version.name).join(', ')}". } ${versions.map((version) => version.name).join(', ')}".
Available sidebar ids are: Available sidebar ids are:
- ${Object.keys(allSidebars).join('\n- ')}`, - ${Object.keys(allSidebars).join('\n- ')}`,
); );
} }
return sidebarEntry[1]; return sidebarEntry[1];
@ -304,9 +304,9 @@ export function useLayoutDoc(
return null; return null;
} }
throw new Error( throw new Error(
`DocNavbarItem: couldn't find any doc with id "${docId}" in version${ `Couldn't find any doc with id "${docId}" in version${
versions.length > 1 ? 's' : '' versions.length > 1 ? 's' : ''
} ${versions.map((version) => version.name).join(', ')}". } "${versions.map((version) => version.name).join(', ')}".
Available doc ids are: Available doc ids are:
- ${uniq(allDocs.map((versionDoc) => versionDoc.id)).join('\n- ')}`, - ${uniq(allDocs.map((versionDoc) => versionDoc.id)).join('\n- ')}`,
); );

View file

@ -0,0 +1,11 @@
/**
* 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.
*/
.errorBoundaryError {
white-space: pre-wrap;
color: red;
}

View file

@ -0,0 +1,56 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, {type ComponentProps} from 'react';
import Translate from '@docusaurus/Translate';
import {getErrorCausalChain} from '@docusaurus/utils-common';
import styles from './errorBoundaryUtils.module.css';
export function ErrorBoundaryTryAgainButton(
props: ComponentProps<'button'>,
): JSX.Element {
return (
<button type="button" {...props}>
<Translate
id="theme.ErrorPageContent.tryAgain"
description="The label of the button to try again rendering when the React error boundary captures an error">
Try again
</Translate>
</button>
);
}
export function ErrorBoundaryError({error}: {error: Error}): JSX.Element {
const causalChain = getErrorCausalChain(error);
const fullMessage = causalChain.map((e) => e.message).join('\n\nCause:\n');
return <p className={styles.errorBoundaryError}>{fullMessage}</p>;
}
/**
* This component is useful to wrap a low-level error into a more meaningful
* error with extra context, using the ES error-cause feature.
*
* <ErrorCauseBoundary
* onError={(error) => new Error("extra context message",{cause: error})}
* >
* <RiskyComponent>
* </ErrorCauseBoundary>
*/
export class ErrorCauseBoundary extends React.Component<
{
children: React.ReactNode;
onError: (error: Error, errorInfo: React.ErrorInfo) => Error;
},
unknown
> {
override componentDidCatch(error: Error, errorInfo: React.ErrorInfo): never {
throw this.props.onError(error, errorInfo);
}
override render(): React.ReactNode {
return this.props.children;
}
}

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 {useEffect} from 'react'; import {useCallback, useEffect} from 'react';
import {useHistory} from '@docusaurus/router'; import {useHistory} from '@docusaurus/router';
// @ts-expect-error: TODO temporary until React 18 upgrade // @ts-expect-error: TODO temporary until React 18 upgrade
import {useSyncExternalStore} from 'use-sync-external-store/shim'; import {useSyncExternalStore} from 'use-sync-external-store/shim';
@ -75,3 +75,42 @@ export function useQueryStringValue(key: string | null): string | null {
return new URLSearchParams(history.location.search).get(key); return new URLSearchParams(history.location.search).get(key);
}); });
} }
export function useQueryStringKeySetter(): (
key: string,
newValue: string | null,
options?: {push: boolean},
) => void {
const history = useHistory();
return useCallback(
(key, newValue, options) => {
const searchParams = new URLSearchParams(history.location.search);
if (newValue) {
searchParams.set(key, newValue);
} else {
searchParams.delete(key);
}
const updaterFn = options?.push ? history.push : history.replace;
updaterFn({
search: searchParams.toString(),
});
},
[history],
);
}
export function useQueryString(
key: string,
): [string, (newValue: string, options?: {push: boolean}) => void] {
const value = useQueryStringValue(key) ?? '';
const setQueryString = useQueryStringKeySetter();
return [
value,
useCallback(
(newValue: string, options) => {
setQueryString(key, newValue, options);
},
[setQueryString, key],
),
];
}

View file

@ -29,12 +29,12 @@ export interface TabValue {
readonly default?: boolean; readonly default?: boolean;
} }
type TabItem = ReactElement<TabItemProps> | null | false | undefined;
export interface TabsProps { export interface TabsProps {
readonly lazy?: boolean; readonly lazy?: boolean;
readonly block?: boolean; readonly block?: boolean;
readonly children: readonly children: TabItem[] | TabItem;
| readonly ReactElement<TabItemProps>[]
| ReactElement<TabItemProps>;
readonly defaultValue?: string | null; readonly defaultValue?: string | null;
readonly values?: readonly TabValue[]; readonly values?: readonly TabValue[];
readonly groupId?: string; readonly groupId?: string;
@ -55,14 +55,16 @@ export interface TabItemProps {
// A very rough duck type, but good enough to guard against mistakes while // A very rough duck type, but good enough to guard against mistakes while
// allowing customization // allowing customization
function isTabItem( function isTabItem(
comp: ReactElement<object>, comp: ReactElement<unknown>,
): comp is ReactElement<TabItemProps> { ): comp is ReactElement<TabItemProps> {
return 'value' in comp.props; const {props} = comp;
return !!props && typeof props === 'object' && 'value' in props;
} }
function ensureValidChildren(children: TabsProps['children']) { function ensureValidChildren(children: TabsProps['children']) {
return React.Children.map(children, (child) => { return (React.Children.map(children, (child) => {
if (isValidElement(child) && isTabItem(child)) { // Pass falsy values through: allow conditionally not rendering a tab
if (!child || (isValidElement(child) && isTabItem(child))) {
return child; return child;
} }
// child.type.name will give non-sensical values in prod because of // child.type.name will give non-sensical values in prod because of
@ -73,7 +75,7 @@ function ensureValidChildren(children: TabsProps['children']) {
typeof child.type === 'string' ? child.type : child.type.name typeof child.type === 'string' ? child.type : child.type.name
}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.`, }>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.`,
); );
}); })?.filter(Boolean) ?? []) as ReactElement<TabItemProps>[];
} }
function extractChildrenTabValues(children: TabsProps['children']): TabValue[] { function extractChildrenTabValues(children: TabsProps['children']): TabValue[] {

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/theme-live-codeblock", "name": "@docusaurus/theme-live-codeblock",
"version": "2.3.1", "version": "2.4.0",
"description": "Docusaurus live code block component.", "description": "Docusaurus live code block component.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "src/theme-live-codeblock.d.ts", "types": "src/theme-live-codeblock.d.ts",
@ -23,10 +23,10 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/core": "2.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/theme-common": "2.3.1", "@docusaurus/theme-common": "2.4.0",
"@docusaurus/theme-translations": "2.3.1", "@docusaurus/theme-translations": "2.4.0",
"@docusaurus/utils-validation": "2.3.1", "@docusaurus/utils-validation": "2.4.0",
"@philpl/buble": "^0.19.7", "@philpl/buble": "^0.19.7",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
@ -34,7 +34,7 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"@types/buble": "^0.20.1" "@types/buble": "^0.20.1"
}, },
"peerDependencies": { "peerDependencies": {

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/theme-mermaid", "name": "@docusaurus/theme-mermaid",
"version": "2.3.1", "version": "2.4.0",
"description": "Mermaid components for Docusaurus.", "description": "Mermaid components for Docusaurus.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "src/theme-mermaid.d.ts", "types": "src/theme-mermaid.d.ts",
@ -33,11 +33,11 @@
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch" "copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
}, },
"dependencies": { "dependencies": {
"@docusaurus/core": "2.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/module-type-aliases": "2.3.1", "@docusaurus/module-type-aliases": "2.4.0",
"@docusaurus/theme-common": "2.3.1", "@docusaurus/theme-common": "2.4.0",
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"@docusaurus/utils-validation": "2.3.1", "@docusaurus/utils-validation": "2.4.0",
"@mdx-js/react": "^1.6.22", "@mdx-js/react": "^1.6.22",
"mermaid": "^9.2.2", "mermaid": "^9.2.2",
"tslib": "^2.4.0" "tslib": "^2.4.0"

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/theme-search-algolia", "name": "@docusaurus/theme-search-algolia",
"version": "2.3.1", "version": "2.4.0",
"description": "Algolia search component for Docusaurus.", "description": "Algolia search component for Docusaurus.",
"main": "lib/index.js", "main": "lib/index.js",
"sideEffects": [ "sideEffects": [
@ -34,13 +34,13 @@
}, },
"dependencies": { "dependencies": {
"@docsearch/react": "^3.1.1", "@docsearch/react": "^3.1.1",
"@docusaurus/core": "2.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/logger": "2.3.1", "@docusaurus/logger": "2.4.0",
"@docusaurus/plugin-content-docs": "2.3.1", "@docusaurus/plugin-content-docs": "2.4.0",
"@docusaurus/theme-common": "2.3.1", "@docusaurus/theme-common": "2.4.0",
"@docusaurus/theme-translations": "2.3.1", "@docusaurus/theme-translations": "2.4.0",
"@docusaurus/utils": "2.3.1", "@docusaurus/utils": "2.4.0",
"@docusaurus/utils-validation": "2.3.1", "@docusaurus/utils-validation": "2.4.0",
"algoliasearch": "^4.13.1", "algoliasearch": "^4.13.1",
"algoliasearch-helper": "^3.10.0", "algoliasearch-helper": "^3.10.0",
"clsx": "^1.2.1", "clsx": "^1.2.1",
@ -51,7 +51,7 @@
"utility-types": "^3.10.0" "utility-types": "^3.10.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "2.3.1" "@docusaurus/module-type-aliases": "2.4.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",

View file

@ -10,8 +10,10 @@ import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
import Head from '@docusaurus/Head'; import Head from '@docusaurus/Head';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
import {useHistory} from '@docusaurus/router'; import {useHistory} from '@docusaurus/router';
import {isRegexpStringMatch} from '@docusaurus/theme-common'; import {
import {useSearchPage} from '@docusaurus/theme-common/internal'; isRegexpStringMatch,
useSearchLinkCreator,
} from '@docusaurus/theme-common';
import { import {
useAlgoliaContextualFacetFilters, useAlgoliaContextualFacetFilters,
useSearchResultUrlProcessor, useSearchResultUrlProcessor,
@ -59,10 +61,10 @@ type ResultsFooterProps = {
}; };
function ResultsFooter({state, onClose}: ResultsFooterProps) { function ResultsFooter({state, onClose}: ResultsFooterProps) {
const {generateSearchPageLink} = useSearchPage(); const createSearchLink = useSearchLinkCreator();
return ( return (
<Link to={generateSearchPageLink(state.query)} onClick={onClose}> <Link to={createSearchLink(state.query)} onClick={onClose}>
<Translate <Translate
id="theme.SearchBar.seeAll" id="theme.SearchBar.seeAll"
values={{count: state.context.nbHits}}> values={{count: state.context.nbHits}}>

View file

@ -21,11 +21,9 @@ import {
HtmlClassNameProvider, HtmlClassNameProvider,
useEvent, useEvent,
usePluralForm, usePluralForm,
useSearchQueryString,
} from '@docusaurus/theme-common'; } from '@docusaurus/theme-common';
import { import {useTitleFormatter} from '@docusaurus/theme-common/internal';
useSearchPage,
useTitleFormatter,
} from '@docusaurus/theme-common/internal';
import Translate, {translate} from '@docusaurus/Translate'; import Translate, {translate} from '@docusaurus/Translate';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import { import {
@ -167,7 +165,7 @@ function SearchPageContent(): JSX.Element {
const documentsFoundPlural = useDocumentsFoundPlural(); const documentsFoundPlural = useDocumentsFoundPlural();
const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers(); const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers();
const {searchQuery, setSearchQuery} = useSearchPage(); const [searchQuery, setSearchQuery] = useSearchQueryString();
const initialSearchResultState: ResultDispatcherState = { const initialSearchResultState: ResultDispatcherState = {
items: [], items: [],
query: null, query: null,

View file

@ -17,7 +17,7 @@
"theme.admonition.danger": "خطر", "theme.admonition.danger": "خطر",
"theme.admonition.info": "معلومات", "theme.admonition.info": "معلومات",
"theme.admonition.note": "ملاحظة", "theme.admonition.note": "ملاحظة",
"theme.admonition.tip": مليح", "theme.admonition.tip": لميح",
"theme.blog.archive.description": "أرشيف", "theme.blog.archive.description": "أرشيف",
"theme.blog.archive.title": "أرشيف", "theme.blog.archive.title": "أرشيف",
"theme.blog.paginator.navAriaLabel": "التنقل في صفحة قائمة المدونة", "theme.blog.paginator.navAriaLabel": "التنقل في صفحة قائمة المدونة",
@ -42,7 +42,7 @@
"theme.docs.breadcrumbs.home": "الرئيسية", "theme.docs.breadcrumbs.home": "الرئيسية",
"theme.docs.breadcrumbs.navAriaLabel": "التنقل التفصيلي", "theme.docs.breadcrumbs.navAriaLabel": "التنقل التفصيلي",
"theme.docs.paginator.navAriaLabel": "التنقل بين صفحات الددات", "theme.docs.paginator.navAriaLabel": "التنقل بين صفحات الددات",
"theme.docs.paginator.next": "التالى", "theme.docs.paginator.next": "التالي",
"theme.docs.paginator.previous": "السابق", "theme.docs.paginator.previous": "السابق",
"theme.docs.sidebar.closeSidebarButtonAriaLabel": "Close navigation bar", "theme.docs.sidebar.closeSidebarButtonAriaLabel": "Close navigation bar",
"theme.docs.sidebar.collapseButtonAriaLabel": "طي الشريط الجانبي", "theme.docs.sidebar.collapseButtonAriaLabel": "طي الشريط الجانبي",

View file

@ -0,0 +1,7 @@
{
"theme.IdealImageMessage.404error": "404. A kép nem található",
"theme.IdealImageMessage.error": "Hiba. Kattints újratöltéshez",
"theme.IdealImageMessage.load": "Kattints a(z) {sizeMessage} betöltéséhez",
"theme.IdealImageMessage.loading": "Betöltés...",
"theme.IdealImageMessage.offline": "A böngésződ offline módban van. A kép nem töltődött be"
}

View file

@ -0,0 +1,5 @@
{
"theme.PwaReloadPopup.closeButtonAriaLabel": "Bezárás",
"theme.PwaReloadPopup.info": "Új verzió elérhető el",
"theme.PwaReloadPopup.refreshButtonText": "Frissítés"
}

View file

@ -0,0 +1,70 @@
{
"theme.AnnouncementBar.closeButtonAriaLabel": "Bezárás",
"theme.BackToTopButton.buttonAriaLabel": "Vissza a tetejére",
"theme.CodeBlock.copied": "Másolva",
"theme.CodeBlock.copy": "Másolás",
"theme.CodeBlock.copyButtonAriaLabel": "Másolás a vágólapra",
"theme.CodeBlock.wordWrapToggle": "Szótörés váltása",
"theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "Összecsukás/kibontás kategória '{label}'",
"theme.ErrorPageContent.title": "Hiba történt a oldalon.",
"theme.ErrorPageContent.tryAgain": "Próbáld újra",
"theme.NavBar.navAriaLabel": "Fő",
"theme.NotFound.p1": "Sajnos nem találtuk azt az oldalt, amit kerestél.",
"theme.NotFound.p2": "Kérjük, lépj kapcsolatba az oldal tulajdonosával, ahonnan erre a hivatkozásra léptél, hogy jelezd, hogy a hivatkozás nem működik.",
"theme.NotFound.title": "Az oldal nem található",
"theme.TOCCollapsible.toggleButtonLabel": "Ezen az oldalon tartalomjegyzék",
"theme.admonition.caution": "vigyázat",
"theme.admonition.danger": "veszély",
"theme.admonition.info": "információ",
"theme.admonition.note": "megjegyzés",
"theme.admonition.tip": "tanács",
"theme.blog.archive.description": "Archívum",
"theme.blog.archive.title": "Archívum",
"theme.blog.paginator.navAriaLabel": "Bloglista oldalának navigációja",
"theme.blog.paginator.newerEntries": "Újabb bejegyzések",
"theme.blog.paginator.olderEntries": "Régebbi bejegyzések",
"theme.blog.post.paginator.navAriaLabel": "Blogbejegyzés oldalának navigációja",
"theme.blog.post.paginator.newerPost": "Újabb bejegyzés",
"theme.blog.post.paginator.olderPost": "Régebbi bejegyzés",
"theme.blog.post.plurals": "Egy bejegyzés|{count} bejegyzés",
"theme.blog.post.readMore": "Olvass tovább",
"theme.blog.post.readMoreLabel": "További információ erről: {title}",
"theme.blog.post.readingTime.plurals": "Egy perc olvasás|{readingTime} perc olvasás",
"theme.blog.sidebar.navAriaLabel": "Navigáció a blog legutóbbi bejegyzései között",
"theme.blog.tagTitle": "{nPosts} a(z) \"{tagName}\" címkével",
"theme.colorToggle.ariaLabel": "Váltás a sötét és világos mód között (jelenleg {mode} van beállítva)",
"theme.colorToggle.ariaLabel.mode.dark": "Sötét mód",
"theme.colorToggle.ariaLabel.mode.light": "Világos mód",
"theme.common.editThisPage": "Szerkesztés GitHub-on",
"theme.common.headingLinkTitle": "Közvetlen hivatkozás erre: {heading}",
"theme.common.skipToMainContent": "Ugrás a fő tartalomhoz",
"theme.docs.DocCard.categoryDescription": "{count} elemek",
"theme.docs.breadcrumbs.home": "Kezdőlap",
"theme.docs.breadcrumbs.navAriaLabel": "Navigációs sáv a jelenlegi oldalhoz",
"theme.docs.paginator.navAriaLabel": "Dokumentációs oldal navigációja",
"theme.docs.paginator.next": "Következő oldal",
"theme.docs.paginator.previous": "Előző oldal",
"theme.docs.sidebar.closeSidebarButtonAriaLabel": "Navigációs sáv bezárása",
"theme.docs.sidebar.collapseButtonAriaLabel": "Oldalsáv összecsukása",
"theme.docs.sidebar.collapseButtonTitle": "Oldalsáv összecsukása",
"theme.docs.sidebar.expandButtonAriaLabel": "Oldalsáv kibontása",
"theme.docs.sidebar.expandButtonTitle": "Oldalsáv kibontása",
"theme.docs.sidebar.navAriaLabel": "Dokumentációs oldalsáv",
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": "Navigációs sáv ki- és bekapcsolása",
"theme.docs.tagDocListPageTitle": "{nDocsTagged} címkével: \"{tagName}\" ",
"theme.docs.tagDocListPageTitle.nDocsTagged": "Egy oldal|{count} oldal",
"theme.docs.versionBadge.label": "Verzió: {versionLabel}",
"theme.docs.versions.latestVersionLinkLabel": "legutolsó verzió",
"theme.docs.versions.latestVersionSuggestionLabel": "Az aktuális dokumentáció itt található: {latestVersionLink} ({versionLabel}).",
"theme.docs.versions.unmaintainedVersionLabel": "Ez a {siteTitle} dokumentáció a {versionLabel} verzióhoz, amely már nem támogatott.",
"theme.docs.versions.unreleasedVersionLabel": "Ez a dokumentáció a jövőbeli {siteTitle} {versionLabel} verzióhoz.",
"theme.lastUpdated.atDate": " {date}-kor",
"theme.lastUpdated.byUser": " {user}-tól",
"theme.lastUpdated.lastUpdatedAtBy": "Utolsó frissítés{atDate}{byUser}",
"theme.navbar.mobileLanguageDropdown.label": "Nyelvek",
"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Vissza a főmenühöz",
"theme.navbar.mobileVersionsDropdown.label": "Verziók",
"theme.tags.tagsListLabel": "Címkék:",
"theme.tags.tagsPageLink": "Összes címke megtekintése",
"theme.tags.tagsPageTitle": "Címkék"
}

View file

@ -0,0 +1,4 @@
{
"theme.Playground.liveEditor": "Interaktív szerkesztő",
"theme.Playground.result": "Eredmény"
}

View file

@ -0,0 +1,36 @@
{
"theme.SearchBar.label": "Keresés",
"theme.SearchBar.seeAll": "Összes eredmény megtekintése ({count})",
"theme.SearchModal.errorScreen.helpText": "Ellenőrizze a hálózati kapcsolatot.",
"theme.SearchModal.errorScreen.titleText": "Nem sikerült az eredményeket lekérni",
"theme.SearchModal.footer.closeKeyAriaLabel": "Escape billentyű",
"theme.SearchModal.footer.closeText": "bezárás",
"theme.SearchModal.footer.navigateDownKeyAriaLabel": "Lefelé nyíl",
"theme.SearchModal.footer.navigateText": "navigációhoz",
"theme.SearchModal.footer.navigateUpKeyAriaLabel": "Felfelé nyíl",
"theme.SearchModal.footer.searchByText": "Motor:",
"theme.SearchModal.footer.selectKeyAriaLabel": "Enter billentyű",
"theme.SearchModal.footer.selectText": "kiválasztás",
"theme.SearchModal.noResultsScreen.noResultsText": "Nincs eredmény",
"theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": "Adja meg nekünk.",
"theme.SearchModal.noResultsScreen.reportMissingResultsText": "Úgy gondolja, hogy ez a keresés eredményt kellene adnia?",
"theme.SearchModal.noResultsScreen.suggestedQueryText": "Próbálja meg keresni",
"theme.SearchModal.placeholder": "Dokumentumok keresése",
"theme.SearchModal.searchBox.cancelButtonText": "Mégse",
"theme.SearchModal.searchBox.resetButtonTitle": "Keresési kérés törlése",
"theme.SearchModal.startScreen.favoriteSearchesTitle": "Kedvencek",
"theme.SearchModal.startScreen.noRecentSearchesText": "Nincsenek legutóbbi keresések",
"theme.SearchModal.startScreen.recentSearchesTitle": "Legutóbbi",
"theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle": "Törölje ezt a keresést a kedvencekből",
"theme.SearchModal.startScreen.removeRecentSearchButtonTitle": "Törölje ezt a keresést az előzményekből",
"theme.SearchModal.startScreen.saveRecentSearchButtonTitle": "Mentsük el ezt a keresést",
"theme.SearchPage.saveRecentSearchButtonTitle": "Mentse ezt a keresést",
"theme.SearchPage.algoliaLabel": "Keresés az Algolia segítségével",
"theme.SearchPage.documentsFound.plurals": "Egy dokumentum|{count} dokumentumok",
"theme.SearchPage.emptyResultsTitle": "Keresés a webhelyen",
"theme.SearchPage.existingResultsTitle": "\"{query}\" keresési eredményei",
"theme.SearchPage.fetchingNewResults": "Új keresési eredmények betöltése...",
"theme.SearchPage.inputLabel": "Keresés",
"theme.SearchPage.inputPlaceholder": "Adja meg a keresendő kifejezést",
"theme.SearchPage.noResultsText": "Nincs találat a keresésre"
}

View file

@ -0,0 +1,7 @@
{
"theme.IdealImageMessage.404error": "404. Bildet kunne ikke lastes inn",
"theme.IdealImageMessage.error": "Noe gikk galt. Klikk for å laste på nytt",
"theme.IdealImageMessage.load": "Klikk for å laste inn på nytt{sizeMessage}",
"theme.IdealImageMessage.loading": "Laster inn...",
"theme.IdealImageMessage.offline": "Nettleseren din er offline. Bildet ble ikke lastet inn."
}

View file

@ -0,0 +1,5 @@
{
"theme.PwaReloadPopup.closeButtonAriaLabel": "Lukk",
"theme.PwaReloadPopup.info": "Ny versjon tilgjengelig",
"theme.PwaReloadPopup.refreshButtonText": "Last inn på nytt"
}

View file

@ -0,0 +1,70 @@
{
"theme.AnnouncementBar.closeButtonAriaLabel": "Lukk",
"theme.BackToTopButton.buttonAriaLabel": "Rull tilbake til toppen",
"theme.CodeBlock.copied": "Kopiert",
"theme.CodeBlock.copy": "Kopiere",
"theme.CodeBlock.copyButtonAriaLabel": "Kopier koden til utklippstavlen",
"theme.CodeBlock.wordWrapToggle": "Slå tekstbryting av/på",
"theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "Veksle på den sammenleggbare sidefeltkategorien '{label}'",
"theme.ErrorPageContent.title": "Denne siden krasjet.",
"theme.ErrorPageContent.tryAgain": "Prøv på nytt",
"theme.NavBar.navAriaLabel": "Hoved",
"theme.NotFound.p1": "Vi kunne ikke finne det du lette etter.",
"theme.NotFound.p2": "Kontakt eieren av nettstedet som koblet deg til den opprinnelige nettadressen og la dem få vite at koblingen deres er ødelagt.",
"theme.NotFound.title": "Siden ble ikke funnet",
"theme.TOCCollapsible.toggleButtonLabel": "På denne siden",
"theme.admonition.caution": "forsiktig",
"theme.admonition.danger": "fare",
"theme.admonition.info": "info",
"theme.admonition.note": "merknad",
"theme.admonition.tip": "tips",
"theme.blog.archive.description": "Arkiv",
"theme.blog.archive.title": "Arkiv",
"theme.blog.paginator.navAriaLabel": "Navigering av bloggliste",
"theme.blog.paginator.newerEntries": "Nyere innlegg",
"theme.blog.paginator.olderEntries": "Eldre innlegg",
"theme.blog.post.paginator.navAriaLabel": "Navigering på blogginnlegg",
"theme.blog.post.paginator.newerPost": "Nyere innlegg",
"theme.blog.post.paginator.olderPost": "Eldre innlegg",
"theme.blog.post.plurals": "Ett innlegg|{count} innlegg",
"theme.blog.post.readMore": "Les mer",
"theme.blog.post.readMoreLabel": "Les mer om {title}",
"theme.blog.post.readingTime.plurals": "Ett min lesetid|{readingTime} min lesetid",
"theme.blog.sidebar.navAriaLabel": "Navigering av siste blogginnlegg",
"theme.blog.tagTitle": "{nPosts} merket med \"{tagName}\"",
"theme.colorToggle.ariaLabel": "Bytt mellom mørk og lys utseende (nå {mode})",
"theme.colorToggle.ariaLabel.mode.dark": "mørk utseende",
"theme.colorToggle.ariaLabel.mode.light": "lys utseende",
"theme.common.editThisPage": "Rediger denne siden",
"theme.common.headingLinkTitle": "Direkte lenke til {heading}",
"theme.common.skipToMainContent": "Gå til hovedinnhold",
"theme.docs.DocCard.categoryDescription": "{count} artikler",
"theme.docs.breadcrumbs.home": "Hjemmeside",
"theme.docs.breadcrumbs.navAriaLabel": "Søkvei",
"theme.docs.paginator.navAriaLabel": "Dokumenter-sidernavigasjon",
"theme.docs.paginator.next": "Neste",
"theme.docs.paginator.previous": "Forrige",
"theme.docs.sidebar.closeSidebarButtonAriaLabel": "Lukk navigasjonslinjen",
"theme.docs.sidebar.collapseButtonAriaLabel": "Skjul sidefeltet",
"theme.docs.sidebar.collapseButtonTitle": "Collapse sidebar",
"theme.docs.sidebar.expandButtonAriaLabel": "Utvid sidefeltet",
"theme.docs.sidebar.expandButtonTitle": "Utvid sidefeltet",
"theme.docs.sidebar.navAriaLabel": "Dokumenter sidefelt",
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": "Vis/skjul navigasjonslinjen",
"theme.docs.tagDocListPageTitle": "{nDocsTagged} med \"{tagName}\"",
"theme.docs.tagDocListPageTitle.nDocsTagged": "Ett dokument merket|{count} dokumenter merket",
"theme.docs.versionBadge.label": "Versjon: {versionLabel}",
"theme.docs.versions.latestVersionLinkLabel": "siste Versjon",
"theme.docs.versions.latestVersionSuggestionLabel": "For oppdatert dokumentasjon, se {latestVersionLink} ({versionLabel}).",
"theme.docs.versions.unmaintainedVersionLabel": "Dette er dokumentasjon for {siteTitle} {versionLabel}, som ikke lenger vedlikeholdes aktivt.",
"theme.docs.versions.unreleasedVersionLabel": "Dette er uutgitt dokumentasjon for {siteTitle} {versionLabel} versjon.",
"theme.lastUpdated.atDate": " den {date}",
"theme.lastUpdated.byUser": " av {user}",
"theme.lastUpdated.lastUpdatedAtBy": "Sist oppdatert{atDate}{byUser}",
"theme.navbar.mobileLanguageDropdown.label": "Språk",
"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Tilbake til hovedmenyen",
"theme.navbar.mobileVersionsDropdown.label": "Versjoner",
"theme.tags.tagsListLabel": "Tagger:",
"theme.tags.tagsPageLink": "Vis alle tagger",
"theme.tags.tagsPageTitle": "Tagger"
}

View file

@ -0,0 +1,4 @@
{
"theme.Playground.liveEditor": "Live Editor",
"theme.Playground.result": "Resultat"
}

View file

@ -0,0 +1,35 @@
{
"theme.SearchBar.label": "Søk",
"theme.SearchBar.seeAll": "Se alle {count} resultat",
"theme.SearchModal.errorScreen.helpText": "Det kan være lurt å sjekke nettverkstilkoblingen.",
"theme.SearchModal.errorScreen.titleText": "Kan ikke hente resultater",
"theme.SearchModal.footer.closeKeyAriaLabel": "Escape-tasten",
"theme.SearchModal.footer.closeText": "Lukk",
"theme.SearchModal.footer.navigateDownKeyAriaLabel": "Pil ned",
"theme.SearchModal.footer.navigateText": "for å navigere",
"theme.SearchModal.footer.navigateUpKeyAriaLabel": "Pil opp",
"theme.SearchModal.footer.searchByText": "Søk på",
"theme.SearchModal.footer.selectKeyAriaLabel": "Enter-tasten",
"theme.SearchModal.footer.selectText": "for å velge",
"theme.SearchModal.noResultsScreen.noResultsText": "Ingen resultat",
"theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": "Gi oss beskjed.",
"theme.SearchModal.noResultsScreen.reportMissingResultsText": "Tenker du at denne spørringen bør gi resultater?",
"theme.SearchModal.noResultsScreen.suggestedQueryText": "Prøv å søke etter",
"theme.SearchModal.placeholder": "Søk i dokumenter",
"theme.SearchModal.searchBox.cancelButtonText": "Avbryt",
"theme.SearchModal.searchBox.resetButtonTitle": "Fjern søket",
"theme.SearchModal.startScreen.favoriteSearchesTitle": "Favoritt",
"theme.SearchModal.startScreen.noRecentSearchesText": "Ingen nylige søk",
"theme.SearchModal.startScreen.recentSearchesTitle": "Nylig",
"theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle": "Fjern dette søket fra favoritter",
"theme.SearchModal.startScreen.removeRecentSearchButtonTitle": "Fjern dette søket fra loggen",
"theme.SearchModal.startScreen.saveRecentSearchButtonTitle": "Lagre dette søket",
"theme.SearchPage.algoliaLabel": "Søk med Algolia",
"theme.SearchPage.documentsFound.plurals": "Ett dokument funnet|{count} dokumenter funnet",
"theme.SearchPage.emptyResultsTitle": "Søk i dokumentasjonen",
"theme.SearchPage.existingResultsTitle": "Søkeresultater for \"{query}\"",
"theme.SearchPage.fetchingNewResults": "Henter nye resultater...",
"theme.SearchPage.inputLabel": "Søk",
"theme.SearchPage.inputPlaceholder": "Skriv inn søket ditt her",
"theme.SearchPage.noResultsText": "Ingen resultater ble funnet"
}

View file

@ -66,7 +66,5 @@
"theme.navbar.mobileVersionsDropdown.label": "Verzije", "theme.navbar.mobileVersionsDropdown.label": "Verzije",
"theme.tags.tagsListLabel": "Oznake:", "theme.tags.tagsListLabel": "Oznake:",
"theme.tags.tagsPageLink": "Poglej vse oznake", "theme.tags.tagsPageLink": "Poglej vse oznake",
"theme.tags.tagsPageTitle": "Oznake", "theme.tags.tagsPageTitle": "Oznake"
"theme.unlistedContent.message": "Ta stran ni zabeležena. Iskalniki je ne bodo indeksirali, do nje bodo uporabniki lahko dostopali le z direktno povezavo.",
"theme.unlistedContent.title": "Nezabeležena stran"
} }

View file

@ -66,7 +66,5 @@
"theme.navbar.mobileVersionsDropdown.label": "Versiyonlar", "theme.navbar.mobileVersionsDropdown.label": "Versiyonlar",
"theme.tags.tagsListLabel": "Etiketler:", "theme.tags.tagsListLabel": "Etiketler:",
"theme.tags.tagsPageLink": "Tüm Etiketleri Görüntüle", "theme.tags.tagsPageLink": "Tüm Etiketleri Görüntüle",
"theme.tags.tagsPageTitle": "Etiketler", "theme.tags.tagsPageTitle": "Etiketler"
"theme.unlistedContent.message": "Bu sayfa listelenmemiş. Arama motorları dizine eklemez ve yalnızca doğrudan bağlantısı olan kullanıcılar buna erişebilir.",
"theme.unlistedContent.title": "Listelenmeyen sayfa"
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/theme-translations", "name": "@docusaurus/theme-translations",
"version": "2.3.1", "version": "2.4.0",
"description": "Docusaurus theme translations.", "description": "Docusaurus theme translations.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
@ -23,8 +23,8 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/core": "2.3.1", "@docusaurus/core": "2.4.0",
"@docusaurus/logger": "2.3.1", "@docusaurus/logger": "2.4.0",
"lodash": "^4.17.21" "lodash": "^4.17.21"
}, },
"engines": { "engines": {

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/types", "name": "@docusaurus/types",
"version": "2.3.1", "version": "2.4.0",
"description": "Common types for Docusaurus packages.", "description": "Common types for Docusaurus packages.",
"types": "./src/index.d.ts", "types": "./src/index.d.ts",
"publishConfig": { "publishConfig": {

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/utils-common", "name": "@docusaurus/utils-common",
"version": "2.3.1", "version": "2.4.0",
"description": "Common (Node/Browser) utility functions for Docusaurus packages.", "description": "Common (Node/Browser) utility functions for Docusaurus packages.",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",

View file

@ -0,0 +1,26 @@
/**
* 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 {getErrorCausalChain} from '../errorUtils';
describe('getErrorCausalChain', () => {
it('works for simple error', () => {
const error = new Error('msg');
expect(getErrorCausalChain(error)).toEqual([error]);
});
it('works for nested errors', () => {
const error = new Error('msg', {
cause: new Error('msg', {cause: new Error('msg')}),
});
expect(getErrorCausalChain(error)).toEqual([
error,
error.cause,
(error.cause as Error).cause,
]);
});
});

View file

@ -0,0 +1,14 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
type CausalChain = [Error, ...Error[]];
export function getErrorCausalChain(error: Error): CausalChain {
if (error.cause) {
return [error, ...getErrorCausalChain(error.cause as Error)];
}
return [error];
}

View file

@ -10,3 +10,4 @@ export {
default as applyTrailingSlash, default as applyTrailingSlash,
type ApplyTrailingSlashParams, type ApplyTrailingSlashParams,
} from './applyTrailingSlash'; } from './applyTrailingSlash';
export {getErrorCausalChain} from './errorUtils';

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/utils-validation", "name": "@docusaurus/utils-validation",
"version": "2.3.1", "version": "2.4.0",
"description": "Node validation utility functions for Docusaurus packages.", "description": "Node validation utility functions for Docusaurus packages.",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
@ -18,8 +18,8 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/logger": "2.3.1", "@docusaurus/logger": "2.4.0",
"@docusaurus/utils": "2.3.1", "@docusaurus/utils": "2.4.0",
"joi": "^17.6.0", "joi": "^17.6.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"tslib": "^2.4.0" "tslib": "^2.4.0"

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/utils", "name": "@docusaurus/utils",
"version": "2.3.1", "version": "2.4.0",
"description": "Node utility functions for Docusaurus packages.", "description": "Node utility functions for Docusaurus packages.",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
@ -18,7 +18,7 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/logger": "2.3.1", "@docusaurus/logger": "2.4.0",
"@svgr/webpack": "^6.2.1", "@svgr/webpack": "^6.2.1",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
@ -39,7 +39,7 @@
"node": ">=16.14" "node": ">=16.14"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"@types/dedent": "^0.7.0", "@types/dedent": "^0.7.0",
"@types/github-slugger": "^1.3.0", "@types/github-slugger": "^1.3.0",
"@types/micromatch": "^4.0.2", "@types/micromatch": "^4.0.2",

View file

@ -24,6 +24,12 @@ import {
} from '../lib/index.js'; } from '../lib/index.js';
import beforeCli from './beforeCli.mjs'; import beforeCli from './beforeCli.mjs';
// Env variables are initialized to dev, but can be overridden by each command
// For example, "docusaurus build" overrides them to "production"
// See also https://github.com/facebook/docusaurus/issues/8599
process.env.BABEL_ENV ??= 'development';
process.env.NODE_ENV ??= 'development';
await beforeCli(); await beforeCli();
cli.version(DOCUSAURUS_VERSION).usage('<command> [options]'); cli.version(DOCUSAURUS_VERSION).usage('<command> [options]');

View file

@ -1,7 +1,7 @@
{ {
"name": "@docusaurus/core", "name": "@docusaurus/core",
"description": "Easy to Maintain Open Source Documentation Websites", "description": "Easy to Maintain Open Source Documentation Websites",
"version": "2.3.1", "version": "2.4.0",
"license": "MIT", "license": "MIT",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
@ -43,13 +43,13 @@
"@babel/runtime": "^7.18.6", "@babel/runtime": "^7.18.6",
"@babel/runtime-corejs3": "^7.18.6", "@babel/runtime-corejs3": "^7.18.6",
"@babel/traverse": "^7.18.8", "@babel/traverse": "^7.18.8",
"@docusaurus/cssnano-preset": "2.3.1", "@docusaurus/cssnano-preset": "2.4.0",
"@docusaurus/logger": "2.3.1", "@docusaurus/logger": "2.4.0",
"@docusaurus/mdx-loader": "2.3.1", "@docusaurus/mdx-loader": "2.4.0",
"@docusaurus/react-loadable": "5.5.2", "@docusaurus/react-loadable": "5.5.2",
"@docusaurus/utils": "2.3.1", "@docusaurus/utils": "2.4.0",
"@docusaurus/utils-common": "2.3.1", "@docusaurus/utils-common": "2.4.0",
"@docusaurus/utils-validation": "2.3.1", "@docusaurus/utils-validation": "2.4.0",
"@slorber/static-site-generator-webpack-plugin": "^4.0.7", "@slorber/static-site-generator-webpack-plugin": "^4.0.7",
"@svgr/webpack": "^6.2.1", "@svgr/webpack": "^6.2.1",
"autoprefixer": "^10.4.7", "autoprefixer": "^10.4.7",
@ -106,8 +106,8 @@
"webpackbar": "^5.0.2" "webpackbar": "^5.0.2"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "2.3.1", "@docusaurus/module-type-aliases": "2.4.0",
"@docusaurus/types": "2.3.1", "@docusaurus/types": "2.4.0",
"@types/detect-port": "^1.3.2", "@types/detect-port": "^1.3.2",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@types/react-router-config": "^5.0.6", "@types/react-router-config": "^5.0.6",

View file

@ -32,7 +32,7 @@ function createInlineHtmlBanner(baseUrl: string) {
return ` return `
<div id="${BannerId}" style="border: thick solid red; background-color: rgb(255, 230, 179); margin: 20px; padding: 20px; font-size: 20px;"> <div id="${BannerId}" style="border: thick solid red; background-color: rgb(255, 230, 179); margin: 20px; padding: 20px; font-size: 20px;">
<p style="font-weight: bold; font-size: 30px;">Your Docusaurus site did not load properly.</p> <p style="font-weight: bold; font-size: 30px;">Your Docusaurus site did not load properly.</p>
<p>A very common reason is a wrong site <a href="https://docusaurus.io/docs/docusaurus.config.js/#baseurl" style="font-weight: bold;">baseUrl configuration</a>.</p> <p>A very common reason is a wrong site <a href="https://docusaurus.io/docs/docusaurus.config.js/#baseUrl" style="font-weight: bold;">baseUrl configuration</a>.</p>
<p>Current configured baseUrl = <span style="font-weight: bold; color: red;">${baseUrl}</span> ${ <p>Current configured baseUrl = <span style="font-weight: bold; color: red;">${baseUrl}</span> ${
baseUrl === '/' ? ' (default value)' : '' baseUrl === '/' ? ' (default value)' : ''
}</p> }</p>

View file

@ -11,6 +11,7 @@
import React from 'react'; import React from 'react';
import Head from '@docusaurus/Head'; import Head from '@docusaurus/Head';
import ErrorBoundary from '@docusaurus/ErrorBoundary'; import ErrorBoundary from '@docusaurus/ErrorBoundary';
import {getErrorCausalChain} from '@docusaurus/utils-common';
import Layout from '@theme/Layout'; import Layout from '@theme/Layout';
import type {Props} from '@theme/Error'; import type {Props} from '@theme/Error';
@ -21,20 +22,38 @@ function ErrorDisplay({error, tryAgain}: Props): JSX.Element {
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'flex-start',
height: '50vh', minHeight: '100vh',
width: '100%', width: '100%',
maxWidth: '80ch',
fontSize: '20px', fontSize: '20px',
margin: '0 auto',
padding: '1rem',
}}>
<h1 style={{fontSize: '3rem'}}>This page crashed</h1>
<button
type="button"
onClick={tryAgain}
style={{
margin: '1rem 0',
fontSize: '2rem',
cursor: 'pointer',
borderRadius: 20,
padding: '1rem',
}}> }}>
<h1>This page crashed.</h1>
<p>{error.message}</p>
<button type="button" onClick={tryAgain}>
Try again Try again
</button> </button>
<ErrorBoundaryError error={error} />
</div> </div>
); );
} }
function ErrorBoundaryError({error}: {error: Error}): JSX.Element {
const causalChain = getErrorCausalChain(error);
const fullMessage = causalChain.map((e) => e.message).join('\n\nCause:\n');
return <p style={{whiteSpace: 'pre-wrap'}}>{fullMessage}</p>;
}
export default function Error({error, tryAgain}: Props): JSX.Element { export default function Error({error, tryAgain}: Props): JSX.Element {
// We wrap the error in its own error boundary because the layout can actually // We wrap the error in its own error boundary because the layout can actually
// throw too... Only the ErrorDisplay component is simple enough to be // throw too... Only the ErrorDisplay component is simple enough to be

View file

@ -46,6 +46,10 @@ export async function build(
// See https://github.com/facebook/docusaurus/pull/2496 // See https://github.com/facebook/docusaurus/pull/2496
forceTerminate: boolean = true, forceTerminate: boolean = true,
): Promise<string> { ): Promise<string> {
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
process.env.DOCUSAURUS_CURRENT_LOCALE = cliOptions.locale;
const siteDir = await fs.realpath(siteDirParam); const siteDir = await fs.realpath(siteDirParam);
['SIGINT', 'SIGTERM'].forEach((sig) => { ['SIGINT', 'SIGTERM'].forEach((sig) => {
@ -117,8 +121,11 @@ async function buildLocale({
forceTerminate: boolean; forceTerminate: boolean;
isLastLocale: boolean; isLastLocale: boolean;
}): Promise<string> { }): Promise<string> {
process.env.BABEL_ENV = 'production'; // Temporary workaround to unlock the ability to translate the site config
process.env.NODE_ENV = 'production'; // We'll remove it if a better official API can be designed
// See https://github.com/facebook/docusaurus/issues/4542
process.env.DOCUSAURUS_CURRENT_LOCALE = locale;
logger.info`name=${`[${locale}]`} Creating an optimized production build...`; logger.info`name=${`[${locale}]`} Creating an optimized production build...`;
const props: Props = await load({ const props: Props = await load({

View file

@ -39,10 +39,13 @@ export async function start(
siteDirParam: string = '.', siteDirParam: string = '.',
cliOptions: Partial<StartCLIOptions> = {}, cliOptions: Partial<StartCLIOptions> = {},
): Promise<void> { ): Promise<void> {
// Temporary workaround to unlock the ability to translate the site config
// We'll remove it if a better official API can be designed
// See https://github.com/facebook/docusaurus/issues/4542
process.env.DOCUSAURUS_CURRENT_LOCALE = cliOptions.locale;
const siteDir = await fs.realpath(siteDirParam); const siteDir = await fs.realpath(siteDirParam);
process.env.NODE_ENV = 'development';
process.env.BABEL_ENV = 'development';
logger.info('Starting the development server...'); logger.info('Starting the development server...');
function loadSite() { function loadSite() {

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/eslint-plugin", "name": "@docusaurus/eslint-plugin",
"version": "2.3.1", "version": "2.4.0",
"description": "ESLint plugin to enforce best Docusaurus practices.", "description": "ESLint plugin to enforce best Docusaurus practices.",
"main": "lib/index.js", "main": "lib/index.js",
"keywords": [ "keywords": [

View file

@ -1,6 +1,6 @@
{ {
"name": "@docusaurus/lqip-loader", "name": "@docusaurus/lqip-loader",
"version": "2.3.1", "version": "2.4.0",
"description": "Low Quality Image Placeholders (LQIP) loader for webpack.", "description": "Low Quality Image Placeholders (LQIP) loader for webpack.",
"main": "lib/index.js", "main": "lib/index.js",
"publishConfig": { "publishConfig": {
@ -17,7 +17,7 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/logger": "2.3.1", "@docusaurus/logger": "2.4.0",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"sharp": "^0.30.7", "sharp": "^0.30.7",

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