Compare commits
24 Commits
main
...
ozaki/show
| Author | SHA1 | Date |
|---|---|---|
|
|
46c57d6cd5 | |
|
|
cdb7c07bdc | |
|
|
e147034d2e | |
|
|
c42c15eb32 | |
|
|
bed9bb3063 | |
|
|
30599c362e | |
|
|
022efa3653 | |
|
|
8bee535ffa | |
|
|
993efd235c | |
|
|
984518efaf | |
|
|
6e40a79b6f | |
|
|
49b67463bd | |
|
|
9790934ec4 | |
|
|
1c825e02d2 | |
|
|
dddb8cfc78 | |
|
|
c542c81cb8 | |
|
|
7daa9a1d04 | |
|
|
8d1b174917 | |
|
|
ec35998bde | |
|
|
7cf981c262 | |
|
|
a2516dc31a | |
|
|
cd3908b36f | |
|
|
f6b0d4622c | |
|
|
2b37b51b36 |
|
|
@ -15,4 +15,4 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@733dd5d4a5203f238c33806593ec0f5fc5343d8c # 4.2.4
|
||||
uses: actions/dependency-review-action@9129d7d40b8c12c1ed0f60400d00c92d437adcce # 4.1.3
|
||||
|
|
|
|||
131
CHANGELOG.md
131
CHANGELOG.md
|
|
@ -1,136 +1,5 @@
|
|||
# Docusaurus 2 Changelog
|
||||
|
||||
## 3.2.0 (2024-03-29)
|
||||
|
||||
#### :rocket: New Feature
|
||||
|
||||
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-sitemap`, `docusaurus-types`, `docusaurus-utils`, `docusaurus`
|
||||
- [#9954](https://github.com/facebook/docusaurus/pull/9954) feat(sitemap): add support for "lastmod" ([@slorber](https://github.com/slorber))
|
||||
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-utils-validation`, `docusaurus-utils`
|
||||
- [#9912](https://github.com/facebook/docusaurus/pull/9912) feat(blog): add LastUpdateAuthor & LastUpdateTime ([@OzakIOne](https://github.com/OzakIOne))
|
||||
- `docusaurus-plugin-debug`, `docusaurus-types`, `docusaurus`
|
||||
- [#9931](https://github.com/facebook/docusaurus/pull/9931) feat(core): add new plugin allContentLoaded lifecycle ([@slorber](https://github.com/slorber))
|
||||
- `docusaurus-theme-translations`
|
||||
- [#9928](https://github.com/facebook/docusaurus/pull/9928) feat(theme-translations) Icelandic (is) ([@Hallinn](https://github.com/Hallinn))
|
||||
- `docusaurus-plugin-content-blog`
|
||||
- [#9886](https://github.com/facebook/docusaurus/pull/9886) feat(blog): allow processing blog posts through a processBlogPosts function ([@OzakIOne](https://github.com/OzakIOne))
|
||||
- [#9838](https://github.com/facebook/docusaurus/pull/9838) feat(blog): add blog pageBasePath plugin option ([@ilg-ul](https://github.com/ilg-ul))
|
||||
- `docusaurus`
|
||||
- [#9681](https://github.com/facebook/docusaurus/pull/9681) feat(swizzle): ask user preferred language if no language CLI option provided ([@yixiaojiu](https://github.com/yixiaojiu))
|
||||
- `create-docusaurus`, `docusaurus-utils`
|
||||
- [#9442](https://github.com/facebook/docusaurus/pull/9442) feat(create-docusaurus): ask user for preferred language when no language CLI option provided ([@Rafael-Martins](https://github.com/Rafael-Martins))
|
||||
- `docusaurus-plugin-vercel-analytics`
|
||||
- [#9687](https://github.com/facebook/docusaurus/pull/9687) feat(plugin-vercel-analytics): add new vercel analytics plugin ([@OzakIOne](https://github.com/OzakIOne))
|
||||
- `docusaurus-mdx-loader`
|
||||
- [#9684](https://github.com/facebook/docusaurus/pull/9684) feat(mdx-loader): the table-of-contents should display toc/headings of imported MDX partials ([@anatolykopyl](https://github.com/anatolykopyl))
|
||||
|
||||
#### :bug: Bug Fix
|
||||
|
||||
- `docusaurus-mdx-loader`
|
||||
- [#9999](https://github.com/facebook/docusaurus/pull/9999) fix(mdx-loader): Ignore contentTitle coming after Markdown thematicBreak ([@slorber](https://github.com/slorber))
|
||||
- `docusaurus-theme-search-algolia`
|
||||
- [#9945](https://github.com/facebook/docusaurus/pull/9945) fix(a11y): move focus algolia-search focus back to search input on Escape ([@mxschmitt](https://github.com/mxschmitt))
|
||||
- `docusaurus-plugin-content-blog`
|
||||
- [#9920](https://github.com/facebook/docusaurus/pull/9920) fix(blog): apply trailing slash to blog feed ([@OzakIOne](https://github.com/OzakIOne))
|
||||
- `docusaurus-theme-classic`
|
||||
- [#9944](https://github.com/facebook/docusaurus/pull/9944) fix(theme): improve a11y of DocSidebarItemCategory expand/collapsed button ([@mxschmitt](https://github.com/mxschmitt))
|
||||
- `docusaurus-theme-translations`
|
||||
- [#9915](https://github.com/facebook/docusaurus/pull/9915) fix(theme-translations): complete and modify Japanese translations ([@Suenaga-Ryuya](https://github.com/Suenaga-Ryuya))
|
||||
- [#9910](https://github.com/facebook/docusaurus/pull/9910) fix(theme-translations): add Japanese translations ([@Suenaga-Ryuya](https://github.com/Suenaga-Ryuya))
|
||||
- [#9872](https://github.com/facebook/docusaurus/pull/9872) fix(theme-translations): complete and improve Spanish theme translations ([@4troDev](https://github.com/4troDev))
|
||||
- [#9812](https://github.com/facebook/docusaurus/pull/9812) fix(i18n): add missing theme translations for fa locale ([@VahidNaderi](https://github.com/VahidNaderi))
|
||||
- `docusaurus-utils`
|
||||
- [#9897](https://github.com/facebook/docusaurus/pull/9897) fix(mdx-loader): mdx-code-block should support CRLF ([@slorber](https://github.com/slorber))
|
||||
- `docusaurus`
|
||||
- [#9878](https://github.com/facebook/docusaurus/pull/9878) fix(core): fix default i18n calendar used, infer it from locale if possible ([@slorber](https://github.com/slorber))
|
||||
- [#9852](https://github.com/facebook/docusaurus/pull/9852) fix(core): ensure core error boundary is able to render theme layout ([@slorber](https://github.com/slorber))
|
||||
- `docusaurus-remark-plugin-npm2yarn`
|
||||
- [#9861](https://github.com/facebook/docusaurus/pull/9861) fix(remark-npm2yarn): update npm-to-yarn from 2.0.0 to 2.2.1, fix pnpm extra args syntax ([@OzakIOne](https://github.com/OzakIOne))
|
||||
- `docusaurus-theme-classic`, `docusaurus-theme-translations`
|
||||
- [#9851](https://github.com/facebook/docusaurus/pull/9851) fix(theme-classic): should use plurals for category items description ([@baradusov](https://github.com/baradusov))
|
||||
|
||||
#### :running_woman: Performance
|
||||
|
||||
- `docusaurus-types`, `docusaurus-utils`, `docusaurus`
|
||||
- [#9975](https://github.com/facebook/docusaurus/pull/9975) refactor(core): improve dev perf, fine-grained site reloads - part 3 ([@slorber](https://github.com/slorber))
|
||||
- `docusaurus-types`, `docusaurus`
|
||||
- [#9968](https://github.com/facebook/docusaurus/pull/9968) refactor(core): improve dev perf, fine-grained site reloads - part2 ([@slorber](https://github.com/slorber))
|
||||
- `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-types`, `docusaurus`
|
||||
- [#9903](https://github.com/facebook/docusaurus/pull/9903) refactor(core): improve dev perf, fine-grained site reloads - part1 ([@slorber](https://github.com/slorber))
|
||||
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-utils`
|
||||
- [#9890](https://github.com/facebook/docusaurus/pull/9890) perf: optimize getFileCommitDate, make it async ([@slorber](https://github.com/slorber))
|
||||
- `docusaurus`
|
||||
- [#9798](https://github.com/facebook/docusaurus/pull/9798) refactor(core): internalize, simplify and optimize the SSG logic ([@slorber](https://github.com/slorber))
|
||||
|
||||
#### :nail_care: Polish
|
||||
|
||||
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`
|
||||
- [#9868](https://github.com/facebook/docusaurus/pull/9868) refactor(theme): dates should be formatted on the client-side instead of in nodejs code ([@OzakIOne](https://github.com/OzakIOne))
|
||||
- `docusaurus-plugin-content-blog`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-types`
|
||||
- [#9669](https://github.com/facebook/docusaurus/pull/9669) refactor(theme): use JSON-LD instead of microdata for blog structured data ([@johnnyreilly](https://github.com/johnnyreilly))
|
||||
- `docusaurus-plugin-content-docs`
|
||||
- [#9839](https://github.com/facebook/docusaurus/pull/9839) refactor(blog): improve doc global data hook error message + add doc warning to blogOnly mode ([@OzakIOne](https://github.com/OzakIOne))
|
||||
|
||||
#### :memo: Documentation
|
||||
|
||||
- [#9937](https://github.com/facebook/docusaurus/pull/9937) docs: use official GitHub Action to deploy to GitHub Pages ([@vlad-nestorov](https://github.com/vlad-nestorov))
|
||||
- [#9971](https://github.com/facebook/docusaurus/pull/9971) docs: replace VuePress by VitePress on tool comparison section ([@sunkanmii](https://github.com/sunkanmii))
|
||||
- [#9914](https://github.com/facebook/docusaurus/pull/9914) docs: update legacy MDX v1 links to markdown links ([@OzakIOne](https://github.com/OzakIOne))
|
||||
- [#9913](https://github.com/facebook/docusaurus/pull/9913) docs: update legacy MDX v1 links to markdown links ([@OzakIOne](https://github.com/OzakIOne))
|
||||
- [#9906](https://github.com/facebook/docusaurus/pull/9906) docs: emphasize "index slug" convention ([@Josh-Cena](https://github.com/Josh-Cena))
|
||||
- [#9877](https://github.com/facebook/docusaurus/pull/9877) docs: fix typos in deployment.mdx ([@Oreoxmt](https://github.com/Oreoxmt))
|
||||
- [#9845](https://github.com/facebook/docusaurus/pull/9845) docs: typo ([@OzakIOne](https://github.com/OzakIOne))
|
||||
- [#9816](https://github.com/facebook/docusaurus/pull/9816) docs: Add docs for Mermaid Component ([@Its-Just-Nans](https://github.com/Its-Just-Nans))
|
||||
|
||||
#### :robot: Dependencies
|
||||
|
||||
- [#9981](https://github.com/facebook/docusaurus/pull/9981) chore(deps): bump actions/dependency-review-action from 4.1.3 to 4.2.4 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#9982](https://github.com/facebook/docusaurus/pull/9982) chore(deps): bump katex from 0.16.8 to 0.16.10 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#9983](https://github.com/facebook/docusaurus/pull/9983) chore(deps): bump express from 4.18.2 to 4.19.2 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#9977](https://github.com/facebook/docusaurus/pull/9977) chore(deps): bump webpack-dev-middleware from 5.3.3 to 5.3.4 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#9958](https://github.com/facebook/docusaurus/pull/9958) chore(deps): bump follow-redirects from 1.15.4 to 1.15.6 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#9892](https://github.com/facebook/docusaurus/pull/9892) chore(deps): bump actions/dependency-review-action from 4.1.2 to 4.1.3 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#9869](https://github.com/facebook/docusaurus/pull/9869) chore(deps): bump actions/dependency-review-action from 4.0.0 to 4.1.2 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#9874](https://github.com/facebook/docusaurus/pull/9874) chore(deps): bump ip from 2.0.0 to 2.0.1 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#9843](https://github.com/facebook/docusaurus/pull/9843) chore(deps): bump actions/setup-node from 4.0.1 to 4.0.2 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#9824](https://github.com/facebook/docusaurus/pull/9824) chore(deps): bump treosh/lighthouse-ci-action from 10.1.0 to 11.4.0 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#9823](https://github.com/facebook/docusaurus/pull/9823) chore(deps): bump marocchino/sticky-pull-request-comment from 2.8.0 to 2.9.0 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
|
||||
#### :wrench: Maintenance
|
||||
|
||||
- `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-docs`, `docusaurus-utils-common`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`
|
||||
- [#9972](https://github.com/facebook/docusaurus/pull/9972) refactor(utils): remove duplicated function ([@OzakIOne](https://github.com/OzakIOne))
|
||||
- Other
|
||||
- [#9965](https://github.com/facebook/docusaurus/pull/9965) refactor(website): organise blog posts by year ([@GingerGeek](https://github.com/GingerGeek))
|
||||
- [#9865](https://github.com/facebook/docusaurus/pull/9865) chore(website): update @crowdin/crowdin-api-client ([@chris-bateman](https://github.com/chris-bateman))
|
||||
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-utils`
|
||||
- [#9963](https://github.com/facebook/docusaurus/pull/9963) refactor(docs,blog): last update timestamp should be in milliseconds instead of seconds ([@slorber](https://github.com/slorber))
|
||||
|
||||
#### Committers: 22
|
||||
|
||||
- Aolin ([@Oreoxmt](https://github.com/Oreoxmt))
|
||||
- Anatoly Kopyl ([@anatolykopyl](https://github.com/anatolykopyl))
|
||||
- Chris Bateman ([@chris-bateman](https://github.com/chris-bateman))
|
||||
- Fafowora Sunkanmi ([@sunkanmii](https://github.com/sunkanmii))
|
||||
- Hallbjörn Magnússon ([@Hallinn](https://github.com/Hallinn))
|
||||
- John Reilly ([@johnnyreilly](https://github.com/johnnyreilly))
|
||||
- Joshua Chen ([@Josh-Cena](https://github.com/Josh-Cena))
|
||||
- Josue [4tro] A ([@4troDev](https://github.com/4troDev))
|
||||
- Liviu Ionescu ([@ilg-ul](https://github.com/ilg-ul))
|
||||
- Max Schmitt ([@mxschmitt](https://github.com/mxschmitt))
|
||||
- Rafael Martins ([@Rafael-Martins](https://github.com/Rafael-Martins))
|
||||
- Sébastien Lorber ([@slorber](https://github.com/slorber))
|
||||
- Vahid Naderi ([@VahidNaderi](https://github.com/VahidNaderi))
|
||||
- Vlad Nestorov ([@vlad-nestorov](https://github.com/vlad-nestorov))
|
||||
- Zed Spencer-Milnes ([@GingerGeek](https://github.com/GingerGeek))
|
||||
- axel7083 ([@axel7083](https://github.com/axel7083))
|
||||
- krinza.eth ([@kaymomin](https://github.com/kaymomin))
|
||||
- n4n5 ([@Its-Just-Nans](https://github.com/Its-Just-Nans))
|
||||
- ozaki ([@OzakIOne](https://github.com/OzakIOne))
|
||||
- suenryu ([@Suenaga-Ryuya](https://github.com/Suenaga-Ryuya))
|
||||
- Нуриль Барадусов ([@baradusov](https://github.com/baradusov))
|
||||
- 翊小久 ([@yixiaojiu](https://github.com/yixiaojiu))
|
||||
|
||||
## 3.1.1 (2024-01-26)
|
||||
|
||||
#### :bug: Bug Fix
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ const CookieName = 'DocusaurusPlaygroundName';
|
|||
|
||||
const PlaygroundConfigs = {
|
||||
codesandbox:
|
||||
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic?file=%2FREADME.md&privacy=public',
|
||||
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic?file=%2FREADME.md',
|
||||
'codesandbox-ts':
|
||||
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic-typescript?file=%2FREADME.md&privacy=public',
|
||||
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic-typescript?file=%2FREADME.md',
|
||||
|
||||
// Slow to load
|
||||
// stackblitz: 'https://stackblitz.com/github/facebook/docusaurus/tree/main/examples/classic',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "new.docusaurus.io",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "npx --package netlify-cli netlify dev"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "argos",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Argos visual diff tests",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
"dev": "docusaurus start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/preset-classic": "3.2.0",
|
||||
"@docusaurus/core": "3.1.1",
|
||||
"@docusaurus/preset-classic": "3.1.1",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
|
|
@ -25,9 +25,9 @@
|
|||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.2.0",
|
||||
"@docusaurus/tsconfig": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/module-type-aliases": "3.1.1",
|
||||
"@docusaurus/tsconfig": "3.1.1",
|
||||
"@docusaurus/types": "3.1.1",
|
||||
"typescript": "~5.2.2"
|
||||
},
|
||||
"browserslist": {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -15,8 +15,8 @@
|
|||
"dev": "docusaurus start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/preset-classic": "3.2.0",
|
||||
"@docusaurus/core": "3.1.1",
|
||||
"@docusaurus/preset-classic": "3.1.1",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
|
|
@ -24,8 +24,8 @@
|
|||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0"
|
||||
"@docusaurus/module-type-aliases": "3.1.1",
|
||||
"@docusaurus/types": "3.1.1"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"useNx": false,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "create-docusaurus",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Create Docusaurus apps easily.",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
|
|
@ -22,8 +22,8 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/logger": "3.2.0",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/logger": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"commander": "^5.1.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "docusaurus-2-classic-typescript-template",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/preset-classic": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/preset-classic": "3.0.0",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
|
|
@ -24,9 +24,9 @@
|
|||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.2.0",
|
||||
"@docusaurus/tsconfig": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/module-type-aliases": "3.0.0",
|
||||
"@docusaurus/tsconfig": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"typescript": "~5.2.2"
|
||||
},
|
||||
"browserslist": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "docusaurus-2-classic-template",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
|
|
@ -14,8 +14,8 @@
|
|||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/preset-classic": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/preset-classic": "3.0.0",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
|
|
@ -23,8 +23,8 @@
|
|||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0"
|
||||
"@docusaurus/module-type-aliases": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/cssnano-preset",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Advanced cssnano preset for maximum optimization.",
|
||||
"main": "lib/index.js",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/logger",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "An encapsulated logger for semantically formatting console messages.",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/mdx-loader",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Docusaurus Loader for MDX",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
|
@ -18,9 +18,9 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/logger": "3.2.0",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/logger": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"@mdx-js/mdx": "^3.0.0",
|
||||
"@slorber/remark-comment": "^1.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
"webpack": "^5.88.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@types/escape-html": "^1.0.2",
|
||||
"@types/mdast": "^4.0.2",
|
||||
"@types/stringify-object": "^3.3.1",
|
||||
|
|
|
|||
|
|
@ -71,21 +71,6 @@ some **markdown** *content*
|
|||
expect(result.data.contentTitle).toBeUndefined();
|
||||
});
|
||||
|
||||
it('ignore contentTitle if after thematic break', async () => {
|
||||
const result = await process(`
|
||||
|
||||
Hey
|
||||
|
||||
---
|
||||
|
||||
# contentTitle 1
|
||||
|
||||
some **markdown** *content*
|
||||
`);
|
||||
|
||||
expect(result.data.contentTitle).toBeUndefined();
|
||||
});
|
||||
|
||||
it('is able to decently serialize Markdown syntax', async () => {
|
||||
const result = await process(`
|
||||
# some **markdown** \`content\` _italic_
|
||||
|
|
|
|||
|
|
@ -34,24 +34,17 @@ const plugin: Plugin = function plugin(
|
|||
const {toString} = await import('mdast-util-to-string');
|
||||
const {visit, EXIT} = await import('unist-util-visit');
|
||||
|
||||
visit(root, ['heading', 'thematicBreak'], (node, index, parent) => {
|
||||
if (node.type === 'heading') {
|
||||
const headingNode = node as Heading;
|
||||
if (headingNode.depth === 1) {
|
||||
vfile.data.contentTitle = toString(headingNode);
|
||||
if (removeContentTitle) {
|
||||
// @ts-expect-error: TODO how to fix?
|
||||
parent!.children.splice(index, 1);
|
||||
}
|
||||
return EXIT; // We only handle the very first heading
|
||||
}
|
||||
// We only handle contentTitle if it's the very first heading found
|
||||
if (headingNode.depth >= 1) {
|
||||
return EXIT;
|
||||
visit(root, 'heading', (headingNode: Heading, index, parent) => {
|
||||
if (headingNode.depth === 1) {
|
||||
vfile.data.contentTitle = toString(headingNode);
|
||||
if (removeContentTitle) {
|
||||
// @ts-expect-error: TODO how to fix?
|
||||
parent!.children.splice(index, 1);
|
||||
}
|
||||
return EXIT; // We only handle the very first heading
|
||||
}
|
||||
// We only handle contentTitle when it's above the first thematic break
|
||||
if (node.type === 'thematicBreak') {
|
||||
// We only handle contentTitle if it's the very first heading found
|
||||
if (headingNode.depth >= 1) {
|
||||
return EXIT;
|
||||
}
|
||||
return undefined;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/module-type-aliases",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Docusaurus module type aliases.",
|
||||
"types": "./src/index.d.ts",
|
||||
"publishConfig": {
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/react-loadable": "5.5.2",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*",
|
||||
"@types/react-router-config": "*",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-client-redirects",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Client redirects plugin for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
|
@ -18,18 +18,18 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/logger": "3.2.0",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/utils-common": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/logger": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"@docusaurus/utils-common": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"eta": "^2.2.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"tslib": "^2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/types": "3.2.0"
|
||||
"@docusaurus/types": "3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {removeTrailingSlash} from '@docusaurus/utils-common';
|
||||
import {removeTrailingSlash} from '@docusaurus/utils';
|
||||
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||
import collectRedirects from '../collectRedirects';
|
||||
import {validateOptions} from '../options';
|
||||
|
|
|
|||
|
|
@ -7,11 +7,8 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import logger from '@docusaurus/logger';
|
||||
import {
|
||||
applyTrailingSlash,
|
||||
addTrailingSlash,
|
||||
removeTrailingSlash,
|
||||
} from '@docusaurus/utils-common';
|
||||
import {addTrailingSlash, removeTrailingSlash} from '@docusaurus/utils';
|
||||
import {applyTrailingSlash} from '@docusaurus/utils-common';
|
||||
import {
|
||||
createFromExtensionsRedirects,
|
||||
createToExtensionsRedirects,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
addTrailingSlash,
|
||||
removeSuffix,
|
||||
removeTrailingSlash,
|
||||
} from '@docusaurus/utils-common';
|
||||
} from '@docusaurus/utils';
|
||||
import type {RedirectItem} from './types';
|
||||
|
||||
const ExtensionAdditionalMessage =
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {addLeadingSlash, removePrefix} from '@docusaurus/utils-common';
|
||||
import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
|
||||
import collectRedirects from './collectRedirects';
|
||||
import writeRedirectFiles, {
|
||||
toRedirectFiles,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-content-blog",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Blog plugin for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"types": "src/plugin-content-blog.d.ts",
|
||||
|
|
@ -31,13 +31,13 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/logger": "3.2.0",
|
||||
"@docusaurus/mdx-loader": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/utils-common": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/logger": "3.0.0",
|
||||
"@docusaurus/mdx-loader": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"@docusaurus/utils-common": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"feed": "^4.2.2",
|
||||
"fs-extra": "^11.1.1",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-content-docs",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Docs plugin for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"sideEffects": false,
|
||||
|
|
@ -35,14 +35,13 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/logger": "3.2.0",
|
||||
"@docusaurus/mdx-loader": "3.2.0",
|
||||
"@docusaurus/module-type-aliases": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/utils-common": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/logger": "3.0.0",
|
||||
"@docusaurus/mdx-loader": "3.0.0",
|
||||
"@docusaurus/module-type-aliases": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"@types/react-router-config": "^5.0.7",
|
||||
"combine-promises": "^1.1.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import path from 'path';
|
||||
import _ from 'lodash';
|
||||
import logger from '@docusaurus/logger';
|
||||
import {addTrailingSlash} from '@docusaurus/utils-common';
|
||||
import {addTrailingSlash} from '@docusaurus/utils';
|
||||
import {createDocsByIdIndex, toCategoryIndexMatcherParam} from '../docs';
|
||||
import type {
|
||||
SidebarItemDoc,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,12 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {isValidPathname, resolvePathname} from '@docusaurus/utils';
|
||||
import {addLeadingSlash, addTrailingSlash} from '@docusaurus/utils-common';
|
||||
import {
|
||||
addLeadingSlash,
|
||||
addTrailingSlash,
|
||||
isValidPathname,
|
||||
resolvePathname,
|
||||
} from '@docusaurus/utils';
|
||||
import {
|
||||
DefaultNumberPrefixParser,
|
||||
stripPathNumberPrefixes,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-content-pages",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Pages plugin for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"types": "src/plugin-content-pages.d.ts",
|
||||
|
|
@ -18,11 +18,11 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/mdx-loader": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/mdx-loader": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"tslib": "^2.6.0",
|
||||
"webpack": "^5.88.1"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
.tsbuildinfo*
|
||||
tsconfig*
|
||||
__tests__
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# `@docusaurus/plugin-content-showcase`
|
||||
|
||||
Showcase plugin for Docusaurus.
|
||||
|
||||
## Usage
|
||||
|
||||
See [plugin-content-showcase documentation](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-showcase).
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-content-showcase",
|
||||
"version": "3.0.0",
|
||||
"description": "Showcase plugin for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"types": "src/plugin-content-showcase.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc --watch"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/docusaurus.git",
|
||||
"directory": "packages/docusaurus-plugin-content-showcase"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"tslib": "^2.6.0",
|
||||
"webpack": "^5.88.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
}
|
||||
}
|
||||
21
packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js
generated
Normal file
21
packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js
generated
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
title: 'My Site',
|
||||
tagline: 'The tagline of my site',
|
||||
url: 'https://your-docusaurus-site.example.com',
|
||||
baseUrl: '/',
|
||||
favicon: 'img/favicon.ico',
|
||||
markdown: {
|
||||
parseFrontMatter: async (params) => {
|
||||
const result = await params.defaultParseFrontMatter(params);
|
||||
result.frontMatter.custom_frontMatter = 'added by parseFrontMatter';
|
||||
return result;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
title: "Hello"
|
||||
description: "World"
|
||||
preview: github.com/ozakione.png
|
||||
website: "https://docusaurus.io/"
|
||||
source: "https://github.com/facebook/docusaurus"
|
||||
tags: ["opensource", "meta"]
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`docusaurus-plugin-content-showcase loads simple showcase 1`] = `
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"description": "World",
|
||||
"preview": "github.com/ozakione.png",
|
||||
"source": "https://github.com/facebook/docusaurus",
|
||||
"tags": [
|
||||
"opensource",
|
||||
"meta",
|
||||
],
|
||||
"title": "Hello",
|
||||
"website": "https://docusaurus.io/",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {escapeRegexp} from '@docusaurus/utils';
|
||||
import {validateShowcaseFrontMatter} from '../frontMatter';
|
||||
import type {ShowcaseFrontMatter} from '@docusaurus/plugin-content-showcase';
|
||||
|
||||
function testField(params: {
|
||||
prefix: string;
|
||||
validFrontMatters: ShowcaseFrontMatter[];
|
||||
convertibleFrontMatter?: [
|
||||
ConvertibleFrontMatter: {[key: string]: unknown},
|
||||
ConvertedFrontMatter: ShowcaseFrontMatter,
|
||||
][];
|
||||
invalidFrontMatters?: [
|
||||
InvalidFrontMatter: {[key: string]: unknown},
|
||||
ErrorMessage: string,
|
||||
][];
|
||||
}) {
|
||||
// eslint-disable-next-line jest/require-top-level-describe
|
||||
test(`[${params.prefix}] accept valid values`, () => {
|
||||
params.validFrontMatters.forEach((frontMatter) => {
|
||||
expect(validateShowcaseFrontMatter(frontMatter)).toEqual(frontMatter);
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/require-top-level-describe
|
||||
test(`[${params.prefix}] convert valid values`, () => {
|
||||
params.convertibleFrontMatter?.forEach(
|
||||
([convertibleFrontMatter, convertedFrontMatter]) => {
|
||||
expect(validateShowcaseFrontMatter(convertibleFrontMatter)).toEqual(
|
||||
convertedFrontMatter,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/require-top-level-describe
|
||||
test(`[${params.prefix}] throw error for values`, () => {
|
||||
params.invalidFrontMatters?.forEach(([frontMatter, message]) => {
|
||||
try {
|
||||
validateShowcaseFrontMatter(frontMatter);
|
||||
throw new Error(
|
||||
`Doc front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify(
|
||||
frontMatter,
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
expect((err as Error).message).toMatch(
|
||||
new RegExp(escapeRegexp(message)),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('doc front matter schema', () => {
|
||||
it('accepts valid frontmatter', () => {
|
||||
const frontMatter: ShowcaseFrontMatter = {
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
preview: 'preview',
|
||||
source: 'source',
|
||||
tags: [],
|
||||
website: 'website',
|
||||
};
|
||||
expect(validateShowcaseFrontMatter(frontMatter)).toEqual(frontMatter);
|
||||
});
|
||||
|
||||
it('reject invalid frontmatter', () => {
|
||||
const frontMatter = {};
|
||||
expect(() =>
|
||||
validateShowcaseFrontMatter(frontMatter),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`""title" is required. "description" is required. "preview" is required. "website" is required. "source" is required. "tags" is required"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateShowcaseFrontMatter full', () => {
|
||||
testField({
|
||||
prefix: 'valid full frontmatter',
|
||||
validFrontMatters: [
|
||||
{
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
preview: 'preview',
|
||||
source: 'source',
|
||||
tags: [],
|
||||
website: 'website',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* 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 path from 'path';
|
||||
import {loadContext} from '@docusaurus/core/src/server/site';
|
||||
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||
|
||||
import pluginContentPages from '../index';
|
||||
import {validateOptions} from '../options';
|
||||
|
||||
describe('docusaurus-plugin-content-showcase', () => {
|
||||
it('loads simple showcase', async () => {
|
||||
const siteDir = path.join(__dirname, '__fixtures__', 'website');
|
||||
const context = await loadContext({siteDir});
|
||||
const plugin = pluginContentPages(
|
||||
context,
|
||||
validateOptions({
|
||||
validate: normalizePluginOptions,
|
||||
options: {
|
||||
path: 'src/showcase',
|
||||
},
|
||||
}),
|
||||
);
|
||||
const showcaseMetadata = await plugin.loadContent!();
|
||||
|
||||
expect(showcaseMetadata).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* 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 {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||
import {validateOptions, DEFAULT_OPTIONS} from '../options';
|
||||
import type {Options} from '@docusaurus/plugin-content-showcase';
|
||||
|
||||
function testValidate(options: Options) {
|
||||
return validateOptions({validate: normalizePluginOptions, options});
|
||||
}
|
||||
const defaultOptions = {
|
||||
...DEFAULT_OPTIONS,
|
||||
id: 'default',
|
||||
};
|
||||
|
||||
describe('normalizeShowcasePluginOptions', () => {
|
||||
it('returns default options for undefined user options', () => {
|
||||
expect(testValidate({})).toEqual(defaultOptions);
|
||||
});
|
||||
|
||||
it('fills in default options for partially defined user options', () => {
|
||||
expect(testValidate({path: 'src/foo'})).toEqual({
|
||||
...defaultOptions,
|
||||
path: 'src/foo',
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts correctly defined user options', () => {
|
||||
const userOptions = {
|
||||
path: 'src/showcase',
|
||||
routeBasePath: '/showcase',
|
||||
include: ['**/*.{yaml,yml}'],
|
||||
exclude: ['**/$*/'],
|
||||
};
|
||||
expect(testValidate(userOptions)).toEqual({
|
||||
...defaultOptions,
|
||||
...userOptions,
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects bad path inputs', () => {
|
||||
expect(() => {
|
||||
testValidate({
|
||||
// @ts-expect-error: bad attribute
|
||||
path: 42,
|
||||
});
|
||||
}).toThrowErrorMatchingInlineSnapshot(`""path" must be a string"`);
|
||||
});
|
||||
|
||||
it('empty routeBasePath replace default path("/")', () => {
|
||||
expect(
|
||||
testValidate({
|
||||
routeBasePath: '',
|
||||
}),
|
||||
).toEqual({
|
||||
...defaultOptions,
|
||||
routeBasePath: '/',
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts correctly defined tags file options', () => {
|
||||
const userOptions = {
|
||||
tags: '@site/showcase/tags.yaml',
|
||||
};
|
||||
expect(testValidate(userOptions)).toEqual({
|
||||
...defaultOptions,
|
||||
...userOptions,
|
||||
});
|
||||
});
|
||||
|
||||
it('reject badly defined tags file options', () => {
|
||||
const userOptions = {
|
||||
tags: 42,
|
||||
};
|
||||
expect(() =>
|
||||
testValidate(
|
||||
// @ts-expect-error: bad attributes
|
||||
userOptions,
|
||||
),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`""tags" must be one of [string, array]"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts correctly defined tags object options', () => {
|
||||
const userOptions = {
|
||||
tags: [
|
||||
{
|
||||
label: 'foo',
|
||||
description: {
|
||||
message: 'bar',
|
||||
id: 'baz',
|
||||
},
|
||||
color: 'red',
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(testValidate(userOptions)).toEqual({
|
||||
...defaultOptions,
|
||||
...userOptions,
|
||||
});
|
||||
});
|
||||
|
||||
it('reject bedly defined tags object options', () => {
|
||||
const userOptions = {
|
||||
tags: [
|
||||
{
|
||||
label: 'foo',
|
||||
description: {
|
||||
message: 'bar',
|
||||
id: 'baz',
|
||||
},
|
||||
color: 42,
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(() =>
|
||||
testValidate(
|
||||
// @ts-expect-error: bad attributes
|
||||
userOptions,
|
||||
),
|
||||
).toThrowErrorMatchingInlineSnapshot(`""tags[0].color" must be a string"`);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* 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 {Joi, validateFrontMatter} from '@docusaurus/utils-validation';
|
||||
import type {ShowcaseFrontMatter} from '@docusaurus/plugin-content-showcase';
|
||||
|
||||
const showcaseFrontMatterSchema = Joi.object({
|
||||
title: Joi.string().required(),
|
||||
description: Joi.string().required(),
|
||||
preview: Joi.string().required(),
|
||||
website: Joi.string().required(),
|
||||
source: Joi.string().required(),
|
||||
tags: Joi.array().items(Joi.string()).required(),
|
||||
});
|
||||
|
||||
export function validateShowcaseFrontMatter(frontMatter: {
|
||||
[key: string]: unknown;
|
||||
}): ShowcaseFrontMatter {
|
||||
return validateFrontMatter(frontMatter, showcaseFrontMatterSchema);
|
||||
}
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
/**
|
||||
* 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 path from 'path';
|
||||
import {
|
||||
getFolderContainingFile,
|
||||
getPluginI18nPath,
|
||||
Globby,
|
||||
} from '@docusaurus/utils';
|
||||
import Yaml from 'js-yaml';
|
||||
|
||||
import {Joi} from '@docusaurus/utils-validation';
|
||||
import {validateShowcaseFrontMatter} from './frontMatter';
|
||||
import {tagSchema} from './options';
|
||||
import type {LoadContext, Plugin} from '@docusaurus/types';
|
||||
import type {
|
||||
PluginOptions,
|
||||
ShowcaseItem,
|
||||
TagOption,
|
||||
} from '@docusaurus/plugin-content-showcase';
|
||||
import type {ShowcaseContentPaths} from './types';
|
||||
|
||||
export function getContentPathList(
|
||||
contentPaths: ShowcaseContentPaths,
|
||||
): string[] {
|
||||
return [contentPaths.contentPathLocalized, contentPaths.contentPath];
|
||||
}
|
||||
|
||||
async function getTagsDefinition(
|
||||
filePath: string | TagOption[],
|
||||
): Promise<string[]> {
|
||||
if (Array.isArray(filePath)) {
|
||||
return filePath.map((tag) => tag.label);
|
||||
}
|
||||
|
||||
const rawYaml = await fs.readFile(filePath, 'utf-8');
|
||||
const unsafeYaml: any = Yaml.load(rawYaml);
|
||||
console.log('unsafeYaml:', unsafeYaml);
|
||||
|
||||
const transformedData = unsafeYaml.tags.map((item: any) => {
|
||||
const [label] = Object.keys(item); // Extract label from object key
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
const {description, color} = item[label]; // Extract description and color
|
||||
return {label, description, color}; // Create new object with transformed structure
|
||||
});
|
||||
console.log('transformedData:', transformedData);
|
||||
|
||||
const safeYaml = tagSchema.validate(transformedData);
|
||||
|
||||
if (safeYaml.error) {
|
||||
throw new Error(`Invalid tags.yaml file: ${safeYaml.error.message}`);
|
||||
}
|
||||
|
||||
const tagLabels = safeYaml.value.map((tag: any) => Object.keys(tag)[0]);
|
||||
return tagLabels;
|
||||
}
|
||||
|
||||
function createTagSchema(tags: string[]): Joi.Schema {
|
||||
return Joi.alternatives().try(
|
||||
Joi.string().valid(...tags), // Schema for single string
|
||||
Joi.array().items(Joi.string().valid(...tags)), // Schema for array of strings
|
||||
);
|
||||
}
|
||||
|
||||
function validateFrontMatterTags(
|
||||
frontMatterTags: string[],
|
||||
tagListSchema: Joi.Schema,
|
||||
): void {
|
||||
const result = tagListSchema.validate(frontMatterTags);
|
||||
if (result.error) {
|
||||
throw new Error(
|
||||
`Front matter contains invalid tags: ${result.error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default function pluginContentShowcase(
|
||||
context: LoadContext,
|
||||
options: PluginOptions,
|
||||
): Plugin<ShowcaseItem | null> {
|
||||
const {siteDir, localizationDir} = context;
|
||||
|
||||
const contentPaths: ShowcaseContentPaths = {
|
||||
contentPath: path.resolve(siteDir, options.path),
|
||||
contentPathLocalized: getPluginI18nPath({
|
||||
localizationDir,
|
||||
pluginName: 'docusaurus-plugin-content-pages',
|
||||
pluginId: options.id,
|
||||
}),
|
||||
};
|
||||
|
||||
return {
|
||||
name: 'docusaurus-plugin-content-showcase',
|
||||
|
||||
// todo doesn't work
|
||||
// getPathsToWatch() {
|
||||
// const {include} = options;
|
||||
// return getContentPathList(contentPaths).flatMap((contentPath) =>
|
||||
// include.map((pattern) => `${contentPath}/${pattern}`),
|
||||
// );
|
||||
// },
|
||||
|
||||
async loadContent(): Promise<ShowcaseItem | null> {
|
||||
const {include} = options;
|
||||
|
||||
if (!(await fs.pathExists(contentPaths.contentPath))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// const {baseUrl} = siteConfig;
|
||||
const showcaseFiles = await Globby(include, {
|
||||
cwd: contentPaths.contentPath,
|
||||
ignore: options.exclude,
|
||||
});
|
||||
|
||||
const filteredShowcaseFiles = showcaseFiles.filter(
|
||||
(source) => source !== 'tags.yaml',
|
||||
);
|
||||
|
||||
// todo refactor ugly
|
||||
const tagFilePath = path.join(
|
||||
await getFolderContainingFile(
|
||||
getContentPathList(contentPaths),
|
||||
'tags.yaml',
|
||||
),
|
||||
'tags.yaml',
|
||||
);
|
||||
|
||||
const tagList = await getTagsDefinition(tagFilePath);
|
||||
const createdTagSchema = createTagSchema(tagList);
|
||||
console.log('createdTagSchema:', createdTagSchema.describe());
|
||||
|
||||
async function processShowcaseSourceFile(relativeSource: string) {
|
||||
// Lookup in localized folder in priority
|
||||
const contentPath = await getFolderContainingFile(
|
||||
getContentPathList(contentPaths),
|
||||
relativeSource,
|
||||
);
|
||||
|
||||
const sourcePath = path.join(contentPath, relativeSource);
|
||||
|
||||
const rawYaml = await fs.readFile(sourcePath, 'utf-8');
|
||||
const unsafeYaml = Yaml.load(rawYaml) as {[key: string]: unknown};
|
||||
const yaml = validateShowcaseFrontMatter(unsafeYaml);
|
||||
|
||||
validateFrontMatterTags(yaml.tags, createdTagSchema);
|
||||
|
||||
return yaml;
|
||||
}
|
||||
|
||||
async function doProcessShowcaseSourceFile(relativeSource: string) {
|
||||
try {
|
||||
return await processShowcaseSourceFile(relativeSource);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Processing of page source file path=${relativeSource} failed.`,
|
||||
{cause: err as Error},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
items: await Promise.all(
|
||||
filteredShowcaseFiles.map(doProcessShowcaseSourceFile),
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
async contentLoaded({content, actions}) {
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {addRoute, createData} = actions;
|
||||
|
||||
const showcaseAllData = await createData(
|
||||
'showcaseAll.json',
|
||||
JSON.stringify(content.items),
|
||||
);
|
||||
|
||||
addRoute({
|
||||
path: '/showcaseAll',
|
||||
component: '@theme/Showcase',
|
||||
modules: {
|
||||
content: showcaseAllData,
|
||||
// img: '@site/src/showcase/website/ozaki/aot.jpg',
|
||||
},
|
||||
exact: true,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export {validateOptions} from './options';
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* 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 type {LoaderContext} from 'webpack';
|
||||
|
||||
export default function markdownLoader(
|
||||
this: LoaderContext<undefined>,
|
||||
fileString: string,
|
||||
): void {
|
||||
const callback = this.async();
|
||||
|
||||
// const options = this.getOptions();
|
||||
|
||||
// TODO provide additional md processing here? like interlinking pages?
|
||||
// fileString = linkify(fileString)
|
||||
|
||||
return callback(null, fileString);
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* 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 {Joi, RouteBasePathSchema} from '@docusaurus/utils-validation';
|
||||
import {GlobExcludeDefault} from '@docusaurus/utils';
|
||||
import type {OptionValidationContext} from '@docusaurus/types';
|
||||
import type {PluginOptions, Options} from '@docusaurus/plugin-content-showcase';
|
||||
|
||||
export const DEFAULT_OPTIONS: PluginOptions = {
|
||||
id: 'showcase',
|
||||
path: 'showcase', // Path to data on filesystem, relative to site dir.
|
||||
routeBasePath: '/', // URL Route.
|
||||
include: ['**/*.{yml,yaml}'], // Extensions to include.
|
||||
exclude: GlobExcludeDefault,
|
||||
tags: '@site/showcase/tags.yaml',
|
||||
};
|
||||
|
||||
export const tagSchema = Joi.array().items(
|
||||
Joi.object({
|
||||
label: Joi.string().required(),
|
||||
description: Joi.object({
|
||||
message: Joi.string().required(),
|
||||
id: Joi.string().required(),
|
||||
}).required(),
|
||||
color: Joi.string().required(),
|
||||
}),
|
||||
);
|
||||
|
||||
const PluginOptionSchema = Joi.object<PluginOptions>({
|
||||
path: Joi.string().default(DEFAULT_OPTIONS.path),
|
||||
routeBasePath: RouteBasePathSchema.default(DEFAULT_OPTIONS.routeBasePath),
|
||||
include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include),
|
||||
exclude: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.exclude),
|
||||
id: Joi.string().default(DEFAULT_OPTIONS.id),
|
||||
tags: Joi.alternatives()
|
||||
.try(Joi.string().default(DEFAULT_OPTIONS.tags), tagSchema)
|
||||
.default(DEFAULT_OPTIONS.tags),
|
||||
});
|
||||
|
||||
export function validateOptions({
|
||||
validate,
|
||||
options,
|
||||
}: OptionValidationContext<Options, PluginOptions>): PluginOptions {
|
||||
const validatedOptions = validate(PluginOptionSchema, options);
|
||||
return validatedOptions;
|
||||
}
|
||||
60
packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts
vendored
Normal file
60
packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare module '@docusaurus/plugin-content-showcase' {
|
||||
import type {LoadContext, Plugin} from '@docusaurus/types';
|
||||
|
||||
export type TagOption = {
|
||||
label: string;
|
||||
description: {
|
||||
message: string;
|
||||
id: string;
|
||||
};
|
||||
color: string;
|
||||
};
|
||||
|
||||
export type PluginOptions = {
|
||||
id?: string;
|
||||
path: string;
|
||||
routeBasePath: string;
|
||||
include: string[];
|
||||
exclude: string[];
|
||||
tags: string | TagOption[];
|
||||
};
|
||||
|
||||
type TagType =
|
||||
| 'favorite'
|
||||
| 'opensource'
|
||||
| 'product'
|
||||
| 'design'
|
||||
| 'i18n'
|
||||
| 'versioning'
|
||||
| 'large'
|
||||
| 'meta'
|
||||
| 'personal'
|
||||
| 'rtl';
|
||||
|
||||
export type ShowcaseFrontMatter = {
|
||||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly preview: string | null; // null = use our serverless screenshot service
|
||||
readonly website: string;
|
||||
readonly source: string | null;
|
||||
readonly tags: TagType[];
|
||||
};
|
||||
|
||||
export type ShowcaseItem = {
|
||||
items: ShowcaseFrontMatter[];
|
||||
};
|
||||
|
||||
export type Options = Partial<PluginOptions>;
|
||||
|
||||
export default function pluginContentShowcase(
|
||||
context: LoadContext,
|
||||
options: PluginOptions,
|
||||
): Promise<Plugin<ShowcaseItem | null>>;
|
||||
}
|
||||
|
|
@ -4,14 +4,7 @@
|
|||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
.wrappingBlock {
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.wrappingBlock code[class^='codeBlockLines'] {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
export type ShowcaseContentPaths = {
|
||||
contentPath: string;
|
||||
contentPathLocalized: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": false,
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": "./lib/.tsbuildinfo",
|
||||
"rootDir": "src",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["**/__tests__/**"]
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-debug",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Debug plugin for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"types": "src/plugin-debug.d.ts",
|
||||
|
|
@ -20,9 +20,9 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"react-json-view-lite": "^1.2.0",
|
||||
"tslib": "^2.6.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-google-analytics",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Global analytics (analytics.js) plugin for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
|
@ -18,9 +18,9 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"tslib": "^2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-google-gtag",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Global Site Tag (gtag.js) plugin for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
|
@ -18,9 +18,9 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"@types/gtag.js": "^0.0.12",
|
||||
"tslib": "^2.6.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-google-tag-manager",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Google Tag Manager (gtm.js) plugin for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
|
@ -18,9 +18,9 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"tslib": "^2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-ideal-image",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Docusaurus Plugin to generate an almost ideal image (responsive, lazy-loading, and low quality placeholder).",
|
||||
"main": "lib/index.js",
|
||||
"types": "src/plugin-ideal-image.d.ts",
|
||||
|
|
@ -20,12 +20,12 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/lqip-loader": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/lqip-loader": "3.0.0",
|
||||
"@docusaurus/responsive-loader": "^1.7.0",
|
||||
"@docusaurus/theme-translations": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/theme-translations": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"@slorber/react-ideal-image": "^0.0.12",
|
||||
"react-waypoint": "^10.3.0",
|
||||
"sharp": "^0.32.3",
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
"webpack": "^5.88.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.2.0",
|
||||
"@docusaurus/module-type-aliases": "3.0.0",
|
||||
"fs-extra": "^11.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-pwa",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Docusaurus Plugin to add PWA support.",
|
||||
"main": "lib/index.js",
|
||||
"types": "src/plugin-pwa.d.ts",
|
||||
|
|
@ -22,12 +22,12 @@
|
|||
"dependencies": {
|
||||
"@babel/core": "^7.23.3",
|
||||
"@babel/preset-env": "^7.23.3",
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/theme-common": "3.2.0",
|
||||
"@docusaurus/theme-translations": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/theme-common": "3.0.0",
|
||||
"@docusaurus/theme-translations": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"babel-loader": "^9.1.3",
|
||||
"clsx": "^2.0.0",
|
||||
"core-js": "^3.31.1",
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
"workbox-window": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.2.0",
|
||||
"@docusaurus/module-type-aliases": "3.0.0",
|
||||
"fs-extra": "^11.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-sitemap",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Simple sitemap generation plugin for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
|
@ -18,12 +18,12 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/logger": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/utils-common": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/logger": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"@docusaurus/utils-common": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"sitemap": "^7.1.1",
|
||||
"tslib": "^2.6.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-vercel-analytics",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Global vercel analytics plugin for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
|
@ -18,11 +18,11 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/logger": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/logger": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"@vercel/analytics": "^1.1.1",
|
||||
"tslib": "^2.6.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/preset-classic",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Classic preset for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
|
@ -18,19 +18,19 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/plugin-content-blog": "3.2.0",
|
||||
"@docusaurus/plugin-content-docs": "3.2.0",
|
||||
"@docusaurus/plugin-content-pages": "3.2.0",
|
||||
"@docusaurus/plugin-debug": "3.2.0",
|
||||
"@docusaurus/plugin-google-analytics": "3.2.0",
|
||||
"@docusaurus/plugin-google-gtag": "3.2.0",
|
||||
"@docusaurus/plugin-google-tag-manager": "3.2.0",
|
||||
"@docusaurus/plugin-sitemap": "3.2.0",
|
||||
"@docusaurus/theme-classic": "3.2.0",
|
||||
"@docusaurus/theme-common": "3.2.0",
|
||||
"@docusaurus/theme-search-algolia": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0"
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/plugin-content-blog": "3.0.0",
|
||||
"@docusaurus/plugin-content-docs": "3.0.0",
|
||||
"@docusaurus/plugin-content-pages": "3.0.0",
|
||||
"@docusaurus/plugin-debug": "3.0.0",
|
||||
"@docusaurus/plugin-google-analytics": "3.0.0",
|
||||
"@docusaurus/plugin-google-gtag": "3.0.0",
|
||||
"@docusaurus/plugin-google-tag-manager": "3.0.0",
|
||||
"@docusaurus/plugin-sitemap": "3.0.0",
|
||||
"@docusaurus/theme-classic": "3.0.0",
|
||||
"@docusaurus/theme-common": "3.0.0",
|
||||
"@docusaurus/theme-search-algolia": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/remark-plugin-npm2yarn",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Remark plugin for converting npm commands to Yarn commands as tabs.",
|
||||
"main": "lib/index.js",
|
||||
"publishConfig": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/theme-classic",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Classic theme for Docusaurus",
|
||||
"main": "lib/index.js",
|
||||
"types": "src/theme-classic.d.ts",
|
||||
|
|
@ -20,18 +20,19 @@
|
|||
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/mdx-loader": "3.2.0",
|
||||
"@docusaurus/module-type-aliases": "3.2.0",
|
||||
"@docusaurus/plugin-content-blog": "3.2.0",
|
||||
"@docusaurus/plugin-content-docs": "3.2.0",
|
||||
"@docusaurus/plugin-content-pages": "3.2.0",
|
||||
"@docusaurus/theme-common": "3.2.0",
|
||||
"@docusaurus/theme-translations": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/utils-common": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/mdx-loader": "3.0.0",
|
||||
"@docusaurus/module-type-aliases": "3.0.0",
|
||||
"@docusaurus/plugin-content-blog": "3.0.0",
|
||||
"@docusaurus/plugin-content-docs": "3.0.0",
|
||||
"@docusaurus/plugin-content-pages": "3.0.0",
|
||||
"@docusaurus/plugin-content-showcase": "3.0.0",
|
||||
"@docusaurus/theme-common": "3.0.0",
|
||||
"@docusaurus/theme-translations": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"@docusaurus/utils-common": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"copy-text-to-clipboard": "^3.2.0",
|
||||
|
|
@ -41,6 +42,7 @@
|
|||
"postcss": "^8.4.26",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"react-popper": "^2.3.0",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"rtlcss": "^4.1.0",
|
||||
"tslib": "^2.6.0",
|
||||
|
|
|
|||
|
|
@ -247,6 +247,96 @@ declare module '@theme/BlogPostItems' {
|
|||
export default function BlogPostItem(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/ShowcaseDetails' {
|
||||
import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase';
|
||||
|
||||
export type User = ShowcaseItem['website'][number];
|
||||
|
||||
export type Props = {
|
||||
content: User;
|
||||
};
|
||||
|
||||
export default function Showcase(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Showcase' {
|
||||
import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase';
|
||||
|
||||
export type User = ShowcaseItem['website'][number];
|
||||
|
||||
export type Props = {
|
||||
content: User[];
|
||||
};
|
||||
|
||||
export default function Showcase(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Showcase/ShowcaseCard' {
|
||||
export type User = {
|
||||
title: string;
|
||||
description: string;
|
||||
preview: string | null; // null = use our serverless screenshot service
|
||||
website: string;
|
||||
source: string | null;
|
||||
tags: TagType[];
|
||||
};
|
||||
|
||||
export interface Props {
|
||||
readonly user: User;
|
||||
}
|
||||
|
||||
export default function ShowcaseCard(props: Props): JSX.Element;
|
||||
}
|
||||
declare module '@theme/Showcase/ShowcaseTooltip' {
|
||||
export interface Props {
|
||||
anchorEl?: HTMLElement | string;
|
||||
id: string;
|
||||
text: string;
|
||||
children: React.ReactElement;
|
||||
}
|
||||
|
||||
export default function ShowcaseTooltip(props: Props): JSX.Element;
|
||||
}
|
||||
declare module '@theme/Showcase/ShowcaseTagSelect' {
|
||||
import {type ComponentProps, type ReactNode, type ReactElement} from 'react';
|
||||
|
||||
export interface Props extends ComponentProps<'input'> {
|
||||
icon: ReactElement<ComponentProps<'svg'>>;
|
||||
label: ReactNode;
|
||||
tag: TagType;
|
||||
}
|
||||
|
||||
export default function ShowcaseTagSelect(props: Props): JSX.Element;
|
||||
}
|
||||
declare module '@theme/Showcase/ShowcaseFilterToggle' {
|
||||
export type Operator = 'OR' | 'AND';
|
||||
|
||||
export default function ShowcaseFilterToggle(): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Showcase/FavoriteIcon' {
|
||||
import {type ReactNode, type ComponentProps} from 'react';
|
||||
|
||||
export type SvgIconProps = ComponentProps<'svg'> & {
|
||||
viewBox?: string;
|
||||
size?: 'inherit' | 'small' | 'medium' | 'large';
|
||||
color?:
|
||||
| 'inherit'
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'success'
|
||||
| 'error'
|
||||
| 'warning';
|
||||
svgClass?: string; // Class attribute on the child
|
||||
colorAttr?: string; // Applies a color attribute to the SVG element.
|
||||
children: ReactNode; // Node passed into the SVG element.
|
||||
};
|
||||
|
||||
export type Props = Omit<SvgIconProps, 'children'>;
|
||||
|
||||
export default function FavoriteIcon(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/BlogPostItem/Container' {
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import type {Props, SvgIconProps} from '@theme/Showcase/FavoriteIcon';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
function Svg(props: SvgIconProps): JSX.Element {
|
||||
const {
|
||||
svgClass,
|
||||
colorAttr,
|
||||
children,
|
||||
color = 'inherit',
|
||||
size = 'medium',
|
||||
viewBox = '0 0 24 24',
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
viewBox={viewBox}
|
||||
color={colorAttr}
|
||||
aria-hidden
|
||||
className={clsx(styles.svgIcon, styles[color], styles[size], svgClass)}
|
||||
{...rest}>
|
||||
{children}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FavoriteIcon(props: Props): JSX.Element {
|
||||
return (
|
||||
<Svg {...props}>
|
||||
<path d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z" />
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.svgIcon {
|
||||
user-select: none;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
display: inline-block;
|
||||
fill: currentColor;
|
||||
flex-shrink: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* font-size */
|
||||
.small {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.medium {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.large {
|
||||
font-size: 2.185rem;
|
||||
}
|
||||
|
||||
/* colors */
|
||||
.primary {
|
||||
color: var(--ifm-color-primary);
|
||||
}
|
||||
|
||||
.secondary {
|
||||
color: var(--ifm-color-secondary);
|
||||
}
|
||||
|
||||
.success {
|
||||
color: var(--ifm-color-success);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--ifm-color-error);
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: var(--ifm-color-warning);
|
||||
}
|
||||
|
||||
.inherit {
|
||||
color: inherit;
|
||||
}
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Link from '@docusaurus/Link';
|
||||
import Translate, {translate} from '@docusaurus/Translate';
|
||||
import FavoriteIcon from '@theme/Showcase/FavoriteIcon';
|
||||
import Heading from '@theme/Heading';
|
||||
import Tooltip from '@theme/Showcase/ShowcaseTooltip';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const Tags: {[type in TagType]: Tag} = {
|
||||
favorite: {
|
||||
label: translate({message: 'Favorite'}),
|
||||
description: translate({
|
||||
message:
|
||||
'Our favorite Docusaurus sites that you must absolutely check out!',
|
||||
id: 'showcase.tag.favorite.description',
|
||||
}),
|
||||
color: '#e9669e',
|
||||
},
|
||||
|
||||
opensource: {
|
||||
label: translate({message: 'Open-Source'}),
|
||||
description: translate({
|
||||
message: 'Open-Source Docusaurus sites can be useful for inspiration!',
|
||||
id: 'showcase.tag.opensource.description',
|
||||
}),
|
||||
color: '#39ca30',
|
||||
},
|
||||
|
||||
product: {
|
||||
label: translate({message: 'Product'}),
|
||||
description: translate({
|
||||
message: 'Docusaurus sites associated to a commercial product!',
|
||||
id: 'showcase.tag.product.description',
|
||||
}),
|
||||
color: '#dfd545',
|
||||
},
|
||||
|
||||
design: {
|
||||
label: translate({message: 'Design'}),
|
||||
description: translate({
|
||||
message:
|
||||
'Beautiful Docusaurus sites, polished and standing out from the initial template!',
|
||||
id: 'showcase.tag.design.description',
|
||||
}),
|
||||
color: '#a44fb7',
|
||||
},
|
||||
|
||||
i18n: {
|
||||
label: translate({message: 'I18n'}),
|
||||
description: translate({
|
||||
message:
|
||||
'Translated Docusaurus sites using the internationalization support with more than 1 locale.',
|
||||
id: 'showcase.tag.i18n.description',
|
||||
}),
|
||||
color: '#127f82',
|
||||
},
|
||||
|
||||
versioning: {
|
||||
label: translate({message: 'Versioning'}),
|
||||
description: translate({
|
||||
message:
|
||||
'Docusaurus sites using the versioning feature of the docs plugin to manage multiple versions.',
|
||||
id: 'showcase.tag.versioning.description',
|
||||
}),
|
||||
color: '#fe6829',
|
||||
},
|
||||
|
||||
large: {
|
||||
label: translate({message: 'Large'}),
|
||||
description: translate({
|
||||
message:
|
||||
'Very large Docusaurus sites, including many more pages than the average!',
|
||||
id: 'showcase.tag.large.description',
|
||||
}),
|
||||
color: '#8c2f00',
|
||||
},
|
||||
|
||||
meta: {
|
||||
label: translate({message: 'Meta'}),
|
||||
description: translate({
|
||||
message: 'Docusaurus sites of Meta (formerly Facebook) projects',
|
||||
id: 'showcase.tag.meta.description',
|
||||
}),
|
||||
color: '#4267b2', // Facebook blue
|
||||
},
|
||||
|
||||
personal: {
|
||||
label: translate({message: 'Personal'}),
|
||||
description: translate({
|
||||
message:
|
||||
'Personal websites, blogs and digital gardens built with Docusaurus',
|
||||
id: 'showcase.tag.personal.description',
|
||||
}),
|
||||
color: '#14cfc3',
|
||||
},
|
||||
|
||||
rtl: {
|
||||
label: translate({message: 'RTL Direction'}),
|
||||
description: translate({
|
||||
message:
|
||||
'Docusaurus sites using the right-to-left reading direction support.',
|
||||
id: 'showcase.tag.rtl.description',
|
||||
}),
|
||||
color: '#ffcfc3',
|
||||
},
|
||||
};
|
||||
|
||||
const TagList = Object.keys(Tags) as TagType[];
|
||||
|
||||
type TagType =
|
||||
| 'favorite'
|
||||
| 'opensource'
|
||||
| 'product'
|
||||
| 'design'
|
||||
| 'i18n'
|
||||
| 'versioning'
|
||||
| 'large'
|
||||
| 'meta'
|
||||
| 'personal'
|
||||
| 'rtl';
|
||||
|
||||
type User = {
|
||||
title: string;
|
||||
description: string;
|
||||
preview: string | null; // null = use our serverless screenshot service
|
||||
website: string;
|
||||
source: string | null;
|
||||
tags: TagType[];
|
||||
};
|
||||
|
||||
type Tag = {
|
||||
label: string;
|
||||
description: string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
function sortBy<T>(
|
||||
array: T[],
|
||||
getter: (item: T) => string | number | boolean,
|
||||
): T[] {
|
||||
const sortedArray = [...array];
|
||||
sortedArray.sort((a, b) =>
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
getter(a) > getter(b) ? 1 : getter(b) > getter(a) ? -1 : 0,
|
||||
);
|
||||
return sortedArray;
|
||||
}
|
||||
|
||||
const TagComp = React.forwardRef<HTMLLIElement, Tag>(
|
||||
({label, color, description}, ref) => (
|
||||
<li ref={ref} className={styles.tag} title={description}>
|
||||
<span className={styles.textLabel}>{label.toLowerCase()}</span>
|
||||
<span className={styles.colorLabel} style={{backgroundColor: color}} />
|
||||
</li>
|
||||
),
|
||||
);
|
||||
|
||||
function ShowcaseCardTag({tags}: {tags: TagType[]}) {
|
||||
const tagObjects = tags.map((tag) => ({tag, ...Tags[tag]}));
|
||||
|
||||
// Keep same order for all tags
|
||||
const tagObjectsSorted = sortBy(tagObjects, (tagObject) =>
|
||||
TagList.indexOf(tagObject.tag),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{tagObjectsSorted.map((tagObject, index) => {
|
||||
const id = `showcase_card_tag_${tagObject.tag}`;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={index}
|
||||
text={tagObject.description}
|
||||
anchorEl="#__docusaurus"
|
||||
id={id}>
|
||||
<TagComp key={index} {...tagObject} />
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function getCardImage(user: User): string {
|
||||
return (
|
||||
user.preview ??
|
||||
`https://slorber-api-screenshot.netlify.app/${encodeURIComponent(
|
||||
user.website,
|
||||
)}/showcase`
|
||||
);
|
||||
}
|
||||
|
||||
function ShowcaseCard({user}: {user: User}) {
|
||||
const image = getCardImage(user);
|
||||
return (
|
||||
<li key={user.title} className="card shadow--md">
|
||||
<div className={clsx('card__image', styles.showcaseCardImage)}>
|
||||
<img src={image} alt={user.title} />
|
||||
</div>
|
||||
<div className="card__body">
|
||||
<div className={clsx(styles.showcaseCardHeader)}>
|
||||
<Heading as="h4" className={styles.showcaseCardTitle}>
|
||||
<Link href={user.website} className={styles.showcaseCardLink}>
|
||||
{user.title}
|
||||
</Link>
|
||||
</Heading>
|
||||
{user.tags.includes('favorite') && (
|
||||
<FavoriteIcon svgClass={styles.svgIconFavorite} size="small" />
|
||||
)}
|
||||
{user.source && (
|
||||
<Link
|
||||
href={user.source}
|
||||
className={clsx(
|
||||
'button button--secondary button--sm',
|
||||
styles.showcaseCardSrcBtn,
|
||||
)}>
|
||||
<Translate id="showcase.card.sourceLink">source</Translate>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<p className={styles.showcaseCardBody}>{user.description}</p>
|
||||
</div>
|
||||
<ul className={clsx('card__footer', styles.cardFooter)}>
|
||||
<ShowcaseCardTag tags={user.tags} />
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ShowcaseCard);
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.showcaseCardImage {
|
||||
overflow: hidden;
|
||||
height: 150px;
|
||||
border-bottom: 2px solid var(--ifm-color-emphasis-200);
|
||||
}
|
||||
|
||||
.showcaseCardHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.showcaseCardTitle {
|
||||
margin-bottom: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.showcaseCardTitle a {
|
||||
text-decoration: none;
|
||||
background: linear-gradient(
|
||||
var(--ifm-color-primary),
|
||||
var(--ifm-color-primary)
|
||||
)
|
||||
0% 100% / 0% 1px no-repeat;
|
||||
transition: background-size ease-out 200ms;
|
||||
}
|
||||
|
||||
.showcaseCardTitle a:not(:focus):hover {
|
||||
background-size: 100% 1px;
|
||||
}
|
||||
|
||||
.showcaseCardTitle,
|
||||
.showcaseCardHeader .svgIconFavorite {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.showcaseCardHeader .svgIconFavorite {
|
||||
color: var(--site-color-svg-icon-favorite);
|
||||
}
|
||||
|
||||
.showcaseCardSrcBtn {
|
||||
margin-left: 6px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.showcaseCardSrcBtn:focus-visible {
|
||||
background-color: var(--ifm-color-secondary-dark);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .showcaseCardSrcBtn {
|
||||
background-color: var(--ifm-color-emphasis-200) !important;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .showcaseCardSrcBtn:hover {
|
||||
background-color: var(--ifm-color-emphasis-300) !important;
|
||||
}
|
||||
|
||||
.showcaseCardBody {
|
||||
font-size: smaller;
|
||||
line-height: 1.66;
|
||||
}
|
||||
|
||||
.cardFooter {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
font-size: 0.675rem;
|
||||
border: 1px solid var(--ifm-color-secondary-darkest);
|
||||
cursor: default;
|
||||
margin-right: 6px;
|
||||
margin-bottom: 6px !important;
|
||||
border-radius: 12px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tag .textLabel {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.tag .colorLabel {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
margin-left: 6px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {useState, useEffect, useCallback} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {useHistory, useLocation} from '@docusaurus/router';
|
||||
|
||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||
import type {Operator} from '@theme/Showcase/ShowcaseFilterToggle';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const OperatorQueryKey = 'operator';
|
||||
|
||||
type UserState = {
|
||||
scrollTopPosition: number;
|
||||
focusedElementId: string | undefined;
|
||||
};
|
||||
|
||||
function prepareUserState(): UserState | undefined {
|
||||
if (ExecutionEnvironment.canUseDOM) {
|
||||
return {
|
||||
scrollTopPosition: window.scrollY,
|
||||
focusedElementId: document.activeElement?.id,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function readOperator(search: string): Operator {
|
||||
return (new URLSearchParams(search).get(OperatorQueryKey) ??
|
||||
'OR') as Operator;
|
||||
}
|
||||
|
||||
export default function ShowcaseFilterToggle(): JSX.Element {
|
||||
const id = 'showcase_filter_toggle';
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const [operator, setOperator] = useState(false);
|
||||
useEffect(() => {
|
||||
setOperator(readOperator(location.search) === 'AND');
|
||||
}, [location]);
|
||||
const toggleOperator = useCallback(() => {
|
||||
setOperator((o) => !o);
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
searchParams.delete(OperatorQueryKey);
|
||||
if (!operator) {
|
||||
searchParams.append(OperatorQueryKey, 'AND');
|
||||
}
|
||||
history.push({
|
||||
...location,
|
||||
search: searchParams.toString(),
|
||||
state: prepareUserState(),
|
||||
});
|
||||
}, [operator, location, history]);
|
||||
|
||||
const ClearTag = () => {
|
||||
history.push({
|
||||
...location,
|
||||
search: '',
|
||||
state: prepareUserState(),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="row" style={{alignItems: 'center'}}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={id}
|
||||
className="screen-reader-only"
|
||||
aria-label="Toggle between or and and for the tags you selected"
|
||||
onChange={toggleOperator}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
toggleOperator();
|
||||
}
|
||||
}}
|
||||
checked={operator}
|
||||
/>
|
||||
<label htmlFor={id} className={clsx(styles.checkboxLabel, 'shadow--md')}>
|
||||
{/* eslint-disable @docusaurus/no-untranslated-text */}
|
||||
<span className={styles.checkboxLabelOr}>OR</span>
|
||||
<span className={styles.checkboxLabelAnd}>AND</span>
|
||||
{/* eslint-enable @docusaurus/no-untranslated-text */}
|
||||
</label>
|
||||
|
||||
{/* eslint-disable-next-line @docusaurus/no-untranslated-text */}
|
||||
<button
|
||||
className="button button--outline button--primary"
|
||||
type="button"
|
||||
onClick={() => ClearTag()}>
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.checkboxLabel {
|
||||
--height: 25px;
|
||||
--width: 80px;
|
||||
--border: 2px;
|
||||
display: flex;
|
||||
width: var(--width);
|
||||
height: var(--height);
|
||||
position: relative;
|
||||
border-radius: var(--height);
|
||||
border: var(--border) solid var(--ifm-color-primary-darkest);
|
||||
cursor: pointer;
|
||||
justify-content: space-around;
|
||||
opacity: 0.75;
|
||||
transition: opacity var(--ifm-transition-fast)
|
||||
var(--ifm-transition-timing-default);
|
||||
box-shadow: var(--ifm-global-shadow-md);
|
||||
}
|
||||
|
||||
.checkboxLabel:hover {
|
||||
opacity: 1;
|
||||
box-shadow: var(--ifm-global-shadow-md),
|
||||
0 0 2px 1px var(--ifm-color-primary-dark);
|
||||
}
|
||||
|
||||
.checkboxLabel::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
inset: 0;
|
||||
width: calc(var(--width) / 2);
|
||||
height: 100%;
|
||||
border-radius: var(--height);
|
||||
background-color: var(--ifm-color-primary-darkest);
|
||||
transition: transform var(--ifm-transition-fast)
|
||||
var(--ifm-transition-timing-default);
|
||||
transform: translateX(calc(var(--width) / 2 - var(--border)));
|
||||
}
|
||||
|
||||
input:focus-visible ~ .checkboxLabel::after {
|
||||
outline: 2px solid currentColor;
|
||||
}
|
||||
|
||||
.checkboxLabel > * {
|
||||
font-size: 0.8rem;
|
||||
color: inherit;
|
||||
transition: opacity 150ms ease-in 50ms;
|
||||
}
|
||||
|
||||
input:checked ~ .checkboxLabel::after {
|
||||
transform: translateX(calc(-1 * var(--border)));
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {
|
||||
useCallback,
|
||||
useState,
|
||||
useEffect,
|
||||
type ComponentProps,
|
||||
type ReactNode,
|
||||
type ReactElement,
|
||||
} from 'react';
|
||||
import {useHistory, useLocation} from '@docusaurus/router';
|
||||
|
||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
type UserState = {
|
||||
scrollTopPosition: number;
|
||||
focusedElementId: string | undefined;
|
||||
};
|
||||
|
||||
function prepareUserState(): UserState | undefined {
|
||||
if (ExecutionEnvironment.canUseDOM) {
|
||||
return {
|
||||
scrollTopPosition: window.scrollY,
|
||||
focusedElementId: document.activeElement?.id,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function toggleListItem<T>(list: T[], item: T): T[] {
|
||||
const itemIndex = list.indexOf(item);
|
||||
if (itemIndex === -1) {
|
||||
return list.concat(item);
|
||||
}
|
||||
const newList = [...list];
|
||||
newList.splice(itemIndex, 1);
|
||||
return newList;
|
||||
}
|
||||
|
||||
type TagType =
|
||||
| 'favorite'
|
||||
| 'opensource'
|
||||
| 'product'
|
||||
| 'design'
|
||||
| 'i18n'
|
||||
| 'versioning'
|
||||
| 'large'
|
||||
| 'meta'
|
||||
| 'personal'
|
||||
| 'rtl';
|
||||
interface Props extends ComponentProps<'input'> {
|
||||
icon: ReactElement<ComponentProps<'svg'>>;
|
||||
label: ReactNode;
|
||||
tag: TagType;
|
||||
}
|
||||
|
||||
const TagQueryStringKey = 'tags';
|
||||
|
||||
function readSearchTags(search: string): TagType[] {
|
||||
return new URLSearchParams(search).getAll(TagQueryStringKey) as TagType[];
|
||||
}
|
||||
|
||||
function replaceSearchTags(search: string, newTags: TagType[]) {
|
||||
const searchParams = new URLSearchParams(search);
|
||||
searchParams.delete(TagQueryStringKey);
|
||||
newTags.forEach((tag) => searchParams.append(TagQueryStringKey, tag));
|
||||
return searchParams.toString();
|
||||
}
|
||||
|
||||
function ShowcaseTagSelect(
|
||||
{id, icon, label, tag, ...rest}: Props,
|
||||
ref: React.ForwardedRef<HTMLLabelElement>,
|
||||
) {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const [selected, setSelected] = useState(false);
|
||||
useEffect(() => {
|
||||
const tags = readSearchTags(location.search);
|
||||
setSelected(tags.includes(tag));
|
||||
}, [tag, location]);
|
||||
const toggleTag = useCallback(() => {
|
||||
const tags = readSearchTags(location.search);
|
||||
const newTags = toggleListItem(tags, tag);
|
||||
const newSearch = replaceSearchTags(location.search, newTags);
|
||||
history.push({
|
||||
...location,
|
||||
search: newSearch,
|
||||
state: prepareUserState(),
|
||||
});
|
||||
}, [tag, location, history]);
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={id}
|
||||
className="screen-reader-only"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
toggleTag();
|
||||
}
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
if (e.relatedTarget) {
|
||||
e.target.nextElementSibling?.dispatchEvent(
|
||||
new KeyboardEvent('focus'),
|
||||
);
|
||||
}
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.nextElementSibling?.dispatchEvent(new KeyboardEvent('blur'));
|
||||
}}
|
||||
onChange={toggleTag}
|
||||
checked={selected}
|
||||
{...rest}
|
||||
/>
|
||||
<label ref={ref} htmlFor={id} className={styles.checkboxLabel}>
|
||||
{label}
|
||||
{icon}
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.forwardRef(ShowcaseTagSelect);
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.checkboxLabel:hover {
|
||||
opacity: 1;
|
||||
box-shadow: 0 0 2px 1px var(--ifm-color-secondary-darkest);
|
||||
}
|
||||
|
||||
input[type='checkbox'] + .checkboxLabel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
line-height: 1.5;
|
||||
border-radius: 4px;
|
||||
padding: 0.275rem 0.8rem;
|
||||
opacity: 0.85;
|
||||
transition: opacity 200ms ease-out;
|
||||
border: 2px solid var(--ifm-color-secondary-darkest);
|
||||
}
|
||||
|
||||
input:focus-visible + .checkboxLabel {
|
||||
outline: 2px solid currentColor;
|
||||
}
|
||||
|
||||
input:checked + .checkboxLabel {
|
||||
opacity: 0.9;
|
||||
background-color: var(--site-color-checkbox-checked-bg);
|
||||
border: 2px solid var(--ifm-color-primary-darkest);
|
||||
}
|
||||
|
||||
input:checked + .checkboxLabel:hover {
|
||||
opacity: 0.75;
|
||||
box-shadow: 0 0 2px 1px var(--ifm-color-primary-dark);
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {useEffect, useState, useRef} from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {usePopper} from 'react-popper';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
interface Props {
|
||||
anchorEl?: HTMLElement | string;
|
||||
id: string;
|
||||
text: string;
|
||||
children: React.ReactElement;
|
||||
}
|
||||
|
||||
export default function Tooltip({
|
||||
children,
|
||||
id,
|
||||
anchorEl,
|
||||
text,
|
||||
}: Props): JSX.Element {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLElement | null>(
|
||||
null,
|
||||
);
|
||||
const [popperElement, setPopperElement] = useState<HTMLElement | null>(null);
|
||||
const [arrowElement, setArrowElement] = useState<HTMLElement | null>(null);
|
||||
const [container, setContainer] = useState<Element | null>(null);
|
||||
const {styles: popperStyles, attributes} = usePopper(
|
||||
referenceElement,
|
||||
popperElement,
|
||||
{
|
||||
modifiers: [
|
||||
{
|
||||
name: 'arrow',
|
||||
options: {
|
||||
element: arrowElement,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [0, 8],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const timeout = useRef<number | null>(null);
|
||||
const tooltipId = `${id}_tooltip`;
|
||||
|
||||
useEffect(() => {
|
||||
if (anchorEl) {
|
||||
if (typeof anchorEl === 'string') {
|
||||
setContainer(document.querySelector(anchorEl));
|
||||
} else {
|
||||
setContainer(anchorEl);
|
||||
}
|
||||
} else {
|
||||
setContainer(document.body);
|
||||
}
|
||||
}, [container, anchorEl]);
|
||||
|
||||
useEffect(() => {
|
||||
const showEvents = ['mouseenter', 'focus'];
|
||||
const hideEvents = ['mouseleave', 'blur'];
|
||||
|
||||
const handleOpen = () => {
|
||||
// There is no point in displaying an empty tooltip.
|
||||
if (text === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the title ahead of time to avoid displaying
|
||||
// two tooltips at the same time (native + this one).
|
||||
referenceElement?.removeAttribute('title');
|
||||
|
||||
timeout.current = window.setTimeout(() => {
|
||||
setOpen(true);
|
||||
}, 400);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
clearInterval(timeout.current!);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
if (referenceElement) {
|
||||
showEvents.forEach((event) => {
|
||||
referenceElement.addEventListener(event, handleOpen);
|
||||
});
|
||||
|
||||
hideEvents.forEach((event) => {
|
||||
referenceElement.addEventListener(event, handleClose);
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (referenceElement) {
|
||||
showEvents.forEach((event) => {
|
||||
referenceElement.removeEventListener(event, handleOpen);
|
||||
});
|
||||
|
||||
hideEvents.forEach((event) => {
|
||||
referenceElement.removeEventListener(event, handleClose);
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [referenceElement, text]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{React.cloneElement(children, {
|
||||
ref: setReferenceElement,
|
||||
'aria-describedby': open ? tooltipId : undefined,
|
||||
})}
|
||||
{container
|
||||
? ReactDOM.createPortal(
|
||||
open && (
|
||||
<div
|
||||
id={tooltipId}
|
||||
role="tooltip"
|
||||
ref={setPopperElement}
|
||||
className={styles.tooltip}
|
||||
style={popperStyles.popper}
|
||||
{...attributes.popper}>
|
||||
{text}
|
||||
<span
|
||||
ref={setArrowElement}
|
||||
className={styles.tooltipArrow}
|
||||
style={popperStyles.arrow}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
container,
|
||||
)
|
||||
: container}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.tooltip {
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
color: var(--site-color-tooltip);
|
||||
background: var(--site-color-tooltip-background);
|
||||
font-size: 0.8rem;
|
||||
z-index: 500;
|
||||
line-height: 1.4;
|
||||
font-weight: 500;
|
||||
max-width: 300px;
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.tooltipArrow {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tooltipArrow,
|
||||
.tooltipArrow::before {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.tooltipArrow::before {
|
||||
visibility: visible;
|
||||
content: '';
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.tooltip[data-popper-placement^='top'] > .tooltipArrow {
|
||||
bottom: -4px;
|
||||
}
|
||||
|
||||
.tooltip[data-popper-placement^='bottom'] > .tooltipArrow {
|
||||
top: -4px;
|
||||
}
|
||||
|
|
@ -0,0 +1,468 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {useEffect, useMemo, useState} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Link from '@docusaurus/Link';
|
||||
import {useHistory, useLocation} from 'react-router-dom';
|
||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||
import Translate, {translate} from '@docusaurus/Translate';
|
||||
import {usePluralForm} from '@docusaurus/theme-common';
|
||||
import type {User, Props} from '@theme/Showcase';
|
||||
import Layout from '@theme/Layout';
|
||||
import Heading from '@theme/Heading';
|
||||
import FavoriteIcon from '@theme/Showcase/FavoriteIcon';
|
||||
import ShowcaseCard from '@theme/Showcase/ShowcaseCard';
|
||||
import ShowcaseTooltip from '@theme/Showcase/ShowcaseTooltip';
|
||||
import ShowcaseTagSelect from '@theme/Showcase/ShowcaseTagSelect';
|
||||
import ShowcaseFilterToggle from '@theme/Showcase/ShowcaseFilterToggle';
|
||||
import type {Operator} from '@theme/Showcase/ShowcaseFilterToggle';
|
||||
import type {TagType} from '@docusaurus/plugin-content-showcase';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
type Users = User[];
|
||||
|
||||
const TITLE = translate({message: 'Docusaurus Site Showcase'});
|
||||
const DESCRIPTION = translate({
|
||||
message: 'List of websites people are building with Docusaurus',
|
||||
});
|
||||
const SUBMIT_URL = 'https://github.com/facebook/docusaurus/discussions/7826';
|
||||
|
||||
const OperatorQueryKey = 'operator';
|
||||
|
||||
function readOperator(search: string): Operator {
|
||||
return (new URLSearchParams(search).get(OperatorQueryKey) ??
|
||||
'OR') as Operator;
|
||||
}
|
||||
type UserState = {
|
||||
scrollTopPosition: number;
|
||||
focusedElementId: string | undefined;
|
||||
};
|
||||
|
||||
type Tag = {
|
||||
label: string;
|
||||
description: string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
const Tags: {[type in TagType]: Tag} = {
|
||||
favorite: {
|
||||
label: translate({message: 'Favorite'}),
|
||||
description: translate({
|
||||
message:
|
||||
'Our favorite Docusaurus sites that you must absolutely check out!',
|
||||
id: 'showcase.tag.favorite.description',
|
||||
}),
|
||||
color: '#e9669e',
|
||||
},
|
||||
|
||||
opensource: {
|
||||
label: translate({message: 'Open-Source'}),
|
||||
description: translate({
|
||||
message: 'Open-Source Docusaurus sites can be useful for inspiration!',
|
||||
id: 'showcase.tag.opensource.description',
|
||||
}),
|
||||
color: '#39ca30',
|
||||
},
|
||||
|
||||
product: {
|
||||
label: translate({message: 'Product'}),
|
||||
description: translate({
|
||||
message: 'Docusaurus sites associated to a commercial product!',
|
||||
id: 'showcase.tag.product.description',
|
||||
}),
|
||||
color: '#dfd545',
|
||||
},
|
||||
|
||||
design: {
|
||||
label: translate({message: 'Design'}),
|
||||
description: translate({
|
||||
message:
|
||||
'Beautiful Docusaurus sites, polished and standing out from the initial template!',
|
||||
id: 'showcase.tag.design.description',
|
||||
}),
|
||||
color: '#a44fb7',
|
||||
},
|
||||
|
||||
i18n: {
|
||||
label: translate({message: 'I18n'}),
|
||||
description: translate({
|
||||
message:
|
||||
'Translated Docusaurus sites using the internationalization support with more than 1 locale.',
|
||||
id: 'showcase.tag.i18n.description',
|
||||
}),
|
||||
color: '#127f82',
|
||||
},
|
||||
|
||||
versioning: {
|
||||
label: translate({message: 'Versioning'}),
|
||||
description: translate({
|
||||
message:
|
||||
'Docusaurus sites using the versioning feature of the docs plugin to manage multiple versions.',
|
||||
id: 'showcase.tag.versioning.description',
|
||||
}),
|
||||
color: '#fe6829',
|
||||
},
|
||||
|
||||
large: {
|
||||
label: translate({message: 'Large'}),
|
||||
description: translate({
|
||||
message:
|
||||
'Very large Docusaurus sites, including many more pages than the average!',
|
||||
id: 'showcase.tag.large.description',
|
||||
}),
|
||||
color: '#8c2f00',
|
||||
},
|
||||
|
||||
meta: {
|
||||
label: translate({message: 'Meta'}),
|
||||
description: translate({
|
||||
message: 'Docusaurus sites of Meta (formerly Facebook) projects',
|
||||
id: 'showcase.tag.meta.description',
|
||||
}),
|
||||
color: '#4267b2', // Facebook blue
|
||||
},
|
||||
|
||||
personal: {
|
||||
label: translate({message: 'Personal'}),
|
||||
description: translate({
|
||||
message:
|
||||
'Personal websites, blogs and digital gardens built with Docusaurus',
|
||||
id: 'showcase.tag.personal.description',
|
||||
}),
|
||||
color: '#14cfc3',
|
||||
},
|
||||
|
||||
rtl: {
|
||||
label: translate({message: 'RTL Direction'}),
|
||||
description: translate({
|
||||
message:
|
||||
'Docusaurus sites using the right-to-left reading direction support.',
|
||||
id: 'showcase.tag.rtl.description',
|
||||
}),
|
||||
color: '#ffcfc3',
|
||||
},
|
||||
};
|
||||
|
||||
const TagList = Object.keys(Tags) as TagType[];
|
||||
|
||||
function sortBy<T>(
|
||||
array: T[],
|
||||
getter: (item: T) => string | number | boolean,
|
||||
): T[] {
|
||||
const sortedArray = [...array];
|
||||
sortedArray.sort((a, b) =>
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
getter(a) > getter(b) ? 1 : getter(b) > getter(a) ? -1 : 0,
|
||||
);
|
||||
return sortedArray;
|
||||
}
|
||||
|
||||
function sortUsers(users: Users): Users {
|
||||
// Sort by site name
|
||||
let result = sortBy(users, (user) => user.title.toLowerCase());
|
||||
// Sort by favorite tag, favorites first
|
||||
result = sortBy(result, (user) => (user.tags.includes('favorite') ? -1 : 1));
|
||||
return result;
|
||||
}
|
||||
|
||||
function ShowcaseHeader() {
|
||||
return (
|
||||
<section className="margin-top--lg margin-bottom--lg text--center">
|
||||
<Heading as="h1">{TITLE}</Heading>
|
||||
<p>{DESCRIPTION}</p>
|
||||
<Link className="button button--primary" to={SUBMIT_URL}>
|
||||
<Translate id="showcase.header.button">
|
||||
🙏 Please add your site
|
||||
</Translate>
|
||||
</Link>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function prepareUserState(): UserState | undefined {
|
||||
if (ExecutionEnvironment.canUseDOM) {
|
||||
return {
|
||||
scrollTopPosition: window.scrollY,
|
||||
focusedElementId: document.activeElement?.id,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function restoreUserState(userState: UserState | null) {
|
||||
const {scrollTopPosition, focusedElementId} = userState ?? {
|
||||
scrollTopPosition: 0,
|
||||
focusedElementId: undefined,
|
||||
};
|
||||
// @ts-expect-error: if focusedElementId is undefined it returns null
|
||||
document.getElementById(focusedElementId)?.focus();
|
||||
window.scrollTo({top: scrollTopPosition});
|
||||
}
|
||||
|
||||
function filterUsers(
|
||||
users: Users,
|
||||
selectedTags: TagType[],
|
||||
operator: Operator,
|
||||
searchName: string | null,
|
||||
) {
|
||||
if (searchName) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
users = users.filter((user) =>
|
||||
user.title.toLowerCase().includes(searchName.toLowerCase()),
|
||||
);
|
||||
}
|
||||
if (selectedTags.length === 0) {
|
||||
return users;
|
||||
}
|
||||
return users.filter((user) => {
|
||||
if (user.tags.length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (operator === 'AND') {
|
||||
return selectedTags.every((tag) => user.tags.includes(tag));
|
||||
}
|
||||
return selectedTags.some((tag) => user.tags.includes(tag));
|
||||
});
|
||||
}
|
||||
|
||||
const SearchNameQueryKey = 'name';
|
||||
|
||||
function readSearchName(search: string) {
|
||||
return new URLSearchParams(search).get(SearchNameQueryKey);
|
||||
}
|
||||
|
||||
const TagQueryStringKey = 'tags';
|
||||
|
||||
function readSearchTags(search: string): TagType[] {
|
||||
return new URLSearchParams(search).getAll(TagQueryStringKey) as TagType[];
|
||||
}
|
||||
|
||||
function useFilteredUsers(users: Users) {
|
||||
const location = useLocation<UserState>();
|
||||
const [operator, setOperator] = useState<Operator>('OR');
|
||||
// On SSR / first mount (hydration) no tag is selected
|
||||
const [selectedTags, setSelectedTags] = useState<TagType[]>([]);
|
||||
const [searchName, setSearchName] = useState<string | null>(null);
|
||||
// Sync tags from QS to state (delayed on purpose to avoid SSR/Client
|
||||
// hydration mismatch)
|
||||
useEffect(() => {
|
||||
setSelectedTags(readSearchTags(location.search));
|
||||
setOperator(readOperator(location.search));
|
||||
setSearchName(readSearchName(location.search));
|
||||
restoreUserState(location.state);
|
||||
}, [location]);
|
||||
|
||||
return useMemo(
|
||||
() => filterUsers(sortUsers(users), selectedTags, operator, searchName),
|
||||
[selectedTags, operator, searchName, users],
|
||||
);
|
||||
}
|
||||
|
||||
function useSiteCountPlural() {
|
||||
const {selectMessage} = usePluralForm();
|
||||
return (sitesCount: number) =>
|
||||
selectMessage(
|
||||
sitesCount,
|
||||
translate(
|
||||
{
|
||||
id: 'showcase.filters.resultCount',
|
||||
description:
|
||||
'Pluralized label for the number of sites found on the showcase. Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',
|
||||
message: '1 site|{sitesCount} sites',
|
||||
},
|
||||
{sitesCount},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function ShowcaseFilters({users}: {users: Users}) {
|
||||
const filteredUsers = useFilteredUsers(users);
|
||||
const siteCountPlural = useSiteCountPlural();
|
||||
return (
|
||||
<section className="container margin-top--l margin-bottom--lg">
|
||||
<div className={clsx('margin-bottom--sm', styles.filterCheckbox)}>
|
||||
<div>
|
||||
<Heading as="h2">
|
||||
<Translate id="showcase.filters.title">Filters</Translate>
|
||||
</Heading>
|
||||
<span>{siteCountPlural(filteredUsers.length)}</span>
|
||||
</div>
|
||||
<ShowcaseFilterToggle />
|
||||
</div>
|
||||
<ul className={clsx('clean-list', styles.checkboxList)}>
|
||||
{TagList.map((tag, i) => {
|
||||
const {label, description, color} = Tags[tag];
|
||||
const id = `showcase_checkbox_id_${tag}`;
|
||||
|
||||
return (
|
||||
<li key={i} className={styles.checkboxListItem}>
|
||||
<ShowcaseTooltip
|
||||
id={id}
|
||||
text={description}
|
||||
anchorEl="#__docusaurus">
|
||||
<ShowcaseTagSelect
|
||||
tag={tag}
|
||||
id={id}
|
||||
label={label}
|
||||
icon={
|
||||
tag === 'favorite' ? (
|
||||
<FavoriteIcon svgClass={styles.svgIconFavoriteXs} />
|
||||
) : (
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
width: 10,
|
||||
height: 10,
|
||||
borderRadius: '50%',
|
||||
marginLeft: 8,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</ShowcaseTooltip>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function SearchBar() {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const [value, setValue] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
setValue(readSearchName(location.search));
|
||||
}, [location]);
|
||||
return (
|
||||
<div className={styles.searchContainer}>
|
||||
<input
|
||||
id="searchbar"
|
||||
placeholder={translate({
|
||||
message: 'Search for site name...',
|
||||
id: 'showcase.searchBar.placeholder',
|
||||
})}
|
||||
value={value ?? undefined}
|
||||
onInput={(e) => {
|
||||
setValue(e.currentTarget.value);
|
||||
const newSearch = new URLSearchParams(location.search);
|
||||
newSearch.delete(SearchNameQueryKey);
|
||||
if (e.currentTarget.value) {
|
||||
newSearch.set(SearchNameQueryKey, e.currentTarget.value);
|
||||
}
|
||||
history.push({
|
||||
...location,
|
||||
search: newSearch.toString(),
|
||||
state: prepareUserState(),
|
||||
});
|
||||
setTimeout(() => {
|
||||
document.getElementById('searchbar')?.focus();
|
||||
}, 0);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ShowcaseCards({users}: {users: Users}) {
|
||||
const filteredUsers = useFilteredUsers(users);
|
||||
|
||||
if (filteredUsers.length === 0) {
|
||||
return (
|
||||
<section className="margin-top--lg margin-bottom--xl">
|
||||
<div className="container padding-vert--md text--center">
|
||||
<Heading as="h2">
|
||||
<Translate id="showcase.usersList.noResult">No result</Translate>
|
||||
</Heading>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const favoriteUsers = users.filter((user) => user.tags.includes('favorite'));
|
||||
const otherUsers = users.filter((user) => !user.tags.includes('favorite'));
|
||||
|
||||
return (
|
||||
<section className="margin-top--lg margin-bottom--xl">
|
||||
{filteredUsers.length === sortUsers(users).length ? (
|
||||
<>
|
||||
<div className={styles.showcaseFavorite}>
|
||||
<div className="container">
|
||||
<div
|
||||
className={clsx(
|
||||
'margin-bottom--md',
|
||||
styles.showcaseFavoriteHeader,
|
||||
)}>
|
||||
<Heading as="h2">
|
||||
<Translate id="showcase.favoritesList.title">
|
||||
Our favorites
|
||||
</Translate>
|
||||
</Heading>
|
||||
<FavoriteIcon svgClass={styles.svgIconFavorite} />
|
||||
</div>
|
||||
<ul
|
||||
className={clsx(
|
||||
'container',
|
||||
'clean-list',
|
||||
styles.showcaseList,
|
||||
)}>
|
||||
{favoriteUsers.map((user) => (
|
||||
<ShowcaseCard key={user.title} user={user} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="container margin-top--lg">
|
||||
<Heading as="h2" className={styles.showcaseHeader}>
|
||||
<Translate id="showcase.usersList.allUsers">All sites</Translate>
|
||||
</Heading>
|
||||
<ul className={clsx('clean-list', styles.showcaseList)}>
|
||||
{otherUsers.map((user) => (
|
||||
<ShowcaseCard key={user.title} user={user} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="container">
|
||||
<div
|
||||
className={clsx('margin-bottom--md', styles.showcaseFavoriteHeader)}
|
||||
/>
|
||||
<ul className={clsx('clean-list', styles.showcaseList)}>
|
||||
{filteredUsers.map((user) => (
|
||||
<ShowcaseCard key={user.title} user={user} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Showcase(props: Props): JSX.Element {
|
||||
const users = props.content;
|
||||
|
||||
return (
|
||||
<Layout title="Showcase">
|
||||
<div>{JSON.stringify(props)}</div>
|
||||
<main className="margin-vert--lg">
|
||||
<ShowcaseHeader />
|
||||
<ShowcaseFilters users={users} />
|
||||
<div
|
||||
style={{display: 'flex', marginLeft: 'auto'}}
|
||||
className="container">
|
||||
<SearchBar />
|
||||
</div>
|
||||
<ShowcaseCards users={users} />
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.filterCheckbox {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.filterCheckbox,
|
||||
.checkboxList {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filterCheckbox > div:first-child {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filterCheckbox > div > * {
|
||||
margin-bottom: 0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.checkboxList {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.checkboxListItem {
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
height: 32px;
|
||||
font-size: 0.8rem;
|
||||
margin-top: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.checkboxListItem:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.searchContainer {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.searchContainer input {
|
||||
height: 30px;
|
||||
border-radius: 15px;
|
||||
padding: 10px;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
.showcaseList {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.showcaseFavorite {
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
background-color: var(--site-color-favorite-background);
|
||||
}
|
||||
|
||||
.showcaseFavoriteHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.showcaseFavoriteHeader > h2 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.showcaseFavoriteHeader > svg {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.svgIconFavoriteXs,
|
||||
.svgIconFavorite {
|
||||
color: var(--site-color-svg-icon-favorite);
|
||||
}
|
||||
|
||||
.svgIconFavoriteXs {
|
||||
margin-left: 0.625rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.svgIconFavorite {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* 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 type {Props} from '@theme/ShowcaseDetails';
|
||||
import Layout from '@theme/Layout';
|
||||
|
||||
export default function Showcase(props: Props): JSX.Element {
|
||||
const {content: MDXPageContent} = props;
|
||||
const {title, description} = MDXPageContent;
|
||||
|
||||
return (
|
||||
<Layout title="Showcase Details">
|
||||
<div>Title {JSON.stringify(title)}</div>
|
||||
<div>Description {JSON.stringify(description)}</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/theme-common",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Common code for Docusaurus themes.",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
|
|
@ -30,13 +30,13 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/mdx-loader": "3.2.0",
|
||||
"@docusaurus/module-type-aliases": "3.2.0",
|
||||
"@docusaurus/plugin-content-blog": "3.2.0",
|
||||
"@docusaurus/plugin-content-docs": "3.2.0",
|
||||
"@docusaurus/plugin-content-pages": "3.2.0",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/utils-common": "3.2.0",
|
||||
"@docusaurus/mdx-loader": "3.0.0",
|
||||
"@docusaurus/module-type-aliases": "3.0.0",
|
||||
"@docusaurus/plugin-content-blog": "3.0.0",
|
||||
"@docusaurus/plugin-content-docs": "3.0.0",
|
||||
"@docusaurus/plugin-content-pages": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"@docusaurus/utils-common": "3.0.0",
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*",
|
||||
"@types/react-router-config": "*",
|
||||
|
|
@ -47,8 +47,8 @@
|
|||
"utility-types": "^3.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"schema-dts": "^1.1.2"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/theme-live-codeblock",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Docusaurus live code block component.",
|
||||
"main": "lib/index.js",
|
||||
"types": "src/theme-live-codeblock.d.ts",
|
||||
|
|
@ -23,10 +23,10 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/theme-common": "3.2.0",
|
||||
"@docusaurus/theme-translations": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/theme-common": "3.0.0",
|
||||
"@docusaurus/theme-translations": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"@philpl/buble": "^0.19.7",
|
||||
"clsx": "^2.0.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
"tslib": "^2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@types/buble": "^0.20.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/theme-mermaid",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Mermaid components for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"types": "src/theme-mermaid.d.ts",
|
||||
|
|
@ -33,11 +33,11 @@
|
|||
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/module-type-aliases": "3.2.0",
|
||||
"@docusaurus/theme-common": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/module-type-aliases": "3.0.0",
|
||||
"@docusaurus/theme-common": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"mermaid": "^10.4.0",
|
||||
"tslib": "^2.6.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/theme-search-algolia",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Algolia search component for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"sideEffects": [
|
||||
|
|
@ -34,13 +34,13 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@docsearch/react": "^3.5.2",
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/logger": "3.2.0",
|
||||
"@docusaurus/plugin-content-docs": "3.2.0",
|
||||
"@docusaurus/theme-common": "3.2.0",
|
||||
"@docusaurus/theme-translations": "3.2.0",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/logger": "3.0.0",
|
||||
"@docusaurus/plugin-content-docs": "3.0.0",
|
||||
"@docusaurus/theme-common": "3.0.0",
|
||||
"@docusaurus/theme-translations": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"algoliasearch": "^4.18.0",
|
||||
"algoliasearch-helper": "^3.13.3",
|
||||
"clsx": "^2.0.0",
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
"utility-types": "^3.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.2.0"
|
||||
"@docusaurus/module-type-aliases": "3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/theme-translations",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Docusaurus theme translations.",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
|
@ -23,8 +23,8 @@
|
|||
"tslib": "^2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/core": "3.2.0",
|
||||
"@docusaurus/logger": "3.2.0",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/logger": "3.0.0",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/tsconfig",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Docusaurus base TypeScript configuration.",
|
||||
"main": "tsconfig.json",
|
||||
"publishConfig": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/types",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Common types for Docusaurus packages.",
|
||||
"types": "./src/index.d.ts",
|
||||
"publishConfig": {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ export type GlobalData = {[pluginName: string]: {[pluginId: string]: unknown}};
|
|||
|
||||
export type LoadContext = {
|
||||
siteDir: string;
|
||||
siteVersion: string | undefined;
|
||||
generatedFilesDir: string;
|
||||
siteConfig: DocusaurusConfig;
|
||||
siteConfigPath: string;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {CodeTranslations, TranslationFile} from './i18n';
|
||||
import type {TranslationFile} from './i18n';
|
||||
import type {RuleSetRule, Configuration as WebpackConfiguration} from 'webpack';
|
||||
import type {CustomizeRuleString} from 'webpack-merge/dist/types';
|
||||
import type {CommanderStatic} from 'commander';
|
||||
|
|
@ -183,9 +183,6 @@ export type InitializedPlugin = Plugin & {
|
|||
|
||||
export type LoadedPlugin = InitializedPlugin & {
|
||||
readonly content: unknown;
|
||||
readonly globalData: unknown;
|
||||
readonly routes: RouteConfig[];
|
||||
readonly defaultCodeTranslations: CodeTranslations;
|
||||
};
|
||||
|
||||
export type PluginModule<Content = unknown> = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/utils-common",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Common (Node/Browser) utility functions for Docusaurus packages.",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@
|
|||
*/
|
||||
|
||||
import applyTrailingSlash, {
|
||||
addTrailingSlash,
|
||||
type ApplyTrailingSlashParams,
|
||||
addLeadingSlash,
|
||||
removeTrailingSlash,
|
||||
} from '../applyTrailingSlash';
|
||||
|
||||
function params(
|
||||
|
|
@ -179,30 +176,3 @@ describe('applyTrailingSlash', () => {
|
|||
).toBe('https://xyz.com/abc/?search#anchor');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addTrailingSlash', () => {
|
||||
it('is no-op for path with trailing slash', () => {
|
||||
expect(addTrailingSlash('/abcd/')).toBe('/abcd/');
|
||||
});
|
||||
it('adds / for path without trailing slash', () => {
|
||||
expect(addTrailingSlash('/abcd')).toBe('/abcd/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addLeadingSlash', () => {
|
||||
it('is no-op for path with leading slash', () => {
|
||||
expect(addLeadingSlash('/abc')).toBe('/abc');
|
||||
});
|
||||
it('adds / for path without leading slash', () => {
|
||||
expect(addLeadingSlash('abc')).toBe('/abc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeTrailingSlash', () => {
|
||||
it('is no-op for path without trailing slash', () => {
|
||||
expect(removeTrailingSlash('/abcd')).toBe('/abcd');
|
||||
});
|
||||
it('removes / for path with trailing slash', () => {
|
||||
expect(removeTrailingSlash('/abcd/')).toBe('/abcd');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,55 +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 {addPrefix, addSuffix, removePrefix, removeSuffix} from '../stringUtils';
|
||||
|
||||
describe('removePrefix', () => {
|
||||
it("is no-op when prefix doesn't exist", () => {
|
||||
expect(removePrefix('abcdef', 'ijk')).toBe('abcdef');
|
||||
expect(removePrefix('abcdef', 'def')).toBe('abcdef');
|
||||
expect(removePrefix('abcdef', '')).toBe('abcdef');
|
||||
});
|
||||
it('removes prefix', () => {
|
||||
expect(removePrefix('prefix', 'pre')).toBe('fix');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeSuffix', () => {
|
||||
it("is no-op when suffix doesn't exist", () => {
|
||||
expect(removeSuffix('abcdef', 'ijk')).toBe('abcdef');
|
||||
expect(removeSuffix('abcdef', 'abc')).toBe('abcdef');
|
||||
expect(removeSuffix('abcdef', '')).toBe('abcdef');
|
||||
});
|
||||
it('removes suffix', () => {
|
||||
expect(removeSuffix('abcdef', 'ef')).toBe('abcd');
|
||||
});
|
||||
it('removes empty suffix', () => {
|
||||
expect(removeSuffix('abcdef', '')).toBe('abcdef');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addPrefix', () => {
|
||||
it('is no-op when prefix already exists', () => {
|
||||
expect(addPrefix('abcdef', 'abc')).toBe('abcdef');
|
||||
expect(addPrefix('abc', '')).toBe('abc');
|
||||
expect(addPrefix('', '')).toBe('');
|
||||
});
|
||||
it('adds prefix', () => {
|
||||
expect(addPrefix('def', 'abc')).toBe('abcdef');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addSuffix', () => {
|
||||
it('is no-op when suffix already exists', () => {
|
||||
expect(addSuffix('abcdef', 'def')).toBe('abcdef');
|
||||
expect(addSuffix('abc', '')).toBe('abc');
|
||||
expect(addSuffix('', '')).toBe('');
|
||||
});
|
||||
it('adds suffix', () => {
|
||||
expect(addSuffix('abc', 'def')).toBe('abcdef');
|
||||
});
|
||||
});
|
||||
|
|
@ -5,7 +5,6 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {addPrefix, removeSuffix} from './stringUtils';
|
||||
import type {DocusaurusConfig} from '@docusaurus/types';
|
||||
|
||||
export type ApplyTrailingSlashParams = Pick<
|
||||
|
|
@ -13,10 +12,6 @@ export type ApplyTrailingSlashParams = Pick<
|
|||
'trailingSlash' | 'baseUrl'
|
||||
>;
|
||||
|
||||
export function addTrailingSlash(str: string): string {
|
||||
return str.endsWith('/') ? str : `${str}/`;
|
||||
}
|
||||
|
||||
// Trailing slash handling depends in some site configuration options
|
||||
export default function applyTrailingSlash(
|
||||
path: string,
|
||||
|
|
@ -29,6 +24,13 @@ export default function applyTrailingSlash(
|
|||
return path;
|
||||
}
|
||||
|
||||
// TODO deduplicate: also present in @docusaurus/utils
|
||||
function addTrailingSlash(str: string): string {
|
||||
return str.endsWith('/') ? str : `${str}/`;
|
||||
}
|
||||
function removeTrailingSlash(str: string): string {
|
||||
return str.endsWith('/') ? str.slice(0, -1) : str;
|
||||
}
|
||||
function handleTrailingSlash(str: string, trailing: boolean): string {
|
||||
return trailing ? addTrailingSlash(str) : removeTrailingSlash(str);
|
||||
}
|
||||
|
|
@ -53,13 +55,3 @@ export default function applyTrailingSlash(
|
|||
|
||||
return path.replace(pathname, newPathname);
|
||||
}
|
||||
|
||||
/** Appends a leading slash to `str`, if one doesn't exist. */
|
||||
export function addLeadingSlash(str: string): string {
|
||||
return addPrefix(str, '/');
|
||||
}
|
||||
|
||||
/** Removes the trailing slash from `str`. */
|
||||
export function removeTrailingSlash(str: string): string {
|
||||
return removeSuffix(str, '/');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,6 @@ export const blogPostContainerID = '__blog-post-container';
|
|||
|
||||
export {
|
||||
default as applyTrailingSlash,
|
||||
addTrailingSlash,
|
||||
addLeadingSlash,
|
||||
removeTrailingSlash,
|
||||
type ApplyTrailingSlashParams,
|
||||
} from './applyTrailingSlash';
|
||||
export {addPrefix, removeSuffix, addSuffix, removePrefix} from './stringUtils';
|
||||
export {getErrorCausalChain} from './errorUtils';
|
||||
|
|
|
|||
|
|
@ -1,30 +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.
|
||||
*/
|
||||
|
||||
/** Adds a given string prefix to `str`. */
|
||||
export function addPrefix(str: string, prefix: string): string {
|
||||
return str.startsWith(prefix) ? str : `${prefix}${str}`;
|
||||
}
|
||||
|
||||
/** Removes a given string suffix from `str`. */
|
||||
export function removeSuffix(str: string, suffix: string): string {
|
||||
if (suffix === '') {
|
||||
// str.slice(0, 0) is ""
|
||||
return str;
|
||||
}
|
||||
return str.endsWith(suffix) ? str.slice(0, -suffix.length) : str;
|
||||
}
|
||||
|
||||
/** Adds a given string suffix to `str`. */
|
||||
export function addSuffix(str: string, suffix: string): string {
|
||||
return str.endsWith(suffix) ? str : `${str}${suffix}`;
|
||||
}
|
||||
|
||||
/** Removes a given string prefix from `str`. */
|
||||
export function removePrefix(str: string, prefix: string): string {
|
||||
return str.startsWith(prefix) ? str.slice(prefix.length) : str;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/utils-validation",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Node validation utility functions for Docusaurus packages.",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
|
|
@ -18,9 +18,8 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/logger": "3.2.0",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/utils-common": "3.2.0",
|
||||
"@docusaurus/logger": "3.0.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"joi": "^17.9.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"tslib": "^2.6.0"
|
||||
|
|
|
|||
|
|
@ -5,8 +5,12 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {isValidPathname, DEFAULT_PLUGIN_ID, type Tag} from '@docusaurus/utils';
|
||||
import {addLeadingSlash} from '@docusaurus/utils-common';
|
||||
import {
|
||||
isValidPathname,
|
||||
DEFAULT_PLUGIN_ID,
|
||||
type Tag,
|
||||
addLeadingSlash,
|
||||
} from '@docusaurus/utils';
|
||||
import Joi from './Joi';
|
||||
import {JoiFrontMatter} from './JoiFrontMatter';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@docusaurus/utils",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Node utility functions for Docusaurus packages.",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
|
|
@ -18,8 +18,7 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/logger": "3.2.0",
|
||||
"@docusaurus/utils-common": "3.2.0",
|
||||
"@docusaurus/logger": "3.0.0",
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"file-loader": "^6.2.0",
|
||||
|
|
@ -42,7 +41,7 @@
|
|||
"node": ">=18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@types/dedent": "^0.7.0",
|
||||
"@types/github-slugger": "^1.3.0",
|
||||
"@types/micromatch": "^4.0.2",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,34 @@
|
|||
|
||||
import {jest} from '@jest/globals';
|
||||
import _ from 'lodash';
|
||||
import {mapAsyncSequential, findAsyncSequential} from '../jsUtils';
|
||||
import {
|
||||
removeSuffix,
|
||||
removePrefix,
|
||||
mapAsyncSequential,
|
||||
findAsyncSequential,
|
||||
} from '../jsUtils';
|
||||
|
||||
describe('removeSuffix', () => {
|
||||
it("is no-op when suffix doesn't exist", () => {
|
||||
expect(removeSuffix('abcdef', 'ijk')).toBe('abcdef');
|
||||
expect(removeSuffix('abcdef', 'abc')).toBe('abcdef');
|
||||
expect(removeSuffix('abcdef', '')).toBe('abcdef');
|
||||
});
|
||||
it('removes suffix', () => {
|
||||
expect(removeSuffix('abcdef', 'ef')).toBe('abcd');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removePrefix', () => {
|
||||
it("is no-op when prefix doesn't exist", () => {
|
||||
expect(removePrefix('abcdef', 'ijk')).toBe('abcdef');
|
||||
expect(removePrefix('abcdef', 'def')).toBe('abcdef');
|
||||
expect(removePrefix('abcdef', '')).toBe('abcdef');
|
||||
});
|
||||
it('removes prefix', () => {
|
||||
expect(removePrefix('prefix', 'pre')).toBe('fix');
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapAsyncSequential', () => {
|
||||
function sleep(timeout: number): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import {
|
|||
getEditUrl,
|
||||
fileToPath,
|
||||
isValidPathname,
|
||||
addTrailingSlash,
|
||||
addLeadingSlash,
|
||||
removeTrailingSlash,
|
||||
resolvePathname,
|
||||
encodePath,
|
||||
buildSshUrl,
|
||||
|
|
@ -204,6 +207,33 @@ describe('isValidPathname', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('addTrailingSlash', () => {
|
||||
it('is no-op for path with trailing slash', () => {
|
||||
expect(addTrailingSlash('/abcd/')).toBe('/abcd/');
|
||||
});
|
||||
it('adds / for path without trailing slash', () => {
|
||||
expect(addTrailingSlash('/abcd')).toBe('/abcd/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addLeadingSlash', () => {
|
||||
it('is no-op for path with leading slash', () => {
|
||||
expect(addLeadingSlash('/abc')).toBe('/abc');
|
||||
});
|
||||
it('adds / for path without leading slash', () => {
|
||||
expect(addLeadingSlash('abc')).toBe('/abc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeTrailingSlash', () => {
|
||||
it('is no-op for path without trailing slash', () => {
|
||||
expect(removeTrailingSlash('/abcd')).toBe('/abcd');
|
||||
});
|
||||
it('removes / for path with trailing slash', () => {
|
||||
expect(removeTrailingSlash('/abcd/')).toBe('/abcd');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseURLPath', () => {
|
||||
it('parse and resolve pathname', () => {
|
||||
expect(parseURLPath('')).toEqual({
|
||||
|
|
|
|||
|
|
@ -12,10 +12,6 @@ import {findAsyncSequential} from './jsUtils';
|
|||
|
||||
const fileHash = new Map<string, string>();
|
||||
|
||||
const hashContent = (content: string): string => {
|
||||
return createHash('md5').update(content).digest('hex');
|
||||
};
|
||||
|
||||
/**
|
||||
* Outputs a file to the generated files directory. Only writes files if content
|
||||
* differs from cache (for hot reload performance).
|
||||
|
|
@ -42,7 +38,7 @@ export async function generate(
|
|||
// first "A" remains in cache. But if the file never existed in cache, no
|
||||
// need to register it.
|
||||
if (fileHash.get(filepath)) {
|
||||
fileHash.set(filepath, hashContent(content));
|
||||
fileHash.set(filepath, createHash('md5').update(content).digest('hex'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -54,11 +50,11 @@ export async function generate(
|
|||
// overwriting and we can reuse old file.
|
||||
if (!lastHash && (await fs.pathExists(filepath))) {
|
||||
const lastContent = await fs.readFile(filepath, 'utf8');
|
||||
lastHash = hashContent(lastContent);
|
||||
lastHash = createHash('md5').update(lastContent).digest('hex');
|
||||
fileHash.set(filepath, lastHash);
|
||||
}
|
||||
|
||||
const currentHash = hashContent(content);
|
||||
const currentHash = createHash('md5').update(content).digest('hex');
|
||||
|
||||
if (lastHash !== currentHash) {
|
||||
await fs.outputFile(filepath, content);
|
||||
|
|
|
|||
|
|
@ -6,16 +6,7 @@
|
|||
*/
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import _ from 'lodash';
|
||||
import shell from 'shelljs'; // TODO replace with async-first version
|
||||
|
||||
const realHasGitFn = () => !!shell.which('git');
|
||||
|
||||
// The hasGit call is synchronous IO so we memoize it
|
||||
// The user won't install Git in the middle of a build anyway...
|
||||
const hasGit =
|
||||
process.env.NODE_ENV === 'test' ? realHasGitFn : _.memoize(realHasGitFn);
|
||||
import shell from 'shelljs';
|
||||
|
||||
/** Custom error thrown when git is not found in `PATH`. */
|
||||
export class GitNotFoundError extends Error {}
|
||||
|
|
@ -95,13 +86,13 @@ export async function getFileCommitDate(
|
|||
timestamp: number;
|
||||
author?: string;
|
||||
}> {
|
||||
if (!hasGit()) {
|
||||
if (!shell.which('git')) {
|
||||
throw new GitNotFoundError(
|
||||
`Failed to retrieve git history for "${file}" because git is not installed.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!(await fs.pathExists(file))) {
|
||||
if (!shell.test('-f', file)) {
|
||||
throw new Error(
|
||||
`Failed to retrieve git history for "${file}" because the file does not exist.`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@
|
|||
|
||||
import path from 'path';
|
||||
import Micromatch from 'micromatch'; // Note: Micromatch is used by Globby
|
||||
import {addSuffix} from '@docusaurus/utils-common';
|
||||
import {addSuffix} from './jsUtils';
|
||||
|
||||
/** A re-export of the globby instance. */
|
||||
export {default as Globby} from 'globby';
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,12 @@ export {
|
|||
getPluginI18nPath,
|
||||
localizePath,
|
||||
} from './i18nUtils';
|
||||
export {mapAsyncSequential, findAsyncSequential} from './jsUtils';
|
||||
export {
|
||||
removeSuffix,
|
||||
removePrefix,
|
||||
mapAsyncSequential,
|
||||
findAsyncSequential,
|
||||
} from './jsUtils';
|
||||
export {
|
||||
normalizeUrl,
|
||||
getEditUrl,
|
||||
|
|
@ -45,6 +50,9 @@ export {
|
|||
resolvePathname,
|
||||
parseURLPath,
|
||||
serializeURLPath,
|
||||
addLeadingSlash,
|
||||
addTrailingSlash,
|
||||
removeTrailingSlash,
|
||||
hasSSHProtocol,
|
||||
buildHttpsUrl,
|
||||
buildSshUrl,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,30 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** Adds a given string prefix to `str`. */
|
||||
export function addPrefix(str: string, prefix: string): string {
|
||||
return str.startsWith(prefix) ? str : `${prefix}${str}`;
|
||||
}
|
||||
|
||||
/** Adds a given string suffix to `str`. */
|
||||
export function addSuffix(str: string, suffix: string): string {
|
||||
return str.endsWith(suffix) ? str : `${str}${suffix}`;
|
||||
}
|
||||
|
||||
/** Removes a given string suffix from `str`. */
|
||||
export function removeSuffix(str: string, suffix: string): string {
|
||||
if (suffix === '') {
|
||||
// str.slice(0, 0) is ""
|
||||
return str;
|
||||
}
|
||||
return str.endsWith(suffix) ? str.slice(0, -suffix.length) : str;
|
||||
}
|
||||
|
||||
/** Removes a given string prefix from `str`. */
|
||||
export function removePrefix(str: string, prefix: string): string {
|
||||
return str.startsWith(prefix) ? str.slice(prefix.length) : str;
|
||||
}
|
||||
|
||||
/**
|
||||
* `Array#map` for async operations where order matters.
|
||||
* @param array The array to traverse.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import resolvePathnameUnsafe from 'resolve-pathname';
|
||||
import {addPrefix, addSuffix, removeSuffix} from './jsUtils';
|
||||
|
||||
/**
|
||||
* Much like `path.join`, but much better. Takes an array of URL segments, and
|
||||
|
|
@ -231,6 +232,22 @@ export function resolvePathname(to: string, from?: string): string {
|
|||
return resolvePathnameUnsafe(to, from);
|
||||
}
|
||||
|
||||
/** Appends a leading slash to `str`, if one doesn't exist. */
|
||||
export function addLeadingSlash(str: string): string {
|
||||
return addPrefix(str, '/');
|
||||
}
|
||||
|
||||
// TODO deduplicate: also present in @docusaurus/utils-common
|
||||
/** Appends a trailing slash to `str`, if one doesn't exist. */
|
||||
export function addTrailingSlash(str: string): string {
|
||||
return addSuffix(str, '/');
|
||||
}
|
||||
|
||||
/** Removes the trailing slash from `str`. */
|
||||
export function removeTrailingSlash(str: string): string {
|
||||
return removeSuffix(str, '/');
|
||||
}
|
||||
|
||||
/** Constructs an SSH URL that can be used to push to GitHub. */
|
||||
export function buildSshUrl(
|
||||
githubHost: string,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@docusaurus/core",
|
||||
"description": "Easy to Maintain Open Source Documentation Websites",
|
||||
"version": "3.2.0",
|
||||
"version": "3.0.0",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
|
@ -43,13 +43,13 @@
|
|||
"@babel/runtime": "^7.22.6",
|
||||
"@babel/runtime-corejs3": "^7.22.6",
|
||||
"@babel/traverse": "^7.22.8",
|
||||
"@docusaurus/cssnano-preset": "3.2.0",
|
||||
"@docusaurus/logger": "3.2.0",
|
||||
"@docusaurus/mdx-loader": "3.2.0",
|
||||
"@docusaurus/cssnano-preset": "3.0.0",
|
||||
"@docusaurus/logger": "3.0.0",
|
||||
"@docusaurus/mdx-loader": "3.0.0",
|
||||
"@docusaurus/react-loadable": "5.5.2",
|
||||
"@docusaurus/utils": "3.2.0",
|
||||
"@docusaurus/utils-common": "3.2.0",
|
||||
"@docusaurus/utils-validation": "3.2.0",
|
||||
"@docusaurus/utils": "3.0.0",
|
||||
"@docusaurus/utils-common": "3.0.0",
|
||||
"@docusaurus/utils-validation": "3.0.0",
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"babel-loader": "^9.1.3",
|
||||
|
|
@ -69,8 +69,8 @@
|
|||
"del": "^6.1.1",
|
||||
"detect-port": "^1.5.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"eta": "^2.2.0",
|
||||
"eval": "^0.1.8",
|
||||
"eta": "^2.2.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"html-minifier-terser": "^7.2.0",
|
||||
|
|
@ -105,8 +105,8 @@
|
|||
"webpackbar": "^5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.2.0",
|
||||
"@docusaurus/types": "3.2.0",
|
||||
"@docusaurus/module-type-aliases": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@total-typescript/shoehorn": "^0.1.2",
|
||||
"@types/detect-port": "^1.3.3",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
|
|
|
|||
|
|
@ -64,15 +64,23 @@ export async function build(
|
|||
process.on(sig, () => process.exit());
|
||||
});
|
||||
|
||||
async function tryToBuildLocale({locale}: {locale: string}) {
|
||||
async function tryToBuildLocale({
|
||||
locale,
|
||||
isLastLocale,
|
||||
}: {
|
||||
locale: string;
|
||||
isLastLocale: boolean;
|
||||
}) {
|
||||
try {
|
||||
await PerfLogger.async(`${logger.name(locale)}`, () =>
|
||||
buildLocale({
|
||||
siteDir,
|
||||
locale,
|
||||
cliOptions,
|
||||
}),
|
||||
);
|
||||
PerfLogger.start(`Building site for locale ${locale}`);
|
||||
await buildLocale({
|
||||
siteDir,
|
||||
locale,
|
||||
cliOptions,
|
||||
forceTerminate,
|
||||
isLastLocale,
|
||||
});
|
||||
PerfLogger.end(`Building site for locale ${locale}`);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
logger.interpolate`Unable to build website for locale name=${locale}.`,
|
||||
|
|
@ -83,28 +91,20 @@ export async function build(
|
|||
}
|
||||
}
|
||||
|
||||
const locales = await PerfLogger.async('Get locales to build', () =>
|
||||
getLocalesToBuild({siteDir, cliOptions}),
|
||||
);
|
||||
PerfLogger.start(`Get locales to build`);
|
||||
const locales = await getLocalesToBuild({siteDir, cliOptions});
|
||||
PerfLogger.end(`Get locales to build`);
|
||||
|
||||
if (locales.length > 1) {
|
||||
logger.info`Website will be built for all these locales: ${locales}`;
|
||||
}
|
||||
|
||||
await PerfLogger.async(`Build`, () =>
|
||||
mapAsyncSequential(locales, async (locale) => {
|
||||
const isLastLocale = locales.indexOf(locale) === locales.length - 1;
|
||||
await tryToBuildLocale({locale});
|
||||
if (isLastLocale) {
|
||||
logger.info`Use code=${'npm run serve'} command to test your build locally.`;
|
||||
}
|
||||
|
||||
// TODO do we really need this historical forceTerminate exit???
|
||||
if (forceTerminate && isLastLocale && !cliOptions.bundleAnalyzer) {
|
||||
process.exit(0);
|
||||
}
|
||||
}),
|
||||
);
|
||||
PerfLogger.start(`Building ${locales.length} locales`);
|
||||
await mapAsyncSequential(locales, (locale) => {
|
||||
const isLastLocale = locales.indexOf(locale) === locales.length - 1;
|
||||
return tryToBuildLocale({locale, isLastLocale});
|
||||
});
|
||||
PerfLogger.end(`Building ${locales.length} locales`);
|
||||
}
|
||||
|
||||
async function getLocalesToBuild({
|
||||
|
|
@ -144,10 +144,14 @@ async function buildLocale({
|
|||
siteDir,
|
||||
locale,
|
||||
cliOptions,
|
||||
forceTerminate,
|
||||
isLastLocale,
|
||||
}: {
|
||||
siteDir: string;
|
||||
locale: string;
|
||||
cliOptions: Partial<BuildCLIOptions>;
|
||||
forceTerminate: boolean;
|
||||
isLastLocale: boolean;
|
||||
}): Promise<string> {
|
||||
// Temporary workaround to unlock the ability to translate the site config
|
||||
// We'll remove it if a better official API can be designed
|
||||
|
|
@ -156,66 +160,81 @@ async function buildLocale({
|
|||
|
||||
logger.info`name=${`[${locale}]`} Creating an optimized production build...`;
|
||||
|
||||
const site = await PerfLogger.async('Load site', () =>
|
||||
loadSite({
|
||||
siteDir,
|
||||
outDir: cliOptions.outDir,
|
||||
config: cliOptions.config,
|
||||
locale,
|
||||
localizePath: cliOptions.locale ? false : undefined,
|
||||
}),
|
||||
);
|
||||
PerfLogger.start('Loading site');
|
||||
const site = await loadSite({
|
||||
siteDir,
|
||||
outDir: cliOptions.outDir,
|
||||
config: cliOptions.config,
|
||||
locale,
|
||||
localizePath: cliOptions.locale ? false : undefined,
|
||||
});
|
||||
PerfLogger.end('Loading site');
|
||||
|
||||
const {props} = site;
|
||||
const {outDir, plugins} = props;
|
||||
|
||||
// We can build the 2 configs in parallel
|
||||
PerfLogger.start('Creating webpack configs');
|
||||
const [{clientConfig, clientManifestPath}, {serverConfig, serverBundlePath}] =
|
||||
await PerfLogger.async('Creating webpack configs', () =>
|
||||
Promise.all([
|
||||
getBuildClientConfig({
|
||||
props,
|
||||
cliOptions,
|
||||
}),
|
||||
getBuildServerConfig({
|
||||
props,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
await Promise.all([
|
||||
getBuildClientConfig({
|
||||
props,
|
||||
cliOptions,
|
||||
}),
|
||||
getBuildServerConfig({
|
||||
props,
|
||||
}),
|
||||
]);
|
||||
PerfLogger.end('Creating webpack configs');
|
||||
|
||||
// Make sure generated client-manifest is cleaned first, so we don't reuse
|
||||
// the one from previous builds.
|
||||
// TODO do we really need this? .docusaurus folder is cleaned between builds
|
||||
PerfLogger.start('Deleting previous client manifest');
|
||||
await ensureUnlink(clientManifestPath);
|
||||
PerfLogger.end('Deleting previous client manifest');
|
||||
|
||||
// Run webpack to build JS bundle (client) and static html files (server).
|
||||
await PerfLogger.async('Bundling with Webpack', () =>
|
||||
compile([clientConfig, serverConfig]),
|
||||
);
|
||||
PerfLogger.start('Bundling');
|
||||
await compile([clientConfig, serverConfig]);
|
||||
PerfLogger.end('Bundling');
|
||||
|
||||
const {collectedData} = await PerfLogger.async('SSG', () =>
|
||||
executeSSG({
|
||||
props,
|
||||
serverBundlePath,
|
||||
clientManifestPath,
|
||||
}),
|
||||
);
|
||||
PerfLogger.start('Executing static site generation');
|
||||
const {collectedData} = await executeSSG({
|
||||
props,
|
||||
serverBundlePath,
|
||||
clientManifestPath,
|
||||
});
|
||||
PerfLogger.end('Executing static site generation');
|
||||
|
||||
// Remove server.bundle.js because it is not needed.
|
||||
await PerfLogger.async('Deleting server bundle', () =>
|
||||
ensureUnlink(serverBundlePath),
|
||||
);
|
||||
PerfLogger.start('Deleting server bundle');
|
||||
await ensureUnlink(serverBundlePath);
|
||||
PerfLogger.end('Deleting server bundle');
|
||||
|
||||
// Plugin Lifecycle - postBuild.
|
||||
await PerfLogger.async('postBuild()', () =>
|
||||
executePluginsPostBuild({plugins, props, collectedData}),
|
||||
);
|
||||
PerfLogger.start('Executing postBuild()');
|
||||
await executePluginsPostBuild({plugins, props, collectedData});
|
||||
PerfLogger.end('Executing postBuild()');
|
||||
|
||||
// TODO execute this in parallel to postBuild?
|
||||
await PerfLogger.async('Broken links checker', () =>
|
||||
executeBrokenLinksCheck({props, collectedData}),
|
||||
);
|
||||
PerfLogger.start('Executing broken links checker');
|
||||
await executeBrokenLinksCheck({props, collectedData});
|
||||
PerfLogger.end('Executing broken links checker');
|
||||
|
||||
logger.success`Generated static files in path=${path.relative(
|
||||
process.cwd(),
|
||||
outDir,
|
||||
)}.`;
|
||||
|
||||
if (isLastLocale) {
|
||||
logger.info`Use code=${'npm run serve'} command to test your build locally.`;
|
||||
}
|
||||
|
||||
if (forceTerminate && isLastLocale && !cliOptions.bundleAnalyzer) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
return outDir;
|
||||
}
|
||||
|
||||
|
|
@ -228,39 +247,40 @@ async function executeSSG({
|
|||
serverBundlePath: string;
|
||||
clientManifestPath: string;
|
||||
}) {
|
||||
const manifest: Manifest = await PerfLogger.async(
|
||||
'Read client manifest',
|
||||
() => fs.readJSON(clientManifestPath, 'utf-8'),
|
||||
);
|
||||
PerfLogger.start('Reading client manifest');
|
||||
const manifest: Manifest = await fs.readJSON(clientManifestPath, 'utf-8');
|
||||
PerfLogger.end('Reading client manifest');
|
||||
|
||||
const ssrTemplate = await PerfLogger.async('Compile SSR template', () =>
|
||||
compileSSRTemplate(props.siteConfig.ssrTemplate ?? defaultSSRTemplate),
|
||||
PerfLogger.start('Compiling SSR template');
|
||||
const ssrTemplate = await compileSSRTemplate(
|
||||
props.siteConfig.ssrTemplate ?? defaultSSRTemplate,
|
||||
);
|
||||
PerfLogger.end('Compiling SSR template');
|
||||
|
||||
const renderer = await PerfLogger.async('Load App renderer', () =>
|
||||
loadAppRenderer({
|
||||
serverBundlePath,
|
||||
}),
|
||||
);
|
||||
PerfLogger.start('Loading App renderer');
|
||||
const renderer = await loadAppRenderer({
|
||||
serverBundlePath,
|
||||
});
|
||||
PerfLogger.end('Loading App renderer');
|
||||
|
||||
const ssgResult = await PerfLogger.async('Generate static files', () =>
|
||||
generateStaticFiles({
|
||||
pathnames: props.routesPaths,
|
||||
renderer,
|
||||
params: {
|
||||
trailingSlash: props.siteConfig.trailingSlash,
|
||||
outDir: props.outDir,
|
||||
baseUrl: props.baseUrl,
|
||||
manifest,
|
||||
headTags: props.headTags,
|
||||
preBodyTags: props.preBodyTags,
|
||||
postBodyTags: props.postBodyTags,
|
||||
ssrTemplate,
|
||||
noIndex: props.siteConfig.noIndex,
|
||||
DOCUSAURUS_VERSION,
|
||||
},
|
||||
}),
|
||||
);
|
||||
PerfLogger.start('Generate static files');
|
||||
const ssgResult = await generateStaticFiles({
|
||||
pathnames: props.routesPaths,
|
||||
renderer,
|
||||
params: {
|
||||
trailingSlash: props.siteConfig.trailingSlash,
|
||||
outDir: props.outDir,
|
||||
baseUrl: props.baseUrl,
|
||||
manifest,
|
||||
headTags: props.headTags,
|
||||
preBodyTags: props.preBodyTags,
|
||||
postBodyTags: props.postBodyTags,
|
||||
ssrTemplate,
|
||||
noIndex: props.siteConfig.noIndex,
|
||||
DOCUSAURUS_VERSION,
|
||||
},
|
||||
});
|
||||
PerfLogger.end('Generate static files');
|
||||
|
||||
return ssgResult;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import {
|
|||
reloadSite,
|
||||
reloadSitePlugin,
|
||||
} from '../../server/site';
|
||||
import {formatPluginName} from '../../server/plugins/pluginsUtils';
|
||||
import type {StartCLIOptions} from './start';
|
||||
import type {LoadedPlugin} from '@docusaurus/types';
|
||||
|
||||
|
|
@ -70,13 +69,10 @@ async function createLoadSiteParams({
|
|||
export async function createReloadableSite(startParams: StartParams) {
|
||||
const openUrlContext = await createOpenUrlContext(startParams);
|
||||
|
||||
const loadSiteParams = await PerfLogger.async('createLoadSiteParams', () =>
|
||||
createLoadSiteParams(startParams),
|
||||
);
|
||||
|
||||
let site = await PerfLogger.async('Load site', () =>
|
||||
loadSite(loadSiteParams),
|
||||
);
|
||||
let site = await PerfLogger.async('Loading site', async () => {
|
||||
const params = await createLoadSiteParams(startParams);
|
||||
return loadSite(params);
|
||||
});
|
||||
|
||||
const get = () => site;
|
||||
|
||||
|
|
@ -93,7 +89,7 @@ export async function createReloadableSite(startParams: StartParams) {
|
|||
const reloadBase = async () => {
|
||||
try {
|
||||
const oldSite = site;
|
||||
site = await PerfLogger.async('Reload site', () => reloadSite(site));
|
||||
site = await PerfLogger.async('Reloading site', () => reloadSite(site));
|
||||
if (oldSite.props.baseUrl !== site.props.baseUrl) {
|
||||
printOpenUrlMessage();
|
||||
}
|
||||
|
|
@ -112,7 +108,7 @@ export async function createReloadableSite(startParams: StartParams) {
|
|||
const reloadPlugin = async (plugin: LoadedPlugin) => {
|
||||
try {
|
||||
site = await PerfLogger.async(
|
||||
`Reload site plugin ${formatPluginName(plugin)}`,
|
||||
`Reloading site plugin ${plugin.name}@${plugin.options.id}`,
|
||||
() => {
|
||||
const pluginIdentifier = {name: plugin.name, id: plugin.options.id};
|
||||
return reloadSitePlugin(site, pluginIdentifier);
|
||||
|
|
@ -120,7 +116,7 @@ export async function createReloadableSite(startParams: StartParams) {
|
|||
);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`Site plugin reload failure - Plugin ${formatPluginName(plugin)}`,
|
||||
`Site plugin reload failure - Plugin ${plugin.name}@${plugin.options.id}`,
|
||||
);
|
||||
console.error(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
writePluginTranslations,
|
||||
writeCodeTranslations,
|
||||
type WriteTranslationsOptions,
|
||||
loadPluginsDefaultCodeTranslationMessages,
|
||||
getPluginsDefaultCodeTranslationMessages,
|
||||
applyDefaultCodeTranslations,
|
||||
} from '../server/translations/translations';
|
||||
import {
|
||||
|
|
@ -114,7 +114,7 @@ Available locales are: ${context.i18n.locales.join(',')}.`,
|
|||
await getExtraSourceCodeFilePaths(),
|
||||
);
|
||||
|
||||
const defaultCodeMessages = await loadPluginsDefaultCodeTranslationMessages(
|
||||
const defaultCodeMessages = await getPluginsDefaultCodeTranslationMessages(
|
||||
plugins,
|
||||
);
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue