Merge branch 'main' of github.com:facebook/docusaurus into lex111/filter-sidebar

This commit is contained in:
Alexey Pyltsyn 2022-03-28 21:38:50 +03:00
commit 2bc3b28cec
543 changed files with 11307 additions and 9410 deletions

View file

@ -1,10 +1,33 @@
{
"name": "Docusaurus Dev Container",
"image": "mcr.microsoft.com/vscode/devcontainers/typescript-node:14-buster",
"image": "mcr.microsoft.com/vscode/devcontainers/base:ubuntu-20.04",
"settings": {
"terminal.integrated.shell.linux": "/bin/bash"
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}
},
"extensions": ["dbaeumer.vscode-eslint", "orta.vscode-jest"],
"extensions": [
"dbaeumer.vscode-eslint",
"orta.vscode-jest",
"esbenp.prettier-vscode",
"streetsidesoftware.code-spell-checker"
],
"forwardPorts": [3000],
"postCreateCommand": "yarn install"
"containerUser": "vscode",
"postCreateCommand": "yarn install",
"waitFor": "postCreateCommand", // otherwise automated jest tests fail
"features": {
"node": {
"version": "14"
},
"github-cli": "latest"
}
}

View file

@ -49,6 +49,7 @@ module.exports = {
curly: [WARNING, 'all'],
'global-require': WARNING,
'lines-between-class-members': OFF,
'max-classes-per-file': OFF,
'max-len': [
WARNING,
{
@ -201,7 +202,13 @@ module.exports = {
'import/no-unresolved': [
ERROR,
{
ignore: ['^@theme', '^@docusaurus', '^@generated', '^@site'],
ignore: [
'^@theme',
'^@docusaurus',
'^@generated',
'^@site',
'^@testing-utils',
],
},
],
'import/order': OFF,
@ -262,6 +269,10 @@ module.exports = {
ERROR,
{'ts-expect-error': 'allow-with-description'},
],
'@typescript-eslint/consistent-indexed-object-style': [
WARNING,
'index-signature',
],
'@typescript-eslint/consistent-type-imports': [
WARNING,
{disallowTypeAnnotations: false},

View file

@ -1,5 +1,224 @@
# Docusaurus 2 Changelog
## 2.0.0-beta.18 (2022-03-25)
#### :rocket: New Feature
- `docusaurus-mdx-loader`, `docusaurus-theme-classic`
- [#6990](https://github.com/facebook/docusaurus/pull/6990) feat: lazy-load external images + ability to customize image display ([@slorber](https://github.com/slorber))
- `docusaurus-module-type-aliases`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-types`, `docusaurus`
- [#6933](https://github.com/facebook/docusaurus/pull/6933) feat(core,theme): useRouteContext + HtmlClassNameProvider ([@slorber](https://github.com/slorber))
- `docusaurus-plugin-debug`, `docusaurus-plugin-google-analytics`, `docusaurus-plugin-google-gtag`, `docusaurus-plugin-ideal-image`, `docusaurus-plugin-pwa`, `docusaurus-theme-classic`, `docusaurus-theme-live-codeblock`, `docusaurus-theme-search-algolia`, `docusaurus-types`, `docusaurus`
- [#6921](https://github.com/facebook/docusaurus/pull/6921) feat(core): allow plugin lifecycles to return relative paths ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-theme-classic`
- [#6697](https://github.com/facebook/docusaurus/pull/6697) feat: add SEO microdata for doc breadcrumbs ([@Josh-Cena](https://github.com/Josh-Cena))
- [#6842](https://github.com/facebook/docusaurus/pull/6842) feat(theme-classic): MDXContent wrapper component ([@slorber](https://github.com/slorber))
- `docusaurus-plugin-content-docs`
- [#6780](https://github.com/facebook/docusaurus/pull/6780) feat(content-docs): allow custom props through _category_.json ([@taejs](https://github.com/taejs))
#### :boom: Breaking Change
- `docusaurus-plugin-content-docs`
- [#6859](https://github.com/facebook/docusaurus/pull/6859) feat(content-docs): autogenerate category with linked doc metadata as fallback ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-theme-classic`
- [#6989](https://github.com/facebook/docusaurus/pull/6989) refactor: extract MDX components ([@slorber](https://github.com/slorber))
- `docusaurus-module-type-aliases`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus`
- [#6925](https://github.com/facebook/docusaurus/pull/6925) refactor(theme-{classic,common}): refactor site/page/search metadata + apply className on html element ([@slorber](https://github.com/slorber))
- `docusaurus-theme-classic`, `docusaurus-theme-common`
- [#6895](https://github.com/facebook/docusaurus/pull/6895) refactor(theme-{classic,common}): split navbar into smaller components + cleanup + swizzle config ([@slorber](https://github.com/slorber))
- [#6930](https://github.com/facebook/docusaurus/pull/6930) refactor(theme-{classic,common}): refactor ColorModeToggle + useColorMode() hook ([@lex111](https://github.com/lex111))
#### :bug: Bug Fix
- `docusaurus`
- [#6993](https://github.com/facebook/docusaurus/pull/6993) fix(core): prevent useBaseUrl returning /base/base when on /base ([@Josh-Cena](https://github.com/Josh-Cena))
- [#6936](https://github.com/facebook/docusaurus/pull/6936) fix: remove semicolon from HTML output ([@lex111](https://github.com/lex111))
- [#6849](https://github.com/facebook/docusaurus/pull/6849) fix(cli): write-heading-id should not generate colliding slugs when not overwriting ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-theme-classic`
- [#6983](https://github.com/facebook/docusaurus/pull/6983) fix(search): bump Infima, fix search issue due to broken CSS selector ([@slorber](https://github.com/slorber))
- `docusaurus-utils-validation`
- [#6977](https://github.com/facebook/docusaurus/pull/6977) fix(validation): allow non-object params to remark/rehype plugins ([@aloisklink](https://github.com/aloisklink))
- `docusaurus-plugin-content-docs`, `docusaurus-utils`
- [#6973](https://github.com/facebook/docusaurus/pull/6973) fix(content-docs): suppress git error on multiple occurrences ([@felipecrs](https://github.com/felipecrs))
- `docusaurus-plugin-content-blog`
- [#6947](https://github.com/facebook/docusaurus/pull/6947) fix(content-blog): only create archive route if there are blog posts ([@Josh-Cena](https://github.com/Josh-Cena))
- [#6918](https://github.com/facebook/docusaurus/pull/6918) fix(content-blog): remove double leading slash in blog-only paginated view ([@heowc](https://github.com/heowc))
- `docusaurus-theme-search-algolia`
- [#6888](https://github.com/facebook/docusaurus/pull/6888) fix(theme-algolia): declare content-docs as dependency ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-theme-translations`
- [#6847](https://github.com/facebook/docusaurus/pull/6847) fix: minor Chinese translation fixes ([@rccttwd](https://github.com/rccttwd))
#### :nail_care: Polish
- `docusaurus-plugin-content-docs`
- [#6859](https://github.com/facebook/docusaurus/pull/6859) feat(content-docs): autogenerate category with linked doc metadata as fallback ([@Josh-Cena](https://github.com/Josh-Cena))
- [#6887](https://github.com/facebook/docusaurus/pull/6887) fix(content-docs): give context about sidebar loading failure ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-plugin-content-docs`, `docusaurus-utils-validation`, `docusaurus`
- [#6997](https://github.com/facebook/docusaurus/pull/6997) fix(validation): improve error messages for a few schemas ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-theme-classic`
- [#6971](https://github.com/facebook/docusaurus/pull/6971) refactor: improve a11y of dropdown menu ([@lex111](https://github.com/lex111))
- [#6987](https://github.com/facebook/docusaurus/pull/6987) refactor(theme-classic): cleanup of code blocks ([@lex111](https://github.com/lex111))
- [#6950](https://github.com/facebook/docusaurus/pull/6950) refactor(theme-classic): clean up CSS of doc cards ([@lex111](https://github.com/lex111))
- [#6994](https://github.com/facebook/docusaurus/pull/6994) refactor: better external link icon positioning ([@lex111](https://github.com/lex111))
- [#6989](https://github.com/facebook/docusaurus/pull/6989) refactor: extract MDX components ([@slorber](https://github.com/slorber))
- [#6985](https://github.com/facebook/docusaurus/pull/6985) refactor(theme-classic): remove span wrappers from layout links ([@lex111](https://github.com/lex111))
- [#6986](https://github.com/facebook/docusaurus/pull/6986) fix(theme-classic): minor code copy button improvements ([@Josh-Cena](https://github.com/Josh-Cena))
- [#6964](https://github.com/facebook/docusaurus/pull/6964) refactor: replace text-based copy code button with icons ([@lex111](https://github.com/lex111))
- [#6932](https://github.com/facebook/docusaurus/pull/6932) refactor(theme-classic): little breadcrumbs improvements ([@lex111](https://github.com/lex111))
- [#6914](https://github.com/facebook/docusaurus/pull/6914) feat(theme-classic): set aria-expanded on expandable sidebar categories ([@pkowaluk](https://github.com/pkowaluk))
- [#6844](https://github.com/facebook/docusaurus/pull/6844) refactor(theme-classic): split sidebar into smaller parts ([@slorber](https://github.com/slorber))
- [#6846](https://github.com/facebook/docusaurus/pull/6846) refactor(theme-classic): consistently add span wrapper for layout links ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`
- [#6980](https://github.com/facebook/docusaurus/pull/6980) feat(utils): JSDoc for all APIs ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-theme-common`
- [#6974](https://github.com/facebook/docusaurus/pull/6974) feat(theme-common): JSDoc for all APIs ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus`
- [#6784](https://github.com/facebook/docusaurus/pull/6784) feat(core): allow configureWebpack to return undefined ([@yorkie](https://github.com/yorkie))
- [#6941](https://github.com/facebook/docusaurus/pull/6941) refactor(core): improve error message when a page has no default-export ([@Josh-Cena](https://github.com/Josh-Cena))
- [#6878](https://github.com/facebook/docusaurus/pull/6878) fix(core): ensure stable webpack theme aliases sorting ([@jrvidal](https://github.com/jrvidal))
- [#6854](https://github.com/facebook/docusaurus/pull/6854) fix(core): fix swizzle legend typo ([@DigiPie](https://github.com/DigiPie))
- [#6850](https://github.com/facebook/docusaurus/pull/6850) fix(core): make plugin lifecycles consistently bound to the plugin instance ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-utils`
- [#6937](https://github.com/facebook/docusaurus/pull/6937) fix(content-docs): warn when files are not tracked ([@felipecrs](https://github.com/felipecrs))
- `docusaurus-module-type-aliases`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus`
- [#6925](https://github.com/facebook/docusaurus/pull/6925) refactor(theme-{classic,common}): refactor site/page/search metadata + apply className on html element ([@slorber](https://github.com/slorber))
- `docusaurus-theme-classic`, `docusaurus-theme-common`
- [#6895](https://github.com/facebook/docusaurus/pull/6895) refactor(theme-{classic,common}): split navbar into smaller components + cleanup + swizzle config ([@slorber](https://github.com/slorber))
- [#6930](https://github.com/facebook/docusaurus/pull/6930) refactor(theme-{classic,common}): refactor ColorModeToggle + useColorMode() hook ([@lex111](https://github.com/lex111))
- [#6894](https://github.com/facebook/docusaurus/pull/6894) refactor(theme-classic): split theme footer into smaller components + swizzle config ([@slorber](https://github.com/slorber))
- `docusaurus-types`, `docusaurus`
- [#6929](https://github.com/facebook/docusaurus/pull/6929) refactor(core): minor routes type improvement ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-plugin-client-redirects`, `docusaurus-plugin-ideal-image`, `docusaurus-plugin-pwa`, `docusaurus-plugin-sitemap`
- [#6928](https://github.com/facebook/docusaurus/pull/6928) chore(pwa, sitemap, client-redirects, ideal-image): JSDoc for types ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-plugin-content-blog`, `docusaurus-theme-classic`, `docusaurus-utils`
- [#6922](https://github.com/facebook/docusaurus/pull/6922) refactor(content-blog): clean up type definitions; in-code documentation ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-theme-translations`
- [#6781](https://github.com/facebook/docusaurus/pull/6781) feat(theme-translations): complete Russian translations ([@dragomano](https://github.com/dragomano))
- [#6877](https://github.com/facebook/docusaurus/pull/6877) chore(theme-translations): complete Vietnamese translations ([@datlechin](https://github.com/datlechin))
- `docusaurus-plugin-content-blog`
- [#6909](https://github.com/facebook/docusaurus/pull/6909) refactor(content-blog): improve error message of authors map validation ([@Josh-Cena](https://github.com/Josh-Cena))
- `create-docusaurus`
- [#6860](https://github.com/facebook/docusaurus/pull/6860) fix(create): load entry file after node version checking ([@taejs](https://github.com/taejs))
#### :memo: Documentation
- Other
- [#6988](https://github.com/facebook/docusaurus/pull/6988) docs: fix example admonition syntax ([@kaycebasques](https://github.com/kaycebasques))
- [#6978](https://github.com/facebook/docusaurus/pull/6978) docs: npm run tsc -> npx tsc ([@jadonn](https://github.com/jadonn))
- [#6952](https://github.com/facebook/docusaurus/pull/6952) docs: add K3ai to showcase ([@alefesta](https://github.com/alefesta))
- [#6948](https://github.com/facebook/docusaurus/pull/6948) docs: add pdfme docs to showcase ([@hand-dot](https://github.com/hand-dot))
- [#6943](https://github.com/facebook/docusaurus/pull/6943) docs: add SeaORM docs to showcase ([@billy1624](https://github.com/billy1624))
- [#6926](https://github.com/facebook/docusaurus/pull/6926) docs: clarify the usage of slug ([@kaycebasques](https://github.com/kaycebasques))
- [#6911](https://github.com/facebook/docusaurus/pull/6911) docs: add Reactive Button site to showcase ([@arifszn](https://github.com/arifszn))
- [#6904](https://github.com/facebook/docusaurus/pull/6904) docs: update image for digital support services ([@PatelN123](https://github.com/PatelN123))
- [#6892](https://github.com/facebook/docusaurus/pull/6892) docs: add EduLinks site to showcase ([@odarpi](https://github.com/odarpi))
- [#6889](https://github.com/facebook/docusaurus/pull/6889) docs: editorial fixes ([@Josh-Cena](https://github.com/Josh-Cena))
- [#6883](https://github.com/facebook/docusaurus/pull/6883) docs(cli): add info about development on github codespaces ([@vedantmgoyal2009](https://github.com/vedantmgoyal2009))
- [#6856](https://github.com/facebook/docusaurus/pull/6856) docs: add Reddit Image Fetcher site to showcase ([@arifszn](https://github.com/arifszn))
- [#6875](https://github.com/facebook/docusaurus/pull/6875) docs: update TRPG Engine showcase ([@moonrailgun](https://github.com/moonrailgun))
- [#6871](https://github.com/facebook/docusaurus/pull/6871) docs: mark clutch and gulp as open-source ([@Josh-Cena](https://github.com/Josh-Cena))
- [#6862](https://github.com/facebook/docusaurus/pull/6862) docs: update showcase data ([@Josh-Cena](https://github.com/Josh-Cena))
- [#6837](https://github.com/facebook/docusaurus/pull/6837) docs: add PcapPlusPlus to showcase ([@seladb](https://github.com/seladb))
- [#6832](https://github.com/facebook/docusaurus/pull/6832) docs: add Spicetify site to showcase ([@afonsojramos](https://github.com/afonsojramos))
- [#6830](https://github.com/facebook/docusaurus/pull/6830) docs: simplify imported code blocks syntax ([@nathan-contino-mongo](https://github.com/nathan-contino-mongo))
- `docusaurus-types`
- [#6881](https://github.com/facebook/docusaurus/pull/6881) docs: mention configureWebpack devServer return value ([@Josh-Cena](https://github.com/Josh-Cena))
- `create-docusaurus`
- [#6833](https://github.com/facebook/docusaurus/pull/6833) docs: make tutorial code block directly copyable ([@samgutentag](https://github.com/samgutentag))
#### :house: Internal
- `create-docusaurus`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-module-type-aliases`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-debug`, `docusaurus-plugin-google-gtag`, `docusaurus-plugin-ideal-image`, `docusaurus-remark-plugin-npm2yarn`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus-theme-translations`, `docusaurus-types`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`, `lqip-loader`
- [#6995](https://github.com/facebook/docusaurus/pull/6995) refactor: ensure all types are using index signature instead of Record ([@Josh-Cena](https://github.com/Josh-Cena))
- `create-docusaurus`, `docusaurus-cssnano-preset`, `docusaurus-plugin-pwa`, `docusaurus-theme-search-algolia`, `docusaurus-utils`, `docusaurus`, `lqip-loader`
- [#6991](https://github.com/facebook/docusaurus/pull/6991) chore: upgrade dependencies ([@Josh-Cena](https://github.com/Josh-Cena))
- `lqip-loader`
- [#6992](https://github.com/facebook/docusaurus/pull/6992) refactor(lqip-loader): remove unused palette option ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus`
- [#6975](https://github.com/facebook/docusaurus/pull/6975) chore: update static-site-generator-webpack-plugin ([@slorber](https://github.com/slorber))
- `stylelint-copyright`
- [#6967](https://github.com/facebook/docusaurus/pull/6967) chore: publish stylelint-copyright again ([@slorber](https://github.com/slorber))
- `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-google-analytics`, `docusaurus-plugin-google-gtag`, `docusaurus-plugin-ideal-image`, `docusaurus-plugin-pwa`, `docusaurus-plugin-sitemap`, `docusaurus-theme-classic`, `docusaurus-theme-live-codeblock`, `docusaurus-theme-search-algolia`, `docusaurus-types`, `docusaurus-utils-validation`, `docusaurus`
- [#6961](https://github.com/facebook/docusaurus/pull/6961) refactor: unify how validateOptions is handled ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-types`
- [#6957](https://github.com/facebook/docusaurus/pull/6957) chore(types): remove querystring from dependencies ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-theme-common`, `docusaurus`
- [#6956](https://github.com/facebook/docusaurus/pull/6956) test: improve test coverage; reorder theme-common files ([@Josh-Cena](https://github.com/Josh-Cena))
- [#6955](https://github.com/facebook/docusaurus/pull/6955) refactor(core): move browserContext and docusaurusContext out of client exports ([@Josh-Cena](https://github.com/Josh-Cena))
- [#6944](https://github.com/facebook/docusaurus/pull/6944) chore: migrate Jest and website to SWC ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-utils`
- [#6951](https://github.com/facebook/docusaurus/pull/6951) test: fix Windows test for gitUtils ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-debug`, `docusaurus-plugin-pwa`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-translations`, `docusaurus-utils`, `docusaurus`, `stylelint-copyright`
- [#6931](https://github.com/facebook/docusaurus/pull/6931) chore: tighten ESLint config ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-module-type-aliases`, `docusaurus-plugin-client-redirects`
- [#6924](https://github.com/facebook/docusaurus/pull/6924) refactor(client-redirects): migrate validation to validateOptions lifecycle ([@Josh-Cena](https://github.com/Josh-Cena))
- `create-docusaurus`, `docusaurus-cssnano-preset`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-ideal-image`, `docusaurus-plugin-pwa`, `docusaurus-theme-classic`, `docusaurus-theme-search-algolia`, `docusaurus-utils`, `docusaurus`, `lqip-loader`
- [#6916](https://github.com/facebook/docusaurus/pull/6916) chore: upgrade dependencies ([@Josh-Cena](https://github.com/Josh-Cena))
- `create-docusaurus`, `docusaurus-plugin-content-docs`, `docusaurus-theme-translations`, `docusaurus-types`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`, `stylelint-copyright`
- [#6912](https://github.com/facebook/docusaurus/pull/6912) test: improve test coverage; multiple internal refactors ([@Josh-Cena](https://github.com/Josh-Cena))
- Other
- [#6910](https://github.com/facebook/docusaurus/pull/6910) refactor: convert Jest infrastructure to TS ([@Josh-Cena](https://github.com/Josh-Cena))
- [#6838](https://github.com/facebook/docusaurus/pull/6838) fix(website): changelog plugin leads to CI bugs on release ([@slorber](https://github.com/slorber))
- `docusaurus-logger`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-utils`, `docusaurus`
- [#6908](https://github.com/facebook/docusaurus/pull/6908) chore: do not print prototype in jest snapshot ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-migrate`, `docusaurus-plugin-content-docs`, `docusaurus-theme-common`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`
- [#6906](https://github.com/facebook/docusaurus/pull/6906) refactor: install eslint-plugin-regexp ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-mdx-loader`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-docs`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus-utils`, `docusaurus`
- [#6905](https://github.com/facebook/docusaurus/pull/6905) test: improve test coverage; properly test core client APIs ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-logger`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-sitemap`, `docusaurus-remark-plugin-npm2yarn`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-live-codeblock`, `docusaurus-theme-translations`, `docusaurus-utils`, `docusaurus`
- [#6903](https://github.com/facebook/docusaurus/pull/6903) chore: spell-check test files ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-migrate`, `docusaurus-module-type-aliases`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-common`, `docusaurus-types`, `docusaurus-utils-common`, `docusaurus-utils`, `docusaurus`, `lqip-loader`
- [#6902](https://github.com/facebook/docusaurus/pull/6902) test(theme-common): improve test coverage ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-cssnano-preset`, `docusaurus-logger`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-sitemap`, `docusaurus-remark-plugin-npm2yarn`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-live-codeblock`, `docusaurus-theme-search-algolia`, `docusaurus-theme-translations`, `docusaurus-utils-common`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`, `lqip-loader`, `stylelint-copyright`
- [#6900](https://github.com/facebook/docusaurus/pull/6900) test: enable a few jest eslint rules ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-logger`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-translations`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`, `lqip-loader`
- [#6898](https://github.com/facebook/docusaurus/pull/6898) refactor: import jest as global; unify import style of some modules ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-theme-classic`, `docusaurus-theme-common`
- [#6891](https://github.com/facebook/docusaurus/pull/6891) refactor(theme-classic): avoid using clsx class dict with CSS modules ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-translations`, `docusaurus-utils`, `docusaurus`
- [#6880](https://github.com/facebook/docusaurus/pull/6880) refactor: prefer fs.outputFile to ensureDir + writeFile ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-plugin-pwa`, `docusaurus-types`, `docusaurus`
- [#6866](https://github.com/facebook/docusaurus/pull/6866) refactor: improve types ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-mdx-loader`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-pwa`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-types`, `docusaurus`, `lqip-loader`
- [#6864](https://github.com/facebook/docusaurus/pull/6864) refactor: remove unnecessary default values normalized during validation ([@Josh-Cena](https://github.com/Josh-Cena))
- `create-docusaurus`, `docusaurus-migrate`, `docusaurus`
- [#6861](https://github.com/facebook/docusaurus/pull/6861) refactor: make JS executables included in the tsconfig for editor hints ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-types`, `docusaurus`
- [#6857](https://github.com/facebook/docusaurus/pull/6857) test: improve test coverage ([@Josh-Cena](https://github.com/Josh-Cena))
- `docusaurus-logger`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-remark-plugin-npm2yarn`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus-utils-common`, `docusaurus-utils`, `docusaurus`, `lqip-loader`
- [#6852](https://github.com/facebook/docusaurus/pull/6852) refactor: enable a few TS flags ([@Josh-Cena](https://github.com/Josh-Cena))
#### Committers: 28
- Afonso Jorge Ramos ([@afonsojramos](https://github.com/afonsojramos))
- Alessandro Festa ([@alefesta](https://github.com/alefesta))
- Alexey Pyltsyn ([@lex111](https://github.com/lex111))
- Alois Klink ([@aloisklink](https://github.com/aloisklink))
- Ariful Alam ([@arifszn](https://github.com/arifszn))
- Begula ([@vedantmgoyal2009](https://github.com/vedantmgoyal2009))
- Billy Chan ([@billy1624](https://github.com/billy1624))
- Bugo ([@dragomano](https://github.com/dragomano))
- Evan ([@DigiPie](https://github.com/DigiPie))
- Felipe Santos ([@felipecrs](https://github.com/felipecrs))
- Jadon N ([@jadonn](https://github.com/jadonn))
- Joshua Chen ([@Josh-Cena](https://github.com/Josh-Cena))
- Kayce Basques ([@kaycebasques](https://github.com/kaycebasques))
- Kyohei Fukuda ([@hand-dot](https://github.com/hand-dot))
- Nayan Patel ([@PatelN123](https://github.com/PatelN123))
- Ngô Quốc Đạt ([@datlechin](https://github.com/datlechin))
- Odarpi ([@odarpi](https://github.com/odarpi))
- Pawel Kowaluk ([@pkowaluk](https://github.com/pkowaluk))
- Roberto Vidal ([@jrvidal](https://github.com/jrvidal))
- Sam Gutentag ([@samgutentag](https://github.com/samgutentag))
- Sébastien Lorber ([@slorber](https://github.com/slorber))
- Tsz W. TAM ([@rccttwd](https://github.com/rccttwd))
- WonChul Heo ([@heowc](https://github.com/heowc))
- Yorkie Liu ([@yorkie](https://github.com/yorkie))
- [@seladb](https://github.com/seladb)
- moonrailgun ([@moonrailgun](https://github.com/moonrailgun))
- nate contino ([@nathan-contino-mongo](https://github.com/nathan-contino-mongo))
- tae ([@taejs](https://github.com/taejs))
## 2.0.0-beta.17 (2022-03-03)
#### :rocket: New Feature

View file

@ -26,9 +26,11 @@ const PlaygroundDocumentationUrl = 'https://docusaurus.io/docs/playground';
export type PlaygroundName = keyof typeof PlaygroundConfigs;
function isValidPlaygroundName(
playgroundName: string,
playgroundName: string | undefined,
): playgroundName is PlaygroundName {
return Object.keys(PlaygroundConfigs).includes(playgroundName);
return (
!!playgroundName && Object.keys(PlaygroundConfigs).includes(playgroundName)
);
}
export function createPlaygroundDocumentationResponse(): HandlerResponse {
@ -54,10 +56,10 @@ export function createPlaygroundResponse(
}
// Inspired by https://stackoverflow.com/a/3409200/82609
function parseCookieString(cookieString: string): Record<string, string> {
const result: Record<string, string> = {};
function parseCookieString(cookieString: string): {[key: string]: string} {
const result: {[key: string]: string} = {};
cookieString.split(';').forEach((cookie) => {
const [name, value] = cookie.split('=');
const [name, value] = cookie.split('=') as [string, string];
result[name.trim()] = decodeURI(value);
});
return result;
@ -66,7 +68,7 @@ function parseCookieString(cookieString: string): Record<string, string> {
export function readPlaygroundName(
event: HandlerEvent,
): PlaygroundName | undefined {
const parsedCookie: Record<string, string> = event.headers.cookie
const parsedCookie: {[key: string]: string} = event.headers.cookie
? parseCookieString(event.headers.cookie)
: {};
const playgroundName: string | undefined = parsedCookie[CookieName];

View file

@ -1,6 +1,6 @@
{
"name": "new.docusaurus.io",
"version": "2.0.0-beta.17",
"version": "2.0.0-beta.18",
"private": true,
"scripts": {
"start": "netlify dev"
@ -9,6 +9,6 @@
"@netlify/functions": "^1.0.0"
},
"devDependencies": {
"netlify-cli": "^9.13.1"
"netlify-cli": "^9.13.4"
}
}

View file

@ -41,14 +41,14 @@ This is my **first Docusaurus document**!
It is also possible to create your sidebar explicitly in `sidebars.js`:
```diff title="sidebars.js"
```js title="sidebars.js"
module.exports = {
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
- items: [...],
+ items: ['hello'],
// highlight-next-line
items: ['hello'],
},
],
};

View file

@ -16,18 +16,18 @@
"dev": "docusaurus start"
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.17",
"@docusaurus/preset-classic": "2.0.0-beta.17",
"@docusaurus/core": "2.0.0-beta.18",
"@docusaurus/preset-classic": "2.0.0-beta.18",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.1.1",
"prism-react-renderer": "^1.2.1",
"react": "^17.0.1",
"react-dom": "^17.0.1"
"prism-react-renderer": "^1.3.1",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-beta.17",
"@tsconfig/docusaurus": "^1.0.4",
"typescript": "^4.6.2"
"@docusaurus/module-type-aliases": "2.0.0-beta.18",
"@tsconfig/docusaurus": "^1.0.5",
"typescript": "^4.6.3"
},
"browserslist": {
"production": [

File diff suppressed because it is too large Load diff

View file

@ -41,14 +41,14 @@ This is my **first Docusaurus document**!
It is also possible to create your sidebar explicitly in `sidebars.js`:
```diff title="sidebars.js"
```js title="sidebars.js"
module.exports = {
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
- items: [...],
+ items: ['hello'],
// highlight-next-line
items: ['hello'],
},
],
};

View file

@ -15,13 +15,13 @@
"dev": "docusaurus start"
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.17",
"@docusaurus/preset-classic": "2.0.0-beta.17",
"@docusaurus/core": "2.0.0-beta.18",
"@docusaurus/preset-classic": "2.0.0-beta.18",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.1.1",
"prism-react-renderer": "^1.2.1",
"react": "^17.0.1",
"react-dom": "^17.0.1"
"prism-react-renderer": "^1.3.1",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"browserslist": {
"production": [

File diff suppressed because it is too large Load diff

View file

@ -41,14 +41,14 @@ This is my **first Docusaurus document**!
It is also possible to create your sidebar explicitly in `sidebars.js`:
```diff title="sidebars.js"
```js title="sidebars.js"
module.exports = {
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
- items: [...],
+ items: ['hello'],
// highlight-next-line
items: ['hello'],
},
],
};

View file

@ -19,25 +19,25 @@
"dev": "docusaurus start"
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.17",
"@docusaurus/preset-classic": "2.0.0-beta.17",
"@docusaurus/core": "2.0.0-beta.18",
"@docusaurus/preset-classic": "2.0.0-beta.18",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.1.1",
"react": "^17.0.1",
"react-dom": "^17.0.1"
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@babel/eslint-parser": "^7.16.3",
"eslint": "^8.8.0",
"eslint-config-airbnb": "^19.0.0",
"eslint-config-prettier": "^8.4.0",
"@babel/eslint-parser": "^7.17.0",
"eslint": "^8.11.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-react": "^7.29.2",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.3.0",
"prettier": "^2.5.1",
"stylelint": "^14.5.3"
"prettier": "^2.6.0",
"stylelint": "^14.6.0"
},
"browserslist": {
"production": [

File diff suppressed because it is too large Load diff

View file

@ -7,18 +7,23 @@
import {fileURLToPath} from 'url';
process.env.TZ = 'UTC';
const ignorePatterns = [
'/node_modules/',
'__fixtures__',
'/testUtils.ts',
'/packages/docusaurus/lib',
'/packages/docusaurus-logger/lib',
'/packages/docusaurus-utils/lib',
'/packages/docusaurus-utils-common/lib',
'/packages/docusaurus-utils-validation/lib',
'/packages/docusaurus-plugin-content-blog/lib',
'/packages/docusaurus-plugin-content-docs/lib',
'/packages/docusaurus-plugin-content-pages/lib',
'/packages/docusaurus-theme-classic/lib',
'/packages/docusaurus-theme-classic/lib-next',
'/packages/docusaurus-theme-common/lib',
'/packages/docusaurus-migrate/lib',
];
@ -28,7 +33,11 @@ export default {
testURL: 'https://docusaurus.io/',
testEnvironment: 'node',
testPathIgnorePatterns: ignorePatterns,
coveragePathIgnorePatterns: ignorePatterns,
coveragePathIgnorePatterns: [
...ignorePatterns,
// We also ignore all package entry points
'/packages/docusaurus-utils/src/index.ts',
],
transform: {
'^.+\\.[jt]sx?$': '@swc/jest',
},
@ -38,8 +47,10 @@ export default {
'^.+\\.(css|jpe?g|png|svg|webp)$': '<rootDir>/jest/emptyModule.ts',
// Using src instead of lib, so we always get fresh source
'@docusaurus/(browserContext|BrowserOnly|ComponentCreator|constants|docusaurusContext|ExecutionEnvironment|Head|Interpolate|isInternalUrl|Link|Noop|renderRoutes|router|Translate|use.*)':
'@docusaurus/(BrowserOnly|ComponentCreator|constants|ExecutionEnvironment|Head|Interpolate|isInternalUrl|Link|Noop|renderRoutes|router|Translate|use.*)':
'@docusaurus/core/src/client/exports/$1',
// TODO create dedicated testing utility for mocking contexts
// Maybe point to a fixture?
'@generated/.*': '<rootDir>/jest/emptyModule.ts',
// TODO use "projects" + multiple configs if we work on another theme?
@ -49,8 +60,13 @@ export default {
// Using src instead of lib, so we always get fresh source
'@docusaurus/plugin-content-docs/client':
'@docusaurus/plugin-content-docs/src/client/index.ts',
'@testing-utils/(.*)': '<rootDir>/jest/utils/$1.ts',
},
snapshotSerializers: ['<rootDir>/jest/snapshotPathNormalizer.ts'],
snapshotSerializers: [
'<rootDir>/jest/snapshotPathNormalizer.ts',
'jest-serializer-react-helmet-async',
],
snapshotFormat: {
printBasicPrototype: false,
},

View file

@ -31,7 +31,7 @@ export function print(
});
return serialize(error);
} else if (val && typeof val === 'object') {
const normalizedValue = _.cloneDeep(val) as Record<string, unknown>;
const normalizedValue = _.cloneDeep(val) as {[key: string]: unknown};
Object.keys(normalizedValue).forEach((key) => {
normalizedValue[key] = normalizePaths(normalizedValue[key]);
@ -46,7 +46,7 @@ export function test(val: unknown): boolean {
(typeof val === 'object' &&
val &&
Object.keys(val).some((key) =>
shouldUpdate((val as Record<string, unknown>)[key]),
shouldUpdate((val as {[key: string]: unknown})[key]),
)) ||
// val.message is non-enumerable in an error
(val instanceof Error && shouldUpdate(val.message)) ||

63
jest/utils/git.ts vendored Normal file
View file

@ -0,0 +1,63 @@
/**
* 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 fs from 'fs-extra';
import os from 'os';
import path from 'path';
import shell from 'shelljs';
class Git {
constructor(private dir: string) {
const res = shell.exec('git init', {cwd: dir, silent: true});
if (res.code !== 0) {
throw new Error(`git init exited with code ${res.code}.
stderr: ${res.stderr}
stdout: ${res.stdout}`);
}
// Doesn't matter currently
shell.exec('git config user.email "test@jc-verse.com"', {
cwd: dir,
silent: true,
});
shell.exec('git config user.name "Test"', {cwd: dir, silent: true});
shell.exec('git commit --allow-empty -m "First commit"', {
cwd: dir,
silent: true,
});
}
commit(msg: string, date: string, author: string): void {
const addRes = shell.exec('git add .', {cwd: this.dir, silent: true});
const commitRes = shell.exec(
`git commit -m "${msg}" --date "${date}T00:00:00Z" --author "${author}"`,
{
cwd: this.dir,
env: {GIT_COMMITTER_DATE: `${date}T00:00:00Z`},
silent: true,
},
);
if (addRes.code !== 0) {
throw new Error(`git add exited with code ${addRes.code}.
stderr: ${addRes.stderr}
stdout: ${addRes.stdout}`);
}
if (commitRes.code !== 0) {
throw new Error(`git commit exited with code ${commitRes.code}.
stderr: ${commitRes.stderr}
stdout: ${commitRes.stdout}`);
}
}
}
// This function is sync so the same mock repo can be shared across tests
export function createTempRepo(): {repoDir: string; git: Git} {
const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'git-test-repo'));
const git = new Git(repoDir);
return {repoDir, git};
}

View file

@ -1,5 +1,5 @@
{
"version": "2.0.0-beta.17",
"version": "2.0.0-beta.18",
"npmClient": "yarn",
"useWorkspaces": true,
"changelog": {

View file

@ -52,7 +52,7 @@
"lint:spelling": "cspell \"**\" --no-progress",
"lint:style": "stylelint \"**/*.css\"",
"lerna": "lerna",
"test": "cross-env TZ=UTC jest",
"test": "jest",
"test:build:website": "./admin/scripts/test-release.sh",
"watch": "yarn lerna run --parallel watch",
"clear": "(yarn workspace website clear || echo 'Failure while running docusaurus clear') && yarn lerna exec --ignore docusaurus yarn rimraf lib lib-next",
@ -61,56 +61,57 @@
},
"devDependencies": {
"@babel/cli": "^7.17.6",
"@babel/core": "^7.17.7",
"@babel/core": "^7.17.8",
"@babel/preset-typescript": "^7.16.7",
"@crowdin/cli": "^3.7.8",
"@swc/core": "^1.2.158",
"@swc/core": "^1.2.160",
"@swc/jest": "^0.2.20",
"@testing-library/react-hooks": "^7.0.2",
"@types/fs-extra": "^9.0.13",
"@types/jest": "^27.4.1",
"@types/lodash": "^4.14.180",
"@types/node": "^17.0.21",
"@types/node": "^17.0.23",
"@types/prompts": "^2.0.14",
"@types/react": "^17.0.40",
"@types/react": "^17.0.43",
"@types/react-dev-utils": "^9.0.10",
"@types/react-test-renderer": "^17.0.1",
"@types/semver": "^7.3.9",
"@types/shelljs": "^0.8.11",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
"@typescript-eslint/eslint-plugin": "^5.16.0",
"@typescript-eslint/parser": "^5.16.0",
"concurrently": "^7.0.0",
"cross-env": "^7.0.3",
"cspell": "^5.19.2",
"cspell": "^5.19.3",
"eslint": "^8.11.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jest": "^26.1.3",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-regexp": "^1.5.1",
"eslint-plugin-regexp": "^1.6.0",
"husky": "^7.0.4",
"image-size": "^1.0.1",
"jest": "^27.5.1",
"jest-serializer-react-helmet-async": "^1.0.21",
"lerna": "^4.0.0",
"lerna-changelog": "^2.2.0",
"lint-staged": "^12.3.5",
"netlify-cli": "^9.13.1",
"lint-staged": "^12.3.7",
"netlify-cli": "^9.13.4",
"nodemon": "^2.0.15",
"prettier": "^2.5.1",
"prettier": "^2.6.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-test-renderer": "^17.0.2",
"remark-parse": "^8.0.2",
"rimraf": "^3.0.2",
"sharp": "^0.30.3",
"stylelint": "^14.5.3",
"stylelint": "^14.6.0",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-standard": "^25.0.0",
"typescript": "^4.6.2",
"typescript": "^4.6.3",
"unified": "^9.2.1"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "create-docusaurus",
"version": "2.0.0-beta.17",
"version": "2.0.0-beta.18",
"description": "Create Docusaurus apps easily.",
"type": "module",
"repository": {
@ -22,7 +22,7 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/logger": "2.0.0-beta.17",
"@docusaurus/logger": "2.0.0-beta.18",
"commander": "^5.1.0",
"fs-extra": "^10.0.1",
"lodash": "^4.17.21",

View file

@ -102,7 +102,7 @@ function isValidGitRepoUrl(gitRepoUrl: string) {
return ['https://', 'git@'].some((item) => gitRepoUrl.startsWith(item));
}
async function updatePkg(pkgPath: string, obj: Record<string, unknown>) {
async function updatePkg(pkgPath: string, obj: {[key: string]: unknown}) {
const content = await fs.readFile(pkgPath, 'utf-8');
const pkg = JSON.parse(content);
const newPkg = Object.assign(pkg, obj);
@ -271,7 +271,7 @@ export default async function init(
return logger.red('Invalid repository URL');
},
message: logger.interpolate`Enter a repository URL from GitHub, Bitbucket, GitLab, or any other public repo.
(e.g: path=${'https://github.com/ownerName/repoName.git'})`,
(e.g: url=${'https://github.com/ownerName/repoName.git'})`,
});
({gitStrategy} = await prompts({
type: 'select',
@ -318,7 +318,7 @@ export default async function init(
logger.info('Creating new Docusaurus project...');
if (isValidGitRepoUrl(template)) {
logger.info`Cloning Git template path=${template}...`;
logger.info`Cloning Git template url=${template}...`;
if (!gitStrategies.includes(gitStrategy)) {
logger.error`Invalid git strategy: name=${gitStrategy}. Value must be one of ${gitStrategies.join(
', ',
@ -416,7 +416,7 @@ export default async function init(
}
const useNpm = pkgManager === 'npm';
logger.success`Created path=${cdpath}.`;
logger.success`Created name=${cdpath}.`;
logger.info`Inside that directory, you can run several commands:
code=${`${pkgManager} start`}

View file

@ -1,6 +1,6 @@
{
"name": "docusaurus-2-classic-typescript-template",
"version": "2.0.0-beta.17",
"version": "2.0.0-beta.18",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@ -15,8 +15,8 @@
"typecheck": "tsc"
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.17",
"@docusaurus/preset-classic": "2.0.0-beta.17",
"@docusaurus/core": "2.0.0-beta.18",
"@docusaurus/preset-classic": "2.0.0-beta.18",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.1.1",
"prism-react-renderer": "^1.3.1",
@ -24,9 +24,9 @@
"react-dom": "^17.0.2"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-beta.17",
"@docusaurus/module-type-aliases": "2.0.0-beta.18",
"@tsconfig/docusaurus": "^1.0.5",
"typescript": "^4.6.2"
"typescript": "^4.6.3"
},
"browserslist": {
"production": [

View file

@ -1,6 +1,6 @@
{
"name": "docusaurus-2-classic-template",
"version": "2.0.0-beta.17",
"version": "2.0.0-beta.18",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@ -14,11 +14,11 @@
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.17",
"@docusaurus/preset-classic": "2.0.0-beta.17",
"@docusaurus/core": "2.0.0-beta.18",
"@docusaurus/preset-classic": "2.0.0-beta.18",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.1.1",
"prism-react-renderer": "^1.2.1",
"prism-react-renderer": "^1.3.1",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},

View file

@ -1,6 +1,6 @@
{
"name": "docusaurus-2-facebook-template",
"version": "2.0.0-beta.17",
"version": "2.0.0-beta.18",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@ -18,8 +18,8 @@
"format:diff": "prettier --config .prettierrc --list-different \"**/*.{js,jsx,ts,tsx,md,mdx}\""
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.17",
"@docusaurus/preset-classic": "2.0.0-beta.17",
"@docusaurus/core": "2.0.0-beta.18",
"@docusaurus/preset-classic": "2.0.0-beta.18",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.1.1",
"react": "^17.0.2",
@ -35,8 +35,8 @@
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.3.0",
"prettier": "^2.5.1",
"stylelint": "^14.5.3"
"prettier": "^2.6.0",
"stylelint": "^14.6.0"
},
"browserslist": {
"production": [

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/cssnano-preset",
"version": "2.0.0-beta.17",
"version": "2.0.0-beta.18",
"description": "Advanced cssnano preset for maximum optimization.",
"main": "index.js",
"license": "MIT",
@ -13,8 +13,8 @@
"directory": "packages/docusaurus-cssnano-preset"
},
"dependencies": {
"cssnano-preset-advanced": "^5.2.5",
"postcss": "^8.4.8",
"cssnano-preset-advanced": "^5.3.1",
"postcss": "^8.4.12",
"postcss-sort-media-queries": "^4.2.1"
},
"devDependencies": {

View file

@ -8,7 +8,8 @@ It exports a single object as default export: `logger`. `logger` has the followi
- Some useful colors.
- Formatters. These functions have the same signature as the formatters of `picocolors`. Note that their implementations are not guaranteed. You should only care about their semantics.
- `path`: formats a file path or URL.
- `path`: formats a file path.
- `url`: formats a URL.
- `id`: formats an identifier.
- `code`: formats a code snippet.
- `subdue`: subdues the text.
@ -34,6 +35,7 @@ To buy anything, enter code=${'buy x'} where code=${'x'} is the item's name; to
An embedded expression is optionally preceded by a flag in the form `%[a-z]+` (a percentage sign followed by a few lowercase letters). If it's not preceded by any flag, it's printed out as-is. Otherwise, it's formatted with one of the formatters:
- `path=`: `path`
- `url=`: `url`
- `name=`: `id`
- `code=`: `code`
- `subdue=`: `subdue`

View file

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

View file

@ -11,7 +11,12 @@ import logger from '../index';
describe('formatters', () => {
it('path', () => {
// cSpell:ignore mhey
expect(logger.path('hey')).toMatchInlineSnapshot(`"hey"`);
expect(logger.path('hey')).toMatchInlineSnapshot(`"\\"hey\\""`);
});
it('url', () => {
expect(logger.url('https://docusaurus.io/')).toMatchInlineSnapshot(
`"https://docusaurus.io/"`,
);
});
it('id', () => {
expect(logger.name('hey')).toMatchInlineSnapshot(`"hey"`);
@ -40,8 +45,7 @@ describe('interpolate', () => {
expect(
logger.interpolate`The package at path=${'packages/docusaurus'} has number=${10} files. name=${'Babel'} is exported here subdue=${'(as a preset)'} that you can with code=${"require.resolve('@docusaurus/core/lib/babel/preset')"}`,
).toMatchInlineSnapshot(
// cSpell:ignore mpackages
`"The package at packages/docusaurus has 10 files. Babel is exported here (as a preset) that you can with \`require.resolve('@docusaurus/core/lib/babel/preset')\`"`,
`"The package at \\"packages/docusaurus\\" has 10 files. Babel is exported here (as a preset) that you can with \`require.resolve('@docusaurus/core/lib/babel/preset')\`"`,
);
});
it('interpolates arrays with flags', () => {

View file

@ -9,7 +9,8 @@ import chalk, {type Chalk} from 'chalk';
type InterpolatableValue = string | number | (string | number)[];
const path = (msg: unknown): string => chalk.cyan(chalk.underline(msg));
const path = (msg: unknown): string => chalk.cyan(chalk.underline(`"${msg}"`));
const url = (msg: unknown): string => chalk.cyan(chalk.underline(msg));
const name = (msg: unknown): string => chalk.blue(chalk.bold(msg));
const code = (msg: unknown): string => chalk.cyan(`\`${msg}\``);
const subdue: Chalk = chalk.gray;
@ -30,6 +31,8 @@ function interpolate(
switch (flag[0]) {
case 'path=':
return path;
case 'url=':
return url;
case 'number=':
return num;
case 'name=':
@ -131,6 +134,7 @@ const logger = {
bold: chalk.bold,
dim: chalk.dim,
path,
url,
name,
code,
subdue,

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/mdx-loader",
"version": "2.0.0-beta.17",
"version": "2.0.0-beta.18",
"description": "Docusaurus Loader for MDX",
"main": "lib/index.js",
"types": "src/mdx-loader.d.ts",
@ -18,10 +18,10 @@
},
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.17.7",
"@babel/parser": "^7.17.8",
"@babel/traverse": "^7.17.3",
"@docusaurus/logger": "2.0.0-beta.17",
"@docusaurus/utils": "2.0.0-beta.17",
"@docusaurus/logger": "2.0.0-beta.18",
"@docusaurus/utils": "2.0.0-beta.18",
"@mdx-js/mdx": "^1.6.22",
"escape-html": "^1.0.3",
"file-loader": "^6.2.0",
@ -36,7 +36,7 @@
"webpack": "^5.70.0"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.17",
"@docusaurus/types": "2.0.0-beta.18",
"@types/escape-html": "^1.0.1",
"@types/mdast": "^3.0.10",
"@types/stringify-object": "^3.3.1",

View file

@ -21,21 +21,21 @@ import toc from './remark/toc';
import unwrapMdxCodeBlocks from './remark/unwrapMdxCodeBlocks';
import transformImage from './remark/transformImage';
import transformLinks from './remark/transformLinks';
import type {RemarkAndRehypePluginOptions} from '@docusaurus/mdx-loader';
import type {MDXOptions} from '@docusaurus/mdx-loader';
import type {LoaderContext} from 'webpack';
const {
loaders: {inlineMarkdownImageFileLoader},
} = getFileLoaderUtils();
const DEFAULT_OPTIONS: RemarkAndRehypePluginOptions = {
const DEFAULT_OPTIONS: MDXOptions = {
rehypePlugins: [],
remarkPlugins: [unwrapMdxCodeBlocks, emoji, headings, toc],
beforeDefaultRemarkPlugins: [],
beforeDefaultRehypePlugins: [],
};
type Options = RemarkAndRehypePluginOptions & {
type Options = MDXOptions & {
staticDirs: string[];
siteDir: string;
isMDXPartial?: (filePath: string) => boolean;
@ -43,9 +43,9 @@ type Options = RemarkAndRehypePluginOptions & {
removeContentTitle?: boolean;
metadataPath?: string | ((filePath: string) => string);
createAssets?: (metadata: {
frontMatter: Record<string, unknown>;
metadata: Record<string, unknown>;
}) => Record<string, unknown>;
frontMatter: {[key: string]: unknown};
metadata: {[key: string]: unknown};
}) => {[key: string]: unknown};
filepath: string;
};
@ -72,7 +72,7 @@ async function readMetadataPath(metadataPath: string) {
*
* `{image: "./myImage.png"}` => `{image: require("./myImage.png")}`
*/
function createAssetsExportCode(assets: Record<string, unknown>) {
function createAssetsExportCode(assets: {[key: string]: unknown}) {
if (Object.keys(assets).length === 0) {
return 'undefined';
}

View file

@ -7,12 +7,12 @@
import type {Plugin} from 'unified';
export type RemarkOrRehypePlugin =
export type MDXPlugin =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[Plugin<any[]>, Record<string, unknown>] | Plugin<any[]>;
export type RemarkAndRehypePluginOptions = {
remarkPlugins: RemarkOrRehypePlugin[];
rehypePlugins: RemarkOrRehypePlugin[];
beforeDefaultRemarkPlugins: RemarkOrRehypePlugin[];
beforeDefaultRehypePlugins: RemarkOrRehypePlugin[];
[Plugin<any[]>, any] | Plugin<any[]>;
export type MDXOptions = {
remarkPlugins: MDXPlugin[];
rehypePlugins: MDXPlugin[];
beforeDefaultRemarkPlugins: MDXPlugin[];
beforeDefaultRehypePlugins: MDXPlugin[];
};

View file

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`transformImage plugin does not choke on invalid image 1`] = `
"<img loading=\\"lazy\\" alt={\\"invalid image\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/invalid.png\\").default} />
"<img alt={\\"invalid image\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/invalid.png\\").default} />
"
`;
@ -19,29 +19,29 @@ exports[`transformImage plugin pathname protocol 1`] = `
exports[`transformImage plugin transform md images to <img /> 1`] = `
"![img](https://example.com/img.png)
<img loading=\\"lazy\\" src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" />
<img src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" />
<img loading=\\"lazy\\" alt={\\"img\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" />
<img alt={\\"img\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" />
<img loading=\\"lazy\\" alt={\\"img from second static folder\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static2/img2.png\\").default} width=\\"256\\" height=\\"82\\" />
<img alt={\\"img from second static folder\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static2/img2.png\\").default} width=\\"256\\" height=\\"82\\" />
<img loading=\\"lazy\\" alt={\\"img from second static folder\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static2/img2.png\\").default} width=\\"256\\" height=\\"82\\" />
<img alt={\\"img from second static folder\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static2/img2.png\\").default} width=\\"256\\" height=\\"82\\" />
<img loading=\\"lazy\\" alt={\\"img with URL encoded chars\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static2/img2 copy.png\\").default} width=\\"256\\" height=\\"82\\" />
<img alt={\\"img with URL encoded chars\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static2/img2 copy.png\\").default} width=\\"256\\" height=\\"82\\" />
<img loading=\\"lazy\\" alt={\\"img\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} title=\\"Title\\" width=\\"200\\" height=\\"200\\" /> <img loading=\\"lazy\\" alt={\\"img\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" />
<img alt={\\"img\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} title=\\"Title\\" width=\\"200\\" height=\\"200\\" /> <img alt={\\"img\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" />
<img loading=\\"lazy\\" alt={\\"img with &quot;quotes&quot;\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} title=\\"&#39;Quoted&#39; title\\" width=\\"200\\" height=\\"200\\" />
<img alt={\\"img with &quot;quotes&quot;\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} title=\\"&#39;Quoted&#39; title\\" width=\\"200\\" height=\\"200\\" />
<img loading=\\"lazy\\" alt={\\"site alias\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" />
<img alt={\\"site alias\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" />
<img loading=\\"lazy\\" alt={\\"img with hash\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default + '#light'} width=\\"200\\" height=\\"200\\" />
<img loading=\\"lazy\\" alt={\\"img with hash\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default + '#dark'} width=\\"200\\" height=\\"200\\" />
<img alt={\\"img with hash\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default + '#light'} width=\\"200\\" height=\\"200\\" />
<img alt={\\"img with hash\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default + '#dark'} width=\\"200\\" height=\\"200\\" />
<img loading=\\"lazy\\" alt={\\"img with query\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10\\").default} width=\\"200\\" height=\\"200\\" />
<img loading=\\"lazy\\" alt={\\"img with query\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10&h=10\\").default} width=\\"200\\" height=\\"200\\" />
<img alt={\\"img with query\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10\\").default} width=\\"200\\" height=\\"200\\" />
<img alt={\\"img with query\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10&h=10\\").default} width=\\"200\\" height=\\"200\\" />
<img loading=\\"lazy\\" alt={\\"img with both\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10&h=10\\").default + '#light'} width=\\"200\\" height=\\"200\\" />
<img alt={\\"img with both\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10&h=10\\").default + '#light'} width=\\"200\\" height=\\"200\\" />
## Heading

View file

@ -80,7 +80,7 @@ ${(err as Error).message}`;
);
(jsxNode as Literal).type = 'jsx';
jsxNode.value = `<img loading="lazy" ${alt}src={${src}}${title}${width}${height} />`;
jsxNode.value = `<img ${alt}src={${src}}${title}${width}${height} />`;
}
async function ensureImageFileExist(imagePath: string, sourceFilePath: string) {

View file

@ -46,7 +46,7 @@ exports[`transformAsset plugin transform md links to <a /> 1`] = `
<a target=\\"_blank\\" href={require('!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>Just staticAsset.pdf</a>, and <a target=\\"_blank\\" href={require('!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}><strong>awesome</strong> staticAsset 2.pdf &#39;It is really &quot;AWESOME&quot;&#39;</a>, but also <a target=\\"_blank\\" href={require('!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>coded <code>staticAsset 3.pdf</code></a>
<a target=\\"_blank\\" href={require('!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAssetImage.png').default}><img loading=\\"lazy\\" alt={\\"Clickable Docusaurus logo\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/staticAssetImage.png\\").default} width=\\"200\\" height=\\"200\\" /></a>
<a target=\\"_blank\\" href={require('!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAssetImage.png').default}><img alt={\\"Clickable Docusaurus logo\\"} src={require(\\"!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/staticAssetImage.png\\").default} width=\\"200\\" height=\\"200\\" /></a>
<a target=\\"_blank\\" href={require('!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default}><span style={{color: \\"red\\"}}>Stylized link to asset file</span></a>

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/migrate",
"version": "2.0.0-beta.17",
"version": "2.0.0-beta.18",
"description": "A CLI tool to migrate from older versions of Docusaurus.",
"license": "MIT",
"engines": {
@ -23,8 +23,8 @@
},
"dependencies": {
"@babel/preset-env": "^7.16.11",
"@docusaurus/logger": "2.0.0-beta.17",
"@docusaurus/utils": "2.0.0-beta.17",
"@docusaurus/logger": "2.0.0-beta.18",
"@docusaurus/utils": "2.0.0-beta.18",
"@mapbox/hast-util-to-jsx": "^2.0.0",
"color": "^4.2.1",
"commander": "^5.1.0",

View file

@ -67,7 +67,7 @@ ${
type MigrationContext = {
siteDir: string;
newDir: string;
deps: Record<string, string>;
deps: {[key: string]: string};
shouldMigrateMdFiles: boolean;
shouldMigratePages: boolean;
v1Config: VersionOneConfig;
@ -83,7 +83,7 @@ export async function migrateDocusaurusProject(
async function createMigrationContext(): Promise<MigrationContext> {
const v1Config = importFresh(`${siteDir}/siteConfig`) as VersionOneConfig;
logger.info('Starting migration from v1 to v2...');
const deps: Record<string, string> = {
const deps = {
'@docusaurus/core': DOCUSAURUS_VERSION,
'@docusaurus/preset-classic': DOCUSAURUS_VERSION,
clsx: '^1.1.1',
@ -206,7 +206,7 @@ export function createConfigFile({
'v1Config' | 'siteDir' | 'newDir'
>): VersionTwoConfig {
const siteConfig = v1Config;
const customConfigFields: Record<string, unknown> = {};
const customConfigFields: {[key: string]: unknown} = {};
// add fields that are unknown to v2 to customConfigFields
Object.keys(siteConfig).forEach((key) => {
const knownFields = [
@ -564,7 +564,7 @@ async function migrateVersionedSidebar(
};
});
return acc;
}, {} as Record<string, Array<string | Record<string, unknown>>>);
}, {} as {[key: string]: Array<string | {[key: string]: unknown}>});
return topLevel;
},
{},
@ -702,9 +702,9 @@ async function migrateLatestDocs(context: MigrationContext) {
async function migratePackageFile(context: MigrationContext): Promise<void> {
const {deps, siteDir, newDir} = context;
const packageFile = importFresh(`${siteDir}/package.json`) as {
scripts?: Record<string, string>;
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
scripts?: {[key: string]: string};
dependencies?: {[key: string]: string};
devDependencies?: {[key: string]: string};
[otherKey: string]: unknown;
};
packageFile.scripts = {

View file

@ -33,8 +33,8 @@ export type SidebarEntry =
export type SidebarEntries = {
[key: string]:
| Record<string, unknown>
| Array<Record<string, unknown> | string>;
| {[key: string]: unknown}
| Array<{[key: string]: unknown} | string>;
};
export interface VersionTwoConfig {
@ -58,7 +58,7 @@ export interface VersionTwoConfig {
logo?: {
src?: string;
};
items: Array<Record<string, unknown> | null>;
items: Array<{[key: string]: unknown} | null>;
};
image?: string;
footer: {
@ -74,7 +74,7 @@ export interface VersionTwoConfig {
src?: string;
};
};
algolia?: Record<string, unknown>;
algolia?: {[key: string]: unknown};
};
customFields: {
[key: string]: unknown;
@ -111,16 +111,16 @@ export type VersionOneConfig = {
copyright?: string;
editUrl?: string;
customDocsPath?: string;
users?: Array<Record<string, unknown>>;
users?: Array<{[key: string]: unknown}>;
disableHeaderTitle?: string;
disableTitleTagline?: string;
separateCss?: Array<Record<string, unknown>>;
separateCss?: Array<{[key: string]: unknown}>;
footerIcon?: string;
translationRecruitingLink?: string;
algolia?: Record<string, unknown>;
algolia?: {[key: string]: unknown};
gaTrackingId?: string;
gaGtag?: boolean;
highlight?: Record<string, unknown>;
highlight?: {[key: string]: unknown};
markdownPlugins?: Array<() => void>;
scripts?: Array<{src: string; [key: string]: unknown} | string>;
stylesheets?: Array<{href: string; [key: string]: unknown} | string>;
@ -133,5 +133,5 @@ export type VersionOneConfig = {
ogImage?: string;
cleanUrl?: boolean;
scrollToTop?: boolean;
scrollToTopOptions?: Record<string, unknown>;
scrollToTopOptions?: {[key: string]: unknown};
};

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/module-type-aliases",
"version": "2.0.0-beta.17",
"version": "2.0.0-beta.18",
"description": "Docusaurus module type aliases.",
"types": "./src/index.d.ts",
"publishConfig": {
@ -12,7 +12,7 @@
"directory": "packages/docusaurus-module-type-aliases"
},
"dependencies": {
"@docusaurus/types": "2.0.0-beta.17",
"@docusaurus/types": "2.0.0-beta.18",
"@types/react": "*",
"@types/react-router-config": "*",
"@types/react-router-dom": "*",

View file

@ -20,9 +20,9 @@ declare module '@generated/docusaurus.config' {
}
declare module '@generated/site-metadata' {
import type {DocusaurusSiteMetadata} from '@docusaurus/types';
import type {SiteMetadata} from '@docusaurus/types';
const siteMetadata: DocusaurusSiteMetadata;
const siteMetadata: SiteMetadata;
export = siteMetadata;
}
@ -44,35 +44,26 @@ declare module '@generated/routes' {
declare module '@generated/routesChunkNames' {
import type {RouteChunksTree} from '@docusaurus/types';
const routesChunkNames: Record<string, RouteChunksTree>;
const routesChunkNames: {[route: string]: RouteChunksTree};
export = routesChunkNames;
}
declare module '@generated/globalData' {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const globalData: Record<string, any>;
import type {GlobalData} from '@docusaurus/types';
const globalData: GlobalData;
export = globalData;
}
declare module '@generated/i18n' {
const i18n: {
defaultLocale: string;
locales: [string, ...string[]];
currentLocale: string;
localeConfigs: Record<
string,
{
label: string;
direction: string;
htmlLang: string;
}
>;
};
import type {I18n} from '@docusaurus/types';
const i18n: I18n;
export = i18n;
}
declare module '@generated/codeTranslations' {
const codeTranslations: Record<string, string>;
const codeTranslations: {[msgId: string]: string};
export = codeTranslations;
}
@ -172,10 +163,9 @@ declare module '@docusaurus/Interpolate' {
? Key | ExtractInterpolatePlaceholders<Rest>
: never;
export type InterpolateValues<
Str extends string,
Value extends ReactNode,
> = Record<ExtractInterpolatePlaceholders<Str>, Value>;
export type InterpolateValues<Str extends string, Value extends ReactNode> = {
[key in ExtractInterpolatePlaceholders<Str>]: Value;
};
// If all the values are plain strings, interpolate returns a simple string
export function interpolate<Str extends string>(
@ -320,17 +310,18 @@ declare module '@docusaurus/renderRoutes' {
}
declare module '@docusaurus/useGlobalData' {
export function useAllPluginInstancesData<T = unknown>(
pluginName: string,
): Record<string, T>;
import type {GlobalData} from '@docusaurus/types';
export function usePluginData<T = unknown>(
export function useAllPluginInstancesData(
pluginName: string,
): GlobalData[string];
export function usePluginData(
pluginName: string,
pluginId?: string,
): T;
): GlobalData[string][string];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default function useGlobalData(): Record<string, any>;
export default function useGlobalData(): GlobalData;
}
declare module '*.svg' {

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-client-redirects",
"version": "2.0.0-beta.17",
"version": "2.0.0-beta.18",
"description": "Client redirects plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-client-redirects.d.ts",
@ -18,18 +18,18 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.0.0-beta.17",
"@docusaurus/logger": "2.0.0-beta.17",
"@docusaurus/utils": "2.0.0-beta.17",
"@docusaurus/utils-common": "2.0.0-beta.17",
"@docusaurus/utils-validation": "2.0.0-beta.17",
"@docusaurus/core": "2.0.0-beta.18",
"@docusaurus/logger": "2.0.0-beta.18",
"@docusaurus/utils": "2.0.0-beta.18",
"@docusaurus/utils-common": "2.0.0-beta.18",
"@docusaurus/utils-validation": "2.0.0-beta.18",
"eta": "^1.12.3",
"fs-extra": "^10.0.1",
"lodash": "^4.17.21",
"tslib": "^2.3.1"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.17"
"@docusaurus/types": "2.0.0-beta.18"
},
"peerDependencies": {
"react": "^16.8.4 || ^17.0.0",

View file

@ -15,11 +15,14 @@ function testValidate(options: Options) {
describe('normalizePluginOptions', () => {
it('returns default options for undefined user options', () => {
expect(testValidate(undefined)).toEqual(DEFAULT_OPTIONS);
expect(testValidate(undefined)).toEqual({
...DEFAULT_OPTIONS,
id: 'default',
});
});
it('returns default options for empty user options', () => {
expect(testValidate(undefined)).toEqual(DEFAULT_OPTIONS);
expect(testValidate({})).toEqual({...DEFAULT_OPTIONS, id: 'default'});
});
it('overrides one default options with valid user options', () => {

View file

@ -13,7 +13,7 @@ const getCompiledRedirectPageTemplate = _.memoize(() =>
eta.compile(redirectPageTemplate.trim()),
);
function renderRedirectPageTemplate(data: Record<string, unknown>) {
function renderRedirectPageTemplate(data: {toUrl: string}) {
const compiled = getCompiledRedirectPageTemplate();
return compiled(data, eta.defaultConfig);
}

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import type {LoadContext, Plugin, Props} from '@docusaurus/types';
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {PluginContext, RedirectMetadata} from './types';
import type {PluginOptions} from '@docusaurus/plugin-client-redirects';
@ -24,7 +24,7 @@ export default function pluginClientRedirectsPages(
return {
name: 'docusaurus-plugin-client-redirects',
async postBuild(props: Props) {
async postBuild(props) {
const pluginContext: PluginContext = {
relativeRoutesPaths: props.routesPaths.map(
(path) => `${addLeadingSlash(removePrefix(path, props.baseUrl))}`,

View file

@ -7,12 +7,10 @@
import type {
PluginOptions,
Options,
RedirectOption,
} from '@docusaurus/plugin-client-redirects';
import type {
OptionValidationContext,
ValidationResult,
} from '@docusaurus/types';
import type {OptionValidationContext} from '@docusaurus/types';
import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
export const DEFAULT_OPTIONS: Partial<PluginOptions> = {
@ -47,6 +45,6 @@ const UserOptionsSchema = Joi.object<PluginOptions>({
export function validateOptions({
validate,
options: userOptions,
}: OptionValidationContext<PluginOptions>): ValidationResult<PluginOptions> {
}: OptionValidationContext<Options, PluginOptions>): PluginOptions {
return validate(UserOptionsSchema, userOptions);
}

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-blog",
"version": "2.0.0-beta.17",
"version": "2.0.0-beta.18",
"description": "Blog plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-content-blog.d.ts",
@ -18,12 +18,12 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.0.0-beta.17",
"@docusaurus/logger": "2.0.0-beta.17",
"@docusaurus/mdx-loader": "2.0.0-beta.17",
"@docusaurus/utils": "2.0.0-beta.17",
"@docusaurus/utils-common": "2.0.0-beta.17",
"@docusaurus/utils-validation": "2.0.0-beta.17",
"@docusaurus/core": "2.0.0-beta.18",
"@docusaurus/logger": "2.0.0-beta.18",
"@docusaurus/mdx-loader": "2.0.0-beta.18",
"@docusaurus/utils": "2.0.0-beta.18",
"@docusaurus/utils-common": "2.0.0-beta.18",
"@docusaurus/utils-validation": "2.0.0-beta.18",
"cheerio": "^1.0.0-rc.10",
"feed": "^4.2.2",
"fs-extra": "^10.0.1",
@ -35,7 +35,7 @@
"webpack": "^5.70.0"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.17",
"@docusaurus/types": "2.0.0-beta.18",
"escape-string-regexp": "^4.0.0"
},
"peerDependencies": {

View file

@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`validateOptions throws Error in case of invalid feed type 1`] = `"\\"feedOptions.type\\" does not match any of the allowed types"`;
exports[`validateOptions throws Error in case of invalid options 1`] = `"\\"postsPerPage\\" must be greater than or equal to 1"`;

View file

@ -1,5 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`blog plugin options schema throws Error in case of invalid feed type 1`] = `[ValidationError: "feedOptions.type" does not match any of the allowed types]`;
exports[`blog plugin options schema throws Error in case of invalid options 1`] = `[ValidationError: "postsPerPage" must be greater than or equal to 1]`;

View file

@ -11,7 +11,7 @@ import fs from 'fs-extra';
import {createBlogFeedFiles} from '../feed';
import type {LoadContext, I18n} from '@docusaurus/types';
import type {BlogContentPaths} from '../types';
import {DEFAULT_OPTIONS} from '../pluginOptionSchema';
import {DEFAULT_OPTIONS} from '../options';
import {generateBlogPosts} from '../blogUtils';
import type {PluginOptions} from '@docusaurus/plugin-content-blog';

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {validateBlogPostFrontMatter} from '../blogFrontMatter';
import {validateBlogPostFrontMatter} from '../frontMatter';
import escapeStringRegexp from 'escape-string-regexp';
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
@ -15,11 +15,11 @@ function testField(params: {
fieldName: keyof BlogPostFrontMatter;
validFrontMatters: BlogPostFrontMatter[];
convertibleFrontMatter?: [
ConvertibleFrontMatter: Record<string, unknown>,
ConvertibleFrontMatter: {[key: string]: unknown},
ConvertedFrontMatter: BlogPostFrontMatter,
][];
invalidFrontMatters?: [
InvalidFrontMatter: Record<string, unknown>,
InvalidFrontMatter: {[key: string]: unknown},
ErrorMessage: string,
][];
}) {

View file

@ -9,9 +9,9 @@ import {jest} from '@jest/globals';
import path from 'path';
import pluginContentBlog from '../index';
import type {DocusaurusConfig, LoadContext, I18n} from '@docusaurus/types';
import {PluginOptionSchema} from '../pluginOptionSchema';
import {validateOptions} from '../options';
import type {BlogPost} from '../types';
import type {Joi} from '@docusaurus/utils-validation';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import {posixPath, getFileCommitDate} from '@docusaurus/utils';
import type {
PluginOptions,
@ -47,18 +47,6 @@ function getI18n(locale: string): I18n {
const DefaultI18N: I18n = getI18n('en');
function validateAndNormalize(
schema: Joi.ObjectSchema,
options: Partial<PluginOptions>,
) {
const {value, error} = schema.validate(options);
if (error) {
throw error;
} else {
return value;
}
}
const PluginPath = 'blog';
const BaseEditUrl = 'https://baseEditUrl.com/edit';
@ -81,11 +69,14 @@ const getPlugin = async (
generatedFilesDir,
i18n,
} as LoadContext,
validateAndNormalize(PluginOptionSchema, {
path: PluginPath,
editUrl: BaseEditUrl,
...pluginOptions,
}),
validateOptions({
validate: normalizePluginOptions,
options: {
path: PluginPath,
editUrl: BaseEditUrl,
...pluginOptions,
},
}) as PluginOptions,
);
};

View file

@ -0,0 +1,160 @@
/**
* 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 {validateOptions, DEFAULT_OPTIONS} from '../options';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import type {Options} from '@docusaurus/plugin-content-blog';
function testValidate(options: Options) {
return validateOptions({validate: normalizePluginOptions, options});
}
// the type of remark/rehype plugins can be either function, object or array
const markdownPluginsFunctionStub = () => {};
const markdownPluginsObjectStub = {};
const defaultOptions = {...DEFAULT_OPTIONS, id: 'default'};
describe('validateOptions', () => {
it('returns default options for undefined user options', () => {
expect(testValidate(undefined)).toEqual(defaultOptions);
});
it('returns default options for empty user options', () => {
expect(testValidate({})).toEqual(defaultOptions);
});
it('accepts correctly defined user options', () => {
const userOptions = {
...defaultOptions,
feedOptions: {type: 'rss' as const, title: 'myTitle'},
path: 'not_blog',
routeBasePath: 'myBlog',
postsPerPage: 5,
include: ['api/*', 'docs/*'],
};
expect(testValidate(userOptions)).toEqual({
...userOptions,
feedOptions: {type: ['rss'], title: 'myTitle', copyright: ''},
});
});
it('accepts valid user options', () => {
const userOptions = {
...defaultOptions,
routeBasePath: 'myBlog',
beforeDefaultRemarkPlugins: [],
beforeDefaultRehypePlugins: [markdownPluginsFunctionStub],
remarkPlugins: [[markdownPluginsFunctionStub, {option1: '42'}]],
rehypePlugins: [
markdownPluginsObjectStub,
[markdownPluginsFunctionStub, {option1: '42'}],
],
};
expect(testValidate(userOptions)).toEqual(userOptions);
});
it('throws Error in case of invalid options', () => {
expect(() =>
testValidate({
path: 'not_blog',
postsPerPage: -1,
include: ['api/*', 'docs/*'],
routeBasePath: 'not_blog',
}),
).toThrowErrorMatchingSnapshot();
});
it('throws Error in case of invalid feed type', () => {
expect(() =>
testValidate({
feedOptions: {
type: 'none',
},
}),
).toThrowErrorMatchingSnapshot();
});
it('converts all feed type to array with other feed type', () => {
expect(
testValidate({
feedOptions: {type: 'all'},
}),
).toEqual({
...defaultOptions,
feedOptions: {type: ['rss', 'atom', 'json'], copyright: ''},
});
});
it('accepts null type and return same', () => {
expect(
testValidate({
feedOptions: {type: null},
}),
).toEqual({
...defaultOptions,
feedOptions: {type: null},
});
});
it('contains array with rss + atom for missing feed type', () => {
expect(
testValidate({
feedOptions: {},
}),
).toEqual(defaultOptions);
});
it('has array with rss + atom, title for missing feed type', () => {
expect(
testValidate({
feedOptions: {title: 'title'},
}),
).toEqual({
...defaultOptions,
feedOptions: {type: ['rss', 'atom'], title: 'title', copyright: ''},
});
});
it('accepts 0 sidebar count', () => {
const userOptions = {blogSidebarCount: 0};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
});
});
it('accepts "ALL" sidebar count', () => {
const userOptions = {blogSidebarCount: 'ALL' as const};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
});
});
it('rejects "abcdef" sidebar count', () => {
const userOptions = {blogSidebarCount: 'abcdef'};
expect(() => testValidate(userOptions)).toThrowErrorMatchingInlineSnapshot(
`"\\"blogSidebarCount\\" must be one of [ALL, number]"`,
);
});
it('accepts "all posts" sidebar title', () => {
const userOptions = {blogSidebarTitle: 'all posts'};
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
});
});
it('rejects 42 sidebar title', () => {
const userOptions = {blogSidebarTitle: 42};
expect(() => testValidate(userOptions)).toThrowErrorMatchingInlineSnapshot(
`"\\"blogSidebarTitle\\" must be a string"`,
);
});
});

View file

@ -1,152 +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 {PluginOptionSchema, DEFAULT_OPTIONS} from '../pluginOptionSchema';
// the type of remark/rehype plugins can be either function, object or array
const markdownPluginsFunctionStub = () => {};
const markdownPluginsObjectStub = {};
describe('blog plugin options schema', () => {
it('normalizes options', () => {
const {value, error} = PluginOptionSchema.validate({});
expect(value).toEqual(DEFAULT_OPTIONS);
expect(error).toBeUndefined();
});
it('accepts correctly defined user options', () => {
const userOptions = {
...DEFAULT_OPTIONS,
feedOptions: {type: 'rss', title: 'myTitle'},
path: 'not_blog',
routeBasePath: 'myBlog',
postsPerPage: 5,
include: ['api/*', 'docs/*'],
};
const {value, error} = PluginOptionSchema.validate(userOptions);
expect(value).toEqual({
...userOptions,
feedOptions: {type: ['rss'], title: 'myTitle', copyright: ''},
});
expect(error).toBeUndefined();
});
it('accepts valid user options', async () => {
const userOptions = {
...DEFAULT_OPTIONS,
routeBasePath: 'myBlog',
beforeDefaultRemarkPlugins: [],
beforeDefaultRehypePlugins: [markdownPluginsFunctionStub],
remarkPlugins: [[markdownPluginsFunctionStub, {option1: '42'}]],
rehypePlugins: [
markdownPluginsObjectStub,
[markdownPluginsFunctionStub, {option1: '42'}],
],
};
const {value, error} = PluginOptionSchema.validate(userOptions);
expect(value).toEqual(userOptions);
expect(error).toBeUndefined();
});
it('throws Error in case of invalid options', () => {
const {error} = PluginOptionSchema.validate({
path: 'not_blog',
postsPerPage: -1,
include: ['api/*', 'docs/*'],
routeBasePath: 'not_blog',
});
expect(error).toMatchSnapshot();
});
it('throws Error in case of invalid feed type', () => {
const {error} = PluginOptionSchema.validate({
feedOptions: {
type: 'none',
},
});
expect(error).toMatchSnapshot();
});
it('converts all feed type to array with other feed type', () => {
const {value} = PluginOptionSchema.validate({
feedOptions: {type: 'all'},
});
expect(value).toEqual({
...DEFAULT_OPTIONS,
feedOptions: {type: ['rss', 'atom', 'json'], copyright: ''},
});
});
it('accepts null type and return same', () => {
const {value, error} = PluginOptionSchema.validate({
feedOptions: {type: null},
});
expect(value).toEqual({
...DEFAULT_OPTIONS,
feedOptions: {type: null},
});
expect(error).toBeUndefined();
});
it('contains array with rss + atom for missing feed type', () => {
const {value} = PluginOptionSchema.validate({
feedOptions: {},
});
expect(value).toEqual(DEFAULT_OPTIONS);
});
it('has array with rss + atom, title for missing feed type', () => {
const {value} = PluginOptionSchema.validate({
feedOptions: {title: 'title'},
});
expect(value).toEqual({
...DEFAULT_OPTIONS,
feedOptions: {type: ['rss', 'atom'], title: 'title', copyright: ''},
});
});
});
describe('blog sidebar', () => {
it('accepts 0 sidebar count', () => {
const userOptions = {blogSidebarCount: 0};
const {value, error} = PluginOptionSchema.validate(userOptions);
expect(value).toEqual({...DEFAULT_OPTIONS, ...userOptions});
expect(error).toBeUndefined();
});
it('accepts "ALL" sidebar count', () => {
const userOptions = {blogSidebarCount: 'ALL'};
const {value, error} = PluginOptionSchema.validate(userOptions);
expect(value).toEqual({...DEFAULT_OPTIONS, ...userOptions});
expect(error).toBeUndefined();
});
it('rejects "abcdef" sidebar count', () => {
const userOptions = {blogSidebarCount: 'abcdef'};
const {error} = PluginOptionSchema.validate(userOptions);
expect(error).toMatchInlineSnapshot(
`[ValidationError: "blogSidebarCount" must be one of [ALL, number]]`,
);
});
it('accepts "all posts" sidebar title', () => {
const userOptions = {blogSidebarTitle: 'all posts'};
const {value, error} = PluginOptionSchema.validate(userOptions);
expect(value).toEqual({...DEFAULT_OPTIONS, ...userOptions});
expect(error).toBeUndefined();
});
it('rejects 42 sidebar title', () => {
const userOptions = {blogSidebarTitle: 42};
const {error} = PluginOptionSchema.validate(userOptions);
expect(error).toMatchInlineSnapshot(
`[ValidationError: "blogSidebarTitle" must be a string]`,
);
});
});

View file

@ -7,7 +7,7 @@
import type {BlogPost, BlogContent} from '../types';
import {getTranslationFiles, translateContent} from '../translations';
import {DEFAULT_OPTIONS} from '../pluginOptionSchema';
import {DEFAULT_OPTIONS} from '../options';
import {updateTranslationFileMessages} from '@docusaurus/utils';
import type {PluginOptions} from '@docusaurus/plugin-content-blog';

View file

@ -15,7 +15,7 @@ import type {
BlogPostFrontMatterAuthors,
} from '@docusaurus/plugin-content-blog';
export type AuthorsMap = Record<string, Author>;
export type AuthorsMap = {[authorKey: string]: Author};
const AuthorsMapSchema = Joi.object<AuthorsMap>()
.pattern(

View file

@ -31,7 +31,7 @@ import {
getContentPathList,
} from '@docusaurus/utils';
import type {LoadContext} from '@docusaurus/types';
import {validateBlogPostFrontMatter} from './blogFrontMatter';
import {validateBlogPostFrontMatter} from './frontMatter';
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
import logger from '@docusaurus/logger';
import type {
@ -43,9 +43,9 @@ export function truncate(fileString: string, truncateMarker: RegExp): string {
return fileString.split(truncateMarker, 1).shift()!;
}
export function getSourceToPermalink(
blogPosts: BlogPost[],
): Record<string, string> {
export function getSourceToPermalink(blogPosts: BlogPost[]): {
[aliasedPath: string]: string;
} {
return Object.fromEntries(
blogPosts.map(({metadata: {source, permalink}}) => [source, permalink]),
);
@ -247,7 +247,7 @@ async function processBlogSourceFile(
});
return result.date;
} catch (err) {
logger.error(err);
logger.warn(err);
return (await fs.stat(blogSourceAbsolute)).birthtime;
}
}

View file

@ -6,7 +6,7 @@
*/
declare module 'remark-admonitions' {
type Options = Record<string, unknown>;
type Options = {[key: string]: unknown};
const plugin: (options?: Options) => void;
export = plugin;

View file

@ -9,7 +9,6 @@ import {Feed, type Author as FeedAuthor, type Item as FeedItem} from 'feed';
import type {BlogPost} from './types';
import {
normalizeUrl,
posixPath,
mapAsyncSequential,
readOutputHTMLFile,
} from '@docusaurus/utils';
@ -128,10 +127,7 @@ async function createBlogFeedFile({
}
})();
try {
await fs.outputFile(
posixPath(path.join(generatePath, feedPath)),
feedContent,
);
await fs.outputFile(path.join(generatePath, feedPath), feedContent);
} catch (err) {
throw new Error(`Generating ${feedType} feed failed: ${err}.`);
}

View file

@ -74,8 +74,8 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
'{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
});
export function validateBlogPostFrontMatter(
frontMatter: Record<string, unknown>,
): BlogPostFrontMatter {
export function validateBlogPostFrontMatter(frontMatter: {
[key: string]: unknown;
}): BlogPostFrontMatter {
return validateFrontMatter(frontMatter, BlogFrontMatterSchema);
}

View file

@ -30,14 +30,7 @@ import type {
BlogContentPaths,
BlogMarkdownLoaderOptions,
} from './types';
import {PluginOptionSchema} from './pluginOptionSchema';
import type {
LoadContext,
Plugin,
HtmlTags,
OptionValidationContext,
ValidationResult,
} from '@docusaurus/types';
import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types';
import {
generateBlogPosts,
getSourceToPermalink,
@ -212,14 +205,14 @@ export default async function pluginContentBlog(
blogTagsListPath,
} = blogContents;
const blogItemsToMetadata: Record<string, BlogPostMetadata> = {};
const blogItemsToMetadata: {[postId: string]: BlogPostMetadata} = {};
const sidebarBlogPosts =
options.blogSidebarCount === 'ALL'
? blogPosts
: blogPosts.slice(0, options.blogSidebarCount);
if (archiveBasePath) {
if (archiveBasePath && blogPosts.length) {
const archiveUrl = normalizeUrl([
baseUrl,
routeBasePath,
@ -323,7 +316,7 @@ export default async function pluginContentBlog(
return;
}
const tagsModule: Record<string, TagModule> = Object.fromEntries(
const tagsModule: {[tagName: string]: TagModule} = Object.fromEntries(
Object.entries(blogTags).map(([, tag]) => {
const tagModule: TagModule = {
allTagsPath: blogTagsListPath,
@ -572,10 +565,4 @@ export default async function pluginContentBlog(
};
}
export function validateOptions({
validate,
options,
}: OptionValidationContext<PluginOptions>): ValidationResult<PluginOptions> {
const validatedOptions = validate(PluginOptionSchema, options);
return validatedOptions;
}
export {validateOptions} from './options';

View file

@ -13,7 +13,8 @@ import {
URISchema,
} from '@docusaurus/utils-validation';
import {GlobExcludeDefault} from '@docusaurus/utils';
import type {PluginOptions} from '@docusaurus/plugin-content-blog';
import type {PluginOptions, Options} from '@docusaurus/plugin-content-blog';
import type {OptionValidationContext} from '@docusaurus/types';
export const DEFAULT_OPTIONS: PluginOptions = {
feedOptions: {type: ['rss', 'atom'], copyright: ''},
@ -46,7 +47,7 @@ export const DEFAULT_OPTIONS: PluginOptions = {
sortPosts: 'descending',
};
export const PluginOptionSchema = Joi.object<PluginOptions>({
const PluginOptionSchema = Joi.object<PluginOptions>({
path: Joi.string().default(DEFAULT_OPTIONS.path),
archiveBasePath: Joi.string()
.default(DEFAULT_OPTIONS.archiveBasePath)
@ -125,4 +126,15 @@ export const PluginOptionSchema = Joi.object<PluginOptions>({
sortPosts: Joi.string()
.valid('descending', 'ascending')
.default(DEFAULT_OPTIONS.sortPosts),
});
}).default(DEFAULT_OPTIONS);
export function validateOptions({
validate,
options,
}: OptionValidationContext<Options, PluginOptions>): PluginOptions {
const validatedOptions = validate(
PluginOptionSchema,
options,
) as PluginOptions;
return validatedOptions;
}

View file

@ -6,8 +6,8 @@
*/
declare module '@docusaurus/plugin-content-blog' {
import type {RemarkAndRehypePluginOptions} from '@docusaurus/mdx-loader';
import type {FrontMatterTag} from '@docusaurus/utils';
import type {MDXOptions} from '@docusaurus/mdx-loader';
import type {FrontMatterTag, Tag} from '@docusaurus/utils';
import type {Overwrite} from 'utility-types';
export interface Assets {
@ -81,10 +81,7 @@ declare module '@docusaurus/plugin-content-blog' {
* @see {@link BlogPostMetadata.tags}
*/
tags?: FrontMatterTag[];
/**
* Custom slug appended after /<baseUrl>/<routeBasePath>/
* @see {@link BlogPostMetadata.slug}
*/
/** Custom slug appended after `/<baseUrl>/<routeBasePath>/` */
slug?: string;
/**
* Marks the post as draft and excludes it from the production build.
@ -130,25 +127,18 @@ declare module '@docusaurus/plugin-content-blog' {
/** @deprecated v1 legacy */
authorImageURL?: string;
/**
* @see {@link BlogPostMetadata.image}
*/
/** Used in the head meta. Should use `assets.image` in priority. */
image?: string;
/**
* Used in the head meta
*/
/** Used in the head meta. */
keywords?: string[];
/**
* Hide the right TOC
*/
/** Hide the right TOC. */
hide_table_of_contents?: boolean;
/**
* Minimum TOC heading level
* Minimum TOC heading level. Must be between 2 and 6 and lower or equal to
* the max value.
*/
toc_min_heading_level?: number;
/**
* Maximum TOC heading level
*/
/** Maximum TOC heading level. Must be between 2 and 6. */
toc_max_heading_level?: number;
};
@ -175,9 +165,7 @@ declare module '@docusaurus/plugin-content-blog' {
| (string | BlogPostFrontMatterAuthor)[];
export type BlogPostMetadata = {
/**
* Path to the Markdown source, with `@site` alias.
*/
/** Path to the Markdown source, with `@site` alias. */
readonly source: string;
/**
* Used to generate the page h1 heading, tab title, and pagination title.
@ -193,9 +181,7 @@ declare module '@docusaurus/plugin-content-blog' {
* render the date regardless of the existence of `Intl.DateTimeFormat`.
*/
readonly formattedDate: string;
/**
* Full link including base URL.
*/
/** Full link including base URL. */
readonly permalink: string;
/**
* Description used in the meta. Could be an empty string (empty content)
@ -229,17 +215,10 @@ declare module '@docusaurus/plugin-content-blog' {
* `assets.authorsImageUrls` on client side.
*/
readonly authors: Author[];
/**
* Front matter, as-is.
*/
readonly frontMatter: BlogPostFrontMatter & Record<string, unknown>;
/**
* Tags, normalized.
*/
readonly tags: readonly {
readonly label: string;
readonly permalink: string;
}[];
/** Front matter, as-is. */
readonly frontMatter: BlogPostFrontMatter & {[key: string]: unknown};
/** Tags, normalized. */
readonly tags: Tag[];
};
/**
* @returns The edit URL that's directly plugged into metadata.
@ -250,17 +229,11 @@ declare module '@docusaurus/plugin-content-blog' {
* site path. Usually the same as `options.path` but can be localized
*/
blogDirPath: string;
/**
* Path to this post file, relative to `blogDirPath`
*/
/** Path to this post file, relative to `blogDirPath`. */
blogPath: string;
/**
* @see {@link BlogPostMetadata.permalink}
*/
/** @see {@link BlogPostMetadata.permalink} */
permalink: string;
/**
* Locale name.
*/
/** Locale name. */
locale: string;
}) => string | undefined;
@ -301,7 +274,7 @@ declare module '@docusaurus/plugin-content-blog' {
/** Markdown content. */
content: string;
/** Front matter. */
frontMatter?: BlogPostFrontMatter & Record<string, unknown>;
frontMatter?: BlogPostFrontMatter & {[key: string]: unknown};
/** Options accepted by ngryman/reading-time. */
options?: ReadingTimeOptions;
}) => number;
@ -325,7 +298,7 @@ declare module '@docusaurus/plugin-content-blog' {
/**
* Plugin options after normalization.
*/
export type PluginOptions = RemarkAndRehypePluginOptions & {
export type PluginOptions = MDXOptions & {
/** Plugin ID. */
id?: string;
/**
@ -402,7 +375,7 @@ declare module '@docusaurus/plugin-content-blog' {
* unlocalized file. Ignored when `editUrl` is a function.
*/
editLocalizedFiles?: boolean;
admonitions: Record<string, unknown>;
admonitions: {[key: string]: unknown};
/** Path to the authors map file, relative to the blog content directory. */
authorsMapPath: string;
/** A callback to customize the reading time number displayed. */
@ -545,7 +518,7 @@ declare module '@theme/BlogTagsListPage' {
/** Blog sidebar. */
readonly sidebar: BlogSidebar;
/** A map from tag names to the full tag module. */
readonly tags: Readonly<Record<string, TagModule>>;
readonly tags: Readonly<{[tagName: string]: TagModule}>;
}
export default function BlogTagsListPage(props: Props): JSX.Element;

View file

@ -6,7 +6,7 @@
*/
import type {BlogContent, BlogPaginated} from './types';
import type {TranslationFileContent, TranslationFiles} from '@docusaurus/types';
import type {TranslationFileContent, TranslationFile} from '@docusaurus/types';
import type {PluginOptions} from '@docusaurus/plugin-content-blog';
function translateListPage(
@ -27,7 +27,7 @@ function translateListPage(
});
}
export function getTranslationFiles(options: PluginOptions): TranslationFiles {
export function getTranslationFiles(options: PluginOptions): TranslationFile[] {
return [
{
path: 'options',
@ -51,7 +51,7 @@ export function getTranslationFiles(options: PluginOptions): TranslationFiles {
export function translateContent(
content: BlogContent,
translationFiles: TranslationFiles,
translationFiles: TranslationFile[],
): BlogContent {
const {content: optionsTranslations} = translationFiles[0]!;
return {

View file

@ -5,10 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import type {
BrokenMarkdownLink,
ContentPaths,
} from '@docusaurus/utils/lib/markdownLinks';
import type {BrokenMarkdownLink, ContentPaths} from '@docusaurus/utils';
import type {BlogPostMetadata} from '@docusaurus/plugin-content-blog';
import type {Metadata as BlogPaginatedMetadata} from '@theme/BlogListPage';
@ -53,6 +50,6 @@ export type BlogMarkdownLoaderOptions = {
siteDir: string;
contentPaths: BlogContentPaths;
truncateMarker: RegExp;
sourceToPermalink: Record<string, string>;
sourceToPermalink: {[aliasedPath: string]: string};
onBrokenMarkdownLink: (brokenMarkdownLink: BlogBrokenMarkdownLink) => void;
};

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-docs",
"version": "2.0.0-beta.17",
"version": "2.0.0-beta.18",
"description": "Docs plugin for Docusaurus.",
"main": "lib/index.js",
"exports": {
@ -23,11 +23,11 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "2.0.0-beta.17",
"@docusaurus/logger": "2.0.0-beta.17",
"@docusaurus/mdx-loader": "2.0.0-beta.17",
"@docusaurus/utils": "2.0.0-beta.17",
"@docusaurus/utils-validation": "2.0.0-beta.17",
"@docusaurus/core": "2.0.0-beta.18",
"@docusaurus/logger": "2.0.0-beta.18",
"@docusaurus/mdx-loader": "2.0.0-beta.18",
"@docusaurus/utils": "2.0.0-beta.18",
"@docusaurus/utils-validation": "2.0.0-beta.18",
"combine-promises": "^1.1.0",
"fs-extra": "^10.0.1",
"import-fresh": "^3.3.0",
@ -39,8 +39,8 @@
"webpack": "^5.70.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-beta.17",
"@docusaurus/types": "2.0.0-beta.17",
"@docusaurus/module-type-aliases": "2.0.0-beta.18",
"@docusaurus/types": "2.0.0-beta.18",
"@types/js-yaml": "^4.0.5",
"@types/picomatch": "^2.3.0",
"commander": "^5.1.0",

View file

@ -1407,6 +1407,7 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"sidebarPosition": 0,
"source": "@site/docs/3-API/01_Core APIs/0 --- Client API.md",
"sourceDirName": "3-API/01_Core APIs",
"title": "Client API",
"unversionedId": "API/Core APIs/Client API",
},
{
@ -1415,6 +1416,7 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"sidebarPosition": 1,
"source": "@site/docs/3-API/01_Core APIs/1 --- Server API.md",
"sourceDirName": "3-API/01_Core APIs",
"title": "Server API",
"unversionedId": "API/Core APIs/Server API",
},
{
@ -1423,6 +1425,7 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"sidebarPosition": 0,
"source": "@site/docs/3-API/02_Extension APIs/0. Plugin API.md",
"sourceDirName": "3-API/02_Extension APIs",
"title": "Plugin API",
"unversionedId": "API/Extension APIs/Plugin API",
},
{
@ -1431,6 +1434,7 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"sidebarPosition": 1,
"source": "@site/docs/3-API/02_Extension APIs/1. Theme API.md",
"sourceDirName": "3-API/02_Extension APIs",
"title": "Theme API",
"unversionedId": "API/Extension APIs/Theme API",
},
{
@ -1439,6 +1443,7 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"sidebarPosition": 3,
"source": "@site/docs/3-API/03_api-end.md",
"sourceDirName": "3-API",
"title": "API End",
"unversionedId": "API/api-end",
},
{
@ -1447,6 +1452,7 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"sidebarPosition": 0,
"source": "@site/docs/3-API/00_api-overview.md",
"sourceDirName": "3-API",
"title": "API Overview",
"unversionedId": "API/api-overview",
},
{
@ -1458,6 +1464,7 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"sidebarPosition": 1,
"source": "@site/docs/Guides/z-guide1.md",
"sourceDirName": "Guides",
"title": "Guide 1",
"unversionedId": "Guides/guide1",
},
{
@ -1468,6 +1475,7 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"sidebarPosition": 2,
"source": "@site/docs/Guides/02-guide2.md",
"sourceDirName": "Guides",
"title": "Guide 2",
"unversionedId": "Guides/guide2",
},
{
@ -1479,6 +1487,7 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"sidebarPosition": 2.5,
"source": "@site/docs/Guides/0-guide2.5.md",
"sourceDirName": "Guides",
"title": "Guide 2.5",
"unversionedId": "Guides/guide2.5",
},
{
@ -1490,6 +1499,7 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"sidebarPosition": 3,
"source": "@site/docs/Guides/guide3.md",
"sourceDirName": "Guides",
"title": "Guide 3",
"unversionedId": "Guides/guide3",
},
{
@ -1500,6 +1510,7 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"sidebarPosition": undefined,
"source": "@site/docs/Guides/a-guide4.md",
"sourceDirName": "Guides",
"title": "Guide 4",
"unversionedId": "Guides/guide4",
},
{
@ -1510,6 +1521,7 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"sidebarPosition": undefined,
"source": "@site/docs/Guides/b-guide5.md",
"sourceDirName": "Guides",
"title": "Guide 5",
"unversionedId": "Guides/guide5",
},
{
@ -1518,6 +1530,7 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"sidebarPosition": 0,
"source": "@site/docs/0-getting-started.md",
"sourceDirName": ".",
"title": "Getting Started",
"unversionedId": "getting-started",
},
{
@ -1526,6 +1539,7 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"sidebarPosition": 1,
"source": "@site/docs/1-installation.md",
"sourceDirName": ".",
"title": "Installation",
"unversionedId": "installation",
},
],
@ -1535,10 +1549,6 @@ exports[`site with custom sidebar items generator sidebarItemsGenerator is calle
"type": "autogenerated",
},
"numberPrefixParser": [Function],
"options": {
"sidebarCollapsed": true,
"sidebarCollapsible": true,
},
"version": {
"contentPath": "docs",
"versionName": "current",

View file

@ -169,7 +169,9 @@ exports[`translateLoadedContent returns translated loaded content 1`] = `
},
],
"isLast": true,
"label": "current label (translated)",
"mainDocId": "",
"path": "/docs/",
"routePriority": undefined,
"sidebarFilePath": "any",
"sidebars": {
@ -221,9 +223,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = `
},
],
},
"versionLabel": "current label (translated)",
"versionName": "current",
"versionPath": "/docs/",
},
{
"contentPath": "any",
@ -311,7 +311,9 @@ exports[`translateLoadedContent returns translated loaded content 1`] = `
},
],
"isLast": true,
"label": "2.0.0 label (translated)",
"mainDocId": "",
"path": "/docs/",
"routePriority": undefined,
"sidebarFilePath": "any",
"sidebars": {
@ -363,9 +365,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = `
},
],
},
"versionLabel": "2.0.0 label (translated)",
"versionName": "2.0.0",
"versionPath": "/docs/",
},
{
"contentPath": "any",
@ -453,7 +453,9 @@ exports[`translateLoadedContent returns translated loaded content 1`] = `
},
],
"isLast": true,
"label": "1.0.0 label (translated)",
"mainDocId": "",
"path": "/docs/",
"routePriority": undefined,
"sidebarFilePath": "any",
"sidebars": {
@ -505,9 +507,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = `
},
],
},
"versionLabel": "1.0.0 label (translated)",
"versionName": "1.0.0",
"versionPath": "/docs/",
},
],
}

View file

@ -181,7 +181,7 @@ describe('docsVersion', () => {
DEFAULT_OPTIONS,
),
).rejects.toThrowErrorMatchingInlineSnapshot(
`"[docs]: there is no docs to version!"`,
`"[docs]: no docs found in <PROJECT_ROOT>/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/empty-site/docs."`,
);
});

View file

@ -18,16 +18,14 @@ import {
import {loadSidebars} from '../sidebars';
import type {Sidebars} from '../sidebars/types';
import {readVersionsMetadata} from '../versions';
import type {
DocFile,
DocMetadataBase,
VersionMetadata,
DocNavLink,
} from '../types';
import type {DocFile} from '../types';
import type {
MetadataOptions,
PluginOptions,
EditUrlFunction,
DocMetadataBase,
VersionMetadata,
PropNavigationLink,
} from '@docusaurus/plugin-content-docs';
import type {LoadContext} from '@docusaurus/types';
import {DEFAULT_OPTIONS} from '../options';
@ -43,7 +41,7 @@ const createFakeDocFile = ({
markdown = 'some markdown content',
}: {
source: string;
frontMatter?: Record<string, string>;
frontMatter?: {[key: string]: string};
markdown?: string;
}): DocFile => {
const content = `---
@ -123,7 +121,11 @@ function createTestUtils({
}
async function generateNavigation(docFiles: DocFile[]): Promise<{
pagination: {prev?: DocNavLink; next?: DocNavLink; id: string}[];
pagination: {
prev?: PropNavigationLink;
next?: PropNavigationLink;
id: string;
}[];
sidebars: Sidebars;
}> {
const rawDocs = docFiles.map((docFile) =>
@ -166,7 +168,7 @@ describe('simple site', () => {
loadSiteOptions: {options: Partial<PluginOptions>} = {options: {}},
) {
const siteDir = path.join(fixtureDir, 'simple-site');
const context = await loadContext(siteDir);
const context = await loadContext({siteDir});
const options = {
id: DEFAULT_PLUGIN_ID,
...DEFAULT_OPTIONS,
@ -521,7 +523,8 @@ describe('versioned site', () => {
},
) {
const siteDir = path.join(fixtureDir, 'versioned-site');
const context = await loadContext(siteDir, {
const context = await loadContext({
siteDir,
locale: loadSiteOptions.locale,
});
const options = {

View file

@ -5,19 +5,19 @@
* LICENSE file in the root directory of this source tree.
*/
import {validateDocFrontMatter} from '../docFrontMatter';
import type {DocFrontMatter} from '../types';
import {validateDocFrontMatter} from '../frontMatter';
import type {DocFrontMatter} from '@docusaurus/plugin-content-docs';
import escapeStringRegexp from 'escape-string-regexp';
function testField(params: {
prefix: string;
validFrontMatters: DocFrontMatter[];
convertibleFrontMatter?: [
ConvertibleFrontMatter: Record<string, unknown>,
ConvertibleFrontMatter: {[key: string]: unknown},
ConvertedFrontMatter: DocFrontMatter,
][];
invalidFrontMatters?: [
InvalidFrontMatter: Record<string, unknown>,
InvalidFrontMatter: {[key: string]: unknown},
ErrorMessage: string,
][];
}) {

View file

@ -12,9 +12,9 @@ describe('toGlobalDataVersion', () => {
expect(
toGlobalDataVersion({
versionName: 'current',
versionLabel: 'Label',
label: 'Label',
isLast: true,
versionPath: '/current',
path: '/current',
mainDocId: 'main',
docs: [
{
@ -86,9 +86,9 @@ describe('toGlobalDataVersion', () => {
sidebar: 'tutorial',
},
],
versionBanner: 'unreleased',
versionBadge: true,
versionClassName: 'current-cls',
banner: 'unreleased',
badge: true,
className: 'current-cls',
tagsPath: '/current/tags',
contentPath: '',
contentPathLocalized: '',

View file

@ -17,10 +17,10 @@ import {loadContext} from '@docusaurus/core/src/server/index';
import {applyConfigureWebpack} from '@docusaurus/core/src/webpack/utils';
import type {RouteConfig} from '@docusaurus/types';
import {posixPath, DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import {sortConfig} from '@docusaurus/core/src/server/plugins';
import {sortConfig} from '@docusaurus/core/src/server/plugins/routeConfig';
import * as cliDocs from '../cli';
import {OptionsSchema} from '../options';
import {validateOptions} from '../options';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import type {LoadedVersion} from '../types';
import type {
@ -52,7 +52,7 @@ Available ids are:\n- ${version.docs.map((d) => d.unversionedId).join('\n- ')}`,
const createFakeActions = (contentDir: string) => {
const routeConfigs: RouteConfig[] = [];
const dataContainer: Record<string, unknown> = {};
const dataContainer: {[key: string]: unknown} = {};
const globalDataContainer: {pluginName?: {pluginId: unknown}} = {};
const actions = {
@ -115,12 +115,15 @@ Entries created:
describe('sidebar', () => {
it('site with wrong sidebar content', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'simple-site');
const context = await loadContext(siteDir);
const context = await loadContext({siteDir});
const sidebarPath = path.join(siteDir, 'wrong-sidebars.json');
const plugin = await pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
sidebarPath,
validateOptions({
validate: normalizePluginOptions,
options: {
sidebarPath,
},
}),
);
await expect(plugin.loadContent!()).rejects.toThrowErrorMatchingSnapshot();
@ -128,13 +131,16 @@ describe('sidebar', () => {
it('site with wrong sidebar file path', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label');
const context = await loadContext(siteDir);
const context = await loadContext({siteDir});
await expect(async () => {
const plugin = await pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
sidebarPath: 'wrong-path-sidebar.json',
validateOptions({
validate: normalizePluginOptions,
options: {
sidebarPath: 'wrong-path-sidebar.json',
},
}),
);
await plugin.loadContent!();
@ -149,11 +155,14 @@ describe('sidebar', () => {
it('site with undefined sidebar', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label');
const context = await loadContext(siteDir);
const context = await loadContext({siteDir});
const plugin = await pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
sidebarPath: undefined,
validateOptions({
validate: normalizePluginOptions,
options: {
sidebarPath: undefined,
},
}),
);
const result = await plugin.loadContent!();
@ -164,11 +173,14 @@ describe('sidebar', () => {
it('site with disabled sidebar', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label');
const context = await loadContext(siteDir);
const context = await loadContext({siteDir});
const plugin = await pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
sidebarPath: false,
validateOptions({
validate: normalizePluginOptions,
options: {
sidebarPath: false,
},
}),
);
const result = await plugin.loadContent!();
@ -182,11 +194,11 @@ describe('empty/no docs website', () => {
const siteDir = path.join(__dirname, '__fixtures__', 'empty-site');
it('no files in docs folder', async () => {
const context = await loadContext(siteDir);
const context = await loadContext({siteDir});
await fs.ensureDir(path.join(siteDir, 'docs'));
const plugin = await pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {}),
validateOptions({validate: normalizePluginOptions, options: {}}),
);
await expect(
plugin.loadContent!(),
@ -196,12 +208,15 @@ describe('empty/no docs website', () => {
});
it('docs folder does not exist', async () => {
const context = await loadContext(siteDir);
const context = await loadContext({siteDir});
await expect(
pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
path: `path/does/not/exist`,
validateOptions({
validate: normalizePluginOptions,
options: {
path: 'path/does/not/exist',
},
}),
),
).rejects.toThrowErrorMatchingInlineSnapshot(
@ -213,13 +228,16 @@ describe('empty/no docs website', () => {
describe('simple website', () => {
async function loadSite() {
const siteDir = path.join(__dirname, '__fixtures__', 'simple-site');
const context = await loadContext(siteDir);
const context = await loadContext({siteDir});
const sidebarPath = path.join(siteDir, 'sidebars.json');
const plugin = await pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
path: 'docs',
sidebarPath,
validateOptions({
validate: normalizePluginOptions,
options: {
path: 'docs',
sidebarPath,
},
}),
);
const pluginContentDir = path.join(context.generatedFilesDir, plugin.name);
@ -323,14 +341,17 @@ describe('simple website', () => {
describe('versioned website', () => {
async function loadSite() {
const siteDir = path.join(__dirname, '__fixtures__', 'versioned-site');
const context = await loadContext(siteDir);
const context = await loadContext({siteDir});
const sidebarPath = path.join(siteDir, 'sidebars.json');
const routeBasePath = 'docs';
const plugin = await pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
routeBasePath,
sidebarPath,
validateOptions({
validate: normalizePluginOptions,
options: {
routeBasePath,
sidebarPath,
},
}),
);
const pluginContentDir = path.join(context.generatedFilesDir, plugin.name);
@ -449,17 +470,20 @@ describe('versioned website', () => {
describe('versioned website (community)', () => {
async function loadSite() {
const siteDir = path.join(__dirname, '__fixtures__', 'versioned-site');
const context = await loadContext(siteDir);
const context = await loadContext({siteDir});
const sidebarPath = path.join(siteDir, 'community_sidebars.json');
const routeBasePath = 'community';
const pluginId = 'community';
const plugin = await pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
id: 'community',
path: 'community',
routeBasePath,
sidebarPath,
validateOptions({
validate: normalizePluginOptions,
options: {
id: 'community',
path: 'community',
routeBasePath,
sidebarPath,
},
}),
);
const pluginContentDir = path.join(context.generatedFilesDir, plugin.name);
@ -554,13 +578,16 @@ describe('versioned website (community)', () => {
describe('site with doc label', () => {
async function loadSite() {
const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label');
const context = await loadContext(siteDir);
const context = await loadContext({siteDir});
const sidebarPath = path.join(siteDir, 'sidebars.json');
const plugin = await pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
path: 'docs',
sidebarPath,
validateOptions({
validate: normalizePluginOptions,
options: {
path: 'docs',
sidebarPath,
},
}),
);
@ -593,11 +620,14 @@ describe('site with full autogenerated sidebar', () => {
'__fixtures__',
'site-with-autogenerated-sidebar',
);
const context = await loadContext(siteDir);
const context = await loadContext({siteDir});
const plugin = await pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
path: 'docs',
validateOptions({
validate: normalizePluginOptions,
options: {
path: 'docs',
},
}),
);
@ -645,17 +675,20 @@ describe('site with partial autogenerated sidebars', () => {
'__fixtures__',
'site-with-autogenerated-sidebar',
);
const context = await loadContext(siteDir, {});
const context = await loadContext({siteDir});
const plugin = await pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
path: 'docs',
sidebarPath: path.join(
__dirname,
'__fixtures__',
'site-with-autogenerated-sidebar',
'partialAutogeneratedSidebars.js',
),
validateOptions({
validate: normalizePluginOptions,
options: {
path: 'docs',
sidebarPath: path.join(
__dirname,
'__fixtures__',
'site-with-autogenerated-sidebar',
'partialAutogeneratedSidebars.js',
),
},
}),
);
@ -698,17 +731,20 @@ describe('site with partial autogenerated sidebars 2 (fix #4638)', () => {
'__fixtures__',
'site-with-autogenerated-sidebar',
);
const context = await loadContext(siteDir, {});
const context = await loadContext({siteDir});
const plugin = await pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
path: 'docs',
sidebarPath: path.join(
__dirname,
'__fixtures__',
'site-with-autogenerated-sidebar',
'partialAutogeneratedSidebars2.js',
),
validateOptions({
validate: normalizePluginOptions,
options: {
path: 'docs',
sidebarPath: path.join(
__dirname,
'__fixtures__',
'site-with-autogenerated-sidebar',
'partialAutogeneratedSidebars2.js',
),
},
}),
);
@ -732,12 +768,15 @@ describe('site with custom sidebar items generator', () => {
'__fixtures__',
'site-with-autogenerated-sidebar',
);
const context = await loadContext(siteDir);
const context = await loadContext({siteDir});
const plugin = await pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
path: 'docs',
sidebarItemsGenerator,
validateOptions({
validate: normalizePluginOptions,
options: {
path: 'docs',
sidebarItemsGenerator,
},
}),
);
const content = (await plugin.loadContent?.())!;

View file

@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {createTempRepo} from '@testing-utils/git';
import {jest} from '@jest/globals';
import fs from 'fs-extra';
import path from 'path';
@ -47,7 +48,7 @@ describe('getFileLastUpdate', () => {
it('non-existing file', async () => {
const consoleMock = jest
.spyOn(console, 'error')
.spyOn(console, 'warn')
.mockImplementation(() => {});
const nonExistingFileName = '.nonExisting';
const nonExistingFilePath = path.join(
@ -65,13 +66,40 @@ describe('getFileLastUpdate', () => {
consoleMock.mockRestore();
});
it('temporary created file that has no git timestamp', async () => {
const tempFilePath = path.join(__dirname, '__fixtures__', '.temp');
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

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {OptionsSchema, DEFAULT_OPTIONS, validateOptions} from '../options';
import {validateOptions, DEFAULT_OPTIONS} from '../options';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import {DefaultSidebarItemsGenerator} from '../sidebars/generator';
import {
@ -13,27 +13,26 @@ import {
DisabledNumberPrefixParser,
} from '../numberPrefix';
import {GlobExcludeDefault} from '@docusaurus/utils';
import type {PluginOptions} from '@docusaurus/plugin-content-docs';
import type {Options} from '@docusaurus/plugin-content-docs';
// the type of remark/rehype plugins is function
const markdownPluginsFunctionStub = () => {};
const markdownPluginsObjectStub = {};
function testValidateOptions(options: Partial<PluginOptions>) {
return validateOptions({
options: {
...DEFAULT_OPTIONS,
...options,
},
validate: normalizePluginOptions,
});
function testValidate(options: Options) {
return validateOptions({validate: normalizePluginOptions, options});
}
const defaultOptions = {
...DEFAULT_OPTIONS,
id: 'default',
// The admonitions plugin is automatically added. Not really worth testing
remarkPlugins: expect.any(Array),
};
describe('normalizeDocsPluginOptions', () => {
it('returns default options for undefined user options', async () => {
const {value, error} = await OptionsSchema.validate({});
expect(value).toEqual(DEFAULT_OPTIONS);
expect(error).toBeUndefined();
expect(testValidate({})).toEqual(defaultOptions);
});
it('accepts correctly defined user options', async () => {
@ -77,14 +76,15 @@ describe('normalizeDocsPluginOptions', () => {
sidebarCollapsible: false,
sidebarCollapsed: false,
};
const {value, error} = await OptionsSchema.validate(userOptions);
expect(value).toEqual(userOptions);
expect(error).toBeUndefined();
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
remarkPlugins: [...userOptions.remarkPlugins, expect.any(Array)],
});
});
it('accepts correctly defined remark and rehype plugin options', async () => {
const userOptions = {
...DEFAULT_OPTIONS,
beforeDefaultRemarkPlugins: [],
beforeDefaultRehypePlugins: [markdownPluginsFunctionStub],
remarkPlugins: [[markdownPluginsFunctionStub, {option1: '42'}]],
@ -93,85 +93,73 @@ describe('normalizeDocsPluginOptions', () => {
[markdownPluginsFunctionStub, {option1: '42'}],
],
};
const {value, error} = await OptionsSchema.validate(userOptions);
expect(value).toEqual(userOptions);
expect(error).toBeUndefined();
expect(testValidate(userOptions)).toEqual({
...defaultOptions,
...userOptions,
remarkPlugins: [...userOptions.remarkPlugins, expect.any(Array)],
});
});
it('accepts admonitions false', async () => {
const admonitionsFalse = {
...DEFAULT_OPTIONS,
admonitions: false,
};
const {value, error} = OptionsSchema.validate(admonitionsFalse);
expect(value).toEqual(admonitionsFalse);
expect(error).toBeUndefined();
});
it('accepts numberPrefixParser function', () => {
function customNumberPrefixParser() {}
expect(
normalizePluginOptions(OptionsSchema, {
...DEFAULT_OPTIONS,
numberPrefixParser: customNumberPrefixParser,
}),
).toEqual({
...DEFAULT_OPTIONS,
id: 'default',
numberPrefixParser: customNumberPrefixParser,
});
});
it('accepts numberPrefixParser false', () => {
expect(
normalizePluginOptions(OptionsSchema, {
...DEFAULT_OPTIONS,
numberPrefixParser: false,
}),
).toEqual({
...DEFAULT_OPTIONS,
id: 'default',
numberPrefixParser: DisabledNumberPrefixParser,
});
});
it('accepts numberPrefixParser true', () => {
expect(
normalizePluginOptions(OptionsSchema, {
...DEFAULT_OPTIONS,
numberPrefixParser: true,
}),
).toEqual({
...DEFAULT_OPTIONS,
id: 'default',
numberPrefixParser: DefaultNumberPrefixParser,
expect(testValidate(admonitionsFalse)).toEqual({
...defaultOptions,
...admonitionsFalse,
});
});
it('rejects admonitions true', async () => {
const admonitionsTrue = {
...DEFAULT_OPTIONS,
admonitions: true,
};
const {error} = OptionsSchema.validate(admonitionsTrue);
expect(error).toMatchInlineSnapshot(
`[ValidationError: "admonitions" contains an invalid value]`,
expect(() =>
testValidate(admonitionsTrue),
).toThrowErrorMatchingInlineSnapshot(
`"\\"admonitions\\" contains an invalid value"`,
);
});
it('accepts numberPrefixParser function', () => {
function customNumberPrefixParser() {}
expect(
testValidate({numberPrefixParser: customNumberPrefixParser}),
).toEqual({
...defaultOptions,
numberPrefixParser: customNumberPrefixParser,
});
});
it('accepts numberPrefixParser false', () => {
expect(testValidate({numberPrefixParser: false})).toEqual({
...defaultOptions,
numberPrefixParser: DisabledNumberPrefixParser,
});
});
it('accepts numberPrefixParser true', () => {
expect(testValidate({numberPrefixParser: true})).toEqual({
...defaultOptions,
numberPrefixParser: DefaultNumberPrefixParser,
});
});
it('rejects invalid remark plugin options', () => {
expect(() => {
normalizePluginOptions(OptionsSchema, {
expect(() =>
testValidate({
remarkPlugins: [[{option1: '42'}, markdownPluginsFunctionStub]],
});
}).toThrowErrorMatchingInlineSnapshot(
`"\\"remarkPlugins[0]\\" does not match any of the allowed types"`,
);
}),
).toThrowErrorMatchingInlineSnapshot(`
"\\"remarkPlugins[0]\\" does not look like a valid MDX plugin config. A plugin config entry should be one of:
- A tuple, like \`[require(\\"rehype-katex\\"), { strict: false }]\`, or
- A simple module, like \`require(\\"remark-math\\")\`"
`);
});
it('rejects invalid rehype plugin options', () => {
expect(() => {
normalizePluginOptions(OptionsSchema, {
expect(() =>
testValidate({
rehypePlugins: [
[
markdownPluginsFunctionStub,
@ -179,61 +167,53 @@ describe('normalizeDocsPluginOptions', () => {
markdownPluginsFunctionStub,
],
],
});
}).toThrowErrorMatchingInlineSnapshot(
`"\\"rehypePlugins[0]\\" does not match any of the allowed types"`,
);
}),
).toThrowErrorMatchingInlineSnapshot(`
"\\"rehypePlugins[0]\\" does not look like a valid MDX plugin config. A plugin config entry should be one of:
- A tuple, like \`[require(\\"rehype-katex\\"), { strict: false }]\`, or
- A simple module, like \`require(\\"remark-math\\")\`"
`);
});
it('rejects bad path inputs', () => {
expect(() => {
normalizePluginOptions(OptionsSchema, {
path: 2,
});
}).toThrowErrorMatchingInlineSnapshot(`"\\"path\\" must be a string"`);
expect(() => testValidate({path: 2})).toThrowErrorMatchingInlineSnapshot(
`"\\"path\\" must be a string"`,
);
});
it('rejects bad include inputs', () => {
expect(() => {
normalizePluginOptions(OptionsSchema, {
include: '**/*.{md,mdx}',
});
}).toThrowErrorMatchingInlineSnapshot(`"\\"include\\" must be an array"`);
expect(() =>
testValidate({include: '**/*.{md,mdx}'}),
).toThrowErrorMatchingInlineSnapshot(`"\\"include\\" must be an array"`);
});
it('rejects bad showLastUpdateTime inputs', () => {
expect(() => {
normalizePluginOptions(OptionsSchema, {
showLastUpdateTime: 'true',
});
}).toThrowErrorMatchingInlineSnapshot(
expect(() =>
testValidate({showLastUpdateTime: 'true'}),
).toThrowErrorMatchingInlineSnapshot(
`"\\"showLastUpdateTime\\" must be a boolean"`,
);
});
it('rejects bad remarkPlugins input', () => {
expect(() => {
normalizePluginOptions(OptionsSchema, {
remarkPlugins: 'remark-math',
});
}).toThrowErrorMatchingInlineSnapshot(
expect(() =>
testValidate({remarkPlugins: 'remark-math'}),
).toThrowErrorMatchingInlineSnapshot(
`"\\"remarkPlugins\\" must be an array"`,
);
});
it('rejects bad lastVersion', () => {
expect(() => {
normalizePluginOptions(OptionsSchema, {
lastVersion: false,
});
}).toThrowErrorMatchingInlineSnapshot(
expect(() =>
testValidate({lastVersion: false}),
).toThrowErrorMatchingInlineSnapshot(
`"\\"lastVersion\\" must be a string"`,
);
});
it('rejects bad versions', () => {
expect(() => {
normalizePluginOptions(OptionsSchema, {
expect(() =>
testValidate({
versions: {
current: {
hey: 3,
@ -243,32 +223,29 @@ describe('normalizeDocsPluginOptions', () => {
label: 'world',
},
},
});
}).toThrowErrorMatchingInlineSnapshot(
}),
).toThrowErrorMatchingInlineSnapshot(
`"\\"versions.current.hey\\" is not allowed"`,
);
});
it('handles sidebarCollapsed option inconsistencies', () => {
expect(
testValidateOptions({
...DEFAULT_OPTIONS,
testValidate({
sidebarCollapsible: true,
sidebarCollapsed: undefined,
}).sidebarCollapsed,
).toBe(true);
expect(
testValidateOptions({
...DEFAULT_OPTIONS,
testValidate({
sidebarCollapsible: false,
sidebarCollapsed: undefined,
}).sidebarCollapsed,
).toBe(false);
expect(
testValidateOptions({
...DEFAULT_OPTIONS,
testValidate({
sidebarCollapsible: false,
sidebarCollapsed: true,
}).sidebarCollapsed,

View file

@ -16,7 +16,7 @@ describe('toTagDocListProp', () => {
it('works', () => {
const tag: Tag = {
name: 'tag1',
label: 'tag1',
permalink: '/tag1',
docIds: ['id1', 'id3'],
};
@ -54,7 +54,7 @@ describe('toTagDocListProp', () => {
expect(result).toEqual({
allTagsPath,
name: tag.name,
name: tag.label,
permalink: tag.permalink,
docs: [doc3, doc1], // docs sorted by title, ignore "id5" absence
});

View file

@ -5,12 +5,13 @@
* LICENSE file in the root directory of this source tree.
*/
import type {LoadedContent, DocMetadata, LoadedVersion} from '../types';
import type {LoadedContent, LoadedVersion} from '../types';
import {CURRENT_VERSION_NAME} from '../constants';
import {
getLoadedContentTranslationFiles,
translateLoadedContent,
} from '../translations';
import type {DocMetadata} from '@docusaurus/plugin-content-docs';
import {updateTranslationFileMessages} from '@docusaurus/utils';
function createSampleDoc(doc: Pick<DocMetadata, 'id'>): DocMetadata {
@ -36,8 +37,8 @@ function createSampleVersion(
version: Pick<LoadedVersion, 'versionName'>,
): LoadedVersion {
return {
versionLabel: `${version.versionName} label`,
versionPath: '/docs/',
label: `${version.versionName} label`,
path: '/docs/',
mainDocId: '',
routePriority: undefined,
sidebarFilePath: 'any',
@ -45,21 +46,11 @@ function createSampleVersion(
contentPath: 'any',
contentPathLocalized: 'any',
docs: [
createSampleDoc({
id: 'doc1',
}),
createSampleDoc({
id: 'doc2',
}),
createSampleDoc({
id: 'doc3',
}),
createSampleDoc({
id: 'doc4',
}),
createSampleDoc({
id: 'doc5',
}),
createSampleDoc({id: 'doc1'}),
createSampleDoc({id: 'doc2'}),
createSampleDoc({id: 'doc3'}),
createSampleDoc({id: 'doc4'}),
createSampleDoc({id: 'doc5'}),
],
sidebars: {
docs: [

View file

@ -15,9 +15,11 @@ import {
} from '../versions';
import {DEFAULT_OPTIONS} from '../options';
import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import type {VersionMetadata} from '../types';
import type {I18n} from '@docusaurus/types';
import type {PluginOptions} from '@docusaurus/plugin-content-docs';
import type {
PluginOptions,
VersionMetadata,
} from '@docusaurus/plugin-content-docs';
const DefaultI18N: I18n = {
currentLocale: 'en',
@ -85,12 +87,12 @@ describe('readVersionsMetadata', () => {
routePriority: -1,
sidebarFilePath: undefined,
tagsPath: '/docs/tags',
versionLabel: 'Next',
label: 'Next',
versionName: 'current',
versionPath: '/docs',
versionBanner: null,
versionBadge: false,
versionClassName: 'docs-version-current',
path: '/docs',
banner: null,
badge: false,
className: 'docs-version-current',
};
return {simpleSiteDir, defaultOptions, defaultContext, vCurrent};
}
@ -120,7 +122,7 @@ describe('readVersionsMetadata', () => {
expect(versionsMetadata).toEqual([
{
...vCurrent,
versionPath: '/myBaseUrl/docs',
path: '/myBaseUrl/docs',
tagsPath: '/myBaseUrl/docs/tags',
},
]);
@ -148,13 +150,13 @@ describe('readVersionsMetadata', () => {
expect(versionsMetadata).toEqual([
{
...vCurrent,
versionPath: '/myBaseUrl/docs/current-path',
versionLabel: 'current-label',
path: '/myBaseUrl/docs/current-path',
label: 'current-label',
routePriority: undefined,
sidebarFilePath: undefined,
tagsPath: '/myBaseUrl/docs/current-path/tags',
versionEditUrl: undefined,
versionEditUrlLocalized: undefined,
editUrl: undefined,
editUrlLocalized: undefined,
},
]);
});
@ -245,12 +247,12 @@ describe('readVersionsMetadata', () => {
routePriority: undefined,
sidebarFilePath: path.join(versionedSiteDir, 'sidebars.json'),
tagsPath: '/docs/next/tags',
versionLabel: 'Next',
label: 'Next',
versionName: 'current',
versionPath: '/docs/next',
versionBanner: 'unreleased',
versionBadge: true,
versionClassName: 'docs-version-current',
path: '/docs/next',
banner: 'unreleased',
badge: true,
className: 'docs-version-current',
};
const v101: VersionMetadata = {
@ -269,12 +271,12 @@ describe('readVersionsMetadata', () => {
'versioned_sidebars/version-1.0.1-sidebars.json',
),
tagsPath: '/docs/tags',
versionLabel: '1.0.1',
label: '1.0.1',
versionName: '1.0.1',
versionPath: '/docs',
versionBanner: null,
versionBadge: true,
versionClassName: 'docs-version-1.0.1',
path: '/docs',
banner: null,
badge: true,
className: 'docs-version-1.0.1',
};
const v100: VersionMetadata = {
@ -293,12 +295,12 @@ describe('readVersionsMetadata', () => {
'versioned_sidebars/version-1.0.0-sidebars.json',
),
tagsPath: '/docs/1.0.0/tags',
versionLabel: '1.0.0',
label: '1.0.0',
versionName: '1.0.0',
versionPath: '/docs/1.0.0',
versionBanner: 'unmaintained',
versionBadge: true,
versionClassName: 'docs-version-1.0.0',
path: '/docs/1.0.0',
banner: 'unmaintained',
badge: true,
className: 'docs-version-1.0.0',
};
const vWithSlugs: VersionMetadata = {
@ -317,12 +319,12 @@ describe('readVersionsMetadata', () => {
'versioned_sidebars/version-withSlugs-sidebars.json',
),
tagsPath: '/docs/withSlugs/tags',
versionLabel: 'withSlugs',
label: 'withSlugs',
versionName: 'withSlugs',
versionPath: '/docs/withSlugs',
versionBanner: 'unmaintained',
versionBadge: true,
versionClassName: 'docs-version-withSlugs',
path: '/docs/withSlugs',
banner: 'unmaintained',
badge: true,
className: 'docs-version-withSlugs',
};
return {
@ -393,27 +395,27 @@ describe('readVersionsMetadata', () => {
{
...vCurrent,
tagsPath: '/docs/current-path/tags',
versionPath: '/docs/current-path',
versionBanner: 'unmaintained',
versionBadge: false,
versionClassName: 'custom-current-className',
path: '/docs/current-path',
banner: 'unmaintained',
badge: false,
className: 'custom-current-className',
},
{
...v101,
isLast: false,
routePriority: undefined,
tagsPath: '/docs/1.0.1/tags',
versionPath: '/docs/1.0.1',
versionBanner: 'unreleased',
path: '/docs/1.0.1',
banner: 'unreleased',
},
{
...v100,
isLast: true,
routePriority: -1,
tagsPath: '/docs/tags',
versionLabel: '1.0.0-label',
versionPath: '/docs',
versionBanner: 'unreleased',
label: '1.0.0-label',
path: '/docs',
banner: 'unreleased',
},
vWithSlugs,
]);
@ -434,30 +436,30 @@ describe('readVersionsMetadata', () => {
expect(versionsMetadata).toEqual([
{
...vCurrent,
versionEditUrl:
editUrl:
'https://github.com/facebook/docusaurus/edit/main/website/docs',
versionEditUrlLocalized:
editUrlLocalized:
'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current',
},
{
...v101,
versionEditUrl:
editUrl:
'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-1.0.1',
versionEditUrlLocalized:
editUrlLocalized:
'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/version-1.0.1',
},
{
...v100,
versionEditUrl:
editUrl:
'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-1.0.0',
versionEditUrlLocalized:
editUrlLocalized:
'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/version-1.0.0',
},
{
...vWithSlugs,
versionEditUrl:
editUrl:
'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-withSlugs',
versionEditUrlLocalized:
editUrlLocalized:
'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/version-withSlugs',
},
]);
@ -479,30 +481,30 @@ describe('readVersionsMetadata', () => {
expect(versionsMetadata).toEqual([
{
...vCurrent,
versionEditUrl:
editUrl:
'https://github.com/facebook/docusaurus/edit/main/website/docs',
versionEditUrlLocalized:
editUrlLocalized:
'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current',
},
{
...v101,
versionEditUrl:
editUrl:
'https://github.com/facebook/docusaurus/edit/main/website/docs',
versionEditUrlLocalized:
editUrlLocalized:
'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current',
},
{
...v100,
versionEditUrl:
editUrl:
'https://github.com/facebook/docusaurus/edit/main/website/docs',
versionEditUrlLocalized:
editUrlLocalized:
'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current',
},
{
...vWithSlugs,
versionEditUrl:
editUrl:
'https://github.com/facebook/docusaurus/edit/main/website/docs',
versionEditUrlLocalized:
editUrlLocalized:
'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current',
},
]);
@ -538,9 +540,9 @@ describe('readVersionsMetadata', () => {
isLast: true,
routePriority: -1,
tagsPath: '/docs/tags',
versionPath: '/docs',
versionBanner: null,
versionBadge: false,
path: '/docs',
banner: null,
badge: false,
},
]);
});
@ -679,12 +681,12 @@ describe('readVersionsMetadata', () => {
routePriority: undefined,
sidebarFilePath: path.join(versionedSiteDir, 'sidebars.json'),
tagsPath: '/communityBasePath/next/tags',
versionLabel: 'Next',
label: 'Next',
versionName: 'current',
versionPath: '/communityBasePath/next',
versionBanner: 'unreleased',
versionBadge: true,
versionClassName: 'docs-version-current',
path: '/communityBasePath/next',
banner: 'unreleased',
badge: true,
className: 'docs-version-current',
};
const v100: VersionMetadata = {
@ -703,12 +705,12 @@ describe('readVersionsMetadata', () => {
'community_versioned_sidebars/version-1.0.0-sidebars.json',
),
tagsPath: '/communityBasePath/tags',
versionLabel: '1.0.0',
label: '1.0.0',
versionName: '1.0.0',
versionPath: '/communityBasePath',
versionBanner: null,
versionBadge: true,
versionClassName: 'docs-version-1.0.0',
path: '/communityBasePath',
banner: null,
badge: true,
className: 'docs-version-1.0.0',
};
return {versionedSiteDir, defaultOptions, defaultContext, vCurrent, v100};
@ -735,7 +737,7 @@ describe('readVersionsMetadata', () => {
expect(versionsMetadata).toEqual([
// vCurrent removed
{...v100, versionBadge: false},
{...v100, badge: false},
]);
});
@ -753,9 +755,9 @@ describe('readVersionsMetadata', () => {
isLast: true,
routePriority: -1,
tagsPath: '/communityBasePath/tags',
versionPath: '/communityBasePath',
versionBanner: null,
versionBadge: false,
path: '/communityBasePath',
banner: null,
badge: false,
},
]);
});

View file

@ -5,7 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
import type {CategoryGeneratedIndexMetadata, DocMetadataBase} from './types';
import type {
CategoryGeneratedIndexMetadata,
DocMetadataBase,
} from '@docusaurus/plugin-content-docs';
import type {SidebarItemCategoryWithGeneratedIndex} from './sidebars/types';
import {type SidebarsUtils, toNavigationLink} from './sidebars/utils';
import {createDocsByIdIndex} from './docs';
@ -17,7 +20,7 @@ function getCategoryGeneratedIndexMetadata({
}: {
category: SidebarItemCategoryWithGeneratedIndex;
sidebarsUtils: SidebarsUtils;
docsById: Record<string, DocMetadataBase>;
docsById: {[docId: string]: DocMetadataBase};
}): CategoryGeneratedIndexMetadata {
const {sidebarName, previous, next} =
sidebarsUtils.getCategoryGeneratedIndexNavigation(category.link.permalink);
@ -29,8 +32,10 @@ function getCategoryGeneratedIndexMetadata({
slug: category.link.slug,
permalink: category.link.permalink,
sidebar: sidebarName!,
previous: toNavigationLink(previous, docsById),
next: toNavigationLink(next, docsById),
navigation: {
previous: toNavigationLink(previous, docsById),
next: toNavigationLink(next, docsById),
},
};
}

View file

@ -117,7 +117,7 @@ export async function cliDocsVersionCommand(
const {path: docsPath, sidebarPath} = options;
// Copy docs files.
const docsDir = path.join(siteDir, docsPath);
const docsDir = path.resolve(siteDir, docsPath);
if (
(await fs.pathExists(docsDir)) &&
@ -127,7 +127,7 @@ export async function cliDocsVersionCommand(
const newVersionDir = path.join(versionedDir, `version-${version}`);
await fs.copy(docsDir, newVersionDir);
} else {
throw new Error(`${pluginIdLogPrefix}: there is no docs to version!`);
throw new Error(`${pluginIdLogPrefix}: no docs found in ${docsDir}.`);
}
await createVersionedSidebarFile({

View file

@ -21,7 +21,7 @@ import _ from 'lodash';
describe('docsClientUtils', () => {
it('getActivePlugin', () => {
const data: Record<string, GlobalPluginData> = {
const data: {[key: string]: GlobalPluginData} = {
pluginIosId: {
path: '/ios',
versions: [],

View file

@ -23,11 +23,11 @@ import type {
// ie the docs of that plugin are currently browsed
// it is useful to support multiple docs plugin instances
export function getActivePlugin(
allPluginDatas: Record<string, GlobalPluginData>,
allPluginData: {[pluginId: string]: GlobalPluginData},
pathname: string,
options: GetActivePluginOptions = {},
): ActivePlugin | undefined {
const activeEntry = Object.entries(allPluginDatas)
const activeEntry = Object.entries(allPluginData)
// Route sorting: '/android/foo' should match '/android' instead of '/'
.sort((a, b) => b[1].path.localeCompare(a[1].path))
.find(
@ -46,7 +46,7 @@ export function getActivePlugin(
if (!activePlugin && options.failfast) {
throw new Error(
`Can't find active docs plugin for "${pathname}" pathname, while it was expected to be found. Maybe you tried to use a docs feature that can only be used on a docs-related page? Existing docs plugin paths are: ${Object.values(
allPluginDatas,
allPluginData,
)
.map((plugin) => plugin.path)
.join(', ')}`,

View file

@ -31,7 +31,7 @@ const StableEmptyObject = {};
// Not using useAllPluginInstancesData() because in blog-only mode, docs hooks
// are still used by the theme. We need a fail-safe fallback when the docs
// plugin is not in use
export const useAllDocsData = (): Record<string, GlobalPluginData> =>
export const useAllDocsData = (): {[pluginId: string]: GlobalPluginData} =>
useGlobalData()['docusaurus-plugin-content-docs'] ?? StableEmptyObject;
export const useDocsData = (pluginId: string | undefined): GlobalPluginData =>

View file

@ -6,7 +6,7 @@
*/
declare module 'remark-admonitions' {
type Options = Record<string, unknown>;
type Options = {[key: string]: unknown};
const plugin: (options?: Options) => void;
export = plugin;

View file

@ -12,6 +12,7 @@ import {
aliasedSitePath,
getEditUrl,
getFolderContainingFile,
getContentPathList,
normalizeUrl,
parseMarkdownString,
posixPath,
@ -21,27 +22,22 @@ import {
import type {LoadContext} from '@docusaurus/types';
import {getFileLastUpdate} from './lastUpdate';
import type {
DocFile,
DocMetadataBase,
DocMetadata,
DocNavLink,
LastUpdateData,
VersionMetadata,
LoadedVersion,
} from './types';
import type {DocFile, LoadedVersion} from './types';
import getSlug from './slug';
import {CURRENT_VERSION_NAME} from './constants';
import {getDocsDirPaths} from './versions';
import {stripPathNumberPrefixes} from './numberPrefix';
import {validateDocFrontMatter} from './docFrontMatter';
import {validateDocFrontMatter} from './frontMatter';
import type {SidebarsUtils} from './sidebars/utils';
import {toDocNavigationLink, toNavigationLink} from './sidebars/utils';
import type {
MetadataOptions,
PluginOptions,
CategoryIndexMatcher,
CategoryIndexMatcherParam,
DocMetadataBase,
DocMetadata,
PropNavigationLink,
LastUpdateData,
VersionMetadata,
} from '@docusaurus/plugin-content-docs';
type LastUpdateOptions = Pick<
@ -85,7 +81,7 @@ export async function readDocFile(
options: LastUpdateOptions,
): Promise<DocFile> {
const contentPath = await getFolderContainingFile(
getDocsDirPaths(versionMetadata),
getContentPathList(versionMetadata),
source,
);
@ -213,7 +209,7 @@ function doProcessDocMetadata({
const description: string = frontMatter.description ?? excerpt ?? '';
const permalink = normalizeUrl([versionMetadata.versionPath, docSlug]);
const permalink = normalizeUrl([versionMetadata.path, docSlug]);
function getDocEditUrl() {
const relativeFilePath = path.relative(contentPath, filePath);
@ -232,8 +228,8 @@ function doProcessDocMetadata({
const isLocalized = contentPath === versionMetadata.contentPathLocalized;
const baseVersionEditUrl =
isLocalized && options.editLocalizedFiles
? versionMetadata.versionEditUrlLocalized
: versionMetadata.versionEditUrl;
? versionMetadata.editUrlLocalized
: versionMetadata.editUrl;
return getEditUrl(relativeFilePath, baseVersionEditUrl);
}
return undefined;
@ -304,7 +300,7 @@ export function addDocNavigation(
const toNavigationLinkByDocId = (
docId: string | null | undefined,
type: 'prev' | 'next',
): DocNavLink | undefined => {
): PropNavigationLink | undefined => {
if (!docId) {
return undefined;
}
@ -401,7 +397,7 @@ export function toCategoryIndexMatcherParam({
}: Pick<
DocMetadataBase,
'source' | 'sourceDirName'
>): CategoryIndexMatcherParam {
>): Parameters<CategoryIndexMatcher>[0] {
// source + sourceDirName are always posix-style
return {
fileName: path.posix.parse(source).name,
@ -424,7 +420,7 @@ export function getDocIds(doc: DocMetadataBase): [string, string] {
// to "id")
export function createDocsByIdIndex<
Doc extends {id: string; unversionedId: string},
>(docs: Doc[]): Record<string, Doc> {
>(docs: Doc[]): {[docId: string]: Doc} {
return Object.fromEntries(
docs.flatMap((doc) => [
[doc.unversionedId, doc],

View file

@ -12,7 +12,7 @@ import {
FrontMatterTOCHeadingLevels,
validateFrontMatter,
} from '@docusaurus/utils-validation';
import type {DocFrontMatter} from './types';
import type {DocFrontMatter} from '@docusaurus/plugin-content-docs';
// 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
@ -41,8 +41,8 @@ const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
...FrontMatterTOCHeadingLevels,
}).unknown();
export function validateDocFrontMatter(
frontMatter: Record<string, unknown>,
): DocFrontMatter {
export function validateDocFrontMatter(frontMatter: {
[key: string]: unknown;
}): DocFrontMatter {
return validateFrontMatter(frontMatter, DocFrontMatterSchema);
}

View file

@ -8,11 +8,11 @@
import _ from 'lodash';
import type {Sidebars} from './sidebars/types';
import {createSidebarsUtils} from './sidebars/utils';
import type {LoadedVersion} from './types';
import type {
CategoryGeneratedIndexMetadata,
DocMetadata,
LoadedVersion,
} from './types';
} from '@docusaurus/plugin-content-docs';
import type {
GlobalVersion,
GlobalSidebar,
@ -40,7 +40,7 @@ function toGlobalDataGeneratedIndex(
function toGlobalSidebars(
sidebars: Sidebars,
version: LoadedVersion,
): Record<string, GlobalSidebar> {
): {[sidebarId: string]: GlobalSidebar} {
const {getFirstLink} = createSidebarsUtils(sidebars);
return _.mapValues(sidebars, (sidebar, sidebarId) => {
const firstLink = getFirstLink(sidebarId);
@ -65,9 +65,9 @@ function toGlobalSidebars(
export function toGlobalDataVersion(version: LoadedVersion): GlobalVersion {
return {
name: version.versionName,
label: version.versionLabel,
label: version.label,
isLast: version.isLast,
path: version.versionPath,
path: version.path,
mainDocId: version.mainDocId,
docs: version.docs
.map(toGlobalDataDoc)

View file

@ -11,6 +11,7 @@ import {
normalizeUrl,
docuHash,
aliasedSitePath,
getContentPathList,
reportMessage,
posixPath,
addTrailingPathSeparator,
@ -27,18 +28,14 @@ import {
addDocNavigation,
getMainDocId,
} from './docs';
import {getDocsDirPaths, readVersionsMetadata} from './versions';
import {readVersionsMetadata} from './versions';
import type {
LoadedContent,
SourceToPermalink,
DocMetadataBase,
VersionMetadata,
LoadedVersion,
DocFile,
DocsMarkdownOption,
VersionTag,
DocFrontMatter,
} from './types';
import type {RuleSetRule} from 'webpack';
import {cliDocsVersionCommand} from './cli';
@ -55,8 +52,10 @@ import {createVersionRoutes} from './routes';
import type {
PropTagsListPage,
PluginOptions,
DocMetadataBase,
VersionMetadata,
DocFrontMatter,
} from '@docusaurus/plugin-content-docs';
import type {GlobalPluginData} from '@docusaurus/plugin-content-docs/client';
import {createSidebarsUtils} from './sidebars/utils';
import {getCategoryGeneratedIndexMetadataList} from './categoryGeneratedIndex';
@ -115,7 +114,7 @@ export default async function pluginContentDocs(
function getVersionPathsToWatch(version: VersionMetadata): string[] {
const result = [
...options.include.flatMap((pattern) =>
getDocsDirPaths(version).map(
getContentPathList(version).map(
(docsDirPath) => `${docsDirPath}/${pattern}`,
),
),
@ -229,7 +228,7 @@ export default async function pluginContentDocs(
const tagsProp: PropTagsListPage['tags'] = Object.values(
versionTags,
).map((tagValue) => ({
name: tagValue.name,
name: tagValue.label,
permalink: tagValue.permalink,
count: tagValue.docIds.length,
}));
@ -293,7 +292,7 @@ export default async function pluginContentDocs(
// TODO tags should be a sub route of the version route
await Promise.all(loadedVersions.map(createVersionTagsRoutes));
setGlobalData<GlobalPluginData>({
setGlobalData({
path: normalizeUrl([baseUrl, options.routeBasePath]),
versions: loadedVersions.map(toGlobalDataVersion),
breadcrumbs,
@ -332,7 +331,7 @@ export default async function pluginContentDocs(
};
function createMDXLoaderRule(): RuleSetRule {
const contentDirs = versionsMetadata.flatMap(getDocsDirPaths);
const contentDirs = versionsMetadata.flatMap(getContentPathList);
return {
test: /\.mdx?$/i,
include: contentDirs

View file

@ -6,15 +6,18 @@
*/
import logger from '@docusaurus/logger';
import {getFileCommitDate, GitNotFoundError} from '@docusaurus/utils';
type FileLastUpdateData = {timestamp?: number; author?: string};
import {
getFileCommitDate,
FileNotTrackedError,
GitNotFoundError,
} from '@docusaurus/utils';
let showedGitRequirementError = false;
let showedFileNotTrackedError = false;
export async function getFileLastUpdate(
filePath?: string,
): Promise<FileLastUpdateData | null> {
): Promise<{timestamp: number; author: string} | null> {
if (!filePath) {
return null;
}
@ -28,11 +31,20 @@ export async function getFileLastUpdate(
});
return {timestamp: result.timestamp, author: result.author};
} catch (err) {
if (err instanceof GitNotFoundError && !showedGitRequirementError) {
logger.warn('Sorry, the docs plugin last update options require Git.');
showedGitRequirementError = true;
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.error(err);
logger.warn(err);
}
return null;
}

View file

@ -12,9 +12,9 @@ import {linkify} from '../linkify';
import type {
DocsMarkdownOption,
SourceToPermalink,
VersionMetadata,
BrokenMarkdownLink,
DocBrokenMarkdownLink,
} from '../../types';
import type {VersionMetadata} from '@docusaurus/plugin-content-docs';
import {VERSIONED_DOCS_DIR, CURRENT_VERSION_NAME} from '../../constants';
function createFakeVersion({
@ -156,22 +156,22 @@ describe('linkify', () => {
filePath: doc5,
link: 'docNotExist1.md',
contentPaths: versionCurrent,
} as BrokenMarkdownLink);
} as DocBrokenMarkdownLink);
expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(2, {
filePath: doc5,
link: './docNotExist2.mdx',
contentPaths: versionCurrent,
} as BrokenMarkdownLink);
} as DocBrokenMarkdownLink);
expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(3, {
filePath: doc5,
link: '../docNotExist3.mdx',
contentPaths: versionCurrent,
} as BrokenMarkdownLink);
} as DocBrokenMarkdownLink);
expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(4, {
filePath: doc5,
link: './subdir/docNotExist4.md',
contentPaths: versionCurrent,
} as BrokenMarkdownLink);
} as DocBrokenMarkdownLink);
});
it('transforms absolute links in versioned docs', async () => {

View file

@ -6,12 +6,11 @@
*/
import type {DocsMarkdownOption} from '../types';
import {getDocsDirPaths} from '../versions';
import {replaceMarkdownLinks} from '@docusaurus/utils';
import {replaceMarkdownLinks, getContentPathList} from '@docusaurus/utils';
function getVersion(filePath: string, options: DocsMarkdownOption) {
const versionFound = options.versionsMetadata.find((version) =>
getDocsDirPaths(version).some((docsDirPath) =>
getContentPathList(version).some((docsDirPath) =>
filePath.startsWith(docsDirPath),
),
);

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import type {PluginOptions} from '@docusaurus/plugin-content-docs';
import type {PluginOptions, Options} from '@docusaurus/plugin-content-docs';
import {
Joi,
RemarkPluginsSchema,
@ -15,10 +15,7 @@ import {
} from '@docusaurus/utils-validation';
import {GlobExcludeDefault} from '@docusaurus/utils';
import type {
OptionValidationContext,
ValidationResult,
} from '@docusaurus/types';
import type {OptionValidationContext} from '@docusaurus/types';
import logger from '@docusaurus/logger';
import admonitions from 'remark-admonitions';
import {DefaultSidebarItemsGenerator} from './sidebars/generator';
@ -70,7 +67,7 @@ const VersionsOptionsSchema = Joi.object()
.pattern(Joi.string().required(), VersionOptionsSchema)
.default(DEFAULT_OPTIONS.versions);
export const OptionsSchema = Joi.object({
const OptionsSchema = Joi.object<PluginOptions>({
path: Joi.string().default(DEFAULT_OPTIONS.path),
editUrl: Joi.alternatives().try(URISchema, Joi.function()),
editCurrentVersion: Joi.boolean().default(DEFAULT_OPTIONS.editCurrentVersion),
@ -80,6 +77,7 @@ export const OptionsSchema = Joi.object({
// .allow('') ""
.default(DEFAULT_OPTIONS.routeBasePath),
tagsBasePath: Joi.string().default(DEFAULT_OPTIONS.tagsBasePath),
// @ts-expect-error: deprecated
homePageId: Joi.any().forbidden().messages({
'any.unknown':
'The docs plugin option homePageId is not supported anymore. To make a doc the "home", please add "slug: /" in its front matter. See: https://docusaurus.io/docs/next/docs-introduction#home-page-docs',
@ -146,7 +144,7 @@ export const OptionsSchema = Joi.object({
export function validateOptions({
validate,
options: userOptions,
}: OptionValidationContext<PluginOptions>): ValidationResult<PluginOptions> {
}: OptionValidationContext<Options, PluginOptions>): PluginOptions {
let options = userOptions;
if (options.sidebarCollapsible === false) {
@ -168,7 +166,7 @@ export function validateOptions({
}
}
const normalizedOptions = validate(OptionsSchema, options);
const normalizedOptions = validate(OptionsSchema, options) as PluginOptions;
if (normalizedOptions.admonitions) {
normalizedOptions.remarkPlugins = normalizedOptions.remarkPlugins.concat([

View file

@ -6,18 +6,25 @@
*/
declare module '@docusaurus/plugin-content-docs' {
import type {RemarkAndRehypePluginOptions} from '@docusaurus/mdx-loader';
import type {MDXOptions} from '@docusaurus/mdx-loader';
import type {ContentPaths, Tag, FrontMatterTag} from '@docusaurus/utils';
import type {Required} from 'utility-types';
export interface Assets {
image?: string;
}
/**
* Custom callback for parsing number prefixes from file/folder names.
*/
export type NumberPrefixParser = (filename: string) => {
/** file name without number prefix, without any other modification. */
filename: string;
/** The number prefix. Can be float, integer, negative, or anything. */
numberPrefix?: number;
};
export type CategoryIndexMatcherParam = {
export type CategoryIndexMatcher = (param: {
/** The file name, without extension */
fileName: string;
/**
@ -27,113 +34,447 @@ declare module '@docusaurus/plugin-content-docs' {
directories: string[];
/** The extension, with a leading dot */
extension: string;
};
export type CategoryIndexMatcher = (
param: CategoryIndexMatcherParam,
) => boolean;
}) => boolean;
export type EditUrlFunction = (editUrlParams: {
/** Name of the version. */
version: string;
/**
* Path of the version's root content path, relative to the site directory.
* Usually the same as `options.path` but can be localized or versioned.
*/
versionDocsDirPath: string;
/** Path of the doc file, relative to `versionDocsDirPath`. */
docPath: string;
/** @see {@link DocMetadata.permalink} */
permalink: string;
/** Locale name. */
locale: string;
}) => string | undefined;
export type MetadataOptions = {
/**
* URL route for the docs section of your site. **DO NOT** include a
* trailing slash. Use `/` for shipping docs without base path.
*/
routeBasePath: string;
/**
* Base URL to edit your site. The final URL is computed by `editUrl +
* relativeDocPath`. Using a function allows more nuanced control for each
* file. Omitting this variable entirely will disable edit links.
*/
editUrl?: string | EditUrlFunction;
/**
* The edit URL will always target the current version doc instead of older
* versions. Ignored when `editUrl` is a function.
*/
editCurrentVersion: boolean;
/**
* The edit URL will target the localized file, instead of the original
* unlocalized file. Ignored when `editUrl` is a function.
*/
editLocalizedFiles: boolean;
/** Whether to display the last date the doc was updated. */
showLastUpdateTime?: boolean;
/** Whether to display the author who last updated the doc. */
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`
* to use the default parser.
*
* @param filename One segment of the path, without any slashes.
* @see https://docusaurus.io/docs/sidebar#using-number-prefixes
*/
numberPrefixParser: NumberPrefixParser;
/** Enable or disable the breadcrumbs on doc pages. */
breadcrumbs: boolean;
};
export type PathOptions = {
/**
* Path to the docs content directory on the file system, relative to site
* directory.
*/
path: string;
/**
* Path to sidebar configuration. Use `false` to disable sidebars, or
* `undefined` to create a fully autogenerated sidebar.
*/
sidebarPath?: string | false | undefined;
};
// TODO support custom version banner?
// {type: "error", content: "html content"}
export type VersionBanner = 'unreleased' | 'unmaintained';
export type VersionOptions = {
path?: string;
label?: string;
banner?: 'none' | VersionBanner;
badge?: boolean;
className?: string;
};
export type VersionsOptions = {
/**
* The version navigated to in priority and displayed by default for docs
* navbar items.
*
* @see https://docusaurus.io/docs/versioning#terminology
*/
lastVersion?: string;
versions: Record<string, VersionOptions>;
/** Only include a subset of all available versions. */
onlyIncludeVersions?: string[];
/**
* Explicitly disable versioning even when multiple versions exist. This
* will make the site only include the current version. Will error if
* `includeCurrentVersion: false` and `disableVersioning: true`.
*/
disableVersioning: boolean;
/** Include the current version of your docs. */
includeCurrentVersion: boolean;
/** Independent customization of each version's properties. */
versions: {
[versionName: string]: {
/**
* The base path of the version, will be appended to `baseUrl` +
* `routeBasePath`.
*/
path?: string;
/** The label of the version to be used in badges, dropdowns, etc. */
label?: string;
/** The banner to show at the top of a doc of that version. */
banner?: 'none' | VersionBanner;
/** Show a badge with the version label at the top of each doc. */
badge?: boolean;
/** Add a custom class name to the <html> element of each doc. */
className?: string;
};
};
};
export type SidebarOptions = {
/**
* Whether sidebar categories are collapsible by default.
*
* @see https://docusaurus.io/docs/sidebar#collapsible-categories
*/
sidebarCollapsible: boolean;
/**
* Whether sidebar categories are collapsed by default.
*
* @see https://docusaurus.io/docs/sidebar#expanded-categories-by-default
*/
sidebarCollapsed: boolean;
};
export type PluginOptions = MetadataOptions &
PathOptions &
VersionsOptions &
RemarkAndRehypePluginOptions &
MDXOptions &
SidebarOptions & {
/** Plugin ID. */
id: string;
/**
* Array of glob patterns matching Markdown files to be built, relative to
* the content path.
*/
include: string[];
/**
* Array of glob patterns matching Markdown files to be excluded. Serves
* as refinement based on the `include` option.
*/
exclude: string[];
/**
* Root layout component of each doc page. Provides the version data
* context, and is not unmounted when switching docs.
*/
docLayoutComponent: string;
/** Main doc container, with TOC, pagination, etc. */
docItemComponent: string;
/** Root component of the "docs containing tag X" page. */
docTagDocListComponent: string;
/** Root component of the tags list page */
docTagsListComponent: string;
/** Root component of the generated category index page. */
docCategoryGeneratedIndexComponent: string;
admonitions: Record<string, unknown>;
disableVersioning: boolean;
includeCurrentVersion: boolean;
admonitions: {[key: string]: unknown};
sidebarItemsGenerator: import('./sidebars/types').SidebarItemsGeneratorOption;
/**
* URL route for the tags section of your doc version. Will be appended to
* `routeBasePath`. **DO NOT** include a trailing slash.
*/
tagsBasePath: string;
};
export type Options = Partial<PluginOptions>;
export type SidebarsConfig = import('./sidebars/types').SidebarsConfig;
export type VersionMetadata = ContentPaths & {
/** A name like `1.0.0`. Acquired from `versions.json`. */
versionName: string;
/** Like `Version 1.0.0`. Can be configured through `versions.label`. */
label: string;
/**
* Version's base path in the form of `/<baseUrl>/<routeBasePath>/1.0.0`.
* Can be configured through `versions.path`.
*/
path: string;
/** Tags base path in the form of `<versionPath>/tags`. */
tagsPath: string;
/**
* The base URL to which the doc file path will be appended. Will be
* `undefined` if `editUrl` is `undefined` or a function.
*/
editUrl?: string | undefined;
/**
* The base URL to which the localized doc file path will be appended. Will
* be `undefined` if `editUrl` is `undefined` or a function.
*/
editUrlLocalized?: string | undefined;
/**
* "unmaintained" is the version before latest; "unreleased" is the version
* after latest. `null` is the latest version without a banner. Can be
* configured with `versions.banner`: `banner: "none"` will be transformed
* to `null` here.
*/
banner: VersionBanner | null;
/** Show a badge with the version label at the top of each doc. */
badge: boolean;
/** Add a custom class name to the <html> element of each doc. */
className: string;
/**
* Whether this version is the "last" version. Can be configured with
* `lastVersion` option.
*/
isLast: boolean;
/**
* Like `versioned_sidebars/1.0.0.json`. Versioned sidebars file may be
* nonexistent since we don't create empty files.
*/
sidebarFilePath: string | false | undefined;
/**
* Will be -1 for the latest docs, and `undefined` for everything else.
* Because `/docs/foo` should always be after `/docs/<versionName>/foo`.
*/
routePriority: number | undefined;
};
export type DocFrontMatter = {
/**
* The last part of the doc ID (will be refactored in the future to be the
* full ID instead)
* @see {@link DocMetadata.id}
*/
id?: string;
/**
* Will override the default title collected from h1 heading.
* @see {@link DocMetadata.title}
*/
title?: string;
/**
* Front matter tags, unnormalized.
* @see {@link DocMetadata.tags}
*/
tags?: FrontMatterTag[];
/**
* If there isn't a Markdown h1 heading (which, if there is, we don't
* remove), this front matter will cause the front matter title to not be
* displayed in the doc page.
*/
hide_title?: boolean;
/** Hide the TOC on the right. */
hide_table_of_contents?: boolean;
/** Used in the head meta. */
keywords?: string[];
/** Used in the head meta. Should use `assets.image` in priority. */
image?: string;
/**
* Will override the default excerpt.
* @see {@link DocMetadata.description}
*/
description?: string;
/**
* Custom slug appended after /<baseUrl>/<routeBasePath>/<versionPath>
* @see {@link DocMetadata.slug}
*/
slug?: string;
/** Customizes the sidebar label for this doc. Will default to its title. */
sidebar_label?: string;
/**
* Controls the position of a doc inside the generated sidebar slice when
* using autogenerated sidebar items.
*
* @see https://docusaurus.io/docs/sidebar#autogenerated-sidebar-metadata
*/
sidebar_position?: number;
/**
* Gives the corresponding sidebar label a special class name when using
* autogenerated sidebars.
*/
sidebar_class_name?: string;
/**
* Will be propagated to the final sidebars data structure. Useful if you
* have swizzled sidebar-related code or simply querying doc data through
* sidebars.
*/
sidebar_custom_props?: {[key: string]: unknown};
/**
* Changes the sidebar association of the current doc. Use `null` to make
* the current doc not associated to any sidebar.
*/
displayed_sidebar?: string | null;
/**
* Customizes the pagination label for this doc. Will default to the sidebar
* label.
*/
pagination_label?: string;
/** Overrides the default URL computed for this doc. */
custom_edit_url?: string | null;
/**
* Whether number prefix parsing is disabled on this doc.
* @see https://docusaurus.io/docs/sidebar#using-number-prefixes
*/
parse_number_prefixes?: boolean;
/**
* Minimum TOC heading level. Must be between 2 and 6 and lower or equal to
* the max value.
*/
toc_min_heading_level?: number;
/** Maximum TOC heading level. Must be between 2 and 6. */
toc_max_heading_level?: number;
/**
* The ID of the documentation you want the "Next" pagination to link to.
* Use `null` to disable showing "Next" for this page.
* @see {@link DocMetadata.next}
*/
pagination_next?: string | null;
/**
* The ID of the documentation you want the "Previous" pagination to link
* to. Use `null` to disable showing "Previous" for this page.
* @see {@link DocMetadata.prev}
*/
pagination_prev?: string | null;
};
export type LastUpdateData = {
/** A timestamp in **seconds**, directly acquired from `git log`. */
lastUpdatedAt?: number;
/** `lastUpdatedAt` formatted as a date according to the current locale. */
formattedLastUpdatedAt?: string;
/** The author's name directly acquired from `git log`. */
lastUpdatedBy?: string;
};
export type DocMetadataBase = LastUpdateData & {
// TODO
/**
* Legacy versioned ID. Will be refactored in the future to be unversioned.
*/
id: string;
// TODO
/**
* Unversioned ID. Should be preferred everywhere over `id` until the latter
* is refactored.
*/
unversionedId: string;
/** The name of the version this doc belongs to. */
version: string;
/**
* Used to generate the page h1 heading, tab title, and pagination title.
*/
title: string;
/**
* Description used in the meta. Could be an empty string (empty content)
*/
description: string;
/** Path to the Markdown source, with `@site` alias. */
source: string;
/**
* Posix path relative to the content path. Can be `"."`.
* e.g. "folder/subfolder/subsubfolder"
*/
sourceDirName: string;
/** `permalink` without base URL or version path. */
slug: string;
/** Full URL to this doc, with base URL and version path. */
permalink: string;
/**
* Position in an autogenerated sidebar slice, acquired through front matter
* or number prefix.
*/
sidebarPosition?: number;
/**
* Acquired from the options; can be customized with front matter.
* `custom_edit_url` will always lead to it being null, but you should treat
* `undefined` and `null` as equivalent.
*/
editUrl?: string | null;
/** Tags, normalized. */
tags: Tag[];
/** Front matter, as-is. */
frontMatter: DocFrontMatter & {[key: string]: unknown};
};
export type DocMetadata = DocMetadataBase &
PropNavigation & {
/** Name of the sidebar this doc is associated with. */
sidebar?: string;
};
export type CategoryGeneratedIndexMetadata = Required<
Omit<
import('./sidebars/types').SidebarItemCategoryLinkGeneratedIndex,
'type'
>,
'title'
> & {
navigation: PropNavigation;
/**
* Name of the sidebar this doc is associated with. Unlike
* `DocMetadata.sidebar`, this will always be defined, because a generated
* index can only be generated from a category.
*/
sidebar: string;
};
export type PropNavigationLink = {
readonly title: string;
readonly permalink: string;
};
export type PropNavigation = {
/**
* Used in pagination. Content is just a subset of another doc's metadata.
*/
readonly previous?: PropNavigationLink;
/**
* Used in pagination. Content is just a subset of another doc's metadata.
*/
readonly next?: PropNavigationLink;
};
export type PropVersionDoc = import('./sidebars/types').PropVersionDoc;
export type PropVersionDocs = import('./sidebars/types').PropVersionDocs;
export type PropVersionDoc = Pick<
DocMetadata,
'id' | 'title' | 'description' | 'sidebar'
>;
export type PropVersionMetadata = {
export type PropVersionDocs = {
[docId: string]: PropVersionDoc;
};
export type PropVersionMetadata = Pick<
VersionMetadata,
'label' | 'banner' | 'badge' | 'className' | 'isLast'
> & {
/** ID of the docs plugin this version belongs to. */
pluginId: string;
/** Name of this version. */
version: string;
label: string;
banner: VersionBanner | null;
badge: boolean;
className: string;
isLast: boolean;
/** Sidebars contained in this version. */
docsSidebars: PropSidebars;
/** Docs contained in this version. */
docs: PropVersionDocs;
};
export type PropCategoryGeneratedIndex = {
title: string;
description?: string;
image?: string;
keywords?: string | readonly string[];
slug: string;
permalink: string;
navigation: PropNavigation;
};
export type PropCategoryGeneratedIndex = Omit<
CategoryGeneratedIndexMetadata,
'sidebar'
>;
export type PropSidebarItemLink =
import('./sidebars/types').PropSidebarItemLink;
export type PropSidebarItemHtml =
import('./sidebars/types').PropSidebarItemHtml;
export type PropSidebarItemCategory =
import('./sidebars/types').PropSidebarItemCategory;
export type PropSidebarItem = import('./sidebars/types').PropSidebarItem;
@ -167,9 +508,10 @@ declare module '@docusaurus/plugin-content-docs' {
declare module '@theme/DocItem' {
import type {TOCItem} from '@docusaurus/types';
import type {
PropNavigationLink,
PropVersionMetadata,
Assets,
DocMetadata,
DocFrontMatter,
} from '@docusaurus/plugin-content-docs';
export type DocumentRoute = {
@ -179,41 +521,12 @@ declare module '@theme/DocItem' {
readonly sidebar?: string;
};
export type FrontMatter = {
readonly id: string;
readonly title: string;
readonly image?: string;
readonly keywords?: readonly string[];
readonly hide_title?: boolean;
readonly hide_table_of_contents?: boolean;
readonly toc_min_heading_level?: number;
readonly toc_max_heading_level?: number;
};
export type Metadata = {
readonly unversionedId?: string;
readonly description?: string;
readonly title?: string;
readonly permalink?: string;
readonly editUrl?: string;
readonly lastUpdatedAt?: number;
readonly formattedLastUpdatedAt?: string;
readonly lastUpdatedBy?: string;
readonly version?: string;
readonly previous?: PropNavigationLink;
readonly next?: PropNavigationLink;
readonly tags: readonly {
readonly label: string;
readonly permalink: string;
}[];
};
export interface Props {
readonly route: DocumentRoute;
readonly versionMetadata: PropVersionMetadata;
readonly content: {
readonly frontMatter: FrontMatter;
readonly metadata: Metadata;
readonly frontMatter: DocFrontMatter;
readonly metadata: DocMetadata;
readonly toc: readonly TOCItem[];
readonly contentTitle: string | undefined;
readonly assets: Assets;
@ -273,6 +586,40 @@ declare module '@theme/DocPage' {
export default function DocPage(props: Props): JSX.Element;
}
declare module '@theme/DocPage/Layout' {
import type {ReactNode} from 'react';
export interface Props {
children: ReactNode;
}
export default function DocPageLayout(props: Props): JSX.Element;
}
declare module '@theme/DocPage/Layout/Aside' {
import type {Dispatch, SetStateAction} from 'react';
import type {PropSidebar} from '@docusaurus/plugin-content-docs';
export interface Props {
sidebar: PropSidebar;
hiddenSidebarContainer: boolean;
setHiddenSidebarContainer: Dispatch<SetStateAction<boolean>>;
}
export default function DocPageLayoutAside(props: Props): JSX.Element;
}
declare module '@theme/DocPage/Layout/Main' {
import type {ReactNode} from 'react';
export interface Props {
hiddenSidebarContainer: boolean;
children: ReactNode;
}
export default function DocPageLayoutMain(props: Props): JSX.Element;
}
// TODO until TS supports exports field... hope it's in 4.6
declare module '@docusaurus/plugin-content-docs/client' {
export type ActivePlugin = {
@ -282,7 +629,7 @@ declare module '@docusaurus/plugin-content-docs/client' {
export type ActiveDocContext = {
activeVersion?: GlobalVersion;
activeDoc?: GlobalDoc;
alternateDocVersions: Record<string, GlobalDoc>;
alternateDocVersions: {[versionName: string]: GlobalDoc};
};
export type GlobalDoc = {
id: string;
@ -297,16 +644,14 @@ declare module '@docusaurus/plugin-content-docs/client' {
path: string;
mainDocId: string; // home doc (if docs homepage configured), or first doc
docs: GlobalDoc[];
sidebars?: Record<string, GlobalSidebar>;
};
export type GlobalSidebarLink = {
label: string;
path: string;
sidebars?: {[sidebarId: string]: GlobalSidebar};
};
export type GlobalSidebar = {
link?: GlobalSidebarLink;
link?: {
label: string;
path: string;
};
// ... we may add other things here later
};
export type GlobalPluginData = {
@ -322,7 +667,7 @@ declare module '@docusaurus/plugin-content-docs/client' {
};
export type GetActivePluginOptions = {failfast?: boolean}; // use fail-fast option if you know for sure one plugin instance is active
export const useAllDocsData: () => Record<string, GlobalPluginData>;
export const useAllDocsData: () => {[pluginId: string]: GlobalPluginData};
export const useDocsData: (pluginId?: string) => GlobalPluginData;
export const useActivePlugin: (
options?: GetActivePluginOptions,

View file

@ -5,13 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
import type {LoadedVersion, VersionTag, DocMetadata} from './types';
import type {LoadedVersion, VersionTag} from './types';
import type {
SidebarItemDoc,
SidebarItem,
SidebarItemCategory,
SidebarItemCategoryLink,
PropVersionDocs,
} from './sidebars/types';
import type {
PropSidebars,
@ -21,6 +20,8 @@ import type {
PropTagDocList,
PropTagDocListDoc,
PropSidebarItemLink,
PropVersionDocs,
DocMetadata,
} from '@docusaurus/plugin-content-docs';
import _ from 'lodash';
import {createDocsByIdIndex} from './docs';
@ -119,10 +120,10 @@ export function toVersionMetadataProp(
return {
pluginId,
version: loadedVersion.versionName,
label: loadedVersion.versionLabel,
banner: loadedVersion.versionBanner,
badge: loadedVersion.versionBadge,
className: loadedVersion.versionClassName,
label: loadedVersion.label,
banner: loadedVersion.banner,
badge: loadedVersion.badge,
className: loadedVersion.className,
isLast: loadedVersion.isLast,
docsSidebars: toSidebarsProp(loadedVersion),
docs: toVersionDocsProp(loadedVersion),
@ -153,7 +154,7 @@ export function toTagDocListProp({
}
return {
name: tag.name,
name: tag.label,
permalink: tag.permalink,
docs: toDocListProp(),
allTagsPath,

View file

@ -7,12 +7,11 @@
import type {PluginContentLoadedActions, RouteConfig} from '@docusaurus/types';
import {docuHash, createSlugger} from '@docusaurus/utils';
import type {LoadedVersion} from './types';
import type {
CategoryGeneratedIndexMetadata,
DocMetadata,
LoadedVersion,
} from './types';
import type {PropCategoryGeneratedIndex} from '@docusaurus/plugin-content-docs';
} from '@docusaurus/plugin-content-docs';
import {toVersionMetadataProp} from './props';
import logger from '@docusaurus/logger';
@ -32,42 +31,19 @@ export async function createCategoryGeneratedIndexRoutes({
async function createCategoryGeneratedIndexRoute(
categoryGeneratedIndex: CategoryGeneratedIndexMetadata,
): Promise<RouteConfig> {
const {
sidebar,
title,
description,
slug,
permalink,
previous,
next,
image,
keywords,
} = categoryGeneratedIndex;
const {sidebar, ...prop} = categoryGeneratedIndex;
const propFileName = slugs.slug(
`${version.versionPath}-${categoryGeneratedIndex.sidebar}-category-${categoryGeneratedIndex.title}`,
`${version.path}-${categoryGeneratedIndex.sidebar}-category-${categoryGeneratedIndex.title}`,
);
const prop: PropCategoryGeneratedIndex = {
title,
description,
slug,
permalink,
image,
keywords,
navigation: {
previous,
next,
},
};
const propData = await actions.createData(
`${docuHash(`category/${propFileName}`)}.json`,
JSON.stringify(prop, null, 2),
);
return {
path: permalink,
path: categoryGeneratedIndex.permalink,
component: docCategoryGeneratedIndexComponent,
exact: true,
modules: {
@ -162,7 +138,7 @@ export async function createVersionRoutes({
}
actions.addRoute({
path: version.versionPath,
path: version.path,
// allow matching /docs/* as well
exact: false,
// main docs component (DocPage)

View file

@ -116,7 +116,7 @@ exports[`DefaultSidebarItemsGenerator generates subfolder sidebar 1`] = `
"type": "doc",
},
],
"label": "subsubsubfolder3 (_category_.json label)",
"label": "Subsubsubfolder category label",
"link": {
"id": "doc1",
"type": "doc",

View file

@ -234,7 +234,7 @@ describe('DefaultSidebarItemsGenerator', () => {
},
'subfolder/subsubfolder/subsubsubfolder3': {
position: 1,
label: 'subsubsubfolder3 (_category_.json label)',
// This item's label is defined from the index doc instead
link: {
type: 'doc',
id: 'doc1', // This is a "fully-qualified" ID that can't be found locally
@ -246,6 +246,7 @@ describe('DefaultSidebarItemsGenerator', () => {
id: 'doc1',
source: 'doc1.md',
sourceDirName: 'subfolder/subsubfolder',
title: 'Subsubsubfolder category label',
sidebarPosition: undefined,
frontMatter: {},
},

View file

@ -34,7 +34,7 @@ describe('postProcess', () => {
},
{
sidebarOptions: {sidebarCollapsed: true, sidebarCollapsible: true},
version: {versionPath: 'version'},
version: {path: 'version'},
},
);
@ -53,7 +53,7 @@ describe('postProcess', () => {
},
{
sidebarOptions: {sidebarCollapsed: true, sidebarCollapsible: true},
version: {versionPath: 'version'},
version: {path: 'version'},
},
);
}).toThrowErrorMatchingInlineSnapshot(
@ -78,7 +78,7 @@ describe('postProcess', () => {
{
sidebarOptions: {sidebarCollapsed: true, sidebarCollapsible: true},
version: {versionPath: 'version'},
version: {path: 'version'},
},
),
).toMatchSnapshot();
@ -98,7 +98,7 @@ describe('postProcess', () => {
{
sidebarOptions: {sidebarCollapsed: false, sidebarCollapsible: false},
version: {versionPath: 'version'},
version: {path: 'version'},
},
),
).toMatchSnapshot();
@ -117,7 +117,7 @@ describe('postProcess', () => {
{
sidebarOptions: {sidebarCollapsed: true, sidebarCollapsible: false},
version: {versionPath: 'version'},
version: {path: 'version'},
},
),
).toMatchSnapshot();

View file

@ -18,7 +18,7 @@ import type {
} from '../types';
import {DefaultSidebarItemsGenerator} from '../generator';
import {createSlugger} from '@docusaurus/utils';
import type {VersionMetadata} from '../../types';
import type {VersionMetadata} from '@docusaurus/plugin-content-docs';
import {DefaultNumberPrefixParser} from '../../numberPrefix';
import {isCategoryIndex} from '../../docs';
@ -57,7 +57,7 @@ describe('processSidebars', () => {
async function testProcessSidebars(
unprocessedSidebars: NormalizedSidebars,
categoriesMetadata: Record<string, CategoryMetadataFile> = {},
categoriesMetadata: {[filePath: string]: CategoryMetadataFile} = {},
paramsOverrides: Partial<SidebarProcessorParams> = {},
) {
return processSidebars(unprocessedSidebars, categoriesMetadata, {
@ -142,7 +142,6 @@ describe('processSidebars', () => {
},
numberPrefixParser: DefaultNumberPrefixParser,
isCategoryIndex,
options: params.sidebarOptions,
});
expect(StaticSidebarItemsGenerator).toHaveBeenCalledWith({
defaultSidebarItemsGenerator: DefaultSidebarItemsGenerator,
@ -154,7 +153,6 @@ describe('processSidebars', () => {
},
numberPrefixParser: DefaultNumberPrefixParser,
isCategoryIndex,
options: params.sidebarOptions,
});
expect(StaticSidebarItemsGenerator).toHaveBeenCalledWith({
defaultSidebarItemsGenerator: DefaultSidebarItemsGenerator,
@ -166,7 +164,6 @@ describe('processSidebars', () => {
},
numberPrefixParser: DefaultNumberPrefixParser,
isCategoryIndex,
options: params.sidebarOptions,
});
expect(processedSidebar).toEqual({

View file

@ -16,7 +16,10 @@ import {
toNavigationLink,
} from '../utils';
import type {Sidebar, Sidebars} from '../types';
import type {DocMetadataBase, DocNavLink} from '../../types';
import type {
DocMetadataBase,
PropNavigationLink,
} from '@docusaurus/plugin-content-docs';
describe('createSidebarsUtils', () => {
const sidebar1: Sidebar = [
@ -618,7 +621,7 @@ describe('toDocNavigationLink', () => {
).toEqual({
title: 'Doc Title',
permalink: '/docPermalink',
} as DocNavLink);
} as PropNavigationLink);
});
it('with pagination_label front matter', () => {
@ -635,7 +638,7 @@ describe('toDocNavigationLink', () => {
).toEqual({
title: 'pagination_label',
permalink: '/docPermalink',
} as DocNavLink);
} as PropNavigationLink);
});
it('with sidebar_label front matter', () => {
@ -652,7 +655,7 @@ describe('toDocNavigationLink', () => {
).toEqual({
title: 'sidebar_label',
permalink: '/docPermalink',
} as DocNavLink);
} as PropNavigationLink);
});
it('with pagination_label + sidebar_label front matter', () => {
@ -670,7 +673,7 @@ describe('toDocNavigationLink', () => {
).toEqual({
title: 'pagination_label',
permalink: '/docPermalink',
} as DocNavLink);
} as PropNavigationLink);
});
});
@ -680,7 +683,7 @@ describe('toNavigationLink', () => {
return {...data, frontMatter: {}} as DocMetadataBase;
}
const docsById: Record<string, DocMetadataBase> = {
const docsById: {[docId: string]: DocMetadataBase} = {
doc1: testDoc({
title: 'Doc 1',
permalink: '/doc1',

View file

@ -14,7 +14,7 @@ import type {
SidebarItemCategoryLinkConfig,
} from './types';
import _ from 'lodash';
import {addTrailingSlash, posixPath} from '@docusaurus/utils';
import {addTrailingSlash} from '@docusaurus/utils';
import logger from '@docusaurus/logger';
import path from 'path';
import {createDocsByIdIndex, toCategoryIndexMatcherParam} from '../docs';
@ -157,10 +157,7 @@ Available doc IDs:
folderName: string,
): WithPosition<NormalizedSidebarItemCategory> {
const categoryMetadata =
categoriesMetadata[posixPath(path.join(autogenDir, fullPath))];
const className = categoryMetadata?.className;
const customProps = categoryMetadata?.customProps;
const {filename, numberPrefix} = numberPrefixParser(folderName);
categoriesMetadata[path.posix.join(autogenDir, fullPath)];
const allItems = Object.entries(dir).map(([key, content]) =>
dirToItem(content, key, `${fullPath}/${key}`),
);
@ -184,41 +181,65 @@ Available doc IDs:
});
}
function getCategoryLinkedDocId(): string | undefined {
const link = categoryMetadata?.link;
if (link !== undefined) {
if (link && link.type === 'doc') {
return findDocByLocalId(link.id)?.id || getDoc(link.id).id;
// In addition to the ID, this function also retrieves metadata of the
// linked doc that could be used as fallback values for category metadata
function getCategoryLinkedDocMetadata():
| {
id: string;
position?: number;
label?: string;
customProps?: {[key: string]: unknown};
className?: string;
}
| undefined {
const link = categoryMetadata?.link;
if (link !== undefined && link?.type !== 'doc') {
// If a link is explicitly specified, we won't apply conventions
return undefined;
}
// Apply default convention to pick index.md, README.md or
// <categoryName>.md as the category doc
return findConventionalCategoryDocLink()?.id;
const id = link
? findDocByLocalId(link.id)?.id ?? getDoc(link.id).id
: findConventionalCategoryDocLink()?.id;
if (!id) {
return undefined;
}
const doc = getDoc(id);
return {
id,
position: doc.sidebarPosition,
label: doc.frontMatter.sidebar_label ?? doc.title,
customProps: doc.frontMatter.sidebar_custom_props,
className: doc.frontMatter.sidebar_class_name,
};
}
const categoryLinkedDocId = getCategoryLinkedDocId();
const categoryLinkedDoc = getCategoryLinkedDocMetadata();
const link: SidebarItemCategoryLinkConfig | null | undefined =
categoryLinkedDocId
categoryLinkedDoc
? {
type: 'doc',
id: categoryLinkedDocId, // We "remap" a potentially "local id" to a "qualified id"
id: categoryLinkedDoc.id, // We "remap" a potentially "local id" to a "qualified id"
}
: categoryMetadata?.link;
// If a doc is linked, remove it from the category subItems
const items = allItems.filter(
(item) => !(item.type === 'doc' && item.id === categoryLinkedDocId),
(item) => !(item.type === 'doc' && item.id === categoryLinkedDoc?.id),
);
const className =
categoryMetadata?.className ?? categoryLinkedDoc?.className;
const customProps =
categoryMetadata?.customProps ?? categoryLinkedDoc?.customProps;
const {filename, numberPrefix} = numberPrefixParser(folderName);
return {
type: 'category',
label: categoryMetadata?.label ?? filename,
label: categoryMetadata?.label ?? categoryLinkedDoc?.label ?? filename,
collapsible: categoryMetadata?.collapsible,
collapsed: categoryMetadata?.collapsed,
position: categoryMetadata?.position ?? numberPrefix,
position:
categoryMetadata?.position ??
categoryLinkedDoc?.position ??
numberPrefix,
source: folderName,
...(customProps !== undefined && {customProps}),
...(className !== undefined && {className}),

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