mirror of
https://github.com/facebook/docusaurus.git
synced 2025-12-30 14:14:17 +00:00
Merge branch 'main' into tinyglobby
This commit is contained in:
commit
86303b31eb
|
|
@ -30,7 +30,7 @@ jobs:
|
|||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
|
|
@ -75,7 +75,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
with:
|
||||
fetch-depth: 0 # Needed to get the commit number with "git rev-list --count HEAD"
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ jobs:
|
|||
|
||||
- name: Add Lighthouse stats as comment
|
||||
id: comment_to_pr
|
||||
uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # 2.9.1
|
||||
uses: marocchino/sticky-pull-request-comment@67d0dec7b07ed060a405f9b2a64b8ab319fdd7db # 2.9.2
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
number: ${{ github.event.pull_request.number }}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Use Node.js ${{ matrix.node }}
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: yarn
|
||||
|
|
@ -84,7 +84,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Use Node.js LTS
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
|
|
@ -153,7 +153,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Use Node.js LTS
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
|
|
@ -193,7 +193,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Use Node.js LTS
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Set up Node LTS
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Use Node.js ${{ matrix.node }}
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- name: Installation
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Use Node.js ${{ matrix.node }}
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: yarn
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@
|
|||
"pr: polish": ":nail_care: Polish",
|
||||
"pr: documentation": ":memo: Documentation",
|
||||
"pr: dependencies": ":robot: Dependencies",
|
||||
"pr: maintenance": ":wrench: Maintenance"
|
||||
"pr: maintenance": ":wrench: Maintenance",
|
||||
"pr: translations": ":globe_with_meridians: Translations"
|
||||
},
|
||||
"cacheDir": ".changelog"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const plugin: Plugin<unknown[], Root> = function plugin(): Transformer<Root> {
|
|||
node.data.hProperties = node.data.hProperties || {};
|
||||
node.data.hProperties.metastring = node.meta;
|
||||
|
||||
// TODO Docusaurus v4: remove special case
|
||||
// Retrocompatible support for live codeblock metastring
|
||||
// Not really the appropriate place to handle that :s
|
||||
node.data.hProperties.live = node.meta?.split(' ').includes('live');
|
||||
|
|
|
|||
|
|
@ -122,6 +122,15 @@ declare module '@theme/Root' {
|
|||
export default function Root({children}: Props): ReactNode;
|
||||
}
|
||||
|
||||
declare module '@theme/ThemeProvider' {
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
export interface Props {
|
||||
readonly children: ReactNode;
|
||||
}
|
||||
export default function ThemeProvider({children}: Props): ReactNode;
|
||||
}
|
||||
|
||||
declare module '@theme/SiteMetadata' {
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@
|
|||
<div class="blog-posts">
|
||||
<xsl:for-each select="atom:feed/atom:entry">
|
||||
<div class="blog-post">
|
||||
<h3><a href="{atom:link[@rel='alternate']/@href}"><xsl:value-of
|
||||
<h3><a href="{atom:link/@href}"><xsl:value-of
|
||||
select="atom:title"
|
||||
/></a></h3>
|
||||
<div class="blog-post-date">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
---
|
||||
title: MDX page
|
||||
description: my MDX page
|
||||
slug: /custom-mdx/slug
|
||||
---
|
||||
|
||||
MDX page
|
||||
|
|
|
|||
|
|
@ -32,11 +32,12 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = `
|
|||
"frontMatter": {
|
||||
"custom_frontMatter": "added by parseFrontMatter",
|
||||
"description": "my MDX page",
|
||||
"slug": "/custom-mdx/slug",
|
||||
"title": "MDX page",
|
||||
},
|
||||
"lastUpdatedAt": undefined,
|
||||
"lastUpdatedBy": undefined,
|
||||
"permalink": "/hello/mdxPage",
|
||||
"permalink": "/custom-mdx/slug",
|
||||
"source": "@site/src/pages/hello/mdxPage.mdx",
|
||||
"title": "MDX page",
|
||||
"type": "mdx",
|
||||
|
|
@ -101,11 +102,12 @@ exports[`docusaurus-plugin-content-pages loads simple pages with french translat
|
|||
"frontMatter": {
|
||||
"custom_frontMatter": "added by parseFrontMatter",
|
||||
"description": "my MDX page",
|
||||
"slug": "/custom-mdx/slug",
|
||||
"title": "MDX page",
|
||||
},
|
||||
"lastUpdatedAt": undefined,
|
||||
"lastUpdatedBy": undefined,
|
||||
"permalink": "/fr/hello/mdxPage",
|
||||
"permalink": "/fr/custom-mdx/slug",
|
||||
"source": "@site/src/pages/hello/mdxPage.mdx",
|
||||
"title": "MDX page",
|
||||
"type": "mdx",
|
||||
|
|
@ -170,11 +172,12 @@ exports[`docusaurus-plugin-content-pages loads simple pages with last update 1`]
|
|||
"frontMatter": {
|
||||
"custom_frontMatter": "added by parseFrontMatter",
|
||||
"description": "my MDX page",
|
||||
"slug": "/custom-mdx/slug",
|
||||
"title": "MDX page",
|
||||
},
|
||||
"lastUpdatedAt": 1539502055000,
|
||||
"lastUpdatedBy": "Author",
|
||||
"permalink": "/hello/mdxPage",
|
||||
"permalink": "/custom-mdx/slug",
|
||||
"source": "@site/src/pages/hello/mdxPage.mdx",
|
||||
"title": "MDX page",
|
||||
"type": "mdx",
|
||||
|
|
|
|||
|
|
@ -106,12 +106,13 @@ async function processPageSourceFile(
|
|||
|
||||
const source = path.join(contentPath, relativeSource);
|
||||
const aliasedSourcePath = aliasedSitePath(source, siteDir);
|
||||
const permalink = normalizeUrl([
|
||||
baseUrl,
|
||||
options.routeBasePath,
|
||||
encodePath(fileToPath(relativeSource)),
|
||||
]);
|
||||
|
||||
const filenameSlug = encodePath(fileToPath(relativeSource));
|
||||
|
||||
if (!isMarkdownSource(relativeSource)) {
|
||||
// For now, slug can't be customized for JSX pages
|
||||
const slug = filenameSlug;
|
||||
const permalink = normalizeUrl([baseUrl, options.routeBasePath, slug]);
|
||||
return {
|
||||
type: 'jsx',
|
||||
permalink,
|
||||
|
|
@ -131,6 +132,9 @@ async function processPageSourceFile(
|
|||
});
|
||||
const frontMatter = validatePageFrontMatter(unsafeFrontMatter);
|
||||
|
||||
const slug = frontMatter.slug ?? filenameSlug;
|
||||
const permalink = normalizeUrl([baseUrl, options.routeBasePath, slug]);
|
||||
|
||||
const pagesDirPath = await getFolderContainingFile(
|
||||
getContentPathList(contentPaths),
|
||||
relativeSource,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const PageFrontMatterSchema = Joi.object<PageFrontMatter>({
|
|||
description: Joi.string().allow(''),
|
||||
keywords: Joi.array().items(Joi.string().required()),
|
||||
image: URISchema,
|
||||
slug: Joi.string(),
|
||||
wrapperClassName: Joi.string(),
|
||||
hide_table_of_contents: Joi.boolean(),
|
||||
...FrontMatterTOCHeadingLevels,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ declare module '@docusaurus/plugin-content-pages' {
|
|||
readonly title?: string;
|
||||
readonly description?: string;
|
||||
readonly image?: string;
|
||||
readonly slug?: string;
|
||||
readonly keywords?: string[];
|
||||
readonly wrapperClassName?: string;
|
||||
readonly hide_table_of_contents?: string;
|
||||
|
|
|
|||
|
|
@ -1569,6 +1569,17 @@ declare module '@theme/ThemedImage' {
|
|||
export default function ThemedImage(props: Props): ReactNode;
|
||||
}
|
||||
|
||||
declare module '@theme/ThemeProvider/TitleFormatter' {
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
export interface Props {
|
||||
readonly children: ReactNode;
|
||||
}
|
||||
export default function ThemeProviderTitleFormatter({
|
||||
children,
|
||||
}: Props): ReactNode;
|
||||
}
|
||||
|
||||
declare module '@theme/Details' {
|
||||
import {Details, type DetailsProps} from '@docusaurus/theme-common/Details';
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export default function Tag({
|
|||
}: Props): ReactNode {
|
||||
return (
|
||||
<Link
|
||||
rel="tag"
|
||||
href={permalink}
|
||||
title={description}
|
||||
className={clsx(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {type ComponentProps, type ReactNode} from 'react';
|
||||
import {TitleFormatterProvider} from '@docusaurus/theme-common/internal';
|
||||
import type {Props} from '@theme/ThemeProvider/TitleFormatter';
|
||||
|
||||
type FormatterProp = ComponentProps<typeof TitleFormatterProvider>['formatter'];
|
||||
|
||||
const formatter: FormatterProp = (params) => {
|
||||
// Add your own title formatting logic here!
|
||||
return params.defaultFormatter(params);
|
||||
};
|
||||
|
||||
export default function ThemeProviderTitleFormatter({
|
||||
children,
|
||||
}: Props): ReactNode {
|
||||
return (
|
||||
<TitleFormatterProvider formatter={formatter}>
|
||||
{children}
|
||||
</TitleFormatterProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {type ReactNode} from 'react';
|
||||
import TitleFormatterProvider from '@theme/ThemeProvider/TitleFormatter';
|
||||
import type {Props} from '@theme/ThemeProvider';
|
||||
|
||||
export default function ThemeProvider({children}: Props): ReactNode {
|
||||
return <TitleFormatterProvider>{children}</TitleFormatterProvider>;
|
||||
}
|
||||
|
|
@ -43,7 +43,10 @@ export {
|
|||
|
||||
export {DEFAULT_SEARCH_TAG} from './utils/searchUtils';
|
||||
|
||||
export {useTitleFormatter} from './utils/generalUtils';
|
||||
export {
|
||||
TitleFormatterProvider,
|
||||
useTitleFormatter,
|
||||
} from './utils/titleFormatterUtils';
|
||||
|
||||
export {useLocationChange} from './utils/useLocationChange';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
/**
|
||||
* 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 {renderHook} from '@testing-library/react-hooks';
|
||||
import {Context} from '@docusaurus/core/src/client/docusaurusContext';
|
||||
import {useTitleFormatter} from '../generalUtils';
|
||||
import type {DocusaurusContext} from '@docusaurus/types';
|
||||
|
||||
describe('useTitleFormatter', () => {
|
||||
const createUseTitleFormatterMock =
|
||||
(context: DocusaurusContext) => (title?: string) =>
|
||||
renderHook(() => useTitleFormatter(title), {
|
||||
wrapper: ({children}) => (
|
||||
<Context.Provider value={context}>{children}</Context.Provider>
|
||||
),
|
||||
}).result.current;
|
||||
it('works', () => {
|
||||
const mockUseTitleFormatter = createUseTitleFormatterMock({
|
||||
siteConfig: {
|
||||
title: 'my site',
|
||||
titleDelimiter: '·',
|
||||
},
|
||||
} as DocusaurusContext);
|
||||
expect(mockUseTitleFormatter('a page')).toBe('a page · my site');
|
||||
expect(mockUseTitleFormatter(undefined)).toBe('my site');
|
||||
expect(mockUseTitleFormatter(' ')).toBe('my site');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* 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 {TitleFormatterFnDefault} from '../titleFormatterUtils';
|
||||
|
||||
describe('TitleFormatterFnDefault', () => {
|
||||
it('works', () => {
|
||||
expect(
|
||||
TitleFormatterFnDefault({
|
||||
title: 'a page',
|
||||
siteTitle: 'my site',
|
||||
titleDelimiter: '·',
|
||||
}),
|
||||
).toBe('a page · my site');
|
||||
});
|
||||
|
||||
it('ignores empty title', () => {
|
||||
expect(
|
||||
TitleFormatterFnDefault({
|
||||
title: ' ',
|
||||
siteTitle: 'my site',
|
||||
titleDelimiter: '·',
|
||||
}),
|
||||
).toBe('my site');
|
||||
});
|
||||
|
||||
it('does not duplicate site title', () => {
|
||||
// Users may pass <Layout title={siteTitle}> leading to duplicate titles
|
||||
// By default it's preferable to avoid duplicate siteTitle
|
||||
// See also https://github.com/facebook/docusaurus/issues/5878#issuecomment-961505856
|
||||
expect(
|
||||
TitleFormatterFnDefault({
|
||||
title: 'my site',
|
||||
siteTitle: 'my site',
|
||||
titleDelimiter: '·',
|
||||
}),
|
||||
).toBe('my site');
|
||||
});
|
||||
});
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
/**
|
||||
* 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 useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
|
||||
/**
|
||||
* Formats the page's title based on relevant site config and other contexts.
|
||||
*/
|
||||
export function useTitleFormatter(title?: string | undefined): string {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
const {title: siteTitle, titleDelimiter} = siteConfig;
|
||||
return title?.trim().length
|
||||
? `${title.trim()} ${titleDelimiter} ${siteTitle}`
|
||||
: siteTitle;
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import clsx from 'clsx';
|
|||
import Head from '@docusaurus/Head';
|
||||
import useRouteContext from '@docusaurus/useRouteContext';
|
||||
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
|
||||
import {useTitleFormatter} from './generalUtils';
|
||||
import {useTitleFormatter} from './titleFormatterUtils';
|
||||
|
||||
type PageMetadataProps = {
|
||||
readonly title?: string;
|
||||
|
|
@ -20,6 +20,55 @@ type PageMetadataProps = {
|
|||
readonly children?: ReactNode;
|
||||
};
|
||||
|
||||
function TitleMetadata({title}: {title: string}) {
|
||||
const titleFormatter = useTitleFormatter();
|
||||
const formattedTitle = titleFormatter.format(title);
|
||||
return (
|
||||
<Head>
|
||||
<title>{formattedTitle}</title>
|
||||
<meta property="og:title" content={formattedTitle} />
|
||||
</Head>
|
||||
);
|
||||
}
|
||||
|
||||
function DescriptionMetadata({description}: {description: string}) {
|
||||
return (
|
||||
<Head>
|
||||
<meta name="description" content={description} />
|
||||
<meta property="og:description" content={description} />
|
||||
</Head>
|
||||
);
|
||||
}
|
||||
|
||||
function ImageMetadata({image}: {image: string}) {
|
||||
const {withBaseUrl} = useBaseUrlUtils();
|
||||
const pageImage = withBaseUrl(image, {absolute: true});
|
||||
return (
|
||||
<Head>
|
||||
<meta property="og:image" content={pageImage} />
|
||||
<meta name="twitter:image" content={pageImage} />
|
||||
</Head>
|
||||
);
|
||||
}
|
||||
|
||||
function KeywordsMetadata({
|
||||
keywords,
|
||||
}: {
|
||||
keywords: PageMetadataProps['keywords'];
|
||||
}) {
|
||||
return (
|
||||
<Head>
|
||||
<meta
|
||||
name="keywords"
|
||||
content={
|
||||
// https://github.com/microsoft/TypeScript/issues/17002
|
||||
(Array.isArray(keywords) ? keywords.join(',') : keywords) as string
|
||||
}
|
||||
/>
|
||||
</Head>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper component to manipulate page metadata and override site defaults.
|
||||
* Works in the same way as Helmet.
|
||||
|
|
@ -31,33 +80,14 @@ export function PageMetadata({
|
|||
image,
|
||||
children,
|
||||
}: PageMetadataProps): ReactNode {
|
||||
const pageTitle = useTitleFormatter(title);
|
||||
const {withBaseUrl} = useBaseUrlUtils();
|
||||
const pageImage = image ? withBaseUrl(image, {absolute: true}) : undefined;
|
||||
|
||||
return (
|
||||
<Head>
|
||||
{title && <title>{pageTitle}</title>}
|
||||
{title && <meta property="og:title" content={pageTitle} />}
|
||||
|
||||
{description && <meta name="description" content={description} />}
|
||||
{description && <meta property="og:description" content={description} />}
|
||||
|
||||
{keywords && (
|
||||
<meta
|
||||
name="keywords"
|
||||
content={
|
||||
// https://github.com/microsoft/TypeScript/issues/17002
|
||||
(Array.isArray(keywords) ? keywords.join(',') : keywords) as string
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{pageImage && <meta property="og:image" content={pageImage} />}
|
||||
{pageImage && <meta name="twitter:image" content={pageImage} />}
|
||||
|
||||
{children}
|
||||
</Head>
|
||||
<>
|
||||
{title && <TitleMetadata title={title} />}
|
||||
{description && <DescriptionMetadata description={description} />}
|
||||
{keywords && <KeywordsMetadata keywords={keywords} />}
|
||||
{image && <ImageMetadata image={image} />}
|
||||
{children && <Head>{children}</Head>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
/**
|
||||
* 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 {createContext, useContext} from 'react';
|
||||
import type {ReactNode} from 'react';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import useRouteContext from '@docusaurus/useRouteContext';
|
||||
import {ReactContextError} from './reactUtils';
|
||||
|
||||
type TitleFormatterParams = {
|
||||
/**
|
||||
* The page title to format
|
||||
* Usually provided with these APIs:
|
||||
* - <PageMetadata title={title}>
|
||||
* - useTitleFormatter().format(title)
|
||||
*/
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* The siteConfig.title value
|
||||
*/
|
||||
siteTitle: string;
|
||||
|
||||
/**
|
||||
* The siteConfig.titleDelimiter value
|
||||
*/
|
||||
titleDelimiter: string;
|
||||
|
||||
/**
|
||||
* The plugin that created the page you are on
|
||||
*/
|
||||
plugin: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the full formatting function, including all useful params
|
||||
* Can be customized through React context with the provider
|
||||
*/
|
||||
export type TitleFormatterFn = (params: TitleFormatterParams) => string;
|
||||
|
||||
/**
|
||||
* The default formatter is provided in params for convenience
|
||||
*/
|
||||
export type TitleFormatterFnWithDefault = (
|
||||
params: TitleFormatterParams & {
|
||||
defaultFormatter: (params: TitleFormatterParams) => string;
|
||||
},
|
||||
) => string;
|
||||
|
||||
export const TitleFormatterFnDefault: TitleFormatterFn = ({
|
||||
title,
|
||||
siteTitle,
|
||||
titleDelimiter,
|
||||
}): string => {
|
||||
const trimmedTitle = title?.trim();
|
||||
if (!trimmedTitle || trimmedTitle === siteTitle) {
|
||||
return siteTitle;
|
||||
}
|
||||
return `${trimmedTitle} ${titleDelimiter} ${siteTitle}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the simpler API exposed to theme/users
|
||||
*/
|
||||
type TitleFormatter = {format: (title: string) => string};
|
||||
|
||||
const TitleFormatterContext = createContext<TitleFormatterFnWithDefault | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
export function TitleFormatterProvider({
|
||||
formatter,
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
formatter: TitleFormatterFnWithDefault;
|
||||
}): ReactNode {
|
||||
return (
|
||||
<TitleFormatterContext.Provider value={formatter}>
|
||||
{children}
|
||||
</TitleFormatterContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function useTitleFormatterContext() {
|
||||
const value = useContext(TitleFormatterContext);
|
||||
if (value === null) {
|
||||
throw new ReactContextError('TitleFormatterProvider');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function to format the page title
|
||||
*/
|
||||
export function useTitleFormatter(): TitleFormatter {
|
||||
const formatter = useTitleFormatterContext();
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
const {title: siteTitle, titleDelimiter} = siteConfig;
|
||||
|
||||
// Unfortunately we can only call this hook here, not in the provider
|
||||
// Route context can't be accessed in any provider applied above the router
|
||||
const {plugin} = useRouteContext();
|
||||
|
||||
return {
|
||||
format: (title: string) =>
|
||||
formatter({
|
||||
title,
|
||||
siteTitle,
|
||||
titleDelimiter,
|
||||
plugin,
|
||||
defaultFormatter: TitleFormatterFnDefault,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
|
@ -104,6 +104,7 @@ export type TableOfContents = {
|
|||
maxHeadingLevel: number;
|
||||
};
|
||||
|
||||
// TODO Docusaurus v4: use interface + declaration merging to enhance
|
||||
// Theme config after validation/normalization
|
||||
export type ThemeConfig = {
|
||||
docs: {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,14 @@ declare module '@docusaurus/theme-live-codeblock' {
|
|||
};
|
||||
}
|
||||
|
||||
declare module '@theme/LiveCodeBlock' {
|
||||
import type {Props as BaseProps} from '@theme/CodeBlock';
|
||||
|
||||
export interface Props extends BaseProps {}
|
||||
|
||||
export default function LiveCodeBlock(props: Props): ReactNode;
|
||||
}
|
||||
|
||||
declare module '@theme/Playground' {
|
||||
import type {ReactNode} from 'react';
|
||||
import type {Props as BaseProps} from '@theme/CodeBlock';
|
||||
|
|
@ -31,6 +39,64 @@ declare module '@theme/Playground' {
|
|||
export default function Playground(props: LiveProviderProps): ReactNode;
|
||||
}
|
||||
|
||||
declare module '@theme/Playground/Provider' {
|
||||
import type {ReactNode} from 'react';
|
||||
import type {Props as PlaygroundProps} from '@theme/Playground';
|
||||
|
||||
export interface Props extends Omit<PlaygroundProps, 'children'> {
|
||||
code: string | undefined;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export default function PlaygroundProvider(props: Props): ReactNode;
|
||||
}
|
||||
|
||||
declare module '@theme/Playground/Container' {
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
export interface Props {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export default function PlaygroundContainer(props: Props): ReactNode;
|
||||
}
|
||||
|
||||
declare module '@theme/Playground/Layout' {
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface Props {}
|
||||
|
||||
export default function PlaygroundLayout(props: Props): ReactNode;
|
||||
}
|
||||
|
||||
declare module '@theme/Playground/Preview' {
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface Props {}
|
||||
|
||||
export default function PlaygroundPreview(props: Props): ReactNode;
|
||||
}
|
||||
|
||||
declare module '@theme/Playground/Editor' {
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface Props {}
|
||||
|
||||
export default function PlaygroundEditor(props: Props): ReactNode;
|
||||
}
|
||||
|
||||
declare module '@theme/Playground/Header' {
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface Props {}
|
||||
|
||||
export default function PlaygroundHeader(props: Props): ReactNode;
|
||||
}
|
||||
|
||||
declare module '@theme/ReactLiveScope' {
|
||||
type Scope = {
|
||||
[key: string]: unknown;
|
||||
|
|
|
|||
|
|
@ -5,21 +5,28 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import Playground from '@theme/Playground';
|
||||
import ReactLiveScope from '@theme/ReactLiveScope';
|
||||
import CodeBlock, {type Props} from '@theme-init/CodeBlock';
|
||||
import React, {type ReactNode} from 'react';
|
||||
import type {Props as CodeBlockProps} from '@theme/CodeBlock';
|
||||
import OriginalCodeBlock from '@theme-init/CodeBlock';
|
||||
import LiveCodeBlock from '@theme/LiveCodeBlock';
|
||||
|
||||
const withLiveEditor = (Component: typeof CodeBlock) => {
|
||||
function WrappedComponent(props: Props) {
|
||||
if (props.live) {
|
||||
return <Playground scope={ReactLiveScope} {...props} />;
|
||||
}
|
||||
|
||||
return <Component {...props} />;
|
||||
// TODO Docusaurus v4: remove special case
|
||||
// see packages/docusaurus-mdx-loader/src/remark/mdx1Compat/codeCompatPlugin.ts
|
||||
// we can just use the metastring instead
|
||||
declare module '@theme/CodeBlock' {
|
||||
interface Props {
|
||||
live?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
return WrappedComponent;
|
||||
};
|
||||
function isLiveCodeBlock(props: CodeBlockProps): boolean {
|
||||
return !!props.live;
|
||||
}
|
||||
|
||||
export default withLiveEditor(CodeBlock);
|
||||
export default function CodeBlockEnhancer(props: CodeBlockProps): ReactNode {
|
||||
return isLiveCodeBlock(props) ? (
|
||||
<LiveCodeBlock {...props} />
|
||||
) : (
|
||||
<OriginalCodeBlock {...props} />
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {type ReactNode} from 'react';
|
||||
import Playground from '@theme/Playground';
|
||||
import ReactLiveScope from '@theme/ReactLiveScope';
|
||||
import type {Props} from '@theme/LiveCodeBlock';
|
||||
|
||||
export default function LiveCodeBlock(props: Props): ReactNode {
|
||||
return <Playground scope={ReactLiveScope} {...props} />;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {type ReactNode} from 'react';
|
||||
|
||||
import type {Props} from '@theme/Playground/Container';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export default function PlaygroundContainer({children}: Props): ReactNode {
|
||||
return <div className={styles.playgroundContainer}>{children}</div>;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.playgroundContainer {
|
||||
margin-bottom: var(--ifm-leading);
|
||||
border-radius: var(--ifm-global-radius);
|
||||
box-shadow: var(--ifm-global-shadow-lw);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {type ReactNode} from 'react';
|
||||
import {LiveEditor} from 'react-live';
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||
import Translate from '@docusaurus/Translate';
|
||||
import PlaygroundHeader from '@theme/Playground/Header';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export default function PlaygroundEditor(): ReactNode {
|
||||
const isBrowser = useIsBrowser();
|
||||
return (
|
||||
<>
|
||||
<PlaygroundHeader>
|
||||
<Translate
|
||||
id="theme.Playground.liveEditor"
|
||||
description="The live editor label of the live codeblocks">
|
||||
Live Editor
|
||||
</Translate>
|
||||
</PlaygroundHeader>
|
||||
<LiveEditor
|
||||
// We force remount the editor on hydration,
|
||||
// otherwise dark prism theme is not applied
|
||||
key={String(isBrowser)}
|
||||
className={styles.playgroundEditor}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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.
|
||||
*/
|
||||
|
||||
.playgroundEditor {
|
||||
font: var(--ifm-code-font-size) / var(--ifm-pre-line-height)
|
||||
var(--ifm-font-family-monospace) !important;
|
||||
/* rtl:ignore */
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.playgroundEditor pre {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {type ReactNode} from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export default function PlaygroundHeader({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}): ReactNode {
|
||||
return <div className={clsx(styles.playgroundHeader)}>{children}</div>;
|
||||
}
|
||||
|
|
@ -5,13 +5,6 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
.playgroundContainer {
|
||||
margin-bottom: var(--ifm-leading);
|
||||
border-radius: var(--ifm-global-radius);
|
||||
box-shadow: var(--ifm-global-shadow-lw);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.playgroundHeader {
|
||||
letter-spacing: 0.08rem;
|
||||
padding: 0.75rem;
|
||||
|
|
@ -26,19 +19,3 @@
|
|||
background: var(--ifm-color-emphasis-600);
|
||||
color: var(--ifm-color-content-inverse);
|
||||
}
|
||||
|
||||
.playgroundEditor {
|
||||
font: var(--ifm-code-font-size) / var(--ifm-pre-line-height)
|
||||
var(--ifm-font-family-monospace) !important;
|
||||
/* rtl:ignore */
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.playgroundEditor pre {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.playgroundPreview {
|
||||
padding: 1rem;
|
||||
background-color: var(--ifm-pre-background);
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {type ReactNode} from 'react';
|
||||
import {useThemeConfig} from '@docusaurus/theme-common';
|
||||
import PlaygroundPreview from '@theme/Playground/Preview';
|
||||
import PlaygroundEditor from '@theme/Playground/Editor';
|
||||
|
||||
import type {ThemeConfig} from '@docusaurus/theme-live-codeblock';
|
||||
|
||||
function useLiveCodeBlockThemeConfig() {
|
||||
const themeConfig = useThemeConfig() as unknown as ThemeConfig;
|
||||
return themeConfig.liveCodeBlock;
|
||||
}
|
||||
|
||||
export default function PlaygroundLayout(): ReactNode {
|
||||
const {playgroundPosition} = useLiveCodeBlockThemeConfig();
|
||||
return (
|
||||
<>
|
||||
{playgroundPosition === 'top' ? (
|
||||
<>
|
||||
<PlaygroundPreview />
|
||||
<PlaygroundEditor />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PlaygroundEditor />
|
||||
<PlaygroundPreview />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {type ReactNode} from 'react';
|
||||
import {LiveError, LivePreview} from 'react-live';
|
||||
import BrowserOnly from '@docusaurus/BrowserOnly';
|
||||
import {ErrorBoundaryErrorMessageFallback} from '@docusaurus/theme-common';
|
||||
import ErrorBoundary from '@docusaurus/ErrorBoundary';
|
||||
import Translate from '@docusaurus/Translate';
|
||||
import PlaygroundHeader from '@theme/Playground/Header';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
function Loader() {
|
||||
// Is it worth improving/translating?
|
||||
// eslint-disable-next-line @docusaurus/no-untranslated-text
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
function PlaygroundLivePreview(): ReactNode {
|
||||
// No SSR for the live preview
|
||||
// See https://github.com/facebook/docusaurus/issues/5747
|
||||
return (
|
||||
<BrowserOnly fallback={<Loader />}>
|
||||
{() => (
|
||||
<>
|
||||
<ErrorBoundary
|
||||
fallback={(params) => (
|
||||
<ErrorBoundaryErrorMessageFallback {...params} />
|
||||
)}>
|
||||
<LivePreview />
|
||||
</ErrorBoundary>
|
||||
<LiveError />
|
||||
</>
|
||||
)}
|
||||
</BrowserOnly>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PlaygroundPreview(): ReactNode {
|
||||
return (
|
||||
<>
|
||||
<PlaygroundHeader>
|
||||
<Translate
|
||||
id="theme.Playground.result"
|
||||
description="The result label of the live codeblocks">
|
||||
Result
|
||||
</Translate>
|
||||
</PlaygroundHeader>
|
||||
<div className={styles.playgroundPreview}>
|
||||
<PlaygroundLivePreview />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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.
|
||||
*/
|
||||
|
||||
.playgroundPreview {
|
||||
padding: 1rem;
|
||||
background-color: var(--ifm-pre-background);
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {type ReactNode} from 'react';
|
||||
import {LiveProvider} from 'react-live';
|
||||
import {usePrismTheme} from '@docusaurus/theme-common';
|
||||
|
||||
import type {Props} from '@theme/Playground/Provider';
|
||||
|
||||
// this should rather be a stable function
|
||||
// see https://github.com/facebook/docusaurus/issues/9630#issuecomment-1855682643
|
||||
const DEFAULT_TRANSFORM_CODE = (code: string) => `${code};`;
|
||||
|
||||
export default function PlaygroundProvider({
|
||||
code,
|
||||
children,
|
||||
...props
|
||||
}: Props): ReactNode {
|
||||
const prismTheme = usePrismTheme();
|
||||
const noInline = props.metastring?.includes('noInline') ?? false;
|
||||
return (
|
||||
<LiveProvider
|
||||
noInline={noInline}
|
||||
theme={prismTheme}
|
||||
{...props}
|
||||
code={code?.replace(/\n$/, '')}
|
||||
transformCode={props.transformCode ?? DEFAULT_TRANSFORM_CODE}>
|
||||
{children}
|
||||
</LiveProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -6,137 +6,22 @@
|
|||
*/
|
||||
|
||||
import React, {type ReactNode} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||
import {LiveProvider, LiveEditor, LiveError, LivePreview} from 'react-live';
|
||||
import Translate from '@docusaurus/Translate';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import BrowserOnly from '@docusaurus/BrowserOnly';
|
||||
import {
|
||||
ErrorBoundaryErrorMessageFallback,
|
||||
usePrismTheme,
|
||||
} from '@docusaurus/theme-common';
|
||||
import ErrorBoundary from '@docusaurus/ErrorBoundary';
|
||||
import PlaygroundProvider from '@theme/Playground/Provider';
|
||||
import PlaygroundContainer from '@theme/Playground/Container';
|
||||
import PlaygroundLayout from '@theme/Playground/Layout';
|
||||
|
||||
import type {Props} from '@theme/Playground';
|
||||
import type {ThemeConfig} from '@docusaurus/theme-live-codeblock';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
function Header({children}: {children: ReactNode}) {
|
||||
return <div className={clsx(styles.playgroundHeader)}>{children}</div>;
|
||||
}
|
||||
|
||||
function LivePreviewLoader() {
|
||||
// Is it worth improving/translating?
|
||||
// eslint-disable-next-line @docusaurus/no-untranslated-text
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
function Preview() {
|
||||
// No SSR for the live preview
|
||||
// See https://github.com/facebook/docusaurus/issues/5747
|
||||
return (
|
||||
<BrowserOnly fallback={<LivePreviewLoader />}>
|
||||
{() => (
|
||||
<>
|
||||
<ErrorBoundary
|
||||
fallback={(params) => (
|
||||
<ErrorBoundaryErrorMessageFallback {...params} />
|
||||
)}>
|
||||
<LivePreview />
|
||||
</ErrorBoundary>
|
||||
<LiveError />
|
||||
</>
|
||||
)}
|
||||
</BrowserOnly>
|
||||
);
|
||||
}
|
||||
|
||||
function ResultWithHeader() {
|
||||
return (
|
||||
<>
|
||||
<Header>
|
||||
<Translate
|
||||
id="theme.Playground.result"
|
||||
description="The result label of the live codeblocks">
|
||||
Result
|
||||
</Translate>
|
||||
</Header>
|
||||
{/* https://github.com/facebook/docusaurus/issues/5747 */}
|
||||
<div className={styles.playgroundPreview}>
|
||||
<Preview />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ThemedLiveEditor() {
|
||||
const isBrowser = useIsBrowser();
|
||||
return (
|
||||
<LiveEditor
|
||||
// We force remount the editor on hydration,
|
||||
// otherwise dark prism theme is not applied
|
||||
key={String(isBrowser)}
|
||||
className={styles.playgroundEditor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function EditorWithHeader() {
|
||||
return (
|
||||
<>
|
||||
<Header>
|
||||
<Translate
|
||||
id="theme.Playground.liveEditor"
|
||||
description="The live editor label of the live codeblocks">
|
||||
Live Editor
|
||||
</Translate>
|
||||
</Header>
|
||||
<ThemedLiveEditor />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// this should rather be a stable function
|
||||
// see https://github.com/facebook/docusaurus/issues/9630#issuecomment-1855682643
|
||||
const DEFAULT_TRANSFORM_CODE = (code: string) => `${code};`;
|
||||
|
||||
export default function Playground({
|
||||
children,
|
||||
transformCode,
|
||||
...props
|
||||
}: Props): ReactNode {
|
||||
const {
|
||||
siteConfig: {themeConfig},
|
||||
} = useDocusaurusContext();
|
||||
const {
|
||||
liveCodeBlock: {playgroundPosition},
|
||||
} = themeConfig as ThemeConfig;
|
||||
const prismTheme = usePrismTheme();
|
||||
|
||||
const noInline = props.metastring?.includes('noInline') ?? false;
|
||||
|
||||
return (
|
||||
<div className={styles.playgroundContainer}>
|
||||
<LiveProvider
|
||||
code={children?.replace(/\n$/, '')}
|
||||
noInline={noInline}
|
||||
transformCode={transformCode ?? DEFAULT_TRANSFORM_CODE}
|
||||
theme={prismTheme}
|
||||
{...props}>
|
||||
{playgroundPosition === 'top' ? (
|
||||
<>
|
||||
<ResultWithHeader />
|
||||
<EditorWithHeader />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<EditorWithHeader />
|
||||
<ResultWithHeader />
|
||||
</>
|
||||
)}
|
||||
</LiveProvider>
|
||||
</div>
|
||||
<PlaygroundContainer>
|
||||
<PlaygroundProvider code={children} {...props}>
|
||||
<PlaygroundLayout />
|
||||
</PlaygroundProvider>
|
||||
</PlaygroundContainer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/// <reference types="@docusaurus/theme-classic" />
|
||||
/// <reference types="@docusaurus/module-type-aliases" />
|
||||
|
||||
declare module '@theme-init/CodeBlock' {
|
||||
import type CodeBlock from '@theme/CodeBlock';
|
||||
import type {Props as BaseProps} from '@theme/CodeBlock';
|
||||
|
||||
export interface Props extends BaseProps {
|
||||
live?: boolean;
|
||||
}
|
||||
const CodeBlockComp: typeof CodeBlock;
|
||||
export default CodeBlockComp;
|
||||
}
|
||||
|
|
@ -25,11 +25,11 @@ import Link from '@docusaurus/Link';
|
|||
import {useAllDocsData} from '@docusaurus/plugin-content-docs/client';
|
||||
import {
|
||||
HtmlClassNameProvider,
|
||||
PageMetadata,
|
||||
useEvent,
|
||||
usePluralForm,
|
||||
useSearchQueryString,
|
||||
} from '@docusaurus/theme-common';
|
||||
import {useTitleFormatter} from '@docusaurus/theme-common/internal';
|
||||
import Translate, {translate} from '@docusaurus/Translate';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import {
|
||||
|
|
@ -160,6 +160,25 @@ type ResultDispatcher =
|
|||
| {type: 'update'; value: ResultDispatcherState}
|
||||
| {type: 'advance'; value?: undefined};
|
||||
|
||||
function getSearchPageTitle(searchQuery: string | undefined): string {
|
||||
return searchQuery
|
||||
? translate(
|
||||
{
|
||||
id: 'theme.SearchPage.existingResultsTitle',
|
||||
message: 'Search results for "{query}"',
|
||||
description: 'The search page title for non-empty query',
|
||||
},
|
||||
{
|
||||
query: searchQuery,
|
||||
},
|
||||
)
|
||||
: translate({
|
||||
id: 'theme.SearchPage.emptyResultsTitle',
|
||||
message: 'Search the documentation',
|
||||
description: 'The search page title for empty query',
|
||||
});
|
||||
}
|
||||
|
||||
function SearchPageContent(): ReactNode {
|
||||
const {
|
||||
i18n: {currentLocale},
|
||||
|
|
@ -167,12 +186,13 @@ function SearchPageContent(): ReactNode {
|
|||
const {
|
||||
algolia: {appId, apiKey, indexName, contextualSearch},
|
||||
} = useAlgoliaThemeConfig();
|
||||
|
||||
const processSearchResultUrl = useSearchResultUrlProcessor();
|
||||
const documentsFoundPlural = useDocumentsFoundPlural();
|
||||
|
||||
const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers();
|
||||
const [searchQuery, setSearchQuery] = useSearchQueryString();
|
||||
const pageTitle = getSearchPageTitle(searchQuery);
|
||||
|
||||
const initialSearchResultState: ResultDispatcherState = {
|
||||
items: [],
|
||||
query: null,
|
||||
|
|
@ -310,24 +330,6 @@ function SearchPageContent(): ReactNode {
|
|||
),
|
||||
);
|
||||
|
||||
const getTitle = () =>
|
||||
searchQuery
|
||||
? translate(
|
||||
{
|
||||
id: 'theme.SearchPage.existingResultsTitle',
|
||||
message: 'Search results for "{query}"',
|
||||
description: 'The search page title for non-empty query',
|
||||
},
|
||||
{
|
||||
query: searchQuery,
|
||||
},
|
||||
)
|
||||
: translate({
|
||||
id: 'theme.SearchPage.emptyResultsTitle',
|
||||
message: 'Search the documentation',
|
||||
description: 'The search page title for empty query',
|
||||
});
|
||||
|
||||
const makeSearch = useEvent((page: number = 0) => {
|
||||
if (contextualSearch) {
|
||||
algoliaHelper.addDisjunctiveFacetRefinement('docusaurus_tag', 'default');
|
||||
|
|
@ -380,8 +382,9 @@ function SearchPageContent(): ReactNode {
|
|||
|
||||
return (
|
||||
<Layout>
|
||||
<PageMetadata title={pageTitle} />
|
||||
|
||||
<Head>
|
||||
<title>{useTitleFormatter(getTitle())}</title>
|
||||
{/*
|
||||
We should not index search pages
|
||||
See https://github.com/facebook/docusaurus/pull/3233
|
||||
|
|
@ -390,7 +393,7 @@ function SearchPageContent(): ReactNode {
|
|||
</Head>
|
||||
|
||||
<div className="container margin-vert--lg">
|
||||
<Heading as="h1">{getTitle()}</Heading>
|
||||
<Heading as="h1">{pageTitle}</Heading>
|
||||
|
||||
<form className="row" onSubmit={(e) => e.preventDefault()}>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import routes from '@generated/routes';
|
|||
import {useLocation} from '@docusaurus/router';
|
||||
import renderRoutes from '@docusaurus/renderRoutes';
|
||||
import Root from '@theme/Root';
|
||||
import ThemeProvider from '@theme/ThemeProvider';
|
||||
import SiteMetadata from '@theme/SiteMetadata';
|
||||
import normalizeLocation from './normalizeLocation';
|
||||
import {BrowserContextProvider} from './browserContext';
|
||||
|
|
@ -43,10 +44,12 @@ export default function App(): ReactNode {
|
|||
<DocusaurusContextProvider>
|
||||
<BrowserContextProvider>
|
||||
<Root>
|
||||
<SiteMetadataDefaults />
|
||||
<SiteMetadata />
|
||||
<BaseUrlIssueBanner />
|
||||
<AppNavigation />
|
||||
<ThemeProvider>
|
||||
<SiteMetadataDefaults />
|
||||
<SiteMetadata />
|
||||
<BaseUrlIssueBanner />
|
||||
<AppNavigation />
|
||||
</ThemeProvider>
|
||||
</Root>
|
||||
<HasHydratedDataAttribute />
|
||||
</BrowserContextProvider>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {type ReactNode} from 'react';
|
||||
import type {Props} from '@theme/ThemeProvider';
|
||||
|
||||
// Wrapper component expected to be implemented by a theme
|
||||
// Unlike <Layout>, it applies to all sites routes and never unmounts
|
||||
//
|
||||
// Unlike <Root>, the theme is expected to provide an implementation
|
||||
// <Root> is empty and the implementation is expected to be provided by the user
|
||||
//
|
||||
// Tree order: Root > ThemeProvider > Layout
|
||||
export default function ThemeProvider({children}: Props): ReactNode {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ exports[`base webpack config creates webpack aliases 1`] = `
|
|||
"@theme-original/PluginThemeComponentOverriddenByUser": "pluginThemeFolder/PluginThemeComponentOverriddenByUser.js",
|
||||
"@theme-original/Root": "../../../../client/theme-fallback/Root/index.tsx",
|
||||
"@theme-original/SiteMetadata": "../../../../client/theme-fallback/SiteMetadata/index.tsx",
|
||||
"@theme-original/ThemeProvider": "../../../../client/theme-fallback/ThemeProvider/index.tsx",
|
||||
"@theme-original/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.js",
|
||||
"@theme/Error": "../../../../client/theme-fallback/Error/index.tsx",
|
||||
"@theme/Layout": "../../../../client/theme-fallback/Layout/index.tsx",
|
||||
|
|
@ -45,6 +46,7 @@ exports[`base webpack config creates webpack aliases 1`] = `
|
|||
"@theme/PluginThemeComponentOverriddenByUser": "src/theme/PluginThemeComponentOverriddenByUser.js",
|
||||
"@theme/Root": "../../../../client/theme-fallback/Root/index.tsx",
|
||||
"@theme/SiteMetadata": "../../../../client/theme-fallback/SiteMetadata/index.tsx",
|
||||
"@theme/ThemeProvider": "../../../../client/theme-fallback/ThemeProvider/index.tsx",
|
||||
"@theme/UserThemeComponent1": "src/theme/UserThemeComponent1.js",
|
||||
"@theme/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.js",
|
||||
"@theme/subfolder/UserThemeComponent2": "src/theme/subfolder/UserThemeComponent2.js",
|
||||
|
|
|
|||
|
|
@ -79,6 +79,10 @@ exports[`loadThemeAliases next alias can override the previous alias 1`] = `
|
|||
"@theme-original/SiteMetadata",
|
||||
"<PROJECT_ROOT>/packages/docusaurus/src/client/theme-fallback/SiteMetadata/index.tsx",
|
||||
],
|
||||
[
|
||||
"@theme-original/ThemeProvider",
|
||||
"<PROJECT_ROOT>/packages/docusaurus/src/client/theme-fallback/ThemeProvider/index.tsx",
|
||||
],
|
||||
[
|
||||
"@theme/Error",
|
||||
"<PROJECT_ROOT>/packages/docusaurus/src/client/theme-fallback/Error/index.tsx",
|
||||
|
|
@ -127,5 +131,9 @@ exports[`loadThemeAliases next alias can override the previous alias 1`] = `
|
|||
"@theme/SiteMetadata",
|
||||
"<PROJECT_ROOT>/packages/docusaurus/src/client/theme-fallback/SiteMetadata/index.tsx",
|
||||
],
|
||||
[
|
||||
"@theme/ThemeProvider",
|
||||
"<PROJECT_ROOT>/packages/docusaurus/src/client/theme-fallback/ThemeProvider/index.tsx",
|
||||
],
|
||||
]
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -25,9 +25,6 @@ beforeinstallprompt
|
|||
Bhatt
|
||||
Blockquotes
|
||||
blockquotes
|
||||
BLUESKY
|
||||
Bluesky
|
||||
bluesky
|
||||
Bokmål
|
||||
bunx
|
||||
caabernathy
|
||||
|
|
@ -36,14 +33,12 @@ cdabcdab
|
|||
cdpath
|
||||
Cena
|
||||
cena
|
||||
changefreq
|
||||
Chedeau
|
||||
chedeau
|
||||
Clément
|
||||
Codegen
|
||||
codegen
|
||||
codesandbox
|
||||
Codespaces
|
||||
commonmark
|
||||
contravariance
|
||||
corejs
|
||||
|
|
@ -61,9 +56,6 @@ dedup
|
|||
devto
|
||||
dingers
|
||||
Dmitry
|
||||
Docsearch
|
||||
docsearch
|
||||
Docsify
|
||||
Docu
|
||||
docu
|
||||
docusuarus
|
||||
|
|
@ -117,8 +109,6 @@ Héctor
|
|||
héllô
|
||||
IANAD
|
||||
Infima
|
||||
infima
|
||||
Infima's
|
||||
inlines
|
||||
interactiveness
|
||||
Interpolatable
|
||||
|
|
@ -140,14 +130,11 @@ Knapen
|
|||
Koyeb
|
||||
Koyeb's
|
||||
Lamana
|
||||
Lastmod
|
||||
lastmod
|
||||
Lifecycles
|
||||
lifecycles
|
||||
lightningcss
|
||||
Linkify
|
||||
linkify
|
||||
lockb
|
||||
Lorber
|
||||
lorber
|
||||
Lorber's
|
||||
|
|
@ -196,7 +183,6 @@ navigations
|
|||
navlink
|
||||
netrc
|
||||
newtab
|
||||
Nextra
|
||||
ngryman
|
||||
Nisarag
|
||||
noflash
|
||||
|
|
@ -230,16 +216,12 @@ paraiso
|
|||
pathinfo
|
||||
paularmstrong
|
||||
philpl
|
||||
Photoshop
|
||||
photoshop
|
||||
Pipeable
|
||||
playbtn
|
||||
Plushie
|
||||
plushie
|
||||
plushies
|
||||
posthog
|
||||
Precache
|
||||
precache
|
||||
precached
|
||||
precaching
|
||||
preconfigured
|
||||
|
|
@ -310,11 +292,9 @@ stackoverflow
|
|||
Stormkit
|
||||
Strikethrough
|
||||
strikethroughs
|
||||
stylelintrc
|
||||
sublabel
|
||||
sublicensable
|
||||
sublist
|
||||
subpage
|
||||
subsetting
|
||||
subsubcategory
|
||||
subsubfolder
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import Readme from "../README.mdx"
|
|||
- [Tabs tests](/tests/pages/tabs-tests)
|
||||
- [z-index tests](/tests/pages/z-index-tests)
|
||||
- [Head metadata tests](/tests/pages/head-metadata)
|
||||
- [Unlisted page](/tests/pages/unlisted)
|
||||
- [Unlisted page](/tests/pages/my-custom/unlisted/slug)
|
||||
- [Analytics](/tests/pages/analytics)
|
||||
- [History tests](/tests/pages/history-tests)
|
||||
- [Embeds](/tests/pages/embeds)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
unlisted: true
|
||||
slug: /my-custom/unlisted/slug
|
||||
---
|
||||
|
||||
# Unlisted page
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ Accepted fields:
|
|||
| `description` | `string` | The first line of Markdown content | The description of your page, which will become the `<meta name="description" content="..."/>` and `<meta property="og:description" content="..."/>` in `<head>`, used by search engines. |
|
||||
| `keywords` | `string[]` | `undefined` | Keywords meta tag, which will become the `<meta name="keywords" content="keyword1,keyword2,..."/>` in `<head>`, used by search engines. |
|
||||
| `image` | `string` | `undefined` | Cover or thumbnail image that will be used as the `<meta property="og:image" content="..."/>` in the `<head>`, enhancing link previews on social media and messaging platforms. |
|
||||
| `slug` | `string` | File path | Allows to customize the page URL (`/<routeBasePath>/<slug>`). Support multiple patterns: `slug: my-page`, `slug: /my/page`, slug: `/`. |
|
||||
| `wrapperClassName` | `string` | | Class name to be added to the wrapper element to allow targeting specific page content. |
|
||||
| `hide_table_of_contents` | `boolean` | `false` | Whether to hide the table of contents to the right. |
|
||||
| `draft` | `boolean` | `false` | Draft pages will only be available during development. |
|
||||
|
|
@ -131,6 +132,7 @@ description: Markdown page SEO description
|
|||
wrapperClassName: markdown-page
|
||||
hide_table_of_contents: false
|
||||
draft: true
|
||||
slug: /markdown-page
|
||||
---
|
||||
|
||||
Markdown page content
|
||||
|
|
|
|||
|
|
@ -67,11 +67,11 @@ image: https://i.imgur.com/mErPwqL.png
|
|||
hide_table_of_contents: false
|
||||
---
|
||||
|
||||
Welcome to this blog. This blog is created with [**Docusaurus 2**](https://docusaurus.io/).
|
||||
Welcome to this blog. This blog is created with [**Docusaurus**](https://docusaurus.io/).
|
||||
|
||||
<!-- truncate -->
|
||||
|
||||
This is my first post on Docusaurus 2.
|
||||
This is my first post on Docusaurus.
|
||||
|
||||
A whole bunch of exploration to follow.
|
||||
```
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {type ComponentProps, type ReactNode} from 'react';
|
||||
import {TitleFormatterProvider} from '@docusaurus/theme-common/internal';
|
||||
import type {Props} from '@theme/ThemeProvider/TitleFormatter';
|
||||
|
||||
type FormatterProp = ComponentProps<typeof TitleFormatterProvider>['formatter'];
|
||||
|
||||
const formatter: FormatterProp = (params) => {
|
||||
// Custom title for dogfood plugin instances
|
||||
if (params.plugin.id.endsWith('tests')) {
|
||||
const pluginLabel = `${params.plugin.name.replace(
|
||||
'docusaurus-plugin-content-',
|
||||
'',
|
||||
)} plugin`;
|
||||
return `🐕 Dogfood - ${pluginLabel}`;
|
||||
}
|
||||
|
||||
// Default title otherwise
|
||||
return params.defaultFormatter(params);
|
||||
};
|
||||
|
||||
export default function ThemeProviderTitleFormatter({
|
||||
children,
|
||||
}: Props): ReactNode {
|
||||
return (
|
||||
<TitleFormatterProvider formatter={formatter}>
|
||||
{children}
|
||||
</TitleFormatterProvider>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue