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
/*
tutorialSidebar: [
'intro',
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['hello'],
items: ['tutorial-basics/create-a-document'],
},
],
*/

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -87,7 +87,7 @@ export default function preset(
throw new Error(
`Unrecognized keys ${Object.keys(rest).join(
', ',
)} found in preset-classic configuration. The allowed keys are debug, docs, blog, pages, sitemap, theme, googleAnalytics, gtag. Check the documentation: https://docusaurus.io/docs/presets#docusauruspreset-classic for more information on how to configure individual plugins.`,
)} found in preset-classic configuration. The allowed keys are debug, docs, blog, pages, sitemap, theme, googleAnalytics, gtag. 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"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-beta.21",
"@docusaurus/types": "2.0.0-beta.21",
"@types/mdx-js__react": "^1.5.5",
"@types/nprogress": "^0.2.0",
"@types/prismjs": "^1.26.0",

View file

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

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 clsx from 'clsx';
import {useThemeConfig} from '@docusaurus/theme-common';
import {useAnnouncementBar} from '@docusaurus/theme-common/internal';
import {translate} from '@docusaurus/Translate';
import IconClose from '@theme/Icon/Close';
import AnnouncementBarCloseButton from '@theme/AnnouncementBar/CloseButton';
import AnnouncementBarContent from '@theme/AnnouncementBar/Content';
import styles from './styles.module.css';
export default function AnnouncementBar(): JSX.Element | null {
const {isActive, close} = useAnnouncementBar();
const {announcementBar} = useThemeConfig();
const {isActive, close} = useAnnouncementBar();
if (!isActive) {
return null;
}
const {content, backgroundColor, textColor, isCloseable} = announcementBar!;
const {backgroundColor, textColor, isCloseable} = announcementBar!;
return (
<div
className={styles.announcementBar}
style={{backgroundColor, color: textColor}}
role="banner">
{isCloseable && <div className={styles.announcementBarPlaceholder} />}
<div
className={styles.announcementBarContent}
// Developer provided the HTML, so assume it's safe.
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{__html: content}}
/>
{isCloseable ? (
<button
type="button"
className={clsx('clean-btn close', styles.announcementBarClose)}
<AnnouncementBarContent className={styles.announcementBarContent} />
{isCloseable && (
<AnnouncementBarCloseButton
onClick={close}
aria-label={translate({
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}
className={styles.announcementBarClose}
/>
)}
</div>
);
}

View file

@ -15,7 +15,8 @@
height: var(--docusaurus-announcement-bar-height);
background-color: var(--ifm-color-white);
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 {
@ -29,15 +30,10 @@ html[data-announcement-bar-initially-dismissed='true'] .announcementBar {
.announcementBarClose {
flex: 0 0 30px;
align-self: stretch;
padding: 0;
line-height: 0;
}
.announcementBarContent {
flex: 1 1 auto;
font-size: 85%;
text-align: center;
padding: 5px 0;
}
@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) {
:root {
--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;
padding: 0 var(--ifm-pre-padding);
background: var(--ifm-pre-background);
overflow-wrap: normal;
}
.codeLineNumber::before {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,7 @@
*/
import React from 'react';
import Translate from '@docusaurus/Translate';
import Translate, {translate} from '@docusaurus/Translate';
import {useSkipToContent} from '@docusaurus/theme-common/internal';
import styles from './styles.module.css';
@ -14,7 +14,10 @@ import styles from './styles.module.css';
export default function SkipToContent(): JSX.Element {
const {containerRef, handleSkip} = useSkipToContent();
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 */}
<a href="#" className={styles.skipToContent} onClick={handleSkip}>
<Translate

View file

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

View file

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

View file

@ -441,26 +441,87 @@ describe('useCurrentSidebarCategory', () => {
</DocsSidebarProvider>
),
}).result.current;
it('works', () => {
const category: PropSidebarItemCategory = {
type: 'category',
label: 'Category',
it('works for sidebar category', () => {
const category: PropSidebarItemCategory = testCategory({
href: '/cat',
collapsible: true,
collapsed: false,
items: [
{type: 'link', href: '/cat/foo', label: 'Foo'},
{type: 'link', href: '/cat/bar', label: 'Bar'},
{type: 'link', href: '/baz', label: 'Baz'},
],
};
const mockUseCurrentSidebarCategory = createUseCurrentSidebarCategoryMock([
{type: 'link', href: '/cat/fake', label: 'Fake'},
});
const sidebar: PropSidebar = [
testLink(),
testLink(),
category,
]);
testCategory(),
];
const mockUseCurrentSidebarCategory =
createUseCurrentSidebarCategoryMock(sidebar);
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', () => {
const category: PropSidebarItemCategory = {
type: 'category',

View file

@ -110,15 +110,18 @@ export function useCurrentSidebarCategory(): PropSidebarItemCategory {
if (!sidebar) {
throw new Error('Unexpected: cant find current sidebar in context');
}
const category = findSidebarCategory(sidebar.items, (item) =>
isSamePath(item.href, pathname),
);
if (!category) {
const categoryBreadcrumbs = getSidebarBreadcrumbs({
sidebarItems: sidebar.items,
pathname,
onlyCategories: true,
});
const deepestCategory = categoryBreadcrumbs.slice(-1)[0];
if (!deepestCategory) {
throw new Error(
`${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) =>
@ -149,6 +152,55 @@ export function isActiveSidebarItem(
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.
* Returns `null` if there's no sidebar or breadcrumbs are disabled.
@ -157,31 +209,10 @@ export function useSidebarBreadcrumbs(): PropSidebarBreadcrumbsItem[] | null {
const sidebar = useDocsSidebar();
const {pathname} = useLocation();
const breadcrumbsOption = useActivePlugin()?.pluginData.breadcrumbs;
if (breadcrumbsOption === false || !sidebar) {
return null;
}
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();
return getSidebarBreadcrumbs({sidebarItems: sidebar.items, pathname});
}
/**
@ -332,3 +363,18 @@ export function useDocRouteMetadata({
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.seeAll": "Xem tất cả {count} kết quả",
"theme.SearchModal.errorScreen.helpText": "You might want to check your network connection.",
"theme.SearchModal.errorScreen.titleText": "Unable to fetch results",
"theme.SearchModal.footer.closeKeyAriaLabel": "Escape key",
"theme.SearchModal.footer.closeText": "to close",
"theme.SearchModal.footer.navigateDownKeyAriaLabel": "Arrow down",
"theme.SearchModal.footer.navigateText": "to navigate",
"theme.SearchModal.footer.navigateUpKeyAriaLabel": "Arrow up",
"theme.SearchModal.footer.searchByText": "Search by",
"theme.SearchModal.footer.selectKeyAriaLabel": "Enter key",
"theme.SearchModal.footer.selectText": "to select",
"theme.SearchModal.noResultsScreen.noResultsText": "No results for",
"theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": "Let us know.",
"theme.SearchModal.noResultsScreen.reportMissingResultsText": "Believe this query should return results?",
"theme.SearchModal.noResultsScreen.suggestedQueryText": "Try searching for",
"theme.SearchModal.placeholder": "Search docs",
"theme.SearchModal.searchBox.cancelButtonText": "Cancel",
"theme.SearchModal.searchBox.resetButtonTitle": "Clear the query",
"theme.SearchModal.startScreen.favoriteSearchesTitle": "Favorite",
"theme.SearchModal.startScreen.noRecentSearchesText": "No recent searches",
"theme.SearchModal.startScreen.recentSearchesTitle": "Recent",
"theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle": "Remove this search from favorites",
"theme.SearchModal.startScreen.removeRecentSearchButtonTitle": "Remove this search from history",
"theme.SearchModal.startScreen.saveRecentSearchButtonTitle": "Save this search",
"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": "Không thể tìm nạp dữ liệu",
"theme.SearchModal.footer.closeKeyAriaLabel": "Phím thoát",
"theme.SearchModal.footer.closeText": "để đóng",
"theme.SearchModal.footer.navigateDownKeyAriaLabel": "Mũi tên xuống",
"theme.SearchModal.footer.navigateText": "để điều hướng",
"theme.SearchModal.footer.navigateUpKeyAriaLabel": "Mũi tên lên",
"theme.SearchModal.footer.searchByText": "Tìm kiếm theo",
"theme.SearchModal.footer.selectKeyAriaLabel": "Nhập khóa",
"theme.SearchModal.footer.selectText": "để chọn",
"theme.SearchModal.noResultsScreen.noResultsText": "Không có kết quả dành cho",
"theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": "Hãy để chúng tôi biết.",
"theme.SearchModal.noResultsScreen.reportMissingResultsText": "Tin rằng truy vấn này sẽ trả về kết quả?",
"theme.SearchModal.noResultsScreen.suggestedQueryText": "Thử tìm kiếm",
"theme.SearchModal.placeholder": "Tìm kiếm tài liệu",
"theme.SearchModal.searchBox.cancelButtonText": "Hủy bỏ",
"theme.SearchModal.searchBox.resetButtonTitle": "Xóa truy vấn",
"theme.SearchModal.startScreen.favoriteSearchesTitle": "Yêu thích",
"theme.SearchModal.startScreen.noRecentSearchesText": "Không có tìm kiếm nào gần đây",
"theme.SearchModal.startScreen.recentSearchesTitle": "Gần đây",
"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": "Xóa tìm kiếm này khỏi lịch sử",
"theme.SearchModal.startScreen.saveRecentSearchButtonTitle": "Lưu tìm kiếm này",
"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.emptyResultsTitle": "Tìm kiếm",

View file

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

View file

@ -86,7 +86,7 @@ export function getFileLoaderUtils(): FileLoaderUtils {
*/
images: () => ({
use: [loaders.url({folder: 'images'})],
test: /\.(?:ico|jpe?g|png|gif|webp)(?:\?.*)?$/i,
test: /\.(?:ico|jpe?g|png|gif|webp|avif)(?:\?.*)?$/i,
}),
fonts: () => ({
@ -100,7 +100,7 @@ export function getFileLoaderUtils(): FileLoaderUtils {
*/
media: () => ({
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: () => ({

View file

@ -12,7 +12,7 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
export default function SiteMetadataDefaults(): JSX.Element {
const {
siteConfig: {favicon, title},
siteConfig: {favicon, title, noIndex},
i18n: {currentLocale, localeConfigs},
} = useDocusaurusContext();
const faviconUrl = useBaseUrl(favicon);
@ -20,9 +20,17 @@ export default function SiteMetadataDefaults(): JSX.Element {
return (
<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} />
<title>{title}</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} />}
</Head>
);

View file

@ -17,7 +17,9 @@ import {matchRoutes} from 'react-router-config';
* @returns Promise object represents whether pathname has been preloaded
*/
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?.()));
}

View file

@ -29,6 +29,14 @@ export type WriteTranslationsCLIOptions = Pick<
> &
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!
* 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
*/
async function getExtraSourceCodeFilePaths(): Promise<string[]> {
try {
const themeCommonSourceDir = path.dirname(
require.resolve('@docusaurus/theme-common/lib'),
);
return globSourceCodeFilePaths([themeCommonSourceDir]);
} catch {
const themeCommonLibDir = resolveThemeCommonLibDir();
if (!themeCommonLibDir) {
return []; // User may not use a Docusaurus official theme? Quite unlikely...
}
return globSourceCodeFilePaths([themeCommonLibDir]);
}
async function writePluginTranslationFiles({
@ -108,6 +113,7 @@ Available locales are: ${context.i18n.locales.join(',')}.`,
babelOptions,
await getExtraSourceCodeFilePaths(),
);
const defaultCodeMessages = await getPluginsDefaultCodeTranslationMessages(
plugins,
);

View file

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

View file

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

View file

@ -249,3 +249,21 @@ echo "short_initially_hidden_string"
</Tabs>
[// 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)
- [Tabs tests](/tests/pages/tabs-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',
label: 'External link',
href: 'https://github.com/facebook/docusaurus',
type: 'category',
label: 'Link tests',
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',

View file

@ -26,6 +26,11 @@ const dogfoodingPluginInstances = [
id: 'docs-tests',
routeBasePath: '/tests/docs',
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
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
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😉

View file

@ -157,7 +157,7 @@ module.exports = {
- `localeConfigs`: Individual options for each locale.
- `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.
- `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`).
- `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 %>>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Docusaurus v<%= it.version %>">
<% if (it.noIndex) { %>
<meta name="robots" content="noindex, nofollow" />
<% } %>
<%~ it.headTags %>
<% it.metaAttributes.forEach((metaAttribute) => { %>
<%~ metaAttribute %>
<% }); %>
<%~ it.headTags %>
<% it.stylesheets.forEach((stylesheet) => { %>
<link rel="stylesheet" href="<%= it.baseUrl %><%= stylesheet %>" />
<% }); %>

View file

@ -142,8 +142,7 @@ type CategoryIndexMatcher = (param: {
#### `VersionsConfig` {#VersionsConfig}
```ts
type VersionsConfig = {
[versionName: string]: {
type VersionConfig = {
/**
* The base path of the version, will be appended to `baseUrl` +
* `routeBasePath`.
@ -155,10 +154,13 @@ type VersionsConfig = {
banner?: 'none' | 'unreleased' | 'unmaintained';
/** 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;
};
};
type VersionsConfig = {[versionName: string]: VersionConfig};
```
### 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
import DocCardList from '@theme/DocCardList';
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
<DocCardList items={useCurrentSidebarCategory().items}/>
<DocCardList />
```
## Default sidebar {#default-sidebar}

View file

@ -8,6 +8,7 @@ slug: /sidebar/items
```mdx-code-block
import Tabs from '@theme/Tabs';
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.
@ -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}
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 {useCurrentSidebarCategory} from '@docusaurus/theme-common';
In this section, we will introduce the following concepts:
<DocCardList items={useCurrentSidebarCategory().items}/>
<DocCardList />
```
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}

View file

@ -65,11 +65,9 @@ import LiteYouTubeEmbed from 'react-lite-youtube-embed';
</div>
```
## Disclaimer {#disclaimer}
## Migrating from v1 {#migrating-from-v1}
Docusaurus v2 is **beta** but already quite stable and widely used.
We highly encourage you to **use Docusaurus v2 over Docusaurus v1**, as Docusaurus v1 will be deprecated soon.
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.
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:**
- :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}

View file

@ -124,7 +124,11 @@ Read more about the robots file in [the Google documentation](https://developers
:::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.
:::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}
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,
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>
```
## Disclaimer {#disclaimer}
## Migrating from v1 {#migrating-from-v1}
Docusaurus v2 is **beta** but already quite stable and widely used.
We highly encourage you to **use Docusaurus v2 over Docusaurus v1**, as Docusaurus v1 will be deprecated soon.
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.
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:**
- :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}