Compare commits

..

40 Commits

Author SHA1 Message Date
Sébastien Lorber 26e0bd928c
docs(website): Announce v3.2 on website/homepage (#10004) 2024-03-29 19:43:45 +01:00
Sébastien Lorber 70415a4cef
chore: fix codesandbox default privacy (#9993) 2024-03-29 18:30:47 +01:00
Sébastien Lorber a34e3f8f91
chore: update examples to v3.2.0 (#10003) 2024-03-29 18:27:36 +01:00
Sébastien Lorber 7edfe0e2d1
docs: fix 3.2 blog post headings (#10002) 2024-03-29 18:06:33 +01:00
Sébastien Lorber debfc87d34
chore: release Docusaurus v3.2.0 (#10000) 2024-03-29 17:51:27 +01:00
Sébastien Lorber 1a5fe5c412
fix(mdx-loader): Ignore contentTitle coming after Markdown thematicBreak (#9999) 2024-03-29 15:07:53 +01:00
ozaki 821247142e
refactor(utils): remove duplicated function (#9972)
Co-authored-by: sebastien <lorber.sebastien@gmail.com>
2024-03-29 14:45:30 +01:00
Sébastien Lorber efbe474e9c
refactor(core): improve dev perf, fine-grained site reloads - part 3 (#9975) 2024-03-28 12:39:07 +01:00
dependabot[bot] 06e70a4f9a
chore(deps): bump actions/dependency-review-action from 4.1.3 to 4.2.4 (#9981)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-28 11:37:03 +01:00
dependabot[bot] 1430c85a82
chore(deps): bump katex from 0.16.8 to 0.16.10 (#9982)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-28 11:36:54 +01:00
dependabot[bot] 8024d9b858
chore(deps): bump express from 4.18.2 to 4.19.2 (#9983)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-28 11:36:44 +01:00
dependabot[bot] 3877c1254f
chore(deps): bump webpack-dev-middleware from 5.3.3 to 5.3.4 (#9977)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <dependabot[bot]@users.noreply.github.com>
2024-03-24 17:26:35 +01:00
Vlad Nestorov 6f17d54938
docs: use official GitHub Action to deploy to GitHub Pages (#9937)
Co-authored-by: ozakione <29860391+OzakIOne@users.noreply.github.com>
2024-03-21 16:01:10 +01:00
Sébastien Lorber 93a09ea086
refactor(core): improve dev perf, fine-grained site reloads - part2 (#9968) 2024-03-21 13:05:19 +01:00
Fafowora Sunkanmi 91f93656d8
docs: replace VuePress by VitePress on tool comparison section (#9971)
Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
2024-03-21 13:05:05 +01:00
Zed Spencer-Milnes 9a47ec0581
refactor(website): organise blog posts by year (#9965) 2024-03-20 18:08:05 +01:00
Sébastien Lorber 9017fb9b1d
feat(sitemap): add support for "lastmod" (#9954) 2024-03-20 11:47:44 +01:00
Sébastien Lorber 465cf4d82c
refactor(docs,blog): last update timestamp should be in milliseconds instead of seconds (#9963) 2024-03-19 09:51:27 +01:00
dependabot[bot] 6c724ed857
chore(deps): bump follow-redirects from 1.15.4 to 1.15.6 (#9958)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-18 16:28:58 +01:00
ozaki c745021b01
feat(blog): add LastUpdateAuthor & LastUpdateTime (#9912)
Co-authored-by: OzakIOne <OzakIOne@users.noreply.github.com>
Co-authored-by: sebastien <lorber.sebastien@gmail.com>
2024-03-15 12:50:06 +01:00
Max Schmitt 7938803747
fix(a11y): move focus algolia-search focus back to search input on Escape (#9945) 2024-03-14 17:26:26 +01:00
ozaki 14bec09670
fix(blog): apply trailing slash to blog feed (#9920)
Co-authored-by: sebastien <lorber.sebastien@gmail.com>
2024-03-14 16:45:00 +01:00
Max Schmitt 032e3b8f4d
fix(theme): improve a11y of DocSidebarItemCategory expand/collapsed button (#9944) 2024-03-14 14:59:36 +01:00
ozaki 6151a41428
docs(website): update wrong translate id (#9946) 2024-03-14 12:54:41 +01:00
Sébastien Lorber 8d115a9e0d
feat(core): add new plugin allContentLoaded lifecycle (#9931) 2024-03-08 19:13:59 +01:00
Sébastien Lorber d02b96f7f5
refactor(core): improve dev perf, fine-grained site reloads - part1 (#9903) 2024-03-08 12:59:11 +01:00
ozaki eb6424cc27
docs(showcase): update link (#9929) 2024-03-07 23:47:28 +01:00
Hallbjörn Magnússon ee53208f9d
feat(theme-translations) Icelandic (is) (#9928)
Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
2024-03-07 19:18:40 +01:00
suenryu 9d89607d4d
fix(theme-translations): complete and modify Japanese translations (#9915) 2024-03-07 18:59:09 +01:00
ozaki 319d73d54f
docs(team): update active docusaurus team member (#9893)
Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
Co-authored-by: OzakIOne <OzakIOne@users.noreply.github.com>
2024-03-06 11:17:28 +01:00
suenryu 2b6d8bd646
fix(theme-translations): add Japanese translations (#9910) 2024-03-06 00:00:57 +01:00
ozaki 6d8108b653
docs: update legacy MDX v1 links to markdown links (#9914) 2024-03-05 23:46:52 +01:00
ozaki 2851c93d0d
feat(blog): allow processing blog posts through a processBlogPosts function (#9886)
Co-authored-by: OzakIOne <OzakIOne@users.noreply.github.com>
Co-authored-by: sebastien <lorber.sebastien@gmail.com>
Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
2024-03-05 22:58:16 +01:00
ozaki 05279dc3d8
docs: update legacy MDX v1 links to markdown links (#9913) 2024-03-05 20:04:12 +01:00
Joshua Chen 324c853da0
docs: emphasize "index slug" convention (#9906) 2024-03-04 12:33:17 +01:00
Sébastien Lorber 2152572a3b
fix(mdx-loader): mdx-code-block should support CRLF (#9897) 2024-02-29 13:20:03 +01:00
dependabot[bot] 0589b1475d
chore(deps): bump actions/dependency-review-action from 4.1.2 to 4.1.3 (#9892)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-27 10:05:12 +01:00
Sébastien Lorber f159bb2472
perf: optimize getFileCommitDate, make it async (#9890) 2024-02-24 23:32:18 +01:00
ozaki 0279c329ad
refactor(theme): dates should be formatted on the client-side instead of in nodejs code (#9868)
Co-authored-by: OzakIOne <OzakIOne@users.noreply.github.com>
Co-authored-by: sebastien <lorber.sebastien@gmail.com>
2024-02-23 18:30:05 +01:00
Sébastien Lorber 6bf21d215c
fix(core): fix default i18n calendar used, infer it from locale if possible (#9878) 2024-02-22 17:06:12 +01:00
418 changed files with 30440 additions and 5731 deletions

8
.eslintrc.js vendored
View File

@ -85,13 +85,14 @@ 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': [WARNING, {allowElseIf: true}],
'no-else-return': OFF,
'no-empty': [WARNING, {allowEmptyCatch: true}],
'no-lonely-if': WARNING,
'no-nested-ternary': WARNING,
@ -347,10 +348,7 @@ module.exports = {
ERROR,
{'ts-expect-error': 'allow-with-description'},
],
'@typescript-eslint/consistent-indexed-object-style': [
WARNING,
'index-signature',
],
'@typescript-eslint/consistent-indexed-object-style': OFF,
'@typescript-eslint/consistent-type-imports': [
WARNING,
{disallowTypeAnnotations: false},

View File

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

View File

@ -1,5 +1,136 @@
# 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

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',
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic?file=%2FREADME.md&privacy=public',
'codesandbox-ts':
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic-typescript?file=%2FREADME.md',
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic-typescript?file=%2FREADME.md&privacy=public',
// 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.0.0",
"version": "3.2.0",
"private": true,
"scripts": {
"start": "npx --package netlify-cli netlify dev"

View File

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

View File

@ -16,8 +16,8 @@
"dev": "docusaurus start"
},
"dependencies": {
"@docusaurus/core": "3.1.1",
"@docusaurus/preset-classic": "3.1.1",
"@docusaurus/core": "3.2.0",
"@docusaurus/preset-classic": "3.2.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.1.1",
"@docusaurus/tsconfig": "3.1.1",
"@docusaurus/types": "3.1.1",
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/tsconfig": "3.2.0",
"@docusaurus/types": "3.2.0",
"typescript": "~5.2.2"
},
"browserslist": {

File diff suppressed because it is too large Load Diff

View File

@ -15,8 +15,8 @@
"dev": "docusaurus start"
},
"dependencies": {
"@docusaurus/core": "3.1.1",
"@docusaurus/preset-classic": "3.1.1",
"@docusaurus/core": "3.2.0",
"@docusaurus/preset-classic": "3.2.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.1.1",
"@docusaurus/types": "3.1.1"
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/types": "3.2.0"
},
"browserslist": {
"production": [

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "docusaurus-2-classic-typescript-template",
"version": "3.0.0",
"version": "3.2.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@ -15,8 +15,8 @@
"typecheck": "tsc"
},
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/preset-classic": "3.0.0",
"@docusaurus/core": "3.2.0",
"@docusaurus/preset-classic": "3.2.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.0.0",
"@docusaurus/tsconfig": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/tsconfig": "3.2.0",
"@docusaurus/types": "3.2.0",
"typescript": "~5.2.2"
},
"browserslist": {

View File

@ -1,6 +1,6 @@
{
"name": "docusaurus-2-classic-template",
"version": "3.0.0",
"version": "3.2.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@ -14,8 +14,8 @@
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/preset-classic": "3.0.0",
"@docusaurus/core": "3.2.0",
"@docusaurus/preset-classic": "3.2.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.0.0",
"@docusaurus/types": "3.0.0"
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/types": "3.2.0"
},
"browserslist": {
"production": [

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/mdx-loader",
"version": "3.0.0",
"version": "3.2.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.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/logger": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-validation": "3.2.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.0.0",
"@docusaurus/types": "3.2.0",
"@types/escape-html": "^1.0.2",
"@types/mdast": "^4.0.2",
"@types/stringify-object": "^3.3.1",

View File

@ -71,6 +71,21 @@ 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,17 +34,24 @@ const plugin: Plugin = function plugin(
const {toString} = await import('mdast-util-to-string');
const {visit, EXIT} = await import('unist-util-visit');
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);
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;
}
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) {
// We only handle contentTitle when it's above the first thematic break
if (node.type === 'thematicBreak') {
return EXIT;
}
return undefined;

View File

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

View File

@ -45,19 +45,6 @@ declare module '@generated/routes' {
export default routes;
}
declare module '@generated/router' {
import type {ReactNode, ComponentType} from 'react';
export type Props = {
basename?: string | undefined;
children?: ReactNode;
};
const Router: ComponentType<Props>;
export default Router;
}
declare module '@generated/routesChunkNames' {
import type {RouteChunkNames} from '@docusaurus/types';

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-client-redirects",
"version": "3.0.0",
"version": "3.2.0",
"description": "Client redirects plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,18 +18,18 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.0",
"@docusaurus/logger": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-common": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"eta": "^2.2.0",
"fs-extra": "^11.1.1",
"lodash": "^4.17.21",
"tslib": "^2.6.0"
},
"devDependencies": {
"@docusaurus/types": "3.0.0"
"@docusaurus/types": "3.2.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';
import {removeTrailingSlash} from '@docusaurus/utils-common';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import collectRedirects from '../collectRedirects';
import {validateOptions} from '../options';

View File

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

View File

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

View File

@ -5,8 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
import logger from '@docusaurus/logger';
import {addLeadingSlash, removePrefix} from '@docusaurus/utils-common';
import collectRedirects from './collectRedirects';
import writeRedirectFiles, {
toRedirectFiles,
@ -16,21 +15,14 @@ import type {LoadContext, Plugin} from '@docusaurus/types';
import type {PluginContext, RedirectItem} from './types';
import type {PluginOptions, Options} from './options';
const PluginName = 'docusaurus-plugin-client-redirects';
export default function pluginClientRedirectsPages(
context: LoadContext,
options: PluginOptions,
): Plugin<void> {
const {trailingSlash, router} = context.siteConfig;
if (router === 'hash') {
logger.warn(`${PluginName} does not support the Hash Router`);
return {name: PluginName};
}
const {trailingSlash} = context.siteConfig;
return {
name: PluginName,
name: 'docusaurus-plugin-client-redirects',
async postBuild(props) {
const pluginContext: PluginContext = {
relativeRoutesPaths: props.routesPaths.map(

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-blog",
"version": "3.0.0",
"version": "3.2.0",
"description": "Blog plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-content-blog.d.ts",
@ -31,13 +31,13 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.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",
"cheerio": "^1.0.0-rc.12",
"feed": "^4.2.2",
"fs-extra": "^11.1.1",
@ -55,5 +55,8 @@
},
"engines": {
"node": ">=18.0"
},
"devDependencies": {
"@total-typescript/shoehorn": "^0.1.2"
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -220,54 +220,95 @@ exports[`atom has feed item for each post 1`] = `
]
`;
exports[`atom has feed item for each post using hash router 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>
<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"/>
<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"/>
<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"/>
<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"/>
<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ô"/>
<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"/>
<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>
@ -275,24 +316,26 @@ exports[`atom has feed item for each post using hash router 1`] = `
</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"/>
<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"/>
<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"/>
<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>
@ -463,41 +506,45 @@ exports[`json has feed item for each post 1`] = `
]
`;
exports[`json has feed item for each post using hash router 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",
"home_page_url": "https://docusaurus.io/myBaseUrl/blog/",
"description": "Hello Blog",
"items": [
{
"id": "https://docusaurus.io/#/myBaseUrl/blog/blog-with-links",
"url": "https://docusaurus.io/#/myBaseUrl/blog/blog-with-links",
"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",
"url": "https://docusaurus.io/#/myBaseUrl/blog/mdx-require-blog-post",
"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",
"url": "https://docusaurus.io/#/myBaseUrl/blog/mdx-blog-post",
"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ô",
"url": "https://docusaurus.io/#/myBaseUrl/blog/hey/my super path/héllô",
"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",
@ -507,8 +554,9 @@ exports[`json has feed item for each post using hash router 1`] = `
]
},
{
"id": "https://docusaurus.io/#/myBaseUrl/blog/simple/slug",
"url": "https://docusaurus.io/#/myBaseUrl/blog/simple/slug",
"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",
@ -519,15 +567,17 @@ exports[`json has feed item for each post using hash router 1`] = `
"tags": []
},
{
"id": "https://docusaurus.io/#/myBaseUrl/blog/heading-as-title",
"url": "https://docusaurus.io/#/myBaseUrl/blog/heading-as-title",
"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",
"url": "https://docusaurus.io/#/myBaseUrl/blog/date-matter",
"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",
@ -536,8 +586,9 @@ exports[`json has feed item for each post using hash router 1`] = `
]
},
{
"id": "https://docusaurus.io/#/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash",
"url": "https://docusaurus.io/#/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash",
"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",
@ -767,13 +818,13 @@ exports[`rss has feed item for each post 1`] = `
]
`;
exports[`rss has feed item for each post using hash router 1`] = `
exports[`rss has feed item for each post - with trailing slash 1`] = `
[
"<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<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>
<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>
@ -782,61 +833,104 @@ exports[`rss has feed item for each post using hash router 1`] = `
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>

View File

@ -1,5 +1,250 @@
// 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,7 +50,6 @@ 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",
@ -94,7 +93,6 @@ 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,12 +8,14 @@
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';
@ -236,7 +238,7 @@ describe('linkify', () => {
hasTruncateMarker: false,
frontMatter: {},
authors: [],
formattedDate: '',
unlisted: false,
},
content: '',
},
@ -295,3 +297,81 @@ 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,18 +9,13 @@ 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';
import type {
LoadContext,
I18n,
DocusaurusConfig,
MarkdownConfig,
RouterType,
} from '@docusaurus/types';
import type {LoadContext, I18n} from '@docusaurus/types';
import type {BlogContentPaths} from '../types';
import type {FeedType, PluginOptions} from '@docusaurus/plugin-content-blog';
import type {PluginOptions} from '@docusaurus/plugin-content-blog';
const DefaultI18N: I18n = {
currentLocale: 'en',
@ -38,13 +33,7 @@ const DefaultI18N: I18n = {
},
};
function partial<T>(t: Partial<T>): T {
return t as T;
}
const markdown = partial<MarkdownConfig>({
parseFrontMatter: DEFAULT_PARSE_FRONT_MATTER,
});
const markdown = {parseFrontMatter: DEFAULT_PARSE_FRONT_MATTER};
function getBlogContentPaths(siteDir: string): BlogContentPaths {
return {
@ -77,61 +66,41 @@ async function testGenerateFeeds(
});
}
function pluginOptions(
feedType: FeedType,
options: Partial<PluginOptions> = {},
): PluginOptions {
return partial<PluginOptions>({
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*-->/,
...options,
});
}
function siteFor(
siteDir: string,
siteOptions?: {baseUrl?: string; router?: RouterType},
) {
const siteConfig = partial<DocusaurusConfig>({
title: 'Hello',
router: siteOptions?.router,
baseUrl: siteOptions?.baseUrl,
url: 'https://docusaurus.io',
favicon: 'image/favicon.ico',
markdown,
});
const outDir = path.join(siteDir, 'build-snap');
const loadContext = partial<LoadContext>({
siteDir,
siteConfig,
i18n: DefaultI18N,
outDir,
});
return {siteConfig, outDir, loadContext};
}
describe.each(['atom', 'rss', 'json'] as FeedType[])('%s', (feedType) => {
describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
const fsMock = jest.spyOn(fs, 'outputFile').mockImplementation(() => {});
it('does not get generated without posts', async () => {
const {loadContext} = siteFor(__dirname);
const siteDir = __dirname;
const siteConfig = {
title: 'Hello',
baseUrl: '/',
url: 'https://docusaurus.io',
favicon: 'image/favicon.ico',
markdown,
};
const outDir = path.join(siteDir, 'build-snap');
await testGenerateFeeds(
loadContext,
pluginOptions(feedType, {
path: 'invalid-blog-path',
fromPartial({
siteDir,
siteConfig,
i18n: DefaultI18N,
outDir,
}),
{
path: 'invalid-blog-path',
routeBasePath: 'blog',
tagsBasePath: 'tags',
authorsMapPath: 'authors.yml',
include: ['*.md', '*.mdx'],
feedOptions: {
type: [feedType],
copyright: 'Copyright',
},
readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}),
truncateMarker: /<!--\s*truncate\s*-->/,
} as PluginOptions,
);
expect(fsMock).toHaveBeenCalledTimes(0);
@ -140,28 +109,40 @@ describe.each(['atom', 'rss', 'json'] as FeedType[])('%s', (feedType) => {
it('has feed item for each post', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const {loadContext} = siteFor(siteDir, {baseUrl: '/myBaseUrl/'});
// Build is quite difficult to mock, so we built the blog beforehand and
// copied the output to the fixture...
await testGenerateFeeds(loadContext, pluginOptions(feedType));
expect(
fsMock.mock.calls.map((call) => call[1] as string),
).toMatchSnapshot();
fsMock.mockClear();
});
it('has feed item for each post using hash router', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const {loadContext} = siteFor(siteDir, {
const outDir = path.join(siteDir, 'build-snap');
const siteConfig = {
title: 'Hello',
baseUrl: '/myBaseUrl/',
router: 'hash',
});
url: 'https://docusaurus.io',
favicon: 'image/favicon.ico',
markdown,
};
// Build is quite difficult to mock, so we built the blog beforehand and
// copied the output to the fixture...
await testGenerateFeeds(loadContext, pluginOptions(feedType));
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),
@ -171,15 +152,31 @@ describe.each(['atom', 'rss', 'json'] as FeedType[])('%s', (feedType) => {
it('filters to the first two entries', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const {loadContext} = siteFor(siteDir, {
const outDir = path.join(siteDir, 'build-snap');
const siteConfig = {
title: 'Hello',
baseUrl: '/myBaseUrl/',
});
url: 'https://docusaurus.io',
favicon: 'image/favicon.ico',
markdown,
};
// Build is quite difficult to mock, so we built the blog beforehand and
// copied the output to the fixture...
await testGenerateFeeds(
loadContext,
pluginOptions(feedType, {
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',
@ -194,7 +191,10 @@ describe.each(['atom', 'rss', 'json'] as FeedType[])('%s', (feedType) => {
});
},
},
}),
readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}),
truncateMarker: /<!--\s*truncate\s*-->/,
} as PluginOptions,
);
expect(
@ -205,21 +205,84 @@ describe.each(['atom', 'rss', 'json'] as FeedType[])('%s', (feedType) => {
it('filters to the first two entries using limit', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const {loadContext} = siteFor(siteDir, {
const outDir = path.join(siteDir, 'build-snap');
const siteConfig = {
title: 'Hello',
baseUrl: '/myBaseUrl/',
});
url: 'https://docusaurus.io',
favicon: 'image/favicon.ico',
markdown,
};
// Build is quite difficult to mock, so we built the blog beforehand and
// copied the output to the fixture...
await testGenerateFeeds(
loadContext,
pluginOptions(feedType, {
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',
limit: 2,
},
readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}),
truncateMarker: /<!--\s*truncate\s*-->/,
} as PluginOptions,
);
expect(
fsMock.mock.calls.map((call) => call[1] as string),
).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(

View File

@ -8,7 +8,11 @@
import {jest} from '@jest/globals';
import path from 'path';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import {posixPath, getFileCommitDate} from '@docusaurus/utils';
import {
posixPath,
getFileCommitDate,
LAST_UPDATE_FALLBACK,
} from '@docusaurus/utils';
import pluginContentBlog from '../index';
import {validateOptions} from '../options';
import type {
@ -41,6 +45,7 @@ const markdown: MarkdownConfig = {
}
return result;
},
remarkRehypeOptions: undefined,
};
function findByTitle(
@ -175,7 +180,6 @@ 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'],
@ -220,7 +224,6 @@ describe('blog plugin', () => {
},
],
date: new Date('2018-12-14'),
formattedDate: 'December 14, 2018',
frontMatter: {
authors: [
{
@ -256,7 +259,6 @@ 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ô',
@ -302,7 +304,6 @@ describe('blog plugin', () => {
title: 'draft',
},
date: new Date('2020-08-15'),
formattedDate: 'August 15, 2020',
frontMatter: {
author: 'Sébastien Lorber',
author_title: 'Docusaurus maintainer',
@ -328,7 +329,6 @@ describe('blog plugin', () => {
description: '',
authors: [],
date: new Date('2019-01-02'),
formattedDate: 'January 2, 2019',
frontMatter: {
date: new Date('2019-01-02'),
},
@ -343,39 +343,6 @@ 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});
@ -474,13 +441,8 @@ 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 = getFileCommitDate(noDateSourceFile, {age: 'oldest'});
const result = await 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,
@ -494,7 +456,6 @@ describe('blog plugin', () => {
description: `no date`,
authors: [],
date: noDateSourceTime,
formattedDate,
frontMatter: {},
tags: [],
prevItem: undefined,
@ -541,4 +502,165 @@ 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,7 +29,6 @@ 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,6 +26,7 @@ import {
getContentPathList,
isUnlisted,
isDraft,
readLastUpdateData,
} from '@docusaurus/utils';
import {validateBlogPostFrontMatter} from './frontMatter';
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
@ -164,25 +165,6 @@ 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,
@ -250,6 +232,12 @@ 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});
@ -277,10 +265,11 @@ async function processBlogSourceFile(
}
try {
const result = getFileCommitDate(blogSourceAbsolute, {
const result = await getFileCommitDate(blogSourceAbsolute, {
age: 'oldest',
includeAuthor: false,
});
return result.date;
} catch (err) {
logger.warn(err);
@ -289,11 +278,6 @@ 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 ?? '';
@ -348,7 +332,6 @@ async function processBlogSourceFile(
title,
description,
date,
formattedDate,
tags: normalizeFrontMatterTags(tagsBasePath, frontMatter.tags),
readingTime: showReadingTime
? options.readingTime({
@ -361,6 +344,8 @@ async function processBlogSourceFile(
authors,
frontMatter,
unlisted,
lastUpdatedAt: lastUpdate.lastUpdatedAt,
lastUpdatedBy: lastUpdate.lastUpdatedBy,
},
content,
};
@ -446,3 +431,19 @@ 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

@ -11,7 +11,10 @@ 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} from '@docusaurus/utils-common';
import {
blogPostContainerID,
applyTrailingSlash,
} from '@docusaurus/utils-common';
import {load as cheerioLoad} from 'cheerio';
import type {DocusaurusConfig} from '@docusaurus/types';
import type {
@ -40,8 +43,14 @@ async function generateBlogFeed({
}
const {feedOptions, routeBasePath} = options;
const {url: siteUrl, baseUrl, title, favicon} = siteConfig;
const blogBaseUrl = normalizeUrl([siteUrl, baseUrl, routeBasePath]);
const {url: siteUrl, baseUrl, title, favicon, trailingSlash} = siteConfig;
const blogBaseUrl = applyTrailingSlash(
normalizeUrl([siteUrl, baseUrl, routeBasePath]),
{
trailingSlash,
baseUrl,
},
);
const blogPostsForFeed =
feedOptions.limit === false || feedOptions.limit === null
@ -76,65 +85,6 @@ async function generateBlogFeed({
return feed;
}
async function readBlogPostContent({
blogPost: post,
absoluteUrl,
siteConfig,
outDir,
}: {
blogPost: BlogPost;
absoluteUrl: string;
siteConfig: DocusaurusConfig;
outDir: string;
}): Promise<string | undefined> {
const {router, trailingSlash, baseUrl} = siteConfig;
const {
metadata: {permalink},
} = post;
// The hash router does not SSG: we can't read content from HTML files
if (router === 'hash') {
return undefined;
}
const content = await readOutputHTMLFile(
permalink.replace(baseUrl, ''),
outDir,
trailingSlash,
);
const $ = cheerioLoad(content);
const toAbsoluteUrl = (src: string) => String(new URL(src, absoluteUrl));
// Make links and image urls absolute
// See https://github.com/facebook/docusaurus/issues/9136
$(`div#${blogPostContainerID} a, div#${blogPostContainerID} img`).each(
(_, elm) => {
if (elm.tagName === 'a') {
const {href} = elm.attribs;
if (href) {
elm.attribs.href = toAbsoluteUrl(href);
}
} else if (elm.tagName === 'img') {
const {src, srcset: srcsetAttr} = elm.attribs;
if (src) {
elm.attribs.src = toAbsoluteUrl(src);
}
if (srcsetAttr) {
elm.attribs.srcset = srcset.stringify(
srcset.parse(srcsetAttr).map((props) => ({
...props,
url: toAbsoluteUrl(props.url),
})),
);
}
}
},
);
return $(`#${blogPostContainerID}`).html()!;
}
async function defaultCreateFeedItems({
blogPosts,
siteConfig,
@ -144,7 +94,7 @@ async function defaultCreateFeedItems({
siteConfig: DocusaurusConfig;
outDir: string;
}): Promise<BlogFeedItem[]> {
const {url: siteUrl, router} = siteConfig;
const {url: siteUrl, baseUrl, trailingSlash} = siteConfig;
function toFeedAuthor(author: Author): FeedAuthor {
return {name: author.name, link: author.url, email: author.email};
@ -163,28 +113,59 @@ async function defaultCreateFeedItems({
},
} = post;
const absoluteUrl = normalizeUrl([
siteUrl,
router === 'hash' ? '/#/' : '',
permalink,
]);
const content = await readBlogPostContent({
blogPost: post,
absoluteUrl,
siteConfig,
const content = await readOutputHTMLFile(
permalink.replace(baseUrl, ''),
outDir,
});
trailingSlash,
);
const $ = cheerioLoad(content);
const blogPostAbsoluteUrl = applyTrailingSlash(
normalizeUrl([siteUrl, permalink]),
{
trailingSlash,
baseUrl,
},
);
const toAbsoluteUrl = (src: string) =>
String(new URL(src, blogPostAbsoluteUrl));
// Make links and image urls absolute
// See https://github.com/facebook/docusaurus/issues/9136
$(`div#${blogPostContainerID} a, div#${blogPostContainerID} img`).each(
(_, elm) => {
if (elm.tagName === 'a') {
const {href} = elm.attribs;
if (href) {
elm.attribs.href = toAbsoluteUrl(href);
}
} else if (elm.tagName === 'img') {
const {src, srcset: srcsetAttr} = elm.attribs;
if (src) {
elm.attribs.src = toAbsoluteUrl(src);
}
if (srcsetAttr) {
elm.attribs.srcset = srcset.stringify(
srcset.parse(srcsetAttr).map((props) => ({
...props,
url: toAbsoluteUrl(props.url),
})),
);
}
}
},
);
const feedItem: BlogFeedItem = {
title: metadataTitle,
id: absoluteUrl,
link: absoluteUrl,
id: blogPostAbsoluteUrl,
link: blogPostAbsoluteUrl,
date,
description,
// Atom feed demands the "term", while other feeds use "name"
category: tags.map((tag) => ({name: tag.label, term: tag.label})),
content,
content: $(`#${blogPostContainerID}`).html()!,
};
// json1() method takes the first item of authors array

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,6 +69,7 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
hide_table_of_contents: Joi.boolean(),
...FrontMatterTOCHeadingLevels,
last_update: FrontMatterLastUpdateSchema,
})
.messages({
'deprecate.error':

View File

@ -11,6 +11,7 @@ import {
normalizeUrl,
docuHash,
aliasedSitePath,
aliasedSitePathToRelativePath,
getPluginI18nPath,
posixPath,
addTrailingPathSeparator,
@ -20,11 +21,12 @@ 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';
@ -32,7 +34,12 @@ import {createBlogFeedFiles} from './feed';
import {toTagProp, toTagsProp} from './props';
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types';
import type {
LoadContext,
Plugin,
HtmlTags,
RouteMetadata,
} from '@docusaurus/types';
import type {
PluginOptions,
BlogPostFrontMatter,
@ -113,7 +120,11 @@ export default async function pluginContentBlog(
const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
const blogPosts = await generateBlogPosts(contentPaths, context, options);
let blogPosts = await generateBlogPosts(contentPaths, context, options);
blogPosts = await applyProcessBlogPosts({
blogPosts,
processBlogPosts: options.processBlogPosts,
});
const listedBlogPosts = blogPosts.filter(shouldBeListed);
if (!blogPosts.length) {
@ -268,6 +279,15 @@ export default async function pluginContentBlog(
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) => {
@ -287,6 +307,7 @@ export default async function pluginContentBlog(
sidebar: aliasedSource(sidebarProp),
content: metadata.source,
},
metadata: createBlogPostRouteMetadata(metadata),
context: {
blogMetadata: aliasedSource(blogMetadataPath),
},

View File

@ -51,6 +51,9 @@ export const DEFAULT_OPTIONS: PluginOptions = {
authorsMapPath: 'authors.yml',
readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}),
sortPosts: 'descending',
showLastUpdateTime: false,
showLastUpdateAuthor: false,
processBlogPosts: async () => undefined,
};
const PluginOptionSchema = Joi.object<PluginOptions>({
@ -134,6 +137,13 @@ 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

@ -10,7 +10,12 @@
declare module '@docusaurus/plugin-content-blog' {
import type {LoadedMDXContent} from '@docusaurus/mdx-loader';
import type {MDXOptions} from '@docusaurus/mdx-loader';
import type {FrontMatterTag, Tag} from '@docusaurus/utils';
import type {
FrontMatterTag,
Tag,
LastUpdateData,
FrontMatterLastUpdate,
} from '@docusaurus/utils';
import type {DocusaurusConfig, Plugin, LoadContext} from '@docusaurus/types';
import type {Item as FeedItem} from 'feed';
import type {Overwrite} from 'utility-types';
@ -156,6 +161,8 @@ 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 & {
@ -180,7 +187,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
| BlogPostFrontMatterAuthor
| (string | BlogPostFrontMatterAuthor)[];
export type BlogPostMetadata = {
export type BlogPostMetadata = LastUpdateData & {
/** Path to the Markdown source, with `@site` alias. */
readonly source: string;
/**
@ -192,11 +199,6 @@ 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;
/**
@ -335,6 +337,11 @@ 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.
*/
@ -426,6 +433,14 @@ 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;
};
/**

View File

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

View File

@ -52,7 +52,6 @@ exports[`simple website content 1`] = `
"description": "Images",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"id": "baz",
"pagination_label": "baz pagination_label",
@ -105,7 +104,6 @@ exports[`simple website content 2`] = `
"description": "Hi, Endilie here :)",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"id": "hello",
"sidebar_label": "Hello sidebar_label",
@ -151,7 +149,6 @@ exports[`simple website content 3`] = `
"description": "This is custom description",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"description": "This is custom description",
"id": "bar",
@ -1485,6 +1482,10 @@ 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",
},
@ -1494,6 +1495,10 @@ 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",
},
@ -1511,6 +1516,10 @@ 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",
},
@ -1519,6 +1528,10 @@ 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",
},
@ -1527,6 +1540,10 @@ 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",
},
@ -1535,6 +1552,10 @@ 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",
},
@ -1544,6 +1565,10 @@ 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",
},
@ -1553,6 +1578,10 @@ 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",
},
@ -1562,6 +1591,10 @@ 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",
},
@ -1571,6 +1604,10 @@ 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",
},
@ -1580,6 +1617,10 @@ 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",
},
@ -1588,6 +1629,10 @@ 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",
},
@ -1596,6 +1641,10 @@ 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",
},
@ -1604,6 +1653,10 @@ 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",
},
@ -1612,6 +1665,10 @@ 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",
},
@ -1621,6 +1678,10 @@ 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",
},
@ -1630,6 +1691,10 @@ 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",
},
@ -1639,6 +1704,10 @@ 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",
},
@ -1647,6 +1716,10 @@ 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",
},
@ -1655,6 +1728,10 @@ 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",
},
@ -1663,6 +1740,10 @@ 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",
},
@ -1672,6 +1753,10 @@ 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",
},
@ -1971,7 +2056,6 @@ 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,
@ -1999,7 +2083,6 @@ 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,
@ -2030,7 +2113,6 @@ 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,
@ -2064,7 +2146,6 @@ 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",
},
@ -2097,7 +2178,6 @@ 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,
@ -2131,7 +2211,6 @@ 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,
@ -2165,7 +2244,6 @@ 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",
},
@ -2198,7 +2276,6 @@ 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",
},
@ -2231,7 +2308,6 @@ 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,
@ -2262,7 +2338,6 @@ 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,
@ -2293,7 +2368,6 @@ 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,
@ -2324,7 +2398,6 @@ 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,
@ -2355,7 +2428,6 @@ 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,
@ -2386,7 +2458,6 @@ 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,
@ -2566,7 +2637,6 @@ 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,
@ -2594,7 +2664,6 @@ 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,
@ -2625,7 +2694,6 @@ 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,
@ -2656,7 +2724,6 @@ 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,
@ -2716,7 +2783,6 @@ exports[`versioned website (community) content 1`] = `
"description": "Team current version (translated)",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"title": "Team title translated",
},
@ -2743,7 +2809,6 @@ exports[`versioned website (community) content 2`] = `
"description": "Team 1.0.0",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "team",
"lastUpdatedAt": undefined,
@ -2963,6 +3028,10 @@ 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",
},
@ -2990,6 +3059,10 @@ 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",
},
@ -3023,7 +3096,6 @@ exports[`versioned website content 1`] = `
"description": "This is next version of bar.",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"slug": "barSlug",
"tags": [
@ -3074,7 +3146,6 @@ exports[`versioned website content 2`] = `
"description": "Bar 1.0.1 !",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {},
"id": "foo/bar",
"lastUpdatedAt": undefined,
@ -3102,7 +3173,6 @@ exports[`versioned website content 3`] = `
"description": "Hello next !",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"slug": "/",
},
@ -3132,7 +3202,6 @@ exports[`versioned website content 4`] = `
"description": "Hello 1.0.1 !",
"draft": false,
"editUrl": undefined,
"formattedLastUpdatedAt": undefined,
"frontMatter": {
"slug": "/",
},
@ -3162,7 +3231,6 @@ 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,
@ -4202,6 +4270,10 @@ 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",
},
@ -4211,6 +4283,10 @@ 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",
},
@ -4220,6 +4296,10 @@ 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",
},
@ -4279,6 +4359,10 @@ 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",
},
@ -4288,6 +4372,10 @@ 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",
},
@ -4296,6 +4384,10 @@ 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",
},
@ -4305,6 +4397,10 @@ 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",
},
@ -4313,6 +4409,10 @@ 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",
},
@ -4321,6 +4421,10 @@ 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",
},
@ -4347,6 +4451,10 @@ 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",
},
@ -4355,6 +4463,10 @@ 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",
},
@ -4363,6 +4475,10 @@ 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",
},
@ -4372,6 +4488,10 @@ 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",
},
@ -4380,6 +4500,10 @@ 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",
},
@ -4388,6 +4512,10 @@ 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",
},
@ -4396,6 +4524,10 @@ 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",
},
@ -4404,6 +4536,10 @@ 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",
},
@ -4430,6 +4566,10 @@ 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",
},
@ -4439,6 +4579,10 @@ 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,8 +7,13 @@
import {jest} from '@jest/globals';
import path from 'path';
import {loadContext} from '@docusaurus/core/src/server/index';
import {createSlugger, posixPath, DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import {loadContext} from '@docusaurus/core/src/server/site';
import {
createSlugger,
posixPath,
DEFAULT_PLUGIN_ID,
LAST_UPDATE_FALLBACK,
} from '@docusaurus/utils';
import {createSidebarsUtils} from '../sidebars/utils';
import {
processDocMetadata,
@ -474,9 +479,8 @@ describe('simple site', () => {
custom_edit_url: 'https://github.com/customUrl/docs/lorem.md',
unrelated_front_matter: "won't be part of metadata",
},
lastUpdatedAt: 1539502055,
formattedLastUpdatedAt: 'Oct 14, 2018',
lastUpdatedBy: 'Author',
lastUpdatedAt: LAST_UPDATE_FALLBACK.lastUpdatedAt,
lastUpdatedBy: LAST_UPDATE_FALLBACK.lastUpdatedBy,
tags: [],
unlisted: false,
});
@ -572,8 +576,7 @@ describe('simple site', () => {
},
title: 'Custom Last Update',
},
lastUpdatedAt: new Date('1/1/2000').getTime() / 1000,
formattedLastUpdatedAt: 'Jan 1, 2000',
lastUpdatedAt: new Date('1/1/2000').getTime(),
lastUpdatedBy: 'Custom Author (processed by parseFrontMatter)',
sidebarPosition: undefined,
tags: [],
@ -611,8 +614,7 @@ describe('simple site', () => {
},
title: 'Last Update Author Only',
},
lastUpdatedAt: 1539502055,
formattedLastUpdatedAt: 'Oct 14, 2018',
lastUpdatedAt: LAST_UPDATE_FALLBACK.lastUpdatedAt,
lastUpdatedBy: 'Custom Author (processed by parseFrontMatter)',
sidebarPosition: undefined,
tags: [],
@ -650,8 +652,7 @@ describe('simple site', () => {
},
title: 'Last Update Date Only',
},
lastUpdatedAt: new Date('1/1/2000').getTime() / 1000,
formattedLastUpdatedAt: 'Jan 1, 2000',
lastUpdatedAt: new Date('1/1/2000').getTime(),
lastUpdatedBy: 'Author',
sidebarPosition: undefined,
tags: [],
@ -691,7 +692,6 @@ 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},
'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).',
'"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',
],
[
{last_update: {}},
'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).',
'"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',
],
[
{last_update: ''},
'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).',
'"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',
],
[
{last_update: {invalid: 'key'}},
'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).',
'"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',
],
[
{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/index';
import {loadContext} from '@docusaurus/core/src/server/site';
import {applyConfigureWebpack} from '@docusaurus/core/src/webpack/utils';
import {sortConfig} from '@docusaurus/core/src/server/plugins/routeConfig';
import {sortRoutes} 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
sortConfig(routeConfigs);
sortRoutes(routeConfigs);
expect(routeConfigs).not.toEqual([]);
expect(routeConfigs).toMatchSnapshot('route config');
expect(dataContainer).toMatchSnapshot('data');

View File

@ -1,117 +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 {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

@ -8,7 +8,6 @@
import path from 'path';
import fs from 'fs-extra';
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {
aliasedSitePath,
getEditUrl,
@ -21,12 +20,11 @@ import {
normalizeFrontMatterTags,
isUnlisted,
isDraft,
readLastUpdateData,
} from '@docusaurus/utils';
import {getFileLastUpdate} from './lastUpdate';
import {validateDocFrontMatter} from './frontMatter';
import getSlug from './slug';
import {stripPathNumberPrefixes} from './numberPrefix';
import {validateDocFrontMatter} from './frontMatter';
import {toDocNavigationLink, toNavigationLink} from './sidebars/utils';
import type {
MetadataOptions,
@ -35,61 +33,13 @@ 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,
@ -142,7 +92,6 @@ async function doProcessDocMetadata({
const {source, content, contentPath, filePath} = docFile;
const {
siteDir,
i18n,
siteConfig: {
markdown: {parseFrontMatter},
},
@ -257,21 +206,6 @@ 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
@ -291,13 +225,6 @@ 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,7 +4,6 @@
* 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,
@ -12,17 +11,15 @@ 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
const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
export const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
id: Joi.string(),
// See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398
title: Joi.string().allow(''),
@ -45,15 +42,7 @@ const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
pagination_next: Joi.string().allow(null),
pagination_prev: Joi.string().allow(null),
...FrontMatterTOCHeadingLevels,
last_update: Joi.object({
author: Joi.string(),
date: Joi.date().raw(),
})
.or('author', 'date')
.messages({
'object.missing': FrontMatterLastUpdateErrorMessage,
'object.base': FrontMatterLastUpdateErrorMessage,
}),
last_update: FrontMatterLastUpdateSchema,
})
.unknown()
.concat(ContentVisibilitySchema);

View File

@ -1,51 +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 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,6 +16,8 @@ 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';
@ -24,14 +26,6 @@ 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.
*/
@ -93,9 +87,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`
@ -401,16 +395,7 @@ 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?: 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;
last_update?: FrontMatterLastUpdate;
};
export type DocMetadataBase = LastUpdateData & {

View File

@ -7,21 +7,38 @@
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {docuHash, createSlugger, normalizeUrl} from '@docusaurus/utils';
import {
docuHash,
createSlugger,
normalizeUrl,
aliasedSitePathToRelativePath,
} from '@docusaurus/utils';
import {
toTagDocListProp,
toTagsListTagsProp,
toVersionMetadataProp,
} from './props';
import {getVersionTags} from './tags';
import type {PluginContentLoadedActions, RouteConfig} from '@docusaurus/types';
import type {
PluginContentLoadedActions,
RouteConfig,
RouteMetadata,
} 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,
@ -68,26 +85,27 @@ async function buildVersionDocRoutes({
options,
}: BuildVersionRoutesParam): Promise<RouteConfig[]> {
return Promise.all(
version.docs.map(async (metadataItem) => {
version.docs.map(async (doc) => {
await actions.createData(
// Note that this created data path must be in sync with
// metadataPath provided to mdx-loader.
`${docuHash(metadataItem.source)}.json`,
JSON.stringify(metadataItem, null, 2),
`${docuHash(doc.source)}.json`,
JSON.stringify(doc, null, 2),
);
const docRoute: RouteConfig = {
path: metadataItem.permalink,
path: doc.permalink,
component: options.docItemComponent,
exact: true,
modules: {
content: metadataItem.source,
content: doc.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)
...(metadataItem.sidebar && {
sidebar: metadataItem.sidebar,
...(doc.sidebar && {
sidebar: doc.sidebar,
}),
};

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-pages",
"version": "3.0.0",
"version": "3.2.0",
"description": "Pages plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-content-pages.d.ts",
@ -18,11 +18,11 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.0",
"@docusaurus/mdx-loader": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-validation": "3.2.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/lib/server';
import {loadContext} from '@docusaurus/core/src/server/site';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import pluginContentPages from '../index';

View File

@ -11,6 +11,7 @@ import {
encodePath,
fileToPath,
aliasedSitePath,
aliasedSitePathToRelativePath,
docuHash,
getPluginI18nPath,
getFolderContainingFile,
@ -24,8 +25,7 @@ import {
isDraft,
} from '@docusaurus/utils';
import {validatePageFrontMatter} from './frontMatter';
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {LoadContext, Plugin, RouteMetadata} from '@docusaurus/types';
import type {PagesContentPaths} from './types';
import type {
PluginOptions,
@ -159,9 +159,20 @@ 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
@ -173,6 +184,7 @@ export default function pluginContentPages(
path: permalink,
component: options.mdxPageComponent,
exact: true,
metadata: routeMetadata,
modules: {
content: source,
},
@ -182,6 +194,7 @@ 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.0.0",
"version": "3.2.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.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/core": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils": "3.2.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 contentLoaded({actions: {createData, addRoute}, allContent}) {
async allContentLoaded({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.0.0",
"version": "3.2.0",
"description": "Global analytics (analytics.js) plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,9 +18,9 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"tslib": "^2.6.0"
},
"peerDependencies": {

View File

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

View File

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

View File

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

View File

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

View File

@ -11,14 +11,11 @@ import WebpackBar from 'webpackbar';
import Terser from 'terser-webpack-plugin';
import {injectManifest} from 'workbox-build';
import {normalizeUrl} from '@docusaurus/utils';
import logger from '@docusaurus/logger';
import {compile} from '@docusaurus/core/lib/webpack/utils';
import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
import type {HtmlTags, LoadContext, Plugin} from '@docusaurus/types';
import type {PluginOptions} from '@docusaurus/plugin-pwa';
const PluginName = 'docusaurus-plugin-pwa';
const isProd = process.env.NODE_ENV === 'production';
function getSWBabelLoader() {
@ -50,7 +47,6 @@ export default function pluginPWA(
outDir,
baseUrl,
i18n: {currentLocale},
siteConfig: {router},
} = context;
const {
debug,
@ -61,13 +57,8 @@ export default function pluginPWA(
swRegister,
} = options;
if (router === 'hash') {
logger.warn(`${PluginName} does not support the Hash Router`);
return {name: PluginName};
}
return {
name: PluginName,
name: 'docusaurus-plugin-pwa',
getThemePath() {
return '../lib/theme';

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-sitemap",
"version": "3.0.0",
"version": "3.2.0",
"description": "Simple sitemap generation plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,16 +18,19 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.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",
"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,115 +6,91 @@
*/
import React from 'react';
import {EnumChangefreq} from 'sitemap';
import {fromPartial} from '@total-typescript/shoehorn';
import createSitemap from '../createSitemap';
import type {PluginOptions} from '../options';
import type {DocusaurusConfig} from '@docusaurus/types';
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));
};
describe('createSitemap', () => {
it('simple site', async () => {
const sitemap = await createSitemap(
{
url: 'https://example.com',
} as DocusaurusConfig,
['/', '/test'],
{},
{
changefreq: EnumChangefreq.DAILY,
priority: 0.7,
ignorePatterns: [],
filename: 'sitemap.xml',
},
);
expect(sitemap).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://example.com/</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://example.com/test</loc><changefreq>daily</changefreq><priority>0.7</priority></url></urlset>"`,
const sitemap = await createSitemap({
siteConfig,
routes: routes(['/', '/test']),
head: {},
options,
});
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('simple site - hash router', async () => {
const sitemap = await createSitemap(
{
url: 'https://example.com',
router: 'hash',
} as DocusaurusConfig,
['/', '/test'],
{},
{
changefreq: EnumChangefreq.DAILY,
priority: 0.7,
ignorePatterns: [],
filename: 'sitemap.xml',
},
);
expect(sitemap).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://example.com/#/</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://example.com/#/test</loc><changefreq>daily</changefreq><priority>0.7</priority></url></urlset>"`,
);
});
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('site with no routes', async () => {
const sitemap = await createSitemap({
siteConfig,
routes: routes([]),
head: {},
options,
});
expect(sitemap).toBeNull();
});
it('excludes patterns configured to be ignored', async () => {
const sitemap = await createSitemap(
{
url: 'https://example.com',
} as DocusaurusConfig,
['/', '/search/', '/tags/', '/search/foo', '/tags/foo/bar'],
{},
{
changefreq: EnumChangefreq.DAILY,
priority: 0.7,
const sitemap = await createSitemap({
siteConfig,
routes: routes([
'/',
'/search/',
'/tags/',
'/search/foo',
'/tags/foo/bar',
]),
head: {},
options: {
...options,
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(
{
url: 'https://example.com',
trailingSlash: undefined,
} as DocusaurusConfig,
['/', '/test', '/nested/test', '/nested/test2/'],
{},
{
changefreq: EnumChangefreq.DAILY,
priority: 0.7,
ignorePatterns: [],
filename: 'sitemap.xml',
},
);
const sitemap = await createSitemap({
siteConfig,
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>');
@ -123,20 +99,12 @@ describe('createSitemap', () => {
});
it('add trailing slash', async () => {
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',
},
);
const sitemap = await createSitemap({
siteConfig: {...siteConfig, trailingSlash: true},
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>');
@ -145,20 +113,16 @@ describe('createSitemap', () => {
});
it('remove trailing slash', async () => {
const sitemap = await createSitemap(
{
const sitemap = await createSitemap({
siteConfig: {
...siteConfig,
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>');
@ -167,13 +131,11 @@ describe('createSitemap', () => {
});
it('filters pages with noindex', async () => {
const sitemap = await createSitemap(
{
url: 'https://example.com',
trailingSlash: false,
} as DocusaurusConfig,
['/', '/noindex', '/nested/test', '/nested/test2/'],
{
const sitemap = await createSitemap({
siteConfig,
routesPaths: ['/', '/noindex', '/nested/test', '/nested/test2/'],
routes: routes(['/', '/noindex', '/nested/test', '/nested/test2/']),
head: {
'/noindex': {
meta: {
// @ts-expect-error: bad lib def
@ -186,24 +148,18 @@ describe('createSitemap', () => {
},
},
},
{
changefreq: EnumChangefreq.DAILY,
priority: 0.7,
ignorePatterns: [],
},
);
options,
});
expect(sitemap).not.toContain('/noindex');
});
it('does not generate anything for all pages with noindex', async () => {
const sitemap = await createSitemap(
{
url: 'https://example.com',
trailingSlash: false,
} as DocusaurusConfig,
['/', '/noindex'],
{
const sitemap = await createSitemap({
siteConfig,
routesPaths: ['/', '/noindex'],
routes: routes(['/', '/noindex']),
head: {
'/': {
meta: {
// @ts-expect-error: bad lib def
@ -221,12 +177,8 @@ describe('createSitemap', () => {
},
},
},
{
changefreq: EnumChangefreq.DAILY,
priority: 0.7,
ignorePatterns: [],
},
);
options,
});
expect(sitemap).toBeNull();
});

View File

@ -0,0 +1,229 @@
/**
* 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,7 +12,6 @@ import {
type Options,
type PluginOptions,
} from '../options';
import type {EnumChangefreq} from 'sitemap';
import type {Validate} from '@docusaurus/types';
function testValidate(options: Options) {
@ -34,9 +33,10 @@ describe('validateOptions', () => {
it('accepts correctly defined user options', () => {
const userOptions: Options = {
changefreq: 'yearly' as EnumChangefreq,
changefreq: 'yearly',
priority: 0.9,
ignorePatterns: ['/search/**'],
lastmod: 'datetime',
};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
@ -44,32 +44,209 @@ describe('validateOptions', () => {
});
});
it('rejects out-of-range priority inputs', () => {
expect(() =>
testValidate({priority: 2}),
).toThrowErrorMatchingInlineSnapshot(
`""priority" must be less than or equal to 1"`,
);
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 bad changefreq inputs', () => {
expect(() =>
testValidate({changefreq: 'annually' as EnumChangefreq}),
).toThrowErrorMatchingInlineSnapshot(
`""changefreq" must be one of [daily, monthly, always, hourly, weekly, yearly, never]"`,
);
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 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"`,
);
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"`,
);
});
});
});

View File

@ -0,0 +1,67 @@
/**
* 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,13 +6,23 @@
*/
import type {ReactElement} from 'react';
import {SitemapStream, streamToPromise} from 'sitemap';
import {applyTrailingSlash} from '@docusaurus/utils-common';
import {createMatcher, normalizeUrl} from '@docusaurus/utils';
import type {DocusaurusConfig} from '@docusaurus/types';
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 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,
@ -47,55 +57,51 @@ function isNoIndexMetaRoute({
);
}
export default async function createSitemap(
siteConfig: DocusaurusConfig,
routesPaths: string[],
head: {[location: string]: HelmetServerState},
options: PluginOptions,
): Promise<string | null> {
const {url: hostname, router} = siteConfig;
if (!hostname) {
throw new Error('URL in docusaurus.config.js cannot be empty/undefined.');
}
const {changefreq, priority, ignorePatterns} = options;
// 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;
const ignoreMatcher = createMatcher(ignorePatterns);
function isRouteExcluded(route: string) {
function isRouteExcluded(route: RouteConfig) {
return (
route.endsWith('404.html') ||
ignoreMatcher(route) ||
isNoIndexMetaRoute({head, route})
ignoreMatcher(route.path) || isNoIndexMetaRoute({head, route: route.path})
);
}
const includedRoutes = routesPaths.filter((route) => !isRouteExcluded(route));
return flattenRoutes(routes).filter((route) => !isRouteExcluded(route));
}
if (includedRoutes.length === 0) {
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) {
return null;
}
const sitemapStream = new SitemapStream({hostname});
const createSitemapUrl = (routePath: string): string => {
const path = normalizeUrl([router === 'hash' ? '/#/' : '', routePath]);
return applyTrailingSlash(path, {
trailingSlash: siteConfig.trailingSlash,
baseUrl: siteConfig.baseUrl,
});
};
includedRoutes.forEach((routePath) =>
sitemapStream.write({
url: createSitemapUrl(routePath),
changefreq,
priority,
}),
);
sitemapStream.end();
const generatedSitemap = (await streamToPromise(sitemapStream)).toString();
return generatedSitemap;
const xmlString = await sitemapItemsToXmlString(items, {
lastmod: params.options.lastmod,
});
return xmlString;
}

View File

@ -0,0 +1,76 @@
/**
* 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, routesPaths, outDir, head}) {
async postBuild({siteConfig, routes, outDir, head}) {
if (siteConfig.noIndex) {
return;
}
// Generate sitemap.
const generatedSitemap = await createSitemap(
const generatedSitemap = await createSitemap({
siteConfig,
routesPaths,
routes,
head,
options,
);
});
if (!generatedSitemap) {
return;
}

View File

@ -6,33 +6,60 @@
*/
import {Joi} from '@docusaurus/utils-validation';
import {EnumChangefreq} from 'sitemap';
import {ChangeFreqList, LastModOptionList} from './types';
import type {OptionValidationContext} from '@docusaurus/types';
import type {ChangeFreq, LastModOption} from './types';
export type PluginOptions = {
/** @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[];
/**
* The path to the created sitemap file, relative to the output directory.
* Useful if you have two plugin instances outputting two files.
*/
filename: string;
/**
* 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
*/
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;
};
export type Options = Partial<PluginOptions>;
export const DEFAULT_OPTIONS: PluginOptions = {
changefreq: EnumChangefreq.WEEKLY,
priority: 0.5,
ignorePatterns: [],
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',
priority: 0.5,
};
const PluginOptionSchema = Joi.object<PluginOptions>({
@ -41,10 +68,28 @@ 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(...Object.values(EnumChangefreq))
.valid(null, ...ChangeFreqList)
.default(DEFAULT_OPTIONS.changefreq),
priority: Joi.number().min(0).max(1).default(DEFAULT_OPTIONS.priority),
// 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),
ignorePatterns: Joi.array()
.items(Joi.string())
.default(DEFAULT_OPTIONS.ignorePatterns),

View File

@ -0,0 +1,67 @@
/**
* 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

@ -0,0 +1,35 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {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();
}

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-classic",
"version": "3.0.0",
"version": "3.2.0",
"description": "Classic theme for Docusaurus",
"main": "lib/index.js",
"types": "src/theme-classic.d.ts",
@ -20,18 +20,18 @@
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
},
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/plugin-content-blog": "3.0.0",
"@docusaurus/plugin-content-docs": "3.0.0",
"@docusaurus/plugin-content-pages": "3.0.0",
"@docusaurus/theme-common": "3.0.0",
"@docusaurus/theme-translations": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.0",
"@docusaurus/mdx-loader": "3.2.0",
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/plugin-content-blog": "3.2.0",
"@docusaurus/plugin-content-docs": "3.2.0",
"@docusaurus/plugin-content-pages": "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-common": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"copy-text-to-clipboard": "^3.2.0",

View File

@ -676,6 +676,16 @@ declare module '@theme/DocVersionSuggestions' {
export default function DocVersionSuggestions(): JSX.Element;
}
declare module '@theme/EditMetaRow' {
export interface Props {
readonly className: string;
readonly editUrl: string | null | undefined;
readonly lastUpdatedAt: number | undefined;
readonly lastUpdatedBy: string | undefined;
}
export default function EditMetaRow(props: Props): JSX.Element;
}
declare module '@theme/EditThisPage' {
export interface Props {
readonly editUrl: string;
@ -824,7 +834,6 @@ declare module '@theme/SearchMetadata' {
declare module '@theme/LastUpdated' {
export interface Props {
readonly lastUpdatedAt?: number;
readonly formattedLastUpdatedAt?: string;
readonly lastUpdatedBy?: string;
}

View File

@ -9,6 +9,7 @@ import React from 'react';
import Link from '@docusaurus/Link';
import {translate} from '@docusaurus/Translate';
import {PageMetadata} from '@docusaurus/theme-common';
import {useDateTimeFormat} from '@docusaurus/theme-common/internal';
import Layout from '@theme/Layout';
import type {ArchiveBlogPost, Props} from '@theme/BlogArchivePage';
import Heading from '@theme/Heading';
@ -19,6 +20,15 @@ type YearProp = {
};
function Year({year, posts}: YearProp) {
const dateTimeFormat = useDateTimeFormat({
day: 'numeric',
month: 'long',
timeZone: 'UTC',
});
const formatDate = (lastUpdated: string) =>
dateTimeFormat.format(new Date(lastUpdated));
return (
<>
<Heading as="h3" id={year}>
@ -28,7 +38,7 @@ function Year({year, posts}: YearProp) {
{posts.map((post) => (
<li key={post.metadata.date}>
<Link to={post.metadata.permalink}>
{post.metadata.formattedDate} - {post.metadata.title}
{formatDate(post.metadata.date)} - {post.metadata.title}
</Link>
</li>
))}

View File

@ -8,15 +8,21 @@
import React from 'react';
import clsx from 'clsx';
import {useBlogPost} from '@docusaurus/theme-common/internal';
import EditThisPage from '@theme/EditThisPage';
import {ThemeClassNames} from '@docusaurus/theme-common';
import EditMetaRow from '@theme/EditMetaRow';
import TagsListInline from '@theme/TagsListInline';
import ReadMoreLink from '@theme/BlogPostItem/Footer/ReadMoreLink';
import styles from './styles.module.css';
export default function BlogPostItemFooter(): JSX.Element | null {
const {metadata, isBlogPostPage} = useBlogPost();
const {tags, title, editUrl, hasTruncateMarker} = metadata;
const {
tags,
title,
editUrl,
hasTruncateMarker,
lastUpdatedBy,
lastUpdatedAt,
} = metadata;
// A post is truncated if it's in the "list view" and it has a truncate marker
const truncatedPost = !isBlogPostPage && hasTruncateMarker;
@ -29,32 +35,56 @@ export default function BlogPostItemFooter(): JSX.Element | null {
return null;
}
return (
<footer
className={clsx(
'row docusaurus-mt-lg',
isBlogPostPage && styles.blogPostFooterDetailsFull,
)}>
{tagsExists && (
<div className={clsx('col', {'col--9': truncatedPost})}>
<TagsListInline tags={tags} />
</div>
)}
// BlogPost footer - details view
if (isBlogPostPage) {
const canDisplayEditMetaRow = !!(editUrl || lastUpdatedAt || lastUpdatedBy);
{isBlogPostPage && editUrl && (
<div className="col margin-top--sm">
<EditThisPage editUrl={editUrl} />
</div>
)}
{truncatedPost && (
<div
className={clsx('col text--right', {
'col--3': tagsExists,
})}>
<ReadMoreLink blogPostTitle={title} to={metadata.permalink} />
</div>
)}
</footer>
);
return (
<footer className="docusaurus-mt-lg">
{tagsExists && (
<div
className={clsx(
'row',
'margin-top--sm',
ThemeClassNames.blog.blogFooterEditMetaRow,
)}>
<div className="col">
<TagsListInline tags={tags} />
</div>
</div>
)}
{canDisplayEditMetaRow && (
<EditMetaRow
className={clsx(
'margin-top--sm',
ThemeClassNames.blog.blogFooterEditMetaRow,
)}
editUrl={editUrl}
lastUpdatedAt={lastUpdatedAt}
lastUpdatedBy={lastUpdatedBy}
/>
)}
</footer>
);
}
// BlogPost footer - list view
else {
return (
<footer className="row docusaurus-mt-lg">
{tagsExists && (
<div className={clsx('col', {'col--9': truncatedPost})}>
<TagsListInline tags={tags} />
</div>
)}
{truncatedPost && (
<div
className={clsx('col text--right', {
'col--3': tagsExists,
})}>
<ReadMoreLink blogPostTitle={title} to={metadata.permalink} />
</div>
)}
</footer>
);
}
}

View File

@ -9,7 +9,10 @@ import React from 'react';
import clsx from 'clsx';
import {translate} from '@docusaurus/Translate';
import {usePluralForm} from '@docusaurus/theme-common';
import {useBlogPost} from '@docusaurus/theme-common/internal';
import {
useBlogPost,
useDateTimeFormat,
} from '@docusaurus/theme-common/internal';
import type {Props} from '@theme/BlogPostItem/Header/Info';
import styles from './styles.module.css';
@ -39,7 +42,13 @@ function ReadingTime({readingTime}: {readingTime: number}) {
return <>{readingTimePlural(readingTime)}</>;
}
function Date({date, formattedDate}: {date: string; formattedDate: string}) {
function DateTime({
date,
formattedDate,
}: {
date: string;
formattedDate: string;
}) {
return <time dateTime={date}>{formattedDate}</time>;
}
@ -51,11 +60,21 @@ export default function BlogPostItemHeaderInfo({
className,
}: Props): JSX.Element {
const {metadata} = useBlogPost();
const {date, formattedDate, readingTime} = metadata;
const {date, readingTime} = metadata;
const dateTimeFormat = useDateTimeFormat({
day: 'numeric',
month: 'long',
year: 'numeric',
timeZone: 'UTC',
});
const formatDate = (blogDate: string) =>
dateTimeFormat.format(new Date(blogDate));
return (
<div className={clsx(styles.container, 'margin-vert--md', className)}>
<Date date={date} formattedDate={formattedDate} />
<DateTime date={date} formattedDate={formatDate(date)} />
{typeof readingTime !== 'undefined' && (
<>
<Spacer />

View File

@ -8,60 +8,14 @@
import React from 'react';
import clsx from 'clsx';
import {ThemeClassNames} from '@docusaurus/theme-common';
import {useDoc, type DocContextValue} from '@docusaurus/theme-common/internal';
import LastUpdated from '@theme/LastUpdated';
import EditThisPage from '@theme/EditThisPage';
import TagsListInline, {
type Props as TagsListInlineProps,
} from '@theme/TagsListInline';
import {useDoc} from '@docusaurus/theme-common/internal';
import TagsListInline from '@theme/TagsListInline';
import styles from './styles.module.css';
function TagsRow(props: TagsListInlineProps) {
return (
<div
className={clsx(
ThemeClassNames.docs.docFooterTagsRow,
'row margin-bottom--sm',
)}>
<div className="col">
<TagsListInline {...props} />
</div>
</div>
);
}
type EditMetaRowProps = Pick<
DocContextValue['metadata'],
'editUrl' | 'lastUpdatedAt' | 'lastUpdatedBy' | 'formattedLastUpdatedAt'
>;
function EditMetaRow({
editUrl,
lastUpdatedAt,
lastUpdatedBy,
formattedLastUpdatedAt,
}: EditMetaRowProps) {
return (
<div className={clsx(ThemeClassNames.docs.docFooterEditMetaRow, 'row')}>
<div className="col">{editUrl && <EditThisPage editUrl={editUrl} />}</div>
<div className={clsx('col', styles.lastUpdated)}>
{(lastUpdatedAt || lastUpdatedBy) && (
<LastUpdated
lastUpdatedAt={lastUpdatedAt}
formattedLastUpdatedAt={formattedLastUpdatedAt}
lastUpdatedBy={lastUpdatedBy}
/>
)}
</div>
</div>
);
}
import EditMetaRow from '@theme/EditMetaRow';
export default function DocItemFooter(): JSX.Element | null {
const {metadata} = useDoc();
const {editUrl, lastUpdatedAt, formattedLastUpdatedAt, lastUpdatedBy, tags} =
metadata;
const {editUrl, lastUpdatedAt, lastUpdatedBy, tags} = metadata;
const canDisplayTagsRow = tags.length > 0;
const canDisplayEditMetaRow = !!(editUrl || lastUpdatedAt || lastUpdatedBy);
@ -75,13 +29,26 @@ export default function DocItemFooter(): JSX.Element | null {
return (
<footer
className={clsx(ThemeClassNames.docs.docFooter, 'docusaurus-mt-lg')}>
{canDisplayTagsRow && <TagsRow tags={tags} />}
{canDisplayTagsRow && (
<div
className={clsx(
'row margin-top--sm',
ThemeClassNames.docs.docFooterTagsRow,
)}>
<div className="col">
<TagsListInline tags={tags} />
</div>
</div>
)}
{canDisplayEditMetaRow && (
<EditMetaRow
className={clsx(
'margin-top--sm',
ThemeClassNames.docs.docFooterEditMetaRow,
)}
editUrl={editUrl}
lastUpdatedAt={lastUpdatedAt}
lastUpdatedBy={lastUpdatedBy}
formattedLastUpdatedAt={formattedLastUpdatedAt}
/>
)}
</footer>

View File

@ -101,6 +101,7 @@ function CollapseButton({
{label: categoryLabel},
)
}
aria-expanded={!collapsed}
type="button"
className="clean-btn menu__caret"
onClick={onClick}
@ -193,7 +194,8 @@ export default function DocSidebarItemCategory({
}
}
aria-current={isCurrentPage ? 'page' : undefined}
aria-expanded={collapsible ? !collapsed : undefined}
role={collapsible && !href ? 'button' : undefined}
aria-expanded={collapsible && !href ? !collapsed : undefined}
href={collapsible ? hrefWithSSRFallback ?? '#' : hrefWithSSRFallback}
{...props}>
{label}

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import clsx from 'clsx';
import EditThisPage from '@theme/EditThisPage';
import type {Props} from '@theme/EditMetaRow';
import LastUpdated from '@theme/LastUpdated';
import styles from './styles.module.css';
export default function EditMetaRow({
className,
editUrl,
lastUpdatedAt,
lastUpdatedBy,
}: Props): JSX.Element {
return (
<div className={clsx('row', className)}>
<div className="col">{editUrl && <EditThisPage editUrl={editUrl} />}</div>
<div className={clsx('col', styles.lastUpdated)}>
{(lastUpdatedAt || lastUpdatedBy) && (
<LastUpdated
lastUpdatedAt={lastUpdatedAt}
lastUpdatedBy={lastUpdatedBy}
/>
)}
</div>
</div>
);
}

View File

@ -6,9 +6,9 @@
*/
.lastUpdated {
margin-top: 0.2rem;
font-style: italic;
font-size: smaller;
font-style: italic;
margin-top: 0.2rem;
}
@media (min-width: 997px) {

View File

@ -8,15 +8,25 @@
import React from 'react';
import Translate from '@docusaurus/Translate';
import {ThemeClassNames} from '@docusaurus/theme-common';
import {useDateTimeFormat} from '@docusaurus/theme-common/internal';
import type {Props} from '@theme/LastUpdated';
function LastUpdatedAtDate({
lastUpdatedAt,
formattedLastUpdatedAt,
}: {
lastUpdatedAt: number;
formattedLastUpdatedAt: string;
}): JSX.Element {
const atDate = new Date(lastUpdatedAt);
const dateTimeFormat = useDateTimeFormat({
day: 'numeric',
month: 'short',
year: 'numeric',
timeZone: 'UTC',
});
const formattedLastUpdatedAt = dateTimeFormat.format(atDate);
return (
<Translate
id="theme.lastUpdated.atDate"
@ -24,7 +34,7 @@ function LastUpdatedAtDate({
values={{
date: (
<b>
<time dateTime={new Date(lastUpdatedAt * 1000).toISOString()}>
<time dateTime={atDate.toISOString()} itemProp="dateModified">
{formattedLastUpdatedAt}
</time>
</b>
@ -54,7 +64,6 @@ function LastUpdatedByUser({
export default function LastUpdated({
lastUpdatedAt,
formattedLastUpdatedAt,
lastUpdatedBy,
}: Props): JSX.Element {
return (
@ -63,15 +72,11 @@ export default function LastUpdated({
id="theme.lastUpdated.lastUpdatedAtBy"
description="The sentence used to display when a page has been last updated, and by who"
values={{
atDate:
lastUpdatedAt && formattedLastUpdatedAt ? (
<LastUpdatedAtDate
lastUpdatedAt={lastUpdatedAt}
formattedLastUpdatedAt={formattedLastUpdatedAt}
/>
) : (
''
),
atDate: lastUpdatedAt ? (
<LastUpdatedAtDate lastUpdatedAt={lastUpdatedAt} />
) : (
''
),
byUser: lastUpdatedBy ? (
<LastUpdatedByUser lastUpdatedBy={lastUpdatedBy} />
) : (

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-common",
"version": "3.0.0",
"version": "3.2.0",
"description": "Common code for Docusaurus themes.",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
@ -30,13 +30,13 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/plugin-content-blog": "3.0.0",
"@docusaurus/plugin-content-docs": "3.0.0",
"@docusaurus/plugin-content-pages": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/mdx-loader": "3.2.0",
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/plugin-content-blog": "3.2.0",
"@docusaurus/plugin-content-docs": "3.2.0",
"@docusaurus/plugin-content-pages": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-common": "3.2.0",
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router-config": "*",
@ -47,8 +47,8 @@
"utility-types": "^3.10.0"
},
"devDependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/core": "3.2.0",
"@docusaurus/types": "3.2.0",
"fs-extra": "^11.1.1",
"lodash": "^4.17.21",
"schema-dts": "^1.1.2"

View File

@ -114,6 +114,7 @@ export {
} from './hooks/useTOCHighlight';
export {useVisibleBlogSidebarItems} from './utils/blogUtils';
export {useDateTimeFormat} from './utils/IntlUtils';
export {useHideableNavbar} from './hooks/useHideableNavbar';
export {

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
export function useCalendar(): string {
const {
i18n: {currentLocale, localeConfigs},
} = useDocusaurusContext();
return localeConfigs[currentLocale]!.calendar;
}
export function useDateTimeFormat(
options: Intl.DateTimeFormatOptions = {},
): Intl.DateTimeFormat {
const {
i18n: {currentLocale},
} = useDocusaurusContext();
const calendar = useCalendar();
return new Intl.DateTimeFormat(currentLocale, {
calendar,
...options,
});
}

View File

@ -73,5 +73,7 @@ export const ThemeClassNames = {
},
blog: {
// TODO add other stable classNames here
blogFooterTagsRow: 'theme-blog-footer-tags-row',
blogFooterEditMetaRow: 'theme-blog-footer-edit-meta-row',
},
} as const;

View File

@ -23,19 +23,23 @@ import type {
} from '@docusaurus/plugin-content-blog';
import type {DocusaurusConfig} from '@docusaurus/types';
const convertDate = (dateMs: number) => new Date(dateMs).toISOString();
function getBlogPost(
blogPostContent: PropBlogPostContent,
siteConfig: DocusaurusConfig,
withBaseUrl: BaseUrlUtils['withBaseUrl'],
) {
): BlogPosting {
const {assets, frontMatter, metadata} = blogPostContent;
const {date, title, description} = metadata;
const {date, title, description, lastUpdatedAt} = metadata;
const image = assets.image ?? frontMatter.image;
const keywords = frontMatter.keywords ?? [];
const blogUrl = `${siteConfig.url}${metadata.permalink}`;
const dateModified = lastUpdatedAt ? convertDate(lastUpdatedAt) : undefined;
return {
'@type': 'BlogPosting',
'@id': blogUrl,
@ -45,6 +49,7 @@ function getBlogPost(
name: title,
description,
datePublished: date,
...(dateModified ? {dateModified} : {}),
...getAuthor(metadata.authors),
...getImage(image, withBaseUrl, title),
...(keywords ? {keywords} : {}),
@ -108,11 +113,13 @@ export function useBlogPostStructuredData(): WithContext<BlogPosting> {
const {siteConfig} = useDocusaurusContext();
const {withBaseUrl} = useBaseUrlUtils();
const {date, title, description, frontMatter} = metadata;
const {date, title, description, frontMatter, lastUpdatedAt} = metadata;
const image = assets.image ?? frontMatter.image;
const keywords = frontMatter.keywords ?? [];
const dateModified = lastUpdatedAt ? convertDate(lastUpdatedAt) : undefined;
const url = `${siteConfig.url}${metadata.permalink}`;
// details on structured data support: https://schema.org/BlogPosting
@ -128,6 +135,7 @@ export function useBlogPostStructuredData(): WithContext<BlogPosting> {
name: title,
description,
datePublished: date,
...(dateModified ? {dateModified} : {}),
...getAuthor(metadata.authors),
...getImage(image, withBaseUrl, title),
...(keywords ? {keywords} : {}),

View File

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

View File

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

View File

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

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