Compare commits

...

24 Commits
main ... v3.1.0

Author SHA1 Message Date
sebastienlorber a5e675821f v3.1.0 2024-01-05 19:23:55 +01:00
sebastienlorber a97a74ff62 update lockfile 2024-01-05 18:16:09 +01:00
sebastienlorber 0da59368fc remove problematic @ts-expect-error 2024-01-05 18:16:01 +01:00
ozaki 9088efb306 docs: broken link in release 3.0 page (#9573)
fix: typo
2024-01-05 17:52:54 +01:00
sebastienlorber 643c33c11c update lockfile 2024-01-05 17:52:39 +01:00
slorber 921fa240e7 refactor: apply lint autofix 2024-01-05 16:18:03 +00:00
Sébastien Lorber a9cef92bd2 fix(theme): allow empty code blocks and live playgrounds (#9704) 2024-01-05 17:10:52 +01:00
Sébastien Lorber a779857353 fix(create-docusaurus): fix init template code blocks, and little improvements (#9696)
Co-authored-by: Ivan Mar (sOkam!) <7308253+heysokam@users.noreply.github.com>
2024-01-05 17:10:45 +01:00
ozaki 760a5ae533 feat(core): make broken link checker detect broken anchors - add `onBrokenAnchors` config (#9528)
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
2024-01-05 17:10:35 +01:00
Simen Bekkhus 6d1897dfd9 fix(pwa-plugin): upgrade workbox (#9668) 2024-01-05 17:10:19 +01:00
Sébastien Lorber 31bd1b188e feat(mdx-loader): add support for siteConfig.markdown.remarkRehypeOptions (#9674) 2024-01-05 17:10:09 +01:00
Tatsunori Uchino 539fd731e0 feat(theme-common): code block MagicComments support for (Visual) Basic/Batch/Fortran/COBOL/ML (#9671) 2024-01-05 17:10:01 +01:00
Sébastien Lorber 803cceee39 chore: attempt fo fix Lint Autofix workflow (#9632) 2024-01-05 17:09:28 +01:00
Sébastien Lorber 6c06a70905 chore: add lint autofix CI job (#9604) 2024-01-05 17:09:16 +01:00
Joshua Chen 839ccbd451 fix(cli): output help when no conventional config + no subcommand (#9648) 2024-01-05 17:06:16 +01:00
Sébastien Lorber 1a91145b3b feat: siteConfig.markdown.parseFrontMatter hook (#9624) 2024-01-05 17:06:02 +01:00
Sébastien Lorber 85e32fd31a fix(live-codeblock): stabilize react-live transformCode callback, fix editor/preview desync (#9631) 2024-01-05 17:05:52 +01:00
ozaki 3c051ee9f9 feat(core): enable port configuration via environment variable (#9610)
Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
2024-01-05 17:05:35 +01:00
Joshua Chen 17a2751cf5 fix(utils): Markdown link replacement with <> but no spaces (#9617) 2024-01-05 17:05:26 +01:00
axmmisaka ab6147a99b fix(type-aliases): add `title` prop for imported inline SVG React components (#9612)
Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
2024-01-05 17:05:14 +01:00
ozaki ed758fce06 fix(content-blog): add baseUrl for author.image_url (#9581) 2024-01-05 17:05:05 +01:00
Janessa Garrow 68cc2814eb refactor(theme-common): allow optional desktopBreakpoint param in useWindowSize (#9335)
Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
2024-01-05 17:04:46 +01:00
c0h1b4 97278be7fb fix(i18n): complete translations for theme-common.json Brazilian Portuguese (pt-BR) (#9477) 2024-01-05 17:04:07 +01:00
Sébastien Lorber a2e05d2118
chore: release Docusaurus 3.0.1 (#9596)
Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
Co-authored-by: Joey Clover <joey@popos.local>
Co-authored-by: reece-white <93522192+reece-white@users.noreply.github.com>
Co-authored-by: Shreesh Nautiyal <114166000+Shreesh09@users.noreply.github.com>
Co-authored-by: Nick Gerleman <nick@nickgerleman.com>
Co-authored-by: Chongyi Zheng <git@zcy.dev>
Co-authored-by: MCR Studio <99176216+mcrstudio@users.noreply.github.com>
fix(create-docusaurus): fix readme docusaurus 2 ref (#9487)
fix(theme): fix firefox CSS :has() support bug (#9530)
fix(theme): docs html sidebar items should always be visible (#9531)
fix: v3 admonitions should support v2 title syntax for nested admonitions (#9535)
fix(theme-classic): fixed wrong cursor on dropdown menu in navbar, when window is small (#9398)
fix(theme): upgrade prism-react-renderer, fix html script and style tag highlighting (#9567)
fix: add v2 retrocompatible support for quoted admonitions (#9570)
2023-11-30 19:47:23 +01:00
152 changed files with 4001 additions and 2397 deletions

44
.github/workflows/lint-autofix.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name: Lint AutoFix
on:
pull_request:
branches:
- main
- docusaurus-v**
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
lint-autofix:
name: Lint AutoFix
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.head_ref }}
- name: Installation
run: yarn
- name: AutoFix Format
run: yarn format
- name: AutoFix JS
run: yarn lint:js:fix
- name: AutoFix Style
run: yarn lint:style:fix
- name: AutoFix Spelling
run: yarn lint:spelling:fix
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: 'refactor: apply lint autofix'

View File

@ -1,8 +1,5 @@
{
"*.{js,jsx,ts,tsx,mjs}": ["eslint --fix"],
"*.css": ["stylelint --allow-empty-input --fix"],
"*": [
"prettier --ignore-unknown --write",
"cspell --no-must-find-files --no-progress"
]
"*": ["prettier --ignore-unknown --write"]
}

View File

@ -24,5 +24,5 @@ website/versioned_sidebars/*.json
examples/
website/static/katex/katex.min.css
website/changelog/_swizzle_theme_tests
website/changelog
website/_dogfooding/_swizzle_theme_tests

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "argos",
"version": "3.0.0",
"version": "3.1.0",
"description": "Argos visual diff tests",
"license": "MIT",
"private": true,

View File

@ -1,6 +1,6 @@
# Website
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
### Installation

View File

@ -1,6 +1,6 @@
# Website
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
### Installation

View File

@ -1,5 +1,5 @@
{
"version": "3.0.0",
"version": "3.1.0",
"npmClient": "yarn",
"useWorkspaces": true,
"useNx": false,

View File

@ -51,8 +51,11 @@
"lint": "yarn lint:js && yarn lint:style && yarn lint:spelling",
"lint:ci": "yarn lint:js --quiet && yarn lint:style && yarn lint:spelling",
"lint:js": "eslint --cache --report-unused-disable-directives \"**/*.{js,jsx,ts,tsx,mjs}\"",
"lint:spelling": "cspell \"**\" --no-progress",
"lint:js:fix": "yarn lint:js --fix",
"lint:spelling": "cspell \"**\" --no-progress --show-context --show-suggestions",
"lint:spelling:fix": "yarn rimraf project-words.txt && echo \"# Project Words - DO NOT TOUCH - This is updated through CI\" >> project-words.txt && yarn -s lint:spelling --words-only --unique --no-exit-code --no-summary \"**\" | sort --ignore-case >> project-words.txt",
"lint:style": "stylelint \"**/*.css\"",
"lint:style:fix": "yarn lint:style --fix",
"lerna": "lerna",
"test": "jest",
"test:build:website": "./admin/scripts/test-release.sh",
@ -80,7 +83,7 @@
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"cross-env": "^7.0.3",
"cspell": "^6.31.2",
"cspell": "^8.1.0",
"eslint": "^8.45.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.8.0",

View File

@ -10,6 +10,10 @@ npm init docusaurus
yarn create docusaurus
```
```bash
npx create-docusaurus@latest
```
## Usage
Please see the [installation documentation](https://docusaurus.io/docs/installation).

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "docusaurus-2-classic-typescript-template",
"version": "3.0.0",
"version": "3.1.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@ -15,18 +15,18 @@
"typecheck": "tsc"
},
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/preset-classic": "3.0.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/preset-classic": "3.1.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^1.2.1",
"prism-react-renderer": "^2.1.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/tsconfig": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/module-type-aliases": "3.1.0",
"@docusaurus/tsconfig": "3.1.0",
"@docusaurus/types": "3.1.0",
"typescript": "~5.2.2"
},
"browserslist": {

View File

@ -1,6 +1,6 @@
{
"name": "docusaurus-2-classic-template",
"version": "3.0.0",
"version": "3.1.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@ -14,17 +14,17 @@
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/preset-classic": "3.0.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/preset-classic": "3.1.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^1.2.1",
"prism-react-renderer": "^2.1.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/types": "3.0.0"
"@docusaurus/module-type-aliases": "3.1.0",
"@docusaurus/types": "3.1.0"
},
"browserslist": {
"production": [

View File

@ -1,6 +1,6 @@
# Website
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
### Installation

View File

@ -61,13 +61,13 @@ You can reference images relative to the current file as well. This is particula
Markdown code blocks are supported with Syntax highlighting.
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return (
<h1>Hello, Docusaurus!</h1>
)
}
```
````md
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return <h1>Hello, Docusaurus!</h1>;
}
```
````
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
@ -79,17 +79,19 @@ function HelloDocusaurus() {
Docusaurus has a special syntax to create admonitions and callouts:
:::tip My tip
```md
:::tip My tip
Use this awesome feature option
Use this awesome feature option
:::
:::
:::danger Take care
:::danger Take care
This action is dangerous
This action is dangerous
:::
:::
```
:::tip My tip

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/cssnano-preset",
"version": "3.0.0",
"version": "3.1.0",
"description": "Advanced cssnano preset for maximum optimization.",
"main": "lib/index.js",
"license": "MIT",

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/mdx-loader",
"version": "3.0.0",
"version": "3.1.0",
"description": "Docusaurus Loader for MDX",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -20,9 +20,9 @@
"dependencies": {
"@babel/parser": "^7.22.7",
"@babel/traverse": "^7.22.8",
"@docusaurus/logger": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/logger": "3.1.0",
"@docusaurus/utils": "3.1.0",
"@docusaurus/utils-validation": "3.1.0",
"@mdx-js/mdx": "^3.0.0",
"@slorber/remark-comment": "^1.0.0",
"escape-html": "^1.0.3",
@ -46,7 +46,7 @@
"webpack": "^5.88.1"
},
"devDependencies": {
"@docusaurus/types": "3.0.0",
"@docusaurus/types": "3.1.0",
"@types/escape-html": "^1.0.2",
"@types/mdast": "^4.0.2",
"@types/stringify-object": "^3.3.1",

View File

@ -8,7 +8,7 @@
import fs from 'fs-extra';
import logger from '@docusaurus/logger';
import {
parseFrontMatter,
DEFAULT_PARSE_FRONT_MATTER,
escapePath,
getFileLoaderUtils,
getWebpackLoaderCompilerName,
@ -133,7 +133,7 @@ function extractContentTitleData(data: {
export async function mdxLoader(
this: LoaderContext<Options>,
fileString: string,
fileContent: string,
): Promise<void> {
const compilerName = getWebpackLoaderCompilerName(this);
const callback = this.async();
@ -143,11 +143,15 @@ export async function mdxLoader(
ensureMarkdownConfig(reqOptions);
const {frontMatter} = parseFrontMatter(fileString);
const {frontMatter} = await reqOptions.markdownConfig.parseFrontMatter({
filePath,
fileContent,
defaultParseFrontMatter: DEFAULT_PARSE_FRONT_MATTER,
});
const mdxFrontMatter = validateMDXFrontMatter(frontMatter.mdx);
const preprocessedContent = preprocessor({
fileContent: fileString,
fileContent,
filePath,
admonitions: reqOptions.admonitions,
markdownConfig: reqOptions.markdownConfig,

View File

@ -165,6 +165,7 @@ async function createProcessorFactory() {
const mdxProcessor = createMdxProcessor({
...processorOptions,
remarkRehypeOptions: options.markdownConfig.remarkRehypeOptions,
format,
});

View File

@ -12,17 +12,17 @@ exports[`transformAsset plugin pathname protocol 1`] = `
exports[`transformAsset plugin transform md links to <a /> 1`] = `
"[asset](https://example.com/asset.pdf)
<a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default} />
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default} />
<a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}>asset</a>
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}>asset</a>
in paragraph <a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}>asset</a>
in paragraph <a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}>asset</a>
<a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset (2).pdf").default}>asset with URL encoded chars</a>
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset (2).pdf").default}>asset with URL encoded chars</a>
<a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default + '#page=2'}>asset with hash</a>
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default + '#page=2'}>asset with hash</a>
<a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default} title="Title">asset</a>
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default} title="Title">asset</a>
[page](noUrl.md)
@ -36,24 +36,24 @@ in paragraph <a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file
[assets](/github/!file-loader!/assets.pdf)
<a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}>asset</a>
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}>asset</a>
<a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static2/asset2.pdf").default}>asset2</a>
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static2/asset2.pdf").default}>asset2</a>
<a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>staticAsset.pdf</a>
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>staticAsset.pdf</a>
<a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>@site/static/staticAsset.pdf</a>
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>@site/static/staticAsset.pdf</a>
<a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default + '#page=2'} title="Title">@site/static/staticAsset.pdf</a>
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default + '#page=2'} title="Title">@site/static/staticAsset.pdf</a>
<a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>Just staticAsset.pdf</a>, and <a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>**awesome** staticAsset 2.pdf 'It is really "AWESOME"'</a>, but also <a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>coded \`staticAsset 3.pdf\`</a>
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>Just staticAsset.pdf</a>, and <a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>**awesome** staticAsset 2.pdf 'It is really "AWESOME"'</a>, but also <a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>coded \`staticAsset 3.pdf\`</a>
<a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAssetImage.png").default}><img alt="Clickable Docusaurus logo" src={require("!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/staticAssetImage.png").default} width="200" height="200" /></a>
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAssetImage.png").default}><img alt="Clickable Docusaurus logo" src={require("!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/staticAssetImage.png").default} width="200" height="200" /></a>
<a target="_blank" href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}><span style={{color: "red"}}>Stylized link to asset file</span></a>
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}><span style={{color: "red"}}>Stylized link to asset file</span></a>
<a target="_blank" href={require("./data.raw!=!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./data.json").default}>JSON</a>
<a target="_blank" data-noBrokenLinkCheck={true} href={require("./data.raw!=!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./data.json").default}>JSON</a>
<a target="_blank" href={require("./static/static-json.raw!=!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/static-json.json").default}>static JSON</a>
<a target="_blank" data-noBrokenLinkCheck={true} href={require("./static/static-json.raw!=!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/static-json.json").default}>static JSON</a>
"
`;

View File

@ -73,6 +73,34 @@ async function toAssetRequireNode(
value: '_blank',
});
// Assets are not routes, and are required by Webpack already
// They should not trigger the broken link checker
attributes.push({
type: 'mdxJsxAttribute',
name: 'data-noBrokenLinkCheck',
value: {
type: 'mdxJsxAttributeValueExpression',
value: 'true',
data: {
estree: {
type: 'Program',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'Literal',
value: true,
raw: 'true',
},
},
],
sourceType: 'module',
comments: [],
},
},
},
});
attributes.push({
type: 'mdxJsxAttribute',
name: 'href',

View File

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

View File

@ -260,6 +260,15 @@ declare module '@docusaurus/useRouteContext' {
export default function useRouteContext(): PluginRouteContext;
}
declare module '@docusaurus/useBrokenLinks' {
export type BrokenLinks = {
collectLink: (link: string) => void;
collectAnchor: (anchor: string) => void;
};
export default function useBrokenLinks(): BrokenLinks;
}
declare module '@docusaurus/useIsBrowser' {
export default function useIsBrowser(): boolean;
}
@ -356,7 +365,9 @@ declare module '@docusaurus/useGlobalData' {
declare module '*.svg' {
import type {ComponentType, SVGProps} from 'react';
const ReactComponent: ComponentType<SVGProps<SVGSVGElement>>;
const ReactComponent: ComponentType<
SVGProps<SVGSVGElement> & {title?: string}
>;
export default ReactComponent;
}

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-client-redirects",
"version": "3.0.0",
"version": "3.1.0",
"description": "Client redirects plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,18 +18,18 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/logger": "3.1.0",
"@docusaurus/utils": "3.1.0",
"@docusaurus/utils-common": "3.1.0",
"@docusaurus/utils-validation": "3.1.0",
"eta": "^2.2.0",
"fs-extra": "^11.1.1",
"lodash": "^4.17.21",
"tslib": "^2.6.0"
},
"devDependencies": {
"@docusaurus/types": "3.0.0"
"@docusaurus/types": "3.1.0"
},
"peerDependencies": {
"react": "^18.0.0",

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-blog",
"version": "3.0.0",
"version": "3.1.0",
"description": "Blog plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-content-blog.d.ts",
@ -19,13 +19,13 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/logger": "3.1.0",
"@docusaurus/mdx-loader": "3.1.0",
"@docusaurus/types": "3.1.0",
"@docusaurus/utils": "3.1.0",
"@docusaurus/utils-common": "3.1.0",
"@docusaurus/utils-validation": "3.1.0",
"cheerio": "^1.0.0-rc.12",
"feed": "^4.2.2",
"fs-extra": "^11.1.1",

View File

@ -19,6 +19,7 @@ describe('getBlogPostAuthors', () => {
getBlogPostAuthors({
frontMatter: {},
authorsMap: undefined,
baseUrl: '/',
}),
).toEqual([]);
expect(
@ -27,6 +28,7 @@ describe('getBlogPostAuthors', () => {
authors: [],
},
authorsMap: undefined,
baseUrl: '/',
}),
).toEqual([]);
});
@ -38,6 +40,7 @@ describe('getBlogPostAuthors', () => {
author: 'Sébastien Lorber',
},
authorsMap: undefined,
baseUrl: '/',
}),
).toEqual([{name: 'Sébastien Lorber'}]);
expect(
@ -46,6 +49,7 @@ describe('getBlogPostAuthors', () => {
authorTitle: 'maintainer',
},
authorsMap: undefined,
baseUrl: '/',
}),
).toEqual([{title: 'maintainer'}]);
expect(
@ -54,8 +58,27 @@ describe('getBlogPostAuthors', () => {
authorImageURL: 'https://github.com/slorber.png',
},
authorsMap: undefined,
baseUrl: '/',
}),
).toEqual([{imageURL: 'https://github.com/slorber.png'}]);
expect(
getBlogPostAuthors({
frontMatter: {
authorImageURL: '/img/slorber.png',
},
authorsMap: undefined,
baseUrl: '/',
}),
).toEqual([{imageURL: '/img/slorber.png'}]);
expect(
getBlogPostAuthors({
frontMatter: {
authorImageURL: '/img/slorber.png',
},
authorsMap: undefined,
baseUrl: '/baseURL',
}),
).toEqual([{imageURL: '/baseURL/img/slorber.png'}]);
expect(
getBlogPostAuthors({
frontMatter: {
@ -68,6 +91,7 @@ describe('getBlogPostAuthors', () => {
authorURL: 'https://github.com/slorber2',
},
authorsMap: undefined,
baseUrl: '/',
}),
).toEqual([
{
@ -86,8 +110,69 @@ describe('getBlogPostAuthors', () => {
authors: 'slorber',
},
authorsMap: {slorber: {name: 'Sébastien Lorber'}},
baseUrl: '/',
}),
).toEqual([{key: 'slorber', name: 'Sébastien Lorber'}]);
expect(
getBlogPostAuthors({
frontMatter: {
authors: 'slorber',
},
authorsMap: {
slorber: {
name: 'Sébastien Lorber',
imageURL: 'https://github.com/slorber.png',
},
},
baseUrl: '/',
}),
).toEqual([
{
key: 'slorber',
name: 'Sébastien Lorber',
imageURL: 'https://github.com/slorber.png',
},
]);
expect(
getBlogPostAuthors({
frontMatter: {
authors: 'slorber',
},
authorsMap: {
slorber: {
name: 'Sébastien Lorber',
imageURL: '/img/slorber.png',
},
},
baseUrl: '/',
}),
).toEqual([
{
key: 'slorber',
name: 'Sébastien Lorber',
imageURL: '/img/slorber.png',
},
]);
expect(
getBlogPostAuthors({
frontMatter: {
authors: 'slorber',
},
authorsMap: {
slorber: {
name: 'Sébastien Lorber',
imageURL: '/img/slorber.png',
},
},
baseUrl: '/baseUrl',
}),
).toEqual([
{
key: 'slorber',
name: 'Sébastien Lorber',
imageURL: '/baseUrl/img/slorber.png',
},
]);
});
it('can read authors string[]', () => {
@ -100,6 +185,7 @@ describe('getBlogPostAuthors', () => {
slorber: {name: 'Sébastien Lorber', title: 'maintainer'},
yangshun: {name: 'Yangshun Tay'},
},
baseUrl: '/',
}),
).toEqual([
{key: 'slorber', name: 'Sébastien Lorber', title: 'maintainer'},
@ -114,6 +200,7 @@ describe('getBlogPostAuthors', () => {
authors: {name: 'Sébastien Lorber', title: 'maintainer'},
},
authorsMap: undefined,
baseUrl: '/',
}),
).toEqual([{name: 'Sébastien Lorber', title: 'maintainer'}]);
});
@ -128,6 +215,7 @@ describe('getBlogPostAuthors', () => {
],
},
authorsMap: undefined,
baseUrl: '/',
}),
).toEqual([
{name: 'Sébastien Lorber', title: 'maintainer'},
@ -153,6 +241,7 @@ describe('getBlogPostAuthors', () => {
slorber: {name: 'Sébastien Lorber', title: 'maintainer'},
yangshun: {name: 'Yangshun Tay', title: 'Yangshun title original'},
},
baseUrl: '/',
}),
).toEqual([
{key: 'slorber', name: 'Sébastien Lorber', title: 'maintainer'},
@ -173,6 +262,7 @@ describe('getBlogPostAuthors', () => {
authors: 'slorber',
},
authorsMap: undefined,
baseUrl: '/',
}),
).toThrowErrorMatchingInlineSnapshot(`
"Can't reference blog post authors by a key (such as 'slorber') because no authors map file could be loaded.
@ -187,6 +277,7 @@ describe('getBlogPostAuthors', () => {
authors: 'slorber',
},
authorsMap: {},
baseUrl: '/',
}),
).toThrowErrorMatchingInlineSnapshot(`
"Can't reference blog post authors by a key (such as 'slorber') because no authors map file could be loaded.
@ -205,6 +296,7 @@ describe('getBlogPostAuthors', () => {
yangshun: {name: 'Yangshun Tay'},
jmarcey: {name: 'Joel Marcey'},
},
baseUrl: '/',
}),
).toThrowErrorMatchingInlineSnapshot(`
"Blog author with key "slorber" not found in the authors map file.
@ -225,6 +317,7 @@ describe('getBlogPostAuthors', () => {
yangshun: {name: 'Yangshun Tay'},
jmarcey: {name: 'Joel Marcey'},
},
baseUrl: '/',
}),
).toThrowErrorMatchingInlineSnapshot(`
"Blog author with key "slorber" not found in the authors map file.
@ -245,6 +338,7 @@ describe('getBlogPostAuthors', () => {
yangshun: {name: 'Yangshun Tay'},
jmarcey: {name: 'Joel Marcey'},
},
baseUrl: '/',
}),
).toThrowErrorMatchingInlineSnapshot(`
"Blog author with key "slorber" not found in the authors map file.
@ -262,6 +356,7 @@ describe('getBlogPostAuthors', () => {
author: 'Yangshun Tay',
},
authorsMap: undefined,
baseUrl: '/',
}),
).toThrowErrorMatchingInlineSnapshot(`
"To declare blog post authors, use the 'authors' front matter in priority.
@ -275,6 +370,7 @@ describe('getBlogPostAuthors', () => {
author_title: 'legacy title',
},
authorsMap: {slorber: {name: 'Sébastien Lorber'}},
baseUrl: '/',
}),
).toThrowErrorMatchingInlineSnapshot(`
"To declare blog post authors, use the 'authors' front matter in priority.

View File

@ -8,6 +8,7 @@
import {jest} from '@jest/globals';
import path from 'path';
import fs from 'fs-extra';
import {DEFAULT_PARSE_FRONT_MATTER} from '@docusaurus/utils';
import {DEFAULT_OPTIONS} from '../options';
import {generateBlogPosts} from '../blogUtils';
import {createBlogFeedFiles} from '../feed';
@ -31,6 +32,8 @@ const DefaultI18N: I18n = {
},
};
const markdown = {parseFrontMatter: DEFAULT_PARSE_FRONT_MATTER};
function getBlogContentPaths(siteDir: string): BlogContentPaths {
return {
contentPath: path.resolve(siteDir, 'blog'),
@ -72,6 +75,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
baseUrl: '/',
url: 'https://docusaurus.io',
favicon: 'image/favicon.ico',
markdown,
};
const outDir = path.join(siteDir, 'build-snap');
@ -110,6 +114,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
baseUrl: '/myBaseUrl/',
url: 'https://docusaurus.io',
favicon: 'image/favicon.ico',
markdown,
};
// Build is quite difficult to mock, so we built the blog beforehand and
@ -152,6 +157,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
baseUrl: '/myBaseUrl/',
url: 'https://docusaurus.io',
favicon: 'image/favicon.ico',
markdown,
};
// Build is quite difficult to mock, so we built the blog beforehand and
@ -204,6 +210,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
baseUrl: '/myBaseUrl/',
url: 'https://docusaurus.io',
favicon: 'image/favicon.ico',
markdown,
};
// Build is quite difficult to mock, so we built the blog beforehand and

View File

@ -16,6 +16,7 @@ import type {
LoadContext,
I18n,
Validate,
MarkdownConfig,
} from '@docusaurus/types';
import type {
BlogPost,
@ -24,6 +25,24 @@ import type {
EditUrlFunction,
} from '@docusaurus/plugin-content-blog';
const markdown: MarkdownConfig = {
format: 'mdx',
mermaid: true,
mdx1Compat: {
comments: true,
headingIds: true,
admonitions: true,
},
parseFrontMatter: async (params) => {
// Reuse the default parser
const result = await params.defaultParseFrontMatter(params);
if (result.frontMatter.title === 'Complex Slug') {
result.frontMatter.custom_frontMatter = 'added by parseFrontMatter';
}
return result;
},
};
function findByTitle(
blogPosts: BlogPost[],
title: string,
@ -81,6 +100,7 @@ const getPlugin = async (
title: 'Hello',
baseUrl: '/',
url: 'https://docusaurus.io',
markdown,
} as DocusaurusConfig;
return pluginContentBlog(
{
@ -242,6 +262,7 @@ describe('blog plugin', () => {
slug: '/hey/my super path/héllô',
title: 'Complex Slug',
tags: ['date', 'complex'],
custom_frontMatter: 'added by parseFrontMatter',
},
tags: [
{

View File

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {getDataFileData} from '@docusaurus/utils';
import {getDataFileData, normalizeUrl} from '@docusaurus/utils';
import {Joi, URISchema} from '@docusaurus/utils-validation';
import type {BlogContentPaths} from './types';
import type {
@ -68,17 +68,37 @@ export async function getAuthorsMap(params: {
type AuthorsParam = {
frontMatter: BlogPostFrontMatter;
authorsMap: AuthorsMap | undefined;
baseUrl: string;
};
function normalizeImageUrl({
imageURL,
baseUrl,
}: {
imageURL: string | undefined;
baseUrl: string;
}) {
return imageURL?.startsWith('/')
? normalizeUrl([baseUrl, imageURL])
: imageURL;
}
// Legacy v1/early-v2 front matter fields
// We may want to deprecate those in favor of using only frontMatter.authors
function getFrontMatterAuthorLegacy(
frontMatter: BlogPostFrontMatter,
): Author | undefined {
function getFrontMatterAuthorLegacy({
baseUrl,
frontMatter,
}: {
baseUrl: string;
frontMatter: BlogPostFrontMatter;
}): Author | undefined {
const name = frontMatter.author;
const title = frontMatter.author_title ?? frontMatter.authorTitle;
const url = frontMatter.author_url ?? frontMatter.authorURL;
const imageURL = frontMatter.author_image_url ?? frontMatter.authorImageURL;
const imageURL = normalizeImageUrl({
imageURL: frontMatter.author_image_url ?? frontMatter.authorImageURL,
baseUrl,
});
if (name || title || url || imageURL) {
return {
@ -148,14 +168,26 @@ ${Object.keys(authorsMap)
return frontMatterAuthors.map(toAuthor);
}
function fixAuthorImageBaseURL(
authors: Author[],
{baseUrl}: {baseUrl: string},
) {
return authors.map((author) => ({
...author,
imageURL: normalizeImageUrl({imageURL: author.imageURL, baseUrl}),
}));
}
export function getBlogPostAuthors(params: AuthorsParam): Author[] {
const authorLegacy = getFrontMatterAuthorLegacy(params.frontMatter);
const authorLegacy = getFrontMatterAuthorLegacy(params);
const authors = getFrontMatterAuthors(params);
const updatedAuthors = fixAuthorImageBaseURL(authors, params);
if (authorLegacy) {
// Technically, we could allow mixing legacy/authors front matter, but do we
// really want to?
if (authors.length > 0) {
if (updatedAuthors.length > 0) {
throw new Error(
`To declare blog post authors, use the 'authors' front matter in priority.
Don't mix 'authors' with other existing 'author_*' front matter. Choose one or the other, not both at the same time.`,
@ -164,5 +196,5 @@ Don't mix 'authors' with other existing 'author_*' front matter. Choose one or t
return [authorLegacy];
}
return authors;
return updatedAuthors;
}

View File

@ -11,7 +11,7 @@ import _ from 'lodash';
import logger from '@docusaurus/logger';
import readingTime from 'reading-time';
import {
parseMarkdownString,
parseMarkdownFile,
normalizeUrl,
aliasedSitePath,
getEditUrl,
@ -29,7 +29,7 @@ import {
} from '@docusaurus/utils';
import {validateBlogPostFrontMatter} from './frontMatter';
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
import type {LoadContext} from '@docusaurus/types';
import type {LoadContext, ParseFrontMatter} from '@docusaurus/types';
import type {
PluginOptions,
ReadingTimeFunction,
@ -180,10 +180,19 @@ function formatBlogPostDate(
}
}
async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) {
const markdownString = await fs.readFile(blogSourceAbsolute, 'utf-8');
async function parseBlogPostMarkdownFile({
filePath,
parseFrontMatter,
}: {
filePath: string;
parseFrontMatter: ParseFrontMatter;
}) {
const fileContent = await fs.readFile(filePath, 'utf-8');
try {
const result = parseMarkdownString(markdownString, {
const result = await parseMarkdownFile({
filePath,
fileContent,
parseFrontMatter,
removeContentTitle: true,
});
return {
@ -191,7 +200,7 @@ async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) {
frontMatter: validateBlogPostFrontMatter(result.frontMatter),
};
} catch (err) {
logger.error`Error while parsing blog post file path=${blogSourceAbsolute}.`;
logger.error`Error while parsing blog post file path=${filePath}.`;
throw err;
}
}
@ -207,7 +216,10 @@ async function processBlogSourceFile(
authorsMap?: AuthorsMap,
): Promise<BlogPost | undefined> {
const {
siteConfig: {baseUrl},
siteConfig: {
baseUrl,
markdown: {parseFrontMatter},
},
siteDir,
i18n,
} = context;
@ -228,7 +240,10 @@ async function processBlogSourceFile(
const blogSourceAbsolute = path.join(blogDirPath, blogSourceRelative);
const {frontMatter, content, contentTitle, excerpt} =
await parseBlogPostMarkdownFile(blogSourceAbsolute);
await parseBlogPostMarkdownFile({
filePath: blogSourceAbsolute,
parseFrontMatter,
});
const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir);
@ -319,7 +334,7 @@ async function processBlogSourceFile(
routeBasePath,
tagsRouteBasePath,
]);
const authors = getBlogPostAuthors({authorsMap, frontMatter});
const authors = getBlogPostAuthors({authorsMap, frontMatter, baseUrl});
return {
id: slug,

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-docs",
"version": "3.0.0",
"version": "3.1.0",
"description": "Docs plugin for Docusaurus.",
"main": "lib/index.js",
"sideEffects": false,
@ -35,13 +35,13 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/logger": "3.1.0",
"@docusaurus/mdx-loader": "3.1.0",
"@docusaurus/module-type-aliases": "3.1.0",
"@docusaurus/types": "3.1.0",
"@docusaurus/utils": "3.1.0",
"@docusaurus/utils-validation": "3.1.0",
"@types/react-router-config": "^5.0.7",
"combine-promises": "^1.1.0",
"fs-extra": "^11.1.1",

View File

@ -11,4 +11,16 @@ module.exports = {
url: 'https://your-docusaurus-site.example.com',
baseUrl: '/',
favicon: 'img/favicon.ico',
markdown: {
parseFrontMatter: async (params) => {
// Reuse the default parser
const result = await params.defaultParseFrontMatter(params);
if (result.frontMatter.last_update?.author) {
result.frontMatter.last_update.author =
result.frontMatter.last_update.author +
' (processed by parseFrontMatter)';
}
return result;
},
},
};

View File

@ -463,7 +463,7 @@ exports[`simple website content: data 1`] = `
"frontMatter": {
"title": "Custom Last Update",
"last_update": {
"author": "Custom Author",
"author": "Custom Author (processed by parseFrontMatter)",
"date": "1/1/2000"
}
}
@ -686,7 +686,7 @@ exports[`simple website content: data 1`] = `
"frontMatter": {
"title": "Last Update Author Only",
"last_update": {
"author": "Custom Author"
"author": "Custom Author (processed by parseFrontMatter)"
}
}
}",

View File

@ -567,14 +567,14 @@ describe('simple site', () => {
description: 'Custom last update',
frontMatter: {
last_update: {
author: 'Custom Author',
author: 'Custom Author (processed by parseFrontMatter)',
date: '1/1/2000',
},
title: 'Custom Last Update',
},
lastUpdatedAt: new Date('1/1/2000').getTime() / 1000,
formattedLastUpdatedAt: 'Jan 1, 2000',
lastUpdatedBy: 'Custom Author',
lastUpdatedBy: 'Custom Author (processed by parseFrontMatter)',
sidebarPosition: undefined,
tags: [],
unlisted: false,
@ -607,13 +607,13 @@ describe('simple site', () => {
description: 'Only custom author, so it will still use the date from Git',
frontMatter: {
last_update: {
author: 'Custom Author',
author: 'Custom Author (processed by parseFrontMatter)',
},
title: 'Last Update Author Only',
},
lastUpdatedAt: 1539502055,
formattedLastUpdatedAt: 'Oct 14, 2018',
lastUpdatedBy: 'Custom Author',
lastUpdatedBy: 'Custom Author (processed by parseFrontMatter)',
sidebarPosition: undefined,
tags: [],
unlisted: false,
@ -685,7 +685,7 @@ describe('simple site', () => {
description: 'Custom last update',
frontMatter: {
last_update: {
author: 'Custom Author',
author: 'Custom Author (processed by parseFrontMatter)',
date: '1/1/2000',
},
title: 'Custom Last Update',

View File

@ -15,7 +15,7 @@ import {
getFolderContainingFile,
getContentPathList,
normalizeUrl,
parseMarkdownString,
parseMarkdownFile,
posixPath,
Globby,
normalizeFrontMatterTags,
@ -140,13 +140,23 @@ async function doProcessDocMetadata({
env: DocEnv;
}): Promise<DocMetadataBase> {
const {source, content, contentPath, filePath} = docFile;
const {siteDir, i18n} = context;
const {
siteDir,
i18n,
siteConfig: {
markdown: {parseFrontMatter},
},
} = context;
const {
frontMatter: unsafeFrontMatter,
contentTitle,
excerpt,
} = parseMarkdownString(content);
} = await parseMarkdownFile({
filePath,
fileContent: content,
parseFrontMatter,
});
const frontMatter = validateDocFrontMatter(unsafeFrontMatter);
const {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-pages",
"version": "3.0.0",
"version": "3.1.0",
"description": "Pages plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-content-pages.d.ts",
@ -18,11 +18,11 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/mdx-loader": "3.1.0",
"@docusaurus/types": "3.1.0",
"@docusaurus/utils": "3.1.0",
"@docusaurus/utils-validation": "3.1.0",
"fs-extra": "^11.1.1",
"tslib": "^2.6.0",
"webpack": "^5.88.1"

View File

@ -11,4 +11,11 @@ module.exports = {
url: 'https://your-docusaurus-site.example.com',
baseUrl: '/',
favicon: 'img/favicon.ico',
markdown: {
parseFrontMatter: async (params) => {
const result = await params.defaultParseFrontMatter(params);
result.frontMatter.custom_frontMatter = 'added by parseFrontMatter';
return result;
},
},
};

View File

@ -14,7 +14,9 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = `
},
{
"description": "Markdown index page",
"frontMatter": {},
"frontMatter": {
"custom_frontMatter": "added by parseFrontMatter",
},
"permalink": "/hello/",
"source": "@site/src/pages/hello/index.md",
"title": "Index",
@ -24,6 +26,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = `
{
"description": "my MDX page",
"frontMatter": {
"custom_frontMatter": "added by parseFrontMatter",
"description": "my MDX page",
"title": "MDX page",
},
@ -40,7 +43,9 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = `
},
{
"description": "translated Markdown page",
"frontMatter": {},
"frontMatter": {
"custom_frontMatter": "added by parseFrontMatter",
},
"permalink": "/hello/translatedMd",
"source": "@site/src/pages/hello/translatedMd.md",
"title": undefined,
@ -69,7 +74,9 @@ exports[`docusaurus-plugin-content-pages loads simple pages with french translat
},
{
"description": "Markdown index page",
"frontMatter": {},
"frontMatter": {
"custom_frontMatter": "added by parseFrontMatter",
},
"permalink": "/fr/hello/",
"source": "@site/src/pages/hello/index.md",
"title": "Index",
@ -79,6 +86,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages with french translat
{
"description": "my MDX page",
"frontMatter": {
"custom_frontMatter": "added by parseFrontMatter",
"description": "my MDX page",
"title": "MDX page",
},
@ -95,7 +103,9 @@ exports[`docusaurus-plugin-content-pages loads simple pages with french translat
},
{
"description": "translated Markdown page (fr)",
"frontMatter": {},
"frontMatter": {
"custom_frontMatter": "added by parseFrontMatter",
},
"permalink": "/fr/hello/translatedMd",
"source": "@site/i18n/fr/docusaurus-plugin-content-pages/hello/translatedMd.md",
"title": undefined,

View File

@ -19,7 +19,7 @@ import {
createAbsoluteFilePathMatcher,
normalizeUrl,
DEFAULT_PLUGIN_ID,
parseMarkdownString,
parseMarkdownFile,
isUnlisted,
isDraft,
} from '@docusaurus/utils';
@ -113,7 +113,11 @@ export default function pluginContentPages(
frontMatter: unsafeFrontMatter,
contentTitle,
excerpt,
} = parseMarkdownString(content);
} = await parseMarkdownFile({
filePath: source,
fileContent: content,
parseFrontMatter: siteConfig.markdown.parseFrontMatter,
});
const frontMatter = validatePageFrontMatter(unsafeFrontMatter);
if (isDraft({frontMatter})) {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-debug",
"version": "3.0.0",
"version": "3.1.0",
"description": "Debug plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-debug.d.ts",
@ -20,11 +20,11 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@microlink/react-json-view": "^1.22.2",
"@docusaurus/core": "3.1.0",
"@docusaurus/types": "3.1.0",
"@docusaurus/utils": "3.1.0",
"fs-extra": "^11.1.1",
"react-json-view-lite": "^1.2.0",
"tslib": "^2.6.0"
},
"peerDependencies": {

View File

@ -6,53 +6,41 @@
*/
import React from 'react';
import BrowserOnly from '@docusaurus/BrowserOnly';
import {JsonView} from 'react-json-view-lite';
import type {Props} from '@theme/DebugJsonView';
import type {ReactJsonViewProps} from '@microlink/react-json-view';
import styles from './styles.module.css';
// Avoids "react-json-view" displaying "root"
const RootName = null;
// Seems ReactJson does not work with SSR
// https://github.com/mac-s-g/react-json-view/issues/121
function BrowserOnlyReactJson(props: ReactJsonViewProps) {
return (
<BrowserOnly>
{() => {
const {default: ReactJson} =
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
require('@microlink/react-json-view') as typeof import('@microlink/react-json-view');
return <ReactJson {...props} />;
}}
</BrowserOnly>
);
}
const paraisoStyles = {
container: styles.containerParaiso!,
basicChildStyle: styles.basicElementParaiso!,
label: styles.labelParaiso!,
nullValue: styles.nullValueParaiso!,
undefinedValue: styles.undefinedValueParaiso!,
stringValue: styles.stringValueParaiso!,
booleanValue: styles.booleanValueParaiso!,
numberValue: styles.numberValueParaiso!,
otherValue: styles.otherValueParaiso!,
punctuation: styles.punctuationParaiso!,
collapseIcon: styles.collapseIconParaiso!,
expandIcon: styles.expandIconParaiso!,
collapsedContent: styles.collapseContentParaiso!,
};
export default function DebugJsonView({
src,
collapseDepth,
}: Props): JSX.Element {
return (
<BrowserOnlyReactJson
src={src as object}
style={{
marginTop: '10px',
padding: '10px',
borderRadius: '4px',
backgroundColor: '#292a2b',
<JsonView
data={src as object}
shouldExpandNode={(idx, value) => {
if (Array.isArray(value)) {
return value.length < 5;
}
return collapseDepth !== undefined && idx < collapseDepth;
}}
name={RootName}
theme="paraiso"
shouldCollapse={(field) =>
// By default, we collapse the json for performance reasons
// See https://github.com/mac-s-g/react-json-view/issues/235
// Non-root elements that are larger than 50 fields are collapsed
field.name !== RootName && Object.keys(field.src).length > 50
}
collapsed={collapseDepth}
groupArraysAfterLength={5}
enableClipboard={false}
displayDataTypes={false}
style={paraisoStyles}
/>
);
}

View File

@ -0,0 +1,101 @@
/**
* 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.
*/
.containerParaiso {
font-family: monospace;
cursor: default;
background-color: rgb(41 42 43);
position: relative;
margin-top: 10px;
padding: 10px;
border-radius: 4px;
font-size: 13px;
}
.basicElementParaiso {
color: white;
padding: 3px 5px 3px 20px;
border-left: 1px solid rgb(79 66 76);
}
.labelParaiso {
color: rgb(231 233 219);
letter-spacing: 0.5px;
margin-right: 3px;
}
.nullValueParaiso {
display: inline-block;
color: rgb(254 196 24);
font-size: 11px;
font-weight: bold;
background-color: rgb(79 66 76);
padding: 1px 2px;
border-radius: 3px;
text-transform: uppercase;
}
.undefinedValueParaiso {
color: rgb(141 134 135);
}
.stringValueParaiso {
color: rgb(249 155 21);
}
.booleanValueParaiso {
color: rgb(129 91 164);
}
.numberValueParaiso {
color: rgb(233 107 168);
}
.otherValueParaiso {
color: white;
}
.punctuationParaiso {
color: white;
}
.expandIconParaiso {
display: inline-block;
color: rgb(129 91 164);
font-size: 22px;
vertical-align: baseline;
margin-right: 3px;
line-height: 10px;
}
.collapseIconParaiso::after {
content: '\25BE';
}
.collapseIconParaiso {
display: inline-block;
color: rgb(6 182 239);
font-size: 22px;
vertical-align: baseline;
margin-right: 3px;
line-height: 10px;
}
.expandIconParaiso::after {
content: '\25B8';
}
.collapseContentParaiso {
color: rgb(249 155 21);
font-size: 18px;
line-height: 10px;
cursor: pointer;
}
.collapseContentParaiso::after {
content: '...';
}

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-google-analytics",
"version": "3.0.0",
"version": "3.1.0",
"description": "Global analytics (analytics.js) plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,9 +18,9 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/types": "3.1.0",
"@docusaurus/utils-validation": "3.1.0",
"tslib": "^2.6.0"
},
"peerDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-google-gtag",
"version": "3.0.0",
"version": "3.1.0",
"description": "Global Site Tag (gtag.js) plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,9 +18,9 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/types": "3.1.0",
"@docusaurus/utils-validation": "3.1.0",
"@types/gtag.js": "^0.0.12",
"tslib": "^2.6.0"
},

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-ideal-image",
"version": "3.0.0",
"version": "3.1.0",
"description": "Docusaurus Plugin to generate an almost ideal image (responsive, lazy-loading, and low quality placeholder).",
"main": "lib/index.js",
"types": "src/plugin-ideal-image.d.ts",
@ -20,12 +20,12 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/lqip-loader": "3.0.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/lqip-loader": "3.1.0",
"@docusaurus/responsive-loader": "^1.7.0",
"@docusaurus/theme-translations": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/theme-translations": "3.1.0",
"@docusaurus/types": "3.1.0",
"@docusaurus/utils-validation": "3.1.0",
"@slorber/react-ideal-image": "^0.0.12",
"react-waypoint": "^10.3.0",
"sharp": "^0.32.3",
@ -33,7 +33,7 @@
"webpack": "^5.88.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/module-type-aliases": "3.1.0",
"fs-extra": "^11.1.0"
},
"peerDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-pwa",
"version": "3.0.0",
"version": "3.1.0",
"description": "Docusaurus Plugin to add PWA support.",
"main": "lib/index.js",
"types": "src/plugin-pwa.d.ts",
@ -20,28 +20,28 @@
},
"license": "MIT",
"dependencies": {
"@babel/core": "^7.22.9",
"@babel/preset-env": "^7.22.9",
"@docusaurus/core": "3.0.0",
"@docusaurus/theme-common": "3.0.0",
"@docusaurus/theme-translations": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@babel/core": "^7.23.3",
"@babel/preset-env": "^7.23.3",
"@docusaurus/core": "3.1.0",
"@docusaurus/theme-common": "3.1.0",
"@docusaurus/theme-translations": "3.1.0",
"@docusaurus/types": "3.1.0",
"@docusaurus/utils": "3.1.0",
"@docusaurus/utils-validation": "3.1.0",
"babel-loader": "^9.1.3",
"clsx": "^1.2.1",
"clsx": "^2.0.0",
"core-js": "^3.31.1",
"terser-webpack-plugin": "^5.3.9",
"tslib": "^2.6.0",
"webpack": "^5.88.1",
"webpack-merge": "^5.9.0",
"webpackbar": "^5.0.2",
"workbox-build": "^6.6.1",
"workbox-precaching": "^6.6.1",
"workbox-window": "^6.6.1"
"workbox-build": "^7.0.0",
"workbox-precaching": "^7.0.0",
"workbox-window": "^7.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/module-type-aliases": "3.1.0",
"fs-extra": "^11.1.0"
},
"peerDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-sitemap",
"version": "3.0.0",
"version": "3.1.0",
"description": "Simple sitemap generation plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,12 +18,12 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/logger": "3.1.0",
"@docusaurus/types": "3.1.0",
"@docusaurus/utils": "3.1.0",
"@docusaurus/utils-common": "3.1.0",
"@docusaurus/utils-validation": "3.1.0",
"fs-extra": "^11.1.1",
"sitemap": "^7.1.1",
"tslib": "^2.6.0"

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/remark-plugin-npm2yarn",
"version": "3.0.0",
"version": "3.1.0",
"description": "Remark plugin for converting npm commands to Yarn commands as tabs.",
"main": "lib/index.js",
"publishConfig": {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-classic",
"version": "3.0.0",
"version": "3.1.0",
"description": "Classic theme for Docusaurus",
"main": "lib/index.js",
"types": "src/theme-classic.d.ts",
@ -20,26 +20,26 @@
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
},
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/plugin-content-blog": "3.0.0",
"@docusaurus/plugin-content-docs": "3.0.0",
"@docusaurus/plugin-content-pages": "3.0.0",
"@docusaurus/theme-common": "3.0.0",
"@docusaurus/theme-translations": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/mdx-loader": "3.1.0",
"@docusaurus/module-type-aliases": "3.1.0",
"@docusaurus/plugin-content-blog": "3.1.0",
"@docusaurus/plugin-content-docs": "3.1.0",
"@docusaurus/plugin-content-pages": "3.1.0",
"@docusaurus/theme-common": "3.1.0",
"@docusaurus/theme-translations": "3.1.0",
"@docusaurus/types": "3.1.0",
"@docusaurus/utils": "3.1.0",
"@docusaurus/utils-common": "3.1.0",
"@docusaurus/utils-validation": "3.1.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^1.2.1",
"clsx": "^2.0.0",
"copy-text-to-clipboard": "^3.2.0",
"infima": "0.2.0-alpha.43",
"lodash": "^4.17.21",
"nprogress": "^0.2.0",
"postcss": "^8.4.26",
"prism-react-renderer": "^2.1.0",
"prism-react-renderer": "^2.3.0",
"prismjs": "^1.29.0",
"react-router-dom": "^5.3.4",
"rtlcss": "^4.1.0",

View File

@ -363,6 +363,14 @@ declare module '@theme/CodeBlock' {
export default function CodeBlock(props: Props): JSX.Element;
}
declare module '@theme/CodeInline' {
import type {ComponentProps} from 'react';
export interface Props extends ComponentProps<'code'> {}
export default function CodeInline(props: Props): JSX.Element;
}
declare module '@theme/CodeBlock/CopyButton' {
export interface Props {
readonly code: string;

View File

@ -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 from 'react';
import type {Props} from '@theme/CodeInline';
// Simple component used to render inline code blocks
// its purpose is to be swizzled and customized
// MDX 1 used to have a inlineCode comp, see https://mdxjs.com/migrating/v2/
export default function CodeInline(props: Props): JSX.Element {
return <code {...props} />;
}

View File

@ -10,11 +10,13 @@ import clsx from 'clsx';
import {translate} from '@docusaurus/Translate';
import {useThemeConfig} from '@docusaurus/theme-common';
import Link from '@docusaurus/Link';
import useBrokenLinks from '@docusaurus/useBrokenLinks';
import type {Props} from '@theme/Heading';
import styles from './styles.module.css';
export default function Heading({as: As, id, ...props}: Props): JSX.Element {
const brokenLinks = useBrokenLinks();
const {
navbar: {hideOnScroll},
} = useThemeConfig();
@ -23,6 +25,8 @@ export default function Heading({as: As, id, ...props}: Props): JSX.Element {
return <As {...props} id={undefined} />;
}
brokenLinks.collectAnchor(id);
const anchorTitle = translate(
{
id: 'theme.common.headingLinkTitle',

View File

@ -8,15 +8,23 @@
import type {ComponentProps} from 'react';
import React from 'react';
import CodeBlock from '@theme/CodeBlock';
import CodeInline from '@theme/CodeInline';
import type {Props} from '@theme/MDXComponents/Code';
export default function MDXCode(props: Props): JSX.Element {
const shouldBeInline = React.Children.toArray(props.children).every(
(el) => typeof el === 'string' && !el.includes('\n'),
function shouldBeInline(props: Props) {
return (
// empty code blocks have no props.children,
// see https://github.com/facebook/docusaurus/pull/9704
typeof props.children !== 'undefined' &&
React.Children.toArray(props.children).every(
(el) => typeof el === 'string' && !el.includes('\n'),
)
);
}
return shouldBeInline ? (
<code {...props} />
export default function MDXCode(props: Props): JSX.Element {
return shouldBeInline(props) ? (
<CodeInline {...props} />
) : (
<CodeBlock {...(props as ComponentProps<typeof CodeBlock>)} />
);

View File

@ -9,8 +9,15 @@
Workaround to avoid rendering empty search container
See https://github.com/facebook/docusaurus/pull/9385
*/
.navbarSearchContainer:not(:has(> *)) {
display: none;
/*
TODO temporary @supports check, remove before 2025
only needed for Firefox < 121
see https://github.com/facebook/docusaurus/issues/9527#issuecomment-1805272379
*/
@supports selector(:has(*)) {
.navbarSearchContainer:not(:has(> *)) {
display: none;
}
}
@media (max-width: 996px) {

View File

@ -19,6 +19,7 @@ import type {
DesktopOrMobileNavBarItemProps,
Props,
} from '@theme/NavbarItem/DropdownNavbarItem';
import styles from './styles.module.css';
function isItemActive(
item: LinkLikeNavbarItemProps,
@ -143,6 +144,7 @@ function DropdownNavbarItemMobile({
<NavbarNavLink
role="button"
className={clsx(
styles.dropdownNavbarItemMobile,
'menu__link menu__link--sublist menu__link--sublist-caret',
className,
)}

View File

@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
.dropdownNavbarItemMobile {
cursor: pointer;
}

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-common",
"version": "3.0.0",
"version": "3.1.0",
"description": "Common code for Docusaurus themes.",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
@ -30,25 +30,25 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/plugin-content-blog": "3.0.0",
"@docusaurus/plugin-content-docs": "3.0.0",
"@docusaurus/plugin-content-pages": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/mdx-loader": "3.1.0",
"@docusaurus/module-type-aliases": "3.1.0",
"@docusaurus/plugin-content-blog": "3.1.0",
"@docusaurus/plugin-content-docs": "3.1.0",
"@docusaurus/plugin-content-pages": "3.1.0",
"@docusaurus/utils": "3.1.0",
"@docusaurus/utils-common": "3.1.0",
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router-config": "*",
"clsx": "^1.2.1",
"clsx": "^2.0.0",
"parse-numeric-range": "^1.3.0",
"prism-react-renderer": "^2.1.0",
"prism-react-renderer": "^2.3.0",
"tslib": "^2.6.0",
"utility-types": "^3.10.0"
},
"devDependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/types": "3.1.0",
"fs-extra": "^11.1.1",
"lodash": "^4.17.21"
},

View File

@ -17,15 +17,20 @@ const windowSizes = {
type WindowSize = keyof typeof windowSizes;
const DesktopThresholdWidth = 996;
// Note: this value is also hardcoded in Infima
// Both JS and CSS must have the same value
// Updating this JS value alone is not enough
// See https://github.com/facebook/docusaurus/issues/9603
const DesktopBreakpoint = 996;
function getWindowSize() {
function getWindowSize(desktopBreakpoint: number): WindowSize {
if (!ExecutionEnvironment.canUseDOM) {
throw new Error(
'getWindowSize() should only be called after React hydration',
);
}
return window.innerWidth > DesktopThresholdWidth
return window.innerWidth > desktopBreakpoint
? windowSizes.desktop
: windowSizes.mobile;
}
@ -40,7 +45,11 @@ function getWindowSize() {
* with mediaquery). We don't return `undefined` on purpose, to make it more
* explicit.
*/
export function useWindowSize(): WindowSize {
export function useWindowSize({
desktopBreakpoint = DesktopBreakpoint,
}: {
desktopBreakpoint?: number;
} = {}): WindowSize {
const [windowSize, setWindowSize] = useState<WindowSize>(
() =>
// super important to return a constant value to avoid hydration mismatch
@ -50,7 +59,7 @@ export function useWindowSize(): WindowSize {
useEffect(() => {
function updateWindowSize() {
setWindowSize(getWindowSize());
setWindowSize(getWindowSize(desktopBreakpoint));
}
updateWindowSize();
@ -60,7 +69,7 @@ export function useWindowSize(): WindowSize {
return () => {
window.removeEventListener('resize', updateWindowSize);
};
}, []);
}, [desktopBreakpoint]);
return windowSize;
}

View File

@ -13,18 +13,31 @@ const codeBlockTitleRegex = /title=(?<quote>["'])(?<title>.*?)\1/;
const metastringLinesRangeRegex = /\{(?<range>[\d,-]+)\}/;
// Supported types of highlight comments
const commentPatterns = {
const popularCommentPatterns = {
js: {start: '\\/\\/', end: ''},
jsBlock: {start: '\\/\\*', end: '\\*\\/'},
jsx: {start: '\\{\\s*\\/\\*', end: '\\*\\/\\s*\\}'},
bash: {start: '#', end: ''},
html: {start: '<!--', end: '-->'},
} as const;
const commentPatterns = {
...popularCommentPatterns, // shallow copy is sufficient
// minor comment styles
lua: {start: '--', end: ''},
wasm: {start: '\\;\\;', end: ''},
tex: {start: '%', end: ''},
};
vb: {start: "[']", end: ''},
rem: {start: '[Rr][Ee][Mm]\\b', end: ''},
f90: {start: '!', end: ''}, // Free format only
ml: {start: '\\(\\*', end: '\\*\\)'},
cobol: {start: '\\*>', end: ''}, // Free format only
} as const;
type CommentType = keyof typeof commentPatterns;
const popularCommentTypes = Object.keys(
popularCommentPatterns,
) as CommentType[];
export type MagicCommentConfig = {
className: string;
@ -99,15 +112,34 @@ function getAllMagicCommentDirectiveStyles(
case 'wasm':
return getCommentPattern(['wasm'], magicCommentDirectives);
case 'vb':
case 'vbnet':
case 'vba':
case 'visual-basic':
return getCommentPattern(['vb', 'rem'], magicCommentDirectives);
case 'batch':
return getCommentPattern(['rem'], magicCommentDirectives);
case 'basic': // https://github.com/PrismJS/prism/blob/master/components/prism-basic.js#L3
return getCommentPattern(['rem', 'f90'], magicCommentDirectives);
case 'fsharp':
return getCommentPattern(['js', 'ml'], magicCommentDirectives);
case 'ocaml':
case 'sml':
return getCommentPattern(['ml'], magicCommentDirectives);
case 'fortran':
return getCommentPattern(['f90'], magicCommentDirectives);
case 'cobol':
return getCommentPattern(['cobol'], magicCommentDirectives);
default:
// All comment types except lua, wasm and matlab
return getCommentPattern(
Object.keys(commentPatterns).filter(
(pattern) =>
!['lua', 'wasm', 'tex', 'latex', 'matlab'].includes(pattern),
) as CommentType[],
magicCommentDirectives,
);
// All popular comment types
return getCommentPattern(popularCommentTypes, magicCommentDirectives);
}
}

View File

@ -177,7 +177,7 @@ export function isVisibleSidebarItem(
// An unlisted item remains visible if it is active
return !item.unlisted || isActiveSidebarItem(item, activePath);
default:
return false;
return true;
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-live-codeblock",
"version": "3.0.0",
"version": "3.1.0",
"description": "Docusaurus live code block component.",
"main": "lib/index.js",
"types": "src/theme-live-codeblock.d.ts",
@ -23,18 +23,18 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/theme-common": "3.0.0",
"@docusaurus/theme-translations": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/theme-common": "3.1.0",
"@docusaurus/theme-translations": "3.1.0",
"@docusaurus/utils-validation": "3.1.0",
"@philpl/buble": "^0.19.7",
"clsx": "^1.2.1",
"clsx": "^2.0.0",
"fs-extra": "^11.1.1",
"react-live": "^4.1.5",
"tslib": "^2.6.0"
},
"devDependencies": {
"@docusaurus/types": "3.0.0",
"@docusaurus/types": "3.1.0",
"@types/buble": "^0.20.1"
},
"peerDependencies": {

View File

@ -24,7 +24,8 @@ declare module '@theme/Playground' {
type LiveProviderProps = React.ComponentProps<typeof LiveProvider>;
export interface Props extends CodeBlockProps, LiveProviderProps {
children: string;
// Allow empty live playgrounds
children?: string;
}
export default function Playground(props: LiveProviderProps): JSX.Element;
}

View File

@ -98,6 +98,10 @@ function EditorWithHeader() {
);
}
// 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,
@ -116,9 +120,9 @@ export default function Playground({
return (
<div className={styles.playgroundContainer}>
<LiveProvider
code={children.replace(/\n$/, '')}
code={children?.replace(/\n$/, '')}
noInline={noInline}
transformCode={transformCode ?? ((code) => `${code};`)}
transformCode={transformCode ?? DEFAULT_TRANSFORM_CODE}
theme={prismTheme}
{...props}>
{playgroundPosition === 'top' ? (

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-search-algolia",
"version": "3.0.0",
"version": "3.1.0",
"description": "Algolia search component for Docusaurus.",
"main": "lib/index.js",
"sideEffects": [
@ -34,16 +34,16 @@
},
"dependencies": {
"@docsearch/react": "^3.5.2",
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/plugin-content-docs": "3.0.0",
"@docusaurus/theme-common": "3.0.0",
"@docusaurus/theme-translations": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/logger": "3.1.0",
"@docusaurus/plugin-content-docs": "3.1.0",
"@docusaurus/theme-common": "3.1.0",
"@docusaurus/theme-translations": "3.1.0",
"@docusaurus/utils": "3.1.0",
"@docusaurus/utils-validation": "3.1.0",
"algoliasearch": "^4.18.0",
"algoliasearch-helper": "^3.13.3",
"clsx": "^1.2.1",
"clsx": "^2.0.0",
"eta": "^2.2.0",
"fs-extra": "^11.1.1",
"lodash": "^4.17.21",
@ -51,7 +51,7 @@
"utility-types": "^3.10.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.0"
"@docusaurus/module-type-aliases": "3.1.0"
},
"peerDependencies": {
"react": "^18.0.0",

View File

@ -6,6 +6,7 @@
*/
import React, {useCallback, useMemo, useRef, useState} from 'react';
import {createPortal} from 'react-dom';
import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
import Head from '@docusaurus/Head';
import Link from '@docusaurus/Link';
@ -20,7 +21,6 @@ import {
} from '@docusaurus/theme-search-algolia/client';
import Translate from '@docusaurus/Translate';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import {createPortal} from 'react-dom';
import translations from '@theme/SearchTranslations';
import type {AutocompleteState} from '@algolia/autocomplete-core';

View File

@ -4,22 +4,22 @@
"theme.CodeBlock.copied": "Copiado",
"theme.CodeBlock.copy": "Copiar",
"theme.CodeBlock.copyButtonAriaLabel": "Copiar código para a área de transferência",
"theme.CodeBlock.wordWrapToggle": "Toggle word wrap",
"theme.DocSidebarItem.collapseCategoryAriaLabel": "Collapse sidebar category '{label}'",
"theme.DocSidebarItem.expandCategoryAriaLabel": "Expand sidebar category '{label}'",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.CodeBlock.wordWrapToggle": "Alternar quebra de linha",
"theme.DocSidebarItem.collapseCategoryAriaLabel": "Fechar a categoria lateral '{label}'",
"theme.DocSidebarItem.expandCategoryAriaLabel": "Expandir a categoria lateral '{label}'",
"theme.ErrorPageContent.title": "Esta página deu erro.",
"theme.ErrorPageContent.tryAgain": "Tente novamente",
"theme.NavBar.navAriaLabel": "Main",
"theme.NotFound.p1": "Não foi possível encontrar o que você está procurando.",
"theme.NotFound.p2": "Entre em contato com o proprietário do site que lhe trouxe para cá e lhe informe que o link está quebrado.",
"theme.NotFound.title": "Página não encontrada",
"theme.TOCCollapsible.toggleButtonLabel": "Nessa página",
"theme.admonition.caution": "caution",
"theme.admonition.danger": "danger",
"theme.admonition.caution": "cuidado",
"theme.admonition.danger": "perigo",
"theme.admonition.info": "info",
"theme.admonition.note": "note",
"theme.admonition.tip": "tip",
"theme.admonition.warning": "warning",
"theme.admonition.note": "nota",
"theme.admonition.tip": "dica",
"theme.admonition.warning": "atenção",
"theme.blog.archive.description": "Arquivo",
"theme.blog.archive.title": "Arquivo",
"theme.blog.paginator.navAriaLabel": "Navegação da página de listagem do blog",
@ -30,32 +30,32 @@
"theme.blog.post.paginator.olderPost": "Postagem mais antiga",
"theme.blog.post.plurals": "Uma postagem|{count} postagens",
"theme.blog.post.readMore": "Leia Mais",
"theme.blog.post.readMoreLabel": "Read more about {title}",
"theme.blog.post.readMoreLabel": "Ler mais sobre {title}",
"theme.blog.post.readingTime.plurals": "Leitura de um minuto|Leitura de {readingTime} minutos",
"theme.blog.sidebar.navAriaLabel": "Blog recent posts navigation",
"theme.blog.tagTitle": "{nPosts} marcadas com \"{tagName}\"",
"theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})",
"theme.colorToggle.ariaLabel.mode.dark": "dark mode",
"theme.colorToggle.ariaLabel.mode.light": "light mode",
"theme.colorToggle.ariaLabel": "Alterar entre os modos claro e escuro (modo {mode} ativado)",
"theme.colorToggle.ariaLabel.mode.dark": "modo escuro",
"theme.colorToggle.ariaLabel.mode.light": "modo claro",
"theme.common.editThisPage": "Editar essa página",
"theme.common.headingLinkTitle": "Link direto para {heading}",
"theme.common.skipToMainContent": "Pular para o conteúdo principal",
"theme.docs.DocCard.categoryDescription": "{count} items",
"theme.docs.breadcrumbs.home": "Home page",
"theme.docs.breadcrumbs.home": "Página Inicial",
"theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs",
"theme.docs.paginator.navAriaLabel": "Páginas de documentação",
"theme.docs.paginator.next": "Próxima",
"theme.docs.paginator.previous": "Anterior",
"theme.docs.sidebar.closeSidebarButtonAriaLabel": "Close navigation bar",
"theme.docs.sidebar.closeSidebarButtonAriaLabel": "Fechar barra de navegação",
"theme.docs.sidebar.collapseButtonAriaLabel": "Fechar painel lateral",
"theme.docs.sidebar.collapseButtonTitle": "Fechar painel lateral",
"theme.docs.sidebar.expandButtonAriaLabel": "Expandir painel lateral",
"theme.docs.sidebar.expandButtonTitle": "Expandir painel lateral",
"theme.docs.sidebar.navAriaLabel": "Docs sidebar",
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": "Toggle navigation bar",
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": "Alternar a barra de navegação",
"theme.docs.tagDocListPageTitle": "{nDocsTagged} com \"{tagName}\"",
"theme.docs.tagDocListPageTitle.nDocsTagged": "Um documento selecionado|{count} documentos selecionados",
"theme.docs.versionBadge.label": "Version: {versionLabel}",
"theme.docs.versionBadge.label": "Versão: {versionLabel}",
"theme.docs.versions.latestVersionLinkLabel": "última versão",
"theme.docs.versions.latestVersionSuggestionLabel": "Para a documentação atualizada, veja: {latestVersionLink} ({versionLabel}).",
"theme.docs.versions.unmaintainedVersionLabel": "Esta é a documentação para {siteTitle} {versionLabel}, que não é mais mantida ativamente.",
@ -63,12 +63,12 @@
"theme.lastUpdated.atDate": " em {date}",
"theme.lastUpdated.byUser": " por {user}",
"theme.lastUpdated.lastUpdatedAtBy": "Última atualização {atDate}{byUser}",
"theme.navbar.mobileLanguageDropdown.label": "Languages",
"theme.navbar.mobileLanguageDropdown.label": "Linguagens",
"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Voltar para o menu principal",
"theme.navbar.mobileVersionsDropdown.label": "Versions",
"theme.navbar.mobileVersionsDropdown.label": "Versões",
"theme.tags.tagsListLabel": "Marcadores:",
"theme.tags.tagsPageLink": "Ver todas os Marcadores",
"theme.tags.tagsPageTitle": "Marcadores",
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
"theme.unlistedContent.title": "Unlisted page"
"theme.unlistedContent.title": "Página não listada"
}

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/tsconfig",
"version": "3.0.0",
"version": "3.1.0",
"description": "Docusaurus base TypeScript configuration.",
"main": "tsconfig.json",
"publishConfig": {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/types",
"version": "3.0.0",
"version": "3.1.0",
"description": "Common types for Docusaurus packages.",
"types": "./src/index.d.ts",
"publishConfig": {
@ -13,6 +13,7 @@
},
"license": "MIT",
"dependencies": {
"@mdx-js/mdx": "^3.0.0",
"@types/history": "^4.7.11",
"@types/react": "*",
"commander": "^5.1.0",

View File

@ -10,6 +10,10 @@ import type {Required as RequireKeys, DeepPartial} from 'utility-types';
import type {I18nConfig} from './i18n';
import type {PluginConfig, PresetConfig, HtmlTagObject} from './plugin';
import type {ProcessorOptions} from '@mdx-js/mdx';
export type RemarkRehypeOptions = ProcessorOptions['remarkRehypeOptions'];
export type ReportingSeverity = 'ignore' | 'log' | 'warn' | 'throw';
export type ThemeConfig = {
@ -27,6 +31,20 @@ export type MDX1CompatOptions = {
headingIds: boolean;
};
export type ParseFrontMatterParams = {filePath: string; fileContent: string};
export type ParseFrontMatterResult = {
frontMatter: {[key: string]: unknown};
content: string;
};
export type DefaultParseFrontMatter = (
params: ParseFrontMatterParams,
) => Promise<ParseFrontMatterResult>;
export type ParseFrontMatter = (
params: ParseFrontMatterParams & {
defaultParseFrontMatter: DefaultParseFrontMatter;
},
) => Promise<ParseFrontMatterResult>;
export type MarkdownConfig = {
/**
* The Markdown format to use by default.
@ -44,6 +62,14 @@ export type MarkdownConfig = {
*/
format: 'mdx' | 'md' | 'detect';
/**
* A function callback that lets users parse the front matter themselves.
* Gives the opportunity to read it from a different source, or process it.
*
* @see https://github.com/facebook/docusaurus/issues/5568
*/
parseFrontMatter: ParseFrontMatter;
/**
* Allow mermaid language code blocks to be rendered into Mermaid diagrams:
*
@ -69,6 +95,12 @@ export type MarkdownConfig = {
* See also https://github.com/facebook/docusaurus/issues/4029
*/
mdx1Compat: MDX1CompatOptions;
/**
* Ability to provide custom remark-rehype options
* See also https://github.com/remarkjs/remark-rehype#options
*/
remarkRehypeOptions: RemarkRehypeOptions;
};
/**
@ -143,6 +175,13 @@ export type DocusaurusConfig = {
* @default "throw"
*/
onBrokenLinks: ReportingSeverity;
/**
* The behavior of Docusaurus when it detects any broken link.
*
* @see https://docusaurus.io/docs/api/docusaurus-config#onBrokenAnchors
* @default "warn"
*/
onBrokenAnchors: ReportingSeverity;
/**
* The behavior of Docusaurus when it detects any broken markdown link.
*

View File

@ -9,6 +9,8 @@ export {
ReportingSeverity,
ThemeConfig,
MarkdownConfig,
DefaultParseFrontMatter,
ParseFrontMatter,
DocusaurusConfig,
Config,
} from './config';

View File

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

View File

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

View File

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

View File

@ -176,6 +176,7 @@ exports[`replaceMarkdownLinks replaces links with same title as URL 1`] = `
"brokenMarkdownLinks": [],
"newContent": "
[foo.md](/docs/foo)
[./foo.md](</docs/foo>)
[./foo.md](/docs/foo)
[foo.md](/docs/foo)
[./foo.md](/docs/foo)

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`parseMarkdownString deletes only first heading 1`] = `
exports[`parseMarkdownFile deletes only first heading 1`] = `
{
"content": "# Markdown Title
@ -15,7 +15,7 @@ test test test # test bar
}
`;
exports[`parseMarkdownString deletes only first heading 2 1`] = `
exports[`parseMarkdownFile deletes only first heading 2 1`] = `
{
"content": "# test
@ -30,7 +30,7 @@ test3",
}
`;
exports[`parseMarkdownString does not warn for duplicate title if markdown title is not at the top 1`] = `
exports[`parseMarkdownFile does not warn for duplicate title if markdown title is not at the top 1`] = `
{
"content": "foo
@ -43,7 +43,7 @@ exports[`parseMarkdownString does not warn for duplicate title if markdown title
}
`;
exports[`parseMarkdownString handles code blocks 1`] = `
exports[`parseMarkdownFile handles code blocks 1`] = `
{
"content": "\`\`\`js
code
@ -56,7 +56,7 @@ Content",
}
`;
exports[`parseMarkdownString handles code blocks 2`] = `
exports[`parseMarkdownFile handles code blocks 2`] = `
{
"content": "\`\`\`\`js
Foo
@ -73,7 +73,7 @@ Content",
}
`;
exports[`parseMarkdownString handles code blocks 3`] = `
exports[`parseMarkdownFile handles code blocks 3`] = `
{
"content": "\`\`\`\`js
Foo
@ -88,7 +88,7 @@ Content",
}
`;
exports[`parseMarkdownString ignores markdown title if its not a first text 1`] = `
exports[`parseMarkdownFile ignores markdown title if its not a first text 1`] = `
{
"content": "foo
# test",
@ -98,7 +98,21 @@ exports[`parseMarkdownString ignores markdown title if its not a first text 1`]
}
`;
exports[`parseMarkdownString parse markdown with front matter 1`] = `
exports[`parseMarkdownFile parse markdown with custom front matter parser 1`] = `
{
"content": "Some text",
"contentTitle": undefined,
"excerpt": "Some text",
"frontMatter": {
"age": 84,
"extra": "value",
"great": true,
"title": "Frontmatter title",
},
}
`;
exports[`parseMarkdownFile parse markdown with front matter 1`] = `
{
"content": "Some text",
"contentTitle": undefined,
@ -109,7 +123,7 @@ exports[`parseMarkdownString parse markdown with front matter 1`] = `
}
`;
exports[`parseMarkdownString parses first heading as contentTitle 1`] = `
exports[`parseMarkdownFile parses first heading as contentTitle 1`] = `
{
"content": "# Markdown Title
@ -120,7 +134,7 @@ Some text",
}
`;
exports[`parseMarkdownString parses front-matter and ignore h2 1`] = `
exports[`parseMarkdownFile parses front-matter and ignore h2 1`] = `
{
"content": "## test",
"contentTitle": undefined,
@ -131,7 +145,7 @@ exports[`parseMarkdownString parses front-matter and ignore h2 1`] = `
}
`;
exports[`parseMarkdownString parses title only 1`] = `
exports[`parseMarkdownFile parses title only 1`] = `
{
"content": "# test",
"contentTitle": "test",
@ -140,7 +154,7 @@ exports[`parseMarkdownString parses title only 1`] = `
}
`;
exports[`parseMarkdownString parses title only alternate 1`] = `
exports[`parseMarkdownFile parses title only alternate 1`] = `
{
"content": "test
===",
@ -150,7 +164,7 @@ exports[`parseMarkdownString parses title only alternate 1`] = `
}
`;
exports[`parseMarkdownString reads front matter only 1`] = `
exports[`parseMarkdownFile reads front matter only 1`] = `
{
"content": "",
"contentTitle": undefined,
@ -161,7 +175,7 @@ exports[`parseMarkdownString reads front matter only 1`] = `
}
`;
exports[`parseMarkdownString warns about duplicate titles (front matter + markdown alternate) 1`] = `
exports[`parseMarkdownFile warns about duplicate titles (front matter + markdown alternate) 1`] = `
{
"content": "Markdown Title alternate
================
@ -175,7 +189,7 @@ Some text",
}
`;
exports[`parseMarkdownString warns about duplicate titles (front matter + markdown) 1`] = `
exports[`parseMarkdownFile warns about duplicate titles (front matter + markdown) 1`] = `
{
"content": "# Markdown Title
@ -188,7 +202,7 @@ Some text",
}
`;
exports[`parseMarkdownString warns about duplicate titles 1`] = `
exports[`parseMarkdownFile warns about duplicate titles 1`] = `
{
"content": "# test",
"contentTitle": "test",

View File

@ -231,6 +231,7 @@ The following operations are defined for [URI]s:
},
fileString: `
[foo.md](foo.md)
[./foo.md](<./foo.md>)
[./foo.md](./foo.md)
[foo.md](./foo.md)
[./foo.md](foo.md)

View File

@ -9,12 +9,14 @@ import dedent from 'dedent';
import {
createExcerpt,
parseMarkdownContentTitle,
parseMarkdownString,
parseMarkdownHeadingId,
writeMarkdownHeadingId,
escapeMarkdownHeadingIds,
unwrapMdxCodeBlocks,
admonitionTitleToDirectiveLabel,
parseMarkdownFile,
DEFAULT_PARSE_FRONT_MATTER,
parseFileContentFrontMatter,
} from '../markdownUtils';
describe('createExcerpt', () => {
@ -623,32 +625,110 @@ Lorem Ipsum
});
});
describe('parseMarkdownString', () => {
it('parse markdown with front matter', () => {
expect(
parseMarkdownString(dedent`
describe('parseFileContentFrontMatter', () => {
function test(fileContent: string) {
return parseFileContentFrontMatter(fileContent);
}
it('can parse front matter', () => {
const input = dedent`
---
title: Frontmatter title
author:
age: 42
birth: 2000-07-23
---
Some text
`;
const expectedResult = {
content: 'Some text',
frontMatter: {
title: 'Frontmatter title',
author: {age: 42, birth: new Date('2000-07-23')},
},
};
const result = test(input) as typeof expectedResult;
expect(result).toEqual(expectedResult);
expect(result.frontMatter.author.birth).toBeInstanceOf(Date);
// A regression test, ensure we don't return gray-matter cached objects
result.frontMatter.title = 'modified';
// @ts-expect-error: ok
result.frontMatter.author.age = 53;
expect(test(input)).toEqual(expectedResult);
});
});
describe('parseMarkdownFile', () => {
async function test(
fileContent: string,
options?: Partial<Parameters<typeof parseMarkdownFile>>[0],
) {
return parseMarkdownFile({
fileContent,
filePath: 'some-file-path.mdx',
parseFrontMatter: DEFAULT_PARSE_FRONT_MATTER,
...options,
});
}
it('parse markdown with front matter', async () => {
await expect(
test(dedent`
---
title: Frontmatter title
---
Some text
`),
).toMatchSnapshot();
).resolves.toMatchSnapshot();
});
it('parses first heading as contentTitle', () => {
expect(
parseMarkdownString(dedent`
it('parse markdown with custom front matter parser', async () => {
await expect(
test(
dedent`
---
title: Frontmatter title
age: 42
---
Some text
`,
{
parseFrontMatter: async (params) => {
const result = await params.defaultParseFrontMatter(params);
return {
...result,
frontMatter: {
...result.frontMatter,
age: result.frontMatter.age * 2,
extra: 'value',
great: true,
},
};
},
},
),
).resolves.toMatchSnapshot();
});
it('parses first heading as contentTitle', async () => {
await expect(
test(dedent`
# Markdown Title
Some text
`),
).toMatchSnapshot();
).resolves.toMatchSnapshot();
});
it('warns about duplicate titles (front matter + markdown)', () => {
expect(
parseMarkdownString(dedent`
it('warns about duplicate titles (front matter + markdown)', async () => {
await expect(
test(dedent`
---
title: Frontmatter title
---
@ -657,12 +737,12 @@ describe('parseMarkdownString', () => {
Some text
`),
).toMatchSnapshot();
).resolves.toMatchSnapshot();
});
it('warns about duplicate titles (front matter + markdown alternate)', () => {
expect(
parseMarkdownString(dedent`
it('warns about duplicate titles (front matter + markdown alternate)', async () => {
await expect(
test(dedent`
---
title: Frontmatter title
---
@ -672,12 +752,12 @@ describe('parseMarkdownString', () => {
Some text
`),
).toMatchSnapshot();
).resolves.toMatchSnapshot();
});
it('does not warn for duplicate title if markdown title is not at the top', () => {
expect(
parseMarkdownString(dedent`
it('does not warn for duplicate title if markdown title is not at the top', async () => {
await expect(
test(dedent`
---
title: Frontmatter title
---
@ -686,12 +766,12 @@ describe('parseMarkdownString', () => {
# Markdown Title
`),
).toMatchSnapshot();
).resolves.toMatchSnapshot();
});
it('deletes only first heading', () => {
expect(
parseMarkdownString(dedent`
it('deletes only first heading', async () => {
await expect(
test(dedent`
# Markdown Title
test test test # test bar
@ -700,12 +780,12 @@ describe('parseMarkdownString', () => {
### Markdown Title h3
`),
).toMatchSnapshot();
).resolves.toMatchSnapshot();
});
it('parses front-matter and ignore h2', () => {
expect(
parseMarkdownString(
it('parses front-matter and ignore h2', async () => {
await expect(
test(
dedent`
---
title: Frontmatter title
@ -713,55 +793,55 @@ describe('parseMarkdownString', () => {
## test
`,
),
).toMatchSnapshot();
).resolves.toMatchSnapshot();
});
it('reads front matter only', () => {
expect(
parseMarkdownString(dedent`
it('reads front matter only', async () => {
await expect(
test(dedent`
---
title: test
---
`),
).toMatchSnapshot();
).resolves.toMatchSnapshot();
});
it('parses title only', () => {
expect(parseMarkdownString('# test')).toMatchSnapshot();
it('parses title only', async () => {
await expect(test('# test')).resolves.toMatchSnapshot();
});
it('parses title only alternate', () => {
expect(
parseMarkdownString(dedent`
it('parses title only alternate', async () => {
await expect(
test(dedent`
test
===
`),
).toMatchSnapshot();
).resolves.toMatchSnapshot();
});
it('warns about duplicate titles', () => {
expect(
parseMarkdownString(dedent`
it('warns about duplicate titles', async () => {
await expect(
test(dedent`
---
title: Frontmatter title
---
# test
`),
).toMatchSnapshot();
).resolves.toMatchSnapshot();
});
it('ignores markdown title if its not a first text', () => {
expect(
parseMarkdownString(dedent`
it('ignores markdown title if its not a first text', async () => {
await expect(
test(dedent`
foo
# test
`),
).toMatchSnapshot();
).resolves.toMatchSnapshot();
});
it('deletes only first heading 2', () => {
expect(
parseMarkdownString(dedent`
it('deletes only first heading 2', async () => {
await expect(
test(dedent`
# test
test test test test test test
@ -770,21 +850,21 @@ describe('parseMarkdownString', () => {
### test
test3
`),
).toMatchSnapshot();
).resolves.toMatchSnapshot();
});
it('handles code blocks', () => {
expect(
parseMarkdownString(dedent`
it('handles code blocks', async () => {
await expect(
test(dedent`
\`\`\`js
code
\`\`\`
Content
`),
).toMatchSnapshot();
expect(
parseMarkdownString(dedent`
).resolves.toMatchSnapshot();
await expect(
test(dedent`
\`\`\`\`js
Foo
\`\`\`diff
@ -795,9 +875,9 @@ describe('parseMarkdownString', () => {
Content
`),
).toMatchSnapshot();
expect(
parseMarkdownString(dedent`
).resolves.toMatchSnapshot();
await expect(
test(dedent`
\`\`\`\`js
Foo
\`\`\`diff
@ -806,17 +886,17 @@ describe('parseMarkdownString', () => {
Content
`),
).toMatchSnapshot();
).resolves.toMatchSnapshot();
});
it('throws for invalid front matter', () => {
expect(() =>
parseMarkdownString(dedent`
it('throws for invalid front matter', async () => {
await expect(
test(dedent`
---
foo: f: a
---
`),
).toThrowErrorMatchingInlineSnapshot(`
).rejects.toThrowErrorMatchingInlineSnapshot(`
"incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line at line 2, column 7:
foo: f: a
^"
@ -1288,17 +1368,23 @@ describe('admonitionTitleToDirectiveLabel', () => {
`);
});
it('does not transform left-padded directives', () => {
it('transforms space indented directives', () => {
expect(
admonitionTitleToDirectiveLabel(
dedent`
before
:::note Title
:::note 1 space
content
content
:::
:::
:::note 2 spaces
content
:::
after
`,
@ -1307,16 +1393,114 @@ describe('admonitionTitleToDirectiveLabel', () => {
).toEqual(dedent`
before
:::note Title
:::note[1 space]
content
content
:::
:::
:::note[2 spaces]
content
:::
after
`);
});
it('transforms tab indented directives', () => {
expect(
admonitionTitleToDirectiveLabel(
`
before
\t:::note 1 tab
\tcontent
\t:::
\t\t:::note 2 tabs
\t\tcontent
\t\t:::
after
`,
directives,
),
).toBe(`
before
\t:::note[1 tab]
\tcontent
\t:::
\t\t:::note[2 tabs]
\t\tcontent
\t\t:::
after
`);
});
it('transforms directives in quotes', () => {
expect(
admonitionTitleToDirectiveLabel(
`
before
> :::caution There be dragons
>
> This is the admonition content
>
> :::
>
>> :::caution There be dragons
>>
>> This is the admonition content
>>
>> :::
> > :::caution There be dragons
> >
> > This is the admonition content
> >
> > :::
after
`,
directives,
),
).toBe(`
before
> :::caution[There be dragons]
>
> This is the admonition content
>
> :::
>
>> :::caution[There be dragons]
>>
>> This is the admonition content
>>
>> :::
> > :::caution[There be dragons]
> >
> > This is the admonition content
> >
> > :::
after
`);
});
it('does not transform admonition without title', () => {
expect(
admonitionTitleToDirectiveLabel(

View File

@ -18,6 +18,8 @@ import {
buildSshUrl,
buildHttpsUrl,
hasSSHProtocol,
parseURLPath,
serializeURLPath,
} from '../urlUtils';
describe('normalizeUrl', () => {
@ -232,6 +234,137 @@ describe('removeTrailingSlash', () => {
});
});
describe('parseURLPath', () => {
it('parse and resolve pathname', () => {
expect(parseURLPath('')).toEqual({
pathname: '/',
search: undefined,
hash: undefined,
});
expect(parseURLPath('/')).toEqual({
pathname: '/',
search: undefined,
hash: undefined,
});
expect(parseURLPath('/page')).toEqual({
pathname: '/page',
search: undefined,
hash: undefined,
});
expect(parseURLPath('/dir1/page')).toEqual({
pathname: '/dir1/page',
search: undefined,
hash: undefined,
});
expect(parseURLPath('/dir1/dir2/./../page')).toEqual({
pathname: '/dir1/page',
search: undefined,
hash: undefined,
});
expect(parseURLPath('/dir1/dir2/../..')).toEqual({
pathname: '/',
search: undefined,
hash: undefined,
});
expect(parseURLPath('/dir1/dir2/../../..')).toEqual({
pathname: '/',
search: undefined,
hash: undefined,
});
expect(parseURLPath('./dir1/dir2./../page', '/dir3/dir4/page2')).toEqual({
pathname: '/dir3/dir4/dir1/page',
search: undefined,
hash: undefined,
});
});
it('parse query string', () => {
expect(parseURLPath('/page')).toEqual({
pathname: '/page',
search: undefined,
hash: undefined,
});
expect(parseURLPath('/page?')).toEqual({
pathname: '/page',
search: '',
hash: undefined,
});
expect(parseURLPath('/page?test')).toEqual({
pathname: '/page',
search: 'test',
hash: undefined,
});
expect(parseURLPath('/page?age=42&great=true')).toEqual({
pathname: '/page',
search: 'age=42&great=true',
hash: undefined,
});
});
it('parse hash', () => {
expect(parseURLPath('/page')).toEqual({
pathname: '/page',
search: undefined,
hash: undefined,
});
expect(parseURLPath('/page#')).toEqual({
pathname: '/page',
search: undefined,
hash: '',
});
expect(parseURLPath('/page#anchor')).toEqual({
pathname: '/page',
search: undefined,
hash: 'anchor',
});
});
it('parse fancy real-world edge cases', () => {
expect(parseURLPath('/page?#')).toEqual({
pathname: '/page',
search: '',
hash: '',
});
expect(
parseURLPath('dir1/dir2/../page?age=42#anchor', '/dir3/page2'),
).toEqual({
pathname: '/dir3/dir1/page',
search: 'age=42',
hash: 'anchor',
});
});
});
describe('serializeURLPath', () => {
function test(input: string, base?: string, expectedOutput?: string) {
expect(serializeURLPath(parseURLPath(input, base))).toEqual(
expectedOutput ?? input,
);
}
it('works for already resolved paths', () => {
test('/');
test('/dir1/page');
test('/dir1/page?');
test('/dir1/page#');
test('/dir1/page?#');
test('/dir1/page?age=42#anchor');
});
it('works for relative paths', () => {
test('', undefined, '/');
test('', '/dir1/dir2/page2', '/dir1/dir2/page2');
test('page', '/dir1/dir2/page2', '/dir1/dir2/page');
test('../page', '/dir1/dir2/page2', '/dir1/page');
test('/dir1/dir2/../page', undefined, '/dir1/page');
test(
'/dir1/dir2/../page?age=42#anchor',
undefined,
'/dir1/page?age=42#anchor',
);
});
});
describe('resolvePathname', () => {
it('works', () => {
// These tests are directly copied from https://github.com/mjackson/resolve-pathname/blob/master/modules/__tests__/resolvePathname-test.js

View File

@ -83,7 +83,9 @@ export const DEFAULT_I18N_DIR_NAME = 'i18n';
export const CODE_TRANSLATIONS_FILE_NAME = 'code.json';
/** Dev server opens on this port by default. */
export const DEFAULT_PORT = 3000;
export const DEFAULT_PORT = process.env.PORT
? parseInt(process.env.PORT, 10)
: 3000;
/** Default plugin ID. */
export const DEFAULT_PLUGIN_ID = 'default';

View File

@ -48,6 +48,8 @@ export {
encodePath,
isValidPathname,
resolvePathname,
parseURLPath,
serializeURLPath,
addLeadingSlash,
addTrailingSlash,
removeTrailingSlash,
@ -55,6 +57,7 @@ export {
buildHttpsUrl,
buildSshUrl,
} from './urlUtils';
export type {URLPath} from './urlUtils';
export {
type Tag,
type TagsListItem,
@ -70,9 +73,9 @@ export {
unwrapMdxCodeBlocks,
admonitionTitleToDirectiveLabel,
createExcerpt,
parseFrontMatter,
DEFAULT_PARSE_FRONT_MATTER,
parseMarkdownContentTitle,
parseMarkdownString,
parseMarkdownFile,
writeMarkdownHeadingId,
type WriteHeadingIDOptions,
} from './markdownUtils';

View File

@ -128,7 +128,7 @@ export function replaceMarkdownLinks<T extends ContentPaths>({
const linkSuffixPattern = '(?:\\?[^#>\\s]+)?(?:#[^>\\s]+)?';
const linkCapture = (forbidden: string) =>
`((?!https?://|@site/)[^${forbidden}#?]+)`;
const linkURLPattern = `(?:${linkCapture(
const linkURLPattern = `(?:(?!<)${linkCapture(
'()\\s',
)}${linkSuffixPattern}|<${linkCapture('>')}${linkSuffixPattern}>)`;
const linkPattern = new RegExp(

View File

@ -8,6 +8,10 @@
import logger from '@docusaurus/logger';
import matter from 'gray-matter';
import {createSlugger, type Slugger, type SluggerOptions} from './slugger';
import type {
ParseFrontMatter,
DefaultParseFrontMatter,
} from '@docusaurus/types';
// Some utilities for parsing Markdown content. These things are only used on
// server-side when we infer metadata like `title` and `description` from the
@ -97,14 +101,16 @@ export function admonitionTitleToDirectiveLabel(
const directiveNameGroup = `(${admonitionContainerDirectives.join('|')})`;
const regexp = new RegExp(
`^(?<directive>:{3,}${directiveNameGroup}) +(?<title>.*)$`,
`^(?<quote>(> ?)*)(?<indentation>( +|\t+))?(?<directive>:{3,}${directiveNameGroup}) +(?<title>.*)$`,
'gm',
);
return content.replaceAll(regexp, (substring, ...args: any[]) => {
const groups = args.at(-1);
return `${groups.directive}[${groups.title}]`;
return `${groups.quote ?? ''}${groups.indentation ?? ''}${
groups.directive
}[${groups.title}]`;
});
}
@ -212,19 +218,40 @@ export function createExcerpt(fileString: string): string | undefined {
* ---
* ```
*/
export function parseFrontMatter(markdownFileContent: string): {
export function parseFileContentFrontMatter(fileContent: string): {
/** Front matter as parsed by gray-matter. */
frontMatter: {[key: string]: unknown};
/** The remaining content, trimmed. */
content: string;
} {
const {data, content} = matter(markdownFileContent);
// TODO Docusaurus v4: replace gray-matter by a better lib
// gray-matter is unmaintained, not flexible, and the code doesn't look good
const {data, content} = matter(fileContent);
// gray-matter has an undocumented front matter caching behavior
// https://github.com/jonschlinkert/gray-matter/blob/ce67a86dba419381db0dd01cc84e2d30a1d1e6a5/index.js#L39
// Unfortunately, this becomes a problem when we mutate returned front matter
// We want to make it possible as part of the parseFrontMatter API
// So we make it safe to mutate by always providing a deep copy
const frontMatter =
// And of course structuredClone() doesn't work well with Date in Jest...
// See https://github.com/jestjs/jest/issues/2549
// So we parse again for tests with a {} option object
// This undocumented empty option object disables gray-matter caching..
process.env.JEST_WORKER_ID
? matter(fileContent, {}).data
: structuredClone(data);
return {
frontMatter: data,
frontMatter,
content: content.trim(),
};
}
export const DEFAULT_PARSE_FRONT_MATTER: DefaultParseFrontMatter = async (
params,
) => parseFileContentFrontMatter(params.fileContent);
function toTextContentTitle(contentTitle: string): string {
return contentTitle.replace(/`(?<text>[^`]*)`/g, '$<text>');
}
@ -307,10 +334,16 @@ export function parseMarkdownContentTitle(
* @throws Throws when `parseFrontMatter` throws, usually because of invalid
* syntax.
*/
export function parseMarkdownString(
markdownFileContent: string,
options?: ParseMarkdownContentTitleOptions,
): {
export async function parseMarkdownFile({
filePath,
fileContent,
parseFrontMatter,
removeContentTitle,
}: {
filePath: string;
fileContent: string;
parseFrontMatter: ParseFrontMatter;
} & ParseMarkdownContentTitleOptions): Promise<{
/** @see {@link parseFrontMatter} */
frontMatter: {[key: string]: unknown};
/** @see {@link parseMarkdownContentTitle} */
@ -322,14 +355,18 @@ export function parseMarkdownString(
* the `removeContentTitle` option.
*/
content: string;
} {
}> {
try {
const {frontMatter, content: contentWithoutFrontMatter} =
parseFrontMatter(markdownFileContent);
await parseFrontMatter({
filePath,
fileContent,
defaultParseFrontMatter: DEFAULT_PARSE_FRONT_MATTER,
});
const {content, contentTitle} = parseMarkdownContentTitle(
contentWithoutFrontMatter,
options,
{removeContentTitle},
);
const excerpt = createExcerpt(content);

View File

@ -165,14 +165,73 @@ export function isValidPathname(str: string): boolean {
}
}
export type URLPath = {pathname: string; search?: string; hash?: string};
// Let's name the concept of (pathname + search + hash) as URLPath
// See also https://twitter.com/kettanaito/status/1741768992866308120
// Note: this function also resolves relative pathnames while parsing!
export function parseURLPath(urlPath: string, fromPath?: string): URLPath {
function parseURL(url: string, base?: string | URL): URL {
try {
// A possible alternative? https://github.com/unjs/ufo#url
return new URL(url, base ?? 'https://example.com');
} catch (e) {
throw new Error(
`Can't parse URL ${url}${base ? ` with base ${base}` : ''}`,
{cause: e},
);
}
}
const base = fromPath ? parseURL(fromPath) : undefined;
const url = parseURL(urlPath, base);
const {pathname} = url;
// Fixes annoying url.search behavior
// "" => undefined
// "?" => ""
// "?param => "param"
const search = url.search
? url.search.slice(1)
: urlPath.includes('?')
? ''
: undefined;
// Fixes annoying url.hash behavior
// "" => undefined
// "#" => ""
// "?param => "param"
const hash = url.hash
? url.hash.slice(1)
: urlPath.includes('#')
? ''
: undefined;
return {
pathname,
search,
hash,
};
}
export function serializeURLPath(urlPath: URLPath): string {
const search = urlPath.search === undefined ? '' : `?${urlPath.search}`;
const hash = urlPath.hash === undefined ? '' : `#${urlPath.hash}`;
return `${urlPath.pathname}${search}${hash}`;
}
/**
* Resolve pathnames and fail-fast if resolution fails. Uses standard URL
* semantics (provided by `resolve-pathname` which is used internally by React
* router)
*/
export function resolvePathname(to: string, from?: string): string {
// TODO do we really need resolve-pathname lib anymore?
// possible alternative: decodeURI(parseURLPath(to, from).pathname);
return resolvePathnameUnsafe(to, from);
}
/** Appends a leading slash to `str`, if one doesn't exist. */
export function addLeadingSlash(str: string): string {
return addPrefix(str, '/');

View File

@ -218,6 +218,9 @@ cli.arguments('<command>').action((cmd) => {
logger.error` Unknown command name=${cmd}.`;
});
// === The above is the commander configuration ===
// They don't start any code execution yet until cli.parse() is called below
/**
* @param {string | undefined} command
*/
@ -237,12 +240,29 @@ function isInternalCommand(command) {
);
}
if (!isInternalCommand(process.argv.slice(2)[0])) {
await externalCommand(cli);
// process.argv always looks like this:
// [
// '/path/to/node',
// '/path/to/docusaurus.mjs',
// '<subcommand>',
// ...subcommandArgs
// ]
// There is no subcommand
// TODO: can we use commander to handle this case?
if (process.argv.length < 3 || process.argv[2]?.startsWith('--')) {
cli.outputHelp();
process.exit(1);
}
if (!process.argv.slice(2).length) {
cli.outputHelp();
// There is an unrecognized subcommand
// Let plugins extend the CLI before parsing
if (!isInternalCommand(process.argv[2])) {
// TODO: in this step, we must assume default site structure because there's
// no way to know the siteDir/config yet. Maybe the root cli should be
// responsible for parsing these arguments?
// https://github.com/facebook/docusaurus/issues/8903
await externalCommand(cli);
}
cli.parse(process.argv);

View File

@ -1,7 +1,7 @@
{
"name": "@docusaurus/core",
"description": "Easy to Maintain Open Source Documentation Websites",
"version": "3.0.0",
"version": "3.1.0",
"license": "MIT",
"publishConfig": {
"access": "public"
@ -33,8 +33,8 @@
"url": "https://github.com/facebook/docusaurus/issues"
},
"dependencies": {
"@babel/core": "^7.22.9",
"@babel/generator": "^7.22.9",
"@babel/core": "^7.23.3",
"@babel/generator": "^7.23.3",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.22.9",
"@babel/preset-env": "^7.22.9",
@ -43,13 +43,13 @@
"@babel/runtime": "^7.22.6",
"@babel/runtime-corejs3": "^7.22.6",
"@babel/traverse": "^7.22.8",
"@docusaurus/cssnano-preset": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/cssnano-preset": "3.1.0",
"@docusaurus/logger": "3.1.0",
"@docusaurus/mdx-loader": "3.1.0",
"@docusaurus/react-loadable": "5.5.2",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/utils": "3.1.0",
"@docusaurus/utils-common": "3.1.0",
"@docusaurus/utils-validation": "3.1.0",
"@slorber/static-site-generator-webpack-plugin": "^4.0.7",
"@svgr/webpack": "^6.5.1",
"autoprefixer": "^10.4.14",
@ -97,7 +97,6 @@
"tslib": "^2.6.0",
"update-notifier": "^6.0.2",
"url-loader": "^4.1.1",
"wait-on": "^7.0.1",
"webpack": "^5.88.1",
"webpack-bundle-analyzer": "^4.9.0",
"webpack-dev-server": "^4.15.1",
@ -105,15 +104,14 @@
"webpackbar": "^5.0.2"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/module-type-aliases": "3.1.0",
"@docusaurus/types": "3.1.0",
"@types/detect-port": "^1.3.3",
"@types/react-dom": "^18.2.7",
"@types/react-router-config": "^5.0.7",
"@types/rtl-detect": "^1.0.0",
"@types/serve-handler": "^6.1.1",
"@types/update-notifier": "^6.0.4",
"@types/wait-on": "^5.3.1",
"@types/webpack-bundle-analyzer": "^4.6.0",
"react-test-renderer": "^18.0.0",
"tmp-promise": "^3.0.3",

View File

@ -0,0 +1,51 @@
/**
* 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, useContext} from 'react';
import type {BrokenLinks} from '@docusaurus/useBrokenLinks';
export type StatefulBrokenLinks = BrokenLinks & {
getCollectedLinks: () => string[];
getCollectedAnchors: () => string[];
};
export const createStatefulBrokenLinks = (): StatefulBrokenLinks => {
// Set to dedup, as it's not useful to collect multiple times the same value
const allAnchors = new Set<string>();
const allLinks = new Set<string>();
return {
collectAnchor: (anchor: string): void => {
allAnchors.add(anchor);
},
collectLink: (link: string): void => {
allLinks.add(link);
},
getCollectedAnchors: (): string[] => [...allAnchors],
getCollectedLinks: (): string[] => [...allLinks],
};
};
const Context = React.createContext<BrokenLinks>({
collectAnchor: () => {
// No-op for client
},
collectLink: () => {
// No-op for client
},
});
export const useBrokenLinksContext = (): BrokenLinks => useContext(Context);
export function BrokenLinksProvider({
children,
brokenLinks,
}: {
children: ReactNode;
brokenLinks: BrokenLinks;
}): JSX.Element {
return <Context.Provider value={brokenLinks}>{children}</Context.Provider>;
}

View File

@ -1,45 +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, {type ReactNode, useContext} from 'react';
type LinksCollector = {
collectLink: (link: string) => void;
};
type StatefulLinksCollector = LinksCollector & {
getCollectedLinks: () => string[];
};
export const createStatefulLinksCollector = (): StatefulLinksCollector => {
// Set to dedup, as it's not useful to collect multiple times the same link
const allLinks = new Set<string>();
return {
collectLink: (link: string): void => {
allLinks.add(link);
},
getCollectedLinks: (): string[] => [...allLinks],
};
};
const Context = React.createContext<LinksCollector>({
collectLink: () => {
// No-op for client. We only use the broken links checker server-side.
},
});
export const useLinksCollector = (): LinksCollector => useContext(Context);
export function LinksCollectorProvider({
children,
linksCollector,
}: {
children: ReactNode;
linksCollector: LinksCollector;
}): JSX.Element {
return <Context.Provider value={linksCollector}>{children}</Context.Provider>;
}

View File

@ -16,7 +16,7 @@ import {applyTrailingSlash} from '@docusaurus/utils-common';
import useDocusaurusContext from './useDocusaurusContext';
import isInternalUrl from './isInternalUrl';
import ExecutionEnvironment from './ExecutionEnvironment';
import {useLinksCollector} from '../LinksCollector';
import useBrokenLinks from './useBrokenLinks';
import {useBaseUrlUtils} from './useBaseUrl';
import type {Props} from '@docusaurus/Link';
@ -44,7 +44,7 @@ function Link(
siteConfig: {trailingSlash, baseUrl},
} = useDocusaurusContext();
const {withBaseUrl} = useBaseUrlUtils();
const linksCollector = useLinksCollector();
const brokenLinks = useBrokenLinks();
const innerRef = useRef<HTMLAnchorElement | null>(null);
useImperativeHandle(forwardedRef, () => innerRef.current!);
@ -144,7 +144,7 @@ function Link(
const isRegularHtmlLink = !targetLink || !isInternal || isAnchorLink;
if (!isRegularHtmlLink && !noBrokenLinkCheck) {
linksCollector.collectLink(targetLink!);
brokenLinks.collectLink(targetLink!);
}
return isRegularHtmlLink ? (

View File

@ -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.
*/
import {useBrokenLinksContext} from '../BrokenLinksContext';
import type {BrokenLinks} from '@docusaurus/useBrokenLinks';
export default function useBrokenLinks(): BrokenLinks {
return useBrokenLinksContext();
}

View File

@ -20,9 +20,9 @@ import {renderStaticApp} from './serverRenderer';
import preload from './preload';
import App from './App';
import {
createStatefulLinksCollector,
LinksCollectorProvider,
} from './LinksCollector';
createStatefulBrokenLinks,
BrokenLinksProvider,
} from './BrokenLinksContext';
import type {Locals} from '@slorber/static-site-generator-webpack-plugin';
const getCompiledSSRTemplate = _.memoize((template: string) =>
@ -96,23 +96,27 @@ async function doRender(locals: Locals & {path: string}) {
const routerContext = {};
const helmetContext = {};
const linksCollector = createStatefulLinksCollector();
const statefulBrokenLinks = createStatefulBrokenLinks();
const app = (
// @ts-expect-error: we are migrating away from react-loadable anyways
<Loadable.Capture report={(moduleName) => modules.add(moduleName)}>
<HelmetProvider context={helmetContext}>
<StaticRouter location={location} context={routerContext}>
<LinksCollectorProvider linksCollector={linksCollector}>
<BrokenLinksProvider brokenLinks={statefulBrokenLinks}>
<App />
</LinksCollectorProvider>
</BrokenLinksProvider>
</StaticRouter>
</HelmetProvider>
</Loadable.Capture>
);
const appHtml = await renderStaticApp(app);
onLinksCollected(location, linksCollector.getCollectedLinks());
onLinksCollected({
staticPagePath: location,
anchors: statefulBrokenLinks.getCollectedAnchors(),
links: statefulBrokenLinks.getCollectedLinks(),
});
const {helmet} = helmetContext as FilledContext;
const htmlAttributes = helmet.htmlAttributes.toString();

View File

@ -152,8 +152,8 @@ async function buildLocale({
generatedFilesDir,
plugins,
siteConfig: {
baseUrl,
onBrokenLinks,
onBrokenAnchors,
staticDirectories: staticDirectoriesOption,
},
routes,
@ -180,13 +180,15 @@ async function buildLocale({
},
);
const allCollectedLinks: {[location: string]: string[]} = {};
const collectedLinks: {
[pathname: string]: {links: string[]; anchors: string[]};
} = {};
const headTags: {[location: string]: HelmetServerState} = {};
let serverConfig: Configuration = await createServerConfig({
props,
onLinksCollected: (staticPagePath, links) => {
allCollectedLinks[staticPagePath] = links;
onLinksCollected: ({staticPagePath, links, anchors}) => {
collectedLinks[staticPagePath] = {links, anchors};
},
onHeadTagsCollected: (staticPagePath, tags) => {
headTags[staticPagePath] = tags;
@ -288,11 +290,10 @@ async function buildLocale({
);
await handleBrokenLinks({
allCollectedLinks,
collectedLinks,
routes,
onBrokenLinks,
outDir,
baseUrl,
onBrokenAnchors,
});
logger.success`Generated static files in path=${path.relative(

View File

@ -42,7 +42,11 @@ declare module '@slorber/static-site-generator-webpack-plugin' {
headTags: string;
preBodyTags: string;
postBodyTags: string;
onLinksCollected: (staticPagePath: string, links: string[]) => void;
onLinksCollected: (params: {
staticPagePath: string;
links: string[];
anchors: string[];
}) => void;
onHeadTagsCollected: (
staticPagePath: string,
tags: HelmetServerState,

View File

@ -1,86 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`handleBrokenLinks reports all broken links 1`] = `
"Docusaurus found broken links!
Please check the pages of your site in the list below, and make sure you don't reference any path that does not exist.
Note: it's possible to ignore broken links with the 'onBrokenLinks' Docusaurus configuration, and let the build pass.
Exhaustive list of all broken links found:
- On source page path = /docs/good doc with space:
-> linking to ./some%20other%20non-existent%20doc1 (resolved as: /docs/some%20other%20non-existent%20doc1)
-> linking to ./break%2F..%2F..%2Fout2 (resolved as: /docs/break%2F..%2F..%2Fout2)
- On source page path = /docs/goodDoc:
-> linking to ../anotherGoodDoc#reported-because-of-bad-relative-path1 (resolved as: /anotherGoodDoc)
-> linking to ./docThatDoesNotExist2 (resolved as: /docs/docThatDoesNotExist2)
-> linking to ./badRelativeLink3 (resolved as: /docs/badRelativeLink3)
-> linking to ../badRelativeLink4 (resolved as: /badRelativeLink4)
- On source page path = /community:
-> linking to /someNonExistentDoc1
-> linking to /badLink2
-> linking to ./badLink3 (resolved as: /badLink3)
- On source page path = /page1:
-> linking to /link1
-> linking to /emptyFolder
- On source page path = /page2:
-> linking to /docs/link2
-> linking to /emptyFolder/
-> linking to /hey/link3
"
`;
exports[`handleBrokenLinks reports frequent broken links 1`] = `
"Docusaurus found broken links!
Please check the pages of your site in the list below, and make sure you don't reference any path that does not exist.
Note: it's possible to ignore broken links with the 'onBrokenLinks' Docusaurus configuration, and let the build pass.
It looks like some of the broken links we found appear in many pages of your site.
Maybe those broken links appear on all pages through your site layout?
We recommend that you check your theme configuration for such links (particularly, theme navbar and footer).
Frequent broken links are linking to:
- /frequent
- ./maybe-not
Exhaustive list of all broken links found:
- On source page path = /docs/good doc with space:
-> linking to ./some%20other%20non-existent%20doc1 (resolved as: /docs/some%20other%20non-existent%20doc1)
-> linking to ./break%2F..%2F..%2Fout2 (resolved as: /docs/break%2F..%2F..%2Fout2)
-> linking to /frequent
-> linking to ./maybe-not (resolved as: /docs/maybe-not)
- On source page path = /docs/goodDoc:
-> linking to ../anotherGoodDoc#reported-because-of-bad-relative-path1 (resolved as: /anotherGoodDoc)
-> linking to ./docThatDoesNotExist2 (resolved as: /docs/docThatDoesNotExist2)
-> linking to ./badRelativeLink3 (resolved as: /docs/badRelativeLink3)
-> linking to ../badRelativeLink4 (resolved as: /badRelativeLink4)
-> linking to /frequent
-> linking to ./maybe-not (resolved as: /docs/maybe-not)
- On source page path = /community:
-> linking to /someNonExistentDoc1
-> linking to /badLink2
-> linking to ./badLink3 (resolved as: /badLink3)
-> linking to /frequent
-> linking to ./maybe-not (resolved as: /maybe-not)
- On source page path = /page1:
-> linking to /link1
-> linking to /emptyFolder
-> linking to /frequent
-> linking to ./maybe-not (resolved as: /maybe-not)
- On source page path = /page2:
-> linking to /docs/link2
-> linking to /emptyFolder/
-> linking to /hey/link3
-> linking to /frequent
-> linking to ./maybe-not (resolved as: /maybe-not)
"
`;

View File

@ -24,9 +24,12 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = `
"headingIds": true,
},
"mermaid": false,
"parseFrontMatter": [Function],
"preprocessor": undefined,
"remarkRehypeOptions": undefined,
},
"noIndex": false,
"onBrokenAnchors": "warn",
"onBrokenLinks": "throw",
"onBrokenMarkdownLinks": "warn",
"onDuplicateRoutes": "warn",
@ -72,9 +75,12 @@ exports[`loadSiteConfig website with ts + js config 1`] = `
"headingIds": true,
},
"mermaid": false,
"parseFrontMatter": [Function],
"preprocessor": undefined,
"remarkRehypeOptions": undefined,
},
"noIndex": false,
"onBrokenAnchors": "warn",
"onBrokenLinks": "throw",
"onBrokenMarkdownLinks": "warn",
"onDuplicateRoutes": "warn",
@ -120,9 +126,12 @@ exports[`loadSiteConfig website with valid JS CJS config 1`] = `
"headingIds": true,
},
"mermaid": false,
"parseFrontMatter": [Function],
"preprocessor": undefined,
"remarkRehypeOptions": undefined,
},
"noIndex": false,
"onBrokenAnchors": "warn",
"onBrokenLinks": "throw",
"onBrokenMarkdownLinks": "warn",
"onDuplicateRoutes": "warn",
@ -168,9 +177,12 @@ exports[`loadSiteConfig website with valid JS ESM config 1`] = `
"headingIds": true,
},
"mermaid": false,
"parseFrontMatter": [Function],
"preprocessor": undefined,
"remarkRehypeOptions": undefined,
},
"noIndex": false,
"onBrokenAnchors": "warn",
"onBrokenLinks": "throw",
"onBrokenMarkdownLinks": "warn",
"onDuplicateRoutes": "warn",
@ -216,9 +228,12 @@ exports[`loadSiteConfig website with valid TypeScript CJS config 1`] = `
"headingIds": true,
},
"mermaid": false,
"parseFrontMatter": [Function],
"preprocessor": undefined,
"remarkRehypeOptions": undefined,
},
"noIndex": false,
"onBrokenAnchors": "warn",
"onBrokenLinks": "throw",
"onBrokenMarkdownLinks": "warn",
"onDuplicateRoutes": "warn",
@ -264,9 +279,12 @@ exports[`loadSiteConfig website with valid TypeScript ESM config 1`] = `
"headingIds": true,
},
"mermaid": false,
"parseFrontMatter": [Function],
"preprocessor": undefined,
"remarkRehypeOptions": undefined,
},
"noIndex": false,
"onBrokenAnchors": "warn",
"onBrokenLinks": "throw",
"onBrokenMarkdownLinks": "warn",
"onDuplicateRoutes": "warn",
@ -312,9 +330,12 @@ exports[`loadSiteConfig website with valid async config 1`] = `
"headingIds": true,
},
"mermaid": false,
"parseFrontMatter": [Function],
"preprocessor": undefined,
"remarkRehypeOptions": undefined,
},
"noIndex": false,
"onBrokenAnchors": "warn",
"onBrokenLinks": "throw",
"onBrokenMarkdownLinks": "warn",
"onDuplicateRoutes": "warn",
@ -362,9 +383,12 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = `
"headingIds": true,
},
"mermaid": false,
"parseFrontMatter": [Function],
"preprocessor": undefined,
"remarkRehypeOptions": undefined,
},
"noIndex": false,
"onBrokenAnchors": "warn",
"onBrokenLinks": "throw",
"onBrokenMarkdownLinks": "warn",
"onDuplicateRoutes": "warn",
@ -412,9 +436,12 @@ exports[`loadSiteConfig website with valid config creator function 1`] = `
"headingIds": true,
},
"mermaid": false,
"parseFrontMatter": [Function],
"preprocessor": undefined,
"remarkRehypeOptions": undefined,
},
"noIndex": false,
"onBrokenAnchors": "warn",
"onBrokenLinks": "throw",
"onBrokenMarkdownLinks": "warn",
"onDuplicateRoutes": "warn",
@ -465,9 +492,12 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = `
"headingIds": true,
},
"mermaid": false,
"parseFrontMatter": [Function],
"preprocessor": undefined,
"remarkRehypeOptions": undefined,
},
"noIndex": false,
"onBrokenAnchors": "warn",
"onBrokenLinks": "throw",
"onBrokenMarkdownLinks": "warn",
"onDuplicateRoutes": "warn",

View File

@ -98,9 +98,12 @@ exports[`load loads props for site with custom i18n path 1`] = `
"headingIds": true,
},
"mermaid": false,
"parseFrontMatter": [Function],
"preprocessor": undefined,
"remarkRehypeOptions": undefined,
},
"noIndex": false,
"onBrokenAnchors": "warn",
"onBrokenLinks": "throw",
"onBrokenMarkdownLinks": "warn",
"onDuplicateRoutes": "warn",

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