chore: backport retro compatible commits for the Docusaurus v2.1 release (#8033)

Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
Co-authored-by: whiteand <andrewbeletskiy@gmail.com>
Co-authored-by: yzhe819 <68207314+yzhe819@users.noreply.github.com>
Co-authored-by: Ngô Quốc Đạt <56961917+datlechin@users.noreply.github.com>
Co-authored-by: Kevin Østerkilde <kevin@oesterkilde.dk>
Co-authored-by: Bagdasar Ovsepyan <66012777+b-ovsepian@users.noreply.github.com>
Co-authored-by: Yoni Chechik <chechik.yoni@gmail.com>
Co-authored-by: adventure-yunfei <adventure.yunfei@gmail.com>
Co-authored-by: Morgane Dubus <30866152+mdubus@users.noreply.github.com>
This commit is contained in:
Sébastien Lorber 2022-09-02 12:20:33 +02:00 committed by GitHub
parent bb65b5c578
commit 26d2b9a018
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
73 changed files with 764 additions and 250 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -36,7 +36,6 @@
"webpack": "^5.73.0" "webpack": "^5.73.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/types": "2.0.0-beta.21",
"escape-string-regexp": "^4.0.0" "escape-string-regexp": "^4.0.0"
}, },
"peerDependencies": { "peerDependencies": {

View file

@ -52,7 +52,6 @@
"webpack": "^5.73.0" "webpack": "^5.73.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/types": "2.0.0-beta.21",
"@types/js-yaml": "^4.0.5", "@types/js-yaml": "^4.0.5",
"@types/picomatch": "^2.3.0", "@types/picomatch": "^2.3.0",
"commander": "^5.1.0", "commander": "^5.1.0",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -44,6 +44,7 @@ export type SidebarItemLink = SidebarItemBase & {
type: 'link'; type: 'link';
href: string; href: string;
label: string; label: string;
autoAddBaseUrl?: boolean;
}; };
export type SidebarItemAutogenerated = SidebarItemBase & { export type SidebarItemAutogenerated = SidebarItemBase & {

View file

@ -59,6 +59,7 @@ const sidebarItemHtmlSchema = sidebarItemBaseSchema.append<SidebarItemHtml>({
const sidebarItemLinkSchema = sidebarItemBaseSchema.append<SidebarItemLink>({ const sidebarItemLinkSchema = sidebarItemBaseSchema.append<SidebarItemLink>({
type: 'link', type: 'link',
href: URISchema.required(), href: URISchema.required(),
autoAddBaseUrl: Joi.boolean(),
label: Joi.string() label: Joi.string()
.required() .required()
.messages({'any.unknown': '"label" must be a string'}), .messages({'any.unknown': '"label" must be a string'}),

View file

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

View file

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

View file

@ -27,9 +27,6 @@
"tslib": "^2.4.0", "tslib": "^2.4.0",
"webpack": "^5.73.0" "webpack": "^5.73.0"
}, },
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"

View file

@ -27,9 +27,6 @@
"react-json-view": "^1.21.3", "react-json-view": "^1.21.3",
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"

View file

@ -23,9 +23,6 @@
"@docusaurus/utils-validation": "2.0.1", "@docusaurus/utils-validation": "2.0.1",
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"

View file

@ -23,9 +23,6 @@
"@docusaurus/utils-validation": "2.0.1", "@docusaurus/utils-validation": "2.0.1",
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"

View file

@ -34,7 +34,6 @@
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "2.0.1", "@docusaurus/module-type-aliases": "2.0.1",
"@docusaurus/types": "2.0.0-beta.21",
"fs-extra": "^10.1.0" "fs-extra": "^10.1.0"
}, },
"peerDependencies": { "peerDependencies": {

View file

@ -28,9 +28,6 @@
"sitemap": "^7.1.1", "sitemap": "^7.1.1",
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.21"
},
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"

View file

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

View file

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

View file

@ -87,7 +87,7 @@ export default function preset(
throw new Error( throw new Error(
`Unrecognized keys ${Object.keys(rest).join( `Unrecognized keys ${Object.keys(rest).join(
', ', ', ',
)} found in preset-classic configuration. The allowed keys are debug, docs, blog, pages, sitemap, theme, googleAnalytics, gtag. Check the documentation: https://docusaurus.io/docs/presets#docusauruspreset-classic for more information on how to configure individual plugins.`, )} found in preset-classic configuration. The allowed keys are debug, docs, blog, pages, sitemap, theme, googleAnalytics, gtag. Check the documentation: https://docusaurus.io/docs/using-plugins#docusauruspreset-classic for more information on how to configure individual plugins.`,
); );
} }

View file

@ -47,8 +47,6 @@
"utility-types": "^3.10.0" "utility-types": "^3.10.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-beta.21",
"@docusaurus/types": "2.0.0-beta.21",
"@types/mdx-js__react": "^1.5.5", "@types/mdx-js__react": "^1.5.5",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/prismjs": "^1.26.0", "@types/prismjs": "^1.26.0",

View file

@ -28,6 +28,14 @@ export default function getSwizzleConfig(): SwizzleConfig {
description: description:
'The color mode toggle to switch between light and dark mode.', 'The color mode toggle to switch between light and dark mode.',
}, },
DocCardList: {
actions: {
eject: 'safe',
wrap: 'safe',
},
description:
'The component responsible for rendering a list of sidebar items cards.\nNotable used on the category generated-index pages.',
},
DocSidebar: { DocSidebar: {
actions: { actions: {
eject: 'unsafe', // Too much technical code in sidebar, not very safe atm eject: 'unsafe', // Too much technical code in sidebar, not very safe atm

View file

@ -55,6 +55,22 @@ declare module '@theme/AnnouncementBar' {
export default function AnnouncementBar(): JSX.Element | null; export default function AnnouncementBar(): JSX.Element | null;
} }
declare module '@theme/AnnouncementBar/Content' {
import type {ComponentProps} from 'react';
export interface Props extends ComponentProps<'div'> {}
export default function AnnouncementBarContent(props: Props): JSX.Element;
}
declare module '@theme/AnnouncementBar/CloseButton' {
import type {ComponentProps} from 'react';
export interface Props extends ComponentProps<'button'> {}
export default function AnnouncementBarCloseButton(props: Props): JSX.Element;
}
declare module '@theme/BackToTopButton' { declare module '@theme/BackToTopButton' {
export default function BackToTopButton(): JSX.Element; export default function BackToTopButton(): JSX.Element;
} }
@ -320,7 +336,7 @@ declare module '@theme/DocCardList' {
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs'; import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
export interface Props { export interface Props {
readonly items: PropSidebarItem[]; readonly items?: PropSidebarItem[];
readonly className?: string; readonly className?: string;
} }

View file

@ -0,0 +1,31 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import clsx from 'clsx';
import {translate} from '@docusaurus/Translate';
import IconClose from '@theme/Icon/Close';
import type {Props} from '@theme/AnnouncementBar/CloseButton';
import styles from './styles.module.css';
export default function AnnouncementBarCloseButton(
props: Props,
): JSX.Element | null {
return (
<button
type="button"
aria-label={translate({
id: 'theme.AnnouncementBar.closeButtonAriaLabel',
message: 'Close',
description: 'The ARIA label for close button of announcement bar',
})}
{...props}
className={clsx('clean-btn close', styles.closeButton, props.className)}>
<IconClose width={14} height={14} strokeWidth={3.1} />
</button>
);
}

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.
*/
.closeButton {
padding: 0;
line-height: 0;
}

View file

@ -0,0 +1,28 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import clsx from 'clsx';
import {useThemeConfig} from '@docusaurus/theme-common';
import type {Props} from '@theme/AnnouncementBar/Content';
import styles from './styles.module.css';
export default function AnnouncementBarContent(
props: Props,
): JSX.Element | null {
const {announcementBar} = useThemeConfig();
const {content} = announcementBar!;
return (
<div
{...props}
className={clsx(styles.content, props.className)}
// Developer provided the HTML, so assume it's safe.
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{__html: content}}
/>
);
}

View file

@ -0,0 +1,17 @@
/**
* 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.
*/
.content {
font-size: 85%;
text-align: center;
padding: 5px 0;
}
.content a {
color: inherit;
text-decoration: underline;
}

View file

@ -6,49 +6,33 @@
*/ */
import React from 'react'; import React from 'react';
import clsx from 'clsx';
import {useThemeConfig} from '@docusaurus/theme-common'; import {useThemeConfig} from '@docusaurus/theme-common';
import {useAnnouncementBar} from '@docusaurus/theme-common/internal'; import {useAnnouncementBar} from '@docusaurus/theme-common/internal';
import {translate} from '@docusaurus/Translate'; import AnnouncementBarCloseButton from '@theme/AnnouncementBar/CloseButton';
import IconClose from '@theme/Icon/Close'; import AnnouncementBarContent from '@theme/AnnouncementBar/Content';
import styles from './styles.module.css'; import styles from './styles.module.css';
export default function AnnouncementBar(): JSX.Element | null { export default function AnnouncementBar(): JSX.Element | null {
const {isActive, close} = useAnnouncementBar();
const {announcementBar} = useThemeConfig(); const {announcementBar} = useThemeConfig();
const {isActive, close} = useAnnouncementBar();
if (!isActive) { if (!isActive) {
return null; return null;
} }
const {backgroundColor, textColor, isCloseable} = announcementBar!;
const {content, backgroundColor, textColor, isCloseable} = announcementBar!;
return ( return (
<div <div
className={styles.announcementBar} className={styles.announcementBar}
style={{backgroundColor, color: textColor}} style={{backgroundColor, color: textColor}}
role="banner"> role="banner">
{isCloseable && <div className={styles.announcementBarPlaceholder} />} {isCloseable && <div className={styles.announcementBarPlaceholder} />}
<div <AnnouncementBarContent className={styles.announcementBarContent} />
className={styles.announcementBarContent} {isCloseable && (
// Developer provided the HTML, so assume it's safe. <AnnouncementBarCloseButton
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{__html: content}}
/>
{isCloseable ? (
<button
type="button"
className={clsx('clean-btn close', styles.announcementBarClose)}
onClick={close} onClick={close}
aria-label={translate({ className={styles.announcementBarClose}
id: 'theme.AnnouncementBar.closeButtonAriaLabel', />
message: 'Close', )}
description: 'The ARIA label for close button of announcement bar',
})}>
<IconClose width={14} height={14} strokeWidth={3.1} />
</button>
) : null}
</div> </div>
); );
} }

View file

@ -15,7 +15,8 @@
height: var(--docusaurus-announcement-bar-height); height: var(--docusaurus-announcement-bar-height);
background-color: var(--ifm-color-white); background-color: var(--ifm-color-white);
color: var(--ifm-color-black); color: var(--ifm-color-black);
border-bottom: 1px solid var(--ifm-color-emphasis-100); box-shadow: var(--ifm-global-shadow-lw);
z-index: calc(var(--ifm-z-index-fixed) + 1); /* just above the navbar */
} }
html[data-announcement-bar-initially-dismissed='true'] .announcementBar { html[data-announcement-bar-initially-dismissed='true'] .announcementBar {
@ -29,15 +30,10 @@ html[data-announcement-bar-initially-dismissed='true'] .announcementBar {
.announcementBarClose { .announcementBarClose {
flex: 0 0 30px; flex: 0 0 30px;
align-self: stretch; align-self: stretch;
padding: 0;
line-height: 0;
} }
.announcementBarContent { .announcementBarContent {
flex: 1 1 auto; flex: 1 1 auto;
font-size: 85%;
text-align: center;
padding: 5px 0;
} }
@media print { @media print {
@ -46,11 +42,6 @@ html[data-announcement-bar-initially-dismissed='true'] .announcementBar {
} }
} }
.announcementBarContent a {
color: inherit;
text-decoration: underline;
}
@media (min-width: 997px) { @media (min-width: 997px) {
:root { :root {
--docusaurus-announcement-bar-height: 30px; --docusaurus-announcement-bar-height: 30px;

View file

@ -35,6 +35,7 @@ the background in custom CSS file due bug https://github.com/facebook/docusaurus
left: 0; left: 0;
padding: 0 var(--ifm-pre-padding); padding: 0 var(--ifm-pre-padding);
background: var(--ifm-pre-background); background: var(--ifm-pre-background);
overflow-wrap: normal;
} }
.codeLineNumber::before { .codeLineNumber::before {

View file

@ -7,25 +7,27 @@
import React from 'react'; import React from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import {findFirstCategoryLink} from '@docusaurus/theme-common/internal'; import {
useCurrentSidebarCategory,
filterDocCardListItems,
} from '@docusaurus/theme-common';
import DocCard from '@theme/DocCard'; import DocCard from '@theme/DocCard';
import type {Props} from '@theme/DocCardList'; import type {Props} from '@theme/DocCardList';
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
// Filter categories that don't have a link. function DocCardListForCurrentSidebarCategory({className}: Props) {
function filterItems(items: PropSidebarItem[]): PropSidebarItem[] { const category = useCurrentSidebarCategory();
return items.filter((item) => { return <DocCardList items={category.items} className={className} />;
if (item.type === 'category') {
return !!findFirstCategoryLink(item);
}
return true;
});
} }
export default function DocCardList({items, className}: Props): JSX.Element { export default function DocCardList(props: Props): JSX.Element {
const {items, className} = props;
if (!items) {
return <DocCardListForCurrentSidebarCategory {...props} />;
}
const filteredItems = filterDocCardListItems(items);
return ( return (
<section className={clsx('row', className)}> <section className={clsx('row', className)}>
{filterItems(items).map((item, index) => ( {filteredItems.map((item, index) => (
<article key={index} className="col col--6 margin-bottom--lg"> <article key={index} className="col col--6 margin-bottom--lg">
<DocCard item={item} /> <DocCard item={item} />
</article> </article>

View file

@ -7,7 +7,11 @@
import React from 'react'; import React from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import {HtmlClassNameProvider, ThemeClassNames} from '@docusaurus/theme-common'; import {
HtmlClassNameProvider,
ThemeClassNames,
PageMetadata,
} from '@docusaurus/theme-common';
import { import {
docVersionSearchTag, docVersionSearchTag,
DocsSidebarProvider, DocsSidebarProvider,
@ -19,13 +23,8 @@ import NotFound from '@theme/NotFound';
import SearchMetadata from '@theme/SearchMetadata'; import SearchMetadata from '@theme/SearchMetadata';
import type {Props} from '@theme/DocPage'; import type {Props} from '@theme/DocPage';
export default function DocPage(props: Props): JSX.Element { function DocPageMetadata(props: Props): JSX.Element {
const {versionMetadata} = props; const {versionMetadata} = props;
const currentDocRouteMetadata = useDocRouteMetadata(props);
if (!currentDocRouteMetadata) {
return <NotFound />;
}
const {docElement, sidebarName, sidebarItems} = currentDocRouteMetadata;
return ( return (
<> <>
<SearchMetadata <SearchMetadata
@ -35,6 +34,25 @@ export default function DocPage(props: Props): JSX.Element {
versionMetadata.version, versionMetadata.version,
)} )}
/> />
<PageMetadata>
{versionMetadata.noIndex && (
<meta name="robots" content="noindex, nofollow" />
)}
</PageMetadata>
</>
);
}
export default function DocPage(props: Props): JSX.Element {
const {versionMetadata} = props;
const currentDocRouteMetadata = useDocRouteMetadata(props);
if (!currentDocRouteMetadata) {
return <NotFound />;
}
const {docElement, sidebarName, sidebarItems} = currentDocRouteMetadata;
return (
<>
<DocPageMetadata {...props} />
<HtmlClassNameProvider <HtmlClassNameProvider
className={clsx( className={clsx(
// TODO: it should be removed from here // TODO: it should be removed from here

View file

@ -136,7 +136,7 @@ export default function DocSidebarItemCategory({
useEffect(() => { useEffect(() => {
if ( if (
collapsible && collapsible &&
expandedItem && expandedItem != null &&
expandedItem !== index && expandedItem !== index &&
autoCollapseCategories autoCollapseCategories
) { ) {

View file

@ -24,7 +24,7 @@ export default function DocSidebarItemLink({
index, index,
...props ...props
}: Props): JSX.Element { }: Props): JSX.Element {
const {href, label, className} = item; const {href, label, className, autoAddBaseUrl} = item;
const isActive = isActiveSidebarItem(item, activePath); const isActive = isActiveSidebarItem(item, activePath);
const isInternalLink = isInternalUrl(href); const isInternalLink = isInternalUrl(href);
return ( return (
@ -44,6 +44,7 @@ export default function DocSidebarItemLink({
'menu__link--active': isActive, 'menu__link--active': isActive,
}, },
)} )}
autoAddBaseUrl={autoAddBaseUrl}
aria-current={isActive ? 'page' : undefined} aria-current={isActive ? 'page' : undefined}
to={href} to={href}
{...(isInternalLink && { {...(isInternalLink && {

View file

@ -34,6 +34,7 @@ export default function LocaleDropdownNavbarItem({
})}`; })}`;
return { return {
label: localeConfigs[locale]!.label, label: localeConfigs[locale]!.label,
lang: localeConfigs[locale]!.htmlLang,
to, to,
target: '_self', target: '_self',
autoAddBaseUrl: false, autoAddBaseUrl: false,

View file

@ -6,7 +6,7 @@
*/ */
import React from 'react'; import React from 'react';
import Translate from '@docusaurus/Translate'; import Translate, {translate} from '@docusaurus/Translate';
import {useSkipToContent} from '@docusaurus/theme-common/internal'; import {useSkipToContent} from '@docusaurus/theme-common/internal';
import styles from './styles.module.css'; import styles from './styles.module.css';
@ -14,7 +14,10 @@ import styles from './styles.module.css';
export default function SkipToContent(): JSX.Element { export default function SkipToContent(): JSX.Element {
const {containerRef, handleSkip} = useSkipToContent(); const {containerRef, handleSkip} = useSkipToContent();
return ( return (
<div ref={containerRef} role="region"> <div
ref={containerRef}
role="region"
aria-label={translate({id: 'theme.common.skipToMainContent'})}>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a href="#" className={styles.skipToContent} onClick={handleSkip}> <a href="#" className={styles.skipToContent} onClick={handleSkip}>
<Translate <Translate

View file

@ -21,7 +21,7 @@
.tagRegular { .tagRegular {
border-radius: 0.5rem; border-radius: 0.5rem;
padding: 0.3rem 0.5rem; padding: 0.2rem 0.5rem 0.3rem;
font-size: 90%; font-size: 90%;
} }

View file

@ -28,7 +28,10 @@ export {createStorageSlot, listStorageKeys} from './utils/storageUtils';
export {useContextualSearchFilters} from './utils/searchUtils'; export {useContextualSearchFilters} from './utils/searchUtils';
export {useCurrentSidebarCategory} from './utils/docsUtils'; export {
useCurrentSidebarCategory,
filterDocCardListItems,
} from './utils/docsUtils';
export {usePluralForm} from './utils/usePluralForm'; export {usePluralForm} from './utils/usePluralForm';

View file

@ -441,26 +441,87 @@ describe('useCurrentSidebarCategory', () => {
</DocsSidebarProvider> </DocsSidebarProvider>
), ),
}).result.current; }).result.current;
it('works', () => {
const category: PropSidebarItemCategory = { it('works for sidebar category', () => {
type: 'category', const category: PropSidebarItemCategory = testCategory({
label: 'Category',
href: '/cat', href: '/cat',
collapsible: true, });
collapsed: false, const sidebar: PropSidebar = [
items: [ testLink(),
{type: 'link', href: '/cat/foo', label: 'Foo'}, testLink(),
{type: 'link', href: '/cat/bar', label: 'Bar'},
{type: 'link', href: '/baz', label: 'Baz'},
],
};
const mockUseCurrentSidebarCategory = createUseCurrentSidebarCategoryMock([
{type: 'link', href: '/cat/fake', label: 'Fake'},
category, category,
]); testCategory(),
];
const mockUseCurrentSidebarCategory =
createUseCurrentSidebarCategoryMock(sidebar);
expect(mockUseCurrentSidebarCategory('/cat')).toEqual(category); expect(mockUseCurrentSidebarCategory('/cat')).toEqual(category);
}); });
it('works for nested sidebar category', () => {
const category2: PropSidebarItemCategory = testCategory({
href: '/cat2',
});
const category1: PropSidebarItemCategory = testCategory({
href: '/cat1',
items: [testLink(), testLink(), category2, testCategory()],
});
const sidebar: PropSidebar = [
testLink(),
testLink(),
category1,
testCategory(),
];
const mockUseCurrentSidebarCategory =
createUseCurrentSidebarCategoryMock(sidebar);
expect(mockUseCurrentSidebarCategory('/cat2')).toEqual(category2);
});
it('works for category link item', () => {
const link = testLink({href: '/my/link/path'});
const category: PropSidebarItemCategory = testCategory({
href: '/cat1',
items: [testLink(), testLink(), link, testCategory()],
});
const sidebar: PropSidebar = [
testLink(),
testLink(),
category,
testCategory(),
];
const mockUseCurrentSidebarCategory =
createUseCurrentSidebarCategoryMock(sidebar);
expect(mockUseCurrentSidebarCategory('/my/link/path')).toEqual(category);
});
it('works for nested category link item', () => {
const link = testLink({href: '/my/link/path'});
const category2: PropSidebarItemCategory = testCategory({
href: '/cat2',
items: [testLink(), testLink(), link, testCategory()],
});
const category1: PropSidebarItemCategory = testCategory({
href: '/cat1',
items: [testLink(), testLink(), category2, testCategory()],
});
const sidebar: PropSidebar = [
testLink(),
testLink(),
category1,
testCategory(),
];
const mockUseCurrentSidebarCategory =
createUseCurrentSidebarCategoryMock(sidebar);
expect(mockUseCurrentSidebarCategory('/my/link/path')).toEqual(category2);
});
it('throws for non-category index page', () => { it('throws for non-category index page', () => {
const category: PropSidebarItemCategory = { const category: PropSidebarItemCategory = {
type: 'category', type: 'category',

View file

@ -110,15 +110,18 @@ export function useCurrentSidebarCategory(): PropSidebarItemCategory {
if (!sidebar) { if (!sidebar) {
throw new Error('Unexpected: cant find current sidebar in context'); throw new Error('Unexpected: cant find current sidebar in context');
} }
const category = findSidebarCategory(sidebar.items, (item) => const categoryBreadcrumbs = getSidebarBreadcrumbs({
isSamePath(item.href, pathname), sidebarItems: sidebar.items,
); pathname,
if (!category) { onlyCategories: true,
});
const deepestCategory = categoryBreadcrumbs.slice(-1)[0];
if (!deepestCategory) {
throw new Error( throw new Error(
`${pathname} is not associated with a category. useCurrentSidebarCategory() should only be used on category index pages.`, `${pathname} is not associated with a category. useCurrentSidebarCategory() should only be used on category index pages.`,
); );
} }
return category; return deepestCategory;
} }
const isActive = (testedPath: string | undefined, activePath: string) => const isActive = (testedPath: string | undefined, activePath: string) =>
@ -149,6 +152,55 @@ export function isActiveSidebarItem(
return false; return false;
} }
function getSidebarBreadcrumbs(param: {
sidebarItems: PropSidebar;
pathname: string;
onlyCategories: true;
}): PropSidebarItemCategory[];
function getSidebarBreadcrumbs(param: {
sidebarItems: PropSidebar;
pathname: string;
}): PropSidebarBreadcrumbsItem[];
/**
* Get the sidebar the breadcrumbs for a given pathname
* Ordered from top to bottom
*/
function getSidebarBreadcrumbs({
sidebarItems,
pathname,
onlyCategories = false,
}: {
sidebarItems: PropSidebar;
pathname: string;
onlyCategories?: boolean;
}): PropSidebarBreadcrumbsItem[] {
const breadcrumbs: PropSidebarBreadcrumbsItem[] = [];
function extract(items: PropSidebarItem[]) {
for (const item of items) {
if (
(item.type === 'category' &&
(isSamePath(item.href, pathname) || extract(item.items))) ||
(item.type === 'link' && isSamePath(item.href, pathname))
) {
const filtered = onlyCategories && item.type !== 'category';
if (!filtered) {
breadcrumbs.unshift(item);
}
return true;
}
}
return false;
}
extract(sidebarItems);
return breadcrumbs;
}
/** /**
* Gets the breadcrumbs of the current doc page, based on its sidebar location. * Gets the breadcrumbs of the current doc page, based on its sidebar location.
* Returns `null` if there's no sidebar or breadcrumbs are disabled. * Returns `null` if there's no sidebar or breadcrumbs are disabled.
@ -157,31 +209,10 @@ export function useSidebarBreadcrumbs(): PropSidebarBreadcrumbsItem[] | null {
const sidebar = useDocsSidebar(); const sidebar = useDocsSidebar();
const {pathname} = useLocation(); const {pathname} = useLocation();
const breadcrumbsOption = useActivePlugin()?.pluginData.breadcrumbs; const breadcrumbsOption = useActivePlugin()?.pluginData.breadcrumbs;
if (breadcrumbsOption === false || !sidebar) { if (breadcrumbsOption === false || !sidebar) {
return null; return null;
} }
return getSidebarBreadcrumbs({sidebarItems: sidebar.items, pathname});
const breadcrumbs: PropSidebarBreadcrumbsItem[] = [];
function extract(items: PropSidebar) {
for (const item of items) {
if (
(item.type === 'category' &&
(isSamePath(item.href, pathname) || extract(item.items))) ||
(item.type === 'link' && isSamePath(item.href, pathname))
) {
breadcrumbs.push(item);
return true;
}
}
return false;
}
extract(sidebar.items);
return breadcrumbs.reverse();
} }
/** /**
@ -332,3 +363,18 @@ export function useDocRouteMetadata({
sidebarItems, sidebarItems,
}; };
} }
/**
* Filter categories that don't have a link.
* @param items
*/
export function filterDocCardListItems(
items: PropSidebarItem[],
): PropSidebarItem[] {
return items.filter((item) => {
if (item.type === 'category') {
return !!findFirstCategoryLink(item);
}
return true;
});
}

View file

@ -0,0 +1,7 @@
{
"theme.IdealImageMessage.404error": "404. Зображення не знайдено",
"theme.IdealImageMessage.error": "Помилка. Натисніть, щоб перезавантажити",
"theme.IdealImageMessage.load": "Натисніть, щоб завантажити {sizeMessage}",
"theme.IdealImageMessage.loading": "Завантаження...",
"theme.IdealImageMessage.offline": "Ваш браузер перебуває в автономному режимі. Зображення не завантажено"
}

View file

@ -0,0 +1,5 @@
{
"theme.PwaReloadPopup.closeButtonAriaLabel": "Закрити",
"theme.PwaReloadPopup.info": "Доступна нова версія",
"theme.PwaReloadPopup.refreshButtonText": "Оновити"
}

View file

@ -0,0 +1,66 @@
{
"theme.AnnouncementBar.closeButtonAriaLabel": "Закрити",
"theme.BackToTopButton.buttonAriaLabel": "Прокрутити до початку",
"theme.CodeBlock.copied": "Скопійовано",
"theme.CodeBlock.copy": "Копіювати",
"theme.CodeBlock.copyButtonAriaLabel": "Копіювати в буфер обміну",
"theme.CodeBlock.wordWrapToggle": "Перемикання обведення слів",
"theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "Згорнути/розгорнути категорію '{label}'",
"theme.ErrorPageContent.title": "На сторінці стався збій.",
"theme.ErrorPageContent.tryAgain": "Спробуйте ще раз",
"theme.NotFound.p1": "На жаль, ми не змогли знайти сторінку, яку ви запитували.",
"theme.NotFound.p2": "Будь ласка, зверніться до власника сайту, з якого ви перейшли на це посилання, щоб повідомити, що посилання не працює.",
"theme.NotFound.title": "Сторінку не знайдено",
"theme.TOCCollapsible.toggleButtonLabel": "Зміст цієї сторінки",
"theme.admonition.caution": "обережно",
"theme.admonition.danger": "небезпека",
"theme.admonition.info": "інформація",
"theme.admonition.note": "примітка",
"theme.admonition.tip": "порада",
"theme.blog.archive.description": "Архів",
"theme.blog.archive.title": "Архів",
"theme.blog.paginator.navAriaLabel": "Навігація по сторінці списку блогів",
"theme.blog.paginator.newerEntries": "Наступні записи",
"theme.blog.paginator.olderEntries": "Попередні записи",
"theme.blog.post.paginator.navAriaLabel": "Навігація по сторінці посту блогу",
"theme.blog.post.paginator.newerPost": "Наступний пост",
"theme.blog.post.paginator.olderPost": "Попередній пост",
"theme.blog.post.plurals": "{count} запис|{count} записи|{count} записів",
"theme.blog.post.readMore": "Читати далі",
"theme.blog.post.readMoreLabel": "Докладніше про {title}",
"theme.blog.post.readingTime.plurals": "{readingTime} хв. читання",
"theme.blog.sidebar.navAriaLabel": "Навігація за останніми постами у блозі",
"theme.blog.tagTitle": "{nPosts} з тегом \"{tagName}\"",
"theme.colorToggle.ariaLabel": "Перемикання між темним та світлим режимом (зараз використовується {mode})",
"theme.colorToggle.ariaLabel.mode.dark": "Темний режим",
"theme.colorToggle.ariaLabel.mode.light": "Світлий режим",
"theme.common.editThisPage": "Відредагувати цю сторінку",
"theme.common.headingLinkTitle": "Пряме посилання на цей заголовок",
"theme.common.skipToMainContent": "Перейти до основного вмісту",
"theme.docs.DocCard.categoryDescription": "{count} елемент|{count} елементи|{count} елементів",
"theme.docs.breadcrumbs.home": "Головна сторінка",
"theme.docs.breadcrumbs.navAriaLabel": "Навігаційний ланцюжок поточної сторінки",
"theme.docs.paginator.navAriaLabel": "Навігація по сторінці документації",
"theme.docs.paginator.next": "Наступна сторінка",
"theme.docs.paginator.previous": "Попередня сторінка",
"theme.docs.sidebar.collapseButtonAriaLabel": "Згорнути сайдбар",
"theme.docs.sidebar.collapseButtonTitle": "Згорнути сайдбар",
"theme.docs.sidebar.expandButtonAriaLabel": "Розгорнути сайдбар",
"theme.docs.sidebar.expandButtonTitle": "Розгорнути сайдбар",
"theme.docs.tagDocListPageTitle": "{nDocsTagged} з тегом \"{tagName}\"",
"theme.docs.tagDocListPageTitle.nDocsTagged": "Одна сторінка|{count} сторінки|{count} сторінок",
"theme.docs.versionBadge.label": "Версія: {versionLabel}",
"theme.docs.versions.latestVersionLinkLabel": "остання версія",
"theme.docs.versions.latestVersionSuggestionLabel": "Актуальна документація знаходиться на сторінці {latestVersionLink} ({versionLabel}).",
"theme.docs.versions.unmaintainedVersionLabel": "Це документація {siteTitle} для версії {versionLabel}, яка вже не підтримується.",
"theme.docs.versions.unreleasedVersionLabel": "Це документація для майбутньої версії {siteTitle} {versionLabel}.",
"theme.lastUpdated.atDate": " {date}",
"theme.lastUpdated.byUser": " від {user}",
"theme.lastUpdated.lastUpdatedAtBy": "Останнє оновлення{atDate}{byUser}",
"theme.navbar.mobileLanguageDropdown.label": "Мови",
"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Перейти до головного меню",
"theme.navbar.mobileVersionsDropdown.label": "Версії",
"theme.tags.tagsListLabel": "Теги:",
"theme.tags.tagsPageLink": "Переглянути всі теги",
"theme.tags.tagsPageTitle": "Теги"
}

View file

@ -0,0 +1,4 @@
{
"theme.Playground.liveEditor": "Інтерактивний редактор",
"theme.Playground.result": "Результат"
}

View file

@ -0,0 +1,35 @@
{
"theme.SearchBar.label": "Пошук",
"theme.SearchBar.seeAll": "Переглянути всі результати ({count})",
"theme.SearchModal.errorScreen.helpText": "Перевірте підключення до мережі.",
"theme.SearchModal.errorScreen.titleText": "Не вдалося отримати результати",
"theme.SearchModal.footer.closeKeyAriaLabel": "Клавіша Escape",
"theme.SearchModal.footer.closeText": "закрити",
"theme.SearchModal.footer.navigateDownKeyAriaLabel": "Стрілка вниз",
"theme.SearchModal.footer.navigateText": "до навігації",
"theme.SearchModal.footer.navigateUpKeyAriaLabel": "Стрілка вгору",
"theme.SearchModal.footer.searchByText": "Пошук за допомогою",
"theme.SearchModal.footer.selectKeyAriaLabel": "Клавіша Enter",
"theme.SearchModal.footer.selectText": "обрати",
"theme.SearchModal.noResultsScreen.noResultsText": "Немає результатів для",
"theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": "Дайте нам знати.",
"theme.SearchModal.noResultsScreen.reportMissingResultsText": "Чи вважаєте ви, що цей запит має повернути результати?",
"theme.SearchModal.noResultsScreen.suggestedQueryText": "Спробуйте пошукати",
"theme.SearchModal.placeholder": "Пошук документів",
"theme.SearchModal.searchBox.cancelButtonText": "Скасувати",
"theme.SearchModal.searchBox.resetButtonTitle": "Очистити запит",
"theme.SearchModal.startScreen.favoriteSearchesTitle": "Обране",
"theme.SearchModal.startScreen.noRecentSearchesText": "Немає останніх пошуків",
"theme.SearchModal.startScreen.recentSearchesTitle": "Останні",
"theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle": "Видалити цей пошук з обраного",
"theme.SearchModal.startScreen.removeRecentSearchButtonTitle": "Видалити цей пошук з історії",
"theme.SearchModal.startScreen.saveRecentSearchButtonTitle": "Зберегти цей пошук",
"theme.SearchPage.algoliaLabel": "Пошук за допомогою Algolia",
"theme.SearchPage.documentsFound.plurals": "{count} документ|{count} документи|{count} документів",
"theme.SearchPage.emptyResultsTitle": "Пошук по сайту",
"theme.SearchPage.existingResultsTitle": "Результати пошуку за запитом \"{query}\"",
"theme.SearchPage.fetchingNewResults": "Завантаження нових результатів пошуку...",
"theme.SearchPage.inputLabel": "Пошук",
"theme.SearchPage.inputPlaceholder": "Введіть фразу для пошуку",
"theme.SearchPage.noResultsText": "За запитом нічого не знайдено"
}

View file

@ -1,29 +1,29 @@
{ {
"theme.SearchBar.label": "Tìm kiếm", "theme.SearchBar.label": "Tìm kiếm",
"theme.SearchBar.seeAll": "Xem tất cả {count} kết quả", "theme.SearchBar.seeAll": "Xem tất cả {count} kết quả",
"theme.SearchModal.errorScreen.helpText": "You might want to check your network connection.", "theme.SearchModal.errorScreen.helpText": "Bạn nên kiểm tra lại kết nối mạng của mình.",
"theme.SearchModal.errorScreen.titleText": "Unable to fetch results", "theme.SearchModal.errorScreen.titleText": "Không thể tìm nạp dữ liệu",
"theme.SearchModal.footer.closeKeyAriaLabel": "Escape key", "theme.SearchModal.footer.closeKeyAriaLabel": "Phím thoát",
"theme.SearchModal.footer.closeText": "to close", "theme.SearchModal.footer.closeText": "để đóng",
"theme.SearchModal.footer.navigateDownKeyAriaLabel": "Arrow down", "theme.SearchModal.footer.navigateDownKeyAriaLabel": "Mũi tên xuống",
"theme.SearchModal.footer.navigateText": "to navigate", "theme.SearchModal.footer.navigateText": "để điều hướng",
"theme.SearchModal.footer.navigateUpKeyAriaLabel": "Arrow up", "theme.SearchModal.footer.navigateUpKeyAriaLabel": "Mũi tên lên",
"theme.SearchModal.footer.searchByText": "Search by", "theme.SearchModal.footer.searchByText": "Tìm kiếm theo",
"theme.SearchModal.footer.selectKeyAriaLabel": "Enter key", "theme.SearchModal.footer.selectKeyAriaLabel": "Nhập khóa",
"theme.SearchModal.footer.selectText": "to select", "theme.SearchModal.footer.selectText": "để chọn",
"theme.SearchModal.noResultsScreen.noResultsText": "No results for", "theme.SearchModal.noResultsScreen.noResultsText": "Không có kết quả dành cho",
"theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": "Let us know.", "theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": "Hãy để chúng tôi biết.",
"theme.SearchModal.noResultsScreen.reportMissingResultsText": "Believe this query should return results?", "theme.SearchModal.noResultsScreen.reportMissingResultsText": "Tin rằng truy vấn này sẽ trả về kết quả?",
"theme.SearchModal.noResultsScreen.suggestedQueryText": "Try searching for", "theme.SearchModal.noResultsScreen.suggestedQueryText": "Thử tìm kiếm",
"theme.SearchModal.placeholder": "Search docs", "theme.SearchModal.placeholder": "Tìm kiếm tài liệu",
"theme.SearchModal.searchBox.cancelButtonText": "Cancel", "theme.SearchModal.searchBox.cancelButtonText": "Hủy bỏ",
"theme.SearchModal.searchBox.resetButtonTitle": "Clear the query", "theme.SearchModal.searchBox.resetButtonTitle": "Xóa truy vấn",
"theme.SearchModal.startScreen.favoriteSearchesTitle": "Favorite", "theme.SearchModal.startScreen.favoriteSearchesTitle": "Yêu thích",
"theme.SearchModal.startScreen.noRecentSearchesText": "No recent searches", "theme.SearchModal.startScreen.noRecentSearchesText": "Không có tìm kiếm nào gần đây",
"theme.SearchModal.startScreen.recentSearchesTitle": "Recent", "theme.SearchModal.startScreen.recentSearchesTitle": "Gần đây",
"theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle": "Remove this search from favorites", "theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle": "Xóa tìm kiếm này khỏi danh sách yêu thích",
"theme.SearchModal.startScreen.removeRecentSearchButtonTitle": "Remove this search from history", "theme.SearchModal.startScreen.removeRecentSearchButtonTitle": "Xóa tìm kiếm này khỏi lịch sử",
"theme.SearchModal.startScreen.saveRecentSearchButtonTitle": "Save this search", "theme.SearchModal.startScreen.saveRecentSearchButtonTitle": "Lưu tìm kiếm này",
"theme.SearchPage.algoliaLabel": "Tìm kiếm với Algolia", "theme.SearchPage.algoliaLabel": "Tìm kiếm với Algolia",
"theme.SearchPage.documentsFound.plurals": "Tìm thấy một kết quả|Tìm thấy {count} kết quả", "theme.SearchPage.documentsFound.plurals": "Tìm thấy một kết quả|Tìm thấy {count} kết quả",
"theme.SearchPage.emptyResultsTitle": "Tìm kiếm", "theme.SearchPage.emptyResultsTitle": "Tìm kiếm",

View file

@ -11,8 +11,9 @@ export type I18nLocaleConfig = {
/** The label displayed for this locale in the locales dropdown. */ /** The label displayed for this locale in the locales dropdown. */
label: string; label: string;
/** /**
* BCP 47 language tag to use in `<html lang="...">` and in * BCP 47 language tag to use in:
* `<link ... hreflang="...">` * - `<html lang="...">` (or any other DOM tag name)
* - `<link ... hreflang="...">`
*/ */
htmlLang: string; htmlLang: string;
/** Used to select the locale's CSS and html meta attribute. */ /** Used to select the locale's CSS and html meta attribute. */

View file

@ -86,7 +86,7 @@ export function getFileLoaderUtils(): FileLoaderUtils {
*/ */
images: () => ({ images: () => ({
use: [loaders.url({folder: 'images'})], use: [loaders.url({folder: 'images'})],
test: /\.(?:ico|jpe?g|png|gif|webp)(?:\?.*)?$/i, test: /\.(?:ico|jpe?g|png|gif|webp|avif)(?:\?.*)?$/i,
}), }),
fonts: () => ({ fonts: () => ({
@ -100,7 +100,7 @@ export function getFileLoaderUtils(): FileLoaderUtils {
*/ */
media: () => ({ media: () => ({
use: [loaders.url({folder: 'medias'})], use: [loaders.url({folder: 'medias'})],
test: /\.(?:mp4|webm|ogv|wav|mp3|m4a|aac|oga|flac)$/i, test: /\.(?:mp4|avi|mov|mkv|mpg|mpeg|vob|wmv|m4v|webm|ogv|wav|mp3|m4a|aac|oga|flac)$/i,
}), }),
svg: () => ({ svg: () => ({

View file

@ -12,7 +12,7 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
export default function SiteMetadataDefaults(): JSX.Element { export default function SiteMetadataDefaults(): JSX.Element {
const { const {
siteConfig: {favicon, title}, siteConfig: {favicon, title, noIndex},
i18n: {currentLocale, localeConfigs}, i18n: {currentLocale, localeConfigs},
} = useDocusaurusContext(); } = useDocusaurusContext();
const faviconUrl = useBaseUrl(favicon); const faviconUrl = useBaseUrl(favicon);
@ -20,9 +20,17 @@ export default function SiteMetadataDefaults(): JSX.Element {
return ( return (
<Head> <Head>
{/*
charSet + generator are handled in the html templates
See https://github.com/facebook/docusaurus/pull/7952
<meta charSet="UTF-8" />
<meta name="generator" content={`Docusaurus v${docusaurusVersion}`} />
*/}
<html lang={htmlLang} dir={htmlDir} /> <html lang={htmlLang} dir={htmlDir} />
<title>{title}</title> <title>{title}</title>
<meta property="og:title" content={title} /> <meta property="og:title" content={title} />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{noIndex && <meta name="robots" content="noindex, nofollow" />}
{favicon && <link rel="icon" href={faviconUrl} />} {favicon && <link rel="icon" href={faviconUrl} />}
</Head> </Head>
); );

View file

@ -17,7 +17,9 @@ import {matchRoutes} from 'react-router-config';
* @returns Promise object represents whether pathname has been preloaded * @returns Promise object represents whether pathname has been preloaded
*/ */
export default function preload(pathname: string): Promise<void[]> { export default function preload(pathname: string): Promise<void[]> {
const matches = matchRoutes(routes, pathname); const matches = Array.from(new Set([pathname, decodeURI(pathname)]))
.map((p) => matchRoutes(routes, p))
.flat();
return Promise.all(matches.map((match) => match.route.component.preload?.())); return Promise.all(matches.map((match) => match.route.component.preload?.()));
} }

View file

@ -29,6 +29,14 @@ export type WriteTranslationsCLIOptions = Pick<
> & > &
WriteTranslationsOptions; WriteTranslationsOptions;
function resolveThemeCommonLibDir(): string | undefined {
try {
return path.dirname(require.resolve('@docusaurus/theme-common'));
} catch {
return undefined;
}
}
/** /**
* This is a hack, so that @docusaurus/theme-common translations are extracted! * This is a hack, so that @docusaurus/theme-common translations are extracted!
* A theme doesn't have a way to express that one of its dependency (like * A theme doesn't have a way to express that one of its dependency (like
@ -37,14 +45,11 @@ export type WriteTranslationsCLIOptions = Pick<
* We just make an exception and assume that user is using an official theme * We just make an exception and assume that user is using an official theme
*/ */
async function getExtraSourceCodeFilePaths(): Promise<string[]> { async function getExtraSourceCodeFilePaths(): Promise<string[]> {
try { const themeCommonLibDir = resolveThemeCommonLibDir();
const themeCommonSourceDir = path.dirname( if (!themeCommonLibDir) {
require.resolve('@docusaurus/theme-common/lib'),
);
return globSourceCodeFilePaths([themeCommonSourceDir]);
} catch {
return []; // User may not use a Docusaurus official theme? Quite unlikely... return []; // User may not use a Docusaurus official theme? Quite unlikely...
} }
return globSourceCodeFilePaths([themeCommonLibDir]);
} }
async function writePluginTranslationFiles({ async function writePluginTranslationFiles({
@ -108,6 +113,7 @@ Available locales are: ${context.i18n.locales.join(',')}.`,
babelOptions, babelOptions,
await getExtraSourceCodeFilePaths(), await getExtraSourceCodeFilePaths(),
); );
const defaultCodeMessages = await getPluginsDefaultCodeTranslationMessages( const defaultCodeMessages = await getPluginsDefaultCodeTranslationMessages(
plugins, plugins,
); );

View file

@ -2,7 +2,6 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Docusaurus"> <meta name="generator" content="Docusaurus">
<title><%= htmlWebpackPlugin.options.title %></title> <title><%= htmlWebpackPlugin.options.title %></title>
<%= htmlWebpackPlugin.options.headTags %> <%= htmlWebpackPlugin.options.headTags %>

View file

@ -10,15 +10,11 @@ export default `
<html <%~ it.htmlAttributes %>> <html <%~ it.htmlAttributes %>>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Docusaurus v<%= it.version %>"> <meta name="generator" content="Docusaurus v<%= it.version %>">
<% if (it.noIndex) { %>
<meta name="robots" content="noindex, nofollow" />
<% } %>
<%~ it.headTags %>
<% it.metaAttributes.forEach((metaAttribute) => { %> <% it.metaAttributes.forEach((metaAttribute) => { %>
<%~ metaAttribute %> <%~ metaAttribute %>
<% }); %> <% }); %>
<%~ it.headTags %>
<% it.stylesheets.forEach((stylesheet) => { %> <% it.stylesheets.forEach((stylesheet) => { %>
<link rel="stylesheet" href="<%= it.baseUrl %><%= stylesheet %>" /> <link rel="stylesheet" href="<%= it.baseUrl %><%= stylesheet %>" />
<% }); %> <% }); %>

View file

@ -249,3 +249,21 @@ echo "short_initially_hidden_string"
</Tabs> </Tabs>
[// spell-checker:enable]: # [// spell-checker:enable]: #
```jsx showLineNumbers
import React from 'react';
import Layout from '@theme/Layout';
export default function MyReactPage() {
return (
<Layout>
<h1>My React page</h1>
<p>
This is a React page. Let's make this sentence bit long. Some more words
to make sure... Some more words to make sure... Some more words to make
sure...
</p>
</Layout>
);
}
```

View file

@ -0,0 +1,17 @@
## Head Metadata tests
This page declares the following custom head metadata:
```html
<head>
<meta name="generator" value="custom generator name!" />
<meta name="viewport" content="initial-scale=1, viewport-fit=cover" />
<meta name="robots" content="noindex, nofollow, my-extra-directive" />
</head>
```
<head>
<meta name="generator" value="custom generator name!" />
<meta name="viewport" content="initial-scale=1, viewport-fit=cover" />
<meta name="robots" content="noindex, nofollow, my-extra-directive" />
</head>

View file

@ -29,3 +29,4 @@ import Readme from "../README.md"
- [TOC tests](/tests/pages/page-toc-tests) - [TOC tests](/tests/pages/page-toc-tests)
- [Tabs tests](/tests/pages/tabs-tests) - [Tabs tests](/tests/pages/tabs-tests)
- [z-index tests](/tests/pages/z-index-tests) - [z-index tests](/tests/pages/z-index-tests)
- [Head metadata tests](/tests/pages/head-metadata)

View file

@ -71,10 +71,27 @@ const sidebars = {
], ],
}, },
{ {
type: 'link', type: 'category',
label: 'External link', label: 'Link tests',
href: 'https://github.com/facebook/docusaurus',
className: 'red', className: 'red',
items: [
{
type: 'link',
label: 'External link absolute',
href: 'https://github.com/facebook/docusaurus',
},
{
type: 'link',
label: 'pathname:/// link',
href: 'pathname:///some/local/path',
},
{
type: 'link',
label: 'pathname:/// link (no baseUrl)',
href: 'pathname:///some/local/path',
autoAddBaseUrl: false,
},
],
}, },
{ {
type: 'category', type: 'category',

View file

@ -26,6 +26,11 @@ const dogfoodingPluginInstances = [
id: 'docs-tests', id: 'docs-tests',
routeBasePath: '/tests/docs', routeBasePath: '/tests/docs',
sidebarPath: '_dogfooding/docs-tests-sidebars.js', sidebarPath: '_dogfooding/docs-tests-sidebars.js',
versions: {
current: {
noIndex: true,
},
},
// Using a _ prefix to test against an edge case regarding MDX partials: https://github.com/facebook/docusaurus/discussions/5181#discussioncomment-1018079 // Using a _ prefix to test against an edge case regarding MDX partials: https://github.com/facebook/docusaurus/discussions/5181#discussioncomment-1018079
path: '_dogfooding/_docs tests', path: '_dogfooding/_docs tests',

View file

@ -4,9 +4,8 @@ This section is not going to be very structured, but we will cover the following
```mdx-code-block ```mdx-code-block
import DocCardList from '@theme/DocCardList'; import DocCardList from '@theme/DocCardList';
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
<DocCardList items={useCurrentSidebarCategory().items}/> <DocCardList />
``` ```
We will assume that you have finished the guides, and know the basics like how to configure plugins, how to write React components, etc. These sections will have plugin authors and code contributors in mind, so we may occasionally refer to [plugin APIs](../api/plugin-methods/README.md) or other architecture details. Don't panic if you don't understand everything😉 We will assume that you have finished the guides, and know the basics like how to configure plugins, how to write React components, etc. These sections will have plugin authors and code contributors in mind, so we may occasionally refer to [plugin APIs](../api/plugin-methods/README.md) or other architecture details. Don't panic if you don't understand everything😉

View file

@ -157,7 +157,7 @@ module.exports = {
- `localeConfigs`: Individual options for each locale. - `localeConfigs`: Individual options for each locale.
- `label`: The label displayed for this locale in the locales dropdown. - `label`: The label displayed for this locale in the locales dropdown.
- `direction`: `ltr` (default) or `rtl` (for [right-to-left languages](https://developer.mozilla.org/en-US/docs/Glossary/rtl) like Farsi, Arabic, Hebrew, etc.). Used to select the locale's CSS and HTML meta attribute. - `direction`: `ltr` (default) or `rtl` (for [right-to-left languages](https://developer.mozilla.org/en-US/docs/Glossary/rtl) like Farsi, Arabic, Hebrew, etc.). Used to select the locale's CSS and HTML meta attribute.
- `htmlLang`: BCP 47 language tag to use in `<html lang="...">` and in `<link ... hreflang="...">` - `htmlLang`: BCP 47 language tag to use in `<html lang="...">` (or any other DOM tag name) and in `<link ... hreflang="...">`
- `calendar`: the [calendar](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar) used to calculate the date era. Note that it doesn't control the actual string displayed: `MM/DD/YYYY` and `DD/MM/YYYY` are both `gregory`. To choose the format (`DD/MM/YYYY` or `MM/DD/YYYY`), set your locale name to `en-GB` or `en-US` (`en` means `en-US`). - `calendar`: the [calendar](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar) used to calculate the date era. Note that it doesn't control the actual string displayed: `MM/DD/YYYY` and `DD/MM/YYYY` are both `gregory`. To choose the format (`DD/MM/YYYY` or `MM/DD/YYYY`), set your locale name to `en-GB` or `en-US` (`en` means `en-US`).
- `path`: Root folder that all plugin localization folders of this locale are relative to. Will be resolved against `i18n.path`. Defaults to the locale's name. Note: this has no effect on the locale's `baseUrl`—customization of base URL is a work-in-progress. - `path`: Root folder that all plugin localization folders of this locale are relative to. Will be resolved against `i18n.path`. Defaults to the locale's name. Note: this has no effect on the locale's `baseUrl`—customization of base URL is a work-in-progress.
@ -509,15 +509,11 @@ module.exports = {
<html <%~ it.htmlAttributes %>> <html <%~ it.htmlAttributes %>>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Docusaurus v<%= it.version %>"> <meta name="generator" content="Docusaurus v<%= it.version %>">
<% if (it.noIndex) { %>
<meta name="robots" content="noindex, nofollow" />
<% } %>
<%~ it.headTags %>
<% it.metaAttributes.forEach((metaAttribute) => { %> <% it.metaAttributes.forEach((metaAttribute) => { %>
<%~ metaAttribute %> <%~ metaAttribute %>
<% }); %> <% }); %>
<%~ it.headTags %>
<% it.stylesheets.forEach((stylesheet) => { %> <% it.stylesheets.forEach((stylesheet) => { %>
<link rel="stylesheet" href="<%= it.baseUrl %><%= stylesheet %>" /> <link rel="stylesheet" href="<%= it.baseUrl %><%= stylesheet %>" />
<% }); %> <% }); %>

View file

@ -142,23 +142,25 @@ type CategoryIndexMatcher = (param: {
#### `VersionsConfig` {#VersionsConfig} #### `VersionsConfig` {#VersionsConfig}
```ts ```ts
type VersionsConfig = { type VersionConfig = {
[versionName: string]: { /**
/** * The base path of the version, will be appended to `baseUrl` +
* The base path of the version, will be appended to `baseUrl` + * `routeBasePath`.
* `routeBasePath`. */
*/ path?: string;
path?: string; /** The label of the version to be used in badges, dropdowns, etc. */
/** The label of the version to be used in badges, dropdowns, etc. */ label?: string;
label?: string; /** The banner to show at the top of a doc of that version. */
/** The banner to show at the top of a doc of that version. */ banner?: 'none' | 'unreleased' | 'unmaintained';
banner?: 'none' | 'unreleased' | 'unmaintained'; /** Show a badge with the version label at the top of each doc. */
/** Show a badge with the version label at the top of each doc. */ badge?: boolean;
badge?: boolean; /** Prevents search engines from indexing this version */
/** Add a custom class name to the <html> element of each doc */ noIndex?: boolean;
className?: string; /** Add a custom class name to the <html> element of each doc */
}; className?: string;
}; };
type VersionsConfig = {[versionName: string]: VersionConfig};
``` ```
### Example configuration {#ex-config} ### Example configuration {#ex-config}

View file

@ -35,9 +35,8 @@ This section serves as an overview of miscellaneous features of the doc sidebar.
```mdx-code-block ```mdx-code-block
import DocCardList from '@theme/DocCardList'; import DocCardList from '@theme/DocCardList';
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
<DocCardList items={useCurrentSidebarCategory().items}/> <DocCardList />
``` ```
## Default sidebar {#default-sidebar} ## Default sidebar {#default-sidebar}

View file

@ -8,6 +8,7 @@ slug: /sidebar/items
```mdx-code-block ```mdx-code-block
import Tabs from '@theme/Tabs'; import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem'; import TabItem from '@theme/TabItem';
import BrowserWindow from '@site/src/components/BrowserWindow';
``` ```
We have introduced three types of item types in the example in the previous section: `doc`, `category`, and `link`, whose usages are fairly intuitive. We will formally introduce their APIs. There's also a fourth type: `autogenerated`, which we will explain in detail later. We have introduced three types of item types in the example in the previous section: `doc`, `category`, and `link`, whose usages are fairly intuitive. We will formally introduce their APIs. There's also a fourth type: `autogenerated`, which we will explain in detail later.
@ -291,18 +292,23 @@ See it in action on the [i18n introduction page](../../../i18n/i18n-introduction
#### Embedding generated index in doc page {#embedding-generated-index-in-doc-page} #### Embedding generated index in doc page {#embedding-generated-index-in-doc-page}
You can embed the generated cards list in a normal doc page as well, as long as the doc is used as a category index page. To do so, you need to use the `DocCardList` component, paired with the `useCurrentSidebarCategory` hook. You can embed the generated cards list in a normal doc page as well with the `DocCardList` component. It will display all the sidebar items of the parent category of the current document.
```jsx title="a-category-index-page.md" ```md title="docs/sidebar/index.md"
import DocCardList from '@theme/DocCardList'; import DocCardList from '@theme/DocCardList';
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
In this section, we will introduce the following concepts: <DocCardList />
<DocCardList items={useCurrentSidebarCategory().items}/>
``` ```
See this in action on the [sidebar guides page](index.md). ```mdx-code-block
<BrowserWindow>
import DocCardList from '@theme/DocCardList';
<DocCardList />
</BrowserWindow>
```
### Collapsible categories {#collapsible-categories} ### Collapsible categories {#collapsible-categories}

View file

@ -65,11 +65,9 @@ import LiteYouTubeEmbed from 'react-lite-youtube-embed';
</div> </div>
``` ```
## Disclaimer {#disclaimer} ## Migrating from v1 {#migrating-from-v1}
Docusaurus v2 is **beta** but already quite stable and widely used. Docusaurus v2 has been a total rewrite from Docusaurus v1, taking advantage of a completely modernized toolchain. After [v2's official release](https://docusaurus.io/blog/2022/08/01/announcing-docusaurus-2.0), we highly encourage you to **use Docusaurus v2 over Docusaurus v1**, as Docusaurus v1 has been deprecated.
We highly encourage you to **use Docusaurus v2 over Docusaurus v1**, as Docusaurus v1 will be deprecated soon.
A [lot of users](/showcase) are already using Docusaurus v2 ([trends](https://www.npmtrends.com/docusaurus-vs-@docusaurus/core)). A [lot of users](/showcase) are already using Docusaurus v2 ([trends](https://www.npmtrends.com/docusaurus-vs-@docusaurus/core)).
@ -83,7 +81,9 @@ A [lot of users](/showcase) are already using Docusaurus v2 ([trends](https://ww
**Use [Docusaurus v1](https://v1.docusaurus.io/) if:** **Use [Docusaurus v1](https://v1.docusaurus.io/) if:**
- :x: You don't want a single-page application (SPA) - :x: You don't want a single-page application (SPA)
- :x: You need support for IE11 - :x: You need support for IE11 (...do you? IE [has already reached end-of-life](https://docs.microsoft.com/en-us/lifecycle/products/internet-explorer-11) and is no longer officially supported)
For existing v1 users that are seeking to upgrade to v2, you can follow our [migration guide](./migration/migration-overview.md).
## Features {#features} ## Features {#features}

View file

@ -124,7 +124,11 @@ Read more about the robots file in [the Google documentation](https://developers
:::caution :::caution
**Important**: the `robots.txt` file does **not** prevent HTML pages from being indexed. Use `<meta name="robots" content="noindex">` as [page metadata](#single-page-metadata) to prevent it from appearing in search results entirely. **Important**: the `robots.txt` file does **not** prevent HTML pages from being indexed.
To prevent your whole Docusaurus site from being indexed, use the [`noIndex`](./api/docusaurus.config.js.md#noIndex) site config. Some [hosting providers](./deployment.mdx) may also let you configure a `X-Robots-Tag: noindex` HTTP header (GitHub Pages does not support this).
To prevent a single page from being indexed, use `<meta name="robots" content="noindex">` as [page metadata](#single-page-metadata). Read more about the [robots meta tag](https://developers.google.com/search/docs/advanced/robots/robots_meta_tag).
::: :::
@ -132,6 +136,20 @@ Read more about the robots file in [the Google documentation](https://developers
Docusaurus provides the [`@docusaurus/plugin-sitemap`](./api/plugins/plugin-sitemap.md) plugin, which is shipped with `preset-classic` by default. It autogenerates a `sitemap.xml` file which will be available at `https://example.com/[baseUrl]/sitemap.xml` after the production build. This sitemap metadata helps search engine crawlers crawl your site more accurately. Docusaurus provides the [`@docusaurus/plugin-sitemap`](./api/plugins/plugin-sitemap.md) plugin, which is shipped with `preset-classic` by default. It autogenerates a `sitemap.xml` file which will be available at `https://example.com/[baseUrl]/sitemap.xml` after the production build. This sitemap metadata helps search engine crawlers crawl your site more accurately.
:::tip
The sitemap plugin automatically filters pages containing a `noindex` [robots meta directive](https://developers.google.com/search/docs/advanced/robots/robots_meta_tag).
For example, [`/examples/noIndex`](/examples/noIndex) is not included in the [Docusaurus sitemap.xml file](pathname:///sitemap.xml) because it contains the following [page metadata](#single-page-metadata):
```html
<head>
<meta name="robots" content="noindex, nofollow" />
</head>
```
:::
## Human readable links {#human-readable-links} ## Human readable links {#human-readable-links}
Docusaurus uses your file names as links, but you can always change that using slugs, see this [tutorial](./guides/docs/docs-introduction.md#document-id) for more details. Docusaurus uses your file names as links, but you can always change that using slugs, see this [tutorial](./guides/docs/docs-introduction.md#document-id) for more details.

View file

@ -356,7 +356,8 @@ const config = {
} }
: undefined, : undefined,
sitemap: { sitemap: {
ignorePatterns: ['/tests/**'], // Note: /tests/docs already has noIndex: true
ignorePatterns: ['/tests/{blog,pages}/**'],
}, },
}), }),
], ],

View file

@ -0,0 +1,25 @@
# No Index Page example
<head>
<meta name="robots" content="nOiNdeX, NoFolLoW" />
</head>
This page will not be indexed by search engines because it contains the page following [page metadata](/docs/seo#single-page-metadata) markup:
```html
<head>
<meta name="robots" content="noindex, nofollow" />
</head>
```
:::tip
The sitemap plugin filters pages containing a `noindex` content value. This page doesn't appear in Docusaurus [sitemap.xml](pathname:///sitemap.xml) file.
:::
:::note
Robots directives are [case-insensitive](https://developers.google.com/search/docs/advanced/robots/robots_meta_tag#directives).
:::

View file

@ -65,11 +65,9 @@ import LiteYouTubeEmbed from 'react-lite-youtube-embed';
</div> </div>
``` ```
## Disclaimer {#disclaimer} ## Migrating from v1 {#migrating-from-v1}
Docusaurus v2 is **beta** but already quite stable and widely used. Docusaurus v2 has been a total rewrite from Docusaurus v1, taking advantage of a completely modernized toolchain. After [v2's official release](https://docusaurus.io/blog/2022/08/01/announcing-docusaurus-2.0), we highly encourage you to **use Docusaurus v2 over Docusaurus v1**, as Docusaurus v1 has been deprecated.
We highly encourage you to **use Docusaurus v2 over Docusaurus v1**, as Docusaurus v1 will be deprecated soon.
A [lot of users](/showcase) are already using Docusaurus v2 ([trends](https://www.npmtrends.com/docusaurus-vs-@docusaurus/core)). A [lot of users](/showcase) are already using Docusaurus v2 ([trends](https://www.npmtrends.com/docusaurus-vs-@docusaurus/core)).
@ -83,7 +81,9 @@ A [lot of users](/showcase) are already using Docusaurus v2 ([trends](https://ww
**Use [Docusaurus v1](https://v1.docusaurus.io/) if:** **Use [Docusaurus v1](https://v1.docusaurus.io/) if:**
- :x: You don't want a single-page application (SPA) - :x: You don't want a single-page application (SPA)
- :x: You need support for IE11 - :x: You need support for IE11 (...do you? IE [has already reached end-of-life](https://docs.microsoft.com/en-us/lifecycle/products/internet-explorer-11) and is no longer officially supported)
For existing v1 users that are seeking to upgrade to v2, you can follow our [migration guide](./migration/migration-overview.md).
## Features {#features} ## Features {#features}