Compare commits

..

No commits in common. "main" and "slorber/test-random" have entirely different histories.

600 changed files with 6175 additions and 33137 deletions

8
.eslintrc.js vendored
View File

@ -85,14 +85,13 @@ module.exports = {
ignorePattern: '(eslint-disable|@)',
},
],
'arrow-body-style': OFF,
'no-await-in-loop': OFF,
'no-case-declarations': WARNING,
'no-console': OFF,
'no-constant-binary-expression': ERROR,
'no-continue': OFF,
'no-control-regex': WARNING,
'no-else-return': OFF,
'no-else-return': [WARNING, {allowElseIf: true}],
'no-empty': [WARNING, {allowEmptyCatch: true}],
'no-lonely-if': WARNING,
'no-nested-ternary': WARNING,
@ -348,7 +347,10 @@ module.exports = {
ERROR,
{'ts-expect-error': 'allow-with-description'},
],
'@typescript-eslint/consistent-indexed-object-style': OFF,
'@typescript-eslint/consistent-indexed-object-style': [
WARNING,
'index-signature',
],
'@typescript-eslint/consistent-type-imports': [
WARNING,
{disallowTypeAnnotations: false},

View File

@ -30,7 +30,7 @@ jobs:
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Use Node.js
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: 18

View File

@ -24,7 +24,7 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up Node
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: '18'
cache: yarn

View File

@ -38,7 +38,7 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up Node
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: '18'
cache: yarn
@ -60,7 +60,7 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up Node
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
cache: yarn
- name: Installation

View File

@ -21,7 +21,7 @@ jobs:
with:
fetch-depth: 0 # Needed to get the commit number with "git rev-list --count HEAD"
- name: Set up Node
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: '18'
cache: yarn

View File

@ -15,4 +15,4 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Dependency Review
uses: actions/dependency-review-action@733dd5d4a5203f238c33806593ec0f5fc5343d8c # 4.2.4
uses: actions/dependency-review-action@4901385134134e04cec5fbe5ddfe3b2c5bd5d976 # 4.0.0

View File

@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Use Node.js
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: 18
@ -36,7 +36,7 @@ jobs:
- name: Audit URLs using Lighthouse
id: lighthouse_audit
uses: treosh/lighthouse-ci-action@1b0e7c33270fbba31a18a0fbb1de7cc5256b6d39 # 11.4.0
uses: treosh/lighthouse-ci-action@03becbfc543944dd6e7534f7ff768abb8a296826 # 10.1.0
with:
urls: |
http://localhost:3000
@ -64,7 +64,7 @@ jobs:
- name: Add Lighthouse stats as comment
id: comment_to_pr
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # 2.9.0
uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd # 2.8.0
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
number: ${{ github.event.pull_request.number }}

View File

@ -22,7 +22,7 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up Node
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: '18'
cache: yarn

View File

@ -24,9 +24,9 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up Node
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: '20'
node-version: '16'
cache: yarn
- name: Installation
run: yarn

View File

@ -43,7 +43,7 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Use Node.js ${{ matrix.node }}
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: ${{ matrix.node }}
cache: yarn
@ -77,7 +77,7 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Use Node.js 18
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: '18'
cache: yarn
@ -131,7 +131,7 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Use Node.js 18
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: '18'
cache: yarn
@ -161,7 +161,7 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Use Node.js 18
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: '18'
cache: yarn

View File

@ -28,7 +28,7 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up Node
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: '18'
cache: yarn

View File

@ -33,7 +33,7 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Use Node.js ${{ matrix.node }}
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: ${{ matrix.node }}
- name: Installation

View File

@ -31,7 +31,7 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Use Node.js ${{ matrix.node }}
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: ${{ matrix.node }}
cache: yarn

View File

@ -1,175 +1,5 @@
# Docusaurus 2 Changelog
## 3.2.0 (2024-03-29)
#### :rocket: New Feature
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-sitemap`, `docusaurus-types`, `docusaurus-utils`, `docusaurus`
- [#9954](https://github.com/facebook/docusaurus/pull/9954) feat(sitemap): add support for "lastmod" ([@slorber](https://github.com/slorber))
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-utils-validation`, `docusaurus-utils`
- [#9912](https://github.com/facebook/docusaurus/pull/9912) feat(blog): add LastUpdateAuthor & LastUpdateTime ([@OzakIOne](https://github.com/OzakIOne))
- `docusaurus-plugin-debug`, `docusaurus-types`, `docusaurus`
- [#9931](https://github.com/facebook/docusaurus/pull/9931) feat(core): add new plugin allContentLoaded lifecycle ([@slorber](https://github.com/slorber))
- `docusaurus-theme-translations`
- [#9928](https://github.com/facebook/docusaurus/pull/9928) feat(theme-translations) Icelandic (is) ([@Hallinn](https://github.com/Hallinn))
- `docusaurus-plugin-content-blog`
- [#9886](https://github.com/facebook/docusaurus/pull/9886) feat(blog): allow processing blog posts through a processBlogPosts function ([@OzakIOne](https://github.com/OzakIOne))
- [#9838](https://github.com/facebook/docusaurus/pull/9838) feat(blog): add blog pageBasePath plugin option ([@ilg-ul](https://github.com/ilg-ul))
- `docusaurus`
- [#9681](https://github.com/facebook/docusaurus/pull/9681) feat(swizzle): ask user preferred language if no language CLI option provided ([@yixiaojiu](https://github.com/yixiaojiu))
- `create-docusaurus`, `docusaurus-utils`
- [#9442](https://github.com/facebook/docusaurus/pull/9442) feat(create-docusaurus): ask user for preferred language when no language CLI option provided ([@Rafael-Martins](https://github.com/Rafael-Martins))
- `docusaurus-plugin-vercel-analytics`
- [#9687](https://github.com/facebook/docusaurus/pull/9687) feat(plugin-vercel-analytics): add new vercel analytics plugin ([@OzakIOne](https://github.com/OzakIOne))
- `docusaurus-mdx-loader`
- [#9684](https://github.com/facebook/docusaurus/pull/9684) feat(mdx-loader): the table-of-contents should display toc/headings of imported MDX partials ([@anatolykopyl](https://github.com/anatolykopyl))
#### :bug: Bug Fix
- `docusaurus-mdx-loader`
- [#9999](https://github.com/facebook/docusaurus/pull/9999) fix(mdx-loader): Ignore contentTitle coming after Markdown thematicBreak ([@slorber](https://github.com/slorber))
- `docusaurus-theme-search-algolia`
- [#9945](https://github.com/facebook/docusaurus/pull/9945) fix(a11y): move focus algolia-search focus back to search input on Escape ([@mxschmitt](https://github.com/mxschmitt))
- `docusaurus-plugin-content-blog`
- [#9920](https://github.com/facebook/docusaurus/pull/9920) fix(blog): apply trailing slash to blog feed ([@OzakIOne](https://github.com/OzakIOne))
- `docusaurus-theme-classic`
- [#9944](https://github.com/facebook/docusaurus/pull/9944) fix(theme): improve a11y of DocSidebarItemCategory expand/collapsed button ([@mxschmitt](https://github.com/mxschmitt))
- `docusaurus-theme-translations`
- [#9915](https://github.com/facebook/docusaurus/pull/9915) fix(theme-translations): complete and modify Japanese translations ([@Suenaga-Ryuya](https://github.com/Suenaga-Ryuya))
- [#9910](https://github.com/facebook/docusaurus/pull/9910) fix(theme-translations): add Japanese translations ([@Suenaga-Ryuya](https://github.com/Suenaga-Ryuya))
- [#9872](https://github.com/facebook/docusaurus/pull/9872) fix(theme-translations): complete and improve Spanish theme translations ([@4troDev](https://github.com/4troDev))
- [#9812](https://github.com/facebook/docusaurus/pull/9812) fix(i18n): add missing theme translations for fa locale ([@VahidNaderi](https://github.com/VahidNaderi))
- `docusaurus-utils`
- [#9897](https://github.com/facebook/docusaurus/pull/9897) fix(mdx-loader): mdx-code-block should support CRLF ([@slorber](https://github.com/slorber))
- `docusaurus`
- [#9878](https://github.com/facebook/docusaurus/pull/9878) fix(core): fix default i18n calendar used, infer it from locale if possible ([@slorber](https://github.com/slorber))
- [#9852](https://github.com/facebook/docusaurus/pull/9852) fix(core): ensure core error boundary is able to render theme layout ([@slorber](https://github.com/slorber))
- `docusaurus-remark-plugin-npm2yarn`
- [#9861](https://github.com/facebook/docusaurus/pull/9861) fix(remark-npm2yarn): update npm-to-yarn from 2.0.0 to 2.2.1, fix pnpm extra args syntax ([@OzakIOne](https://github.com/OzakIOne))
- `docusaurus-theme-classic`, `docusaurus-theme-translations`
- [#9851](https://github.com/facebook/docusaurus/pull/9851) fix(theme-classic): should use plurals for category items description ([@baradusov](https://github.com/baradusov))
#### :running_woman: Performance
- `docusaurus-types`, `docusaurus-utils`, `docusaurus`
- [#9975](https://github.com/facebook/docusaurus/pull/9975) refactor(core): improve dev perf, fine-grained site reloads - part 3 ([@slorber](https://github.com/slorber))
- `docusaurus-types`, `docusaurus`
- [#9968](https://github.com/facebook/docusaurus/pull/9968) refactor(core): improve dev perf, fine-grained site reloads - part2 ([@slorber](https://github.com/slorber))
- `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-types`, `docusaurus`
- [#9903](https://github.com/facebook/docusaurus/pull/9903) refactor(core): improve dev perf, fine-grained site reloads - part1 ([@slorber](https://github.com/slorber))
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-utils`
- [#9890](https://github.com/facebook/docusaurus/pull/9890) perf: optimize getFileCommitDate, make it async ([@slorber](https://github.com/slorber))
- `docusaurus`
- [#9798](https://github.com/facebook/docusaurus/pull/9798) refactor(core): internalize, simplify and optimize the SSG logic ([@slorber](https://github.com/slorber))
#### :nail_care: Polish
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`
- [#9868](https://github.com/facebook/docusaurus/pull/9868) refactor(theme): dates should be formatted on the client-side instead of in nodejs code ([@OzakIOne](https://github.com/OzakIOne))
- `docusaurus-plugin-content-blog`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-types`
- [#9669](https://github.com/facebook/docusaurus/pull/9669) refactor(theme): use JSON-LD instead of microdata for blog structured data ([@johnnyreilly](https://github.com/johnnyreilly))
- `docusaurus-plugin-content-docs`
- [#9839](https://github.com/facebook/docusaurus/pull/9839) refactor(blog): improve doc global data hook error message + add doc warning to blogOnly mode ([@OzakIOne](https://github.com/OzakIOne))
#### :memo: Documentation
- [#9937](https://github.com/facebook/docusaurus/pull/9937) docs: use official GitHub Action to deploy to GitHub Pages ([@vlad-nestorov](https://github.com/vlad-nestorov))
- [#9971](https://github.com/facebook/docusaurus/pull/9971) docs: replace VuePress by VitePress on tool comparison section ([@sunkanmii](https://github.com/sunkanmii))
- [#9914](https://github.com/facebook/docusaurus/pull/9914) docs: update legacy MDX v1 links to markdown links ([@OzakIOne](https://github.com/OzakIOne))
- [#9913](https://github.com/facebook/docusaurus/pull/9913) docs: update legacy MDX v1 links to markdown links ([@OzakIOne](https://github.com/OzakIOne))
- [#9906](https://github.com/facebook/docusaurus/pull/9906) docs: emphasize "index slug" convention ([@Josh-Cena](https://github.com/Josh-Cena))
- [#9877](https://github.com/facebook/docusaurus/pull/9877) docs: fix typos in deployment.mdx ([@Oreoxmt](https://github.com/Oreoxmt))
- [#9845](https://github.com/facebook/docusaurus/pull/9845) docs: typo ([@OzakIOne](https://github.com/OzakIOne))
- [#9816](https://github.com/facebook/docusaurus/pull/9816) docs: Add docs for Mermaid Component ([@Its-Just-Nans](https://github.com/Its-Just-Nans))
#### :robot: Dependencies
- [#9981](https://github.com/facebook/docusaurus/pull/9981) chore(deps): bump actions/dependency-review-action from 4.1.3 to 4.2.4 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9982](https://github.com/facebook/docusaurus/pull/9982) chore(deps): bump katex from 0.16.8 to 0.16.10 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9983](https://github.com/facebook/docusaurus/pull/9983) chore(deps): bump express from 4.18.2 to 4.19.2 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9977](https://github.com/facebook/docusaurus/pull/9977) chore(deps): bump webpack-dev-middleware from 5.3.3 to 5.3.4 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9958](https://github.com/facebook/docusaurus/pull/9958) chore(deps): bump follow-redirects from 1.15.4 to 1.15.6 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9892](https://github.com/facebook/docusaurus/pull/9892) chore(deps): bump actions/dependency-review-action from 4.1.2 to 4.1.3 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9869](https://github.com/facebook/docusaurus/pull/9869) chore(deps): bump actions/dependency-review-action from 4.0.0 to 4.1.2 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9874](https://github.com/facebook/docusaurus/pull/9874) chore(deps): bump ip from 2.0.0 to 2.0.1 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9843](https://github.com/facebook/docusaurus/pull/9843) chore(deps): bump actions/setup-node from 4.0.1 to 4.0.2 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9824](https://github.com/facebook/docusaurus/pull/9824) chore(deps): bump treosh/lighthouse-ci-action from 10.1.0 to 11.4.0 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9823](https://github.com/facebook/docusaurus/pull/9823) chore(deps): bump marocchino/sticky-pull-request-comment from 2.8.0 to 2.9.0 ([@dependabot[bot]](https://github.com/apps/dependabot))
#### :wrench: Maintenance
- `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-docs`, `docusaurus-utils-common`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`
- [#9972](https://github.com/facebook/docusaurus/pull/9972) refactor(utils): remove duplicated function ([@OzakIOne](https://github.com/OzakIOne))
- Other
- [#9965](https://github.com/facebook/docusaurus/pull/9965) refactor(website): organise blog posts by year ([@GingerGeek](https://github.com/GingerGeek))
- [#9865](https://github.com/facebook/docusaurus/pull/9865) chore(website): update @crowdin/crowdin-api-client ([@chris-bateman](https://github.com/chris-bateman))
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-utils`
- [#9963](https://github.com/facebook/docusaurus/pull/9963) refactor(docs,blog): last update timestamp should be in milliseconds instead of seconds ([@slorber](https://github.com/slorber))
#### Committers: 22
- Aolin ([@Oreoxmt](https://github.com/Oreoxmt))
- Anatoly Kopyl ([@anatolykopyl](https://github.com/anatolykopyl))
- Chris Bateman ([@chris-bateman](https://github.com/chris-bateman))
- Fafowora Sunkanmi ([@sunkanmii](https://github.com/sunkanmii))
- Hallbjörn Magnússon ([@Hallinn](https://github.com/Hallinn))
- John Reilly ([@johnnyreilly](https://github.com/johnnyreilly))
- Joshua Chen ([@Josh-Cena](https://github.com/Josh-Cena))
- Josue [4tro] A ([@4troDev](https://github.com/4troDev))
- Liviu Ionescu ([@ilg-ul](https://github.com/ilg-ul))
- Max Schmitt ([@mxschmitt](https://github.com/mxschmitt))
- Rafael Martins ([@Rafael-Martins](https://github.com/Rafael-Martins))
- Sébastien Lorber ([@slorber](https://github.com/slorber))
- Vahid Naderi ([@VahidNaderi](https://github.com/VahidNaderi))
- Vlad Nestorov ([@vlad-nestorov](https://github.com/vlad-nestorov))
- Zed Spencer-Milnes ([@GingerGeek](https://github.com/GingerGeek))
- axel7083 ([@axel7083](https://github.com/axel7083))
- krinza.eth ([@kaymomin](https://github.com/kaymomin))
- n4n5 ([@Its-Just-Nans](https://github.com/Its-Just-Nans))
- ozaki ([@OzakIOne](https://github.com/OzakIOne))
- suenryu ([@Suenaga-Ryuya](https://github.com/Suenaga-Ryuya))
- Нуриль Барадусов ([@baradusov](https://github.com/baradusov))
- 翊小久 ([@yixiaojiu](https://github.com/yixiaojiu))
## 3.1.1 (2024-01-26)
#### :bug: Bug Fix
- `docusaurus-types`, `docusaurus`
- [#9791](https://github.com/facebook/docusaurus/pull/9791) fix(core): broken links optimization behaves differently than non-optimized logic ([@slorber](https://github.com/slorber))
- `docusaurus`
- [#9788](https://github.com/facebook/docusaurus/pull/9788) fix(core): links with target "\_blank" should no be checked by the broken link checker ([@slorber](https://github.com/slorber))
- [#9407](https://github.com/facebook/docusaurus/pull/9407) fix(core): conditionally include `hostname` parameter when using… ([@jack-robson](https://github.com/jack-robson))
- `docusaurus-utils`
- [#9776](https://github.com/facebook/docusaurus/pull/9776) fix(mdx-loader): allow spaces before `mdx-code-block` info string ([@eitsupi](https://github.com/eitsupi))
- `create-docusaurus`
- [#9783](https://github.com/facebook/docusaurus/pull/9783) fix(create-docusaurus): fix typo in init template sample docs ([@dawei-wang](https://github.com/dawei-wang))
- `docusaurus-theme-common`
- [#9727](https://github.com/facebook/docusaurus/pull/9727) fix(theme-common): fix missing code block MagicComments style in Visual Basic (.NET) 16 ([@tats-u](https://github.com/tats-u))
- `docusaurus-theme-classic`, `docusaurus-theme-mermaid`
- [#9733](https://github.com/facebook/docusaurus/pull/9733) fix: remove old useless mdx typedefs ([@slorber](https://github.com/slorber))
- `docusaurus-module-type-aliases`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-utils`, `docusaurus`
- [#9732](https://github.com/facebook/docusaurus/pull/9732) fix(core): various broken anchor link fixes ([@slorber](https://github.com/slorber))
#### :running_woman: Performance
- `docusaurus`
- [#9778](https://github.com/facebook/docusaurus/pull/9778) perf(core): optimize broken links checker ([@slorber](https://github.com/slorber))
#### :nail_care: Polish
- `docusaurus-theme-classic`
- [#9470](https://github.com/facebook/docusaurus/pull/9470) polish(theme): MDX images should use async decoding ([@sanjaiyan-dev](https://github.com/sanjaiyan-dev))
#### Committers: 6
- Jack Robson ([@jack-robson](https://github.com/jack-robson))
- Sanjaiyan Parthipan ([@sanjaiyan-dev](https://github.com/sanjaiyan-dev))
- Sébastien Lorber ([@slorber](https://github.com/slorber))
- Tatsunori Uchino ([@tats-u](https://github.com/tats-u))
- [@dawei-wang](https://github.com/dawei-wang)
- [@eitsupi](https://github.com/eitsupi)
## 3.1.0 (2024-01-05)
#### :rocket: New Feature

View File

@ -11,9 +11,9 @@ const CookieName = 'DocusaurusPlaygroundName';
const PlaygroundConfigs = {
codesandbox:
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic?file=%2FREADME.md&privacy=public',
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic?file=%2FREADME.md',
'codesandbox-ts':
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic-typescript?file=%2FREADME.md&privacy=public',
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic-typescript?file=%2FREADME.md',
// Slow to load
// stackblitz: 'https://stackblitz.com/github/facebook/docusaurus/tree/main/examples/classic',

View File

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

View File

@ -25,8 +25,7 @@ async function generateTemplateExample(template) {
// Run the docusaurus script to create the template in the examples folder
const command = template.endsWith('-typescript')
? template.replace('-typescript', ' -- --typescript')
: `${template} -- --javascript`;
: template;
shell.exec(
// We use the published init script on purpose, because the local init is
// too new and could generate upcoming/unavailable config options.

View File

@ -52,7 +52,7 @@ git diff --name-only -- '*.json' | sed 's, ,\\&,g' | xargs git checkout --
cd ..
# Build skeleton website with new version
npm_config_registry="$CUSTOM_REGISTRY_URL" npx create-docusaurus@"$NEW_VERSION" test-website classic --javascript $EXTRA_OPTS
npm_config_registry="$CUSTOM_REGISTRY_URL" npx create-docusaurus@"$NEW_VERSION" test-website classic $EXTRA_OPTS
# Stop Docker container
if [[ -z "${KEEP_CONTAINER:-true}" ]] && ( $(docker container inspect "$CONTAINER_NAME" > /dev/null 2>&1) ); then

View File

@ -1,6 +1,6 @@
{
"name": "argos",
"version": "3.2.0",
"version": "3.0.0",
"description": "Argos visual diff tests",
"license": "MIT",
"private": true,
@ -10,8 +10,8 @@
"report": "playwright show-report"
},
"dependencies": {
"@argos-ci/playwright": "^1.9.3",
"@playwright/test": "^1.41.2",
"@argos-ci/playwright": "^1.0.1",
"@playwright/test": "^1.36.1",
"cheerio": "^1.0.0-rc.12"
}
}

View File

@ -28,7 +28,7 @@ tags: [greetings]
Congratulations, you have made your first post!
Feel free to play around and edit this post as much as you like.
Feel free to play around and edit this post as much you like.
```
A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings).

View File

@ -16,8 +16,8 @@
"dev": "docusaurus start"
},
"dependencies": {
"@docusaurus/core": "3.2.0",
"@docusaurus/preset-classic": "3.2.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/preset-classic": "3.1.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
@ -25,9 +25,9 @@
"react-dom": "^18.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/tsconfig": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/module-type-aliases": "3.1.0",
"@docusaurus/tsconfig": "3.1.0",
"@docusaurus/types": "3.1.0",
"typescript": "~5.2.2"
},
"browserslist": {

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,7 @@ tags: [greetings]
Congratulations, you have made your first post!
Feel free to play around and edit this post as much as you like.
Feel free to play around and edit this post as much you like.
```
A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings).

View File

@ -15,8 +15,8 @@
"dev": "docusaurus start"
},
"dependencies": {
"@docusaurus/core": "3.2.0",
"@docusaurus/preset-classic": "3.2.0",
"@docusaurus/core": "3.1.0",
"@docusaurus/preset-classic": "3.1.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
@ -24,8 +24,8 @@
"react-dom": "^18.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/types": "3.2.0"
"@docusaurus/module-type-aliases": "3.1.0",
"@docusaurus/types": "3.1.0"
},
"browserslist": {
"production": [

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -25,7 +25,7 @@ For Docusaurus maintainers, templates can be tested with:
```bash
cd `git rev-parse --show-toplevel` # Back to repo root
rm -rf test-website
yarn create-docusaurus test-website classic --javascript
yarn create-docusaurus test-website classic
cd test-website
yarn start
```
@ -37,7 +37,7 @@ Use the following to test the templates against local packages:
```bash
cd `git rev-parse --show-toplevel` # Back to repo root
rm -rf test-website-in-workspace
yarn create-docusaurus test-website-in-workspace classic --javascript
yarn create-docusaurus test-website-in-workspace classic
cd test-website-in-workspace
yarn build
yarn start

View File

@ -38,7 +38,6 @@ program
'Do not run package manager immediately after scaffolding',
)
.option('-t, --typescript', 'Use the TypeScript template variant')
.option('-j, --javascript', 'Use the JavaScript template variant')
.option(
'-g, --git-strategy <strategy>',
`Only used if the template is a git repository.

View File

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

View File

@ -13,29 +13,15 @@ import logger from '@docusaurus/logger';
import shell from 'shelljs';
import prompts, {type Choice} from 'prompts';
import supportsColor from 'supports-color';
import {escapeShellArg, askPreferredLanguage} from '@docusaurus/utils';
import {escapeShellArg} from '@docusaurus/utils';
type LanguagesOptions = {
javascript?: boolean;
typescript?: boolean;
};
type CLIOptions = LanguagesOptions & {
type CLIOptions = {
packageManager?: PackageManager;
skipInstall?: boolean;
typescript?: boolean;
gitStrategy?: GitStrategy;
};
async function getLanguage(options: LanguagesOptions) {
if (options.typescript) {
return 'typescript';
}
if (options.javascript) {
return 'javascript';
}
return askPreferredLanguage();
}
// Only used in the rare, rare case of running globally installed create +
// using --skip-install. We need a default name to show the tip text
const defaultPackageManager = 'npm';
@ -167,14 +153,11 @@ async function readTemplates(): Promise<Template[]> {
async function copyTemplate(
template: Template,
dest: string,
language: 'javascript' | 'typescript',
typescript: boolean,
): Promise<void> {
await fs.copy(path.join(templatesDir, 'shared'), dest);
const sourcePath =
language === 'typescript' ? template.tsVariantPath! : template.path;
await fs.copy(sourcePath, dest, {
await fs.copy(typescript ? template.tsVariantPath! : template.path, dest, {
// Symlinks don't exist in published npm packages anymore, so this is only
// to prevent errors during local testing
filter: async (filePath) => !(await fs.lstat(filePath)).isSymbolicLink(),
@ -200,33 +183,6 @@ function createTemplateChoices(templates: Template[]): Choice[] {
];
}
async function askTemplateChoice({
templates,
cliOptions,
}: {
templates: Template[];
cliOptions: CLIOptions;
}) {
return cliOptions.gitStrategy
? 'Git repository'
: (
(await prompts(
{
type: 'select',
name: 'template',
message: 'Select a template below...',
choices: createTemplateChoices(templates),
},
{
onCancel() {
logger.error('A choice is required.');
process.exit(1);
},
},
)) as {template: Template | 'Git repository' | 'Local template'}
).template;
}
function isValidGitRepoUrl(gitRepoUrl: string): boolean {
return ['https://', 'git@'].some((item) => gitRepoUrl.startsWith(item));
}
@ -304,7 +260,7 @@ type Source =
| {
type: 'template';
template: Template;
language: 'javascript' | 'typescript';
typescript: boolean;
}
| {
type: 'git';
@ -316,193 +272,166 @@ type Source =
path: string;
};
async function createTemplateSource({
template,
cliOptions,
}: {
template: Template;
cliOptions: CLIOptions;
}): Promise<Source> {
const language = await getLanguage(cliOptions);
if (language === 'typescript' && !template.tsVariantPath) {
logger.error`Template name=${template.name} doesn't provide a TypeScript variant.`;
process.exit(1);
}
return {
type: 'template',
template,
language,
};
}
async function getTemplateSource({
templateName,
templates,
cliOptions,
}: {
templateName: string;
templates: Template[];
cliOptions: CLIOptions;
}): Promise<Source> {
const template = templates.find((t) => t.name === templateName);
if (!template) {
logger.error('Invalid template.');
process.exit(1);
}
return createTemplateSource({template, cliOptions});
}
// Get the template source explicitly requested by the user provided cli option
async function getUserProvidedSource({
reqTemplate,
templates,
cliOptions,
}: {
reqTemplate: string;
templates: Template[];
cliOptions: CLIOptions;
}): Promise<Source> {
if (isValidGitRepoUrl(reqTemplate)) {
if (
cliOptions.gitStrategy &&
!gitStrategies.includes(cliOptions.gitStrategy)
) {
logger.error`Invalid git strategy: name=${
cliOptions.gitStrategy
}. Value must be one of ${gitStrategies.join(', ')}.`;
process.exit(1);
}
return {
type: 'git',
url: reqTemplate,
strategy: cliOptions.gitStrategy ?? 'deep',
};
}
if (await fs.pathExists(path.resolve(reqTemplate))) {
return {
type: 'local',
path: path.resolve(reqTemplate),
};
}
return getTemplateSource({
templateName: reqTemplate,
templates,
cliOptions,
});
}
async function askGitRepositorySource({
cliOptions,
}: {
cliOptions: CLIOptions;
}): Promise<Source> {
const {gitRepoUrl} = (await prompts(
{
type: 'text',
name: 'gitRepoUrl',
validate: (url?: string) => {
if (url && isValidGitRepoUrl(url)) {
return true;
}
return logger.red('Invalid repository URL');
},
message: logger.interpolate`Enter a repository URL from GitHub, Bitbucket, GitLab, or any other public repo.
(e.g: url=${'https://github.com/ownerName/repoName.git'})`,
},
{
onCancel() {
logger.error('A git repo URL is required.');
process.exit(1);
},
},
)) as {gitRepoUrl: string};
let strategy = cliOptions.gitStrategy;
if (!strategy) {
({strategy} = (await prompts(
{
type: 'select',
name: 'strategy',
message: 'How should we clone this repo?',
choices: [
{title: 'Deep clone: preserve full history', value: 'deep'},
{title: 'Shallow clone: clone with --depth=1', value: 'shallow'},
{
title: 'Copy: do a shallow clone, but do not create a git repo',
value: 'copy',
},
{
title: 'Custom: enter your custom git clone command',
value: 'custom',
},
],
},
{
onCancel() {
logger.info`Falling back to name=${'deep'}`;
},
},
)) as {strategy?: GitStrategy});
}
return {
type: 'git',
url: gitRepoUrl,
strategy: strategy ?? 'deep',
};
}
async function askLocalSource(): Promise<Source> {
const {templateDir} = (await prompts(
{
type: 'text',
name: 'templateDir',
validate: async (dir?: string) => {
if (dir) {
const fullDir = path.resolve(dir);
if (await fs.pathExists(fullDir)) {
return true;
}
return logger.red(
logger.interpolate`path=${fullDir} does not exist.`,
);
}
return logger.red('Please enter a valid path.');
},
message:
'Enter a local folder path, relative to the current working directory.',
},
{
onCancel() {
logger.error('A file path is required.');
process.exit(1);
},
},
)) as {templateDir: string};
return {
type: 'local',
path: templateDir,
};
}
async function getSource(
reqTemplate: string | undefined,
templates: Template[],
cliOptions: CLIOptions,
): Promise<Source> {
if (reqTemplate) {
return getUserProvidedSource({reqTemplate, templates, cliOptions});
if (isValidGitRepoUrl(reqTemplate)) {
if (
cliOptions.gitStrategy &&
!gitStrategies.includes(cliOptions.gitStrategy)
) {
logger.error`Invalid git strategy: name=${
cliOptions.gitStrategy
}. Value must be one of ${gitStrategies.join(', ')}.`;
process.exit(1);
}
return {
type: 'git',
url: reqTemplate,
strategy: cliOptions.gitStrategy ?? 'deep',
};
} else if (await fs.pathExists(path.resolve(reqTemplate))) {
return {
type: 'local',
path: path.resolve(reqTemplate),
};
}
const template = templates.find((t) => t.name === reqTemplate);
if (!template) {
logger.error('Invalid template.');
process.exit(1);
}
if (cliOptions.typescript && !template.tsVariantPath) {
logger.error`Template name=${reqTemplate} doesn't provide the TypeScript variant.`;
process.exit(1);
}
return {
type: 'template',
template,
typescript: cliOptions.typescript ?? false,
};
}
const template = await askTemplateChoice({templates, cliOptions});
const template = cliOptions.gitStrategy
? 'Git repository'
: (
(await prompts(
{
type: 'select',
name: 'template',
message: 'Select a template below...',
choices: createTemplateChoices(templates),
},
{
onCancel() {
logger.error('A choice is required.');
process.exit(1);
},
},
)) as {template: Template | 'Git repository' | 'Local template'}
).template;
if (template === 'Git repository') {
return askGitRepositorySource({cliOptions});
const {gitRepoUrl} = (await prompts(
{
type: 'text',
name: 'gitRepoUrl',
validate: (url?: string) => {
if (url && isValidGitRepoUrl(url)) {
return true;
}
return logger.red('Invalid repository URL');
},
message: logger.interpolate`Enter a repository URL from GitHub, Bitbucket, GitLab, or any other public repo.
(e.g: url=${'https://github.com/ownerName/repoName.git'})`,
},
{
onCancel() {
logger.error('A git repo URL is required.');
process.exit(1);
},
},
)) as {gitRepoUrl: string};
let strategy = cliOptions.gitStrategy;
if (!strategy) {
({strategy} = (await prompts(
{
type: 'select',
name: 'strategy',
message: 'How should we clone this repo?',
choices: [
{title: 'Deep clone: preserve full history', value: 'deep'},
{title: 'Shallow clone: clone with --depth=1', value: 'shallow'},
{
title: 'Copy: do a shallow clone, but do not create a git repo',
value: 'copy',
},
{
title: 'Custom: enter your custom git clone command',
value: 'custom',
},
],
},
{
onCancel() {
logger.info`Falling back to name=${'deep'}`;
},
},
)) as {strategy?: GitStrategy});
}
return {
type: 'git',
url: gitRepoUrl,
strategy: strategy ?? 'deep',
};
} else if (template === 'Local template') {
const {templateDir} = (await prompts(
{
type: 'text',
name: 'templateDir',
validate: async (dir?: string) => {
if (dir) {
const fullDir = path.resolve(dir);
if (await fs.pathExists(fullDir)) {
return true;
}
return logger.red(
logger.interpolate`path=${fullDir} does not exist.`,
);
}
return logger.red('Please enter a valid path.');
},
message:
'Enter a local folder path, relative to the current working directory.',
},
{
onCancel() {
logger.error('A file path is required.');
process.exit(1);
},
},
)) as {templateDir: string};
return {
type: 'local',
path: templateDir,
};
}
if (template === 'Local template') {
return askLocalSource();
let useTS = cliOptions.typescript;
if (!useTS && template.tsVariantPath) {
({useTS} = (await prompts({
type: 'confirm',
name: 'useTS',
message:
'This template is available in TypeScript. Do you want to use the TS variant?',
initial: false,
})) as {useTS?: boolean});
}
return createTemplateSource({
return {
type: 'template',
template,
cliOptions,
});
typescript: useTS ?? false,
};
}
async function updatePkg(pkgPath: string, obj: {[key: string]: unknown}) {
@ -523,7 +452,6 @@ export default async function init(
getSiteName(reqName, rootDir),
]);
const dest = path.resolve(rootDir, siteName);
const source = await getSource(reqTemplate, templates, cliOptions);
logger.info('Creating new Docusaurus project...');
@ -542,7 +470,7 @@ export default async function init(
}
} else if (source.type === 'template') {
try {
await copyTemplate(source.template, dest, source.language);
await copyTemplate(source.template, dest, source.typescript);
} catch (err) {
logger.error`Copying Docusaurus template name=${source.template.name} failed!`;
throw err;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -71,21 +71,6 @@ some **markdown** *content*
expect(result.data.contentTitle).toBeUndefined();
});
it('ignore contentTitle if after thematic break', async () => {
const result = await process(`
Hey
---
# contentTitle 1
some **markdown** *content*
`);
expect(result.data.contentTitle).toBeUndefined();
});
it('is able to decently serialize Markdown syntax', async () => {
const result = await process(`
# some **markdown** \`content\` _italic_

View File

@ -34,24 +34,17 @@ const plugin: Plugin = function plugin(
const {toString} = await import('mdast-util-to-string');
const {visit, EXIT} = await import('unist-util-visit');
visit(root, ['heading', 'thematicBreak'], (node, index, parent) => {
if (node.type === 'heading') {
const headingNode = node as Heading;
if (headingNode.depth === 1) {
vfile.data.contentTitle = toString(headingNode);
if (removeContentTitle) {
// @ts-expect-error: TODO how to fix?
parent!.children.splice(index, 1);
}
return EXIT; // We only handle the very first heading
}
// We only handle contentTitle if it's the very first heading found
if (headingNode.depth >= 1) {
return EXIT;
visit(root, 'heading', (headingNode: Heading, index, parent) => {
if (headingNode.depth === 1) {
vfile.data.contentTitle = toString(headingNode);
if (removeContentTitle) {
// @ts-expect-error: TODO how to fix?
parent!.children.splice(index, 1);
}
return EXIT; // We only handle the very first heading
}
// We only handle contentTitle when it's above the first thematic break
if (node.type === 'thematicBreak') {
// We only handle contentTitle if it's the very first heading found
if (headingNode.depth >= 1) {
return EXIT;
}
return undefined;

View File

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

View File

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

View File

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {removeTrailingSlash} from '@docusaurus/utils-common';
import {removeTrailingSlash} from '@docusaurus/utils';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import collectRedirects from '../collectRedirects';
import {validateOptions} from '../options';

View File

@ -7,11 +7,8 @@
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {
applyTrailingSlash,
addTrailingSlash,
removeTrailingSlash,
} from '@docusaurus/utils-common';
import {addTrailingSlash, removeTrailingSlash} from '@docusaurus/utils';
import {applyTrailingSlash} from '@docusaurus/utils-common';
import {
createFromExtensionsRedirects,
createToExtensionsRedirects,

View File

@ -9,7 +9,7 @@ import {
addTrailingSlash,
removeSuffix,
removeTrailingSlash,
} from '@docusaurus/utils-common';
} from '@docusaurus/utils';
import type {RedirectItem} from './types';
const ExtensionAdditionalMessage =

View File

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {addLeadingSlash, removePrefix} from '@docusaurus/utils-common';
import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
import collectRedirects from './collectRedirects';
import writeRedirectFiles, {
toRedirectFiles,

View File

@ -1,24 +1,12 @@
{
"name": "@docusaurus/plugin-content-blog",
"version": "3.2.0",
"version": "3.0.0",
"description": "Blog plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-content-blog.d.ts",
"exports": {
"./lib/*": "./lib/*",
"./src/*": "./src/*",
"./client": {
"type": "./lib/client/index.d.ts",
"default": "./lib/client/index.js"
},
".": {
"types": "./src/plugin-content-blog.d.ts",
"default": "./lib/index.js"
}
},
"scripts": {
"build": "tsc --build",
"watch": "tsc --build --watch",
"build": "tsc",
"watch": "tsc --watch",
"test:generate-build-snap": "yarn docusaurus build src/__tests__/__fixtures__/website --out-dir build-snap && yarn rimraf src/__tests__/__fixtures__/website/.docusaurus && yarn rimraf src/__tests__/__fixtures__/website/build-snap/assets && git add src/__tests__/__fixtures__/website/build-snap"
},
"repository": {
@ -31,13 +19,13 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.2.0",
"@docusaurus/logger": "3.2.0",
"@docusaurus/mdx-loader": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-common": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"cheerio": "^1.0.0-rc.12",
"feed": "^4.2.2",
"fs-extra": "^11.1.1",
@ -55,8 +43,5 @@
},
"engines": {
"node": ">=18.0"
},
"devDependencies": {
"@total-typescript/shoehorn": "^0.1.2"
}
}

View File

@ -1,9 +0,0 @@
---
title: Author
slug: author
author: ozaki
last_update:
author: seb
---
author

View File

@ -1,11 +0,0 @@
---
title: Both
slug: both
date: 2020-01-01
last_update:
date: 2021-01-01
author: seb
author: ozaki
---
last update date

View File

@ -1,9 +0,0 @@
---
title: Last update date
slug: lastUpdateDate
date: 2020-01-01
last_update:
date: 2021-01-01
---
last update date

View File

@ -1,6 +0,0 @@
---
title: Nothing
slug: nothing
---
nothing

View File

@ -23,32 +23,7 @@ title: This post links to another one!
[Linked post](/blog/2018/12/14/Happy-First-Birthday-Slash)"
`;
exports[`paginateBlogPosts generates a single page 1`] = `
[
{
"items": [
"post1",
"post2",
"post3",
"post4",
"post5",
],
"metadata": {
"blogDescription": "Blog Description",
"blogTitle": "Blog Title",
"nextPage": undefined,
"page": 1,
"permalink": "/",
"postsPerPage": 10,
"previousPage": undefined,
"totalCount": 5,
"totalPages": 1,
},
},
]
`;
exports[`paginateBlogPosts generates pages 1`] = `
exports[`paginateBlogPosts generates right pages 1`] = `
[
{
"items": [
@ -103,7 +78,7 @@ exports[`paginateBlogPosts generates pages 1`] = `
]
`;
exports[`paginateBlogPosts generates pages at blog root 1`] = `
exports[`paginateBlogPosts generates right pages 2`] = `
[
{
"items": [
@ -158,56 +133,26 @@ exports[`paginateBlogPosts generates pages at blog root 1`] = `
]
`;
exports[`paginateBlogPosts generates pages with custom pageBasePath 1`] = `
exports[`paginateBlogPosts generates right pages 3`] = `
[
{
"items": [
"post1",
"post2",
],
"metadata": {
"blogDescription": "Blog Description",
"blogTitle": "Blog Title",
"nextPage": "/blog/customPageBasePath/2",
"page": 1,
"permalink": "/blog",
"postsPerPage": 2,
"previousPage": undefined,
"totalCount": 5,
"totalPages": 3,
},
},
{
"items": [
"post3",
"post4",
],
"metadata": {
"blogDescription": "Blog Description",
"blogTitle": "Blog Title",
"nextPage": "/blog/customPageBasePath/3",
"page": 2,
"permalink": "/blog/customPageBasePath/2",
"postsPerPage": 2,
"previousPage": "/blog",
"totalCount": 5,
"totalPages": 3,
},
},
{
"items": [
"post5",
],
"metadata": {
"blogDescription": "Blog Description",
"blogTitle": "Blog Title",
"nextPage": undefined,
"page": 3,
"permalink": "/blog/customPageBasePath/3",
"postsPerPage": 2,
"previousPage": "/blog/customPageBasePath/2",
"page": 1,
"permalink": "/",
"postsPerPage": 10,
"previousPage": undefined,
"totalCount": 5,
"totalPages": 3,
"totalPages": 1,
},
},
]

View File

@ -220,134 +220,6 @@ exports[`atom has feed item for each post 1`] = `
]
`;
exports[`atom has feed item for each post - with trailing slash 1`] = `
[
"<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>https://docusaurus.io/myBaseUrl/blog/</id>
<title>Hello Blog</title>
<updated>2023-07-23T00:00:00.000Z</updated>
<generator>https://github.com/jpmonette/feed</generator>
<link rel="alternate" href="https://docusaurus.io/myBaseUrl/blog/"/>
<subtitle>Hello Blog</subtitle>
<icon>https://docusaurus.io/myBaseUrl/image/favicon.ico</icon>
<rights>Copyright</rights>
<entry>
<title type="html"><![CDATA[test links]]></title>
<id>https://docusaurus.io/myBaseUrl/blog/blog-with-links/</id>
<link href="https://docusaurus.io/myBaseUrl/blog/blog-with-links/"/>
<updated>2023-07-23T00:00:00.000Z</updated>
<summary type="html"><![CDATA[absolute full url]]></summary>
<content type="html"><![CDATA[<p><a href="https://github.com/facebook/docusaurus" target="_blank" rel="noopener noreferrer">absolute full url</a></p>
<p><a href="https://docusaurus.io/blog/heading-as-title">absolute pathname</a></p>
<p><a href="https://docusaurus.io/blog/heading-as-title">relative pathname</a></p>
<p><a href="https://docusaurus.io/blog/heading-as-title">md link</a></p>
<p><a href="https://docusaurus.io/myBaseUrl/blog/blog-with-links/#title">anchor</a></p>
<p><a href="https://docusaurus.io/blog/heading-as-title#title">relative pathname + anchor</a></p>
<p><img loading="lazy" src="https://docusaurus.io/assets/images/test-image-742d39e51f41482e8132e79c09ad4eea.png" width="760" height="160" class="img_yGFe"></p>
<p><img loading="lazy" src="https://docusaurus.io/assets/images/slash-introducing-411a16dd05086935b8e9ddae38ae9b45.svg" alt="" class="img_yGFe"></p>
<img srcset="https://docusaurus.io/img/test-image.png 300w, https://docusaurus.io/img/docusaurus-social-card.png 500w">
<img src="https://docusaurus.io/img/test-image.png">]]></content>
</entry>
<entry>
<title type="html"><![CDATA[MDX Blog Sample with require calls]]></title>
<id>https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post/</id>
<link href="https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post/"/>
<updated>2021-03-06T00:00:00.000Z</updated>
<summary type="html"><![CDATA[Test MDX with require calls]]></summary>
<content type="html"><![CDATA[<p>Test MDX with require calls</p>
<!-- -->
<!-- -->
<img src="https://docusaurus.io/img/test-image.png">
<img src="https://docusaurus.io/assets/images/test-image-742d39e51f41482e8132e79c09ad4eea.png">
<img src="https://docusaurus.io/assets/images/test-image-742d39e51f41482e8132e79c09ad4eea.png">]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Full Blog Sample]]></title>
<id>https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/</id>
<link href="https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/"/>
<updated>2021-03-05T00:00:00.000Z</updated>
<summary type="html"><![CDATA[HTML Heading 1]]></summary>
<content type="html"><![CDATA[<h1>HTML Heading 1</h1>
<h2>HTML Heading 2</h2>
<p>HTML Paragraph</p>
<!-- -->
<!-- -->
<p>Import DOM</p>
<h1>Heading 1</h1>
<h2 class="anchor anchorWithHideOnScrollNavbar_G5V2" id="heading-2">Heading 2<a href="https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/#heading-2" class="hash-link" aria-label="Direct link to Heading 2" title="Direct link to Heading 2"></a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_G5V2" id="heading-3">Heading 3<a href="https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/#heading-3" class="hash-link" aria-label="Direct link to Heading 3" title="Direct link to Heading 3"></a></h3>
<h4 class="anchor anchorWithHideOnScrollNavbar_G5V2" id="heading-4">Heading 4<a href="https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/#heading-4" class="hash-link" aria-label="Direct link to Heading 4" title="Direct link to Heading 4"></a></h4>
<h5 class="anchor anchorWithHideOnScrollNavbar_G5V2" id="heading-5">Heading 5<a href="https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/#heading-5" class="hash-link" aria-label="Direct link to Heading 5" title="Direct link to Heading 5"></a></h5>
<ul>
<li>list1</li>
<li>list2</li>
<li>list3</li>
</ul>
<ul>
<li>list1</li>
<li>list2</li>
<li>list3</li>
</ul>
<p>Normal Text <em>Italics Text</em> <strong>Bold Text</strong></p>
<p><a href="https://v2.docusaurus.io/" target="_blank" rel="noopener noreferrer">link</a> <img loading="lazy" src="https://v2.docusaurus.io/" alt="image" class="img_yGFe"></p>]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Complex Slug]]></title>
<id>https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô/</id>
<link href="https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô/"/>
<updated>2020-08-16T00:00:00.000Z</updated>
<summary type="html"><![CDATA[complex url slug]]></summary>
<content type="html"><![CDATA[<p>complex url slug</p>]]></content>
<category label="date" term="date"/>
<category label="complex" term="complex"/>
</entry>
<entry>
<title type="html"><![CDATA[Simple Slug]]></title>
<id>https://docusaurus.io/myBaseUrl/blog/simple/slug/</id>
<link href="https://docusaurus.io/myBaseUrl/blog/simple/slug/"/>
<updated>2020-08-15T00:00:00.000Z</updated>
<summary type="html"><![CDATA[simple url slug]]></summary>
<content type="html"><![CDATA[<p>simple url slug</p>]]></content>
<author>
<name>Sébastien Lorber</name>
<uri>https://sebastienlorber.com</uri>
</author>
</entry>
<entry>
<title type="html"><![CDATA[some heading]]></title>
<id>https://docusaurus.io/myBaseUrl/blog/heading-as-title/</id>
<link href="https://docusaurus.io/myBaseUrl/blog/heading-as-title/"/>
<updated>2019-01-02T00:00:00.000Z</updated>
</entry>
<entry>
<title type="html"><![CDATA[date-matter]]></title>
<id>https://docusaurus.io/myBaseUrl/blog/date-matter/</id>
<link href="https://docusaurus.io/myBaseUrl/blog/date-matter/"/>
<updated>2019-01-01T00:00:00.000Z</updated>
<summary type="html"><![CDATA[date inside front matter]]></summary>
<content type="html"><![CDATA[<p>date inside front matter</p>]]></content>
<category label="date" term="date"/>
</entry>
<entry>
<title type="html"><![CDATA[Happy 1st Birthday Slash! (translated)]]></title>
<id>https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/</id>
<link href="https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/"/>
<updated>2018-12-14T00:00:00.000Z</updated>
<summary type="html"><![CDATA[Happy birthday! (translated)]]></summary>
<content type="html"><![CDATA[<p>Happy birthday! (translated)</p>]]></content>
<author>
<name>Yangshun Tay (translated)</name>
</author>
<author>
<name>Sébastien Lorber (translated)</name>
<email>lorber.sebastien@gmail.com</email>
</author>
</entry>
</feed>",
]
`;
exports[`json filters to the first two entries 1`] = `
[
"{
@ -506,102 +378,6 @@ exports[`json has feed item for each post 1`] = `
]
`;
exports[`json has feed item for each post - with trailing slash 1`] = `
[
"{
"version": "https://jsonfeed.org/version/1",
"title": "Hello Blog",
"home_page_url": "https://docusaurus.io/myBaseUrl/blog/",
"description": "Hello Blog",
"items": [
{
"id": "https://docusaurus.io/myBaseUrl/blog/blog-with-links/",
"content_html": "<p><a href=\\"https://github.com/facebook/docusaurus\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">absolute full url</a></p>/n<p><a href=\\"https://docusaurus.io/blog/heading-as-title\\">absolute pathname</a></p>/n<p><a href=\\"https://docusaurus.io/blog/heading-as-title\\">relative pathname</a></p>/n<p><a href=\\"https://docusaurus.io/blog/heading-as-title\\">md link</a></p>/n<p><a href=\\"https://docusaurus.io/myBaseUrl/blog/blog-with-links/#title\\">anchor</a></p>/n<p><a href=\\"https://docusaurus.io/blog/heading-as-title#title\\">relative pathname + anchor</a></p>/n<p><img loading=\\"lazy\\" src=\\"https://docusaurus.io/assets/images/test-image-742d39e51f41482e8132e79c09ad4eea.png\\" width=\\"760\\" height=\\"160\\" class=\\"img_yGFe\\"></p>/n<p><img loading=\\"lazy\\" src=\\"https://docusaurus.io/assets/images/slash-introducing-411a16dd05086935b8e9ddae38ae9b45.svg\\" alt=\\"\\" class=\\"img_yGFe\\"></p>/n<img srcset=\\"https://docusaurus.io/img/test-image.png 300w, https://docusaurus.io/img/docusaurus-social-card.png 500w\\">/n<img src=\\"https://docusaurus.io/img/test-image.png\\">",
"url": "https://docusaurus.io/myBaseUrl/blog/blog-with-links/",
"title": "test links",
"summary": "absolute full url",
"date_modified": "2023-07-23T00:00:00.000Z",
"tags": []
},
{
"id": "https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post/",
"content_html": "<p>Test MDX with require calls</p>/n<!-- -->/n<!-- -->/n<img src=\\"https://docusaurus.io/img/test-image.png\\">/n<img src=\\"https://docusaurus.io/assets/images/test-image-742d39e51f41482e8132e79c09ad4eea.png\\">/n<img src=\\"https://docusaurus.io/assets/images/test-image-742d39e51f41482e8132e79c09ad4eea.png\\">",
"url": "https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post/",
"title": "MDX Blog Sample with require calls",
"summary": "Test MDX with require calls",
"date_modified": "2021-03-06T00:00:00.000Z",
"tags": []
},
{
"id": "https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/",
"content_html": "<h1>HTML Heading 1</h1>/n<h2>HTML Heading 2</h2>/n<p>HTML Paragraph</p>/n<!-- -->/n<!-- -->/n<p>Import DOM</p>/n<h1>Heading 1</h1>/n<h2 class=\\"anchor anchorWithHideOnScrollNavbar_G5V2\\" id=\\"heading-2\\">Heading 2<a href=\\"https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/#heading-2\\" class=\\"hash-link\\" aria-label=\\"Direct link to Heading 2\\" title=\\"Direct link to Heading 2\\"></a></h2>/n<h3 class=\\"anchor anchorWithHideOnScrollNavbar_G5V2\\" id=\\"heading-3\\">Heading 3<a href=\\"https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/#heading-3\\" class=\\"hash-link\\" aria-label=\\"Direct link to Heading 3\\" title=\\"Direct link to Heading 3\\"></a></h3>/n<h4 class=\\"anchor anchorWithHideOnScrollNavbar_G5V2\\" id=\\"heading-4\\">Heading 4<a href=\\"https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/#heading-4\\" class=\\"hash-link\\" aria-label=\\"Direct link to Heading 4\\" title=\\"Direct link to Heading 4\\"></a></h4>/n<h5 class=\\"anchor anchorWithHideOnScrollNavbar_G5V2\\" id=\\"heading-5\\">Heading 5<a href=\\"https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/#heading-5\\" class=\\"hash-link\\" aria-label=\\"Direct link to Heading 5\\" title=\\"Direct link to Heading 5\\"></a></h5>/n<ul>/n<li>list1</li>/n<li>list2</li>/n<li>list3</li>/n</ul>/n<ul>/n<li>list1</li>/n<li>list2</li>/n<li>list3</li>/n</ul>/n<p>Normal Text <em>Italics Text</em> <strong>Bold Text</strong></p>/n<p><a href=\\"https://v2.docusaurus.io/\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">link</a> <img loading=\\"lazy\\" src=\\"https://v2.docusaurus.io/\\" alt=\\"image\\" class=\\"img_yGFe\\"></p>",
"url": "https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/",
"title": "Full Blog Sample",
"summary": "HTML Heading 1",
"date_modified": "2021-03-05T00:00:00.000Z",
"tags": []
},
{
"id": "https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô/",
"content_html": "<p>complex url slug</p>",
"url": "https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô/",
"title": "Complex Slug",
"summary": "complex url slug",
"date_modified": "2020-08-16T00:00:00.000Z",
"tags": [
"date",
"complex"
]
},
{
"id": "https://docusaurus.io/myBaseUrl/blog/simple/slug/",
"content_html": "<p>simple url slug</p>",
"url": "https://docusaurus.io/myBaseUrl/blog/simple/slug/",
"title": "Simple Slug",
"summary": "simple url slug",
"date_modified": "2020-08-15T00:00:00.000Z",
"author": {
"name": "Sébastien Lorber",
"url": "https://sebastienlorber.com"
},
"tags": []
},
{
"id": "https://docusaurus.io/myBaseUrl/blog/heading-as-title/",
"content_html": "",
"url": "https://docusaurus.io/myBaseUrl/blog/heading-as-title/",
"title": "some heading",
"date_modified": "2019-01-02T00:00:00.000Z",
"tags": []
},
{
"id": "https://docusaurus.io/myBaseUrl/blog/date-matter/",
"content_html": "<p>date inside front matter</p>",
"url": "https://docusaurus.io/myBaseUrl/blog/date-matter/",
"title": "date-matter",
"summary": "date inside front matter",
"date_modified": "2019-01-01T00:00:00.000Z",
"tags": [
"date"
]
},
{
"id": "https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/",
"content_html": "<p>Happy birthday! (translated)</p>",
"url": "https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/",
"title": "Happy 1st Birthday Slash! (translated)",
"summary": "Happy birthday! (translated)",
"date_modified": "2018-12-14T00:00:00.000Z",
"author": {
"name": "Yangshun Tay (translated)"
},
"tags": []
}
]
}",
]
`;
exports[`rss filters to the first two entries 1`] = `
[
"<?xml version="1.0" encoding="utf-8"?>
@ -817,123 +593,3 @@ exports[`rss has feed item for each post 1`] = `
</rss>",
]
`;
exports[`rss has feed item for each post - with trailing slash 1`] = `
[
"<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Hello Blog</title>
<link>https://docusaurus.io/myBaseUrl/blog/</link>
<description>Hello Blog</description>
<lastBuildDate>Sun, 23 Jul 2023 00:00:00 GMT</lastBuildDate>
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
<generator>https://github.com/jpmonette/feed</generator>
<language>en</language>
<copyright>Copyright</copyright>
<item>
<title><![CDATA[test links]]></title>
<link>https://docusaurus.io/myBaseUrl/blog/blog-with-links/</link>
<guid>https://docusaurus.io/myBaseUrl/blog/blog-with-links/</guid>
<pubDate>Sun, 23 Jul 2023 00:00:00 GMT</pubDate>
<description><![CDATA[absolute full url]]></description>
<content:encoded><![CDATA[<p><a href="https://github.com/facebook/docusaurus" target="_blank" rel="noopener noreferrer">absolute full url</a></p>
<p><a href="https://docusaurus.io/blog/heading-as-title">absolute pathname</a></p>
<p><a href="https://docusaurus.io/blog/heading-as-title">relative pathname</a></p>
<p><a href="https://docusaurus.io/blog/heading-as-title">md link</a></p>
<p><a href="https://docusaurus.io/myBaseUrl/blog/blog-with-links/#title">anchor</a></p>
<p><a href="https://docusaurus.io/blog/heading-as-title#title">relative pathname + anchor</a></p>
<p><img loading="lazy" src="https://docusaurus.io/assets/images/test-image-742d39e51f41482e8132e79c09ad4eea.png" width="760" height="160" class="img_yGFe"></p>
<p><img loading="lazy" src="https://docusaurus.io/assets/images/slash-introducing-411a16dd05086935b8e9ddae38ae9b45.svg" alt="" class="img_yGFe"></p>
<img srcset="https://docusaurus.io/img/test-image.png 300w, https://docusaurus.io/img/docusaurus-social-card.png 500w">
<img src="https://docusaurus.io/img/test-image.png">]]></content:encoded>
</item>
<item>
<title><![CDATA[MDX Blog Sample with require calls]]></title>
<link>https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post/</link>
<guid>https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post/</guid>
<pubDate>Sat, 06 Mar 2021 00:00:00 GMT</pubDate>
<description><![CDATA[Test MDX with require calls]]></description>
<content:encoded><![CDATA[<p>Test MDX with require calls</p>
<!-- -->
<!-- -->
<img src="https://docusaurus.io/img/test-image.png">
<img src="https://docusaurus.io/assets/images/test-image-742d39e51f41482e8132e79c09ad4eea.png">
<img src="https://docusaurus.io/assets/images/test-image-742d39e51f41482e8132e79c09ad4eea.png">]]></content:encoded>
</item>
<item>
<title><![CDATA[Full Blog Sample]]></title>
<link>https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/</link>
<guid>https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/</guid>
<pubDate>Fri, 05 Mar 2021 00:00:00 GMT</pubDate>
<description><![CDATA[HTML Heading 1]]></description>
<content:encoded><![CDATA[<h1>HTML Heading 1</h1>
<h2>HTML Heading 2</h2>
<p>HTML Paragraph</p>
<!-- -->
<!-- -->
<p>Import DOM</p>
<h1>Heading 1</h1>
<h2 class="anchor anchorWithHideOnScrollNavbar_G5V2" id="heading-2">Heading 2<a href="https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/#heading-2" class="hash-link" aria-label="Direct link to Heading 2" title="Direct link to Heading 2"></a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_G5V2" id="heading-3">Heading 3<a href="https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/#heading-3" class="hash-link" aria-label="Direct link to Heading 3" title="Direct link to Heading 3"></a></h3>
<h4 class="anchor anchorWithHideOnScrollNavbar_G5V2" id="heading-4">Heading 4<a href="https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/#heading-4" class="hash-link" aria-label="Direct link to Heading 4" title="Direct link to Heading 4"></a></h4>
<h5 class="anchor anchorWithHideOnScrollNavbar_G5V2" id="heading-5">Heading 5<a href="https://docusaurus.io/myBaseUrl/blog/mdx-blog-post/#heading-5" class="hash-link" aria-label="Direct link to Heading 5" title="Direct link to Heading 5"></a></h5>
<ul>
<li>list1</li>
<li>list2</li>
<li>list3</li>
</ul>
<ul>
<li>list1</li>
<li>list2</li>
<li>list3</li>
</ul>
<p>Normal Text <em>Italics Text</em> <strong>Bold Text</strong></p>
<p><a href="https://v2.docusaurus.io/" target="_blank" rel="noopener noreferrer">link</a> <img loading="lazy" src="https://v2.docusaurus.io/" alt="image" class="img_yGFe"></p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Complex Slug]]></title>
<link>https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô/</link>
<guid>https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô/</guid>
<pubDate>Sun, 16 Aug 2020 00:00:00 GMT</pubDate>
<description><![CDATA[complex url slug]]></description>
<content:encoded><![CDATA[<p>complex url slug</p>]]></content:encoded>
<category>date</category>
<category>complex</category>
</item>
<item>
<title><![CDATA[Simple Slug]]></title>
<link>https://docusaurus.io/myBaseUrl/blog/simple/slug/</link>
<guid>https://docusaurus.io/myBaseUrl/blog/simple/slug/</guid>
<pubDate>Sat, 15 Aug 2020 00:00:00 GMT</pubDate>
<description><![CDATA[simple url slug]]></description>
<content:encoded><![CDATA[<p>simple url slug</p>]]></content:encoded>
</item>
<item>
<title><![CDATA[some heading]]></title>
<link>https://docusaurus.io/myBaseUrl/blog/heading-as-title/</link>
<guid>https://docusaurus.io/myBaseUrl/blog/heading-as-title/</guid>
<pubDate>Wed, 02 Jan 2019 00:00:00 GMT</pubDate>
</item>
<item>
<title><![CDATA[date-matter]]></title>
<link>https://docusaurus.io/myBaseUrl/blog/date-matter/</link>
<guid>https://docusaurus.io/myBaseUrl/blog/date-matter/</guid>
<pubDate>Tue, 01 Jan 2019 00:00:00 GMT</pubDate>
<description><![CDATA[date inside front matter]]></description>
<content:encoded><![CDATA[<p>date inside front matter</p>]]></content:encoded>
<category>date</category>
</item>
<item>
<title><![CDATA[Happy 1st Birthday Slash! (translated)]]></title>
<link>https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/</link>
<guid>https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/</guid>
<pubDate>Fri, 14 Dec 2018 00:00:00 GMT</pubDate>
<description><![CDATA[Happy birthday! (translated)]]></description>
<content:encoded><![CDATA[<p>Happy birthday! (translated)</p>]]></content:encoded>
<author>lorber.sebastien@gmail.com (Sébastien Lorber (translated))</author>
</item>
</channel>
</rss>",
]
`;

View File

@ -1,250 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`blog plugin process blog posts load content 1`] = `
{
"/blog/tags/tag-1": {
"items": [
"/simple/slug/another",
"/another/tags",
"/another/tags2",
],
"label": "tag1",
"pages": [
{
"items": [
"/simple/slug/another",
],
"metadata": {
"blogDescription": "Blog",
"blogTitle": "Blog",
"nextPage": "/blog/tags/tag-1/page/2",
"page": 1,
"permalink": "/blog/tags/tag-1",
"postsPerPage": 1,
"previousPage": undefined,
"totalCount": 3,
"totalPages": 3,
},
},
{
"items": [
"/another/tags",
],
"metadata": {
"blogDescription": "Blog",
"blogTitle": "Blog",
"nextPage": "/blog/tags/tag-1/page/3",
"page": 2,
"permalink": "/blog/tags/tag-1/page/2",
"postsPerPage": 1,
"previousPage": "/blog/tags/tag-1",
"totalCount": 3,
"totalPages": 3,
},
},
{
"items": [
"/another/tags2",
],
"metadata": {
"blogDescription": "Blog",
"blogTitle": "Blog",
"nextPage": undefined,
"page": 3,
"permalink": "/blog/tags/tag-1/page/3",
"postsPerPage": 1,
"previousPage": "/blog/tags/tag-1/page/2",
"totalCount": 3,
"totalPages": 3,
},
},
],
"permalink": "/blog/tags/tag-1",
"unlisted": false,
},
"/blog/tags/tag-2": {
"items": [
"/another/tags",
"/another/tags2",
],
"label": "tag2",
"pages": [
{
"items": [
"/another/tags",
],
"metadata": {
"blogDescription": "Blog",
"blogTitle": "Blog",
"nextPage": "/blog/tags/tag-2/page/2",
"page": 1,
"permalink": "/blog/tags/tag-2",
"postsPerPage": 1,
"previousPage": undefined,
"totalCount": 2,
"totalPages": 2,
},
},
{
"items": [
"/another/tags2",
],
"metadata": {
"blogDescription": "Blog",
"blogTitle": "Blog",
"nextPage": undefined,
"page": 2,
"permalink": "/blog/tags/tag-2/page/2",
"postsPerPage": 1,
"previousPage": "/blog/tags/tag-2",
"totalCount": 2,
"totalPages": 2,
},
},
],
"permalink": "/blog/tags/tag-2",
"unlisted": false,
},
}
`;
exports[`blog plugin process blog posts load content 2`] = `
[
{
"content": "simple url slug",
"id": "/simple/slug/another",
"metadata": {
"authors": [
{
"imageURL": undefined,
"name": "Sébastien Lorber",
"title": "Docusaurus maintainer",
"url": "https://sebastienlorber.com",
},
],
"date": 2020-08-15T00:00:00.000Z,
"description": "simple url slug",
"editUrl": "https://baseEditUrl.com/edit/blog/another-simple-slug-with-tags.md",
"frontMatter": {
"author": "Sébastien Lorber",
"author_title": "Docusaurus maintainer",
"author_url": "https://sebastienlorber.com",
"date": 2020-08-15T00:00:00.000Z,
"slug": "/simple/slug/another",
"tags": [
"tag1",
],
"title": "Another Simple Slug",
},
"hasTruncateMarker": false,
"lastUpdatedAt": undefined,
"lastUpdatedBy": undefined,
"nextItem": {
"permalink": "/blog/another/tags",
"title": "Another With Tag",
},
"permalink": "/blog/simple/slug/another",
"readingTime": 0.015,
"source": "@site/blog/another-simple-slug-with-tags.md",
"tags": [
{
"label": "tag1",
"permalink": "/blog/tags/tag-1",
},
],
"title": "Another Simple Slug",
"unlisted": false,
},
},
{
"content": "with tag",
"id": "/another/tags",
"metadata": {
"authors": [],
"date": 2020-08-15T00:00:00.000Z,
"description": "with tag",
"editUrl": "https://baseEditUrl.com/edit/blog/another-with-tags.md",
"frontMatter": {
"date": 2020-08-15T00:00:00.000Z,
"slug": "/another/tags",
"tags": [
"tag1",
"tag2",
],
"title": "Another With Tag",
},
"hasTruncateMarker": false,
"lastUpdatedAt": undefined,
"lastUpdatedBy": undefined,
"nextItem": {
"permalink": "/blog/another/tags2",
"title": "Another With Tag",
},
"permalink": "/blog/another/tags",
"prevItem": {
"permalink": "/blog/simple/slug/another",
"title": "Another Simple Slug",
},
"readingTime": 0.01,
"source": "@site/blog/another-with-tags.md",
"tags": [
{
"label": "tag1",
"permalink": "/blog/tags/tag-1",
},
{
"label": "tag2",
"permalink": "/blog/tags/tag-2",
},
],
"title": "Another With Tag",
"unlisted": false,
},
},
{
"content": "with tag",
"id": "/another/tags2",
"metadata": {
"authors": [],
"date": 2020-08-15T00:00:00.000Z,
"description": "with tag",
"editUrl": "https://baseEditUrl.com/edit/blog/another-with-tags2.md",
"frontMatter": {
"date": 2020-08-15T00:00:00.000Z,
"slug": "/another/tags2",
"tags": [
"tag1",
"tag2",
],
"title": "Another With Tag",
},
"hasTruncateMarker": false,
"lastUpdatedAt": undefined,
"lastUpdatedBy": undefined,
"permalink": "/blog/another/tags2",
"prevItem": {
"permalink": "/blog/another/tags",
"title": "Another With Tag",
},
"readingTime": 0.01,
"source": "@site/blog/another-with-tags2.md",
"tags": [
{
"label": "tag1",
"permalink": "/blog/tags/tag-1",
},
{
"label": "tag2",
"permalink": "/blog/tags/tag-2",
},
],
"title": "Another With Tag",
"unlisted": false,
},
},
]
`;
exports[`blog plugin works on blog tags without pagination 1`] = `
{
"/blog/tags/tag-1": {

View File

@ -50,6 +50,7 @@ exports[`translateContent falls back when translation is incomplete 1`] = `
"authors": [],
"date": 2021-07-19T00:00:00.000Z,
"description": "/blog/2021/06/19/hello",
"formattedDate": "June 19, 2021",
"frontMatter": {},
"hasTruncateMarker": true,
"permalink": "/blog/2021/06/19/hello",
@ -93,6 +94,7 @@ exports[`translateContent returns translated loaded 1`] = `
"authors": [],
"date": 2021-07-19T00:00:00.000Z,
"description": "/blog/2021/06/19/hello",
"formattedDate": "June 19, 2021",
"frontMatter": {},
"hasTruncateMarker": true,
"permalink": "/blog/2021/06/19/hello",

View File

@ -8,14 +8,12 @@
import {jest} from '@jest/globals';
import fs from 'fs-extra';
import path from 'path';
import {fromPartial} from '@total-typescript/shoehorn';
import {
truncate,
parseBlogFileName,
linkify,
getSourceToPermalink,
paginateBlogPosts,
applyProcessBlogPosts,
type LinkifyParams,
} from '../blogUtils';
import type {BlogBrokenMarkdownLink, BlogContentPaths} from '../types';
@ -40,15 +38,14 @@ describe('truncate', () => {
});
describe('paginateBlogPosts', () => {
const blogPosts = [
{id: 'post1', metadata: {}, content: 'Foo 1'},
{id: 'post2', metadata: {}, content: 'Foo 2'},
{id: 'post3', metadata: {}, content: 'Foo 3'},
{id: 'post4', metadata: {}, content: 'Foo 4'},
{id: 'post5', metadata: {}, content: 'Foo 5'},
] as BlogPost[];
it('generates pages', () => {
it('generates right pages', () => {
const blogPosts = [
{id: 'post1', metadata: {}, content: 'Foo 1'},
{id: 'post2', metadata: {}, content: 'Foo 2'},
{id: 'post3', metadata: {}, content: 'Foo 3'},
{id: 'post4', metadata: {}, content: 'Foo 4'},
{id: 'post5', metadata: {}, content: 'Foo 5'},
] as BlogPost[];
expect(
paginateBlogPosts({
blogPosts,
@ -56,12 +53,8 @@ describe('paginateBlogPosts', () => {
blogTitle: 'Blog Title',
blogDescription: 'Blog Description',
postsPerPageOption: 2,
pageBasePath: 'page',
}),
).toMatchSnapshot();
});
it('generates pages at blog root', () => {
expect(
paginateBlogPosts({
blogPosts,
@ -69,12 +62,8 @@ describe('paginateBlogPosts', () => {
blogTitle: 'Blog Title',
blogDescription: 'Blog Description',
postsPerPageOption: 2,
pageBasePath: 'page',
}),
).toMatchSnapshot();
});
it('generates a single page', () => {
expect(
paginateBlogPosts({
blogPosts,
@ -82,20 +71,6 @@ describe('paginateBlogPosts', () => {
blogTitle: 'Blog Title',
blogDescription: 'Blog Description',
postsPerPageOption: 10,
pageBasePath: 'page',
}),
).toMatchSnapshot();
});
it('generates pages with custom pageBasePath', () => {
expect(
paginateBlogPosts({
blogPosts,
basePageUrl: '/blog',
blogTitle: 'Blog Title',
blogDescription: 'Blog Description',
postsPerPageOption: 2,
pageBasePath: 'customPageBasePath',
}),
).toMatchSnapshot();
});
@ -238,7 +213,7 @@ describe('linkify', () => {
hasTruncateMarker: false,
frontMatter: {},
authors: [],
unlisted: false,
formattedDate: '',
},
content: '',
},
@ -297,81 +272,3 @@ describe('linkify', () => {
} as BlogBrokenMarkdownLink);
});
});
describe('processBlogPosts', () => {
const blogPost2022: BlogPost = fromPartial({
metadata: {date: new Date('2022-01-01')},
});
const blogPost2023: BlogPost = fromPartial({
metadata: {date: new Date('2023-01-01')},
});
const blogPost2024: BlogPost = fromPartial({
metadata: {date: new Date('2024-01-01')},
});
it('filter blogs only from 2024', async () => {
const processedBlogPosts = await applyProcessBlogPosts({
blogPosts: [blogPost2022, blogPost2023, blogPost2024],
processBlogPosts: async ({blogPosts}: {blogPosts: BlogPost[]}) =>
blogPosts.filter(
(blogPost) => blogPost.metadata.date.getFullYear() === 2024,
),
});
expect(processedBlogPosts).toEqual([blogPost2024]);
});
it('sort blogs by date in ascending order', async () => {
const processedBlogPosts = await applyProcessBlogPosts({
blogPosts: [blogPost2023, blogPost2022, blogPost2024],
processBlogPosts: async ({blogPosts}: {blogPosts: BlogPost[]}) =>
blogPosts.sort(
(a, b) => a.metadata.date.getTime() - b.metadata.date.getTime(),
),
});
expect(processedBlogPosts).toEqual([
blogPost2022,
blogPost2023,
blogPost2024,
]);
});
it('sort blogs by date in descending order', async () => {
const processedBlogPosts = await applyProcessBlogPosts({
blogPosts: [blogPost2023, blogPost2022, blogPost2024],
processBlogPosts: async ({blogPosts}: {blogPosts: BlogPost[]}) =>
blogPosts.sort(
(a, b) => b.metadata.date.getTime() - a.metadata.date.getTime(),
),
});
expect(processedBlogPosts).toEqual([
blogPost2024,
blogPost2023,
blogPost2022,
]);
});
it('processBlogPosts return 2022 only', async () => {
const processedBlogPosts = await applyProcessBlogPosts({
blogPosts: [blogPost2023, blogPost2022, blogPost2024],
processBlogPosts: async () => [blogPost2022],
});
expect(processedBlogPosts).toEqual([blogPost2022]);
});
it('processBlogPosts return undefined', async () => {
const processedBlogPosts = await applyProcessBlogPosts({
blogPosts: [blogPost2023, blogPost2022, blogPost2024],
processBlogPosts: async () => {},
});
expect(processedBlogPosts).toEqual([
blogPost2023,
blogPost2022,
blogPost2024,
]);
});
});

View File

@ -9,7 +9,6 @@ import {jest} from '@jest/globals';
import path from 'path';
import fs from 'fs-extra';
import {DEFAULT_PARSE_FRONT_MATTER} from '@docusaurus/utils';
import {fromPartial} from '@total-typescript/shoehorn';
import {DEFAULT_OPTIONS} from '../options';
import {generateBlogPosts} from '../blogUtils';
import {createBlogFeedFiles} from '../feed';
@ -81,12 +80,12 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
const outDir = path.join(siteDir, 'build-snap');
await testGenerateFeeds(
fromPartial({
{
siteDir,
siteConfig,
i18n: DefaultI18N,
outDir,
}),
} as LoadContext,
{
path: 'invalid-blog-path',
routeBasePath: 'blog',
@ -121,12 +120,12 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
// Build is quite difficult to mock, so we built the blog beforehand and
// copied the output to the fixture...
await testGenerateFeeds(
fromPartial({
{
siteDir,
siteConfig,
i18n: DefaultI18N,
outDir,
}),
} as LoadContext,
{
path: 'blog',
routeBasePath: 'blog',
@ -164,12 +163,12 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
// Build is quite difficult to mock, so we built the blog beforehand and
// copied the output to the fixture...
await testGenerateFeeds(
fromPartial({
{
siteDir,
siteConfig,
i18n: DefaultI18N,
outDir,
}),
} as LoadContext,
{
path: 'blog',
routeBasePath: 'blog',
@ -217,12 +216,12 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
// Build is quite difficult to mock, so we built the blog beforehand and
// copied the output to the fixture...
await testGenerateFeeds(
fromPartial({
{
siteDir,
siteConfig,
i18n: DefaultI18N,
outDir,
}),
} as LoadContext,
{
path: 'blog',
routeBasePath: 'blog',
@ -246,48 +245,4 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
).toMatchSnapshot();
fsMock.mockClear();
});
it('has feed item for each post - with trailing slash', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const outDir = path.join(siteDir, 'build-snap');
const siteConfig = {
title: 'Hello',
baseUrl: '/myBaseUrl/',
url: 'https://docusaurus.io',
favicon: 'image/favicon.ico',
trailingSlash: true,
markdown,
};
// Build is quite difficult to mock, so we built the blog beforehand and
// copied the output to the fixture...
await testGenerateFeeds(
fromPartial({
siteDir,
siteConfig,
i18n: DefaultI18N,
outDir,
}),
{
path: 'blog',
routeBasePath: 'blog',
tagsBasePath: 'tags',
authorsMapPath: 'authors.yml',
include: DEFAULT_OPTIONS.include,
exclude: DEFAULT_OPTIONS.exclude,
feedOptions: {
type: [feedType],
copyright: 'Copyright',
},
readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}),
truncateMarker: /<!--\s*truncate\s*-->/,
} as PluginOptions,
);
expect(
fsMock.mock.calls.map((call) => call[1] as string),
).toMatchSnapshot();
fsMock.mockClear();
});
});

View File

@ -8,11 +8,7 @@
import {jest} from '@jest/globals';
import path from 'path';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import {
posixPath,
getFileCommitDate,
LAST_UPDATE_FALLBACK,
} from '@docusaurus/utils';
import {posixPath, getFileCommitDate} from '@docusaurus/utils';
import pluginContentBlog from '../index';
import {validateOptions} from '../options';
import type {
@ -45,7 +41,6 @@ const markdown: MarkdownConfig = {
}
return result;
},
remarkRehypeOptions: undefined,
};
function findByTitle(
@ -180,6 +175,7 @@ describe('blog plugin', () => {
description: `date inside front matter`,
authors: [],
date: new Date('2019-01-01'),
formattedDate: 'January 1, 2019',
frontMatter: {
date: new Date('2019-01-01'),
tags: ['date'],
@ -224,6 +220,7 @@ describe('blog plugin', () => {
},
],
date: new Date('2018-12-14'),
formattedDate: 'December 14, 2018',
frontMatter: {
authors: [
{
@ -259,6 +256,7 @@ describe('blog plugin', () => {
title: 'Simple Slug',
},
date: new Date('2020-08-16'),
formattedDate: 'August 16, 2020',
frontMatter: {
date: '2020/08/16',
slug: '/hey/my super path/héllô',
@ -304,6 +302,7 @@ describe('blog plugin', () => {
title: 'draft',
},
date: new Date('2020-08-15'),
formattedDate: 'August 15, 2020',
frontMatter: {
author: 'Sébastien Lorber',
author_title: 'Docusaurus maintainer',
@ -329,6 +328,7 @@ describe('blog plugin', () => {
description: '',
authors: [],
date: new Date('2019-01-02'),
formattedDate: 'January 2, 2019',
frontMatter: {
date: new Date('2019-01-02'),
},
@ -343,6 +343,39 @@ describe('blog plugin', () => {
});
});
it('builds simple website blog with localized dates', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const blogPostsFrench = await getBlogPosts(siteDir, {}, getI18n('fr'));
expect(blogPostsFrench).toHaveLength(10);
expect(blogPostsFrench[0]!.metadata.formattedDate).toMatchInlineSnapshot(
`"23 juillet 2023"`,
);
expect(blogPostsFrench[1]!.metadata.formattedDate).toMatchInlineSnapshot(
`"6 mars 2021"`,
);
expect(blogPostsFrench[2]!.metadata.formattedDate).toMatchInlineSnapshot(
`"5 mars 2021"`,
);
expect(blogPostsFrench[3]!.metadata.formattedDate).toMatchInlineSnapshot(
`"16 août 2020"`,
);
expect(blogPostsFrench[4]!.metadata.formattedDate).toMatchInlineSnapshot(
`"15 août 2020"`,
);
expect(blogPostsFrench[5]!.metadata.formattedDate).toMatchInlineSnapshot(
`"27 février 2020"`,
);
expect(blogPostsFrench[6]!.metadata.formattedDate).toMatchInlineSnapshot(
`"27 février 2020"`,
);
expect(blogPostsFrench[7]!.metadata.formattedDate).toMatchInlineSnapshot(
`"2 janvier 2019"`,
);
expect(blogPostsFrench[8]!.metadata.formattedDate).toMatchInlineSnapshot(
`"1 janvier 2019"`,
);
});
it('handles edit URL with editLocalizedBlogs: true', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const blogPosts = await getBlogPosts(siteDir, {editLocalizedFiles: true});
@ -441,8 +474,13 @@ describe('blog plugin', () => {
const noDateSource = path.posix.join('@site', PluginPath, 'no date.md');
const noDateSourceFile = path.posix.join(siteDir, PluginPath, 'no date.md');
// We know the file exists and we know we have git
const result = await getFileCommitDate(noDateSourceFile, {age: 'oldest'});
const result = getFileCommitDate(noDateSourceFile, {age: 'oldest'});
const noDateSourceTime = result.date;
const formattedDate = Intl.DateTimeFormat('en', {
day: 'numeric',
month: 'long',
year: 'numeric',
}).format(noDateSourceTime);
expect({
...getByTitle(blogPosts, 'no date').metadata,
@ -456,6 +494,7 @@ describe('blog plugin', () => {
description: `no date`,
authors: [],
date: noDateSourceTime,
formattedDate,
frontMatter: {},
tags: [],
prevItem: undefined,
@ -502,165 +541,4 @@ describe('blog plugin', () => {
expect(blogTags).toMatchSnapshot();
});
it('process blog posts load content', async () => {
const siteDir = path.join(
__dirname,
'__fixtures__',
'website-blog-with-tags',
);
const plugin = await getPlugin(
siteDir,
{
postsPerPage: 1,
processBlogPosts: async ({blogPosts}) =>
blogPosts.filter((blog) => blog.metadata.tags[0]?.label === 'tag1'),
},
DefaultI18N,
);
const {blogPosts, blogTags, blogListPaginated} =
(await plugin.loadContent!())!;
expect(blogListPaginated).toHaveLength(3);
expect(Object.keys(blogTags)).toHaveLength(2);
expect(blogTags).toMatchSnapshot();
expect(blogPosts).toHaveLength(3);
expect(blogPosts).toMatchSnapshot();
});
});
describe('last update', () => {
const siteDir = path.join(
__dirname,
'__fixtures__',
'website-blog-with-last-update',
);
const lastUpdateFor = (date: string) => new Date(date).getTime();
it('author and time', async () => {
const plugin = await getPlugin(
siteDir,
{
showLastUpdateAuthor: true,
showLastUpdateTime: true,
},
DefaultI18N,
);
const {blogPosts} = (await plugin.loadContent!())!;
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBe('seb');
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBe(
LAST_UPDATE_FALLBACK.lastUpdatedAt,
);
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBe(
LAST_UPDATE_FALLBACK.lastUpdatedBy,
);
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBe(
LAST_UPDATE_FALLBACK.lastUpdatedAt,
);
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBe('seb');
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBe(
lastUpdateFor('2021-01-01'),
);
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBe(
LAST_UPDATE_FALLBACK.lastUpdatedBy,
);
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBe(
lastUpdateFor('2021-01-01'),
);
});
it('time only', async () => {
const plugin = await getPlugin(
siteDir,
{
showLastUpdateAuthor: false,
showLastUpdateTime: true,
},
DefaultI18N,
);
const {blogPosts} = (await plugin.loadContent!())!;
expect(blogPosts[0]?.metadata.title).toBe('Author');
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBe(
LAST_UPDATE_FALLBACK.lastUpdatedAt,
);
expect(blogPosts[1]?.metadata.title).toBe('Nothing');
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBe(
LAST_UPDATE_FALLBACK.lastUpdatedAt,
);
expect(blogPosts[2]?.metadata.title).toBe('Both');
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBe(
lastUpdateFor('2021-01-01'),
);
expect(blogPosts[3]?.metadata.title).toBe('Last update date');
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBe(
lastUpdateFor('2021-01-01'),
);
});
it('author only', async () => {
const plugin = await getPlugin(
siteDir,
{
showLastUpdateAuthor: true,
showLastUpdateTime: false,
},
DefaultI18N,
);
const {blogPosts} = (await plugin.loadContent!())!;
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBe('seb');
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBeUndefined();
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBe(
LAST_UPDATE_FALLBACK.lastUpdatedBy,
);
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBeUndefined();
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBe('seb');
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBeUndefined();
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBe(
LAST_UPDATE_FALLBACK.lastUpdatedBy,
);
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBeUndefined();
});
it('none', async () => {
const plugin = await getPlugin(
siteDir,
{
showLastUpdateAuthor: false,
showLastUpdateTime: false,
},
DefaultI18N,
);
const {blogPosts} = (await plugin.loadContent!())!;
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBeUndefined();
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBeUndefined();
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBeUndefined();
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBeUndefined();
});
});

View File

@ -29,6 +29,7 @@ const sampleBlogPosts: BlogPost[] = [
source: '/blog/2021/06/19/hello',
description: '/blog/2021/06/19/hello',
date: new Date(2021, 6, 19),
formattedDate: 'June 19, 2021',
tags: [],
title: 'Hello',
hasTruncateMarker: true,

View File

@ -26,7 +26,6 @@ import {
getContentPathList,
isUnlisted,
isDraft,
readLastUpdateData,
} from '@docusaurus/utils';
import {validateBlogPostFrontMatter} from './frontMatter';
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
@ -58,14 +57,12 @@ export function paginateBlogPosts({
blogTitle,
blogDescription,
postsPerPageOption,
pageBasePath,
}: {
blogPosts: BlogPost[];
basePageUrl: string;
blogTitle: string;
blogDescription: string;
postsPerPageOption: number | 'ALL';
pageBasePath: string;
}): BlogPaginated[] {
const totalCount = blogPosts.length;
const postsPerPage =
@ -76,7 +73,7 @@ export function paginateBlogPosts({
function permalink(page: number) {
return page > 0
? normalizeUrl([basePageUrl, pageBasePath, `${page + 1}`])
? normalizeUrl([basePageUrl, `page/${page + 1}`])
: basePageUrl;
}
@ -114,7 +111,6 @@ export function getBlogTags({
blogTitle: string;
blogDescription: string;
postsPerPageOption: number | 'ALL';
pageBasePath: string;
}): BlogTags {
const groups = groupTaggedItems(
blogPosts,
@ -165,6 +161,25 @@ export function parseBlogFileName(
return {date: undefined, text, slug};
}
function formatBlogPostDate(
locale: string,
date: Date,
calendar: string,
): string {
try {
return new Intl.DateTimeFormat(locale, {
day: 'numeric',
month: 'long',
year: 'numeric',
timeZone: 'UTC',
calendar,
}).format(date);
} catch (err) {
logger.error`Can't format blog post date "${String(date)}"`;
throw err;
}
}
async function parseBlogPostMarkdownFile({
filePath,
parseFrontMatter,
@ -232,12 +247,6 @@ async function processBlogSourceFile(
const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir);
const lastUpdate = await readLastUpdateData(
blogSourceAbsolute,
options,
frontMatter.last_update,
);
const draft = isDraft({frontMatter});
const unlisted = isUnlisted({frontMatter});
@ -265,11 +274,10 @@ async function processBlogSourceFile(
}
try {
const result = await getFileCommitDate(blogSourceAbsolute, {
const result = getFileCommitDate(blogSourceAbsolute, {
age: 'oldest',
includeAuthor: false,
});
return result.date;
} catch (err) {
logger.warn(err);
@ -278,6 +286,11 @@ async function processBlogSourceFile(
}
const date = await getDate();
const formattedDate = formatBlogPostDate(
i18n.currentLocale,
date,
i18n.localeConfigs[i18n.currentLocale]!.calendar,
);
const title = frontMatter.title ?? contentTitle ?? parsedBlogFileName.text;
const description = frontMatter.description ?? excerpt ?? '';
@ -332,6 +345,7 @@ async function processBlogSourceFile(
title,
description,
date,
formattedDate,
tags: normalizeFrontMatterTags(tagsBasePath, frontMatter.tags),
readingTime: showReadingTime
? options.readingTime({
@ -344,8 +358,6 @@ async function processBlogSourceFile(
authors,
frontMatter,
unlisted,
lastUpdatedAt: lastUpdate.lastUpdatedAt,
lastUpdatedBy: lastUpdate.lastUpdatedBy,
},
content,
};
@ -431,19 +443,3 @@ export function linkify({
return newContent;
}
export async function applyProcessBlogPosts({
blogPosts,
processBlogPosts,
}: {
blogPosts: BlogPost[];
processBlogPosts: PluginOptions['processBlogPosts'];
}): Promise<BlogPost[]> {
const processedBlogPosts = await processBlogPosts({blogPosts});
if (Array.isArray(processedBlogPosts)) {
return processedBlogPosts;
}
return blogPosts;
}

View File

@ -1,20 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import useRouteContext from '@docusaurus/useRouteContext';
import type {BlogMetadata} from '@docusaurus/plugin-content-blog';
export function useBlogMetadata(): BlogMetadata {
const routeContext = useRouteContext();
const blogMetadata = routeContext?.data?.blogMetadata;
if (!blogMetadata) {
throw new Error(
"useBlogMetadata() can't be called on the current route because the blog metadata could not be found in route context",
);
}
return blogMetadata as BlogMetadata;
}

View File

@ -11,10 +11,7 @@ import logger from '@docusaurus/logger';
import {Feed, type Author as FeedAuthor} from 'feed';
import * as srcset from 'srcset';
import {normalizeUrl, readOutputHTMLFile} from '@docusaurus/utils';
import {
blogPostContainerID,
applyTrailingSlash,
} from '@docusaurus/utils-common';
import {blogPostContainerID} from '@docusaurus/utils-common';
import {load as cheerioLoad} from 'cheerio';
import type {DocusaurusConfig} from '@docusaurus/types';
import type {
@ -43,14 +40,8 @@ async function generateBlogFeed({
}
const {feedOptions, routeBasePath} = options;
const {url: siteUrl, baseUrl, title, favicon, trailingSlash} = siteConfig;
const blogBaseUrl = applyTrailingSlash(
normalizeUrl([siteUrl, baseUrl, routeBasePath]),
{
trailingSlash,
baseUrl,
},
);
const {url: siteUrl, baseUrl, title, favicon} = siteConfig;
const blogBaseUrl = normalizeUrl([siteUrl, baseUrl, routeBasePath]);
const blogPostsForFeed =
feedOptions.limit === false || feedOptions.limit === null
@ -94,7 +85,7 @@ async function defaultCreateFeedItems({
siteConfig: DocusaurusConfig;
outDir: string;
}): Promise<BlogFeedItem[]> {
const {url: siteUrl, baseUrl, trailingSlash} = siteConfig;
const {url: siteUrl} = siteConfig;
function toFeedAuthor(author: Author): FeedAuthor {
return {name: author.name, link: author.url, email: author.email};
@ -114,19 +105,13 @@ async function defaultCreateFeedItems({
} = post;
const content = await readOutputHTMLFile(
permalink.replace(baseUrl, ''),
permalink.replace(siteConfig.baseUrl, ''),
outDir,
trailingSlash,
siteConfig.trailingSlash,
);
const $ = cheerioLoad(content);
const blogPostAbsoluteUrl = applyTrailingSlash(
normalizeUrl([siteUrl, permalink]),
{
trailingSlash,
baseUrl,
},
);
const blogPostAbsoluteUrl = normalizeUrl([siteUrl, permalink]);
const toAbsoluteUrl = (src: string) =>
String(new URL(src, blogPostAbsoluteUrl));

View File

@ -4,14 +4,14 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {
ContentVisibilitySchema,
FrontMatterLastUpdateSchema,
FrontMatterTOCHeadingLevels,
FrontMatterTagsSchema,
JoiFrontMatter as Joi, // Custom instance for front matter
URISchema,
validateFrontMatter,
FrontMatterTagsSchema,
FrontMatterTOCHeadingLevels,
ContentVisibilitySchema,
} from '@docusaurus/utils-validation';
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
@ -69,7 +69,6 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
hide_table_of_contents: Joi.boolean(),
...FrontMatterTOCHeadingLevels,
last_update: FrontMatterLastUpdateSchema,
})
.messages({
'deprecate.error':

View File

@ -11,7 +11,6 @@ import {
normalizeUrl,
docuHash,
aliasedSitePath,
aliasedSitePathToRelativePath,
getPluginI18nPath,
posixPath,
addTrailingPathSeparator,
@ -21,12 +20,11 @@ import {
DEFAULT_PLUGIN_ID,
} from '@docusaurus/utils';
import {
generateBlogPosts,
getSourceToPermalink,
getBlogTags,
paginateBlogPosts,
shouldBeListed,
applyProcessBlogPosts,
generateBlogPosts,
} from './blogUtils';
import footnoteIDFixer from './remark/footnoteIDFixer';
import {translateContent, getTranslationFiles} from './translations';
@ -34,12 +32,7 @@ import {createBlogFeedFiles} from './feed';
import {toTagProp, toTagsProp} from './props';
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
import type {
LoadContext,
Plugin,
HtmlTags,
RouteMetadata,
} from '@docusaurus/types';
import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types';
import type {
PluginOptions,
BlogPostFrontMatter,
@ -49,7 +42,6 @@ import type {
BlogTags,
BlogContent,
BlogPaginated,
BlogMetadata,
} from '@docusaurus/plugin-content-blog';
export default async function pluginContentBlog(
@ -115,16 +107,11 @@ export default async function pluginContentBlog(
blogDescription,
blogTitle,
blogSidebarTitle,
pageBasePath,
} = options;
const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
let blogPosts = await generateBlogPosts(contentPaths, context, options);
blogPosts = await applyProcessBlogPosts({
blogPosts,
processBlogPosts: options.processBlogPosts,
});
const blogPosts = await generateBlogPosts(contentPaths, context, options);
const listedBlogPosts = blogPosts.filter(shouldBeListed);
if (!blogPosts.length) {
@ -134,10 +121,11 @@ export default async function pluginContentBlog(
blogListPaginated: [],
blogTags: {},
blogTagsListPath,
blogTagsPaginated: [],
};
}
// Collocate next and prev metadata.
// Colocate next and prev metadata.
listedBlogPosts.forEach((blogPost, index) => {
const prevItem = index > 0 ? listedBlogPosts[index - 1] : null;
if (prevItem) {
@ -165,7 +153,6 @@ export default async function pluginContentBlog(
blogDescription,
postsPerPageOption,
basePageUrl: baseBlogUrl,
pageBasePath,
});
const blogTags: BlogTags = getBlogTags({
@ -173,7 +160,6 @@ export default async function pluginContentBlog(
postsPerPageOption,
blogDescription,
blogTitle,
pageBasePath,
});
return {
@ -194,7 +180,6 @@ export default async function pluginContentBlog(
blogArchiveComponent,
routeBasePath,
archiveBasePath,
blogTitle,
} = options;
const {addRoute, createData} = actions;
@ -270,24 +255,6 @@ export default async function pluginContentBlog(
),
);
const blogMetadata: BlogMetadata = {
blogBasePath: normalizeUrl([baseUrl, routeBasePath]),
blogTitle,
};
const blogMetadataPath = await createData(
`blogMetadata-${pluginId}.json`,
JSON.stringify(blogMetadata, null, 2),
);
function createBlogPostRouteMetadata(
blogPostMeta: BlogPostMetadata,
): RouteMetadata {
return {
sourceFilePath: aliasedSitePathToRelativePath(blogPostMeta.source),
lastUpdatedAt: blogPostMeta.lastUpdatedAt,
};
}
// Create routes for blog entries.
await Promise.all(
blogPosts.map(async (blogPost) => {
@ -307,10 +274,6 @@ export default async function pluginContentBlog(
sidebar: aliasedSource(sidebarProp),
content: metadata.source,
},
metadata: createBlogPostRouteMetadata(metadata),
context: {
blogMetadata: aliasedSource(blogMetadataPath),
},
});
blogItemsToMetadata[id] = metadata;

View File

@ -45,15 +45,11 @@ export const DEFAULT_OPTIONS: PluginOptions = {
routeBasePath: 'blog',
tagsBasePath: 'tags',
archiveBasePath: 'archive',
pageBasePath: 'page',
path: 'blog',
editLocalizedFiles: false,
authorsMapPath: 'authors.yml',
readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}),
sortPosts: 'descending',
showLastUpdateTime: false,
showLastUpdateAuthor: false,
processBlogPosts: async () => undefined,
};
const PluginOptionSchema = Joi.object<PluginOptions>({
@ -63,7 +59,6 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
.allow(null),
routeBasePath: RouteBasePathSchema.default(DEFAULT_OPTIONS.routeBasePath),
tagsBasePath: Joi.string().default(DEFAULT_OPTIONS.tagsBasePath),
pageBasePath: Joi.string().default(DEFAULT_OPTIONS.pageBasePath),
include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include),
exclude: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.exclude),
postsPerPage: Joi.alternatives()
@ -137,13 +132,6 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
sortPosts: Joi.string()
.valid('descending', 'ascending')
.default(DEFAULT_OPTIONS.sortPosts),
showLastUpdateTime: Joi.bool().default(DEFAULT_OPTIONS.showLastUpdateTime),
showLastUpdateAuthor: Joi.bool().default(
DEFAULT_OPTIONS.showLastUpdateAuthor,
),
processBlogPosts: Joi.function()
.optional()
.default(() => DEFAULT_OPTIONS.processBlogPosts),
}).default(DEFAULT_OPTIONS);
export function validateOptions({

View File

@ -5,17 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
/// <reference types="@docusaurus/module-type-aliases" />
declare module '@docusaurus/plugin-content-blog' {
import type {LoadedMDXContent} from '@docusaurus/mdx-loader';
import type {MDXOptions} from '@docusaurus/mdx-loader';
import type {
FrontMatterTag,
Tag,
LastUpdateData,
FrontMatterLastUpdate,
} from '@docusaurus/utils';
import type {FrontMatterTag, Tag} from '@docusaurus/utils';
import type {DocusaurusConfig, Plugin, LoadContext} from '@docusaurus/types';
import type {Item as FeedItem} from 'feed';
import type {Overwrite} from 'utility-types';
@ -161,8 +154,6 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
toc_min_heading_level?: number;
/** Maximum TOC heading level. Must be between 2 and 6. */
toc_max_heading_level?: number;
/** Allows overriding the last updated author and/or date. */
last_update?: FrontMatterLastUpdate;
};
export type BlogPostFrontMatterAuthor = Author & {
@ -187,7 +178,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
| BlogPostFrontMatterAuthor
| (string | BlogPostFrontMatterAuthor)[];
export type BlogPostMetadata = LastUpdateData & {
export type BlogPostMetadata = {
/** Path to the Markdown source, with `@site` alias. */
readonly source: string;
/**
@ -199,6 +190,11 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
* into a string.
*/
readonly date: Date;
/**
* Publish date formatted according to the locale, so that the client can
* render the date regardless of the existence of `Intl.DateTimeFormat`.
*/
readonly formattedDate: string;
/** Full link including base URL. */
readonly permalink: string;
/**
@ -337,11 +333,6 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
defaultReadingTime: ReadingTimeFunction;
},
) => number | undefined;
export type ProcessBlogPostsFn = (params: {
blogPosts: BlogPost[];
}) => Promise<void | BlogPost[]>;
/**
* Plugin options after normalization.
*/
@ -360,15 +351,10 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
routeBasePath: string;
/**
* URL route for the tags section of your blog. Will be appended to
* `routeBasePath`.
* `routeBasePath`. **DO NOT** include a trailing slash.
*/
tagsBasePath: string;
/**
* URL route for the pages section of your blog. Will be appended to
* `routeBasePath`.
*/
pageBasePath: string;
/**
* URL route for the archive section of your blog. Will be appended to
* `routeBasePath`. **DO NOT** include a trailing slash. Use `null` to
* disable generation of archive.
@ -433,14 +419,6 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
readingTime: ReadingTimeFunctionOption;
/** Governs the direction of blog post sorting. */
sortPosts: 'ascending' | 'descending';
/** Whether to display the last date the doc was updated. */
showLastUpdateTime: boolean;
/** Whether to display the author who last updated the doc. */
showLastUpdateAuthor: boolean;
/** An optional function which can be used to transform blog posts
* (filter, modify, delete, etc...).
*/
processBlogPosts: ProcessBlogPostsFn;
};
/**
@ -483,13 +461,6 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
blogTagsListPath: string;
};
export type BlogMetadata = {
/** the path to the base of the blog */
blogBasePath: string;
/** title of the overall blog */
blogTitle: string;
};
export type BlogTags = {
[permalink: string]: BlogTag;
};
@ -561,7 +532,6 @@ declare module '@theme/BlogPostPage' {
BlogPostFrontMatter,
BlogSidebar,
PropBlogPostContent,
BlogMetadata,
} from '@docusaurus/plugin-content-blog';
export type FrontMatter = BlogPostFrontMatter;
@ -573,8 +543,6 @@ declare module '@theme/BlogPostPage' {
readonly sidebar: BlogSidebar;
/** Content of this post as an MDX component, with useful metadata. */
readonly content: Content;
/** Metadata about the blog. */
readonly blogMetadata: BlogMetadata;
}
export default function BlogPostPage(props: Props): JSX.Element;
@ -584,10 +552,6 @@ declare module '@theme/BlogPostPage/Metadata' {
export default function BlogPostPageMetadata(): JSX.Element;
}
declare module '@theme/BlogPostPage/StructuredData' {
export default function BlogPostStructuredData(): JSX.Element;
}
declare module '@theme/BlogListPage' {
import type {Content} from '@theme/BlogPostPage';
import type {
@ -610,28 +574,6 @@ declare module '@theme/BlogListPage' {
export default function BlogListPage(props: Props): JSX.Element;
}
declare module '@theme/BlogListPage/StructuredData' {
import type {Content} from '@theme/BlogPostPage';
import type {
BlogSidebar,
BlogPaginatedMetadata,
} from '@docusaurus/plugin-content-blog';
export interface Props {
/** Blog sidebar. */
readonly sidebar: BlogSidebar;
/** Metadata of the current listing page. */
readonly metadata: BlogPaginatedMetadata;
/**
* Array of blog posts included on this page. Every post's metadata is also
* available.
*/
readonly items: readonly {readonly content: Content}[];
}
export default function BlogListPageStructuredData(props: Props): JSX.Element;
}
declare module '@theme/BlogTagsListPage' {
import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
import type {TagsListItem} from '@docusaurus/utils';

View File

@ -1,16 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"noEmit": false,
"composite": true,
"incremental": true,
"tsBuildInfoFile": "./lib/.tsbuildinfo-client",
"moduleResolution": "bundler",
"module": "esnext",
"target": "esnext",
"rootDir": "src",
"outDir": "lib"
},
"include": ["src/client", "src/*.d.ts"],
"exclude": ["**/__tests__/**"]
}

View File

@ -1,6 +1,5 @@
{
"extends": "../../tsconfig.json",
"references": [{"path": "./tsconfig.client.json"}],
"compilerOptions": {
"noEmit": false,
"incremental": true,
@ -9,5 +8,5 @@
"outDir": "lib"
},
"include": ["src"],
"exclude": ["src/client", "**/__tests__/**"]
"exclude": ["**/__tests__/**"]
}

View File

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

View File

@ -52,6 +52,7 @@ exports[`simple website content 1`] = `
"description": "Images",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"id": "baz",
"pagination_label": "baz pagination_label",
@ -104,6 +105,7 @@ exports[`simple website content 2`] = `
"description": "Hi, Endilie here :)",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"id": "hello",
"sidebar_label": "Hello sidebar_label",
@ -149,6 +151,7 @@ exports[`simple website content 3`] = `
"description": "This is custom description",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"description": "This is custom description",
"id": "bar",
@ -1482,10 +1485,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/hello.md",
},
"modules": {
"content": "@site/docs/hello.md",
},
@ -1495,10 +1494,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/slugs/absoluteSlug.md",
},
"modules": {
"content": "@site/docs/slugs/absoluteSlug.md",
},
@ -1516,10 +1511,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/customLastUpdate.md",
},
"modules": {
"content": "@site/docs/customLastUpdate.md",
},
@ -1528,10 +1519,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/doc with space.md",
},
"modules": {
"content": "@site/docs/doc with space.md",
},
@ -1540,10 +1527,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/doc-draft.md",
},
"modules": {
"content": "@site/docs/doc-draft.md",
},
@ -1552,10 +1535,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/doc-unlisted.md",
},
"modules": {
"content": "@site/docs/doc-unlisted.md",
},
@ -1565,10 +1544,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/foo/bar.md",
},
"modules": {
"content": "@site/docs/foo/bar.md",
},
@ -1578,10 +1553,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/foo/baz.md",
},
"modules": {
"content": "@site/docs/foo/baz.md",
},
@ -1591,10 +1562,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/headingAsTitle.md",
},
"modules": {
"content": "@site/docs/headingAsTitle.md",
},
@ -1604,10 +1571,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/rootResolvedSlug.md",
},
"modules": {
"content": "@site/docs/rootResolvedSlug.md",
},
@ -1617,10 +1580,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/ipsum.md",
},
"modules": {
"content": "@site/docs/ipsum.md",
},
@ -1629,10 +1588,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/lastUpdateAuthorOnly.md",
},
"modules": {
"content": "@site/docs/lastUpdateAuthorOnly.md",
},
@ -1641,10 +1596,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/lastUpdateDateOnly.md",
},
"modules": {
"content": "@site/docs/lastUpdateDateOnly.md",
},
@ -1653,10 +1604,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/lorem.md",
},
"modules": {
"content": "@site/docs/lorem.md",
},
@ -1665,10 +1612,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/rootAbsoluteSlug.md",
},
"modules": {
"content": "@site/docs/rootAbsoluteSlug.md",
},
@ -1678,10 +1621,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/rootRelativeSlug.md",
},
"modules": {
"content": "@site/docs/rootRelativeSlug.md",
},
@ -1691,10 +1630,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/rootTryToEscapeSlug.md",
},
"modules": {
"content": "@site/docs/rootTryToEscapeSlug.md",
},
@ -1704,10 +1639,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/slugs/resolvedSlug.md",
},
"modules": {
"content": "@site/docs/slugs/resolvedSlug.md",
},
@ -1716,10 +1647,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/slugs/relativeSlug.md",
},
"modules": {
"content": "@site/docs/slugs/relativeSlug.md",
},
@ -1728,10 +1655,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/slugs/tryToEscapeSlug.md",
},
"modules": {
"content": "@site/docs/slugs/tryToEscapeSlug.md",
},
@ -1740,10 +1663,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/unlisted-category/index.md",
},
"modules": {
"content": "@site/docs/unlisted-category/index.md",
},
@ -1753,10 +1672,6 @@ exports[`simple website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/unlisted-category/unlisted-category-doc.md",
},
"modules": {
"content": "@site/docs/unlisted-category/unlisted-category-doc.md",
},
@ -2056,6 +1971,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha
"description": "Getting started text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "getting-started",
"lastUpdatedAt": undefined,
@ -2083,6 +1999,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha
"description": "Installation text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "installation",
"lastUpdatedAt": undefined,
@ -2113,6 +2030,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha
"description": "Guide 1 text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"id": "guide1",
"sidebar_position": 1,
@ -2146,6 +2064,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha
"description": "Guide 2 text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"id": "guide2",
},
@ -2178,6 +2097,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha
"description": "Guide 2.5 text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"id": "guide2.5",
"sidebar_position": 2.5,
@ -2211,6 +2131,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha
"description": "Guide 3 text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"id": "guide3",
"sidebar_position": 3,
@ -2244,6 +2165,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha
"description": "Guide 4 text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"id": "guide4",
},
@ -2276,6 +2198,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha
"description": "Guide 5 text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"id": "guide5",
},
@ -2308,6 +2231,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha
"description": "API Overview text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "API/api-overview",
"lastUpdatedAt": undefined,
@ -2338,6 +2262,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha
"description": "Client API text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "API/Core APIs/Client API",
"lastUpdatedAt": undefined,
@ -2368,6 +2293,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha
"description": "Server API text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "API/Core APIs/Server API",
"lastUpdatedAt": undefined,
@ -2398,6 +2324,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha
"description": "Plugin API text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "API/Extension APIs/Plugin API",
"lastUpdatedAt": undefined,
@ -2428,6 +2355,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha
"description": "Theme API text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "API/Extension APIs/Theme API",
"lastUpdatedAt": undefined,
@ -2458,6 +2386,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha
"description": "API End text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "API/api-end",
"lastUpdatedAt": undefined,
@ -2637,6 +2566,7 @@ exports[`site with partial autogenerated sidebars docs in partially generated si
"description": "API End text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "API/api-end",
"lastUpdatedAt": undefined,
@ -2664,6 +2594,7 @@ exports[`site with partial autogenerated sidebars docs in partially generated si
"description": "API Overview text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "API/api-overview",
"lastUpdatedAt": undefined,
@ -2694,6 +2625,7 @@ exports[`site with partial autogenerated sidebars docs in partially generated si
"description": "Plugin API text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "API/Extension APIs/Plugin API",
"lastUpdatedAt": undefined,
@ -2724,6 +2656,7 @@ exports[`site with partial autogenerated sidebars docs in partially generated si
"description": "Theme API text",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "API/Extension APIs/Theme API",
"lastUpdatedAt": undefined,
@ -2783,6 +2716,7 @@ exports[`versioned website (community) content 1`] = `
"description": "Team current version (translated)",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"title": "Team title translated",
},
@ -2809,6 +2743,7 @@ exports[`versioned website (community) content 2`] = `
"description": "Team 1.0.0",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "team",
"lastUpdatedAt": undefined,
@ -3028,10 +2963,6 @@ exports[`versioned website (community) content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "i18n/en/docusaurus-plugin-content-docs-community/current/team.md",
},
"modules": {
"content": "@site/i18n/en/docusaurus-plugin-content-docs-community/current/team.md",
},
@ -3059,10 +2990,6 @@ exports[`versioned website (community) content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "community_versioned_docs/version-1.0.0/team.md",
},
"modules": {
"content": "@site/community_versioned_docs/version-1.0.0/team.md",
},
@ -3096,6 +3023,7 @@ exports[`versioned website content 1`] = `
"description": "This is next version of bar.",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"slug": "barSlug",
"tags": [
@ -3146,6 +3074,7 @@ exports[`versioned website content 2`] = `
"description": "Bar 1.0.1 !",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "foo/bar",
"lastUpdatedAt": undefined,
@ -3173,6 +3102,7 @@ exports[`versioned website content 3`] = `
"description": "Hello next !",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"slug": "/",
},
@ -3202,6 +3132,7 @@ exports[`versioned website content 4`] = `
"description": "Hello 1.0.1 !",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"slug": "/",
},
@ -3231,6 +3162,7 @@ exports[`versioned website content 5`] = `
"description": "Baz 1.0.0 ! This will be deleted in next subsequent versions.",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "foo/baz",
"lastUpdatedAt": undefined,
@ -4270,10 +4202,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md",
},
"modules": {
"content": "@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md",
},
@ -4283,10 +4211,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "versioned_docs/version-1.0.0/foo/bar.md",
},
"modules": {
"content": "@site/versioned_docs/version-1.0.0/foo/bar.md",
},
@ -4296,10 +4220,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "versioned_docs/version-1.0.0/foo/baz.md",
},
"modules": {
"content": "@site/versioned_docs/version-1.0.0/foo/baz.md",
},
@ -4359,10 +4279,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/hello.md",
},
"modules": {
"content": "@site/docs/hello.md",
},
@ -4372,10 +4288,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/slugs/absoluteSlug.md",
},
"modules": {
"content": "@site/docs/slugs/absoluteSlug.md",
},
@ -4384,10 +4296,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/foo/bar.md",
},
"modules": {
"content": "@site/docs/foo/bar.md",
},
@ -4397,10 +4305,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/slugs/resolvedSlug.md",
},
"modules": {
"content": "@site/docs/slugs/resolvedSlug.md",
},
@ -4409,10 +4313,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/slugs/relativeSlug.md",
},
"modules": {
"content": "@site/docs/slugs/relativeSlug.md",
},
@ -4421,10 +4321,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "docs/slugs/tryToEscapeSlug.md",
},
"modules": {
"content": "@site/docs/slugs/tryToEscapeSlug.md",
},
@ -4451,10 +4347,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "versioned_docs/version-withSlugs/slugs/absoluteSlug.md",
},
"modules": {
"content": "@site/versioned_docs/version-withSlugs/slugs/absoluteSlug.md",
},
@ -4463,10 +4355,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "versioned_docs/version-withSlugs/rootResolvedSlug.md",
},
"modules": {
"content": "@site/versioned_docs/version-withSlugs/rootResolvedSlug.md",
},
@ -4475,10 +4363,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "versioned_docs/version-withSlugs/rootAbsoluteSlug.md",
},
"modules": {
"content": "@site/versioned_docs/version-withSlugs/rootAbsoluteSlug.md",
},
@ -4488,10 +4372,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "versioned_docs/version-withSlugs/rootRelativeSlug.md",
},
"modules": {
"content": "@site/versioned_docs/version-withSlugs/rootRelativeSlug.md",
},
@ -4500,10 +4380,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "versioned_docs/version-withSlugs/rootTryToEscapeSlug.md",
},
"modules": {
"content": "@site/versioned_docs/version-withSlugs/rootTryToEscapeSlug.md",
},
@ -4512,10 +4388,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "versioned_docs/version-withSlugs/slugs/resolvedSlug.md",
},
"modules": {
"content": "@site/versioned_docs/version-withSlugs/slugs/resolvedSlug.md",
},
@ -4524,10 +4396,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "versioned_docs/version-withSlugs/slugs/relativeSlug.md",
},
"modules": {
"content": "@site/versioned_docs/version-withSlugs/slugs/relativeSlug.md",
},
@ -4536,10 +4404,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "versioned_docs/version-withSlugs/slugs/tryToEscapeSlug.md",
},
"modules": {
"content": "@site/versioned_docs/version-withSlugs/slugs/tryToEscapeSlug.md",
},
@ -4566,10 +4430,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "versioned_docs/version-1.0.1/hello.md",
},
"modules": {
"content": "@site/versioned_docs/version-1.0.1/hello.md",
},
@ -4579,10 +4439,6 @@ exports[`versioned website content: route config 1`] = `
{
"component": "@theme/DocItem",
"exact": true,
"metadata": {
"lastUpdatedAt": undefined,
"sourceFilePath": "versioned_docs/version-1.0.1/foo/bar.md",
},
"modules": {
"content": "@site/versioned_docs/version-1.0.1/foo/bar.md",
},

View File

@ -7,13 +7,8 @@
import {jest} from '@jest/globals';
import path from 'path';
import {loadContext} from '@docusaurus/core/src/server/site';
import {
createSlugger,
posixPath,
DEFAULT_PLUGIN_ID,
LAST_UPDATE_FALLBACK,
} from '@docusaurus/utils';
import {loadContext} from '@docusaurus/core/src/server/index';
import {createSlugger, posixPath, DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import {createSidebarsUtils} from '../sidebars/utils';
import {
processDocMetadata,
@ -479,8 +474,9 @@ describe('simple site', () => {
custom_edit_url: 'https://github.com/customUrl/docs/lorem.md',
unrelated_front_matter: "won't be part of metadata",
},
lastUpdatedAt: LAST_UPDATE_FALLBACK.lastUpdatedAt,
lastUpdatedBy: LAST_UPDATE_FALLBACK.lastUpdatedBy,
lastUpdatedAt: 1539502055,
formattedLastUpdatedAt: 'Oct 14, 2018',
lastUpdatedBy: 'Author',
tags: [],
unlisted: false,
});
@ -576,7 +572,8 @@ describe('simple site', () => {
},
title: 'Custom Last Update',
},
lastUpdatedAt: new Date('1/1/2000').getTime(),
lastUpdatedAt: new Date('1/1/2000').getTime() / 1000,
formattedLastUpdatedAt: 'Jan 1, 2000',
lastUpdatedBy: 'Custom Author (processed by parseFrontMatter)',
sidebarPosition: undefined,
tags: [],
@ -614,7 +611,8 @@ describe('simple site', () => {
},
title: 'Last Update Author Only',
},
lastUpdatedAt: LAST_UPDATE_FALLBACK.lastUpdatedAt,
lastUpdatedAt: 1539502055,
formattedLastUpdatedAt: 'Oct 14, 2018',
lastUpdatedBy: 'Custom Author (processed by parseFrontMatter)',
sidebarPosition: undefined,
tags: [],
@ -652,7 +650,8 @@ describe('simple site', () => {
},
title: 'Last Update Date Only',
},
lastUpdatedAt: new Date('1/1/2000').getTime(),
lastUpdatedAt: new Date('1/1/2000').getTime() / 1000,
formattedLastUpdatedAt: 'Jan 1, 2000',
lastUpdatedBy: 'Author',
sidebarPosition: undefined,
tags: [],
@ -692,6 +691,7 @@ describe('simple site', () => {
title: 'Custom Last Update',
},
lastUpdatedAt: undefined,
formattedLastUpdatedAt: undefined,
lastUpdatedBy: undefined,
sidebarPosition: undefined,
tags: [],

View File

@ -444,19 +444,19 @@ describe('validateDocFrontMatter last_update', () => {
invalidFrontMatters: [
[
{last_update: null},
'"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date',
'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).',
],
[
{last_update: {}},
'"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date',
'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).',
],
[
{last_update: ''},
'"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date',
'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).',
],
[
{last_update: {invalid: 'key'}},
'"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date',
'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).',
],
[
{last_update: {author: 'test author', date: 'I am not a date :('}},

View File

@ -12,9 +12,9 @@ import _ from 'lodash';
import {isMatch} from 'picomatch';
import commander from 'commander';
import webpack from 'webpack';
import {loadContext} from '@docusaurus/core/src/server/site';
import {loadContext} from '@docusaurus/core/src/server/index';
import {applyConfigureWebpack} from '@docusaurus/core/src/webpack/utils';
import {sortRoutes} from '@docusaurus/core/src/server/plugins/routeConfig';
import {sortConfig} from '@docusaurus/core/src/server/plugins/routeConfig';
import {posixPath} from '@docusaurus/utils';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
@ -109,7 +109,7 @@ Entries created:
expectSnapshot: () => {
// Sort the route config like in src/server/plugins/index.ts for
// consistent snapshot ordering
sortRoutes(routeConfigs);
sortConfig(routeConfigs);
expect(routeConfigs).not.toEqual([]);
expect(routeConfigs).toMatchSnapshot('route config');
expect(dataContainer).toMatchSnapshot('data');

View File

@ -0,0 +1,117 @@
/**
* 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 {jest} from '@jest/globals';
import fs from 'fs-extra';
import path from 'path';
import shell from 'shelljs';
import {createTempRepo} from '@testing-utils/git';
import {getFileLastUpdate} from '../lastUpdate';
describe('getFileLastUpdate', () => {
const existingFilePath = path.join(
__dirname,
'__fixtures__/simple-site/docs/hello.md',
);
it('existing test file in repository with Git timestamp', async () => {
const lastUpdateData = await getFileLastUpdate(existingFilePath);
expect(lastUpdateData).not.toBeNull();
const {author, timestamp} = lastUpdateData!;
expect(author).not.toBeNull();
expect(typeof author).toBe('string');
expect(timestamp).not.toBeNull();
expect(typeof timestamp).toBe('number');
});
it('existing test file with spaces in path', async () => {
const filePathWithSpace = path.join(
__dirname,
'__fixtures__/simple-site/docs/doc with space.md',
);
const lastUpdateData = await getFileLastUpdate(filePathWithSpace);
expect(lastUpdateData).not.toBeNull();
const {author, timestamp} = lastUpdateData!;
expect(author).not.toBeNull();
expect(typeof author).toBe('string');
expect(timestamp).not.toBeNull();
expect(typeof timestamp).toBe('number');
});
it('non-existing file', async () => {
const consoleMock = jest
.spyOn(console, 'warn')
.mockImplementation(() => {});
const nonExistingFileName = '.nonExisting';
const nonExistingFilePath = path.join(
__dirname,
'__fixtures__',
nonExistingFileName,
);
await expect(getFileLastUpdate(nonExistingFilePath)).resolves.toBeNull();
expect(consoleMock).toHaveBeenCalledTimes(1);
expect(consoleMock).toHaveBeenLastCalledWith(
expect.stringMatching(/because the file does not exist./),
);
consoleMock.mockRestore();
});
it('temporary created file that is not tracked by git', async () => {
const consoleMock = jest
.spyOn(console, 'warn')
.mockImplementation(() => {});
const {repoDir} = createTempRepo();
const tempFilePath = path.join(repoDir, 'file.md');
await fs.writeFile(tempFilePath, 'Lorem ipsum :)');
await expect(getFileLastUpdate(tempFilePath)).resolves.toBeNull();
expect(consoleMock).toHaveBeenCalledTimes(1);
expect(consoleMock).toHaveBeenLastCalledWith(
expect.stringMatching(/not tracked by git./),
);
await fs.unlink(tempFilePath);
});
it('multiple files not tracked by git', async () => {
const consoleMock = jest
.spyOn(console, 'warn')
.mockImplementation(() => {});
const {repoDir} = createTempRepo();
const tempFilePath1 = path.join(repoDir, 'file1.md');
const tempFilePath2 = path.join(repoDir, 'file2.md');
await fs.writeFile(tempFilePath1, 'Lorem ipsum :)');
await fs.writeFile(tempFilePath2, 'Lorem ipsum :)');
await expect(getFileLastUpdate(tempFilePath1)).resolves.toBeNull();
await expect(getFileLastUpdate(tempFilePath2)).resolves.toBeNull();
expect(consoleMock).toHaveBeenCalledTimes(1);
expect(consoleMock).toHaveBeenLastCalledWith(
expect.stringMatching(/not tracked by git./),
);
await fs.unlink(tempFilePath1);
await fs.unlink(tempFilePath2);
});
it('git does not exist', async () => {
const mock = jest.spyOn(shell, 'which').mockImplementationOnce(() => null);
const consoleMock = jest
.spyOn(console, 'warn')
.mockImplementation(() => {});
const lastUpdateData = await getFileLastUpdate(existingFilePath);
expect(lastUpdateData).toBeNull();
expect(consoleMock).toHaveBeenLastCalledWith(
expect.stringMatching(
/.*\[WARNING\].* Sorry, the docs plugin last update options require Git\..*/,
),
);
consoleMock.mockRestore();
mock.mockRestore();
});
});

View File

@ -86,20 +86,10 @@ export const useAllDocsData = (): {[pluginId: string]: GlobalPluginData} =>
}
| undefined) ?? StableEmptyObject;
export const useDocsData = (pluginId: string | undefined): GlobalPluginData => {
try {
return usePluginData('docusaurus-plugin-content-docs', pluginId, {
failfast: true,
}) as GlobalPluginData;
} catch (error) {
throw new Error(
`You are using a feature of the Docusaurus docs plugin, but this plugin does not seem to be enabled${
pluginId === 'Default' ? '' : ` (pluginId=${pluginId}`
}`,
{cause: error as Error},
);
}
};
export const useDocsData = (pluginId: string | undefined): GlobalPluginData =>
usePluginData('docusaurus-plugin-content-docs', pluginId, {
failfast: true,
}) as GlobalPluginData;
// TODO this feature should be provided by docusaurus core
export function useActivePlugin(

View File

@ -8,6 +8,7 @@
import path from 'path';
import fs from 'fs-extra';
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {
aliasedSitePath,
getEditUrl,
@ -20,11 +21,12 @@ import {
normalizeFrontMatterTags,
isUnlisted,
isDraft,
readLastUpdateData,
} from '@docusaurus/utils';
import {validateDocFrontMatter} from './frontMatter';
import {getFileLastUpdate} from './lastUpdate';
import getSlug from './slug';
import {stripPathNumberPrefixes} from './numberPrefix';
import {validateDocFrontMatter} from './frontMatter';
import {toDocNavigationLink, toNavigationLink} from './sidebars/utils';
import type {
MetadataOptions,
@ -33,13 +35,61 @@ import type {
DocMetadataBase,
DocMetadata,
PropNavigationLink,
LastUpdateData,
VersionMetadata,
LoadedVersion,
FileChange,
} from '@docusaurus/plugin-content-docs';
import type {LoadContext} from '@docusaurus/types';
import type {SidebarsUtils} from './sidebars/utils';
import type {DocFile} from './types';
type LastUpdateOptions = Pick<
PluginOptions,
'showLastUpdateAuthor' | 'showLastUpdateTime'
>;
async function readLastUpdateData(
filePath: string,
options: LastUpdateOptions,
lastUpdateFrontMatter: FileChange | undefined,
): Promise<LastUpdateData> {
const {showLastUpdateAuthor, showLastUpdateTime} = options;
if (showLastUpdateAuthor || showLastUpdateTime) {
const frontMatterTimestamp = lastUpdateFrontMatter?.date
? new Date(lastUpdateFrontMatter.date).getTime() / 1000
: undefined;
if (lastUpdateFrontMatter?.author && lastUpdateFrontMatter.date) {
return {
lastUpdatedAt: frontMatterTimestamp,
lastUpdatedBy: lastUpdateFrontMatter.author,
};
}
// Use fake data in dev for faster development.
const fileLastUpdateData =
process.env.NODE_ENV === 'production'
? await getFileLastUpdate(filePath)
: {
author: 'Author',
timestamp: 1539502055,
};
const {author, timestamp} = fileLastUpdateData ?? {};
return {
lastUpdatedBy: showLastUpdateAuthor
? lastUpdateFrontMatter?.author ?? author
: undefined,
lastUpdatedAt: showLastUpdateTime
? frontMatterTimestamp ?? timestamp
: undefined,
};
}
return {};
}
export async function readDocFile(
versionMetadata: Pick<
VersionMetadata,
@ -92,6 +142,7 @@ async function doProcessDocMetadata({
const {source, content, contentPath, filePath} = docFile;
const {
siteDir,
i18n,
siteConfig: {
markdown: {parseFrontMatter},
},
@ -206,6 +257,21 @@ async function doProcessDocMetadata({
const draft = isDraft({env, frontMatter});
const unlisted = isUnlisted({env, frontMatter});
const formatDate = (locale: string, date: Date, calendar: string): string => {
try {
return new Intl.DateTimeFormat(locale, {
day: 'numeric',
month: 'short',
year: 'numeric',
timeZone: 'UTC',
calendar,
}).format(date);
} catch (err) {
logger.error`Can't format docs lastUpdatedAt date "${String(date)}"`;
throw err;
}
};
// Assign all of object properties during instantiation (if possible) for
// NodeJS optimization.
// Adding properties to object after instantiation will cause hidden
@ -225,6 +291,13 @@ async function doProcessDocMetadata({
version: versionMetadata.versionName,
lastUpdatedBy: lastUpdate.lastUpdatedBy,
lastUpdatedAt: lastUpdate.lastUpdatedAt,
formattedLastUpdatedAt: lastUpdate.lastUpdatedAt
? formatDate(
i18n.currentLocale,
new Date(lastUpdate.lastUpdatedAt * 1000),
i18n.localeConfigs[i18n.currentLocale]!.calendar,
)
: undefined,
sidebarPosition,
frontMatter,
};

View File

@ -4,6 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {
JoiFrontMatter as Joi, // Custom instance for front matter
URISchema,
@ -11,15 +12,17 @@ import {
FrontMatterTOCHeadingLevels,
validateFrontMatter,
ContentVisibilitySchema,
FrontMatterLastUpdateSchema,
} from '@docusaurus/utils-validation';
import type {DocFrontMatter} from '@docusaurus/plugin-content-docs';
const FrontMatterLastUpdateErrorMessage =
'{{#label}} does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).';
// NOTE: we don't add any default value on purpose here
// We don't want default values to magically appear in doc metadata and props
// While the user did not provide those values explicitly
// We use default values in code instead
export const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
id: Joi.string(),
// See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398
title: Joi.string().allow(''),
@ -42,7 +45,15 @@ export const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
pagination_next: Joi.string().allow(null),
pagination_prev: Joi.string().allow(null),
...FrontMatterTOCHeadingLevels,
last_update: FrontMatterLastUpdateSchema,
last_update: Joi.object({
author: Joi.string(),
date: Joi.date().raw(),
})
.or('author', 'date')
.messages({
'object.missing': FrontMatterLastUpdateErrorMessage,
'object.base': FrontMatterLastUpdateErrorMessage,
}),
})
.unknown()
.concat(ContentVisibilitySchema);

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import logger from '@docusaurus/logger';
import {
getFileCommitDate,
FileNotTrackedError,
GitNotFoundError,
} from '@docusaurus/utils';
let showedGitRequirementError = false;
let showedFileNotTrackedError = false;
export async function getFileLastUpdate(
filePath: string,
): Promise<{timestamp: number; author: string} | null> {
if (!filePath) {
return null;
}
// Wrap in try/catch in case the shell commands fail
// (e.g. project doesn't use Git, etc).
try {
const result = getFileCommitDate(filePath, {
age: 'newest',
includeAuthor: true,
});
return {timestamp: result.timestamp, author: result.author};
} catch (err) {
if (err instanceof GitNotFoundError) {
if (!showedGitRequirementError) {
logger.warn('Sorry, the docs plugin last update options require Git.');
showedGitRequirementError = true;
}
} else if (err instanceof FileNotTrackedError) {
if (!showedFileNotTrackedError) {
logger.warn(
'Cannot infer the update date for some files, as they are not tracked by git.',
);
showedFileNotTrackedError = true;
}
} else {
logger.warn(err);
}
return null;
}
}

View File

@ -16,8 +16,6 @@ declare module '@docusaurus/plugin-content-docs' {
TagsListItem,
TagModule,
Tag,
FrontMatterLastUpdate,
LastUpdateData,
} from '@docusaurus/utils';
import type {Plugin, LoadContext} from '@docusaurus/types';
import type {Overwrite, Required} from 'utility-types';
@ -26,6 +24,14 @@ declare module '@docusaurus/plugin-content-docs' {
image?: string;
};
export type FileChange = {
author?: string;
/** Date can be any
* [parsable date string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse).
*/
date?: Date | string;
};
/**
* Custom callback for parsing number prefixes from file/folder names.
*/
@ -87,9 +93,9 @@ declare module '@docusaurus/plugin-content-docs' {
*/
editLocalizedFiles: boolean;
/** Whether to display the last date the doc was updated. */
showLastUpdateTime: boolean;
showLastUpdateTime?: boolean;
/** Whether to display the author who last updated the doc. */
showLastUpdateAuthor: boolean;
showLastUpdateAuthor?: boolean;
/**
* Custom parsing logic to extract number prefixes from file names. Use
* `false` to disable this behavior and leave the docs untouched, and `true`
@ -395,7 +401,16 @@ declare module '@docusaurus/plugin-content-docs' {
/** Should this doc be accessible but hidden in production builds? */
unlisted?: boolean;
/** Allows overriding the last updated author and/or date. */
last_update?: FrontMatterLastUpdate;
last_update?: FileChange;
};
export type LastUpdateData = {
/** A timestamp in **seconds**, directly acquired from `git log`. */
lastUpdatedAt?: number;
/** `lastUpdatedAt` formatted as a date according to the current locale. */
formattedLastUpdatedAt?: string;
/** The author's name directly acquired from `git log`. */
lastUpdatedBy?: string;
};
export type DocMetadataBase = LastUpdateData & {

View File

@ -7,38 +7,21 @@
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {
docuHash,
createSlugger,
normalizeUrl,
aliasedSitePathToRelativePath,
} from '@docusaurus/utils';
import {docuHash, createSlugger, normalizeUrl} from '@docusaurus/utils';
import {
toTagDocListProp,
toTagsListTagsProp,
toVersionMetadataProp,
} from './props';
import {getVersionTags} from './tags';
import type {
PluginContentLoadedActions,
RouteConfig,
RouteMetadata,
} from '@docusaurus/types';
import type {PluginContentLoadedActions, RouteConfig} from '@docusaurus/types';
import type {FullVersion, VersionTag} from './types';
import type {
CategoryGeneratedIndexMetadata,
DocMetadata,
PluginOptions,
PropTagsListPage,
} from '@docusaurus/plugin-content-docs';
function createDocRouteMetadata(docMeta: DocMetadata): RouteMetadata {
return {
sourceFilePath: aliasedSitePathToRelativePath(docMeta.source),
lastUpdatedAt: docMeta.lastUpdatedAt,
};
}
async function buildVersionCategoryGeneratedIndexRoutes({
version,
actions,
@ -85,27 +68,26 @@ async function buildVersionDocRoutes({
options,
}: BuildVersionRoutesParam): Promise<RouteConfig[]> {
return Promise.all(
version.docs.map(async (doc) => {
version.docs.map(async (metadataItem) => {
await actions.createData(
// Note that this created data path must be in sync with
// metadataPath provided to mdx-loader.
`${docuHash(doc.source)}.json`,
JSON.stringify(doc, null, 2),
`${docuHash(metadataItem.source)}.json`,
JSON.stringify(metadataItem, null, 2),
);
const docRoute: RouteConfig = {
path: doc.permalink,
path: metadataItem.permalink,
component: options.docItemComponent,
exact: true,
modules: {
content: doc.source,
content: metadataItem.source,
},
metadata: createDocRouteMetadata(doc),
// Because the parent (DocRoot) comp need to access it easily
// This permits to render the sidebar once without unmount/remount when
// navigating (and preserve sidebar state)
...(doc.sidebar && {
sidebar: doc.sidebar,
...(metadataItem.sidebar && {
sidebar: metadataItem.sidebar,
}),
};

View File

@ -8,7 +8,7 @@
import path from 'path';
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {addTrailingSlash} from '@docusaurus/utils-common';
import {addTrailingSlash} from '@docusaurus/utils';
import {createDocsByIdIndex, toCategoryIndexMatcherParam} from '../docs';
import type {
SidebarItemDoc,

View File

@ -5,8 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
import {isValidPathname, resolvePathname} from '@docusaurus/utils';
import {addLeadingSlash, addTrailingSlash} from '@docusaurus/utils-common';
import {
addLeadingSlash,
addTrailingSlash,
isValidPathname,
resolvePathname,
} from '@docusaurus/utils';
import {
DefaultNumberPrefixParser,
stripPathNumberPrefixes,

View File

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

View File

@ -6,7 +6,7 @@
*/
import path from 'path';
import {loadContext} from '@docusaurus/core/src/server/site';
import {loadContext} from '@docusaurus/core/lib/server';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import pluginContentPages from '../index';

View File

@ -11,7 +11,6 @@ import {
encodePath,
fileToPath,
aliasedSitePath,
aliasedSitePathToRelativePath,
docuHash,
getPluginI18nPath,
getFolderContainingFile,
@ -25,7 +24,8 @@ import {
isDraft,
} from '@docusaurus/utils';
import {validatePageFrontMatter} from './frontMatter';
import type {LoadContext, Plugin, RouteMetadata} from '@docusaurus/types';
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {PagesContentPaths} from './types';
import type {
PluginOptions,
@ -159,20 +159,9 @@ export default function pluginContentPages(
const {addRoute, createData} = actions;
function createPageRouteMetadata(metadata: Metadata): RouteMetadata {
return {
sourceFilePath: aliasedSitePathToRelativePath(metadata.source),
// TODO add support for last updated date in the page plugin
// at least for Markdown files
// lastUpdatedAt: metadata.lastUpdatedAt,
lastUpdatedAt: undefined,
};
}
await Promise.all(
content.map(async (metadata) => {
const {permalink, source} = metadata;
const routeMetadata = createPageRouteMetadata(metadata);
if (metadata.type === 'mdx') {
await createData(
// Note that this created data path must be in sync with
@ -184,7 +173,6 @@ export default function pluginContentPages(
path: permalink,
component: options.mdxPageComponent,
exact: true,
metadata: routeMetadata,
modules: {
content: source,
},
@ -194,7 +182,6 @@ export default function pluginContentPages(
path: permalink,
component: source,
exact: true,
metadata: routeMetadata,
modules: {
config: `@generated/docusaurus.config`,
},

View File

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

View File

@ -30,7 +30,7 @@ export default function pluginDebug({
return '../src/theme';
},
async allContentLoaded({actions: {createData, addRoute}, allContent}) {
async contentLoaded({actions: {createData, addRoute}, allContent}) {
const allContentPath = await createData(
// Note that this created data path must be in sync with
// metadataPath provided to mdx-loader.

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-pwa",
"version": "3.2.0",
"version": "3.0.0",
"description": "Docusaurus Plugin to add PWA support.",
"main": "lib/index.js",
"types": "src/plugin-pwa.d.ts",
@ -22,12 +22,12 @@
"dependencies": {
"@babel/core": "^7.23.3",
"@babel/preset-env": "^7.23.3",
"@docusaurus/core": "3.2.0",
"@docusaurus/theme-common": "3.2.0",
"@docusaurus/theme-translations": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"@docusaurus/core": "3.0.0",
"@docusaurus/theme-common": "3.0.0",
"@docusaurus/theme-translations": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"babel-loader": "^9.1.3",
"clsx": "^2.0.0",
"core-js": "^3.31.1",
@ -41,7 +41,7 @@
"workbox-window": "^7.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/module-type-aliases": "3.0.0",
"fs-extra": "^11.1.0"
},
"peerDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-sitemap",
"version": "3.2.0",
"version": "3.0.0",
"description": "Simple sitemap generation plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,19 +18,16 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.2.0",
"@docusaurus/logger": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-common": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"fs-extra": "^11.1.1",
"sitemap": "^7.1.1",
"tslib": "^2.6.0"
},
"devDependencies": {
"@total-typescript/shoehorn": "^0.1.2"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"

View File

@ -6,91 +6,95 @@
*/
import React from 'react';
import {fromPartial} from '@total-typescript/shoehorn';
import {EnumChangefreq} from 'sitemap';
import createSitemap from '../createSitemap';
import type {PluginOptions} from '../options';
import type {DocusaurusConfig, RouteConfig} from '@docusaurus/types';
const siteConfig: DocusaurusConfig = fromPartial({
url: 'https://example.com',
});
const options: PluginOptions = {
changefreq: 'daily',
priority: 0.7,
ignorePatterns: [],
filename: 'sitemap.xml',
lastmod: 'datetime',
};
const route = (routePath: string, routePaths?: string[]): RouteConfig => {
return fromPartial({
path: routePath,
routes: routePaths?.map((p) => route(p)),
});
};
const routes = (routePaths: string[]): RouteConfig[] => {
return routePaths.map((p) => route(p));
};
import type {DocusaurusConfig} from '@docusaurus/types';
describe('createSitemap', () => {
it('simple site', async () => {
const sitemap = await createSitemap({
siteConfig,
routes: routes(['/', '/test']),
head: {},
options,
});
const sitemap = await createSitemap(
{
url: 'https://example.com',
} as DocusaurusConfig,
['/', '/test'],
{},
{
changefreq: EnumChangefreq.DAILY,
priority: 0.7,
ignorePatterns: [],
filename: 'sitemap.xml',
},
);
expect(sitemap).toContain(
`<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">`,
);
});
it('site with no routes', async () => {
const sitemap = await createSitemap({
siteConfig,
routes: routes([]),
head: {},
options,
});
expect(sitemap).toBeNull();
it('empty site', () =>
expect(async () => {
// @ts-expect-error: test
await createSitemap({}, [], {}, {} as PluginOptions);
}).rejects.toThrow(
'URL in docusaurus.config.js cannot be empty/undefined.',
));
it('exclusion of 404 page', async () => {
const sitemap = await createSitemap(
{
url: 'https://example.com',
} as DocusaurusConfig,
['/', '/404.html', '/my-page'],
{},
{
changefreq: EnumChangefreq.DAILY,
priority: 0.7,
ignorePatterns: [],
filename: 'sitemap.xml',
},
);
expect(sitemap).not.toContain('404');
});
it('excludes patterns configured to be ignored', async () => {
const sitemap = await createSitemap({
siteConfig,
routes: routes([
'/',
'/search/',
'/tags/',
'/search/foo',
'/tags/foo/bar',
]),
head: {},
options: {
...options,
const sitemap = await createSitemap(
{
url: 'https://example.com',
} as DocusaurusConfig,
['/', '/search/', '/tags/', '/search/foo', '/tags/foo/bar'],
{},
{
changefreq: EnumChangefreq.DAILY,
priority: 0.7,
ignorePatterns: [
// Shallow ignore
'/search/',
// Deep ignore
'/tags/**',
],
filename: 'sitemap.xml',
},
});
);
expect(sitemap).not.toContain('/search/</loc>');
expect(sitemap).toContain('/search/foo');
expect(sitemap).not.toContain('/tags');
});
it('keep trailing slash unchanged', async () => {
const sitemap = await createSitemap({
siteConfig,
routes: routes(['/', '/test', '/nested/test', '/nested/test2/']),
head: {},
options,
});
const sitemap = await createSitemap(
{
url: 'https://example.com',
trailingSlash: undefined,
} as DocusaurusConfig,
['/', '/test', '/nested/test', '/nested/test2/'],
{},
{
changefreq: EnumChangefreq.DAILY,
priority: 0.7,
ignorePatterns: [],
filename: 'sitemap.xml',
},
);
expect(sitemap).toContain('<loc>https://example.com/</loc>');
expect(sitemap).toContain('<loc>https://example.com/test</loc>');
@ -99,12 +103,20 @@ describe('createSitemap', () => {
});
it('add trailing slash', async () => {
const sitemap = await createSitemap({
siteConfig: {...siteConfig, trailingSlash: true},
routes: routes(['/', '/test', '/nested/test', '/nested/test2/']),
head: {},
options,
});
const sitemap = await createSitemap(
{
url: 'https://example.com',
trailingSlash: true,
} as DocusaurusConfig,
['/', '/test', '/nested/test', '/nested/test2/'],
{},
{
changefreq: EnumChangefreq.DAILY,
priority: 0.7,
ignorePatterns: [],
filename: 'sitemap.xml',
},
);
expect(sitemap).toContain('<loc>https://example.com/</loc>');
expect(sitemap).toContain('<loc>https://example.com/test/</loc>');
@ -113,16 +125,20 @@ describe('createSitemap', () => {
});
it('remove trailing slash', async () => {
const sitemap = await createSitemap({
siteConfig: {
...siteConfig,
const sitemap = await createSitemap(
{
url: 'https://example.com',
trailingSlash: false,
} as DocusaurusConfig,
['/', '/test', '/nested/test', '/nested/test2/'],
{},
{
changefreq: EnumChangefreq.DAILY,
priority: 0.7,
ignorePatterns: [],
filename: 'sitemap.xml',
},
routes: routes(['/', '/test', '/nested/test', '/nested/test2/']),
head: {},
options,
});
);
expect(sitemap).toContain('<loc>https://example.com/</loc>');
expect(sitemap).toContain('<loc>https://example.com/test</loc>');
@ -131,11 +147,13 @@ describe('createSitemap', () => {
});
it('filters pages with noindex', async () => {
const sitemap = await createSitemap({
siteConfig,
routesPaths: ['/', '/noindex', '/nested/test', '/nested/test2/'],
routes: routes(['/', '/noindex', '/nested/test', '/nested/test2/']),
head: {
const sitemap = await createSitemap(
{
url: 'https://example.com',
trailingSlash: false,
} as DocusaurusConfig,
['/', '/noindex', '/nested/test', '/nested/test2/'],
{
'/noindex': {
meta: {
// @ts-expect-error: bad lib def
@ -148,18 +166,24 @@ describe('createSitemap', () => {
},
},
},
options,
});
{
changefreq: EnumChangefreq.DAILY,
priority: 0.7,
ignorePatterns: [],
},
);
expect(sitemap).not.toContain('/noindex');
});
it('does not generate anything for all pages with noindex', async () => {
const sitemap = await createSitemap({
siteConfig,
routesPaths: ['/', '/noindex'],
routes: routes(['/', '/noindex']),
head: {
const sitemap = await createSitemap(
{
url: 'https://example.com',
trailingSlash: false,
} as DocusaurusConfig,
['/', '/noindex'],
{
'/': {
meta: {
// @ts-expect-error: bad lib def
@ -177,8 +201,12 @@ describe('createSitemap', () => {
},
},
},
options,
});
{
changefreq: EnumChangefreq.DAILY,
priority: 0.7,
ignorePatterns: [],
},
);
expect(sitemap).toBeNull();
});

View File

@ -1,229 +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 {fromPartial} from '@total-typescript/shoehorn';
import {createSitemapItem} from '../createSitemapItem';
import {DEFAULT_OPTIONS} from '../options';
import type {PluginOptions} from '../options';
import type {DocusaurusConfig, RouteConfig} from '@docusaurus/types';
const siteConfig: DocusaurusConfig = fromPartial({
url: 'https://example.com',
});
function test(params: {
route: Partial<RouteConfig>;
siteConfig?: Partial<DocusaurusConfig>;
options?: Partial<PluginOptions>;
}) {
return createSitemapItem({
route: params.route as unknown as RouteConfig,
siteConfig: {...siteConfig, ...params.siteConfig},
options: {...DEFAULT_OPTIONS, ...params.options},
});
}
function testRoute(route: Partial<RouteConfig>) {
return test({
route,
});
}
describe('createSitemapItem', () => {
it('simple item', async () => {
await expect(testRoute({path: '/routePath'})).resolves
.toMatchInlineSnapshot(`
{
"changefreq": "weekly",
"lastmod": null,
"priority": 0.5,
"url": "https://example.com/routePath",
}
`);
});
describe('lastmod', () => {
const date = new Date('2024/01/01');
describe('read from route metadata', () => {
const route = {
path: '/routePath',
metadata: {lastUpdatedAt: date.getTime()},
};
it('lastmod default option', async () => {
await expect(
test({
route,
}),
).resolves.toMatchInlineSnapshot(`
{
"changefreq": "weekly",
"lastmod": null,
"priority": 0.5,
"url": "https://example.com/routePath",
}
`);
});
it('lastmod date option', async () => {
await expect(
test({
route,
options: {
lastmod: 'date',
},
}),
).resolves.toMatchInlineSnapshot(`
{
"changefreq": "weekly",
"lastmod": "2024-01-01",
"priority": 0.5,
"url": "https://example.com/routePath",
}
`);
});
it('lastmod datetime option', async () => {
await expect(
test({
route,
options: {
lastmod: 'datetime',
},
}),
).resolves.toMatchInlineSnapshot(`
{
"changefreq": "weekly",
"lastmod": "2024-01-01T00:00:00.000Z",
"priority": 0.5,
"url": "https://example.com/routePath",
}
`);
});
});
describe('read from git', () => {
const route = {
path: '/routePath',
metadata: {sourceFilePath: 'route/file.md'},
};
it('lastmod default option', async () => {
await expect(
test({
route,
}),
).resolves.toMatchInlineSnapshot(`
{
"changefreq": "weekly",
"lastmod": null,
"priority": 0.5,
"url": "https://example.com/routePath",
}
`);
});
it('lastmod date option', async () => {
await expect(
test({
route,
options: {
lastmod: 'date',
},
}),
).resolves.toMatchInlineSnapshot(`
{
"changefreq": "weekly",
"lastmod": "2018-10-14",
"priority": 0.5,
"url": "https://example.com/routePath",
}
`);
});
it('lastmod datetime option', async () => {
await expect(
test({
route,
options: {
lastmod: 'datetime',
},
}),
).resolves.toMatchInlineSnapshot(`
{
"changefreq": "weekly",
"lastmod": "2018-10-14T07:27:35.000Z",
"priority": 0.5,
"url": "https://example.com/routePath",
}
`);
});
});
describe('read from both - route metadata takes precedence', () => {
const route = {
path: '/routePath',
metadata: {
sourceFilePath: 'route/file.md',
lastUpdatedAt: date.getTime(),
},
};
it('lastmod default option', async () => {
await expect(
test({
route,
}),
).resolves.toMatchInlineSnapshot(`
{
"changefreq": "weekly",
"lastmod": null,
"priority": 0.5,
"url": "https://example.com/routePath",
}
`);
});
it('lastmod date option', async () => {
await expect(
test({
route,
options: {
lastmod: 'date',
},
}),
).resolves.toMatchInlineSnapshot(`
{
"changefreq": "weekly",
"lastmod": "2024-01-01",
"priority": 0.5,
"url": "https://example.com/routePath",
}
`);
});
it('lastmod datetime option', async () => {
await expect(
test({
route,
options: {
lastmod: 'datetime',
},
}),
).resolves.toMatchInlineSnapshot(`
{
"changefreq": "weekly",
"lastmod": "2024-01-01T00:00:00.000Z",
"priority": 0.5,
"url": "https://example.com/routePath",
}
`);
});
});
});
});

View File

@ -12,6 +12,7 @@ import {
type Options,
type PluginOptions,
} from '../options';
import type {EnumChangefreq} from 'sitemap';
import type {Validate} from '@docusaurus/types';
function testValidate(options: Options) {
@ -33,10 +34,9 @@ describe('validateOptions', () => {
it('accepts correctly defined user options', () => {
const userOptions: Options = {
changefreq: 'yearly',
changefreq: 'yearly' as EnumChangefreq,
priority: 0.9,
ignorePatterns: ['/search/**'],
lastmod: 'datetime',
};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
@ -44,209 +44,32 @@ describe('validateOptions', () => {
});
});
describe('lastmod', () => {
it('accepts lastmod undefined', () => {
const userOptions: Options = {
lastmod: undefined,
};
expect(testValidate(userOptions)).toEqual(defaultOptions);
});
it('accepts lastmod null', () => {
const userOptions: Options = {
lastmod: null,
};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
});
});
it('accepts lastmod datetime', () => {
const userOptions: Options = {
lastmod: 'datetime',
};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
});
});
it('rejects lastmod bad input', () => {
const userOptions: Options = {
// @ts-expect-error: bad value on purpose
lastmod: 'dateTimeZone',
};
expect(() =>
testValidate(userOptions),
).toThrowErrorMatchingInlineSnapshot(
`""lastmod" must be one of [null, date, datetime]"`,
);
});
it('rejects out-of-range priority inputs', () => {
expect(() =>
testValidate({priority: 2}),
).toThrowErrorMatchingInlineSnapshot(
`""priority" must be less than or equal to 1"`,
);
});
describe('priority', () => {
it('accepts priority undefined', () => {
const userOptions: Options = {
priority: undefined,
};
expect(testValidate(userOptions)).toEqual(defaultOptions);
});
it('accepts priority null', () => {
const userOptions: Options = {
priority: null,
};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
});
});
it('accepts priority 0', () => {
const userOptions: Options = {
priority: 0,
};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
});
});
it('accepts priority 0.4', () => {
const userOptions: Options = {
priority: 0.4,
};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
});
});
it('accepts priority 1', () => {
const userOptions: Options = {
priority: 1,
};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
});
});
it('rejects priority > 1', () => {
const userOptions: Options = {
priority: 2,
};
expect(() =>
testValidate(userOptions),
).toThrowErrorMatchingInlineSnapshot(
`""priority" must be less than or equal to 1"`,
);
});
it('rejects priority < 0', () => {
const userOptions: Options = {
priority: -3,
};
expect(() =>
testValidate(userOptions),
).toThrowErrorMatchingInlineSnapshot(
`""priority" must be greater than or equal to 0"`,
);
});
it('rejects bad changefreq inputs', () => {
expect(() =>
testValidate({changefreq: 'annually' as EnumChangefreq}),
).toThrowErrorMatchingInlineSnapshot(
`""changefreq" must be one of [daily, monthly, always, hourly, weekly, yearly, never]"`,
);
});
describe('changefreq', () => {
it('accepts changefreq undefined', () => {
const userOptions: Options = {
changefreq: undefined,
};
expect(testValidate(userOptions)).toEqual(defaultOptions);
});
it('accepts changefreq null', () => {
const userOptions: Options = {
changefreq: null,
};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
});
});
it('accepts changefreq always', () => {
const userOptions: Options = {
changefreq: 'always',
};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
});
});
it('rejects changefreq bad inputs', () => {
const userOptions: Options = {
// @ts-expect-error: bad value on purpose
changefreq: 'annually',
};
expect(() =>
testValidate(userOptions),
).toThrowErrorMatchingInlineSnapshot(
`""changefreq" must be one of [null, hourly, daily, weekly, monthly, yearly, always, never]"`,
);
});
});
describe('ignorePatterns', () => {
it('accept ignorePatterns undefined', () => {
const userOptions: Options = {
ignorePatterns: undefined,
};
expect(testValidate(userOptions)).toEqual(defaultOptions);
});
it('accept ignorePatterns empty', () => {
const userOptions: Options = {
ignorePatterns: [],
};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
});
});
it('accept ignorePatterns valid', () => {
const userOptions: Options = {
ignorePatterns: ['/tags/**'],
};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
});
});
it('rejects ignorePatterns bad input array', () => {
const userOptions: Options = {
// @ts-expect-error: test
ignorePatterns: '/search',
};
expect(() =>
testValidate(userOptions),
).toThrowErrorMatchingInlineSnapshot(
`""ignorePatterns" must be an array"`,
);
});
it('rejects ignorePatterns bad input item string', () => {
const userOptions: Options = {
// @ts-expect-error: test
ignorePatterns: [/^\/search/],
};
expect(() =>
testValidate(userOptions),
).toThrowErrorMatchingInlineSnapshot(
`""ignorePatterns[0]" must be a string"`,
);
});
it('rejects bad ignorePatterns inputs', () => {
expect(() =>
// @ts-expect-error: test
testValidate({ignorePatterns: '/search'}),
).toThrowErrorMatchingInlineSnapshot(`""ignorePatterns" must be an array"`);
expect(() =>
// @ts-expect-error: test
testValidate({ignorePatterns: [/^\/search/]}),
).toThrowErrorMatchingInlineSnapshot(
`""ignorePatterns[0]" must be a string"`,
);
});
});

View File

@ -1,67 +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 {sitemapItemsToXmlString} from '../xml';
import type {SitemapItem} from '../types';
const options = {lastmod: 'datetime'} as const;
describe('createSitemap', () => {
it('no items', async () => {
const items: SitemapItem[] = [];
await expect(
sitemapItemsToXmlString(items, options),
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Can't generate a sitemap with no items"`,
);
});
it('simple item', async () => {
const items: SitemapItem[] = [{url: 'https://docusaurus.io/docs/doc1'}];
await expect(
sitemapItemsToXmlString(items, options),
).resolves.toMatchInlineSnapshot(
`"<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>https://docusaurus.io/docs/doc1</loc></url></urlset>"`,
);
});
it('complex item', async () => {
const items: SitemapItem[] = [
{
url: 'https://docusaurus.io/docs/doc1',
changefreq: 'always',
priority: 1,
lastmod: new Date('01/01/2024').toISOString(),
},
];
await expect(
sitemapItemsToXmlString(items, options),
).resolves.toMatchInlineSnapshot(
`"<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>https://docusaurus.io/docs/doc1</loc><lastmod>2024-01-01T00:00:00.000Z</lastmod><changefreq>always</changefreq><priority>1.0</priority></url></urlset>"`,
);
});
it('date only lastmod', async () => {
const items: SitemapItem[] = [
{
url: 'https://docusaurus.io/docs/doc1',
changefreq: 'always',
priority: 1,
lastmod: new Date('01/01/2024').toISOString(),
},
];
await expect(
sitemapItemsToXmlString(items, {lastmod: 'date'}),
).resolves.toMatchInlineSnapshot(
`"<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>https://docusaurus.io/docs/doc1</loc><lastmod>2024-01-01</lastmod><changefreq>always</changefreq><priority>1.0</priority></url></urlset>"`,
);
});
});

View File

@ -6,23 +6,13 @@
*/
import type {ReactElement} from 'react';
import {createMatcher, flattenRoutes} from '@docusaurus/utils';
import {sitemapItemsToXmlString} from './xml';
import {createSitemapItem} from './createSitemapItem';
import type {SitemapItem} from './types';
import type {DocusaurusConfig, RouteConfig} from '@docusaurus/types';
import {SitemapStream, streamToPromise} from 'sitemap';
import {applyTrailingSlash} from '@docusaurus/utils-common';
import {createMatcher} from '@docusaurus/utils';
import type {DocusaurusConfig} from '@docusaurus/types';
import type {HelmetServerState} from 'react-helmet-async';
import type {PluginOptions} from './options';
type CreateSitemapParams = {
siteConfig: DocusaurusConfig;
routes: RouteConfig[];
head: {[location: string]: HelmetServerState};
options: PluginOptions;
};
// Maybe we want to add a routeConfig.metadata.noIndex instead?
// But using Helmet is more reliable for third-party plugins...
function isNoIndexMetaRoute({
head,
route,
@ -57,51 +47,50 @@ function isNoIndexMetaRoute({
);
}
// Not all routes should appear in the sitemap, and we should filter:
// - parent routes, used for layouts
// - routes matching options.ignorePatterns
// - routes with no index metadata
function getSitemapRoutes({routes, head, options}: CreateSitemapParams) {
const {ignorePatterns} = options;
export default async function createSitemap(
siteConfig: DocusaurusConfig,
routesPaths: string[],
head: {[location: string]: HelmetServerState},
options: PluginOptions,
): Promise<string | null> {
const {url: hostname} = siteConfig;
if (!hostname) {
throw new Error('URL in docusaurus.config.js cannot be empty/undefined.');
}
const {changefreq, priority, ignorePatterns} = options;
const ignoreMatcher = createMatcher(ignorePatterns);
function isRouteExcluded(route: RouteConfig) {
function isRouteExcluded(route: string) {
return (
ignoreMatcher(route.path) || isNoIndexMetaRoute({head, route: route.path})
route.endsWith('404.html') ||
ignoreMatcher(route) ||
isNoIndexMetaRoute({head, route})
);
}
return flattenRoutes(routes).filter((route) => !isRouteExcluded(route));
}
const includedRoutes = routesPaths.filter((route) => !isRouteExcluded(route));
async function createSitemapItems(
params: CreateSitemapParams,
): Promise<SitemapItem[]> {
const sitemapRoutes = getSitemapRoutes(params);
if (sitemapRoutes.length === 0) {
return [];
}
return Promise.all(
sitemapRoutes.map((route) =>
createSitemapItem({
route,
siteConfig: params.siteConfig,
options: params.options,
}),
),
);
}
export default async function createSitemap(
params: CreateSitemapParams,
): Promise<string | null> {
const items = await createSitemapItems(params);
if (items.length === 0) {
if (includedRoutes.length === 0) {
return null;
}
const xmlString = await sitemapItemsToXmlString(items, {
lastmod: params.options.lastmod,
});
return xmlString;
const sitemapStream = new SitemapStream({hostname});
includedRoutes.forEach((routePath) =>
sitemapStream.write({
url: applyTrailingSlash(routePath, {
trailingSlash: siteConfig.trailingSlash,
baseUrl: siteConfig.baseUrl,
}),
changefreq,
priority,
}),
);
sitemapStream.end();
const generatedSitemap = (await streamToPromise(sitemapStream)).toString();
return generatedSitemap;
}

View File

@ -1,76 +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 {applyTrailingSlash} from '@docusaurus/utils-common';
import {getLastUpdate, normalizeUrl} from '@docusaurus/utils';
import type {LastModOption, SitemapItem} from './types';
import type {DocusaurusConfig, RouteConfig} from '@docusaurus/types';
import type {PluginOptions} from './options';
async function getRouteLastUpdatedAt(
route: RouteConfig,
): Promise<number | undefined> {
if (route.metadata?.lastUpdatedAt) {
return route.metadata?.lastUpdatedAt;
}
if (route.metadata?.sourceFilePath) {
const lastUpdate = await getLastUpdate(route.metadata?.sourceFilePath);
return lastUpdate?.lastUpdatedAt;
}
return undefined;
}
type LastModFormatter = (timestamp: number) => string;
const LastmodFormatters: Record<LastModOption, LastModFormatter> = {
date: (timestamp) => new Date(timestamp).toISOString().split('T')[0]!,
datetime: (timestamp) => new Date(timestamp).toISOString(),
};
function formatLastmod(timestamp: number, lastmodOption: LastModOption) {
const format = LastmodFormatters[lastmodOption];
return format(timestamp);
}
async function getRouteLastmod({
route,
lastmod,
}: {
route: RouteConfig;
lastmod: LastModOption | null;
}): Promise<string | null> {
if (lastmod === null) {
return null;
}
const lastUpdatedAt = (await getRouteLastUpdatedAt(route)) ?? null;
return lastUpdatedAt ? formatLastmod(lastUpdatedAt, lastmod) : null;
}
export async function createSitemapItem({
route,
siteConfig,
options,
}: {
route: RouteConfig;
siteConfig: DocusaurusConfig;
options: PluginOptions;
}): Promise<SitemapItem> {
const {changefreq, priority, lastmod} = options;
return {
url: normalizeUrl([
siteConfig.url,
applyTrailingSlash(route.path, {
trailingSlash: siteConfig.trailingSlash,
baseUrl: siteConfig.baseUrl,
}),
]),
changefreq,
priority,
lastmod: await getRouteLastmod({route, lastmod}),
};
}

View File

@ -19,17 +19,17 @@ export default function pluginSitemap(
return {
name: 'docusaurus-plugin-sitemap',
async postBuild({siteConfig, routes, outDir, head}) {
async postBuild({siteConfig, routesPaths, outDir, head}) {
if (siteConfig.noIndex) {
return;
}
// Generate sitemap.
const generatedSitemap = await createSitemap({
const generatedSitemap = await createSitemap(
siteConfig,
routes,
routesPaths,
head,
options,
});
);
if (!generatedSitemap) {
return;
}

View File

@ -6,60 +6,33 @@
*/
import {Joi} from '@docusaurus/utils-validation';
import {ChangeFreqList, LastModOptionList} from './types';
import {EnumChangefreq} from 'sitemap';
import type {OptionValidationContext} from '@docusaurus/types';
import type {ChangeFreq, LastModOption} from './types';
export type PluginOptions = {
/**
* The path to the created sitemap file, relative to the output directory.
* Useful if you have two plugin instances outputting two files.
*/
filename: string;
/** @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions */
changefreq: EnumChangefreq;
/** @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions */
priority: number;
/**
* A list of glob patterns; matching route paths will be filtered from the
* sitemap. Note that you may need to include the base URL in here.
*/
ignorePatterns: string[];
/**
* Defines the format of the "lastmod" sitemap item entry, between:
* - null: do not compute/add a "lastmod" sitemap entry
* - "date": add a "lastmod" sitemap entry without time (YYYY-MM-DD)
* - "datetime": add a "lastmod" sitemap entry with time (ISO 8601 datetime)
* @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions
* @see https://www.w3.org/TR/NOTE-datetime
* The path to the created sitemap file, relative to the output directory.
* Useful if you have two plugin instances outputting two files.
*/
lastmod: LastModOption | null;
/**
* TODO Docusaurus v4 breaking change: remove useless option
* @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions
*/
changefreq: ChangeFreq | null;
/**
* TODO Docusaurus v4 breaking change: remove useless option
* @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions
*/
priority: number | null;
filename: string;
};
export type Options = Partial<PluginOptions>;
export const DEFAULT_OPTIONS: PluginOptions = {
filename: 'sitemap.xml',
ignorePatterns: [],
// TODO Docusaurus v4 breaking change
// change default to "date" if no bug or perf issue reported
lastmod: null,
// TODO Docusaurus v4 breaking change
// those options are useless and should be removed
changefreq: 'weekly',
changefreq: EnumChangefreq.WEEKLY,
priority: 0.5,
ignorePatterns: [],
filename: 'sitemap.xml',
};
const PluginOptionSchema = Joi.object<PluginOptions>({
@ -68,28 +41,10 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
'any.unknown':
'Option `cacheTime` in sitemap config is deprecated. Please remove it.',
}),
// TODO remove for Docusaurus v4 breaking changes?
// This is not even used by Google crawlers
// See also https://github.com/facebook/docusaurus/issues/2604
changefreq: Joi.string()
.valid(null, ...ChangeFreqList)
.valid(...Object.values(EnumChangefreq))
.default(DEFAULT_OPTIONS.changefreq),
// TODO remove for Docusaurus v4 breaking changes?
// This is not even used by Google crawlers
// The priority is "relative", and using the same priority for all routes
// does not make sense according to the spec
// See also https://github.com/facebook/docusaurus/issues/2604
// See also https://www.sitemaps.org/protocol.html
priority: Joi.alternatives()
.try(Joi.valid(null), Joi.number().min(0).max(1))
.default(DEFAULT_OPTIONS.priority),
lastmod: Joi.string()
.valid(null, ...LastModOptionList)
.default(DEFAULT_OPTIONS.lastmod),
priority: Joi.number().min(0).max(1).default(DEFAULT_OPTIONS.priority),
ignorePatterns: Joi.array()
.items(Joi.string())
.default(DEFAULT_OPTIONS.ignorePatterns),

View File

@ -1,67 +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.
*/
export const LastModOptionList = ['date', 'datetime'] as const;
export type LastModOption = (typeof LastModOptionList)[number];
// types are according to the sitemap spec:
// see also https://www.sitemaps.org/protocol.html
export const ChangeFreqList = [
'hourly',
'daily',
'weekly',
'monthly',
'yearly',
'always',
'never',
] as const;
export type ChangeFreq = (typeof ChangeFreqList)[number];
// We re-recreate our own type because the "sitemap" lib types are not good
export type SitemapItem = {
/**
* URL of the page.
* This URL must begin with the protocol (such as http).
* It should eventually end with a trailing slash.
* It should be less than 2,048 characters.
*/
url: string;
/**
* ISO 8601 date string.
* See also https://www.w3.org/TR/NOTE-datetime
*
* It is recommended to use one of:
* - date.toISOString()
* - YYYY-MM-DD
*
* Note: as of 2024, Google uses this value for crawling priority.
* See also https://github.com/facebook/docusaurus/issues/2604
*/
lastmod?: string | null;
/**
* One of the specified enum values
*
* Note: as of 2024, Google ignores this value.
* See also https://github.com/facebook/docusaurus/issues/2604
*/
changefreq?: ChangeFreq | null;
/**
* The priority of this URL relative to other URLs on your site.
* Valid values range from 0.0 to 1.0.
* The default priority of a page is 0.5.
*
* Note: as of 2024, Google ignores this value.
* See also https://github.com/facebook/docusaurus/issues/2604
*/
priority?: number | null;
};

View File

@ -1,35 +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 {SitemapStream, streamToPromise} from 'sitemap';
import type {LastModOption, SitemapItem} from './types';
export async function sitemapItemsToXmlString(
items: SitemapItem[],
options: {lastmod: LastModOption | null},
): Promise<string> {
if (items.length === 0) {
// Note: technically we could, but there is a bug in the lib code
// and the code below would never resolve, so it's better to fail fast
throw new Error("Can't generate a sitemap with no items");
}
// TODO remove sitemap lib dependency?
// https://github.com/ekalinin/sitemap.js
// it looks like an outdated confusion super old lib
// we might as well achieve the same result with a pure xml lib
const sitemapStream = new SitemapStream({
// WTF is this lib reformatting the string YYYY-MM-DD to datetime...
lastmodDateOnly: options?.lastmod === 'date',
});
items.forEach((item) => sitemapStream.write(item));
sitemapStream.end();
const buffer = await streamToPromise(sitemapStream);
return buffer.toString();
}

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