mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-15 10:07:33 +02:00
chore: release Docusaurus v3.1 (#9705)
Co-authored-by: Joshua Chen <sidachen2003@gmail.com> Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com> Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com> Co-authored-by: Ivan Mar (sOkam!) <7308253+heysokam@users.noreply.github.com> Co-authored-by: c0h1b4 <dwidman@gmail.com> Co-authored-by: Janessa Garrow <janessa.garrow@gmail.com> Co-authored-by: ozaki <29860391+OzakIOne@users.noreply.github.com> Co-authored-by: axmmisaka <6500159+axmmisaka@users.noreply.github.com> Co-authored-by: Tatsunori Uchino <tats.u@live.jp> Co-authored-by: Simen Bekkhus <sbekkhus91@gmail.com> fix(i18n): complete translations for theme-common.json Brazilian Portuguese (pt-BR) (#9477) fix(content-blog): add baseUrl for author.image_url (#9581) fix(type-aliases): add `title` prop for imported inline SVG React components (#9612) fix(utils): Markdown link replacement with <> but no spaces (#9617) fix(live-codeblock): stabilize react-live transformCode callback, fix editor/preview desync (#9631) fix(cli): output help when no conventional config + no subcommand (#9648) fix CI job (#9604) fix Lint Autofix workflow (#9632) fix(pwa-plugin): upgrade workbox (#9668) fix(create-docusaurus): fix init template code blocks, and little improvements (#9696) fix(theme): allow empty code blocks and live playgrounds (#9704)
This commit is contained in:
parent
a2e05d2118
commit
7b1b89041f
140 changed files with 3187 additions and 1707 deletions
44
.github/workflows/lint-autofix.yml
vendored
Normal file
44
.github/workflows/lint-autofix.yml
vendored
Normal 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'
|
|
@ -1,8 +1,5 @@
|
||||||
{
|
{
|
||||||
"*.{js,jsx,ts,tsx,mjs}": ["eslint --fix"],
|
"*.{js,jsx,ts,tsx,mjs}": ["eslint --fix"],
|
||||||
"*.css": ["stylelint --allow-empty-input --fix"],
|
"*.css": ["stylelint --allow-empty-input --fix"],
|
||||||
"*": [
|
"*": ["prettier --ignore-unknown --write"]
|
||||||
"prettier --ignore-unknown --write",
|
|
||||||
"cspell --no-must-find-files --no-progress"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,5 +24,5 @@ website/versioned_sidebars/*.json
|
||||||
examples/
|
examples/
|
||||||
website/static/katex/katex.min.css
|
website/static/katex/katex.min.css
|
||||||
|
|
||||||
website/changelog/_swizzle_theme_tests
|
website/changelog
|
||||||
website/_dogfooding/_swizzle_theme_tests
|
website/_dogfooding/_swizzle_theme_tests
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "new.docusaurus.io",
|
"name": "new.docusaurus.io",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npx --package netlify-cli netlify dev"
|
"start": "npx --package netlify-cli netlify dev"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "argos",
|
"name": "argos",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Argos visual diff tests",
|
"description": "Argos visual diff tests",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"useNx": false,
|
"useNx": false,
|
||||||
|
|
|
@ -51,8 +51,11 @@
|
||||||
"lint": "yarn lint:js && yarn lint:style && yarn lint:spelling",
|
"lint": "yarn lint:js && yarn lint:style && yarn lint:spelling",
|
||||||
"lint:ci": "yarn lint:js --quiet && 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: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": "stylelint \"**/*.css\"",
|
||||||
|
"lint:style:fix": "yarn lint:style --fix",
|
||||||
"lerna": "lerna",
|
"lerna": "lerna",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:build:website": "./admin/scripts/test-release.sh",
|
"test:build:website": "./admin/scripts/test-release.sh",
|
||||||
|
@ -80,7 +83,7 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
"@typescript-eslint/parser": "^5.62.0",
|
"@typescript-eslint/parser": "^5.62.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cspell": "^6.31.2",
|
"cspell": "^8.1.0",
|
||||||
"eslint": "^8.45.0",
|
"eslint": "^8.45.0",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
|
|
|
@ -10,6 +10,10 @@ npm init docusaurus
|
||||||
yarn create docusaurus
|
yarn create docusaurus
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx create-docusaurus@latest
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Please see the [installation documentation](https://docusaurus.io/docs/installation).
|
Please see the [installation documentation](https://docusaurus.io/docs/installation).
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "create-docusaurus",
|
"name": "create-docusaurus",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Create Docusaurus apps easily.",
|
"description": "Create Docusaurus apps easily.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -22,8 +22,8 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/logger": "3.0.1",
|
"@docusaurus/logger": "3.1.0",
|
||||||
"@docusaurus/utils": "3.0.1",
|
"@docusaurus/utils": "3.1.0",
|
||||||
"commander": "^5.1.0",
|
"commander": "^5.1.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "docusaurus-2-classic-typescript-template",
|
"name": "docusaurus-2-classic-typescript-template",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"docusaurus": "docusaurus",
|
"docusaurus": "docusaurus",
|
||||||
|
@ -15,8 +15,8 @@
|
||||||
"typecheck": "tsc"
|
"typecheck": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/preset-classic": "3.0.1",
|
"@docusaurus/preset-classic": "3.1.0",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"prism-react-renderer": "^2.3.0",
|
"prism-react-renderer": "^2.3.0",
|
||||||
|
@ -24,9 +24,9 @@
|
||||||
"react-dom": "^18.0.0"
|
"react-dom": "^18.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "3.0.1",
|
"@docusaurus/module-type-aliases": "3.1.0",
|
||||||
"@docusaurus/tsconfig": "3.0.1",
|
"@docusaurus/tsconfig": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"typescript": "~5.2.2"
|
"typescript": "~5.2.2"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "docusaurus-2-classic-template",
|
"name": "docusaurus-2-classic-template",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"docusaurus": "docusaurus",
|
"docusaurus": "docusaurus",
|
||||||
|
@ -14,8 +14,8 @@
|
||||||
"write-heading-ids": "docusaurus write-heading-ids"
|
"write-heading-ids": "docusaurus write-heading-ids"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/preset-classic": "3.0.1",
|
"@docusaurus/preset-classic": "3.1.0",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"prism-react-renderer": "^2.3.0",
|
"prism-react-renderer": "^2.3.0",
|
||||||
|
@ -23,8 +23,8 @@
|
||||||
"react-dom": "^18.0.0"
|
"react-dom": "^18.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "3.0.1",
|
"@docusaurus/module-type-aliases": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1"
|
"@docusaurus/types": "3.1.0"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|
|
@ -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.
|
Markdown code blocks are supported with Syntax highlighting.
|
||||||
|
|
||||||
```jsx title="src/components/HelloDocusaurus.js"
|
````md
|
||||||
function HelloDocusaurus() {
|
```jsx title="src/components/HelloDocusaurus.js"
|
||||||
return (
|
function HelloDocusaurus() {
|
||||||
<h1>Hello, Docusaurus!</h1>
|
return <h1>Hello, Docusaurus!</h1>;
|
||||||
)
|
}
|
||||||
}
|
```
|
||||||
```
|
````
|
||||||
|
|
||||||
```jsx title="src/components/HelloDocusaurus.js"
|
```jsx title="src/components/HelloDocusaurus.js"
|
||||||
function HelloDocusaurus() {
|
function HelloDocusaurus() {
|
||||||
|
@ -79,17 +79,19 @@ function HelloDocusaurus() {
|
||||||
|
|
||||||
Docusaurus has a special syntax to create admonitions and callouts:
|
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
|
:::tip My tip
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/cssnano-preset",
|
"name": "@docusaurus/cssnano-preset",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Advanced cssnano preset for maximum optimization.",
|
"description": "Advanced cssnano preset for maximum optimization.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/logger",
|
"name": "@docusaurus/logger",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "An encapsulated logger for semantically formatting console messages.",
|
"description": "An encapsulated logger for semantically formatting console messages.",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/mdx-loader",
|
"name": "@docusaurus/mdx-loader",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Docusaurus Loader for MDX",
|
"description": "Docusaurus Loader for MDX",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
@ -20,9 +20,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.22.7",
|
"@babel/parser": "^7.22.7",
|
||||||
"@babel/traverse": "^7.22.8",
|
"@babel/traverse": "^7.22.8",
|
||||||
"@docusaurus/logger": "3.0.1",
|
"@docusaurus/logger": "3.1.0",
|
||||||
"@docusaurus/utils": "3.0.1",
|
"@docusaurus/utils": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"@mdx-js/mdx": "^3.0.0",
|
"@mdx-js/mdx": "^3.0.0",
|
||||||
"@slorber/remark-comment": "^1.0.0",
|
"@slorber/remark-comment": "^1.0.0",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
"webpack": "^5.88.1"
|
"webpack": "^5.88.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@types/escape-html": "^1.0.2",
|
"@types/escape-html": "^1.0.2",
|
||||||
"@types/mdast": "^4.0.2",
|
"@types/mdast": "^4.0.2",
|
||||||
"@types/stringify-object": "^3.3.1",
|
"@types/stringify-object": "^3.3.1",
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import logger from '@docusaurus/logger';
|
import logger from '@docusaurus/logger';
|
||||||
import {
|
import {
|
||||||
parseFrontMatter,
|
DEFAULT_PARSE_FRONT_MATTER,
|
||||||
escapePath,
|
escapePath,
|
||||||
getFileLoaderUtils,
|
getFileLoaderUtils,
|
||||||
getWebpackLoaderCompilerName,
|
getWebpackLoaderCompilerName,
|
||||||
|
@ -133,7 +133,7 @@ function extractContentTitleData(data: {
|
||||||
|
|
||||||
export async function mdxLoader(
|
export async function mdxLoader(
|
||||||
this: LoaderContext<Options>,
|
this: LoaderContext<Options>,
|
||||||
fileString: string,
|
fileContent: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const compilerName = getWebpackLoaderCompilerName(this);
|
const compilerName = getWebpackLoaderCompilerName(this);
|
||||||
const callback = this.async();
|
const callback = this.async();
|
||||||
|
@ -143,11 +143,15 @@ export async function mdxLoader(
|
||||||
|
|
||||||
ensureMarkdownConfig(reqOptions);
|
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 mdxFrontMatter = validateMDXFrontMatter(frontMatter.mdx);
|
||||||
|
|
||||||
const preprocessedContent = preprocessor({
|
const preprocessedContent = preprocessor({
|
||||||
fileContent: fileString,
|
fileContent,
|
||||||
filePath,
|
filePath,
|
||||||
admonitions: reqOptions.admonitions,
|
admonitions: reqOptions.admonitions,
|
||||||
markdownConfig: reqOptions.markdownConfig,
|
markdownConfig: reqOptions.markdownConfig,
|
||||||
|
|
|
@ -165,6 +165,7 @@ async function createProcessorFactory() {
|
||||||
|
|
||||||
const mdxProcessor = createMdxProcessor({
|
const mdxProcessor = createMdxProcessor({
|
||||||
...processorOptions,
|
...processorOptions,
|
||||||
|
remarkRehypeOptions: options.markdownConfig.remarkRehypeOptions,
|
||||||
format,
|
format,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -12,17 +12,17 @@ exports[`transformAsset plugin pathname protocol 1`] = `
|
||||||
exports[`transformAsset plugin transform md links to <a /> 1`] = `
|
exports[`transformAsset plugin transform md links to <a /> 1`] = `
|
||||||
"[asset](https://example.com/asset.pdf)
|
"[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)
|
[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)
|
[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>
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -73,6 +73,34 @@ async function toAssetRequireNode(
|
||||||
value: '_blank',
|
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({
|
attributes.push({
|
||||||
type: 'mdxJsxAttribute',
|
type: 'mdxJsxAttribute',
|
||||||
name: 'href',
|
name: 'href',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/module-type-aliases",
|
"name": "@docusaurus/module-type-aliases",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Docusaurus module type aliases.",
|
"description": "Docusaurus module type aliases.",
|
||||||
"types": "./src/index.d.ts",
|
"types": "./src/index.d.ts",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/react-loadable": "5.5.2",
|
"@docusaurus/react-loadable": "5.5.2",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@types/history": "^4.7.11",
|
"@types/history": "^4.7.11",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"@types/react-router-config": "*",
|
"@types/react-router-config": "*",
|
||||||
|
|
|
@ -260,6 +260,15 @@ declare module '@docusaurus/useRouteContext' {
|
||||||
export default function useRouteContext(): PluginRouteContext;
|
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' {
|
declare module '@docusaurus/useIsBrowser' {
|
||||||
export default function useIsBrowser(): boolean;
|
export default function useIsBrowser(): boolean;
|
||||||
}
|
}
|
||||||
|
@ -356,7 +365,9 @@ declare module '@docusaurus/useGlobalData' {
|
||||||
declare module '*.svg' {
|
declare module '*.svg' {
|
||||||
import type {ComponentType, SVGProps} from 'react';
|
import type {ComponentType, SVGProps} from 'react';
|
||||||
|
|
||||||
const ReactComponent: ComponentType<SVGProps<SVGSVGElement>>;
|
const ReactComponent: ComponentType<
|
||||||
|
SVGProps<SVGSVGElement> & {title?: string}
|
||||||
|
>;
|
||||||
|
|
||||||
export default ReactComponent;
|
export default ReactComponent;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/plugin-client-redirects",
|
"name": "@docusaurus/plugin-client-redirects",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Client redirects plugin for Docusaurus.",
|
"description": "Client redirects plugin for Docusaurus.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
@ -18,18 +18,18 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/logger": "3.0.1",
|
"@docusaurus/logger": "3.1.0",
|
||||||
"@docusaurus/utils": "3.0.1",
|
"@docusaurus/utils": "3.1.0",
|
||||||
"@docusaurus/utils-common": "3.0.1",
|
"@docusaurus/utils-common": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"eta": "^2.2.0",
|
"eta": "^2.2.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/types": "3.0.1"
|
"@docusaurus/types": "3.1.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/plugin-content-blog",
|
"name": "@docusaurus/plugin-content-blog",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Blog plugin for Docusaurus.",
|
"description": "Blog plugin for Docusaurus.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "src/plugin-content-blog.d.ts",
|
"types": "src/plugin-content-blog.d.ts",
|
||||||
|
@ -19,13 +19,13 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/logger": "3.0.1",
|
"@docusaurus/logger": "3.1.0",
|
||||||
"@docusaurus/mdx-loader": "3.0.1",
|
"@docusaurus/mdx-loader": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@docusaurus/utils": "3.0.1",
|
"@docusaurus/utils": "3.1.0",
|
||||||
"@docusaurus/utils-common": "3.0.1",
|
"@docusaurus/utils-common": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
"feed": "^4.2.2",
|
"feed": "^4.2.2",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
|
|
|
@ -19,6 +19,7 @@ describe('getBlogPostAuthors', () => {
|
||||||
getBlogPostAuthors({
|
getBlogPostAuthors({
|
||||||
frontMatter: {},
|
frontMatter: {},
|
||||||
authorsMap: undefined,
|
authorsMap: undefined,
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toEqual([]);
|
).toEqual([]);
|
||||||
expect(
|
expect(
|
||||||
|
@ -27,6 +28,7 @@ describe('getBlogPostAuthors', () => {
|
||||||
authors: [],
|
authors: [],
|
||||||
},
|
},
|
||||||
authorsMap: undefined,
|
authorsMap: undefined,
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toEqual([]);
|
).toEqual([]);
|
||||||
});
|
});
|
||||||
|
@ -38,6 +40,7 @@ describe('getBlogPostAuthors', () => {
|
||||||
author: 'Sébastien Lorber',
|
author: 'Sébastien Lorber',
|
||||||
},
|
},
|
||||||
authorsMap: undefined,
|
authorsMap: undefined,
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toEqual([{name: 'Sébastien Lorber'}]);
|
).toEqual([{name: 'Sébastien Lorber'}]);
|
||||||
expect(
|
expect(
|
||||||
|
@ -46,6 +49,7 @@ describe('getBlogPostAuthors', () => {
|
||||||
authorTitle: 'maintainer',
|
authorTitle: 'maintainer',
|
||||||
},
|
},
|
||||||
authorsMap: undefined,
|
authorsMap: undefined,
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toEqual([{title: 'maintainer'}]);
|
).toEqual([{title: 'maintainer'}]);
|
||||||
expect(
|
expect(
|
||||||
|
@ -54,8 +58,27 @@ describe('getBlogPostAuthors', () => {
|
||||||
authorImageURL: 'https://github.com/slorber.png',
|
authorImageURL: 'https://github.com/slorber.png',
|
||||||
},
|
},
|
||||||
authorsMap: undefined,
|
authorsMap: undefined,
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toEqual([{imageURL: 'https://github.com/slorber.png'}]);
|
).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(
|
expect(
|
||||||
getBlogPostAuthors({
|
getBlogPostAuthors({
|
||||||
frontMatter: {
|
frontMatter: {
|
||||||
|
@ -68,6 +91,7 @@ describe('getBlogPostAuthors', () => {
|
||||||
authorURL: 'https://github.com/slorber2',
|
authorURL: 'https://github.com/slorber2',
|
||||||
},
|
},
|
||||||
authorsMap: undefined,
|
authorsMap: undefined,
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toEqual([
|
).toEqual([
|
||||||
{
|
{
|
||||||
|
@ -86,8 +110,69 @@ describe('getBlogPostAuthors', () => {
|
||||||
authors: 'slorber',
|
authors: 'slorber',
|
||||||
},
|
},
|
||||||
authorsMap: {slorber: {name: 'Sébastien Lorber'}},
|
authorsMap: {slorber: {name: 'Sébastien Lorber'}},
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toEqual([{key: 'slorber', name: 'Sébastien Lorber'}]);
|
).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[]', () => {
|
it('can read authors string[]', () => {
|
||||||
|
@ -100,6 +185,7 @@ describe('getBlogPostAuthors', () => {
|
||||||
slorber: {name: 'Sébastien Lorber', title: 'maintainer'},
|
slorber: {name: 'Sébastien Lorber', title: 'maintainer'},
|
||||||
yangshun: {name: 'Yangshun Tay'},
|
yangshun: {name: 'Yangshun Tay'},
|
||||||
},
|
},
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toEqual([
|
).toEqual([
|
||||||
{key: 'slorber', name: 'Sébastien Lorber', title: 'maintainer'},
|
{key: 'slorber', name: 'Sébastien Lorber', title: 'maintainer'},
|
||||||
|
@ -114,6 +200,7 @@ describe('getBlogPostAuthors', () => {
|
||||||
authors: {name: 'Sébastien Lorber', title: 'maintainer'},
|
authors: {name: 'Sébastien Lorber', title: 'maintainer'},
|
||||||
},
|
},
|
||||||
authorsMap: undefined,
|
authorsMap: undefined,
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toEqual([{name: 'Sébastien Lorber', title: 'maintainer'}]);
|
).toEqual([{name: 'Sébastien Lorber', title: 'maintainer'}]);
|
||||||
});
|
});
|
||||||
|
@ -128,6 +215,7 @@ describe('getBlogPostAuthors', () => {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
authorsMap: undefined,
|
authorsMap: undefined,
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toEqual([
|
).toEqual([
|
||||||
{name: 'Sébastien Lorber', title: 'maintainer'},
|
{name: 'Sébastien Lorber', title: 'maintainer'},
|
||||||
|
@ -153,6 +241,7 @@ describe('getBlogPostAuthors', () => {
|
||||||
slorber: {name: 'Sébastien Lorber', title: 'maintainer'},
|
slorber: {name: 'Sébastien Lorber', title: 'maintainer'},
|
||||||
yangshun: {name: 'Yangshun Tay', title: 'Yangshun title original'},
|
yangshun: {name: 'Yangshun Tay', title: 'Yangshun title original'},
|
||||||
},
|
},
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toEqual([
|
).toEqual([
|
||||||
{key: 'slorber', name: 'Sébastien Lorber', title: 'maintainer'},
|
{key: 'slorber', name: 'Sébastien Lorber', title: 'maintainer'},
|
||||||
|
@ -173,6 +262,7 @@ describe('getBlogPostAuthors', () => {
|
||||||
authors: 'slorber',
|
authors: 'slorber',
|
||||||
},
|
},
|
||||||
authorsMap: undefined,
|
authorsMap: undefined,
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingInlineSnapshot(`
|
).toThrowErrorMatchingInlineSnapshot(`
|
||||||
"Can't reference blog post authors by a key (such as 'slorber') because no authors map file could be loaded.
|
"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',
|
authors: 'slorber',
|
||||||
},
|
},
|
||||||
authorsMap: {},
|
authorsMap: {},
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingInlineSnapshot(`
|
).toThrowErrorMatchingInlineSnapshot(`
|
||||||
"Can't reference blog post authors by a key (such as 'slorber') because no authors map file could be loaded.
|
"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'},
|
yangshun: {name: 'Yangshun Tay'},
|
||||||
jmarcey: {name: 'Joel Marcey'},
|
jmarcey: {name: 'Joel Marcey'},
|
||||||
},
|
},
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingInlineSnapshot(`
|
).toThrowErrorMatchingInlineSnapshot(`
|
||||||
"Blog author with key "slorber" not found in the authors map file.
|
"Blog author with key "slorber" not found in the authors map file.
|
||||||
|
@ -225,6 +317,7 @@ describe('getBlogPostAuthors', () => {
|
||||||
yangshun: {name: 'Yangshun Tay'},
|
yangshun: {name: 'Yangshun Tay'},
|
||||||
jmarcey: {name: 'Joel Marcey'},
|
jmarcey: {name: 'Joel Marcey'},
|
||||||
},
|
},
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingInlineSnapshot(`
|
).toThrowErrorMatchingInlineSnapshot(`
|
||||||
"Blog author with key "slorber" not found in the authors map file.
|
"Blog author with key "slorber" not found in the authors map file.
|
||||||
|
@ -245,6 +338,7 @@ describe('getBlogPostAuthors', () => {
|
||||||
yangshun: {name: 'Yangshun Tay'},
|
yangshun: {name: 'Yangshun Tay'},
|
||||||
jmarcey: {name: 'Joel Marcey'},
|
jmarcey: {name: 'Joel Marcey'},
|
||||||
},
|
},
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingInlineSnapshot(`
|
).toThrowErrorMatchingInlineSnapshot(`
|
||||||
"Blog author with key "slorber" not found in the authors map file.
|
"Blog author with key "slorber" not found in the authors map file.
|
||||||
|
@ -262,6 +356,7 @@ describe('getBlogPostAuthors', () => {
|
||||||
author: 'Yangshun Tay',
|
author: 'Yangshun Tay',
|
||||||
},
|
},
|
||||||
authorsMap: undefined,
|
authorsMap: undefined,
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingInlineSnapshot(`
|
).toThrowErrorMatchingInlineSnapshot(`
|
||||||
"To declare blog post authors, use the 'authors' front matter in priority.
|
"To declare blog post authors, use the 'authors' front matter in priority.
|
||||||
|
@ -275,6 +370,7 @@ describe('getBlogPostAuthors', () => {
|
||||||
author_title: 'legacy title',
|
author_title: 'legacy title',
|
||||||
},
|
},
|
||||||
authorsMap: {slorber: {name: 'Sébastien Lorber'}},
|
authorsMap: {slorber: {name: 'Sébastien Lorber'}},
|
||||||
|
baseUrl: '/',
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingInlineSnapshot(`
|
).toThrowErrorMatchingInlineSnapshot(`
|
||||||
"To declare blog post authors, use the 'authors' front matter in priority.
|
"To declare blog post authors, use the 'authors' front matter in priority.
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import {jest} from '@jest/globals';
|
import {jest} from '@jest/globals';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
import {DEFAULT_PARSE_FRONT_MATTER} from '@docusaurus/utils';
|
||||||
import {DEFAULT_OPTIONS} from '../options';
|
import {DEFAULT_OPTIONS} from '../options';
|
||||||
import {generateBlogPosts} from '../blogUtils';
|
import {generateBlogPosts} from '../blogUtils';
|
||||||
import {createBlogFeedFiles} from '../feed';
|
import {createBlogFeedFiles} from '../feed';
|
||||||
|
@ -31,6 +32,8 @@ const DefaultI18N: I18n = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const markdown = {parseFrontMatter: DEFAULT_PARSE_FRONT_MATTER};
|
||||||
|
|
||||||
function getBlogContentPaths(siteDir: string): BlogContentPaths {
|
function getBlogContentPaths(siteDir: string): BlogContentPaths {
|
||||||
return {
|
return {
|
||||||
contentPath: path.resolve(siteDir, 'blog'),
|
contentPath: path.resolve(siteDir, 'blog'),
|
||||||
|
@ -72,6 +75,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
url: 'https://docusaurus.io',
|
url: 'https://docusaurus.io',
|
||||||
favicon: 'image/favicon.ico',
|
favicon: 'image/favicon.ico',
|
||||||
|
markdown,
|
||||||
};
|
};
|
||||||
const outDir = path.join(siteDir, 'build-snap');
|
const outDir = path.join(siteDir, 'build-snap');
|
||||||
|
|
||||||
|
@ -110,6 +114,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
|
||||||
baseUrl: '/myBaseUrl/',
|
baseUrl: '/myBaseUrl/',
|
||||||
url: 'https://docusaurus.io',
|
url: 'https://docusaurus.io',
|
||||||
favicon: 'image/favicon.ico',
|
favicon: 'image/favicon.ico',
|
||||||
|
markdown,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build is quite difficult to mock, so we built the blog beforehand and
|
// 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/',
|
baseUrl: '/myBaseUrl/',
|
||||||
url: 'https://docusaurus.io',
|
url: 'https://docusaurus.io',
|
||||||
favicon: 'image/favicon.ico',
|
favicon: 'image/favicon.ico',
|
||||||
|
markdown,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build is quite difficult to mock, so we built the blog beforehand and
|
// 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/',
|
baseUrl: '/myBaseUrl/',
|
||||||
url: 'https://docusaurus.io',
|
url: 'https://docusaurus.io',
|
||||||
favicon: 'image/favicon.ico',
|
favicon: 'image/favicon.ico',
|
||||||
|
markdown,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build is quite difficult to mock, so we built the blog beforehand and
|
// Build is quite difficult to mock, so we built the blog beforehand and
|
||||||
|
|
|
@ -16,6 +16,7 @@ import type {
|
||||||
LoadContext,
|
LoadContext,
|
||||||
I18n,
|
I18n,
|
||||||
Validate,
|
Validate,
|
||||||
|
MarkdownConfig,
|
||||||
} from '@docusaurus/types';
|
} from '@docusaurus/types';
|
||||||
import type {
|
import type {
|
||||||
BlogPost,
|
BlogPost,
|
||||||
|
@ -24,6 +25,24 @@ import type {
|
||||||
EditUrlFunction,
|
EditUrlFunction,
|
||||||
} from '@docusaurus/plugin-content-blog';
|
} 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(
|
function findByTitle(
|
||||||
blogPosts: BlogPost[],
|
blogPosts: BlogPost[],
|
||||||
title: string,
|
title: string,
|
||||||
|
@ -81,6 +100,7 @@ const getPlugin = async (
|
||||||
title: 'Hello',
|
title: 'Hello',
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
url: 'https://docusaurus.io',
|
url: 'https://docusaurus.io',
|
||||||
|
markdown,
|
||||||
} as DocusaurusConfig;
|
} as DocusaurusConfig;
|
||||||
return pluginContentBlog(
|
return pluginContentBlog(
|
||||||
{
|
{
|
||||||
|
@ -242,6 +262,7 @@ describe('blog plugin', () => {
|
||||||
slug: '/hey/my super path/héllô',
|
slug: '/hey/my super path/héllô',
|
||||||
title: 'Complex Slug',
|
title: 'Complex Slug',
|
||||||
tags: ['date', 'complex'],
|
tags: ['date', 'complex'],
|
||||||
|
custom_frontMatter: 'added by parseFrontMatter',
|
||||||
},
|
},
|
||||||
tags: [
|
tags: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {getDataFileData} from '@docusaurus/utils';
|
import {getDataFileData, normalizeUrl} from '@docusaurus/utils';
|
||||||
import {Joi, URISchema} from '@docusaurus/utils-validation';
|
import {Joi, URISchema} from '@docusaurus/utils-validation';
|
||||||
import type {BlogContentPaths} from './types';
|
import type {BlogContentPaths} from './types';
|
||||||
import type {
|
import type {
|
||||||
|
@ -68,17 +68,37 @@ export async function getAuthorsMap(params: {
|
||||||
type AuthorsParam = {
|
type AuthorsParam = {
|
||||||
frontMatter: BlogPostFrontMatter;
|
frontMatter: BlogPostFrontMatter;
|
||||||
authorsMap: AuthorsMap | undefined;
|
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
|
// Legacy v1/early-v2 front matter fields
|
||||||
// We may want to deprecate those in favor of using only frontMatter.authors
|
// We may want to deprecate those in favor of using only frontMatter.authors
|
||||||
function getFrontMatterAuthorLegacy(
|
function getFrontMatterAuthorLegacy({
|
||||||
frontMatter: BlogPostFrontMatter,
|
baseUrl,
|
||||||
): Author | undefined {
|
frontMatter,
|
||||||
|
}: {
|
||||||
|
baseUrl: string;
|
||||||
|
frontMatter: BlogPostFrontMatter;
|
||||||
|
}): Author | undefined {
|
||||||
const name = frontMatter.author;
|
const name = frontMatter.author;
|
||||||
const title = frontMatter.author_title ?? frontMatter.authorTitle;
|
const title = frontMatter.author_title ?? frontMatter.authorTitle;
|
||||||
const url = frontMatter.author_url ?? frontMatter.authorURL;
|
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) {
|
if (name || title || url || imageURL) {
|
||||||
return {
|
return {
|
||||||
|
@ -148,14 +168,26 @@ ${Object.keys(authorsMap)
|
||||||
return frontMatterAuthors.map(toAuthor);
|
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[] {
|
export function getBlogPostAuthors(params: AuthorsParam): Author[] {
|
||||||
const authorLegacy = getFrontMatterAuthorLegacy(params.frontMatter);
|
const authorLegacy = getFrontMatterAuthorLegacy(params);
|
||||||
const authors = getFrontMatterAuthors(params);
|
const authors = getFrontMatterAuthors(params);
|
||||||
|
|
||||||
|
const updatedAuthors = fixAuthorImageBaseURL(authors, params);
|
||||||
|
|
||||||
if (authorLegacy) {
|
if (authorLegacy) {
|
||||||
// Technically, we could allow mixing legacy/authors front matter, but do we
|
// Technically, we could allow mixing legacy/authors front matter, but do we
|
||||||
// really want to?
|
// really want to?
|
||||||
if (authors.length > 0) {
|
if (updatedAuthors.length > 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`To declare blog post authors, use the 'authors' front matter in priority.
|
`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.`,
|
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 [authorLegacy];
|
||||||
}
|
}
|
||||||
|
|
||||||
return authors;
|
return updatedAuthors;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import _ from 'lodash';
|
||||||
import logger from '@docusaurus/logger';
|
import logger from '@docusaurus/logger';
|
||||||
import readingTime from 'reading-time';
|
import readingTime from 'reading-time';
|
||||||
import {
|
import {
|
||||||
parseMarkdownString,
|
parseMarkdownFile,
|
||||||
normalizeUrl,
|
normalizeUrl,
|
||||||
aliasedSitePath,
|
aliasedSitePath,
|
||||||
getEditUrl,
|
getEditUrl,
|
||||||
|
@ -29,7 +29,7 @@ import {
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {validateBlogPostFrontMatter} from './frontMatter';
|
import {validateBlogPostFrontMatter} from './frontMatter';
|
||||||
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
|
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
|
||||||
import type {LoadContext} from '@docusaurus/types';
|
import type {LoadContext, ParseFrontMatter} from '@docusaurus/types';
|
||||||
import type {
|
import type {
|
||||||
PluginOptions,
|
PluginOptions,
|
||||||
ReadingTimeFunction,
|
ReadingTimeFunction,
|
||||||
|
@ -180,10 +180,19 @@ function formatBlogPostDate(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) {
|
async function parseBlogPostMarkdownFile({
|
||||||
const markdownString = await fs.readFile(blogSourceAbsolute, 'utf-8');
|
filePath,
|
||||||
|
parseFrontMatter,
|
||||||
|
}: {
|
||||||
|
filePath: string;
|
||||||
|
parseFrontMatter: ParseFrontMatter;
|
||||||
|
}) {
|
||||||
|
const fileContent = await fs.readFile(filePath, 'utf-8');
|
||||||
try {
|
try {
|
||||||
const result = parseMarkdownString(markdownString, {
|
const result = await parseMarkdownFile({
|
||||||
|
filePath,
|
||||||
|
fileContent,
|
||||||
|
parseFrontMatter,
|
||||||
removeContentTitle: true,
|
removeContentTitle: true,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
@ -191,7 +200,7 @@ async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) {
|
||||||
frontMatter: validateBlogPostFrontMatter(result.frontMatter),
|
frontMatter: validateBlogPostFrontMatter(result.frontMatter),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error`Error while parsing blog post file path=${blogSourceAbsolute}.`;
|
logger.error`Error while parsing blog post file path=${filePath}.`;
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,7 +216,10 @@ async function processBlogSourceFile(
|
||||||
authorsMap?: AuthorsMap,
|
authorsMap?: AuthorsMap,
|
||||||
): Promise<BlogPost | undefined> {
|
): Promise<BlogPost | undefined> {
|
||||||
const {
|
const {
|
||||||
siteConfig: {baseUrl},
|
siteConfig: {
|
||||||
|
baseUrl,
|
||||||
|
markdown: {parseFrontMatter},
|
||||||
|
},
|
||||||
siteDir,
|
siteDir,
|
||||||
i18n,
|
i18n,
|
||||||
} = context;
|
} = context;
|
||||||
|
@ -228,7 +240,10 @@ async function processBlogSourceFile(
|
||||||
const blogSourceAbsolute = path.join(blogDirPath, blogSourceRelative);
|
const blogSourceAbsolute = path.join(blogDirPath, blogSourceRelative);
|
||||||
|
|
||||||
const {frontMatter, content, contentTitle, excerpt} =
|
const {frontMatter, content, contentTitle, excerpt} =
|
||||||
await parseBlogPostMarkdownFile(blogSourceAbsolute);
|
await parseBlogPostMarkdownFile({
|
||||||
|
filePath: blogSourceAbsolute,
|
||||||
|
parseFrontMatter,
|
||||||
|
});
|
||||||
|
|
||||||
const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir);
|
const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir);
|
||||||
|
|
||||||
|
@ -319,7 +334,7 @@ async function processBlogSourceFile(
|
||||||
routeBasePath,
|
routeBasePath,
|
||||||
tagsRouteBasePath,
|
tagsRouteBasePath,
|
||||||
]);
|
]);
|
||||||
const authors = getBlogPostAuthors({authorsMap, frontMatter});
|
const authors = getBlogPostAuthors({authorsMap, frontMatter, baseUrl});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: slug,
|
id: slug,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/plugin-content-docs",
|
"name": "@docusaurus/plugin-content-docs",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Docs plugin for Docusaurus.",
|
"description": "Docs plugin for Docusaurus.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
@ -35,13 +35,13 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/logger": "3.0.1",
|
"@docusaurus/logger": "3.1.0",
|
||||||
"@docusaurus/mdx-loader": "3.0.1",
|
"@docusaurus/mdx-loader": "3.1.0",
|
||||||
"@docusaurus/module-type-aliases": "3.0.1",
|
"@docusaurus/module-type-aliases": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@docusaurus/utils": "3.0.1",
|
"@docusaurus/utils": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"@types/react-router-config": "^5.0.7",
|
"@types/react-router-config": "^5.0.7",
|
||||||
"combine-promises": "^1.1.0",
|
"combine-promises": "^1.1.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
|
|
|
@ -11,4 +11,16 @@ module.exports = {
|
||||||
url: 'https://your-docusaurus-site.example.com',
|
url: 'https://your-docusaurus-site.example.com',
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
favicon: 'img/favicon.ico',
|
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;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -463,7 +463,7 @@ exports[`simple website content: data 1`] = `
|
||||||
"frontMatter": {
|
"frontMatter": {
|
||||||
"title": "Custom Last Update",
|
"title": "Custom Last Update",
|
||||||
"last_update": {
|
"last_update": {
|
||||||
"author": "Custom Author",
|
"author": "Custom Author (processed by parseFrontMatter)",
|
||||||
"date": "1/1/2000"
|
"date": "1/1/2000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -686,7 +686,7 @@ exports[`simple website content: data 1`] = `
|
||||||
"frontMatter": {
|
"frontMatter": {
|
||||||
"title": "Last Update Author Only",
|
"title": "Last Update Author Only",
|
||||||
"last_update": {
|
"last_update": {
|
||||||
"author": "Custom Author"
|
"author": "Custom Author (processed by parseFrontMatter)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}",
|
}",
|
||||||
|
|
|
@ -567,14 +567,14 @@ describe('simple site', () => {
|
||||||
description: 'Custom last update',
|
description: 'Custom last update',
|
||||||
frontMatter: {
|
frontMatter: {
|
||||||
last_update: {
|
last_update: {
|
||||||
author: 'Custom Author',
|
author: 'Custom Author (processed by parseFrontMatter)',
|
||||||
date: '1/1/2000',
|
date: '1/1/2000',
|
||||||
},
|
},
|
||||||
title: 'Custom Last Update',
|
title: 'Custom Last Update',
|
||||||
},
|
},
|
||||||
lastUpdatedAt: new Date('1/1/2000').getTime() / 1000,
|
lastUpdatedAt: new Date('1/1/2000').getTime() / 1000,
|
||||||
formattedLastUpdatedAt: 'Jan 1, 2000',
|
formattedLastUpdatedAt: 'Jan 1, 2000',
|
||||||
lastUpdatedBy: 'Custom Author',
|
lastUpdatedBy: 'Custom Author (processed by parseFrontMatter)',
|
||||||
sidebarPosition: undefined,
|
sidebarPosition: undefined,
|
||||||
tags: [],
|
tags: [],
|
||||||
unlisted: false,
|
unlisted: false,
|
||||||
|
@ -607,13 +607,13 @@ describe('simple site', () => {
|
||||||
description: 'Only custom author, so it will still use the date from Git',
|
description: 'Only custom author, so it will still use the date from Git',
|
||||||
frontMatter: {
|
frontMatter: {
|
||||||
last_update: {
|
last_update: {
|
||||||
author: 'Custom Author',
|
author: 'Custom Author (processed by parseFrontMatter)',
|
||||||
},
|
},
|
||||||
title: 'Last Update Author Only',
|
title: 'Last Update Author Only',
|
||||||
},
|
},
|
||||||
lastUpdatedAt: 1539502055,
|
lastUpdatedAt: 1539502055,
|
||||||
formattedLastUpdatedAt: 'Oct 14, 2018',
|
formattedLastUpdatedAt: 'Oct 14, 2018',
|
||||||
lastUpdatedBy: 'Custom Author',
|
lastUpdatedBy: 'Custom Author (processed by parseFrontMatter)',
|
||||||
sidebarPosition: undefined,
|
sidebarPosition: undefined,
|
||||||
tags: [],
|
tags: [],
|
||||||
unlisted: false,
|
unlisted: false,
|
||||||
|
@ -685,7 +685,7 @@ describe('simple site', () => {
|
||||||
description: 'Custom last update',
|
description: 'Custom last update',
|
||||||
frontMatter: {
|
frontMatter: {
|
||||||
last_update: {
|
last_update: {
|
||||||
author: 'Custom Author',
|
author: 'Custom Author (processed by parseFrontMatter)',
|
||||||
date: '1/1/2000',
|
date: '1/1/2000',
|
||||||
},
|
},
|
||||||
title: 'Custom Last Update',
|
title: 'Custom Last Update',
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
getFolderContainingFile,
|
getFolderContainingFile,
|
||||||
getContentPathList,
|
getContentPathList,
|
||||||
normalizeUrl,
|
normalizeUrl,
|
||||||
parseMarkdownString,
|
parseMarkdownFile,
|
||||||
posixPath,
|
posixPath,
|
||||||
Globby,
|
Globby,
|
||||||
normalizeFrontMatterTags,
|
normalizeFrontMatterTags,
|
||||||
|
@ -140,13 +140,23 @@ async function doProcessDocMetadata({
|
||||||
env: DocEnv;
|
env: DocEnv;
|
||||||
}): Promise<DocMetadataBase> {
|
}): Promise<DocMetadataBase> {
|
||||||
const {source, content, contentPath, filePath} = docFile;
|
const {source, content, contentPath, filePath} = docFile;
|
||||||
const {siteDir, i18n} = context;
|
const {
|
||||||
|
siteDir,
|
||||||
|
i18n,
|
||||||
|
siteConfig: {
|
||||||
|
markdown: {parseFrontMatter},
|
||||||
|
},
|
||||||
|
} = context;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
frontMatter: unsafeFrontMatter,
|
frontMatter: unsafeFrontMatter,
|
||||||
contentTitle,
|
contentTitle,
|
||||||
excerpt,
|
excerpt,
|
||||||
} = parseMarkdownString(content);
|
} = await parseMarkdownFile({
|
||||||
|
filePath,
|
||||||
|
fileContent: content,
|
||||||
|
parseFrontMatter,
|
||||||
|
});
|
||||||
const frontMatter = validateDocFrontMatter(unsafeFrontMatter);
|
const frontMatter = validateDocFrontMatter(unsafeFrontMatter);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/plugin-content-pages",
|
"name": "@docusaurus/plugin-content-pages",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Pages plugin for Docusaurus.",
|
"description": "Pages plugin for Docusaurus.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "src/plugin-content-pages.d.ts",
|
"types": "src/plugin-content-pages.d.ts",
|
||||||
|
@ -18,11 +18,11 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/mdx-loader": "3.0.1",
|
"@docusaurus/mdx-loader": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@docusaurus/utils": "3.0.1",
|
"@docusaurus/utils": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"tslib": "^2.6.0",
|
"tslib": "^2.6.0",
|
||||||
"webpack": "^5.88.1"
|
"webpack": "^5.88.1"
|
||||||
|
|
|
@ -11,4 +11,11 @@ module.exports = {
|
||||||
url: 'https://your-docusaurus-site.example.com',
|
url: 'https://your-docusaurus-site.example.com',
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
favicon: 'img/favicon.ico',
|
favicon: 'img/favicon.ico',
|
||||||
|
markdown: {
|
||||||
|
parseFrontMatter: async (params) => {
|
||||||
|
const result = await params.defaultParseFrontMatter(params);
|
||||||
|
result.frontMatter.custom_frontMatter = 'added by parseFrontMatter';
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,9 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = `
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Markdown index page",
|
"description": "Markdown index page",
|
||||||
"frontMatter": {},
|
"frontMatter": {
|
||||||
|
"custom_frontMatter": "added by parseFrontMatter",
|
||||||
|
},
|
||||||
"permalink": "/hello/",
|
"permalink": "/hello/",
|
||||||
"source": "@site/src/pages/hello/index.md",
|
"source": "@site/src/pages/hello/index.md",
|
||||||
"title": "Index",
|
"title": "Index",
|
||||||
|
@ -24,6 +26,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = `
|
||||||
{
|
{
|
||||||
"description": "my MDX page",
|
"description": "my MDX page",
|
||||||
"frontMatter": {
|
"frontMatter": {
|
||||||
|
"custom_frontMatter": "added by parseFrontMatter",
|
||||||
"description": "my MDX page",
|
"description": "my MDX page",
|
||||||
"title": "MDX page",
|
"title": "MDX page",
|
||||||
},
|
},
|
||||||
|
@ -40,7 +43,9 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = `
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "translated Markdown page",
|
"description": "translated Markdown page",
|
||||||
"frontMatter": {},
|
"frontMatter": {
|
||||||
|
"custom_frontMatter": "added by parseFrontMatter",
|
||||||
|
},
|
||||||
"permalink": "/hello/translatedMd",
|
"permalink": "/hello/translatedMd",
|
||||||
"source": "@site/src/pages/hello/translatedMd.md",
|
"source": "@site/src/pages/hello/translatedMd.md",
|
||||||
"title": undefined,
|
"title": undefined,
|
||||||
|
@ -69,7 +74,9 @@ exports[`docusaurus-plugin-content-pages loads simple pages with french translat
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Markdown index page",
|
"description": "Markdown index page",
|
||||||
"frontMatter": {},
|
"frontMatter": {
|
||||||
|
"custom_frontMatter": "added by parseFrontMatter",
|
||||||
|
},
|
||||||
"permalink": "/fr/hello/",
|
"permalink": "/fr/hello/",
|
||||||
"source": "@site/src/pages/hello/index.md",
|
"source": "@site/src/pages/hello/index.md",
|
||||||
"title": "Index",
|
"title": "Index",
|
||||||
|
@ -79,6 +86,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages with french translat
|
||||||
{
|
{
|
||||||
"description": "my MDX page",
|
"description": "my MDX page",
|
||||||
"frontMatter": {
|
"frontMatter": {
|
||||||
|
"custom_frontMatter": "added by parseFrontMatter",
|
||||||
"description": "my MDX page",
|
"description": "my MDX page",
|
||||||
"title": "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)",
|
"description": "translated Markdown page (fr)",
|
||||||
"frontMatter": {},
|
"frontMatter": {
|
||||||
|
"custom_frontMatter": "added by parseFrontMatter",
|
||||||
|
},
|
||||||
"permalink": "/fr/hello/translatedMd",
|
"permalink": "/fr/hello/translatedMd",
|
||||||
"source": "@site/i18n/fr/docusaurus-plugin-content-pages/hello/translatedMd.md",
|
"source": "@site/i18n/fr/docusaurus-plugin-content-pages/hello/translatedMd.md",
|
||||||
"title": undefined,
|
"title": undefined,
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
createAbsoluteFilePathMatcher,
|
createAbsoluteFilePathMatcher,
|
||||||
normalizeUrl,
|
normalizeUrl,
|
||||||
DEFAULT_PLUGIN_ID,
|
DEFAULT_PLUGIN_ID,
|
||||||
parseMarkdownString,
|
parseMarkdownFile,
|
||||||
isUnlisted,
|
isUnlisted,
|
||||||
isDraft,
|
isDraft,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
|
@ -113,7 +113,11 @@ export default function pluginContentPages(
|
||||||
frontMatter: unsafeFrontMatter,
|
frontMatter: unsafeFrontMatter,
|
||||||
contentTitle,
|
contentTitle,
|
||||||
excerpt,
|
excerpt,
|
||||||
} = parseMarkdownString(content);
|
} = await parseMarkdownFile({
|
||||||
|
filePath: source,
|
||||||
|
fileContent: content,
|
||||||
|
parseFrontMatter: siteConfig.markdown.parseFrontMatter,
|
||||||
|
});
|
||||||
const frontMatter = validatePageFrontMatter(unsafeFrontMatter);
|
const frontMatter = validatePageFrontMatter(unsafeFrontMatter);
|
||||||
|
|
||||||
if (isDraft({frontMatter})) {
|
if (isDraft({frontMatter})) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/plugin-debug",
|
"name": "@docusaurus/plugin-debug",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Debug plugin for Docusaurus.",
|
"description": "Debug plugin for Docusaurus.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "src/plugin-debug.d.ts",
|
"types": "src/plugin-debug.d.ts",
|
||||||
|
@ -20,9 +20,9 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@docusaurus/utils": "3.0.1",
|
"@docusaurus/utils": "3.1.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"react-json-view-lite": "^1.2.0",
|
"react-json-view-lite": "^1.2.0",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/plugin-google-analytics",
|
"name": "@docusaurus/plugin-google-analytics",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Global analytics (analytics.js) plugin for Docusaurus.",
|
"description": "Global analytics (analytics.js) plugin for Docusaurus.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
@ -18,9 +18,9 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/plugin-google-gtag",
|
"name": "@docusaurus/plugin-google-gtag",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Global Site Tag (gtag.js) plugin for Docusaurus.",
|
"description": "Global Site Tag (gtag.js) plugin for Docusaurus.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
@ -18,9 +18,9 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"@types/gtag.js": "^0.0.12",
|
"@types/gtag.js": "^0.0.12",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/plugin-google-tag-manager",
|
"name": "@docusaurus/plugin-google-tag-manager",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Google Tag Manager (gtm.js) plugin for Docusaurus.",
|
"description": "Google Tag Manager (gtm.js) plugin for Docusaurus.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
@ -18,9 +18,9 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/plugin-ideal-image",
|
"name": "@docusaurus/plugin-ideal-image",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Docusaurus Plugin to generate an almost ideal image (responsive, lazy-loading, and low quality placeholder).",
|
"description": "Docusaurus Plugin to generate an almost ideal image (responsive, lazy-loading, and low quality placeholder).",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "src/plugin-ideal-image.d.ts",
|
"types": "src/plugin-ideal-image.d.ts",
|
||||||
|
@ -20,12 +20,12 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/lqip-loader": "3.0.1",
|
"@docusaurus/lqip-loader": "3.1.0",
|
||||||
"@docusaurus/responsive-loader": "^1.7.0",
|
"@docusaurus/responsive-loader": "^1.7.0",
|
||||||
"@docusaurus/theme-translations": "3.0.1",
|
"@docusaurus/theme-translations": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"@slorber/react-ideal-image": "^0.0.12",
|
"@slorber/react-ideal-image": "^0.0.12",
|
||||||
"react-waypoint": "^10.3.0",
|
"react-waypoint": "^10.3.0",
|
||||||
"sharp": "^0.32.3",
|
"sharp": "^0.32.3",
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
"webpack": "^5.88.1"
|
"webpack": "^5.88.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "3.0.1",
|
"@docusaurus/module-type-aliases": "3.1.0",
|
||||||
"fs-extra": "^11.1.0"
|
"fs-extra": "^11.1.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/plugin-pwa",
|
"name": "@docusaurus/plugin-pwa",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Docusaurus Plugin to add PWA support.",
|
"description": "Docusaurus Plugin to add PWA support.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "src/plugin-pwa.d.ts",
|
"types": "src/plugin-pwa.d.ts",
|
||||||
|
@ -22,12 +22,12 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.23.3",
|
"@babel/core": "^7.23.3",
|
||||||
"@babel/preset-env": "^7.23.3",
|
"@babel/preset-env": "^7.23.3",
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/theme-common": "3.0.1",
|
"@docusaurus/theme-common": "3.1.0",
|
||||||
"@docusaurus/theme-translations": "3.0.1",
|
"@docusaurus/theme-translations": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@docusaurus/utils": "3.0.1",
|
"@docusaurus/utils": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"babel-loader": "^9.1.3",
|
"babel-loader": "^9.1.3",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"core-js": "^3.31.1",
|
"core-js": "^3.31.1",
|
||||||
|
@ -36,12 +36,12 @@
|
||||||
"webpack": "^5.88.1",
|
"webpack": "^5.88.1",
|
||||||
"webpack-merge": "^5.9.0",
|
"webpack-merge": "^5.9.0",
|
||||||
"webpackbar": "^5.0.2",
|
"webpackbar": "^5.0.2",
|
||||||
"workbox-build": "^6.6.1",
|
"workbox-build": "^7.0.0",
|
||||||
"workbox-precaching": "^6.6.1",
|
"workbox-precaching": "^7.0.0",
|
||||||
"workbox-window": "^6.6.1"
|
"workbox-window": "^7.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "3.0.1",
|
"@docusaurus/module-type-aliases": "3.1.0",
|
||||||
"fs-extra": "^11.1.0"
|
"fs-extra": "^11.1.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/plugin-sitemap",
|
"name": "@docusaurus/plugin-sitemap",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Simple sitemap generation plugin for Docusaurus.",
|
"description": "Simple sitemap generation plugin for Docusaurus.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
@ -18,12 +18,12 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/logger": "3.0.1",
|
"@docusaurus/logger": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@docusaurus/utils": "3.0.1",
|
"@docusaurus/utils": "3.1.0",
|
||||||
"@docusaurus/utils-common": "3.0.1",
|
"@docusaurus/utils-common": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"sitemap": "^7.1.1",
|
"sitemap": "^7.1.1",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/preset-classic",
|
"name": "@docusaurus/preset-classic",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Classic preset for Docusaurus.",
|
"description": "Classic preset for Docusaurus.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
@ -18,19 +18,19 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/plugin-content-blog": "3.0.1",
|
"@docusaurus/plugin-content-blog": "3.1.0",
|
||||||
"@docusaurus/plugin-content-docs": "3.0.1",
|
"@docusaurus/plugin-content-docs": "3.1.0",
|
||||||
"@docusaurus/plugin-content-pages": "3.0.1",
|
"@docusaurus/plugin-content-pages": "3.1.0",
|
||||||
"@docusaurus/plugin-debug": "3.0.1",
|
"@docusaurus/plugin-debug": "3.1.0",
|
||||||
"@docusaurus/plugin-google-analytics": "3.0.1",
|
"@docusaurus/plugin-google-analytics": "3.1.0",
|
||||||
"@docusaurus/plugin-google-gtag": "3.0.1",
|
"@docusaurus/plugin-google-gtag": "3.1.0",
|
||||||
"@docusaurus/plugin-google-tag-manager": "3.0.1",
|
"@docusaurus/plugin-google-tag-manager": "3.1.0",
|
||||||
"@docusaurus/plugin-sitemap": "3.0.1",
|
"@docusaurus/plugin-sitemap": "3.1.0",
|
||||||
"@docusaurus/theme-classic": "3.0.1",
|
"@docusaurus/theme-classic": "3.1.0",
|
||||||
"@docusaurus/theme-common": "3.0.1",
|
"@docusaurus/theme-common": "3.1.0",
|
||||||
"@docusaurus/theme-search-algolia": "3.0.1",
|
"@docusaurus/theme-search-algolia": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1"
|
"@docusaurus/types": "3.1.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/remark-plugin-npm2yarn",
|
"name": "@docusaurus/remark-plugin-npm2yarn",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Remark plugin for converting npm commands to Yarn commands as tabs.",
|
"description": "Remark plugin for converting npm commands to Yarn commands as tabs.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/theme-classic",
|
"name": "@docusaurus/theme-classic",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Classic theme for Docusaurus",
|
"description": "Classic theme for Docusaurus",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "src/theme-classic.d.ts",
|
"types": "src/theme-classic.d.ts",
|
||||||
|
@ -20,18 +20,18 @@
|
||||||
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
|
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/mdx-loader": "3.0.1",
|
"@docusaurus/mdx-loader": "3.1.0",
|
||||||
"@docusaurus/module-type-aliases": "3.0.1",
|
"@docusaurus/module-type-aliases": "3.1.0",
|
||||||
"@docusaurus/plugin-content-blog": "3.0.1",
|
"@docusaurus/plugin-content-blog": "3.1.0",
|
||||||
"@docusaurus/plugin-content-docs": "3.0.1",
|
"@docusaurus/plugin-content-docs": "3.1.0",
|
||||||
"@docusaurus/plugin-content-pages": "3.0.1",
|
"@docusaurus/plugin-content-pages": "3.1.0",
|
||||||
"@docusaurus/theme-common": "3.0.1",
|
"@docusaurus/theme-common": "3.1.0",
|
||||||
"@docusaurus/theme-translations": "3.0.1",
|
"@docusaurus/theme-translations": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@docusaurus/utils": "3.0.1",
|
"@docusaurus/utils": "3.1.0",
|
||||||
"@docusaurus/utils-common": "3.0.1",
|
"@docusaurus/utils-common": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"copy-text-to-clipboard": "^3.2.0",
|
"copy-text-to-clipboard": "^3.2.0",
|
||||||
|
|
|
@ -363,6 +363,14 @@ declare module '@theme/CodeBlock' {
|
||||||
export default function CodeBlock(props: Props): JSX.Element;
|
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' {
|
declare module '@theme/CodeBlock/CopyButton' {
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly code: string;
|
readonly code: string;
|
||||||
|
|
|
@ -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} />;
|
||||||
|
}
|
|
@ -10,11 +10,13 @@ import clsx from 'clsx';
|
||||||
import {translate} from '@docusaurus/Translate';
|
import {translate} from '@docusaurus/Translate';
|
||||||
import {useThemeConfig} from '@docusaurus/theme-common';
|
import {useThemeConfig} from '@docusaurus/theme-common';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
|
import useBrokenLinks from '@docusaurus/useBrokenLinks';
|
||||||
import type {Props} from '@theme/Heading';
|
import type {Props} from '@theme/Heading';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
export default function Heading({as: As, id, ...props}: Props): JSX.Element {
|
export default function Heading({as: As, id, ...props}: Props): JSX.Element {
|
||||||
|
const brokenLinks = useBrokenLinks();
|
||||||
const {
|
const {
|
||||||
navbar: {hideOnScroll},
|
navbar: {hideOnScroll},
|
||||||
} = useThemeConfig();
|
} = useThemeConfig();
|
||||||
|
@ -23,6 +25,8 @@ export default function Heading({as: As, id, ...props}: Props): JSX.Element {
|
||||||
return <As {...props} id={undefined} />;
|
return <As {...props} id={undefined} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
brokenLinks.collectAnchor(id);
|
||||||
|
|
||||||
const anchorTitle = translate(
|
const anchorTitle = translate(
|
||||||
{
|
{
|
||||||
id: 'theme.common.headingLinkTitle',
|
id: 'theme.common.headingLinkTitle',
|
||||||
|
|
|
@ -8,15 +8,23 @@
|
||||||
import type {ComponentProps} from 'react';
|
import type {ComponentProps} from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import CodeBlock from '@theme/CodeBlock';
|
import CodeBlock from '@theme/CodeBlock';
|
||||||
|
import CodeInline from '@theme/CodeInline';
|
||||||
import type {Props} from '@theme/MDXComponents/Code';
|
import type {Props} from '@theme/MDXComponents/Code';
|
||||||
|
|
||||||
export default function MDXCode(props: Props): JSX.Element {
|
function shouldBeInline(props: Props) {
|
||||||
const shouldBeInline = React.Children.toArray(props.children).every(
|
return (
|
||||||
(el) => typeof el === 'string' && !el.includes('\n'),
|
// 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 ? (
|
export default function MDXCode(props: Props): JSX.Element {
|
||||||
<code {...props} />
|
return shouldBeInline(props) ? (
|
||||||
|
<CodeInline {...props} />
|
||||||
) : (
|
) : (
|
||||||
<CodeBlock {...(props as ComponentProps<typeof CodeBlock>)} />
|
<CodeBlock {...(props as ComponentProps<typeof CodeBlock>)} />
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/theme-common",
|
"name": "@docusaurus/theme-common",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Common code for Docusaurus themes.",
|
"description": "Common code for Docusaurus themes.",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
|
@ -30,13 +30,13 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/mdx-loader": "3.0.1",
|
"@docusaurus/mdx-loader": "3.1.0",
|
||||||
"@docusaurus/module-type-aliases": "3.0.1",
|
"@docusaurus/module-type-aliases": "3.1.0",
|
||||||
"@docusaurus/plugin-content-blog": "3.0.1",
|
"@docusaurus/plugin-content-blog": "3.1.0",
|
||||||
"@docusaurus/plugin-content-docs": "3.0.1",
|
"@docusaurus/plugin-content-docs": "3.1.0",
|
||||||
"@docusaurus/plugin-content-pages": "3.0.1",
|
"@docusaurus/plugin-content-pages": "3.1.0",
|
||||||
"@docusaurus/utils": "3.0.1",
|
"@docusaurus/utils": "3.1.0",
|
||||||
"@docusaurus/utils-common": "3.0.1",
|
"@docusaurus/utils-common": "3.1.0",
|
||||||
"@types/history": "^4.7.11",
|
"@types/history": "^4.7.11",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"@types/react-router-config": "*",
|
"@types/react-router-config": "*",
|
||||||
|
@ -47,8 +47,8 @@
|
||||||
"utility-types": "^3.10.0"
|
"utility-types": "^3.10.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21"
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,15 +17,20 @@ const windowSizes = {
|
||||||
|
|
||||||
type WindowSize = keyof typeof 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) {
|
if (!ExecutionEnvironment.canUseDOM) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'getWindowSize() should only be called after React hydration',
|
'getWindowSize() should only be called after React hydration',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return window.innerWidth > DesktopThresholdWidth
|
|
||||||
|
return window.innerWidth > desktopBreakpoint
|
||||||
? windowSizes.desktop
|
? windowSizes.desktop
|
||||||
: windowSizes.mobile;
|
: windowSizes.mobile;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +45,11 @@ function getWindowSize() {
|
||||||
* with mediaquery). We don't return `undefined` on purpose, to make it more
|
* with mediaquery). We don't return `undefined` on purpose, to make it more
|
||||||
* explicit.
|
* explicit.
|
||||||
*/
|
*/
|
||||||
export function useWindowSize(): WindowSize {
|
export function useWindowSize({
|
||||||
|
desktopBreakpoint = DesktopBreakpoint,
|
||||||
|
}: {
|
||||||
|
desktopBreakpoint?: number;
|
||||||
|
} = {}): WindowSize {
|
||||||
const [windowSize, setWindowSize] = useState<WindowSize>(
|
const [windowSize, setWindowSize] = useState<WindowSize>(
|
||||||
() =>
|
() =>
|
||||||
// super important to return a constant value to avoid hydration mismatch
|
// super important to return a constant value to avoid hydration mismatch
|
||||||
|
@ -50,7 +59,7 @@ export function useWindowSize(): WindowSize {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function updateWindowSize() {
|
function updateWindowSize() {
|
||||||
setWindowSize(getWindowSize());
|
setWindowSize(getWindowSize(desktopBreakpoint));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateWindowSize();
|
updateWindowSize();
|
||||||
|
@ -60,7 +69,7 @@ export function useWindowSize(): WindowSize {
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('resize', updateWindowSize);
|
window.removeEventListener('resize', updateWindowSize);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [desktopBreakpoint]);
|
||||||
|
|
||||||
return windowSize;
|
return windowSize;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,18 +13,31 @@ const codeBlockTitleRegex = /title=(?<quote>["'])(?<title>.*?)\1/;
|
||||||
const metastringLinesRangeRegex = /\{(?<range>[\d,-]+)\}/;
|
const metastringLinesRangeRegex = /\{(?<range>[\d,-]+)\}/;
|
||||||
|
|
||||||
// Supported types of highlight comments
|
// Supported types of highlight comments
|
||||||
const commentPatterns = {
|
const popularCommentPatterns = {
|
||||||
js: {start: '\\/\\/', end: ''},
|
js: {start: '\\/\\/', end: ''},
|
||||||
jsBlock: {start: '\\/\\*', end: '\\*\\/'},
|
jsBlock: {start: '\\/\\*', end: '\\*\\/'},
|
||||||
jsx: {start: '\\{\\s*\\/\\*', end: '\\*\\/\\s*\\}'},
|
jsx: {start: '\\{\\s*\\/\\*', end: '\\*\\/\\s*\\}'},
|
||||||
bash: {start: '#', end: ''},
|
bash: {start: '#', end: ''},
|
||||||
html: {start: '<!--', end: '-->'},
|
html: {start: '<!--', end: '-->'},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const commentPatterns = {
|
||||||
|
...popularCommentPatterns, // shallow copy is sufficient
|
||||||
|
// minor comment styles
|
||||||
lua: {start: '--', end: ''},
|
lua: {start: '--', end: ''},
|
||||||
wasm: {start: '\\;\\;', end: ''},
|
wasm: {start: '\\;\\;', end: ''},
|
||||||
tex: {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;
|
type CommentType = keyof typeof commentPatterns;
|
||||||
|
const popularCommentTypes = Object.keys(
|
||||||
|
popularCommentPatterns,
|
||||||
|
) as CommentType[];
|
||||||
|
|
||||||
export type MagicCommentConfig = {
|
export type MagicCommentConfig = {
|
||||||
className: string;
|
className: string;
|
||||||
|
@ -99,15 +112,34 @@ function getAllMagicCommentDirectiveStyles(
|
||||||
case 'wasm':
|
case 'wasm':
|
||||||
return getCommentPattern(['wasm'], magicCommentDirectives);
|
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:
|
default:
|
||||||
// All comment types except lua, wasm and matlab
|
// All popular comment types
|
||||||
return getCommentPattern(
|
return getCommentPattern(popularCommentTypes, magicCommentDirectives);
|
||||||
Object.keys(commentPatterns).filter(
|
|
||||||
(pattern) =>
|
|
||||||
!['lua', 'wasm', 'tex', 'latex', 'matlab'].includes(pattern),
|
|
||||||
) as CommentType[],
|
|
||||||
magicCommentDirectives,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/theme-live-codeblock",
|
"name": "@docusaurus/theme-live-codeblock",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Docusaurus live code block component.",
|
"description": "Docusaurus live code block component.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "src/theme-live-codeblock.d.ts",
|
"types": "src/theme-live-codeblock.d.ts",
|
||||||
|
@ -23,10 +23,10 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/theme-common": "3.0.1",
|
"@docusaurus/theme-common": "3.1.0",
|
||||||
"@docusaurus/theme-translations": "3.0.1",
|
"@docusaurus/theme-translations": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"@philpl/buble": "^0.19.7",
|
"@philpl/buble": "^0.19.7",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@types/buble": "^0.20.1"
|
"@types/buble": "^0.20.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|
|
@ -24,7 +24,8 @@ declare module '@theme/Playground' {
|
||||||
type LiveProviderProps = React.ComponentProps<typeof LiveProvider>;
|
type LiveProviderProps = React.ComponentProps<typeof LiveProvider>;
|
||||||
|
|
||||||
export interface Props extends CodeBlockProps, LiveProviderProps {
|
export interface Props extends CodeBlockProps, LiveProviderProps {
|
||||||
children: string;
|
// Allow empty live playgrounds
|
||||||
|
children?: string;
|
||||||
}
|
}
|
||||||
export default function Playground(props: LiveProviderProps): JSX.Element;
|
export default function Playground(props: LiveProviderProps): JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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({
|
export default function Playground({
|
||||||
children,
|
children,
|
||||||
transformCode,
|
transformCode,
|
||||||
|
@ -116,9 +120,9 @@ export default function Playground({
|
||||||
return (
|
return (
|
||||||
<div className={styles.playgroundContainer}>
|
<div className={styles.playgroundContainer}>
|
||||||
<LiveProvider
|
<LiveProvider
|
||||||
code={children.replace(/\n$/, '')}
|
code={children?.replace(/\n$/, '')}
|
||||||
noInline={noInline}
|
noInline={noInline}
|
||||||
transformCode={transformCode ?? ((code) => `${code};`)}
|
transformCode={transformCode ?? DEFAULT_TRANSFORM_CODE}
|
||||||
theme={prismTheme}
|
theme={prismTheme}
|
||||||
{...props}>
|
{...props}>
|
||||||
{playgroundPosition === 'top' ? (
|
{playgroundPosition === 'top' ? (
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/theme-mermaid",
|
"name": "@docusaurus/theme-mermaid",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Mermaid components for Docusaurus.",
|
"description": "Mermaid components for Docusaurus.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "src/theme-mermaid.d.ts",
|
"types": "src/theme-mermaid.d.ts",
|
||||||
|
@ -33,11 +33,11 @@
|
||||||
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
|
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/module-type-aliases": "3.0.1",
|
"@docusaurus/module-type-aliases": "3.1.0",
|
||||||
"@docusaurus/theme-common": "3.0.1",
|
"@docusaurus/theme-common": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"mermaid": "^10.4.0",
|
"mermaid": "^10.4.0",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/theme-search-algolia",
|
"name": "@docusaurus/theme-search-algolia",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Algolia search component for Docusaurus.",
|
"description": "Algolia search component for Docusaurus.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"sideEffects": [
|
"sideEffects": [
|
||||||
|
@ -34,13 +34,13 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docsearch/react": "^3.5.2",
|
"@docsearch/react": "^3.5.2",
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/logger": "3.0.1",
|
"@docusaurus/logger": "3.1.0",
|
||||||
"@docusaurus/plugin-content-docs": "3.0.1",
|
"@docusaurus/plugin-content-docs": "3.1.0",
|
||||||
"@docusaurus/theme-common": "3.0.1",
|
"@docusaurus/theme-common": "3.1.0",
|
||||||
"@docusaurus/theme-translations": "3.0.1",
|
"@docusaurus/theme-translations": "3.1.0",
|
||||||
"@docusaurus/utils": "3.0.1",
|
"@docusaurus/utils": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"algoliasearch": "^4.18.0",
|
"algoliasearch": "^4.18.0",
|
||||||
"algoliasearch-helper": "^3.13.3",
|
"algoliasearch-helper": "^3.13.3",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
"utility-types": "^3.10.0"
|
"utility-types": "^3.10.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "3.0.1"
|
"@docusaurus/module-type-aliases": "3.1.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useCallback, useMemo, useRef, useState} from 'react';
|
import React, {useCallback, useMemo, useRef, useState} from 'react';
|
||||||
|
import {createPortal} from 'react-dom';
|
||||||
import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
|
import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
|
||||||
import Head from '@docusaurus/Head';
|
import Head from '@docusaurus/Head';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
|
@ -20,7 +21,6 @@ import {
|
||||||
} from '@docusaurus/theme-search-algolia/client';
|
} from '@docusaurus/theme-search-algolia/client';
|
||||||
import Translate from '@docusaurus/Translate';
|
import Translate from '@docusaurus/Translate';
|
||||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||||
import {createPortal} from 'react-dom';
|
|
||||||
import translations from '@theme/SearchTranslations';
|
import translations from '@theme/SearchTranslations';
|
||||||
|
|
||||||
import type {AutocompleteState} from '@algolia/autocomplete-core';
|
import type {AutocompleteState} from '@algolia/autocomplete-core';
|
||||||
|
|
|
@ -4,22 +4,22 @@
|
||||||
"theme.CodeBlock.copied": "Copiado",
|
"theme.CodeBlock.copied": "Copiado",
|
||||||
"theme.CodeBlock.copy": "Copiar",
|
"theme.CodeBlock.copy": "Copiar",
|
||||||
"theme.CodeBlock.copyButtonAriaLabel": "Copiar código para a área de transferência",
|
"theme.CodeBlock.copyButtonAriaLabel": "Copiar código para a área de transferência",
|
||||||
"theme.CodeBlock.wordWrapToggle": "Toggle word wrap",
|
"theme.CodeBlock.wordWrapToggle": "Alternar quebra de linha",
|
||||||
"theme.DocSidebarItem.collapseCategoryAriaLabel": "Collapse sidebar category '{label}'",
|
"theme.DocSidebarItem.collapseCategoryAriaLabel": "Fechar a categoria lateral '{label}'",
|
||||||
"theme.DocSidebarItem.expandCategoryAriaLabel": "Expand sidebar category '{label}'",
|
"theme.DocSidebarItem.expandCategoryAriaLabel": "Expandir a categoria lateral '{label}'",
|
||||||
"theme.ErrorPageContent.title": "This page crashed.",
|
"theme.ErrorPageContent.title": "Esta página deu erro.",
|
||||||
"theme.ErrorPageContent.tryAgain": "Try again",
|
"theme.ErrorPageContent.tryAgain": "Tente novamente",
|
||||||
"theme.NavBar.navAriaLabel": "Main",
|
"theme.NavBar.navAriaLabel": "Main",
|
||||||
"theme.NotFound.p1": "Não foi possível encontrar o que você está procurando.",
|
"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.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.NotFound.title": "Página não encontrada",
|
||||||
"theme.TOCCollapsible.toggleButtonLabel": "Nessa página",
|
"theme.TOCCollapsible.toggleButtonLabel": "Nessa página",
|
||||||
"theme.admonition.caution": "caution",
|
"theme.admonition.caution": "cuidado",
|
||||||
"theme.admonition.danger": "danger",
|
"theme.admonition.danger": "perigo",
|
||||||
"theme.admonition.info": "info",
|
"theme.admonition.info": "info",
|
||||||
"theme.admonition.note": "note",
|
"theme.admonition.note": "nota",
|
||||||
"theme.admonition.tip": "tip",
|
"theme.admonition.tip": "dica",
|
||||||
"theme.admonition.warning": "warning",
|
"theme.admonition.warning": "atenção",
|
||||||
"theme.blog.archive.description": "Arquivo",
|
"theme.blog.archive.description": "Arquivo",
|
||||||
"theme.blog.archive.title": "Arquivo",
|
"theme.blog.archive.title": "Arquivo",
|
||||||
"theme.blog.paginator.navAriaLabel": "Navegação da página de listagem do blog",
|
"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.paginator.olderPost": "Postagem mais antiga",
|
||||||
"theme.blog.post.plurals": "Uma postagem|{count} postagens",
|
"theme.blog.post.plurals": "Uma postagem|{count} postagens",
|
||||||
"theme.blog.post.readMore": "Leia Mais",
|
"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.post.readingTime.plurals": "Leitura de um minuto|Leitura de {readingTime} minutos",
|
||||||
"theme.blog.sidebar.navAriaLabel": "Blog recent posts navigation",
|
"theme.blog.sidebar.navAriaLabel": "Blog recent posts navigation",
|
||||||
"theme.blog.tagTitle": "{nPosts} marcadas com \"{tagName}\"",
|
"theme.blog.tagTitle": "{nPosts} marcadas com \"{tagName}\"",
|
||||||
"theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})",
|
"theme.colorToggle.ariaLabel": "Alterar entre os modos claro e escuro (modo {mode} ativado)",
|
||||||
"theme.colorToggle.ariaLabel.mode.dark": "dark mode",
|
"theme.colorToggle.ariaLabel.mode.dark": "modo escuro",
|
||||||
"theme.colorToggle.ariaLabel.mode.light": "light mode",
|
"theme.colorToggle.ariaLabel.mode.light": "modo claro",
|
||||||
"theme.common.editThisPage": "Editar essa página",
|
"theme.common.editThisPage": "Editar essa página",
|
||||||
"theme.common.headingLinkTitle": "Link direto para {heading}",
|
"theme.common.headingLinkTitle": "Link direto para {heading}",
|
||||||
"theme.common.skipToMainContent": "Pular para o conteúdo principal",
|
"theme.common.skipToMainContent": "Pular para o conteúdo principal",
|
||||||
"theme.docs.DocCard.categoryDescription": "{count} items",
|
"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.breadcrumbs.navAriaLabel": "Breadcrumbs",
|
||||||
"theme.docs.paginator.navAriaLabel": "Páginas de documentação",
|
"theme.docs.paginator.navAriaLabel": "Páginas de documentação",
|
||||||
"theme.docs.paginator.next": "Próxima",
|
"theme.docs.paginator.next": "Próxima",
|
||||||
"theme.docs.paginator.previous": "Anterior",
|
"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.collapseButtonAriaLabel": "Fechar painel lateral",
|
||||||
"theme.docs.sidebar.collapseButtonTitle": "Fechar painel lateral",
|
"theme.docs.sidebar.collapseButtonTitle": "Fechar painel lateral",
|
||||||
"theme.docs.sidebar.expandButtonAriaLabel": "Expandir painel lateral",
|
"theme.docs.sidebar.expandButtonAriaLabel": "Expandir painel lateral",
|
||||||
"theme.docs.sidebar.expandButtonTitle": "Expandir painel lateral",
|
"theme.docs.sidebar.expandButtonTitle": "Expandir painel lateral",
|
||||||
"theme.docs.sidebar.navAriaLabel": "Docs sidebar",
|
"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} com \"{tagName}\"",
|
||||||
"theme.docs.tagDocListPageTitle.nDocsTagged": "Um documento selecionado|{count} documentos selecionados",
|
"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.latestVersionLinkLabel": "última versão",
|
||||||
"theme.docs.versions.latestVersionSuggestionLabel": "Para a documentação atualizada, veja: {latestVersionLink} ({versionLabel}).",
|
"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.",
|
"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.atDate": " em {date}",
|
||||||
"theme.lastUpdated.byUser": " por {user}",
|
"theme.lastUpdated.byUser": " por {user}",
|
||||||
"theme.lastUpdated.lastUpdatedAtBy": "Última atualização {atDate}{byUser}",
|
"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.mobileSidebarSecondaryMenu.backButtonLabel": "← Voltar para o menu principal",
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
"theme.navbar.mobileVersionsDropdown.label": "Versões",
|
||||||
"theme.tags.tagsListLabel": "Marcadores:",
|
"theme.tags.tagsListLabel": "Marcadores:",
|
||||||
"theme.tags.tagsPageLink": "Ver todas os Marcadores",
|
"theme.tags.tagsPageLink": "Ver todas os Marcadores",
|
||||||
"theme.tags.tagsPageTitle": "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.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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/theme-translations",
|
"name": "@docusaurus/theme-translations",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Docusaurus theme translations.",
|
"description": "Docusaurus theme translations.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
@ -23,8 +23,8 @@
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/core": "3.0.1",
|
"@docusaurus/core": "3.1.0",
|
||||||
"@docusaurus/logger": "3.0.1",
|
"@docusaurus/logger": "3.1.0",
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/tsconfig",
|
"name": "@docusaurus/tsconfig",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Docusaurus base TypeScript configuration.",
|
"description": "Docusaurus base TypeScript configuration.",
|
||||||
"main": "tsconfig.json",
|
"main": "tsconfig.json",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/types",
|
"name": "@docusaurus/types",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Common types for Docusaurus packages.",
|
"description": "Common types for Docusaurus packages.",
|
||||||
"types": "./src/index.d.ts",
|
"types": "./src/index.d.ts",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mdx-js/mdx": "^3.0.0",
|
||||||
"@types/history": "^4.7.11",
|
"@types/history": "^4.7.11",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"commander": "^5.1.0",
|
"commander": "^5.1.0",
|
||||||
|
|
39
packages/docusaurus-types/src/config.d.ts
vendored
39
packages/docusaurus-types/src/config.d.ts
vendored
|
@ -10,6 +10,10 @@ import type {Required as RequireKeys, DeepPartial} from 'utility-types';
|
||||||
import type {I18nConfig} from './i18n';
|
import type {I18nConfig} from './i18n';
|
||||||
import type {PluginConfig, PresetConfig, HtmlTagObject} from './plugin';
|
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 ReportingSeverity = 'ignore' | 'log' | 'warn' | 'throw';
|
||||||
|
|
||||||
export type ThemeConfig = {
|
export type ThemeConfig = {
|
||||||
|
@ -27,6 +31,20 @@ export type MDX1CompatOptions = {
|
||||||
headingIds: boolean;
|
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 = {
|
export type MarkdownConfig = {
|
||||||
/**
|
/**
|
||||||
* The Markdown format to use by default.
|
* The Markdown format to use by default.
|
||||||
|
@ -44,6 +62,14 @@ export type MarkdownConfig = {
|
||||||
*/
|
*/
|
||||||
format: 'mdx' | 'md' | 'detect';
|
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:
|
* 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
|
* See also https://github.com/facebook/docusaurus/issues/4029
|
||||||
*/
|
*/
|
||||||
mdx1Compat: MDX1CompatOptions;
|
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"
|
* @default "throw"
|
||||||
*/
|
*/
|
||||||
onBrokenLinks: ReportingSeverity;
|
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.
|
* The behavior of Docusaurus when it detects any broken markdown link.
|
||||||
*
|
*
|
||||||
|
|
2
packages/docusaurus-types/src/index.d.ts
vendored
2
packages/docusaurus-types/src/index.d.ts
vendored
|
@ -9,6 +9,8 @@ export {
|
||||||
ReportingSeverity,
|
ReportingSeverity,
|
||||||
ThemeConfig,
|
ThemeConfig,
|
||||||
MarkdownConfig,
|
MarkdownConfig,
|
||||||
|
DefaultParseFrontMatter,
|
||||||
|
ParseFrontMatter,
|
||||||
DocusaurusConfig,
|
DocusaurusConfig,
|
||||||
Config,
|
Config,
|
||||||
} from './config';
|
} from './config';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/utils-common",
|
"name": "@docusaurus/utils-common",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Common (Node/Browser) utility functions for Docusaurus packages.",
|
"description": "Common (Node/Browser) utility functions for Docusaurus packages.",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/utils-validation",
|
"name": "@docusaurus/utils-validation",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Node validation utility functions for Docusaurus packages.",
|
"description": "Node validation utility functions for Docusaurus packages.",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
|
@ -18,8 +18,8 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/logger": "3.0.1",
|
"@docusaurus/logger": "3.1.0",
|
||||||
"@docusaurus/utils": "3.0.1",
|
"@docusaurus/utils": "3.1.0",
|
||||||
"joi": "^17.9.2",
|
"joi": "^17.9.2",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/utils",
|
"name": "@docusaurus/utils",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Node utility functions for Docusaurus packages.",
|
"description": "Node utility functions for Docusaurus packages.",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/logger": "3.0.1",
|
"@docusaurus/logger": "3.1.0",
|
||||||
"@svgr/webpack": "^6.5.1",
|
"@svgr/webpack": "^6.5.1",
|
||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
"node": ">=18.0"
|
"node": ">=18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@types/dedent": "^0.7.0",
|
"@types/dedent": "^0.7.0",
|
||||||
"@types/github-slugger": "^1.3.0",
|
"@types/github-slugger": "^1.3.0",
|
||||||
"@types/micromatch": "^4.0.2",
|
"@types/micromatch": "^4.0.2",
|
||||||
|
|
|
@ -176,6 +176,7 @@ exports[`replaceMarkdownLinks replaces links with same title as URL 1`] = `
|
||||||
"brokenMarkdownLinks": [],
|
"brokenMarkdownLinks": [],
|
||||||
"newContent": "
|
"newContent": "
|
||||||
[foo.md](/docs/foo)
|
[foo.md](/docs/foo)
|
||||||
|
[./foo.md](</docs/foo>)
|
||||||
[./foo.md](/docs/foo)
|
[./foo.md](/docs/foo)
|
||||||
[foo.md](/docs/foo)
|
[foo.md](/docs/foo)
|
||||||
[./foo.md](/docs/foo)
|
[./foo.md](/docs/foo)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`parseMarkdownString deletes only first heading 1`] = `
|
exports[`parseMarkdownFile deletes only first heading 1`] = `
|
||||||
{
|
{
|
||||||
"content": "# Markdown Title
|
"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
|
"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
|
"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
|
"content": "\`\`\`js
|
||||||
code
|
code
|
||||||
|
@ -56,7 +56,7 @@ Content",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`parseMarkdownString handles code blocks 2`] = `
|
exports[`parseMarkdownFile handles code blocks 2`] = `
|
||||||
{
|
{
|
||||||
"content": "\`\`\`\`js
|
"content": "\`\`\`\`js
|
||||||
Foo
|
Foo
|
||||||
|
@ -73,7 +73,7 @@ Content",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`parseMarkdownString handles code blocks 3`] = `
|
exports[`parseMarkdownFile handles code blocks 3`] = `
|
||||||
{
|
{
|
||||||
"content": "\`\`\`\`js
|
"content": "\`\`\`\`js
|
||||||
Foo
|
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
|
"content": "foo
|
||||||
# test",
|
# 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",
|
"content": "Some text",
|
||||||
"contentTitle": undefined,
|
"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
|
"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",
|
"content": "## test",
|
||||||
"contentTitle": undefined,
|
"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",
|
"content": "# test",
|
||||||
"contentTitle": "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
|
"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": "",
|
"content": "",
|
||||||
"contentTitle": undefined,
|
"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
|
"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
|
"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",
|
"content": "# test",
|
||||||
"contentTitle": "test",
|
"contentTitle": "test",
|
||||||
|
|
|
@ -231,6 +231,7 @@ The following operations are defined for [URI]s:
|
||||||
},
|
},
|
||||||
fileString: `
|
fileString: `
|
||||||
[foo.md](foo.md)
|
[foo.md](foo.md)
|
||||||
|
[./foo.md](<./foo.md>)
|
||||||
[./foo.md](./foo.md)
|
[./foo.md](./foo.md)
|
||||||
[foo.md](./foo.md)
|
[foo.md](./foo.md)
|
||||||
[./foo.md](foo.md)
|
[./foo.md](foo.md)
|
||||||
|
|
|
@ -9,12 +9,14 @@ import dedent from 'dedent';
|
||||||
import {
|
import {
|
||||||
createExcerpt,
|
createExcerpt,
|
||||||
parseMarkdownContentTitle,
|
parseMarkdownContentTitle,
|
||||||
parseMarkdownString,
|
|
||||||
parseMarkdownHeadingId,
|
parseMarkdownHeadingId,
|
||||||
writeMarkdownHeadingId,
|
writeMarkdownHeadingId,
|
||||||
escapeMarkdownHeadingIds,
|
escapeMarkdownHeadingIds,
|
||||||
unwrapMdxCodeBlocks,
|
unwrapMdxCodeBlocks,
|
||||||
admonitionTitleToDirectiveLabel,
|
admonitionTitleToDirectiveLabel,
|
||||||
|
parseMarkdownFile,
|
||||||
|
DEFAULT_PARSE_FRONT_MATTER,
|
||||||
|
parseFileContentFrontMatter,
|
||||||
} from '../markdownUtils';
|
} from '../markdownUtils';
|
||||||
|
|
||||||
describe('createExcerpt', () => {
|
describe('createExcerpt', () => {
|
||||||
|
@ -623,32 +625,110 @@ Lorem Ipsum
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('parseMarkdownString', () => {
|
describe('parseFileContentFrontMatter', () => {
|
||||||
it('parse markdown with front matter', () => {
|
function test(fileContent: string) {
|
||||||
expect(
|
return parseFileContentFrontMatter(fileContent);
|
||||||
parseMarkdownString(dedent`
|
}
|
||||||
|
|
||||||
|
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
|
title: Frontmatter title
|
||||||
---
|
---
|
||||||
|
|
||||||
Some text
|
Some text
|
||||||
`),
|
`),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parses first heading as contentTitle', () => {
|
it('parse markdown with custom front matter parser', async () => {
|
||||||
expect(
|
await expect(
|
||||||
parseMarkdownString(dedent`
|
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
|
# Markdown Title
|
||||||
|
|
||||||
Some text
|
Some text
|
||||||
`),
|
`),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('warns about duplicate titles (front matter + markdown)', () => {
|
it('warns about duplicate titles (front matter + markdown)', async () => {
|
||||||
expect(
|
await expect(
|
||||||
parseMarkdownString(dedent`
|
test(dedent`
|
||||||
---
|
---
|
||||||
title: Frontmatter title
|
title: Frontmatter title
|
||||||
---
|
---
|
||||||
|
@ -657,12 +737,12 @@ describe('parseMarkdownString', () => {
|
||||||
|
|
||||||
Some text
|
Some text
|
||||||
`),
|
`),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('warns about duplicate titles (front matter + markdown alternate)', () => {
|
it('warns about duplicate titles (front matter + markdown alternate)', async () => {
|
||||||
expect(
|
await expect(
|
||||||
parseMarkdownString(dedent`
|
test(dedent`
|
||||||
---
|
---
|
||||||
title: Frontmatter title
|
title: Frontmatter title
|
||||||
---
|
---
|
||||||
|
@ -672,12 +752,12 @@ describe('parseMarkdownString', () => {
|
||||||
|
|
||||||
Some text
|
Some text
|
||||||
`),
|
`),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not warn for duplicate title if markdown title is not at the top', () => {
|
it('does not warn for duplicate title if markdown title is not at the top', async () => {
|
||||||
expect(
|
await expect(
|
||||||
parseMarkdownString(dedent`
|
test(dedent`
|
||||||
---
|
---
|
||||||
title: Frontmatter title
|
title: Frontmatter title
|
||||||
---
|
---
|
||||||
|
@ -686,12 +766,12 @@ describe('parseMarkdownString', () => {
|
||||||
|
|
||||||
# Markdown Title
|
# Markdown Title
|
||||||
`),
|
`),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deletes only first heading', () => {
|
it('deletes only first heading', async () => {
|
||||||
expect(
|
await expect(
|
||||||
parseMarkdownString(dedent`
|
test(dedent`
|
||||||
# Markdown Title
|
# Markdown Title
|
||||||
|
|
||||||
test test test # test bar
|
test test test # test bar
|
||||||
|
@ -700,12 +780,12 @@ describe('parseMarkdownString', () => {
|
||||||
|
|
||||||
### Markdown Title h3
|
### Markdown Title h3
|
||||||
`),
|
`),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parses front-matter and ignore h2', () => {
|
it('parses front-matter and ignore h2', async () => {
|
||||||
expect(
|
await expect(
|
||||||
parseMarkdownString(
|
test(
|
||||||
dedent`
|
dedent`
|
||||||
---
|
---
|
||||||
title: Frontmatter title
|
title: Frontmatter title
|
||||||
|
@ -713,55 +793,55 @@ describe('parseMarkdownString', () => {
|
||||||
## test
|
## test
|
||||||
`,
|
`,
|
||||||
),
|
),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reads front matter only', () => {
|
it('reads front matter only', async () => {
|
||||||
expect(
|
await expect(
|
||||||
parseMarkdownString(dedent`
|
test(dedent`
|
||||||
---
|
---
|
||||||
title: test
|
title: test
|
||||||
---
|
---
|
||||||
`),
|
`),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parses title only', () => {
|
it('parses title only', async () => {
|
||||||
expect(parseMarkdownString('# test')).toMatchSnapshot();
|
await expect(test('# test')).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parses title only alternate', () => {
|
it('parses title only alternate', async () => {
|
||||||
expect(
|
await expect(
|
||||||
parseMarkdownString(dedent`
|
test(dedent`
|
||||||
test
|
test
|
||||||
===
|
===
|
||||||
`),
|
`),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('warns about duplicate titles', () => {
|
it('warns about duplicate titles', async () => {
|
||||||
expect(
|
await expect(
|
||||||
parseMarkdownString(dedent`
|
test(dedent`
|
||||||
---
|
---
|
||||||
title: Frontmatter title
|
title: Frontmatter title
|
||||||
---
|
---
|
||||||
# test
|
# test
|
||||||
`),
|
`),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores markdown title if its not a first text', () => {
|
it('ignores markdown title if its not a first text', async () => {
|
||||||
expect(
|
await expect(
|
||||||
parseMarkdownString(dedent`
|
test(dedent`
|
||||||
foo
|
foo
|
||||||
# test
|
# test
|
||||||
`),
|
`),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deletes only first heading 2', () => {
|
it('deletes only first heading 2', async () => {
|
||||||
expect(
|
await expect(
|
||||||
parseMarkdownString(dedent`
|
test(dedent`
|
||||||
# test
|
# test
|
||||||
|
|
||||||
test test test test test test
|
test test test test test test
|
||||||
|
@ -770,21 +850,21 @@ describe('parseMarkdownString', () => {
|
||||||
### test
|
### test
|
||||||
test3
|
test3
|
||||||
`),
|
`),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles code blocks', () => {
|
it('handles code blocks', async () => {
|
||||||
expect(
|
await expect(
|
||||||
parseMarkdownString(dedent`
|
test(dedent`
|
||||||
\`\`\`js
|
\`\`\`js
|
||||||
code
|
code
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
Content
|
Content
|
||||||
`),
|
`),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
expect(
|
await expect(
|
||||||
parseMarkdownString(dedent`
|
test(dedent`
|
||||||
\`\`\`\`js
|
\`\`\`\`js
|
||||||
Foo
|
Foo
|
||||||
\`\`\`diff
|
\`\`\`diff
|
||||||
|
@ -795,9 +875,9 @@ describe('parseMarkdownString', () => {
|
||||||
|
|
||||||
Content
|
Content
|
||||||
`),
|
`),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
expect(
|
await expect(
|
||||||
parseMarkdownString(dedent`
|
test(dedent`
|
||||||
\`\`\`\`js
|
\`\`\`\`js
|
||||||
Foo
|
Foo
|
||||||
\`\`\`diff
|
\`\`\`diff
|
||||||
|
@ -806,17 +886,17 @@ describe('parseMarkdownString', () => {
|
||||||
|
|
||||||
Content
|
Content
|
||||||
`),
|
`),
|
||||||
).toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws for invalid front matter', () => {
|
it('throws for invalid front matter', async () => {
|
||||||
expect(() =>
|
await expect(
|
||||||
parseMarkdownString(dedent`
|
test(dedent`
|
||||||
---
|
---
|
||||||
foo: f: a
|
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:
|
"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
|
foo: f: a
|
||||||
^"
|
^"
|
||||||
|
|
|
@ -18,6 +18,8 @@ import {
|
||||||
buildSshUrl,
|
buildSshUrl,
|
||||||
buildHttpsUrl,
|
buildHttpsUrl,
|
||||||
hasSSHProtocol,
|
hasSSHProtocol,
|
||||||
|
parseURLPath,
|
||||||
|
serializeURLPath,
|
||||||
} from '../urlUtils';
|
} from '../urlUtils';
|
||||||
|
|
||||||
describe('normalizeUrl', () => {
|
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', () => {
|
describe('resolvePathname', () => {
|
||||||
it('works', () => {
|
it('works', () => {
|
||||||
// These tests are directly copied from https://github.com/mjackson/resolve-pathname/blob/master/modules/__tests__/resolvePathname-test.js
|
// These tests are directly copied from https://github.com/mjackson/resolve-pathname/blob/master/modules/__tests__/resolvePathname-test.js
|
||||||
|
|
|
@ -83,7 +83,9 @@ export const DEFAULT_I18N_DIR_NAME = 'i18n';
|
||||||
export const CODE_TRANSLATIONS_FILE_NAME = 'code.json';
|
export const CODE_TRANSLATIONS_FILE_NAME = 'code.json';
|
||||||
|
|
||||||
/** Dev server opens on this port by default. */
|
/** 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. */
|
/** Default plugin ID. */
|
||||||
export const DEFAULT_PLUGIN_ID = 'default';
|
export const DEFAULT_PLUGIN_ID = 'default';
|
||||||
|
|
|
@ -48,6 +48,8 @@ export {
|
||||||
encodePath,
|
encodePath,
|
||||||
isValidPathname,
|
isValidPathname,
|
||||||
resolvePathname,
|
resolvePathname,
|
||||||
|
parseURLPath,
|
||||||
|
serializeURLPath,
|
||||||
addLeadingSlash,
|
addLeadingSlash,
|
||||||
addTrailingSlash,
|
addTrailingSlash,
|
||||||
removeTrailingSlash,
|
removeTrailingSlash,
|
||||||
|
@ -55,6 +57,7 @@ export {
|
||||||
buildHttpsUrl,
|
buildHttpsUrl,
|
||||||
buildSshUrl,
|
buildSshUrl,
|
||||||
} from './urlUtils';
|
} from './urlUtils';
|
||||||
|
export type {URLPath} from './urlUtils';
|
||||||
export {
|
export {
|
||||||
type Tag,
|
type Tag,
|
||||||
type TagsListItem,
|
type TagsListItem,
|
||||||
|
@ -70,9 +73,9 @@ export {
|
||||||
unwrapMdxCodeBlocks,
|
unwrapMdxCodeBlocks,
|
||||||
admonitionTitleToDirectiveLabel,
|
admonitionTitleToDirectiveLabel,
|
||||||
createExcerpt,
|
createExcerpt,
|
||||||
parseFrontMatter,
|
DEFAULT_PARSE_FRONT_MATTER,
|
||||||
parseMarkdownContentTitle,
|
parseMarkdownContentTitle,
|
||||||
parseMarkdownString,
|
parseMarkdownFile,
|
||||||
writeMarkdownHeadingId,
|
writeMarkdownHeadingId,
|
||||||
type WriteHeadingIDOptions,
|
type WriteHeadingIDOptions,
|
||||||
} from './markdownUtils';
|
} from './markdownUtils';
|
||||||
|
|
|
@ -128,7 +128,7 @@ export function replaceMarkdownLinks<T extends ContentPaths>({
|
||||||
const linkSuffixPattern = '(?:\\?[^#>\\s]+)?(?:#[^>\\s]+)?';
|
const linkSuffixPattern = '(?:\\?[^#>\\s]+)?(?:#[^>\\s]+)?';
|
||||||
const linkCapture = (forbidden: string) =>
|
const linkCapture = (forbidden: string) =>
|
||||||
`((?!https?://|@site/)[^${forbidden}#?]+)`;
|
`((?!https?://|@site/)[^${forbidden}#?]+)`;
|
||||||
const linkURLPattern = `(?:${linkCapture(
|
const linkURLPattern = `(?:(?!<)${linkCapture(
|
||||||
'()\\s',
|
'()\\s',
|
||||||
)}${linkSuffixPattern}|<${linkCapture('>')}${linkSuffixPattern}>)`;
|
)}${linkSuffixPattern}|<${linkCapture('>')}${linkSuffixPattern}>)`;
|
||||||
const linkPattern = new RegExp(
|
const linkPattern = new RegExp(
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
import logger from '@docusaurus/logger';
|
import logger from '@docusaurus/logger';
|
||||||
import matter from 'gray-matter';
|
import matter from 'gray-matter';
|
||||||
import {createSlugger, type Slugger, type SluggerOptions} from './slugger';
|
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
|
// Some utilities for parsing Markdown content. These things are only used on
|
||||||
// server-side when we infer metadata like `title` and `description` from the
|
// server-side when we infer metadata like `title` and `description` from the
|
||||||
|
@ -214,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. */
|
/** Front matter as parsed by gray-matter. */
|
||||||
frontMatter: {[key: string]: unknown};
|
frontMatter: {[key: string]: unknown};
|
||||||
/** The remaining content, trimmed. */
|
/** The remaining content, trimmed. */
|
||||||
content: string;
|
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 {
|
return {
|
||||||
frontMatter: data,
|
frontMatter,
|
||||||
content: content.trim(),
|
content: content.trim(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_PARSE_FRONT_MATTER: DefaultParseFrontMatter = async (
|
||||||
|
params,
|
||||||
|
) => parseFileContentFrontMatter(params.fileContent);
|
||||||
|
|
||||||
function toTextContentTitle(contentTitle: string): string {
|
function toTextContentTitle(contentTitle: string): string {
|
||||||
return contentTitle.replace(/`(?<text>[^`]*)`/g, '$<text>');
|
return contentTitle.replace(/`(?<text>[^`]*)`/g, '$<text>');
|
||||||
}
|
}
|
||||||
|
@ -309,10 +334,16 @@ export function parseMarkdownContentTitle(
|
||||||
* @throws Throws when `parseFrontMatter` throws, usually because of invalid
|
* @throws Throws when `parseFrontMatter` throws, usually because of invalid
|
||||||
* syntax.
|
* syntax.
|
||||||
*/
|
*/
|
||||||
export function parseMarkdownString(
|
export async function parseMarkdownFile({
|
||||||
markdownFileContent: string,
|
filePath,
|
||||||
options?: ParseMarkdownContentTitleOptions,
|
fileContent,
|
||||||
): {
|
parseFrontMatter,
|
||||||
|
removeContentTitle,
|
||||||
|
}: {
|
||||||
|
filePath: string;
|
||||||
|
fileContent: string;
|
||||||
|
parseFrontMatter: ParseFrontMatter;
|
||||||
|
} & ParseMarkdownContentTitleOptions): Promise<{
|
||||||
/** @see {@link parseFrontMatter} */
|
/** @see {@link parseFrontMatter} */
|
||||||
frontMatter: {[key: string]: unknown};
|
frontMatter: {[key: string]: unknown};
|
||||||
/** @see {@link parseMarkdownContentTitle} */
|
/** @see {@link parseMarkdownContentTitle} */
|
||||||
|
@ -324,14 +355,18 @@ export function parseMarkdownString(
|
||||||
* the `removeContentTitle` option.
|
* the `removeContentTitle` option.
|
||||||
*/
|
*/
|
||||||
content: string;
|
content: string;
|
||||||
} {
|
}> {
|
||||||
try {
|
try {
|
||||||
const {frontMatter, content: contentWithoutFrontMatter} =
|
const {frontMatter, content: contentWithoutFrontMatter} =
|
||||||
parseFrontMatter(markdownFileContent);
|
await parseFrontMatter({
|
||||||
|
filePath,
|
||||||
|
fileContent,
|
||||||
|
defaultParseFrontMatter: DEFAULT_PARSE_FRONT_MATTER,
|
||||||
|
});
|
||||||
|
|
||||||
const {content, contentTitle} = parseMarkdownContentTitle(
|
const {content, contentTitle} = parseMarkdownContentTitle(
|
||||||
contentWithoutFrontMatter,
|
contentWithoutFrontMatter,
|
||||||
options,
|
{removeContentTitle},
|
||||||
);
|
);
|
||||||
|
|
||||||
const excerpt = createExcerpt(content);
|
const excerpt = createExcerpt(content);
|
||||||
|
|
|
@ -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
|
* Resolve pathnames and fail-fast if resolution fails. Uses standard URL
|
||||||
* semantics (provided by `resolve-pathname` which is used internally by React
|
* semantics (provided by `resolve-pathname` which is used internally by React
|
||||||
* router)
|
* router)
|
||||||
*/
|
*/
|
||||||
export function resolvePathname(to: string, from?: string): string {
|
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);
|
return resolvePathnameUnsafe(to, from);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Appends a leading slash to `str`, if one doesn't exist. */
|
/** Appends a leading slash to `str`, if one doesn't exist. */
|
||||||
export function addLeadingSlash(str: string): string {
|
export function addLeadingSlash(str: string): string {
|
||||||
return addPrefix(str, '/');
|
return addPrefix(str, '/');
|
||||||
|
|
|
@ -218,6 +218,9 @@ cli.arguments('<command>').action((cmd) => {
|
||||||
logger.error` Unknown command name=${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
|
* @param {string | undefined} command
|
||||||
*/
|
*/
|
||||||
|
@ -237,12 +240,29 @@ function isInternalCommand(command) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isInternalCommand(process.argv.slice(2)[0])) {
|
// process.argv always looks like this:
|
||||||
await externalCommand(cli);
|
// [
|
||||||
|
// '/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) {
|
// There is an unrecognized subcommand
|
||||||
cli.outputHelp();
|
// 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);
|
cli.parse(process.argv);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/core",
|
"name": "@docusaurus/core",
|
||||||
"description": "Easy to Maintain Open Source Documentation Websites",
|
"description": "Easy to Maintain Open Source Documentation Websites",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
@ -43,13 +43,13 @@
|
||||||
"@babel/runtime": "^7.22.6",
|
"@babel/runtime": "^7.22.6",
|
||||||
"@babel/runtime-corejs3": "^7.22.6",
|
"@babel/runtime-corejs3": "^7.22.6",
|
||||||
"@babel/traverse": "^7.22.8",
|
"@babel/traverse": "^7.22.8",
|
||||||
"@docusaurus/cssnano-preset": "3.0.1",
|
"@docusaurus/cssnano-preset": "3.1.0",
|
||||||
"@docusaurus/logger": "3.0.1",
|
"@docusaurus/logger": "3.1.0",
|
||||||
"@docusaurus/mdx-loader": "3.0.1",
|
"@docusaurus/mdx-loader": "3.1.0",
|
||||||
"@docusaurus/react-loadable": "5.5.2",
|
"@docusaurus/react-loadable": "5.5.2",
|
||||||
"@docusaurus/utils": "3.0.1",
|
"@docusaurus/utils": "3.1.0",
|
||||||
"@docusaurus/utils-common": "3.0.1",
|
"@docusaurus/utils-common": "3.1.0",
|
||||||
"@docusaurus/utils-validation": "3.0.1",
|
"@docusaurus/utils-validation": "3.1.0",
|
||||||
"@slorber/static-site-generator-webpack-plugin": "^4.0.7",
|
"@slorber/static-site-generator-webpack-plugin": "^4.0.7",
|
||||||
"@svgr/webpack": "^6.5.1",
|
"@svgr/webpack": "^6.5.1",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
|
@ -104,8 +104,8 @@
|
||||||
"webpackbar": "^5.0.2"
|
"webpackbar": "^5.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "3.0.1",
|
"@docusaurus/module-type-aliases": "3.1.0",
|
||||||
"@docusaurus/types": "3.0.1",
|
"@docusaurus/types": "3.1.0",
|
||||||
"@types/detect-port": "^1.3.3",
|
"@types/detect-port": "^1.3.3",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@types/react-router-config": "^5.0.7",
|
"@types/react-router-config": "^5.0.7",
|
||||||
|
|
51
packages/docusaurus/src/client/BrokenLinksContext.tsx
Normal file
51
packages/docusaurus/src/client/BrokenLinksContext.tsx
Normal 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>;
|
||||||
|
}
|
|
@ -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>;
|
|
||||||
}
|
|
|
@ -16,7 +16,7 @@ import {applyTrailingSlash} from '@docusaurus/utils-common';
|
||||||
import useDocusaurusContext from './useDocusaurusContext';
|
import useDocusaurusContext from './useDocusaurusContext';
|
||||||
import isInternalUrl from './isInternalUrl';
|
import isInternalUrl from './isInternalUrl';
|
||||||
import ExecutionEnvironment from './ExecutionEnvironment';
|
import ExecutionEnvironment from './ExecutionEnvironment';
|
||||||
import {useLinksCollector} from '../LinksCollector';
|
import useBrokenLinks from './useBrokenLinks';
|
||||||
import {useBaseUrlUtils} from './useBaseUrl';
|
import {useBaseUrlUtils} from './useBaseUrl';
|
||||||
import type {Props} from '@docusaurus/Link';
|
import type {Props} from '@docusaurus/Link';
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ function Link(
|
||||||
siteConfig: {trailingSlash, baseUrl},
|
siteConfig: {trailingSlash, baseUrl},
|
||||||
} = useDocusaurusContext();
|
} = useDocusaurusContext();
|
||||||
const {withBaseUrl} = useBaseUrlUtils();
|
const {withBaseUrl} = useBaseUrlUtils();
|
||||||
const linksCollector = useLinksCollector();
|
const brokenLinks = useBrokenLinks();
|
||||||
const innerRef = useRef<HTMLAnchorElement | null>(null);
|
const innerRef = useRef<HTMLAnchorElement | null>(null);
|
||||||
|
|
||||||
useImperativeHandle(forwardedRef, () => innerRef.current!);
|
useImperativeHandle(forwardedRef, () => innerRef.current!);
|
||||||
|
@ -144,7 +144,7 @@ function Link(
|
||||||
const isRegularHtmlLink = !targetLink || !isInternal || isAnchorLink;
|
const isRegularHtmlLink = !targetLink || !isInternal || isAnchorLink;
|
||||||
|
|
||||||
if (!isRegularHtmlLink && !noBrokenLinkCheck) {
|
if (!isRegularHtmlLink && !noBrokenLinkCheck) {
|
||||||
linksCollector.collectLink(targetLink!);
|
brokenLinks.collectLink(targetLink!);
|
||||||
}
|
}
|
||||||
|
|
||||||
return isRegularHtmlLink ? (
|
return isRegularHtmlLink ? (
|
||||||
|
|
13
packages/docusaurus/src/client/exports/useBrokenLinks.ts
Normal file
13
packages/docusaurus/src/client/exports/useBrokenLinks.ts
Normal 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();
|
||||||
|
}
|
|
@ -20,9 +20,9 @@ import {renderStaticApp} from './serverRenderer';
|
||||||
import preload from './preload';
|
import preload from './preload';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import {
|
import {
|
||||||
createStatefulLinksCollector,
|
createStatefulBrokenLinks,
|
||||||
LinksCollectorProvider,
|
BrokenLinksProvider,
|
||||||
} from './LinksCollector';
|
} from './BrokenLinksContext';
|
||||||
import type {Locals} from '@slorber/static-site-generator-webpack-plugin';
|
import type {Locals} from '@slorber/static-site-generator-webpack-plugin';
|
||||||
|
|
||||||
const getCompiledSSRTemplate = _.memoize((template: string) =>
|
const getCompiledSSRTemplate = _.memoize((template: string) =>
|
||||||
|
@ -96,23 +96,27 @@ async function doRender(locals: Locals & {path: string}) {
|
||||||
const routerContext = {};
|
const routerContext = {};
|
||||||
const helmetContext = {};
|
const helmetContext = {};
|
||||||
|
|
||||||
const linksCollector = createStatefulLinksCollector();
|
const statefulBrokenLinks = createStatefulBrokenLinks();
|
||||||
|
|
||||||
const app = (
|
const app = (
|
||||||
// @ts-expect-error: we are migrating away from react-loadable anyways
|
// @ts-expect-error: we are migrating away from react-loadable anyways
|
||||||
<Loadable.Capture report={(moduleName) => modules.add(moduleName)}>
|
<Loadable.Capture report={(moduleName) => modules.add(moduleName)}>
|
||||||
<HelmetProvider context={helmetContext}>
|
<HelmetProvider context={helmetContext}>
|
||||||
<StaticRouter location={location} context={routerContext}>
|
<StaticRouter location={location} context={routerContext}>
|
||||||
<LinksCollectorProvider linksCollector={linksCollector}>
|
<BrokenLinksProvider brokenLinks={statefulBrokenLinks}>
|
||||||
<App />
|
<App />
|
||||||
</LinksCollectorProvider>
|
</BrokenLinksProvider>
|
||||||
</StaticRouter>
|
</StaticRouter>
|
||||||
</HelmetProvider>
|
</HelmetProvider>
|
||||||
</Loadable.Capture>
|
</Loadable.Capture>
|
||||||
);
|
);
|
||||||
|
|
||||||
const appHtml = await renderStaticApp(app);
|
const appHtml = await renderStaticApp(app);
|
||||||
onLinksCollected(location, linksCollector.getCollectedLinks());
|
onLinksCollected({
|
||||||
|
staticPagePath: location,
|
||||||
|
anchors: statefulBrokenLinks.getCollectedAnchors(),
|
||||||
|
links: statefulBrokenLinks.getCollectedLinks(),
|
||||||
|
});
|
||||||
|
|
||||||
const {helmet} = helmetContext as FilledContext;
|
const {helmet} = helmetContext as FilledContext;
|
||||||
const htmlAttributes = helmet.htmlAttributes.toString();
|
const htmlAttributes = helmet.htmlAttributes.toString();
|
||||||
|
|
|
@ -152,8 +152,8 @@ async function buildLocale({
|
||||||
generatedFilesDir,
|
generatedFilesDir,
|
||||||
plugins,
|
plugins,
|
||||||
siteConfig: {
|
siteConfig: {
|
||||||
baseUrl,
|
|
||||||
onBrokenLinks,
|
onBrokenLinks,
|
||||||
|
onBrokenAnchors,
|
||||||
staticDirectories: staticDirectoriesOption,
|
staticDirectories: staticDirectoriesOption,
|
||||||
},
|
},
|
||||||
routes,
|
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} = {};
|
const headTags: {[location: string]: HelmetServerState} = {};
|
||||||
|
|
||||||
let serverConfig: Configuration = await createServerConfig({
|
let serverConfig: Configuration = await createServerConfig({
|
||||||
props,
|
props,
|
||||||
onLinksCollected: (staticPagePath, links) => {
|
onLinksCollected: ({staticPagePath, links, anchors}) => {
|
||||||
allCollectedLinks[staticPagePath] = links;
|
collectedLinks[staticPagePath] = {links, anchors};
|
||||||
},
|
},
|
||||||
onHeadTagsCollected: (staticPagePath, tags) => {
|
onHeadTagsCollected: (staticPagePath, tags) => {
|
||||||
headTags[staticPagePath] = tags;
|
headTags[staticPagePath] = tags;
|
||||||
|
@ -288,11 +290,10 @@ async function buildLocale({
|
||||||
);
|
);
|
||||||
|
|
||||||
await handleBrokenLinks({
|
await handleBrokenLinks({
|
||||||
allCollectedLinks,
|
collectedLinks,
|
||||||
routes,
|
routes,
|
||||||
onBrokenLinks,
|
onBrokenLinks,
|
||||||
outDir,
|
onBrokenAnchors,
|
||||||
baseUrl,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.success`Generated static files in path=${path.relative(
|
logger.success`Generated static files in path=${path.relative(
|
||||||
|
|
6
packages/docusaurus/src/deps.d.ts
vendored
6
packages/docusaurus/src/deps.d.ts
vendored
|
@ -42,7 +42,11 @@ declare module '@slorber/static-site-generator-webpack-plugin' {
|
||||||
headTags: string;
|
headTags: string;
|
||||||
preBodyTags: string;
|
preBodyTags: string;
|
||||||
postBodyTags: string;
|
postBodyTags: string;
|
||||||
onLinksCollected: (staticPagePath: string, links: string[]) => void;
|
onLinksCollected: (params: {
|
||||||
|
staticPagePath: string;
|
||||||
|
links: string[];
|
||||||
|
anchors: string[];
|
||||||
|
}) => void;
|
||||||
onHeadTagsCollected: (
|
onHeadTagsCollected: (
|
||||||
staticPagePath: string,
|
staticPagePath: string,
|
||||||
tags: HelmetServerState,
|
tags: HelmetServerState,
|
||||||
|
|
|
@ -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)
|
|
||||||
"
|
|
||||||
`;
|
|
|
@ -24,9 +24,12 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = `
|
||||||
"headingIds": true,
|
"headingIds": true,
|
||||||
},
|
},
|
||||||
"mermaid": false,
|
"mermaid": false,
|
||||||
|
"parseFrontMatter": [Function],
|
||||||
"preprocessor": undefined,
|
"preprocessor": undefined,
|
||||||
|
"remarkRehypeOptions": undefined,
|
||||||
},
|
},
|
||||||
"noIndex": false,
|
"noIndex": false,
|
||||||
|
"onBrokenAnchors": "warn",
|
||||||
"onBrokenLinks": "throw",
|
"onBrokenLinks": "throw",
|
||||||
"onBrokenMarkdownLinks": "warn",
|
"onBrokenMarkdownLinks": "warn",
|
||||||
"onDuplicateRoutes": "warn",
|
"onDuplicateRoutes": "warn",
|
||||||
|
@ -72,9 +75,12 @@ exports[`loadSiteConfig website with ts + js config 1`] = `
|
||||||
"headingIds": true,
|
"headingIds": true,
|
||||||
},
|
},
|
||||||
"mermaid": false,
|
"mermaid": false,
|
||||||
|
"parseFrontMatter": [Function],
|
||||||
"preprocessor": undefined,
|
"preprocessor": undefined,
|
||||||
|
"remarkRehypeOptions": undefined,
|
||||||
},
|
},
|
||||||
"noIndex": false,
|
"noIndex": false,
|
||||||
|
"onBrokenAnchors": "warn",
|
||||||
"onBrokenLinks": "throw",
|
"onBrokenLinks": "throw",
|
||||||
"onBrokenMarkdownLinks": "warn",
|
"onBrokenMarkdownLinks": "warn",
|
||||||
"onDuplicateRoutes": "warn",
|
"onDuplicateRoutes": "warn",
|
||||||
|
@ -120,9 +126,12 @@ exports[`loadSiteConfig website with valid JS CJS config 1`] = `
|
||||||
"headingIds": true,
|
"headingIds": true,
|
||||||
},
|
},
|
||||||
"mermaid": false,
|
"mermaid": false,
|
||||||
|
"parseFrontMatter": [Function],
|
||||||
"preprocessor": undefined,
|
"preprocessor": undefined,
|
||||||
|
"remarkRehypeOptions": undefined,
|
||||||
},
|
},
|
||||||
"noIndex": false,
|
"noIndex": false,
|
||||||
|
"onBrokenAnchors": "warn",
|
||||||
"onBrokenLinks": "throw",
|
"onBrokenLinks": "throw",
|
||||||
"onBrokenMarkdownLinks": "warn",
|
"onBrokenMarkdownLinks": "warn",
|
||||||
"onDuplicateRoutes": "warn",
|
"onDuplicateRoutes": "warn",
|
||||||
|
@ -168,9 +177,12 @@ exports[`loadSiteConfig website with valid JS ESM config 1`] = `
|
||||||
"headingIds": true,
|
"headingIds": true,
|
||||||
},
|
},
|
||||||
"mermaid": false,
|
"mermaid": false,
|
||||||
|
"parseFrontMatter": [Function],
|
||||||
"preprocessor": undefined,
|
"preprocessor": undefined,
|
||||||
|
"remarkRehypeOptions": undefined,
|
||||||
},
|
},
|
||||||
"noIndex": false,
|
"noIndex": false,
|
||||||
|
"onBrokenAnchors": "warn",
|
||||||
"onBrokenLinks": "throw",
|
"onBrokenLinks": "throw",
|
||||||
"onBrokenMarkdownLinks": "warn",
|
"onBrokenMarkdownLinks": "warn",
|
||||||
"onDuplicateRoutes": "warn",
|
"onDuplicateRoutes": "warn",
|
||||||
|
@ -216,9 +228,12 @@ exports[`loadSiteConfig website with valid TypeScript CJS config 1`] = `
|
||||||
"headingIds": true,
|
"headingIds": true,
|
||||||
},
|
},
|
||||||
"mermaid": false,
|
"mermaid": false,
|
||||||
|
"parseFrontMatter": [Function],
|
||||||
"preprocessor": undefined,
|
"preprocessor": undefined,
|
||||||
|
"remarkRehypeOptions": undefined,
|
||||||
},
|
},
|
||||||
"noIndex": false,
|
"noIndex": false,
|
||||||
|
"onBrokenAnchors": "warn",
|
||||||
"onBrokenLinks": "throw",
|
"onBrokenLinks": "throw",
|
||||||
"onBrokenMarkdownLinks": "warn",
|
"onBrokenMarkdownLinks": "warn",
|
||||||
"onDuplicateRoutes": "warn",
|
"onDuplicateRoutes": "warn",
|
||||||
|
@ -264,9 +279,12 @@ exports[`loadSiteConfig website with valid TypeScript ESM config 1`] = `
|
||||||
"headingIds": true,
|
"headingIds": true,
|
||||||
},
|
},
|
||||||
"mermaid": false,
|
"mermaid": false,
|
||||||
|
"parseFrontMatter": [Function],
|
||||||
"preprocessor": undefined,
|
"preprocessor": undefined,
|
||||||
|
"remarkRehypeOptions": undefined,
|
||||||
},
|
},
|
||||||
"noIndex": false,
|
"noIndex": false,
|
||||||
|
"onBrokenAnchors": "warn",
|
||||||
"onBrokenLinks": "throw",
|
"onBrokenLinks": "throw",
|
||||||
"onBrokenMarkdownLinks": "warn",
|
"onBrokenMarkdownLinks": "warn",
|
||||||
"onDuplicateRoutes": "warn",
|
"onDuplicateRoutes": "warn",
|
||||||
|
@ -312,9 +330,12 @@ exports[`loadSiteConfig website with valid async config 1`] = `
|
||||||
"headingIds": true,
|
"headingIds": true,
|
||||||
},
|
},
|
||||||
"mermaid": false,
|
"mermaid": false,
|
||||||
|
"parseFrontMatter": [Function],
|
||||||
"preprocessor": undefined,
|
"preprocessor": undefined,
|
||||||
|
"remarkRehypeOptions": undefined,
|
||||||
},
|
},
|
||||||
"noIndex": false,
|
"noIndex": false,
|
||||||
|
"onBrokenAnchors": "warn",
|
||||||
"onBrokenLinks": "throw",
|
"onBrokenLinks": "throw",
|
||||||
"onBrokenMarkdownLinks": "warn",
|
"onBrokenMarkdownLinks": "warn",
|
||||||
"onDuplicateRoutes": "warn",
|
"onDuplicateRoutes": "warn",
|
||||||
|
@ -362,9 +383,12 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = `
|
||||||
"headingIds": true,
|
"headingIds": true,
|
||||||
},
|
},
|
||||||
"mermaid": false,
|
"mermaid": false,
|
||||||
|
"parseFrontMatter": [Function],
|
||||||
"preprocessor": undefined,
|
"preprocessor": undefined,
|
||||||
|
"remarkRehypeOptions": undefined,
|
||||||
},
|
},
|
||||||
"noIndex": false,
|
"noIndex": false,
|
||||||
|
"onBrokenAnchors": "warn",
|
||||||
"onBrokenLinks": "throw",
|
"onBrokenLinks": "throw",
|
||||||
"onBrokenMarkdownLinks": "warn",
|
"onBrokenMarkdownLinks": "warn",
|
||||||
"onDuplicateRoutes": "warn",
|
"onDuplicateRoutes": "warn",
|
||||||
|
@ -412,9 +436,12 @@ exports[`loadSiteConfig website with valid config creator function 1`] = `
|
||||||
"headingIds": true,
|
"headingIds": true,
|
||||||
},
|
},
|
||||||
"mermaid": false,
|
"mermaid": false,
|
||||||
|
"parseFrontMatter": [Function],
|
||||||
"preprocessor": undefined,
|
"preprocessor": undefined,
|
||||||
|
"remarkRehypeOptions": undefined,
|
||||||
},
|
},
|
||||||
"noIndex": false,
|
"noIndex": false,
|
||||||
|
"onBrokenAnchors": "warn",
|
||||||
"onBrokenLinks": "throw",
|
"onBrokenLinks": "throw",
|
||||||
"onBrokenMarkdownLinks": "warn",
|
"onBrokenMarkdownLinks": "warn",
|
||||||
"onDuplicateRoutes": "warn",
|
"onDuplicateRoutes": "warn",
|
||||||
|
@ -465,9 +492,12 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = `
|
||||||
"headingIds": true,
|
"headingIds": true,
|
||||||
},
|
},
|
||||||
"mermaid": false,
|
"mermaid": false,
|
||||||
|
"parseFrontMatter": [Function],
|
||||||
"preprocessor": undefined,
|
"preprocessor": undefined,
|
||||||
|
"remarkRehypeOptions": undefined,
|
||||||
},
|
},
|
||||||
"noIndex": false,
|
"noIndex": false,
|
||||||
|
"onBrokenAnchors": "warn",
|
||||||
"onBrokenLinks": "throw",
|
"onBrokenLinks": "throw",
|
||||||
"onBrokenMarkdownLinks": "warn",
|
"onBrokenMarkdownLinks": "warn",
|
||||||
"onDuplicateRoutes": "warn",
|
"onDuplicateRoutes": "warn",
|
||||||
|
|
|
@ -98,9 +98,12 @@ exports[`load loads props for site with custom i18n path 1`] = `
|
||||||
"headingIds": true,
|
"headingIds": true,
|
||||||
},
|
},
|
||||||
"mermaid": false,
|
"mermaid": false,
|
||||||
|
"parseFrontMatter": [Function],
|
||||||
"preprocessor": undefined,
|
"preprocessor": undefined,
|
||||||
|
"remarkRehypeOptions": undefined,
|
||||||
},
|
},
|
||||||
"noIndex": false,
|
"noIndex": false,
|
||||||
|
"onBrokenAnchors": "warn",
|
||||||
"onBrokenLinks": "throw",
|
"onBrokenLinks": "throw",
|
||||||
"onBrokenMarkdownLinks": "warn",
|
"onBrokenMarkdownLinks": "warn",
|
||||||
"onDuplicateRoutes": "warn",
|
"onDuplicateRoutes": "warn",
|
||||||
|
|
|
@ -6,190 +6,608 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {jest} from '@jest/globals';
|
import {jest} from '@jest/globals';
|
||||||
import path from 'path';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import {handleBrokenLinks} from '../brokenLinks';
|
import {handleBrokenLinks} from '../brokenLinks';
|
||||||
import type {RouteConfig} from '@docusaurus/types';
|
import type {RouteConfig} from '@docusaurus/types';
|
||||||
|
|
||||||
|
type Params = Parameters<typeof handleBrokenLinks>[0];
|
||||||
|
|
||||||
|
// We don't need all the routes attributes for our tests
|
||||||
|
type SimpleRoute = {path: string; routes?: SimpleRoute[]};
|
||||||
|
|
||||||
|
// Conveniently apply defaults to function under test
|
||||||
|
async function testBrokenLinks(params: {
|
||||||
|
collectedLinks?: Params['collectedLinks'];
|
||||||
|
onBrokenLinks?: Params['onBrokenLinks'];
|
||||||
|
onBrokenAnchors?: Params['onBrokenAnchors'];
|
||||||
|
routes?: SimpleRoute[];
|
||||||
|
}) {
|
||||||
|
await handleBrokenLinks({
|
||||||
|
collectedLinks: {},
|
||||||
|
onBrokenLinks: 'throw',
|
||||||
|
onBrokenAnchors: 'throw',
|
||||||
|
...params,
|
||||||
|
// Unsafe but convenient for tests
|
||||||
|
routes: (params.routes ?? []) as RouteConfig[],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe('handleBrokenLinks', () => {
|
describe('handleBrokenLinks', () => {
|
||||||
const routes: RouteConfig[] = [
|
it('accepts valid link', async () => {
|
||||||
{
|
await testBrokenLinks({
|
||||||
path: '/community',
|
routes: [{path: '/page1'}, {path: '/page2'}],
|
||||||
component: '',
|
collectedLinks: {
|
||||||
},
|
'/page1': {links: ['/page2'], anchors: []},
|
||||||
{
|
'/page2': {links: [], anchors: []},
|
||||||
path: '/docs',
|
},
|
||||||
component: '',
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts valid link to uncollected page', async () => {
|
||||||
|
await testBrokenLinks({
|
||||||
|
routes: [{path: '/page1'}, {path: '/page2'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {links: ['/page2'], anchors: []},
|
||||||
|
// /page2 is absent on purpose: it doesn't contain any link/anchor
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts valid link to nested route', async () => {
|
||||||
|
await testBrokenLinks({
|
||||||
routes: [
|
routes: [
|
||||||
{path: '/docs/goodDoc', component: ''},
|
{path: '/page1'},
|
||||||
{path: '/docs/anotherGoodDoc', component: ''},
|
{path: '/nested/', routes: [{path: '/nested/page2'}]},
|
||||||
{path: '/docs/good doc with space', component: ''},
|
|
||||||
{path: '/docs/another good doc with space', component: ''},
|
|
||||||
{path: '/docs/weird%20but%20good', component: ''},
|
|
||||||
],
|
],
|
||||||
},
|
collectedLinks: {
|
||||||
{
|
'/page1': {links: ['/nested/page2'], anchors: []},
|
||||||
path: '*',
|
},
|
||||||
component: '',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const link1 = '/link1';
|
|
||||||
const link2 = '/docs/link2';
|
|
||||||
const link3 = '/hey/link3';
|
|
||||||
|
|
||||||
const linkToJavadoc1 = '/javadoc';
|
|
||||||
const linkToJavadoc2 = '/javadoc/';
|
|
||||||
const linkToJavadoc3 = '/javadoc/index.html';
|
|
||||||
const linkToJavadoc4 = '/javadoc/index.html#foo';
|
|
||||||
|
|
||||||
const linkToZipFile = '/files/file.zip';
|
|
||||||
const linkToHtmlFile1 = '/files/hey.html';
|
|
||||||
const linkToHtmlFile2 = '/files/hey';
|
|
||||||
|
|
||||||
const linkToEmptyFolder1 = '/emptyFolder';
|
|
||||||
const linkToEmptyFolder2 = '/emptyFolder/';
|
|
||||||
const allCollectedLinks = {
|
|
||||||
'/docs/good doc with space': [
|
|
||||||
// Good - valid file with spaces in name
|
|
||||||
'./another%20good%20doc%20with%20space',
|
|
||||||
// Good - valid file with percent-20 in its name
|
|
||||||
'./weird%20but%20good',
|
|
||||||
// Bad - non-existent file with spaces in name
|
|
||||||
'./some%20other%20non-existent%20doc1',
|
|
||||||
// Evil - trying to use ../../ but '/' won't get decoded
|
|
||||||
// cSpell:ignore Fout
|
|
||||||
'./break%2F..%2F..%2Fout2',
|
|
||||||
],
|
|
||||||
'/docs/goodDoc': [
|
|
||||||
// Good links
|
|
||||||
'./anotherGoodDoc#someHash',
|
|
||||||
'/docs/anotherGoodDoc?someQueryString=true#someHash',
|
|
||||||
'../docs/anotherGoodDoc?someQueryString=true',
|
|
||||||
'../docs/anotherGoodDoc#someHash',
|
|
||||||
// Bad links
|
|
||||||
'../anotherGoodDoc#reported-because-of-bad-relative-path1',
|
|
||||||
'./docThatDoesNotExist2',
|
|
||||||
'./badRelativeLink3',
|
|
||||||
'../badRelativeLink4',
|
|
||||||
],
|
|
||||||
'/community': [
|
|
||||||
// Good links
|
|
||||||
'/docs/goodDoc',
|
|
||||||
'/docs/anotherGoodDoc#someHash',
|
|
||||||
'./docs/goodDoc#someHash',
|
|
||||||
'./docs/anotherGoodDoc',
|
|
||||||
// Bad links
|
|
||||||
'/someNonExistentDoc1',
|
|
||||||
'/badLink2',
|
|
||||||
'./badLink3',
|
|
||||||
],
|
|
||||||
'/page1': [
|
|
||||||
link1,
|
|
||||||
linkToHtmlFile1,
|
|
||||||
linkToJavadoc1,
|
|
||||||
linkToHtmlFile2,
|
|
||||||
linkToJavadoc3,
|
|
||||||
linkToJavadoc4,
|
|
||||||
linkToEmptyFolder1, // Not filtered!
|
|
||||||
],
|
|
||||||
'/page2': [
|
|
||||||
link2,
|
|
||||||
linkToEmptyFolder2, // Not filtered!
|
|
||||||
linkToJavadoc2,
|
|
||||||
link3,
|
|
||||||
linkToJavadoc3,
|
|
||||||
linkToZipFile,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const outDir = path.resolve(__dirname, '__fixtures__/brokenLinks/outDir');
|
|
||||||
|
|
||||||
it('do not report anything for correct paths', async () => {
|
|
||||||
const consoleMock = jest
|
|
||||||
.spyOn(console, 'warn')
|
|
||||||
.mockImplementation(() => {});
|
|
||||||
const allCollectedCorrectLinks = {
|
|
||||||
'/docs/good doc with space': [
|
|
||||||
'./another%20good%20doc%20with%20space',
|
|
||||||
'./weird%20but%20good',
|
|
||||||
],
|
|
||||||
'/docs/goodDoc': [
|
|
||||||
'./anotherGoodDoc#someHash',
|
|
||||||
'/docs/anotherGoodDoc?someQueryString=true#someHash',
|
|
||||||
'../docs/anotherGoodDoc?someQueryString=true',
|
|
||||||
'../docs/anotherGoodDoc#someHash',
|
|
||||||
],
|
|
||||||
'/community': [
|
|
||||||
'/docs/goodDoc',
|
|
||||||
'/docs/anotherGoodDoc#someHash',
|
|
||||||
'./docs/goodDoc#someHash',
|
|
||||||
'./docs/anotherGoodDoc',
|
|
||||||
],
|
|
||||||
'/page1': [
|
|
||||||
linkToHtmlFile1,
|
|
||||||
linkToJavadoc1,
|
|
||||||
linkToHtmlFile2,
|
|
||||||
linkToJavadoc3,
|
|
||||||
linkToJavadoc4,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
await handleBrokenLinks({
|
|
||||||
allCollectedLinks: allCollectedCorrectLinks,
|
|
||||||
onBrokenLinks: 'warn',
|
|
||||||
routes,
|
|
||||||
baseUrl: '/',
|
|
||||||
outDir,
|
|
||||||
});
|
});
|
||||||
expect(consoleMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reports all broken links', async () => {
|
it('accepts valid relative link', async () => {
|
||||||
|
await testBrokenLinks({
|
||||||
|
routes: [{path: '/dir/page1'}, {path: '/dir/page2'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/dir/page1': {
|
||||||
|
links: ['./page2', '../dir/page2', '/dir/page2'],
|
||||||
|
anchors: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts valid link with anchor', async () => {
|
||||||
|
await testBrokenLinks({
|
||||||
|
routes: [{path: '/page1'}, {path: '/page2'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {links: ['/page2#page2anchor'], anchors: []},
|
||||||
|
'/page2': {links: [], anchors: ['page2anchor']},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts valid link with querystring + anchor', async () => {
|
||||||
|
await testBrokenLinks({
|
||||||
|
routes: [{path: '/page1'}, {path: '/page2'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {
|
||||||
|
links: ['/page2?age=42&theme=dark#page2anchor'],
|
||||||
|
anchors: [],
|
||||||
|
},
|
||||||
|
'/page2': {links: [], anchors: ['page2anchor']},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts valid link to self', async () => {
|
||||||
|
await testBrokenLinks({
|
||||||
|
routes: [{path: '/page1'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {
|
||||||
|
links: [
|
||||||
|
'/page1',
|
||||||
|
'./page1',
|
||||||
|
'',
|
||||||
|
'/page1#anchor1',
|
||||||
|
'#anchor1',
|
||||||
|
'/page1?age=42#anchor1',
|
||||||
|
'?age=42#anchor1',
|
||||||
|
],
|
||||||
|
anchors: ['anchor1'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts valid link with spaces and encoding', async () => {
|
||||||
|
await testBrokenLinks({
|
||||||
|
routes: [{path: '/page 1'}, {path: '/page 2'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page 1': {
|
||||||
|
links: [
|
||||||
|
'/page 1',
|
||||||
|
'/page%201',
|
||||||
|
'/page%201?age=42',
|
||||||
|
'/page 2',
|
||||||
|
'/page%202',
|
||||||
|
'/page%202?age=42',
|
||||||
|
'/page%202?age=42#page2anchor',
|
||||||
|
],
|
||||||
|
anchors: [],
|
||||||
|
},
|
||||||
|
'/page 2': {links: [], anchors: ['page2anchor']},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects broken link', async () => {
|
||||||
await expect(() =>
|
await expect(() =>
|
||||||
handleBrokenLinks({
|
testBrokenLinks({
|
||||||
allCollectedLinks,
|
routes: [{path: '/page1'}, {path: '/page2'}],
|
||||||
onBrokenLinks: 'throw',
|
collectedLinks: {
|
||||||
routes,
|
'/page1': {links: ['/brokenLink'], anchors: []},
|
||||||
baseUrl: '/',
|
},
|
||||||
outDir,
|
|
||||||
}),
|
}),
|
||||||
).rejects.toThrowErrorMatchingSnapshot();
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"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:
|
||||||
|
- Broken link on source page path = /page1:
|
||||||
|
-> linking to /brokenLink
|
||||||
|
"
|
||||||
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('no-op for ignore', async () => {
|
it('rejects broken link with anchor', async () => {
|
||||||
// In any case, _.mapValues will always be called, unless handleBrokenLinks
|
await expect(() =>
|
||||||
// has already bailed
|
testBrokenLinks({
|
||||||
const lodashMock = jest.spyOn(_, 'mapValues');
|
routes: [{path: '/page1'}, {path: '/page2'}],
|
||||||
await handleBrokenLinks({
|
collectedLinks: {
|
||||||
allCollectedLinks,
|
'/page1': {links: ['/brokenLink#anchor'], anchors: []},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"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:
|
||||||
|
- Broken link on source page path = /page1:
|
||||||
|
-> linking to /brokenLink#anchor
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects broken link with querystring + anchor', async () => {
|
||||||
|
await expect(() =>
|
||||||
|
testBrokenLinks({
|
||||||
|
routes: [{path: '/page1'}, {path: '/page2'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {links: ['/brokenLink?age=42#anchor'], anchors: []},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"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:
|
||||||
|
- Broken link on source page path = /page1:
|
||||||
|
-> linking to /brokenLink?age=42#anchor
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects valid link with broken anchor', async () => {
|
||||||
|
await expect(() =>
|
||||||
|
testBrokenLinks({
|
||||||
|
routes: [{path: '/page1'}, {path: '/page2'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {links: ['/page2#brokenAnchor'], anchors: []},
|
||||||
|
'/page2': {links: [], anchors: []},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Docusaurus found broken anchors!
|
||||||
|
|
||||||
|
Please check the pages of your site in the list below, and make sure you don't reference any anchor that does not exist.
|
||||||
|
Note: it's possible to ignore broken anchors with the 'onBrokenAnchors' Docusaurus configuration, and let the build pass.
|
||||||
|
|
||||||
|
Exhaustive list of all broken anchors found:
|
||||||
|
- Broken anchor on source page path = /page1:
|
||||||
|
-> linking to /page2#brokenAnchor
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects valid link with empty broken anchor', async () => {
|
||||||
|
await expect(() =>
|
||||||
|
testBrokenLinks({
|
||||||
|
routes: [{path: '/page1'}, {path: '/page2'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {links: ['/page2#'], anchors: []},
|
||||||
|
'/page2': {links: [], anchors: []},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Docusaurus found broken anchors!
|
||||||
|
|
||||||
|
Please check the pages of your site in the list below, and make sure you don't reference any anchor that does not exist.
|
||||||
|
Note: it's possible to ignore broken anchors with the 'onBrokenAnchors' Docusaurus configuration, and let the build pass.
|
||||||
|
|
||||||
|
Exhaustive list of all broken anchors found:
|
||||||
|
- Broken anchor on source page path = /page1:
|
||||||
|
-> linking to /page2#
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects valid link with broken anchor + query-string', async () => {
|
||||||
|
await expect(() =>
|
||||||
|
testBrokenLinks({
|
||||||
|
routes: [{path: '/page1'}, {path: '/page2'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {
|
||||||
|
links: ['/page2?age=42&theme=dark#brokenAnchor'],
|
||||||
|
anchors: [],
|
||||||
|
},
|
||||||
|
'/page2': {links: [], anchors: []},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Docusaurus found broken anchors!
|
||||||
|
|
||||||
|
Please check the pages of your site in the list below, and make sure you don't reference any anchor that does not exist.
|
||||||
|
Note: it's possible to ignore broken anchors with the 'onBrokenAnchors' Docusaurus configuration, and let the build pass.
|
||||||
|
|
||||||
|
Exhaustive list of all broken anchors found:
|
||||||
|
- Broken anchor on source page path = /page1:
|
||||||
|
-> linking to /page2?age=42&theme=dark#brokenAnchor
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects valid link with broken anchor to self', async () => {
|
||||||
|
await expect(() =>
|
||||||
|
testBrokenLinks({
|
||||||
|
routes: [{path: '/page1'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {
|
||||||
|
links: [
|
||||||
|
'/page1',
|
||||||
|
'',
|
||||||
|
'#goodAnchor',
|
||||||
|
'/page1#goodAnchor',
|
||||||
|
'/page1?age=42#goodAnchor',
|
||||||
|
'#badAnchor1',
|
||||||
|
'/page1#badAnchor2',
|
||||||
|
'/page1?age=42#badAnchor3',
|
||||||
|
],
|
||||||
|
|
||||||
|
anchors: ['goodAnchor'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Docusaurus found broken anchors!
|
||||||
|
|
||||||
|
Please check the pages of your site in the list below, and make sure you don't reference any anchor that does not exist.
|
||||||
|
Note: it's possible to ignore broken anchors with the 'onBrokenAnchors' Docusaurus configuration, and let the build pass.
|
||||||
|
|
||||||
|
Exhaustive list of all broken anchors found:
|
||||||
|
- Broken anchor on source page path = /page1:
|
||||||
|
-> linking to #badAnchor1 (resolved as: /page1#badAnchor1)
|
||||||
|
-> linking to /page1#badAnchor2
|
||||||
|
-> linking to /page1?age=42#badAnchor3
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects valid link with broken anchor to uncollected page', async () => {
|
||||||
|
await expect(() =>
|
||||||
|
testBrokenLinks({
|
||||||
|
routes: [{path: '/page1'}, {path: '/page2'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {links: ['/page2#brokenAnchor'], anchors: []},
|
||||||
|
// /page2 is absent on purpose: it doesn't contain any link/anchor
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Docusaurus found broken anchors!
|
||||||
|
|
||||||
|
Please check the pages of your site in the list below, and make sure you don't reference any anchor that does not exist.
|
||||||
|
Note: it's possible to ignore broken anchors with the 'onBrokenAnchors' Docusaurus configuration, and let the build pass.
|
||||||
|
|
||||||
|
Exhaustive list of all broken anchors found:
|
||||||
|
- Broken anchor on source page path = /page1:
|
||||||
|
-> linking to /page2#brokenAnchor
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects broken anchor with query-string to uncollected page', async () => {
|
||||||
|
await expect(() =>
|
||||||
|
testBrokenLinks({
|
||||||
|
routes: [{path: '/page1'}, {path: '/page2'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {
|
||||||
|
links: ['/page2?age=42&theme=dark#brokenAnchor'],
|
||||||
|
anchors: [],
|
||||||
|
},
|
||||||
|
// /page2 is absent on purpose: it doesn't contain any link/anchor
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Docusaurus found broken anchors!
|
||||||
|
|
||||||
|
Please check the pages of your site in the list below, and make sure you don't reference any anchor that does not exist.
|
||||||
|
Note: it's possible to ignore broken anchors with the 'onBrokenAnchors' Docusaurus configuration, and let the build pass.
|
||||||
|
|
||||||
|
Exhaustive list of all broken anchors found:
|
||||||
|
- Broken anchor on source page path = /page1:
|
||||||
|
-> linking to /page2?age=42&theme=dark#brokenAnchor
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can ignore broken links', async () => {
|
||||||
|
await testBrokenLinks({
|
||||||
onBrokenLinks: 'ignore',
|
onBrokenLinks: 'ignore',
|
||||||
routes,
|
routes: [{path: '/page1'}],
|
||||||
baseUrl: '/',
|
collectedLinks: {
|
||||||
outDir,
|
'/page1': {
|
||||||
|
links: ['/page2'],
|
||||||
|
anchors: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
expect(lodashMock).toHaveBeenCalledTimes(0);
|
|
||||||
lodashMock.mockRestore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reports frequent broken links', async () => {
|
it('can ignore broken anchors', async () => {
|
||||||
Object.values(allCollectedLinks).forEach((links) =>
|
await testBrokenLinks({
|
||||||
links.push(
|
onBrokenAnchors: 'ignore',
|
||||||
'/frequent',
|
routes: [{path: '/page1'}],
|
||||||
// This is in the gray area of what should be reported. Relative paths
|
collectedLinks: {
|
||||||
// may be resolved to different slugs on different locations. But if
|
'/page1': {
|
||||||
// this comes from a layout link, it should be reported anyways
|
links: ['/page1#brokenAnchor'],
|
||||||
'./maybe-not',
|
anchors: [],
|
||||||
),
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can ignore broken anchors but report broken link', async () => {
|
||||||
|
await expect(() =>
|
||||||
|
testBrokenLinks({
|
||||||
|
onBrokenAnchors: 'ignore',
|
||||||
|
routes: [{path: '/page1'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {
|
||||||
|
links: ['/page1#brokenAnchor', '/page2'],
|
||||||
|
anchors: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"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:
|
||||||
|
- Broken link on source page path = /page1:
|
||||||
|
-> linking to /page2
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can ignore broken link but report broken anchors', async () => {
|
||||||
|
await expect(() =>
|
||||||
|
testBrokenLinks({
|
||||||
|
onBrokenLinks: 'ignore',
|
||||||
|
routes: [{path: '/page1'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {
|
||||||
|
links: [
|
||||||
|
'/page2',
|
||||||
|
'/page1#brokenAnchor1',
|
||||||
|
'/page1#brokenAnchor2',
|
||||||
|
'#brokenAnchor3',
|
||||||
|
],
|
||||||
|
|
||||||
|
anchors: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Docusaurus found broken anchors!
|
||||||
|
|
||||||
|
Please check the pages of your site in the list below, and make sure you don't reference any anchor that does not exist.
|
||||||
|
Note: it's possible to ignore broken anchors with the 'onBrokenAnchors' Docusaurus configuration, and let the build pass.
|
||||||
|
|
||||||
|
Exhaustive list of all broken anchors found:
|
||||||
|
- Broken anchor on source page path = /page1:
|
||||||
|
-> linking to /page1#brokenAnchor1
|
||||||
|
-> linking to /page1#brokenAnchor2
|
||||||
|
-> linking to #brokenAnchor3 (resolved as: /page1#brokenAnchor3)
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can warn for broken links', async () => {
|
||||||
|
const warnMock = jest.spyOn(console, 'warn');
|
||||||
|
|
||||||
|
await testBrokenLinks({
|
||||||
|
onBrokenLinks: 'warn',
|
||||||
|
routes: [{path: '/page1'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {
|
||||||
|
links: ['/page2'],
|
||||||
|
anchors: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(warnMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(warnMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"[WARNING] 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:
|
||||||
|
- Broken link on source page path = /page1:
|
||||||
|
-> linking to /page2
|
||||||
|
",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
warnMock.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can warn for broken anchors', async () => {
|
||||||
|
const warnMock = jest.spyOn(console, 'warn');
|
||||||
|
|
||||||
|
await testBrokenLinks({
|
||||||
|
onBrokenAnchors: 'warn',
|
||||||
|
routes: [{path: '/page1'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {
|
||||||
|
links: ['/page1#brokenAnchor'],
|
||||||
|
anchors: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(warnMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(warnMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"[WARNING] Docusaurus found broken anchors!
|
||||||
|
|
||||||
|
Please check the pages of your site in the list below, and make sure you don't reference any anchor that does not exist.
|
||||||
|
Note: it's possible to ignore broken anchors with the 'onBrokenAnchors' Docusaurus configuration, and let the build pass.
|
||||||
|
|
||||||
|
Exhaustive list of all broken anchors found:
|
||||||
|
- Broken anchor on source page path = /page1:
|
||||||
|
-> linking to /page1#brokenAnchor
|
||||||
|
",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
warnMock.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can warn for both broken links and anchors', async () => {
|
||||||
|
const warnMock = jest.spyOn(console, 'warn');
|
||||||
|
|
||||||
|
await testBrokenLinks({
|
||||||
|
onBrokenLinks: 'warn',
|
||||||
|
onBrokenAnchors: 'warn',
|
||||||
|
routes: [{path: '/page1'}],
|
||||||
|
collectedLinks: {
|
||||||
|
'/page1': {
|
||||||
|
links: ['/page1#brokenAnchor', '/page2'],
|
||||||
|
anchors: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(warnMock).toHaveBeenCalledTimes(2);
|
||||||
|
expect(warnMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"[WARNING] 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:
|
||||||
|
- Broken link on source page path = /page1:
|
||||||
|
-> linking to /page2
|
||||||
|
",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"[WARNING] Docusaurus found broken anchors!
|
||||||
|
|
||||||
|
Please check the pages of your site in the list below, and make sure you don't reference any anchor that does not exist.
|
||||||
|
Note: it's possible to ignore broken anchors with the 'onBrokenAnchors' Docusaurus configuration, and let the build pass.
|
||||||
|
|
||||||
|
Exhaustive list of all broken anchors found:
|
||||||
|
- Broken anchor on source page path = /page1:
|
||||||
|
-> linking to /page1#brokenAnchor
|
||||||
|
",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
warnMock.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports frequent broken links differently', async () => {
|
||||||
|
const pagePaths = [
|
||||||
|
'/page1',
|
||||||
|
'/page2',
|
||||||
|
'/dir/page3',
|
||||||
|
'/dir/page4',
|
||||||
|
'/dir/page5',
|
||||||
|
];
|
||||||
|
|
||||||
|
const routes: SimpleRoute[] = pagePaths.map((pagePath) => ({
|
||||||
|
path: pagePath,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const collectedLinks: Params['collectedLinks'] = Object.fromEntries(
|
||||||
|
pagePaths.map((pagePath) => [
|
||||||
|
pagePath,
|
||||||
|
{
|
||||||
|
links: ['/frequentBrokenLink', './relativeFrequentBrokenLink'],
|
||||||
|
anchors: [],
|
||||||
|
},
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(() =>
|
await expect(() =>
|
||||||
handleBrokenLinks({
|
testBrokenLinks({
|
||||||
allCollectedLinks,
|
|
||||||
onBrokenLinks: 'throw',
|
|
||||||
routes,
|
routes,
|
||||||
baseUrl: '/',
|
collectedLinks,
|
||||||
outDir,
|
|
||||||
}),
|
}),
|
||||||
).rejects.toThrowErrorMatchingSnapshot();
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"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:
|
||||||
|
- /frequentBrokenLink
|
||||||
|
- ./relativeFrequentBrokenLink
|
||||||
|
|
||||||
|
Exhaustive list of all broken links found:
|
||||||
|
- Broken link on source page path = /page1:
|
||||||
|
-> linking to /frequentBrokenLink
|
||||||
|
-> linking to ./relativeFrequentBrokenLink (resolved as: /relativeFrequentBrokenLink)
|
||||||
|
- Broken link on source page path = /page2:
|
||||||
|
-> linking to /frequentBrokenLink
|
||||||
|
-> linking to ./relativeFrequentBrokenLink (resolved as: /relativeFrequentBrokenLink)
|
||||||
|
- Broken link on source page path = /dir/page3:
|
||||||
|
-> linking to /frequentBrokenLink
|
||||||
|
-> linking to ./relativeFrequentBrokenLink (resolved as: /dir/relativeFrequentBrokenLink)
|
||||||
|
- Broken link on source page path = /dir/page4:
|
||||||
|
-> linking to /frequentBrokenLink
|
||||||
|
-> linking to ./relativeFrequentBrokenLink (resolved as: /dir/relativeFrequentBrokenLink)
|
||||||
|
- Broken link on source page path = /dir/page5:
|
||||||
|
-> linking to /frequentBrokenLink
|
||||||
|
-> linking to ./relativeFrequentBrokenLink (resolved as: /dir/relativeFrequentBrokenLink)
|
||||||
|
"
|
||||||
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -61,12 +61,17 @@ describe('normalizeConfig', () => {
|
||||||
markdown: {
|
markdown: {
|
||||||
format: 'md',
|
format: 'md',
|
||||||
mermaid: true,
|
mermaid: true,
|
||||||
|
parseFrontMatter: async (params) =>
|
||||||
|
params.defaultParseFrontMatter(params),
|
||||||
preprocessor: ({fileContent}) => fileContent,
|
preprocessor: ({fileContent}) => fileContent,
|
||||||
mdx1Compat: {
|
mdx1Compat: {
|
||||||
comments: true,
|
comments: true,
|
||||||
admonitions: false,
|
admonitions: false,
|
||||||
headingIds: true,
|
headingIds: true,
|
||||||
},
|
},
|
||||||
|
remarkRehypeOptions: {
|
||||||
|
footnoteLabel: 'Pied de page',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const normalizedConfig = normalizeConfig(userConfig);
|
const normalizedConfig = normalizeConfig(userConfig);
|
||||||
|
@ -504,12 +509,19 @@ describe('markdown', () => {
|
||||||
const markdown: DocusaurusConfig['markdown'] = {
|
const markdown: DocusaurusConfig['markdown'] = {
|
||||||
format: 'md',
|
format: 'md',
|
||||||
mermaid: true,
|
mermaid: true,
|
||||||
|
parseFrontMatter: async (params) =>
|
||||||
|
params.defaultParseFrontMatter(params),
|
||||||
preprocessor: ({fileContent}) => fileContent,
|
preprocessor: ({fileContent}) => fileContent,
|
||||||
mdx1Compat: {
|
mdx1Compat: {
|
||||||
comments: false,
|
comments: false,
|
||||||
admonitions: true,
|
admonitions: true,
|
||||||
headingIds: false,
|
headingIds: false,
|
||||||
},
|
},
|
||||||
|
remarkRehypeOptions: {
|
||||||
|
footnoteLabel: 'Notes de bas de page',
|
||||||
|
// @ts-expect-error: we don't validate it on purpose
|
||||||
|
anyKey: 'heck we accept it on purpose',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
normalizeConfig({
|
normalizeConfig({
|
||||||
|
|
|
@ -5,45 +5,42 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from 'fs-extra';
|
|
||||||
import path from 'path';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import logger from '@docusaurus/logger';
|
import logger from '@docusaurus/logger';
|
||||||
import combinePromises from 'combine-promises';
|
|
||||||
import {matchRoutes} from 'react-router-config';
|
import {matchRoutes} from 'react-router-config';
|
||||||
import {removePrefix, removeSuffix, resolvePathname} from '@docusaurus/utils';
|
import {parseURLPath, serializeURLPath, type URLPath} from '@docusaurus/utils';
|
||||||
import {getAllFinalRoutes} from './utils';
|
import {getAllFinalRoutes} from './utils';
|
||||||
import type {RouteConfig, ReportingSeverity} from '@docusaurus/types';
|
import type {RouteConfig, ReportingSeverity} from '@docusaurus/types';
|
||||||
|
|
||||||
type BrokenLink = {
|
type BrokenLink = {
|
||||||
link: string;
|
link: string;
|
||||||
resolvedLink: string;
|
resolvedLink: string;
|
||||||
|
anchor: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// matchRoutes does not support qs/anchors, so we remove it!
|
type BrokenLinksMap = {[pathname: string]: BrokenLink[]};
|
||||||
function onlyPathname(link: string) {
|
|
||||||
return link.split('#')[0]!.split('?')[0]!;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPageBrokenLinks({
|
// The linking data that has been collected on Docusaurus pages during SSG
|
||||||
|
// {rendered page pathname => links and anchors collected on that page}
|
||||||
|
type CollectedLinks = {
|
||||||
|
[pathname: string]: {links: string[]; anchors: string[]};
|
||||||
|
};
|
||||||
|
|
||||||
|
function getBrokenLinksForPage({
|
||||||
|
collectedLinks,
|
||||||
pagePath,
|
pagePath,
|
||||||
pageLinks,
|
pageLinks,
|
||||||
routes,
|
routes,
|
||||||
}: {
|
}: {
|
||||||
|
collectedLinks: CollectedLinks;
|
||||||
pagePath: string;
|
pagePath: string;
|
||||||
pageLinks: string[];
|
pageLinks: string[];
|
||||||
|
pageAnchors: string[];
|
||||||
routes: RouteConfig[];
|
routes: RouteConfig[];
|
||||||
}): BrokenLink[] {
|
}): BrokenLink[] {
|
||||||
// ReactRouter is able to support links like ./../somePath but `matchRoutes`
|
// console.log('routes:', routes);
|
||||||
// does not do this resolution internally. We must resolve the links before
|
function isPathBrokenLink(linkPath: URLPath) {
|
||||||
// using `matchRoutes`. `resolvePathname` is used internally by React Router
|
const matchedRoutes = [linkPath.pathname, decodeURI(linkPath.pathname)]
|
||||||
function resolveLink(link: string) {
|
|
||||||
const resolvedLink = resolvePathname(onlyPathname(link), pagePath);
|
|
||||||
return {link, resolvedLink};
|
|
||||||
}
|
|
||||||
|
|
||||||
function isBrokenLink(link: string) {
|
|
||||||
const matchedRoutes = [link, decodeURI(link)]
|
|
||||||
// @ts-expect-error: React router types RouteConfig with an actual React
|
// @ts-expect-error: React router types RouteConfig with an actual React
|
||||||
// component, but we load route components with string paths.
|
// component, but we load route components with string paths.
|
||||||
// We don't actually access component here, so it's fine.
|
// We don't actually access component here, so it's fine.
|
||||||
|
@ -52,7 +49,52 @@ function getPageBrokenLinks({
|
||||||
return matchedRoutes.length === 0;
|
return matchedRoutes.length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pageLinks.map(resolveLink).filter((l) => isBrokenLink(l.resolvedLink));
|
function isAnchorBrokenLink(linkPath: URLPath) {
|
||||||
|
const {pathname, hash} = linkPath;
|
||||||
|
|
||||||
|
// Link has no hash: it can't be a broken anchor link
|
||||||
|
if (hash === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetPage =
|
||||||
|
collectedLinks[pathname] || collectedLinks[decodeURI(pathname)];
|
||||||
|
|
||||||
|
// link with anchor to a page that does not exist (or did not collect any
|
||||||
|
// link/anchor) is considered as a broken anchor
|
||||||
|
if (!targetPage) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's a broken anchor if the target page exists
|
||||||
|
// but the anchor does not exist on that page
|
||||||
|
return !targetPage.anchors.includes(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
const brokenLinks = pageLinks.flatMap((link) => {
|
||||||
|
const linkPath = parseURLPath(link, pagePath);
|
||||||
|
if (isPathBrokenLink(linkPath)) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
link,
|
||||||
|
resolvedLink: serializeURLPath(linkPath),
|
||||||
|
anchor: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (isAnchorBrokenLink(linkPath)) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
link,
|
||||||
|
resolvedLink: serializeURLPath(linkPath),
|
||||||
|
anchor: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
return brokenLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,45 +108,76 @@ function filterIntermediateRoutes(routesInput: RouteConfig[]): RouteConfig[] {
|
||||||
return getAllFinalRoutes(routesWithout404);
|
return getAllFinalRoutes(routesWithout404);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllBrokenLinks({
|
function getBrokenLinks({
|
||||||
allCollectedLinks,
|
collectedLinks,
|
||||||
routes,
|
routes,
|
||||||
}: {
|
}: {
|
||||||
allCollectedLinks: {[location: string]: string[]};
|
collectedLinks: CollectedLinks;
|
||||||
routes: RouteConfig[];
|
routes: RouteConfig[];
|
||||||
}): {[location: string]: BrokenLink[]} {
|
}): BrokenLinksMap {
|
||||||
const filteredRoutes = filterIntermediateRoutes(routes);
|
const filteredRoutes = filterIntermediateRoutes(routes);
|
||||||
|
|
||||||
const allBrokenLinks = _.mapValues(allCollectedLinks, (pageLinks, pagePath) =>
|
return _.mapValues(collectedLinks, (pageCollectedData, pagePath) =>
|
||||||
getPageBrokenLinks({pageLinks, pagePath, routes: filteredRoutes}),
|
getBrokenLinksForPage({
|
||||||
|
collectedLinks,
|
||||||
|
pageLinks: pageCollectedData.links,
|
||||||
|
pageAnchors: pageCollectedData.anchors,
|
||||||
|
pagePath,
|
||||||
|
routes: filteredRoutes,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return _.pickBy(allBrokenLinks, (brokenLinks) => brokenLinks.length > 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBrokenLinksErrorMessage(allBrokenLinks: {
|
function brokenLinkMessage(brokenLink: BrokenLink): string {
|
||||||
[location: string]: BrokenLink[];
|
const showResolvedLink = brokenLink.link !== brokenLink.resolvedLink;
|
||||||
}): string | undefined {
|
return `${brokenLink.link}${
|
||||||
if (Object.keys(allBrokenLinks).length === 0) {
|
showResolvedLink ? ` (resolved as: ${brokenLink.resolvedLink})` : ''
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBrokenLinksMessage(
|
||||||
|
pagePath: string,
|
||||||
|
brokenLinks: BrokenLink[],
|
||||||
|
): string {
|
||||||
|
const type = brokenLinks[0]?.anchor === true ? 'anchor' : 'link';
|
||||||
|
|
||||||
|
const anchorMessage =
|
||||||
|
brokenLinks.length > 0
|
||||||
|
? `- Broken ${type} on source page path = ${pagePath}:
|
||||||
|
-> linking to ${brokenLinks
|
||||||
|
.map(brokenLinkMessage)
|
||||||
|
.join('\n -> linking to ')}`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return `${anchorMessage}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBrokenAnchorsMessage(
|
||||||
|
brokenAnchors: BrokenLinksMap,
|
||||||
|
): string | undefined {
|
||||||
|
if (Object.keys(brokenAnchors).length === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function brokenLinkMessage(brokenLink: BrokenLink): string {
|
return `Docusaurus found broken anchors!
|
||||||
const showResolvedLink = brokenLink.link !== brokenLink.resolvedLink;
|
|
||||||
return `${brokenLink.link}${
|
|
||||||
showResolvedLink ? ` (resolved as: ${brokenLink.resolvedLink})` : ''
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function pageBrokenLinksMessage(
|
Please check the pages of your site in the list below, and make sure you don't reference any anchor that does not exist.
|
||||||
pagePath: string,
|
Note: it's possible to ignore broken anchors with the 'onBrokenAnchors' Docusaurus configuration, and let the build pass.
|
||||||
brokenLinks: BrokenLink[],
|
|
||||||
): string {
|
Exhaustive list of all broken anchors found:
|
||||||
return `
|
${Object.entries(brokenAnchors)
|
||||||
- On source page path = ${pagePath}:
|
.map(([pagePath, brokenLinks]) =>
|
||||||
-> linking to ${brokenLinks
|
createBrokenLinksMessage(pagePath, brokenLinks),
|
||||||
.map(brokenLinkMessage)
|
)
|
||||||
.join('\n -> linking to ')}`;
|
.join('\n')}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBrokenPathsMessage(
|
||||||
|
brokenPathsMap: BrokenLinksMap,
|
||||||
|
): string | undefined {
|
||||||
|
if (Object.keys(brokenPathsMap).length === 0) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -113,7 +186,7 @@ function getBrokenLinksErrorMessage(allBrokenLinks: {
|
||||||
* this out. See https://github.com/facebook/docusaurus/issues/3567#issuecomment-706973805
|
* this out. See https://github.com/facebook/docusaurus/issues/3567#issuecomment-706973805
|
||||||
*/
|
*/
|
||||||
function getLayoutBrokenLinksHelpMessage() {
|
function getLayoutBrokenLinksHelpMessage() {
|
||||||
const flatList = Object.entries(allBrokenLinks).flatMap(
|
const flatList = Object.entries(brokenPathsMap).flatMap(
|
||||||
([pagePage, brokenLinks]) =>
|
([pagePage, brokenLinks]) =>
|
||||||
brokenLinks.map((brokenLink) => ({pagePage, brokenLink})),
|
brokenLinks.map((brokenLink) => ({pagePage, brokenLink})),
|
||||||
);
|
);
|
||||||
|
@ -146,102 +219,78 @@ Please check the pages of your site in the list below, and make sure you don't r
|
||||||
Note: it's possible to ignore broken links with the 'onBrokenLinks' Docusaurus configuration, and let the build pass.${getLayoutBrokenLinksHelpMessage()}
|
Note: it's possible to ignore broken links with the 'onBrokenLinks' Docusaurus configuration, and let the build pass.${getLayoutBrokenLinksHelpMessage()}
|
||||||
|
|
||||||
Exhaustive list of all broken links found:
|
Exhaustive list of all broken links found:
|
||||||
${Object.entries(allBrokenLinks)
|
${Object.entries(brokenPathsMap)
|
||||||
.map(([pagePath, brokenLinks]) =>
|
.map(([pagePath, brokenPaths]) =>
|
||||||
pageBrokenLinksMessage(pagePath, brokenLinks),
|
createBrokenLinksMessage(pagePath, brokenPaths),
|
||||||
)
|
)
|
||||||
.join('\n')}
|
.join('\n')}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isExistingFile(filePath: string) {
|
function splitBrokenLinks(brokenLinks: BrokenLinksMap): {
|
||||||
try {
|
brokenPaths: BrokenLinksMap;
|
||||||
return (await fs.stat(filePath)).isFile();
|
brokenAnchors: BrokenLinksMap;
|
||||||
} catch {
|
} {
|
||||||
return false;
|
const brokenPaths: BrokenLinksMap = {};
|
||||||
}
|
const brokenAnchors: BrokenLinksMap = {};
|
||||||
}
|
|
||||||
|
|
||||||
// If a file actually exist on the file system, we know the link is valid
|
Object.entries(brokenLinks).forEach(([pathname, pageBrokenLinks]) => {
|
||||||
// even if docusaurus does not know about this file, so we don't report it
|
const [anchorBrokenLinks, pathBrokenLinks] = _.partition(
|
||||||
async function filterExistingFileLinks({
|
pageBrokenLinks,
|
||||||
baseUrl,
|
(link) => link.anchor,
|
||||||
outDir,
|
|
||||||
allCollectedLinks,
|
|
||||||
}: {
|
|
||||||
baseUrl: string;
|
|
||||||
outDir: string;
|
|
||||||
allCollectedLinks: {[location: string]: string[]};
|
|
||||||
}): Promise<{[location: string]: string[]}> {
|
|
||||||
async function linkFileExists(link: string) {
|
|
||||||
// /baseUrl/javadoc/ -> /outDir/javadoc
|
|
||||||
const baseFilePath = onlyPathname(
|
|
||||||
removeSuffix(`${outDir}/${removePrefix(link, baseUrl)}`, '/'),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// -> /outDir/javadoc
|
if (pathBrokenLinks.length > 0) {
|
||||||
// -> /outDir/javadoc.html
|
brokenPaths[pathname] = pathBrokenLinks;
|
||||||
// -> /outDir/javadoc/index.html
|
|
||||||
const filePathsToTry: string[] = [baseFilePath];
|
|
||||||
if (!path.extname(baseFilePath)) {
|
|
||||||
filePathsToTry.push(
|
|
||||||
`${baseFilePath}.html`,
|
|
||||||
path.join(baseFilePath, 'index.html'),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
if (anchorBrokenLinks.length > 0) {
|
||||||
|
brokenAnchors[pathname] = anchorBrokenLinks;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
for (const file of filePathsToTry) {
|
return {brokenPaths, brokenAnchors};
|
||||||
if (await isExistingFile(file)) {
|
}
|
||||||
return true;
|
|
||||||
}
|
function reportBrokenLinks({
|
||||||
}
|
brokenLinks,
|
||||||
return false;
|
onBrokenLinks,
|
||||||
|
onBrokenAnchors,
|
||||||
|
}: {
|
||||||
|
brokenLinks: BrokenLinksMap;
|
||||||
|
onBrokenLinks: ReportingSeverity;
|
||||||
|
onBrokenAnchors: ReportingSeverity;
|
||||||
|
}) {
|
||||||
|
// We need to split the broken links reporting in 2 for better granularity
|
||||||
|
// This is because we need to report broken path/anchors independently
|
||||||
|
// For v3.x retro-compatibility, we can't throw by default for broken anchors
|
||||||
|
// TODO Docusaurus v4: make onBrokenAnchors throw by default?
|
||||||
|
const {brokenPaths, brokenAnchors} = splitBrokenLinks(brokenLinks);
|
||||||
|
|
||||||
|
const pathErrorMessage = createBrokenPathsMessage(brokenPaths);
|
||||||
|
if (pathErrorMessage) {
|
||||||
|
logger.report(onBrokenLinks)(pathErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return combinePromises(
|
const anchorErrorMessage = createBrokenAnchorsMessage(brokenAnchors);
|
||||||
_.mapValues(allCollectedLinks, async (links) =>
|
if (anchorErrorMessage) {
|
||||||
(
|
logger.report(onBrokenAnchors)(anchorErrorMessage);
|
||||||
await Promise.all(
|
}
|
||||||
links.map(async (link) => ((await linkFileExists(link)) ? '' : link)),
|
|
||||||
)
|
|
||||||
).filter(Boolean),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleBrokenLinks({
|
export async function handleBrokenLinks({
|
||||||
allCollectedLinks,
|
collectedLinks,
|
||||||
onBrokenLinks,
|
onBrokenLinks,
|
||||||
|
onBrokenAnchors,
|
||||||
routes,
|
routes,
|
||||||
baseUrl,
|
|
||||||
outDir,
|
|
||||||
}: {
|
}: {
|
||||||
allCollectedLinks: {[location: string]: string[]};
|
collectedLinks: CollectedLinks;
|
||||||
onBrokenLinks: ReportingSeverity;
|
onBrokenLinks: ReportingSeverity;
|
||||||
|
onBrokenAnchors: ReportingSeverity;
|
||||||
routes: RouteConfig[];
|
routes: RouteConfig[];
|
||||||
baseUrl: string;
|
|
||||||
outDir: string;
|
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
if (onBrokenLinks === 'ignore') {
|
if (onBrokenLinks === 'ignore' && onBrokenAnchors === 'ignore') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const brokenLinks = getBrokenLinks({routes, collectedLinks});
|
||||||
// If we link to a file like /myFile.zip, and the file actually exist for the
|
reportBrokenLinks({brokenLinks, onBrokenLinks, onBrokenAnchors});
|
||||||
// file system. It is not a broken link, it may simply be a link to an
|
|
||||||
// existing static file...
|
|
||||||
const allCollectedLinksFiltered = await filterExistingFileLinks({
|
|
||||||
allCollectedLinks,
|
|
||||||
baseUrl,
|
|
||||||
outDir,
|
|
||||||
});
|
|
||||||
|
|
||||||
const allBrokenLinks = getAllBrokenLinks({
|
|
||||||
allCollectedLinks: allCollectedLinksFiltered,
|
|
||||||
routes,
|
|
||||||
});
|
|
||||||
|
|
||||||
const errorMessage = getBrokenLinksErrorMessage(allBrokenLinks);
|
|
||||||
if (errorMessage) {
|
|
||||||
logger.report(onBrokenLinks)(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
DEFAULT_PARSE_FRONT_MATTER,
|
||||||
DEFAULT_STATIC_DIR_NAME,
|
DEFAULT_STATIC_DIR_NAME,
|
||||||
DEFAULT_I18N_DIR_NAME,
|
DEFAULT_I18N_DIR_NAME,
|
||||||
addLeadingSlash,
|
addLeadingSlash,
|
||||||
|
@ -13,7 +14,11 @@ import {
|
||||||
removeTrailingSlash,
|
removeTrailingSlash,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {Joi, printWarning} from '@docusaurus/utils-validation';
|
import {Joi, printWarning} from '@docusaurus/utils-validation';
|
||||||
import type {DocusaurusConfig, I18nConfig} from '@docusaurus/types';
|
import type {
|
||||||
|
DocusaurusConfig,
|
||||||
|
I18nConfig,
|
||||||
|
MarkdownConfig,
|
||||||
|
} from '@docusaurus/types';
|
||||||
|
|
||||||
const DEFAULT_I18N_LOCALE = 'en';
|
const DEFAULT_I18N_LOCALE = 'en';
|
||||||
|
|
||||||
|
@ -24,10 +29,24 @@ export const DEFAULT_I18N_CONFIG: I18nConfig = {
|
||||||
localeConfigs: {},
|
localeConfigs: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_MARKDOWN_CONFIG: MarkdownConfig = {
|
||||||
|
format: 'mdx', // TODO change this to "detect" in Docusaurus v4?
|
||||||
|
mermaid: false,
|
||||||
|
preprocessor: undefined,
|
||||||
|
parseFrontMatter: DEFAULT_PARSE_FRONT_MATTER,
|
||||||
|
mdx1Compat: {
|
||||||
|
comments: true,
|
||||||
|
admonitions: true,
|
||||||
|
headingIds: true,
|
||||||
|
},
|
||||||
|
remarkRehypeOptions: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
export const DEFAULT_CONFIG: Pick<
|
export const DEFAULT_CONFIG: Pick<
|
||||||
DocusaurusConfig,
|
DocusaurusConfig,
|
||||||
| 'i18n'
|
| 'i18n'
|
||||||
| 'onBrokenLinks'
|
| 'onBrokenLinks'
|
||||||
|
| 'onBrokenAnchors'
|
||||||
| 'onBrokenMarkdownLinks'
|
| 'onBrokenMarkdownLinks'
|
||||||
| 'onDuplicateRoutes'
|
| 'onDuplicateRoutes'
|
||||||
| 'plugins'
|
| 'plugins'
|
||||||
|
@ -48,6 +67,7 @@ export const DEFAULT_CONFIG: Pick<
|
||||||
> = {
|
> = {
|
||||||
i18n: DEFAULT_I18N_CONFIG,
|
i18n: DEFAULT_I18N_CONFIG,
|
||||||
onBrokenLinks: 'throw',
|
onBrokenLinks: 'throw',
|
||||||
|
onBrokenAnchors: 'warn', // TODO Docusaurus v4: change to throw
|
||||||
onBrokenMarkdownLinks: 'warn',
|
onBrokenMarkdownLinks: 'warn',
|
||||||
onDuplicateRoutes: 'warn',
|
onDuplicateRoutes: 'warn',
|
||||||
plugins: [],
|
plugins: [],
|
||||||
|
@ -64,37 +84,26 @@ export const DEFAULT_CONFIG: Pick<
|
||||||
tagline: '',
|
tagline: '',
|
||||||
baseUrlIssueBanner: true,
|
baseUrlIssueBanner: true,
|
||||||
staticDirectories: [DEFAULT_STATIC_DIR_NAME],
|
staticDirectories: [DEFAULT_STATIC_DIR_NAME],
|
||||||
markdown: {
|
markdown: DEFAULT_MARKDOWN_CONFIG,
|
||||||
format: 'mdx', // TODO change this to "detect" in Docusaurus v4?
|
|
||||||
mermaid: false,
|
|
||||||
preprocessor: undefined,
|
|
||||||
mdx1Compat: {
|
|
||||||
comments: true,
|
|
||||||
admonitions: true,
|
|
||||||
headingIds: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function createPluginSchema(theme: boolean) {
|
function createPluginSchema(theme: boolean) {
|
||||||
return (
|
return Joi.alternatives()
|
||||||
Joi.alternatives()
|
.try(
|
||||||
.try(
|
Joi.function(),
|
||||||
Joi.function(),
|
Joi.array()
|
||||||
Joi.array()
|
.ordered(Joi.function().required(), Joi.object().required())
|
||||||
.ordered(Joi.function().required(), Joi.object().required())
|
.length(2),
|
||||||
.length(2),
|
Joi.string(),
|
||||||
Joi.string(),
|
Joi.array()
|
||||||
Joi.array()
|
.ordered(Joi.string().required(), Joi.object().required())
|
||||||
.ordered(Joi.string().required(), Joi.object().required())
|
.length(2),
|
||||||
.length(2),
|
Joi.any().valid(false, null),
|
||||||
Joi.any().valid(false, null),
|
)
|
||||||
)
|
.error((errors) => {
|
||||||
// @ts-expect-error: bad lib def, doesn't recognize an array of reports
|
errors.forEach((error) => {
|
||||||
.error((errors) => {
|
const validConfigExample = theme
|
||||||
errors.forEach((error) => {
|
? `Example valid theme config:
|
||||||
const validConfigExample = theme
|
|
||||||
? `Example valid theme config:
|
|
||||||
{
|
{
|
||||||
themes: [
|
themes: [
|
||||||
["@docusaurus/theme-classic",options],
|
["@docusaurus/theme-classic",options],
|
||||||
|
@ -104,7 +113,7 @@ function createPluginSchema(theme: boolean) {
|
||||||
[function myTheme() { },options]
|
[function myTheme() { },options]
|
||||||
],
|
],
|
||||||
};`
|
};`
|
||||||
: `Example valid plugin config:
|
: `Example valid plugin config:
|
||||||
{
|
{
|
||||||
plugins: [
|
plugins: [
|
||||||
["@docusaurus/plugin-content-docs",options],
|
["@docusaurus/plugin-content-docs",options],
|
||||||
|
@ -115,17 +124,16 @@ function createPluginSchema(theme: boolean) {
|
||||||
],
|
],
|
||||||
};`;
|
};`;
|
||||||
|
|
||||||
error.message = ` => Bad Docusaurus ${
|
error.message = ` => Bad Docusaurus ${
|
||||||
theme ? 'theme' : 'plugin'
|
theme ? 'theme' : 'plugin'
|
||||||
} value ${error.path.reduce((acc, cur) =>
|
} value ${error.path.reduce((acc, cur) =>
|
||||||
typeof cur === 'string' ? `${acc}.${cur}` : `${acc}[${cur}]`,
|
typeof cur === 'string' ? `${acc}.${cur}` : `${acc}[${cur}]`,
|
||||||
)}.
|
)}.
|
||||||
${validConfigExample}
|
${validConfigExample}
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
return errors;
|
return errors;
|
||||||
})
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PluginSchema = createPluginSchema(false);
|
const PluginSchema = createPluginSchema(false);
|
||||||
|
@ -202,6 +210,9 @@ export const ConfigSchema = Joi.object<DocusaurusConfig>({
|
||||||
onBrokenLinks: Joi.string()
|
onBrokenLinks: Joi.string()
|
||||||
.equal('ignore', 'log', 'warn', 'throw')
|
.equal('ignore', 'log', 'warn', 'throw')
|
||||||
.default(DEFAULT_CONFIG.onBrokenLinks),
|
.default(DEFAULT_CONFIG.onBrokenLinks),
|
||||||
|
onBrokenAnchors: Joi.string()
|
||||||
|
.equal('ignore', 'log', 'warn', 'throw')
|
||||||
|
.default(DEFAULT_CONFIG.onBrokenAnchors),
|
||||||
onBrokenMarkdownLinks: Joi.string()
|
onBrokenMarkdownLinks: Joi.string()
|
||||||
.equal('ignore', 'log', 'warn', 'throw')
|
.equal('ignore', 'log', 'warn', 'throw')
|
||||||
.default(DEFAULT_CONFIG.onBrokenMarkdownLinks),
|
.default(DEFAULT_CONFIG.onBrokenMarkdownLinks),
|
||||||
|
@ -280,6 +291,9 @@ export const ConfigSchema = Joi.object<DocusaurusConfig>({
|
||||||
format: Joi.string()
|
format: Joi.string()
|
||||||
.equal('mdx', 'md', 'detect')
|
.equal('mdx', 'md', 'detect')
|
||||||
.default(DEFAULT_CONFIG.markdown.format),
|
.default(DEFAULT_CONFIG.markdown.format),
|
||||||
|
parseFrontMatter: Joi.function().default(
|
||||||
|
() => DEFAULT_CONFIG.markdown.parseFrontMatter,
|
||||||
|
),
|
||||||
mermaid: Joi.boolean().default(DEFAULT_CONFIG.markdown.mermaid),
|
mermaid: Joi.boolean().default(DEFAULT_CONFIG.markdown.mermaid),
|
||||||
preprocessor: Joi.function()
|
preprocessor: Joi.function()
|
||||||
.arity(1)
|
.arity(1)
|
||||||
|
@ -296,6 +310,11 @@ export const ConfigSchema = Joi.object<DocusaurusConfig>({
|
||||||
DEFAULT_CONFIG.markdown.mdx1Compat.headingIds,
|
DEFAULT_CONFIG.markdown.mdx1Compat.headingIds,
|
||||||
),
|
),
|
||||||
}).default(DEFAULT_CONFIG.markdown.mdx1Compat),
|
}).default(DEFAULT_CONFIG.markdown.mdx1Compat),
|
||||||
|
remarkRehypeOptions:
|
||||||
|
// add proper external options validation?
|
||||||
|
// Not sure if it's a good idea, validation is likely to become stale
|
||||||
|
// See https://github.com/remarkjs/remark-rehype#options
|
||||||
|
Joi.object().unknown(),
|
||||||
}).default(DEFAULT_CONFIG.markdown),
|
}).default(DEFAULT_CONFIG.markdown),
|
||||||
}).messages({
|
}).messages({
|
||||||
'docusaurus.configValidationWarning':
|
'docusaurus.configValidationWarning':
|
||||||
|
|
|
@ -16,6 +16,7 @@ exports[`base webpack config creates webpack aliases 1`] = `
|
||||||
"@docusaurus/renderRoutes": "../../../../client/exports/renderRoutes.ts",
|
"@docusaurus/renderRoutes": "../../../../client/exports/renderRoutes.ts",
|
||||||
"@docusaurus/router": "../../../../client/exports/router.ts",
|
"@docusaurus/router": "../../../../client/exports/router.ts",
|
||||||
"@docusaurus/useBaseUrl": "../../../../client/exports/useBaseUrl.ts",
|
"@docusaurus/useBaseUrl": "../../../../client/exports/useBaseUrl.ts",
|
||||||
|
"@docusaurus/useBrokenLinks": "../../../../client/exports/useBrokenLinks.ts",
|
||||||
"@docusaurus/useDocusaurusContext": "../../../../client/exports/useDocusaurusContext.ts",
|
"@docusaurus/useDocusaurusContext": "../../../../client/exports/useDocusaurusContext.ts",
|
||||||
"@docusaurus/useGlobalData": "../../../../client/exports/useGlobalData.ts",
|
"@docusaurus/useGlobalData": "../../../../client/exports/useGlobalData.ts",
|
||||||
"@docusaurus/useIsBrowser": "../../../../client/exports/useIsBrowser.ts",
|
"@docusaurus/useIsBrowser": "../../../../client/exports/useIsBrowser.ts",
|
||||||
|
|
|
@ -16,6 +16,7 @@ exports[`getDocusaurusAliases returns appropriate webpack aliases 1`] = `
|
||||||
"@docusaurus/renderRoutes": "<PROJECT_ROOT>/packages/docusaurus/src/client/exports/renderRoutes.ts",
|
"@docusaurus/renderRoutes": "<PROJECT_ROOT>/packages/docusaurus/src/client/exports/renderRoutes.ts",
|
||||||
"@docusaurus/router": "<PROJECT_ROOT>/packages/docusaurus/src/client/exports/router.ts",
|
"@docusaurus/router": "<PROJECT_ROOT>/packages/docusaurus/src/client/exports/router.ts",
|
||||||
"@docusaurus/useBaseUrl": "<PROJECT_ROOT>/packages/docusaurus/src/client/exports/useBaseUrl.ts",
|
"@docusaurus/useBaseUrl": "<PROJECT_ROOT>/packages/docusaurus/src/client/exports/useBaseUrl.ts",
|
||||||
|
"@docusaurus/useBrokenLinks": "<PROJECT_ROOT>/packages/docusaurus/src/client/exports/useBrokenLinks.ts",
|
||||||
"@docusaurus/useDocusaurusContext": "<PROJECT_ROOT>/packages/docusaurus/src/client/exports/useDocusaurusContext.ts",
|
"@docusaurus/useDocusaurusContext": "<PROJECT_ROOT>/packages/docusaurus/src/client/exports/useDocusaurusContext.ts",
|
||||||
"@docusaurus/useGlobalData": "<PROJECT_ROOT>/packages/docusaurus/src/client/exports/useGlobalData.ts",
|
"@docusaurus/useGlobalData": "<PROJECT_ROOT>/packages/docusaurus/src/client/exports/useGlobalData.ts",
|
||||||
"@docusaurus/useIsBrowser": "<PROJECT_ROOT>/packages/docusaurus/src/client/exports/useIsBrowser.ts",
|
"@docusaurus/useIsBrowser": "<PROJECT_ROOT>/packages/docusaurus/src/client/exports/useIsBrowser.ts",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/eslint-plugin",
|
"name": "@docusaurus/eslint-plugin",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "ESLint plugin to enforce best Docusaurus practices.",
|
"description": "ESLint plugin to enforce best Docusaurus practices.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/lqip-loader",
|
"name": "@docusaurus/lqip-loader",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Low Quality Image Placeholders (LQIP) loader for webpack.",
|
"description": "Low Quality Image Placeholders (LQIP) loader for webpack.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/logger": "3.0.1",
|
"@docusaurus/logger": "3.1.0",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"sharp": "^0.32.3",
|
"sharp": "^0.32.3",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "stylelint-copyright",
|
"name": "stylelint-copyright",
|
||||||
"version": "3.0.1",
|
"version": "3.1.0",
|
||||||
"description": "Stylelint plugin to check CSS files for a copyright header.",
|
"description": "Stylelint plugin to check CSS files for a copyright header.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue