From 347070bb07d941da8a39650d74a370795d574ded Mon Sep 17 00:00:00 2001 From: Sergio Date: Thu, 1 Aug 2024 12:15:53 +0200 Subject: [PATCH 01/10] fix(translations): Fix and Improve Spanish translations (#10360) Co-authored-by: sebastien --- .../locales/es/plugin-ideal-image.json | 2 +- .../locales/es/theme-common.json | 20 +++++++++---------- .../locales/es/theme-search-algolia.json | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/docusaurus-theme-translations/locales/es/plugin-ideal-image.json b/packages/docusaurus-theme-translations/locales/es/plugin-ideal-image.json index 69d9de78425b..785d93799ebf 100644 --- a/packages/docusaurus-theme-translations/locales/es/plugin-ideal-image.json +++ b/packages/docusaurus-theme-translations/locales/es/plugin-ideal-image.json @@ -3,5 +3,5 @@ "theme.IdealImageMessage.error": "Error. Click para recargar", "theme.IdealImageMessage.load": "Click para recargar{sizeMessage}", "theme.IdealImageMessage.loading": "Cargando...", - "theme.IdealImageMessage.offline": "Tu navegador está desconectado. Image no cargada" + "theme.IdealImageMessage.offline": "Tu navegador está desconectado. Imagen no cargada" } diff --git a/packages/docusaurus-theme-translations/locales/es/theme-common.json b/packages/docusaurus-theme-translations/locales/es/theme-common.json index f581fbcb7e09..29fe4934840c 100644 --- a/packages/docusaurus-theme-translations/locales/es/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/es/theme-common.json @@ -5,21 +5,21 @@ "theme.CodeBlock.copy": "Copiar", "theme.CodeBlock.copyButtonAriaLabel": "Copiar código", "theme.CodeBlock.wordWrapToggle": "Alternar ajuste de palabras", - "theme.DocSidebarItem.collapseCategoryAriaLabel": "Colapsar categoría '{label}' de barra lateral", + "theme.DocSidebarItem.collapseCategoryAriaLabel": "Colapsar categoría '{label}' de la barra lateral", "theme.DocSidebarItem.expandCategoryAriaLabel": "Ampliar la categoría '{label}' de la barra lateral", "theme.ErrorPageContent.title": "Esta página ha fallado.", "theme.ErrorPageContent.tryAgain": "Intente de nuevo", "theme.NavBar.navAriaLabel": "Principal", "theme.NotFound.p1": "No pudimos encontrar lo que buscaba.", - "theme.NotFound.p2": "Comuníquese con el dueño del sitio que lo vinculó a la URL original y hágale saber que su vínculo está roto.", + "theme.NotFound.p2": "Comuníquese con el dueño del sitio que le proporcionó la URL original y hágale saber que su vínculo está roto.", "theme.NotFound.title": "Página No Encontrada", "theme.TOCCollapsible.toggleButtonLabel": "En esta página", "theme.admonition.caution": "precaución", - "theme.admonition.danger": "danger", + "theme.admonition.danger": "peligro", "theme.admonition.info": "info", - "theme.admonition.note": "note", + "theme.admonition.note": "nota", "theme.admonition.tip": "tip", - "theme.admonition.warning": "warning", + "theme.admonition.warning": "aviso", "theme.blog.archive.description": "Archivo", "theme.blog.archive.title": "Archivo", "theme.blog.paginator.navAriaLabel": "Navegación por la página de la lista de blogs ", @@ -30,7 +30,7 @@ "theme.blog.post.paginator.olderPost": "Publicación más antigua", "theme.blog.post.plurals": "Una publicación|{count} publicaciones", "theme.blog.post.readMore": "Leer Más", - "theme.blog.post.readMoreLabel": "Leer más acerca {title}", + "theme.blog.post.readMoreLabel": "Leer más acerca de {title}", "theme.blog.post.readingTime.plurals": "Lectura de un minuto|{readingTime} min de lectura", "theme.blog.sidebar.navAriaLabel": "Navegación de publicaciones recientes", "theme.blog.tagTitle": "{nPosts} etiquetados con \"{tagName}\"", @@ -42,7 +42,7 @@ "theme.common.skipToMainContent": "Saltar al contenido principal", "theme.docs.DocCard.categoryDescription.plurals": "1 artículo|{count} artículos", "theme.docs.breadcrumbs.home": "Página de Inicio", - "theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs", + "theme.docs.breadcrumbs.navAriaLabel": "Migas de pan", "theme.docs.paginator.navAriaLabel": "Página del documento", "theme.docs.paginator.next": "Siguiente", "theme.docs.paginator.previous": "Anterior", @@ -58,12 +58,12 @@ "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "última versión", "theme.docs.versions.latestVersionSuggestionLabel": "Para la documentación actualizada, vea {latestVersionLink} ({versionLabel}).", - "theme.docs.versions.unmaintainedVersionLabel": "Esta es documentación para {siteTitle} {versionLabel}, que ya no se mantiene activamente.", - "theme.docs.versions.unreleasedVersionLabel": "Esta es documentación sin liberar para {siteTitle} {versionLabel} versión.", + "theme.docs.versions.unmaintainedVersionLabel": "Esta es la documentación para {siteTitle} {versionLabel}, que ya no se mantiene activamente.", + "theme.docs.versions.unreleasedVersionLabel": "Esta es la documentación sin publicar para {siteTitle}, versión {versionLabel}.", "theme.lastUpdated.atDate": " en {date}", "theme.lastUpdated.byUser": " por {user}", "theme.lastUpdated.lastUpdatedAtBy": "Última actualización{atDate}{byUser}", - "theme.navbar.mobileLanguageDropdown.label": "Lenguajes", + "theme.navbar.mobileLanguageDropdown.label": "Idiomas", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Volver al menú principal", "theme.navbar.mobileVersionsDropdown.label": "Versiones", "theme.tags.tagsListLabel": "Etiquetas:", diff --git a/packages/docusaurus-theme-translations/locales/es/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/es/theme-search-algolia.json index e2d8f7702861..987ba4194c85 100644 --- a/packages/docusaurus-theme-translations/locales/es/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/es/theme-search-algolia.json @@ -13,7 +13,7 @@ "theme.SearchModal.footer.selectText": "seleccionar", "theme.SearchModal.noResultsScreen.noResultsText": "Sin resultados para", "theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": "Háganos saber.", - "theme.SearchModal.noResultsScreen.reportMissingResultsText": "Creo que esta consulta debería devolver resultados?", + "theme.SearchModal.noResultsScreen.reportMissingResultsText": "Crees que esta consulta debería devolver resultados?", "theme.SearchModal.noResultsScreen.suggestedQueryText": "Intenta buscando por", "theme.SearchModal.placeholder": "Buscar documentos", "theme.SearchModal.searchBox.cancelButtonText": "Cancelar", From 50f9fce29be71f211d8e2221e7f40075d8a3bd23 Mon Sep 17 00:00:00 2001 From: yujonglee Date: Thu, 1 Aug 2024 22:23:04 +0900 Subject: [PATCH 02/10] docs: rename @getcanary/docusaurus-pagefind in docs (#10361) --- website/community/2-resources.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/community/2-resources.mdx b/website/community/2-resources.mdx index 8e3b6a845aa5..47febb2582b6 100644 --- a/website/community/2-resources.mdx +++ b/website/community/2-resources.mdx @@ -43,7 +43,7 @@ See the showcase - [meilisearch-docsearch](https://github.com/tauri-apps/meilisearch-docsearch) - Docusaurus plugin for [Meilisearch](https://www.meilisearch.com) - [@orama/plugin-docusaurus](https://github.com/askorama/orama/tree/main/packages/plugin-docusaurus) - [Orama](https://askorama.ai/) plugin for Docusaurus v2 - [@orama/plugin-docusaurus-v3](https://github.com/askorama/orama/tree/main/packages/plugin-docusaurus-v3) - [Orama](https://askorama.ai/) plugin for Docusaurus v3 -- [@getcanary/docusaurus-pagefind](https://getcanary.dev/docs/integrations/docusaurus.html) - Create [Pagefind](https://pagefind.app/) index and use [Canary](https://github.com/fastrepl/canary) as UI primitives. +- [@getcanary/docusaurus-theme-search-pagefind](https://getcanary.dev/docs/integrations/docusaurus.html) - Create [Pagefind](https://pagefind.app/) index and use [Canary](https://github.com/fastrepl/canary) as UI primitives. ### Integrations {#integrations} From f356e29938e48e9c4653f4a49caff2e8ca760cb3 Mon Sep 17 00:00:00 2001 From: ozaki <29860391+OzakIOne@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:30:49 +0200 Subject: [PATCH 03/10] feat(blog): authors page (#10216) Co-authored-by: OzakIOne Co-authored-by: sebastien Co-authored-by: slorber --- .../__fixtures__/website/blog/authors.yml | 1 + .../authors.yml | 2 + .../__snapshots__/blogUtils.test.ts.snap | 19 + .../__snapshots__/index.test.ts.snap | 2 + .../src/__tests__/authors.test.ts | 475 ++++++------------ .../src/__tests__/authorsMap.test.ts | 307 +++++++++++ .../src/__tests__/authorsProblems.test.ts | 62 +-- .../src/__tests__/blogUtils.test.ts | 18 + .../src/__tests__/feed.test.ts | 11 +- .../src/__tests__/index.test.ts | 7 + .../src/authors.ts | 168 +++---- .../src/authorsMap.ts | 171 +++++++ .../src/blogUtils.ts | 11 +- .../src/index.ts | 24 +- .../src/options.ts | 12 + .../src/plugin-content-blog.d.ts | 102 +++- .../src/props.ts | 15 + .../src/routes.ts | 99 +++- .../src/getSwizzleConfig.ts | 21 + .../src/theme-classic.d.ts | 49 +- .../Components}/Author/Socials/index.tsx | 11 +- .../Author/Socials/styles.module.css | 9 +- .../theme/Blog/Components/Author/index.tsx | 99 ++++ .../Blog/Components/Author/styles.module.css | 74 +++ .../Blog/Pages/BlogAuthorsListPage/index.tsx | 62 +++ .../BlogAuthorsListPage/styles.module.css | 11 + .../Blog/Pages/BlogAuthorsPostsPage/index.tsx | 73 +++ .../BlogPostItem/Header/Author/index.tsx | 62 --- .../Header/Author/styles.module.css | 21 - .../BlogPostItem/Header/Authors/index.tsx | 5 +- .../src/theme/BlogTagsPostsPage/index.tsx | 34 +- .../docusaurus-theme-common/src/internal.ts | 7 + .../src/translations/blogTranslations.tsx | 79 +++ .../src/utils/ThemeClassNames.ts | 2 + .../{tagUtils.test.ts => tagsUtils.test.ts} | 21 +- .../locales/base/theme-common.json | 3 + .../dataFiles/actualData/bad.json | 1 - .../__fixtures__/dataFiles/actualData/bad.yml | 1 - .../dataFiles/actualData/valid.json | 1 - .../dataFiles/actualData/valid.yml | 1 - .../dataFiles/dataFiles/dataFile.json | 1 + .../dataFiles/dataFiles/dataFile.yml | 1 + .../dataFiles/dataFiles/invalid.yml | 1 + .../dataFiles/localized/dataFile.yml | 1 + .../src/__tests__/dataFileUtils.test.ts | 52 +- .../docusaurus-utils/src/dataFileUtils.ts | 27 +- packages/docusaurus-utils/src/index.ts | 2 +- packages/docusaurus-utils/src/tags.ts | 1 + project-words.txt | 1 + .../_blog tests/2024-07-03-dual-author.mdx | 8 +- .../_blog tests/2024-07-03-single-author.mdx | 1 - website/_dogfooding/_blog tests/authors.yml | 5 + website/blog/authors.yml | 12 +- .../docs/api/plugins/plugin-content-blog.mdx | 69 +++ website/docs/blog.mdx | 33 ++ .../ChangelogItem/Header/Author/index.tsx | 2 +- 56 files changed, 1667 insertions(+), 703 deletions(-) create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/authorsMap.test.ts create mode 100644 packages/docusaurus-plugin-content-blog/src/authorsMap.ts rename packages/docusaurus-theme-classic/src/theme/{BlogPostItem/Header => Blog/Components}/Author/Socials/index.tsx (86%) rename packages/docusaurus-theme-classic/src/theme/{BlogPostItem/Header => Blog/Components}/Author/Socials/styles.module.css (75%) create mode 100644 packages/docusaurus-theme-classic/src/theme/Blog/Components/Author/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/Blog/Components/Author/styles.module.css create mode 100644 packages/docusaurus-theme-classic/src/theme/Blog/Pages/BlogAuthorsListPage/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/Blog/Pages/BlogAuthorsListPage/styles.module.css create mode 100644 packages/docusaurus-theme-classic/src/theme/Blog/Pages/BlogAuthorsPostsPage/index.tsx delete mode 100644 packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/index.tsx delete mode 100644 packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/styles.module.css create mode 100644 packages/docusaurus-theme-common/src/translations/blogTranslations.tsx rename packages/docusaurus-theme-common/src/utils/__tests__/{tagUtils.test.ts => tagsUtils.test.ts} (78%) delete mode 100644 packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/bad.json delete mode 100644 packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/bad.yml delete mode 100644 packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/valid.json delete mode 100644 packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/valid.yml create mode 100644 packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/dataFile.json create mode 100644 packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/dataFile.yml create mode 100644 packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/invalid.yml create mode 100644 packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/localized/dataFile.yml diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/authors.yml b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/authors.yml index c44d2ee68da6..6e7482b6ebd6 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/authors.yml +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/authors.yml @@ -7,3 +7,4 @@ slorber: twitter: sebastienlorber x: https://x.com/sebastienlorber github: slorber + page: true diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/authors.yml b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/authors.yml index f509f4ff45ee..0e68e44c2151 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/authors.yml +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/authors.yml @@ -2,3 +2,5 @@ slorber: name: Sébastien Lorber (translated) title: Docusaurus maintainer (translated) email: lorber.sebastien@gmail.com + page: + permalink: "/slorber-custom-permalink-localized" diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/blogUtils.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/blogUtils.test.ts.snap index 038e71ca8f8e..12f6076df5ad 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/blogUtils.test.ts.snap +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/blogUtils.test.ts.snap @@ -25,6 +25,25 @@ exports[`paginateBlogPosts generates a single page 1`] = ` ] `; +exports[`paginateBlogPosts generates pages - 0 blog post 1`] = ` +[ + { + "items": [], + "metadata": { + "blogDescription": "Blog Description", + "blogTitle": "Blog Title", + "nextPage": undefined, + "page": 1, + "permalink": "/blog", + "postsPerPage": 2, + "previousPage": undefined, + "totalCount": 0, + "totalPages": 1, + }, + }, +] +`; + exports[`paginateBlogPosts generates pages 1`] = ` [ { diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap index a9ff614dbab4..30c280918767 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap @@ -121,7 +121,9 @@ exports[`blog plugin process blog posts load content 2`] = ` "authors": [ { "imageURL": undefined, + "key": null, "name": "Sébastien Lorber", + "page": null, "title": "Docusaurus maintainer", "url": "https://sebastienlorber.com", }, diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts index 851bf77fa2fd..7072e8124caa 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts @@ -5,13 +5,13 @@ * LICENSE file in the root directory of this source tree. */ -import * as path from 'path'; -import { - type AuthorsMap, - getAuthorsMap, - getBlogPostAuthors, - validateAuthorsMap, -} from '../authors'; +import {fromPartial, type PartialDeep} from '@total-typescript/shoehorn'; +import {getBlogPostAuthors, groupBlogPostsByAuthorKey} from '../authors'; +import type {AuthorsMap, BlogPost} from '@docusaurus/plugin-content-blog'; + +function post(partial: PartialDeep): BlogPost { + return fromPartial(partial); +} describe('getBlogPostAuthors', () => { it('can read no authors', () => { @@ -42,7 +42,15 @@ describe('getBlogPostAuthors', () => { authorsMap: undefined, baseUrl: '/', }), - ).toEqual([{name: 'Sébastien Lorber'}]); + ).toEqual([ + { + name: 'Sébastien Lorber', + imageURL: undefined, + key: null, + page: null, + title: undefined, + }, + ]); expect( getBlogPostAuthors({ frontMatter: { @@ -51,7 +59,15 @@ describe('getBlogPostAuthors', () => { authorsMap: undefined, baseUrl: '/', }), - ).toEqual([{title: 'maintainer'}]); + ).toEqual([ + { + title: 'maintainer', + imageURL: undefined, + key: null, + name: undefined, + page: null, + }, + ]); expect( getBlogPostAuthors({ frontMatter: { @@ -60,7 +76,14 @@ describe('getBlogPostAuthors', () => { authorsMap: undefined, baseUrl: '/', }), - ).toEqual([{imageURL: 'https://github.com/slorber.png'}]); + ).toEqual([ + { + imageURL: 'https://github.com/slorber.png', + key: null, + name: undefined, + page: null, + }, + ]); expect( getBlogPostAuthors({ frontMatter: { @@ -69,7 +92,14 @@ describe('getBlogPostAuthors', () => { authorsMap: undefined, baseUrl: '/', }), - ).toEqual([{imageURL: '/img/slorber.png'}]); + ).toEqual([ + { + imageURL: '/img/slorber.png', + key: null, + name: undefined, + page: null, + }, + ]); expect( getBlogPostAuthors({ frontMatter: { @@ -78,7 +108,15 @@ describe('getBlogPostAuthors', () => { authorsMap: undefined, baseUrl: '/baseURL', }), - ).toEqual([{imageURL: '/baseURL/img/slorber.png'}]); + ).toEqual([ + { + imageURL: '/baseURL/img/slorber.png', + + key: null, + name: undefined, + page: null, + }, + ]); expect( getBlogPostAuthors({ frontMatter: { @@ -99,6 +137,8 @@ describe('getBlogPostAuthors', () => { title: 'maintainer1', imageURL: 'https://github.com/slorber1.png', url: 'https://github.com/slorber1', + key: null, + page: null, }, ]); }); @@ -109,10 +149,19 @@ describe('getBlogPostAuthors', () => { frontMatter: { authors: 'slorber', }, - authorsMap: {slorber: {name: 'Sébastien Lorber'}}, + authorsMap: { + slorber: {name: 'Sébastien Lorber', key: 'slorber', page: null}, + }, baseUrl: '/', }), - ).toEqual([{key: 'slorber', name: 'Sébastien Lorber'}]); + ).toEqual([ + { + key: 'slorber', + name: 'Sébastien Lorber', + imageURL: undefined, + page: null, + }, + ]); expect( getBlogPostAuthors({ frontMatter: { @@ -122,6 +171,8 @@ describe('getBlogPostAuthors', () => { slorber: { name: 'Sébastien Lorber', imageURL: 'https://github.com/slorber.png', + key: 'slorber', + page: null, }, }, baseUrl: '/', @@ -131,6 +182,7 @@ describe('getBlogPostAuthors', () => { key: 'slorber', name: 'Sébastien Lorber', imageURL: 'https://github.com/slorber.png', + page: null, }, ]); expect( @@ -142,6 +194,8 @@ describe('getBlogPostAuthors', () => { slorber: { name: 'Sébastien Lorber', imageURL: '/img/slorber.png', + key: 'slorber', + page: null, }, }, baseUrl: '/', @@ -151,6 +205,7 @@ describe('getBlogPostAuthors', () => { key: 'slorber', name: 'Sébastien Lorber', imageURL: '/img/slorber.png', + page: null, }, ]); expect( @@ -162,6 +217,8 @@ describe('getBlogPostAuthors', () => { slorber: { name: 'Sébastien Lorber', imageURL: '/img/slorber.png', + key: 'slorber', + page: null, }, }, baseUrl: '/baseUrl', @@ -171,6 +228,7 @@ describe('getBlogPostAuthors', () => { key: 'slorber', name: 'Sébastien Lorber', imageURL: '/baseUrl/img/slorber.png', + page: null, }, ]); }); @@ -182,14 +240,31 @@ describe('getBlogPostAuthors', () => { authors: ['slorber', 'yangshun'], }, authorsMap: { - slorber: {name: 'Sébastien Lorber', title: 'maintainer'}, - yangshun: {name: 'Yangshun Tay'}, + slorber: { + name: 'Sébastien Lorber', + title: 'maintainer', + key: 'slorber', + page: null, + }, + yangshun: {name: 'Yangshun Tay', key: 'yangshun', page: null}, }, baseUrl: '/', }), ).toEqual([ - {key: 'slorber', name: 'Sébastien Lorber', title: 'maintainer'}, - {key: 'yangshun', name: 'Yangshun Tay'}, + { + key: 'slorber', + name: 'Sébastien Lorber', + title: 'maintainer', + imageURL: undefined, + page: null, + }, + { + key: 'yangshun', + name: 'Yangshun Tay', + imageURL: undefined, + + page: null, + }, ]); }); @@ -202,7 +277,15 @@ describe('getBlogPostAuthors', () => { authorsMap: undefined, baseUrl: '/', }), - ).toEqual([{name: 'Sébastien Lorber', title: 'maintainer'}]); + ).toEqual([ + { + name: 'Sébastien Lorber', + title: 'maintainer', + imageURL: undefined, + key: null, + page: null, + }, + ]); }); it('can read authors Author[]', () => { @@ -218,8 +301,14 @@ describe('getBlogPostAuthors', () => { baseUrl: '/', }), ).toEqual([ - {name: 'Sébastien Lorber', title: 'maintainer'}, - {name: 'Yangshun Tay'}, + { + name: 'Sébastien Lorber', + title: 'maintainer', + imageURL: undefined, + key: null, + page: null, + }, + {name: 'Yangshun Tay', imageURL: undefined, key: null, page: null}, ]); }); @@ -238,66 +327,38 @@ describe('getBlogPostAuthors', () => { ], }, authorsMap: { - slorber: {name: 'Sébastien Lorber', title: 'maintainer'}, - yangshun: {name: 'Yangshun Tay', title: 'Yangshun title original'}, + slorber: { + name: 'Sébastien Lorber', + title: 'maintainer', + key: 'slorber', + page: null, + }, + yangshun: { + name: 'Yangshun Tay', + title: 'Yangshun title original', + key: 'yangshun', + page: null, + }, }, baseUrl: '/', }), ).toEqual([ - {key: 'slorber', name: 'Sébastien Lorber', title: 'maintainer'}, + { + key: 'slorber', + name: 'Sébastien Lorber', + title: 'maintainer', + imageURL: undefined, + page: null, + }, { key: 'yangshun', name: 'Yangshun Tay', title: 'Yangshun title local override', extra: 42, + imageURL: undefined, + page: null, }, - {name: 'Alexey'}, - ]); - }); - - it('can normalize inline authors', () => { - expect( - getBlogPostAuthors({ - frontMatter: { - authors: [ - { - name: 'Seb1', - socials: { - x: 'https://x.com/sebastienlorber', - twitter: 'sebastienlorber', - github: 'slorber', - }, - }, - { - name: 'Seb2', - socials: { - x: 'sebastienlorber', - twitter: 'https://twitter.com/sebastienlorber', - github: 'https://github.com/slorber', - }, - }, - ], - }, - authorsMap: {}, - baseUrl: '/', - }), - ).toEqual([ - { - name: 'Seb1', - socials: { - x: 'https://x.com/sebastienlorber', - twitter: 'https://twitter.com/sebastienlorber', - github: 'https://github.com/slorber', - }, - }, - { - name: 'Seb2', - socials: { - x: 'https://x.com/sebastienlorber', - twitter: 'https://twitter.com/sebastienlorber', - github: 'https://github.com/slorber', - }, - }, + {name: 'Alexey', imageURL: undefined, key: null, page: null}, ]); }); @@ -339,8 +400,8 @@ describe('getBlogPostAuthors', () => { }, authorsMap: { - yangshun: {name: 'Yangshun Tay'}, - jmarcey: {name: 'Joel Marcey'}, + yangshun: {name: 'Yangshun Tay', key: 'yangshun', page: null}, + jmarcey: {name: 'Joel Marcey', key: 'jmarcey', page: null}, }, baseUrl: '/', }), @@ -360,8 +421,8 @@ describe('getBlogPostAuthors', () => { }, authorsMap: { - yangshun: {name: 'Yangshun Tay'}, - jmarcey: {name: 'Joel Marcey'}, + yangshun: {name: 'Yangshun Tay', key: 'yangshun', page: null}, + jmarcey: {name: 'Joel Marcey', key: 'jmarcey', page: null}, }, baseUrl: '/', }), @@ -381,8 +442,8 @@ describe('getBlogPostAuthors', () => { }, authorsMap: { - yangshun: {name: 'Yangshun Tay'}, - jmarcey: {name: 'Joel Marcey'}, + yangshun: {name: 'Yangshun Tay', key: 'yangshun', page: null}, + jmarcey: {name: 'Joel Marcey', key: 'jmarcey', page: null}, }, baseUrl: '/', }), @@ -415,7 +476,9 @@ describe('getBlogPostAuthors', () => { authors: [{key: 'slorber'}], author_title: 'legacy title', }, - authorsMap: {slorber: {name: 'Sébastien Lorber'}}, + authorsMap: { + slorber: {name: 'Sébastien Lorber', key: 'slorber', page: null}, + }, baseUrl: '/', }), ).toThrowErrorMatchingInlineSnapshot(` @@ -425,241 +488,37 @@ describe('getBlogPostAuthors', () => { }); }); -describe('getAuthorsMap', () => { - const fixturesDir = path.join(__dirname, '__fixtures__/authorsMapFiles'); - const contentPaths = { - contentPathLocalized: fixturesDir, - contentPath: fixturesDir, - }; - - it('getAuthorsMap can read yml file', async () => { - await expect( - getAuthorsMap({ - contentPaths, - authorsMapPath: 'authors.yml', - }), - ).resolves.toBeDefined(); +describe('groupBlogPostsByAuthorKey', () => { + const authorsMap: AuthorsMap = fromPartial({ + ozaki: {}, + slorber: {}, + keyWithNoPost: {}, }); - it('getAuthorsMap can read json file', async () => { - await expect( - getAuthorsMap({ - contentPaths, - authorsMapPath: 'authors.json', - }), - ).resolves.toBeDefined(); - }); - - it('getAuthorsMap can return undefined if yaml file not found', async () => { - await expect( - getAuthorsMap({ - contentPaths, - authorsMapPath: 'authors_does_not_exist.yml', - }), - ).resolves.toBeUndefined(); - }); - - describe('getAuthorsMap returns normalized', () => { - it('socials', async () => { - const authorsMap = await getAuthorsMap({ - contentPaths, - authorsMapPath: 'authors.yml', - }); - expect(authorsMap.slorber.socials).toMatchInlineSnapshot(` - { - "stackoverflow": "https://stackoverflow.com/users/82609", - "twitter": "https://twitter.com/sebastienlorber", - "x": "https://x.com/sebastienlorber", - } - `); - expect(authorsMap.JMarcey.socials).toMatchInlineSnapshot(` - { - "stackoverflow": "https://stackoverflow.com/users/102705/Joel-Marcey", - "twitter": "https://twitter.com/JoelMarcey", - "x": "https://x.com/JoelMarcey", - } - `); + it('can group blog posts', () => { + const post1 = post({metadata: {authors: [{key: 'ozaki'}]}}); + const post2 = post({ + metadata: {authors: [{key: 'slorber'}, {key: 'ozaki'}]}, }); - }); -}); - -describe('validateAuthorsMap', () => { - it('accept valid authors map', () => { - const authorsMap: AuthorsMap = { - slorber: { - name: 'Sébastien Lorber', - title: 'maintainer', - url: 'https://sebastienlorber.com', - imageURL: 'https://github.com/slorber.png', - }, - yangshun: { - name: 'Yangshun Tay', - imageURL: 'https://github.com/yangshun.png', - randomField: 42, - }, - jmarcey: { - name: 'Joel', - title: 'creator of Docusaurus', - hello: new Date(), - }, - }; - expect(validateAuthorsMap(authorsMap)).toEqual(authorsMap); - }); - - it('rename snake case image_url to camelCase imageURL', () => { - const authorsMap: AuthorsMap = { - slorber: { - name: 'Sébastien Lorber', - image_url: 'https://github.com/slorber.png', - }, - }; - expect(validateAuthorsMap(authorsMap)).toEqual({ - slorber: { - name: 'Sébastien Lorber', - imageURL: 'https://github.com/slorber.png', - }, + const post3 = post({metadata: {authors: [{key: 'slorber'}]}}); + const post4 = post({ + metadata: {authors: [{name: 'Inline author 1'}, {key: 'slorber'}]}, + }); + const post5 = post({ + metadata: {authors: [{name: 'Inline author 2'}]}, + }); + const post6 = post({ + metadata: {authors: [{key: 'unknownKey'}]}, }); - }); - - it('accept author with only image', () => { - const authorsMap: AuthorsMap = { - slorber: { - imageURL: 'https://github.com/slorber.png', - url: 'https://github.com/slorber', - }, - }; - expect(validateAuthorsMap(authorsMap)).toEqual(authorsMap); - }); - - it('reject author without name or image', () => { - const authorsMap: AuthorsMap = { - slorber: { - title: 'foo', - }, - }; - expect(() => - validateAuthorsMap(authorsMap), - ).toThrowErrorMatchingInlineSnapshot( - `""slorber" must contain at least one of [name, imageURL]"`, - ); - }); - - it('reject undefined author', () => { - expect(() => - validateAuthorsMap({ - slorber: undefined, - }), - ).toThrowErrorMatchingInlineSnapshot( - `""slorber" cannot be undefined. It should be an author object containing properties like name, title, and imageURL."`, - ); - }); - - it('reject null author', () => { - expect(() => - validateAuthorsMap({ - slorber: null, - }), - ).toThrowErrorMatchingInlineSnapshot( - `""slorber" should be an author object containing properties like name, title, and imageURL."`, - ); - }); - - it('reject array author', () => { - expect(() => - validateAuthorsMap({slorber: []}), - ).toThrowErrorMatchingInlineSnapshot( - `""slorber" should be an author object containing properties like name, title, and imageURL."`, - ); - }); - - it('reject array content', () => { - expect(() => validateAuthorsMap([])).toThrowErrorMatchingInlineSnapshot( - `"The authors map file should contain an object where each entry contains an author key and the corresponding author's data."`, - ); - }); - - it('reject flat author', () => { - expect(() => - validateAuthorsMap({name: 'Sébastien'}), - ).toThrowErrorMatchingInlineSnapshot( - `""name" should be an author object containing properties like name, title, and imageURL."`, - ); - }); - - it('reject non-map author', () => { - const authorsMap: AuthorsMap = { - // @ts-expect-error: for tests - slorber: [], - }; - expect(() => - validateAuthorsMap(authorsMap), - ).toThrowErrorMatchingInlineSnapshot( - `""slorber" should be an author object containing properties like name, title, and imageURL."`, - ); - }); -}); - -describe('authors socials', () => { - it('valid known author map socials', () => { - const authorsMap: AuthorsMap = { - ozaki: { - name: 'ozaki', - socials: { - twitter: 'ozakione', - github: 'ozakione', - }, - }, - }; - - expect(validateAuthorsMap(authorsMap)).toEqual(authorsMap); - }); - - it('throw socials that are not strings', () => { - const authorsMap: AuthorsMap = { - ozaki: { - name: 'ozaki', - socials: { - // @ts-expect-error: for tests - twitter: 42, - }, - }, - }; - - expect(() => - validateAuthorsMap(authorsMap), - ).toThrowErrorMatchingInlineSnapshot( - `""ozaki.socials.twitter" must be a string"`, - ); - }); - - it('throw socials that are objects', () => { - const authorsMap: AuthorsMap = { - ozaki: { - name: 'ozaki', - socials: { - // @ts-expect-error: for tests - twitter: {link: 'ozakione'}, - }, - }, - }; - - expect(() => - validateAuthorsMap(authorsMap), - ).toThrowErrorMatchingInlineSnapshot( - `""ozaki.socials.twitter" must be a string"`, - ); - }); - it('valid unknown author map socials', () => { - const authorsMap: AuthorsMap = { - ozaki: { - name: 'ozaki', - socials: { - random: 'ozakione', - }, - }, - }; + const blogPosts = [post1, post2, post3, post4, post5, post6]; - expect(validateAuthorsMap(authorsMap)).toEqual(authorsMap); + expect(groupBlogPostsByAuthorKey({authorsMap, blogPosts})).toEqual({ + ozaki: [post1, post2], + slorber: [post2, post3, post4], + keyWithNoPost: [], + // We don't care about this edge case, it doesn't happen in practice + unknownKey: undefined, + }); }); }); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/authorsMap.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/authorsMap.test.ts new file mode 100644 index 000000000000..b6393ede6854 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/authorsMap.test.ts @@ -0,0 +1,307 @@ +/** + * 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 { + type AuthorsMapInput, + checkAuthorsMapPermalinkCollisions, + getAuthorsMap, + validateAuthorsMap, + validateAuthorsMapInput, +} from '../authorsMap'; +import type {AuthorsMap} from '@docusaurus/plugin-content-blog'; + +describe('checkAuthorsMapPermalinkCollisions', () => { + it('do not throw when permalinks are unique', () => { + const authors: AuthorsMap = { + author1: { + name: 'author1', + key: 'author1', + page: { + permalink: '/author1', + }, + }, + author2: { + name: 'author2', + key: 'author2', + page: { + permalink: '/author2', + }, + }, + }; + + expect(() => { + checkAuthorsMapPermalinkCollisions(authors); + }).not.toThrow(); + }); + + it('throw when permalinks collide', () => { + const authors: AuthorsMap = { + author1: { + name: 'author1', + key: 'author1', + page: { + permalink: '/author1', + }, + }, + author2: { + name: 'author1', + key: 'author1', + page: { + permalink: '/author1', + }, + }, + }; + + expect(() => { + checkAuthorsMapPermalinkCollisions(authors); + }).toThrowErrorMatchingInlineSnapshot(` + "The following permalinks are duplicated: + Permalink: /author1 + Authors: author1, author1" + `); + }); +}); + +describe('getAuthorsMap', () => { + const fixturesDir = path.join(__dirname, '__fixtures__/authorsMapFiles'); + const contentPaths = { + contentPathLocalized: fixturesDir, + contentPath: fixturesDir, + }; + + it('getAuthorsMap can read yml file', async () => { + await expect( + getAuthorsMap({ + contentPaths, + authorsMapPath: 'authors.yml', + authorsBaseRoutePath: '/authors', + }), + ).resolves.toBeDefined(); + }); + + it('getAuthorsMap can read json file', async () => { + await expect( + getAuthorsMap({ + contentPaths, + authorsMapPath: 'authors.json', + authorsBaseRoutePath: '/authors', + }), + ).resolves.toBeDefined(); + }); + + it('getAuthorsMap can return undefined if yaml file not found', async () => { + await expect( + getAuthorsMap({ + contentPaths, + authorsMapPath: 'authors_does_not_exist.yml', + authorsBaseRoutePath: '/authors', + }), + ).resolves.toBeUndefined(); + }); +}); + +describe('validateAuthorsMapInput', () => { + it('accept valid authors map', () => { + const authorsMap: AuthorsMapInput = { + slorber: { + name: 'Sébastien Lorber', + title: 'maintainer', + url: 'https://sebastienlorber.com', + imageURL: 'https://github.com/slorber.png', + key: 'slorber', + page: false, + }, + yangshun: { + name: 'Yangshun Tay', + imageURL: 'https://github.com/yangshun.png', + randomField: 42, + key: 'yangshun', + page: false, + }, + jmarcey: { + name: 'Joel', + title: 'creator of Docusaurus', + hello: new Date(), + key: 'jmarcey', + page: false, + }, + }; + expect(validateAuthorsMapInput(authorsMap)).toEqual(authorsMap); + }); + + it('rename snake case image_url to camelCase imageURL', () => { + const authorsMap: AuthorsMapInput = { + slorber: { + name: 'Sébastien Lorber', + image_url: 'https://github.com/slorber.png', + key: 'slorber', + page: false, + }, + }; + expect(validateAuthorsMapInput(authorsMap)).toEqual({ + slorber: { + name: 'Sébastien Lorber', + imageURL: 'https://github.com/slorber.png', + page: false, + key: 'slorber', + }, + }); + }); + + it('accept author with only image', () => { + const authorsMap: AuthorsMapInput = { + slorber: { + imageURL: 'https://github.com/slorber.png', + url: 'https://github.com/slorber', + key: 'slorber', + page: false, + }, + }; + expect(validateAuthorsMapInput(authorsMap)).toEqual(authorsMap); + }); + + it('reject author without name or image', () => { + const authorsMap: AuthorsMapInput = { + slorber: { + title: 'foo', + key: 'slorber', + page: false, + }, + }; + expect(() => + validateAuthorsMapInput(authorsMap), + ).toThrowErrorMatchingInlineSnapshot( + `""slorber" must contain at least one of [name, imageURL]"`, + ); + }); + + it('reject undefined author', () => { + expect(() => + validateAuthorsMapInput({ + slorber: undefined, + }), + ).toThrowErrorMatchingInlineSnapshot( + `""slorber" cannot be undefined. It should be an author object containing properties like name, title, and imageURL."`, + ); + }); + + it('reject null author', () => { + expect(() => + validateAuthorsMapInput({ + slorber: null, + }), + ).toThrowErrorMatchingInlineSnapshot( + `""slorber" should be an author object containing properties like name, title, and imageURL."`, + ); + }); + + it('reject array author', () => { + expect(() => + validateAuthorsMapInput({slorber: []}), + ).toThrowErrorMatchingInlineSnapshot( + `""slorber" should be an author object containing properties like name, title, and imageURL."`, + ); + }); + + it('reject array content', () => { + expect(() => + validateAuthorsMapInput([]), + ).toThrowErrorMatchingInlineSnapshot( + `"The authors map file should contain an object where each entry contains an author key and the corresponding author's data."`, + ); + }); + + it('reject flat author', () => { + expect(() => + validateAuthorsMapInput({name: 'Sébastien'}), + ).toThrowErrorMatchingInlineSnapshot( + `""name" should be an author object containing properties like name, title, and imageURL."`, + ); + }); + + it('reject non-map author', () => { + const authorsMap: AuthorsMapInput = { + // @ts-expect-error: intentionally invalid + slorber: [], + }; + expect(() => + validateAuthorsMapInput(authorsMap), + ).toThrowErrorMatchingInlineSnapshot( + `""slorber" should be an author object containing properties like name, title, and imageURL."`, + ); + }); +}); + +describe('authors socials', () => { + it('valid known author map socials', () => { + const authorsMap: AuthorsMapInput = { + ozaki: { + name: 'ozaki', + socials: { + twitter: 'ozakione', + github: 'ozakione', + }, + key: 'ozaki', + page: false, + }, + }; + + expect(validateAuthorsMap(authorsMap)).toEqual(authorsMap); + }); + + it('throw socials that are not strings', () => { + const authorsMap: AuthorsMapInput = { + ozaki: { + name: 'ozaki', + socials: { + // @ts-expect-error: for tests + twitter: 42, + }, + }, + }; + + expect(() => + validateAuthorsMap(authorsMap), + ).toThrowErrorMatchingInlineSnapshot( + `""ozaki.socials.twitter" must be a string"`, + ); + }); + + it('throw socials that are objects', () => { + const authorsMap: AuthorsMapInput = { + ozaki: { + name: 'ozaki', + socials: { + // @ts-expect-error: for tests + twitter: {link: 'ozakione'}, + }, + }, + }; + + expect(() => + validateAuthorsMap(authorsMap), + ).toThrowErrorMatchingInlineSnapshot( + `""ozaki.socials.twitter" must be a string"`, + ); + }); + + it('valid unknown author map socials', () => { + const authorsMap: AuthorsMapInput = { + ozaki: { + name: 'ozaki', + socials: { + random: 'ozakione', + }, + key: 'ozaki', + page: false, + }, + }; + + expect(validateAuthorsMap(authorsMap)).toEqual(authorsMap); + }); +}); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/authorsProblems.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/authorsProblems.test.ts index 84296df1c931..ec64b3f225a0 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/authorsProblems.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/authorsProblems.test.ts @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import {jest} from '@jest/globals'; import {reportDuplicateAuthors, reportInlineAuthors} from '../authorsProblems'; import type {Author} from '@docusaurus/plugin-content-blog'; @@ -23,9 +22,13 @@ describe('duplicate authors', () => { const authors: Author[] = [ { name: 'Sébastien Lorber', + key: null, + page: null, }, { name: 'Sébastien Lorber', + key: null, + page: null, }, ]; @@ -42,11 +45,13 @@ describe('duplicate authors', () => { key: 'slorber', name: 'Sébastien Lorber 1', title: 'some title', + page: null, }, { key: 'slorber', name: 'Sébastien Lorber 2', imageURL: '/slorber.png', + page: null, }, ]; @@ -56,7 +61,7 @@ describe('duplicate authors', () => { }), ).toThrowErrorMatchingInlineSnapshot(` "Duplicate blog post authors were found in blog post "doc.md" front matter: - - {"key":"slorber","name":"Sébastien Lorber 2","imageURL":"/slorber.png"}" + - {"key":"slorber","name":"Sébastien Lorber 2","imageURL":"/slorber.png","page":null}" `); }); }); @@ -91,10 +96,12 @@ describe('inline authors', () => { { key: 'slorber', name: 'Sébastien Lorber', + page: null, }, { key: 'ozaki', name: 'Clément Couriol', + page: null, }, ]; @@ -110,13 +117,15 @@ describe('inline authors', () => { { key: 'slorber', name: 'Sébastien Lorber', + page: null, }, - {name: 'Inline author 1'}, + {name: 'Inline author 1', page: null, key: null}, { key: 'ozaki', name: 'Clément Couriol', + page: null, }, - {imageURL: '/inline-author2.png'}, + {imageURL: '/inline-author2.png', page: null, key: null}, ]; expect(() => @@ -125,8 +134,8 @@ describe('inline authors', () => { }), ).toThrowErrorMatchingInlineSnapshot(` "Some blog authors used in "doc.md" are not defined in "authors.yml": - - {"name":"Inline author 1"} - - {"imageURL":"/inline-author2.png"} + - {"name":"Inline author 1","page":null,"key":null} + - {"imageURL":"/inline-author2.png","page":null,"key":null} Note that we recommend to declare authors once in a "authors.yml" file and reference them by key in blog posts front matter to avoid author info duplication. But if you want to allow inline blog authors, you can disable this message by setting onInlineAuthors: 'ignore' in your blog plugin options. @@ -134,45 +143,4 @@ describe('inline authors', () => { " `); }); - - it('warn inline authors', () => { - const authors: Author[] = [ - { - key: 'slorber', - name: 'Sébastien Lorber', - }, - {name: 'Inline author 1'}, - { - key: 'ozaki', - name: 'Clément Couriol', - }, - {imageURL: '/inline-author2.png'}, - ]; - - const consoleMock = jest - .spyOn(console, 'warn') - .mockImplementation(() => {}); - - expect(() => - testReport({ - authors, - options: { - onInlineAuthors: 'warn', - }, - }), - ).not.toThrow(); - expect(consoleMock).toHaveBeenCalledTimes(1); - expect(consoleMock.mock.calls[0]).toMatchInlineSnapshot(` - [ - "[WARNING] Some blog authors used in "doc.md" are not defined in "authors.yml": - - {"name":"Inline author 1"} - - {"imageURL":"/inline-author2.png"} - - Note that we recommend to declare authors once in a "authors.yml" file and reference them by key in blog posts front matter to avoid author info duplication. - But if you want to allow inline blog authors, you can disable this message by setting onInlineAuthors: 'ignore' in your blog plugin options. - More info at https://docusaurus.io/docs/blog - ", - ] - `); - }); }); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts index 5b45f13a0823..a340ec61e79e 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts @@ -54,6 +54,24 @@ describe('paginateBlogPosts', () => { ).toMatchSnapshot(); }); + it('generates pages - 0 blog post', () => { + const pages = paginateBlogPosts({ + blogPosts: [], + basePageUrl: '/blog', + blogTitle: 'Blog Title', + blogDescription: 'Blog Description', + postsPerPageOption: 2, + pageBasePath: 'page', + }); + // As part ot https://github.com/facebook/docusaurus/pull/10216 + // it was decided that authors with "page: true" that haven't written any + // blog posts yet should still have a dedicated author page + // For this purpose, we generate an empty first page + expect(pages).toHaveLength(1); + expect(pages[0]!.items).toHaveLength(0); + expect(pages).toMatchSnapshot(); + }); + it('generates pages at blog root', () => { expect( paginateBlogPosts({ diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts index 3fbd4dbf27fd..ef57b4c7d63e 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts @@ -13,6 +13,7 @@ import {fromPartial} from '@total-typescript/shoehorn'; import {DEFAULT_OPTIONS} from '../options'; import {generateBlogPosts} from '../blogUtils'; import {createBlogFeedFiles} from '../feed'; +import {getAuthorsMap} from '../authorsMap'; import type {LoadContext, I18n} from '@docusaurus/types'; import type {BlogContentPaths} from '../types'; import type {PluginOptions} from '@docusaurus/plugin-content-blog'; @@ -51,10 +52,18 @@ async function testGenerateFeeds( context: LoadContext, options: PluginOptions, ): Promise { + const contentPaths = getBlogContentPaths(context.siteDir); + const authorsMap = await getAuthorsMap({ + contentPaths, + authorsMapPath: options.authorsMapPath, + authorsBaseRoutePath: '/authors', + }); + const blogPosts = await generateBlogPosts( - getBlogContentPaths(context.siteDir), + contentPaths, context, options, + authorsMap, ); await createBlogFeedFiles({ diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index 5280a72c18c8..b5ffcb7571b6 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -220,12 +220,17 @@ describe('blog plugin', () => { authors: [ { name: 'Yangshun Tay (translated)', + imageURL: undefined, + key: null, + page: null, }, { email: 'lorber.sebastien@gmail.com', key: 'slorber', name: 'Sébastien Lorber (translated)', title: 'Docusaurus maintainer (translated)', + imageURL: undefined, + page: {permalink: '/blog/authors/slorber-custom-permalink-localized'}, }, ], date: new Date('2018-12-14'), @@ -319,6 +324,8 @@ describe('blog plugin', () => { title: 'Docusaurus maintainer', url: 'https://sebastienlorber.com', imageURL: undefined, + page: null, + key: null, }, ], prevItem: undefined, diff --git a/packages/docusaurus-plugin-content-blog/src/authors.ts b/packages/docusaurus-plugin-content-blog/src/authors.ts index 541521286e02..1fe0ef923815 100644 --- a/packages/docusaurus-plugin-content-blog/src/authors.ts +++ b/packages/docusaurus-plugin-content-blog/src/authors.ts @@ -5,83 +5,16 @@ * LICENSE file in the root directory of this source tree. */ -import * as _ from 'lodash'; -import {getDataFileData, normalizeUrl} from '@docusaurus/utils'; -import {Joi, URISchema} from '@docusaurus/utils-validation'; -import {AuthorSocialsSchema, normalizeSocials} from './authorsSocials'; -import type {BlogContentPaths} from './types'; +import _ from 'lodash'; +import {normalizeUrl} from '@docusaurus/utils'; import type { Author, + AuthorsMap, + BlogPost, BlogPostFrontMatter, BlogPostFrontMatterAuthor, - BlogPostFrontMatterAuthors, } from '@docusaurus/plugin-content-blog'; -export type AuthorsMap = {[authorKey: string]: Author}; - -const AuthorsMapSchema = Joi.object() - .pattern( - Joi.string(), - Joi.object({ - name: Joi.string(), - url: URISchema, - imageURL: URISchema, - title: Joi.string(), - email: Joi.string(), - socials: AuthorSocialsSchema, - }) - .rename('image_url', 'imageURL') - .or('name', 'imageURL') - .unknown() - .required() - .messages({ - 'object.base': - '{#label} should be an author object containing properties like name, title, and imageURL.', - 'any.required': - '{#label} cannot be undefined. It should be an author object containing properties like name, title, and imageURL.', - }), - ) - .messages({ - 'object.base': - "The authors map file should contain an object where each entry contains an author key and the corresponding author's data.", - }); - -export function validateAuthorsMap(content: unknown): AuthorsMap { - const {error, value} = AuthorsMapSchema.validate(content); - if (error) { - throw error; - } - return value; -} - -function normalizeAuthor(author: Author): Author { - return { - ...author, - socials: author.socials ? normalizeSocials(author.socials) : undefined, - }; -} - -function normalizeAuthorsMap(authorsMap: AuthorsMap): AuthorsMap { - return _.mapValues(authorsMap, normalizeAuthor); -} - -export async function getAuthorsMap(params: { - authorsMapPath: string; - contentPaths: BlogContentPaths; -}): Promise { - const authorsMap = await getDataFileData( - { - filePath: params.authorsMapPath, - contentPaths: params.contentPaths, - fileType: 'authors map', - }, - // TODO annoying to test: tightly coupled FS reads + validation... - validateAuthorsMap, - ); - - return authorsMap ? normalizeAuthorsMap(authorsMap) : undefined; -} - type AuthorsParam = { frontMatter: BlogPostFrontMatter; authorsMap: AuthorsMap | undefined; @@ -102,6 +35,7 @@ function normalizeImageUrl({ // Legacy v1/early-v2 front matter fields // We may want to deprecate those in favor of using only frontMatter.authors +// TODO Docusaurus v4: remove this legacy front matter function getFrontMatterAuthorLegacy({ baseUrl, frontMatter, @@ -123,37 +57,40 @@ function getFrontMatterAuthorLegacy({ title, url, imageURL, + // legacy front matter authors do not have an author key/page + key: null, + page: null, }; } return undefined; } -function normalizeFrontMatterAuthors( - frontMatterAuthors: BlogPostFrontMatterAuthors = [], -): BlogPostFrontMatterAuthor[] { - function normalizeFrontMatterAuthor( - authorInput: string | Author, - ): BlogPostFrontMatterAuthor { - if (typeof authorInput === 'string') { - // Technically, we could allow users to provide an author's name here, but - // we only support keys, otherwise, a typo in a key would fallback to - // becoming a name and may end up unnoticed - return {key: authorInput}; +function getFrontMatterAuthors(params: AuthorsParam): Author[] { + const {authorsMap, frontMatter, baseUrl} = params; + return normalizeFrontMatterAuthors().map(toAuthor); + + function normalizeFrontMatterAuthors(): BlogPostFrontMatterAuthor[] { + if (frontMatter.authors === undefined) { + return []; } - return authorInput; - } - return Array.isArray(frontMatterAuthors) - ? frontMatterAuthors.map(normalizeFrontMatterAuthor) - : [normalizeFrontMatterAuthor(frontMatterAuthors)]; -} + function normalizeAuthor( + authorInput: string | BlogPostFrontMatterAuthor, + ): BlogPostFrontMatterAuthor { + if (typeof authorInput === 'string') { + // We could allow users to provide an author's name here, but we only + // support keys, otherwise, a typo in a key would fall back to + // becoming a name and may end up unnoticed + return {key: authorInput}; + } + return authorInput; + } -function getFrontMatterAuthors(params: AuthorsParam): Author[] { - const {authorsMap} = params; - const frontMatterAuthors = normalizeFrontMatterAuthors( - params.frontMatter.authors, - ); + return Array.isArray(frontMatter.authors) + ? frontMatter.authors.map(normalizeAuthor) + : [normalizeAuthor(frontMatter.authors)]; + } function getAuthorsMapAuthor(key: string | undefined): Author | undefined { if (key) { @@ -175,36 +112,29 @@ ${Object.keys(authorsMap) } function toAuthor(frontMatterAuthor: BlogPostFrontMatterAuthor): Author { - return normalizeAuthor({ + const author = { // Author def from authorsMap can be locally overridden by front matter ...getAuthorsMapAuthor(frontMatterAuthor.key), ...frontMatterAuthor, - }); - } - - return frontMatterAuthors.map(toAuthor); -} + }; -function fixAuthorImageBaseURL( - authors: Author[], - {baseUrl}: {baseUrl: string}, -) { - return authors.map((author) => ({ - ...author, - imageURL: normalizeImageUrl({imageURL: author.imageURL, baseUrl}), - })); + return { + ...author, + key: author.key ?? null, + page: author.page ?? null, + imageURL: normalizeImageUrl({imageURL: author.imageURL, baseUrl}), + }; + } } export function getBlogPostAuthors(params: AuthorsParam): Author[] { const authorLegacy = getFrontMatterAuthorLegacy(params); const authors = getFrontMatterAuthors(params); - const updatedAuthors = fixAuthorImageBaseURL(authors, params); - if (authorLegacy) { // Technically, we could allow mixing legacy/authors front matter, but do we // really want to? - if (updatedAuthors.length > 0) { + if (authors.length > 0) { throw new Error( `To declare blog post authors, use the 'authors' front matter in priority. Don't mix 'authors' with other existing 'author_*' front matter. Choose one or the other, not both at the same time.`, @@ -213,5 +143,21 @@ Don't mix 'authors' with other existing 'author_*' front matter. Choose one or t return [authorLegacy]; } - return updatedAuthors; + return authors; +} + +/** + * Group blog posts by author key + * Blog posts with only inline authors are ignored + */ +export function groupBlogPostsByAuthorKey({ + blogPosts, + authorsMap, +}: { + blogPosts: BlogPost[]; + authorsMap: AuthorsMap | undefined; +}): Record { + return _.mapValues(authorsMap, (author, key) => + blogPosts.filter((p) => p.metadata.authors.some((a) => a.key === key)), + ); } diff --git a/packages/docusaurus-plugin-content-blog/src/authorsMap.ts b/packages/docusaurus-plugin-content-blog/src/authorsMap.ts new file mode 100644 index 000000000000..d1378aa3f26d --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/authorsMap.ts @@ -0,0 +1,171 @@ +/** + * 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 _ from 'lodash'; +import {readDataFile, normalizeUrl} from '@docusaurus/utils'; +import {Joi, URISchema} from '@docusaurus/utils-validation'; +import {AuthorSocialsSchema, normalizeSocials} from './authorsSocials'; +import type {BlogContentPaths} from './types'; +import type { + Author, + AuthorAttributes, + AuthorPage, + AuthorsMap, +} from '@docusaurus/plugin-content-blog'; + +type AuthorInput = AuthorAttributes & { + page?: boolean | AuthorPage; +}; + +export type AuthorsMapInput = {[authorKey: string]: AuthorInput}; + +const AuthorPageSchema = Joi.object({ + permalink: Joi.string().required(), +}); + +const AuthorsMapInputSchema = Joi.object() + .pattern( + Joi.string(), + Joi.object({ + name: Joi.string(), + url: URISchema, + imageURL: URISchema, + title: Joi.string(), + email: Joi.string(), + page: Joi.alternatives(Joi.bool(), AuthorPageSchema), + socials: AuthorSocialsSchema, + description: Joi.string(), + }) + .rename('image_url', 'imageURL') + .or('name', 'imageURL') + .unknown() + .required() + .messages({ + 'object.base': + '{#label} should be an author object containing properties like name, title, and imageURL.', + 'any.required': + '{#label} cannot be undefined. It should be an author object containing properties like name, title, and imageURL.', + }), + ) + .messages({ + 'object.base': + "The authors map file should contain an object where each entry contains an author key and the corresponding author's data.", + }); + +export function checkAuthorsMapPermalinkCollisions( + authorsMap: AuthorsMap | undefined, +): void { + if (!authorsMap) { + return; + } + + const permalinkCounts = _(authorsMap) + // Filter to keep only authors with a page + .pickBy((author) => !!author.page) + // Group authors by their permalink + .groupBy((author) => author.page?.permalink) + // Filter to keep only permalinks with more than one author + .pickBy((authors) => authors.length > 1) + // Transform the object into an array of [permalink, authors] pairs + .toPairs() + .value(); + + if (permalinkCounts.length > 0) { + const errorMessage = permalinkCounts + .map( + ([permalink, authors]) => + `Permalink: ${permalink}\nAuthors: ${authors + .map((author) => author.name || 'Unknown') + .join(', ')}`, + ) + .join('\n'); + + throw new Error( + `The following permalinks are duplicated:\n${errorMessage}`, + ); + } +} + +function normalizeAuthor({ + authorsBaseRoutePath, + authorKey, + author, +}: { + authorsBaseRoutePath: string; + authorKey: string; + author: AuthorInput; +}): Author & {key: string} { + function getAuthorPage(): AuthorPage | null { + if (!author.page) { + return null; + } + const slug = + author.page === true ? _.kebabCase(authorKey) : author.page.permalink; + return { + permalink: normalizeUrl([authorsBaseRoutePath, slug]), + }; + } + + return { + ...author, + key: authorKey, + page: getAuthorPage(), + socials: author.socials ? normalizeSocials(author.socials) : undefined, + }; +} + +function normalizeAuthorsMap({ + authorsBaseRoutePath, + authorsMapInput, +}: { + authorsBaseRoutePath: string; + authorsMapInput: AuthorsMapInput; +}): AuthorsMap { + return _.mapValues(authorsMapInput, (author, authorKey) => { + return normalizeAuthor({authorsBaseRoutePath, authorKey, author}); + }); +} + +export function validateAuthorsMapInput(content: unknown): AuthorsMapInput { + const {error, value} = AuthorsMapInputSchema.validate(content); + if (error) { + throw error; + } + return value; +} + +async function getAuthorsMapInput(params: { + authorsMapPath: string; + contentPaths: BlogContentPaths; +}): Promise { + const content = await readDataFile({ + filePath: params.authorsMapPath, + contentPaths: params.contentPaths, + }); + return content ? validateAuthorsMapInput(content) : undefined; +} + +export async function getAuthorsMap(params: { + authorsMapPath: string; + authorsBaseRoutePath: string; + contentPaths: BlogContentPaths; +}): Promise { + const authorsMapInput = await getAuthorsMapInput(params); + if (!authorsMapInput) { + return undefined; + } + const authorsMap = normalizeAuthorsMap({authorsMapInput, ...params}); + return authorsMap; +} + +export function validateAuthorsMap(content: unknown): AuthorsMapInput { + const {error, value} = AuthorsMapInputSchema.validate(content); + if (error) { + throw error; + } + return value; +} diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index 68f429cc8c0c..d26d319c0f1b 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -29,11 +29,12 @@ import { } from '@docusaurus/utils'; import {getTagsFile} from '@docusaurus/utils-validation'; import {validateBlogPostFrontMatter} from './frontMatter'; -import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors'; +import {getBlogPostAuthors} from './authors'; import {reportAuthorsProblems} from './authorsProblems'; import type {TagsFile} from '@docusaurus/utils'; import type {LoadContext, ParseFrontMatter} from '@docusaurus/types'; import type { + AuthorsMap, PluginOptions, ReadingTimeFunction, BlogPost, @@ -64,7 +65,7 @@ export function paginateBlogPosts({ const totalCount = blogPosts.length; const postsPerPage = postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption; - const numberOfPages = Math.ceil(totalCount / postsPerPage); + const numberOfPages = Math.max(1, Math.ceil(totalCount / postsPerPage)); const pages: BlogPaginated[] = []; @@ -366,6 +367,7 @@ export async function generateBlogPosts( contentPaths: BlogContentPaths, context: LoadContext, options: PluginOptions, + authorsMap?: AuthorsMap, ): Promise { const {include, exclude} = options; @@ -378,11 +380,6 @@ export async function generateBlogPosts( ignore: exclude, }); - const authorsMap = await getAuthorsMap({ - contentPaths, - authorsMapPath: options.authorsMapPath, - }); - const tagsFile = await getTagsFile({contentPaths, tags: options.tags}); async function doProcessBlogSourceFile(blogSourceFile: string) { diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts index 2792e9015e15..679924729e5f 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.ts +++ b/packages/docusaurus-plugin-content-blog/src/index.ts @@ -34,6 +34,7 @@ import {translateContent, getTranslationFiles} from './translations'; import {createBlogFeedFiles, createFeedHtmlHeadTags} from './feed'; import {createAllRoutes} from './routes'; +import {checkAuthorsMapPermalinkCollisions, getAuthorsMap} from './authorsMap'; import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types'; import type {LoadContext, Plugin} from '@docusaurus/types'; import type { @@ -160,11 +161,30 @@ export default async function pluginContentBlog( blogTitle, blogSidebarTitle, pageBasePath, + authorsBasePath, + authorsMapPath, } = options; const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]); const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]); - let blogPosts = await generateBlogPosts(contentPaths, context, options); + + const authorsMap = await getAuthorsMap({ + contentPaths, + authorsMapPath, + authorsBaseRoutePath: normalizeUrl([ + baseUrl, + routeBasePath, + authorsBasePath, + ]), + }); + checkAuthorsMapPermalinkCollisions(authorsMap); + + let blogPosts = await generateBlogPosts( + contentPaths, + context, + options, + authorsMap, + ); blogPosts = await applyProcessBlogPosts({ blogPosts, processBlogPosts: options.processBlogPosts, @@ -178,6 +198,7 @@ export default async function pluginContentBlog( blogListPaginated: [], blogTags: {}, blogTagsListPath, + authorsMap, }; } @@ -226,6 +247,7 @@ export default async function pluginContentBlog( blogListPaginated, blogTags, blogTagsListPath, + authorsMap, }; }, diff --git a/packages/docusaurus-plugin-content-blog/src/options.ts b/packages/docusaurus-plugin-content-blog/src/options.ts index 68660ffa2b6f..20e0c3427948 100644 --- a/packages/docusaurus-plugin-content-blog/src/options.ts +++ b/packages/docusaurus-plugin-content-blog/src/options.ts @@ -34,6 +34,8 @@ export const DEFAULT_OPTIONS: PluginOptions = { showReadingTime: true, blogTagsPostsComponent: '@theme/BlogTagsPostsPage', blogTagsListComponent: '@theme/BlogTagsListPage', + blogAuthorsPostsComponent: '@theme/Blog/Pages/BlogAuthorsPostsPage', + blogAuthorsListComponent: '@theme/Blog/Pages/BlogAuthorsListPage', blogPostComponent: '@theme/BlogPostPage', blogListComponent: '@theme/BlogListPage', blogArchiveComponent: '@theme/BlogArchivePage', @@ -58,6 +60,7 @@ export const DEFAULT_OPTIONS: PluginOptions = { processBlogPosts: async () => undefined, onInlineTags: 'warn', tags: undefined, + authorsBasePath: 'authors', onInlineAuthors: 'warn', }; @@ -82,6 +85,12 @@ const PluginOptionSchema = Joi.object({ blogTagsPostsComponent: Joi.string().default( DEFAULT_OPTIONS.blogTagsPostsComponent, ), + blogAuthorsPostsComponent: Joi.string().default( + DEFAULT_OPTIONS.blogAuthorsPostsComponent, + ), + blogAuthorsListComponent: Joi.string().default( + DEFAULT_OPTIONS.blogAuthorsListComponent, + ), blogArchiveComponent: Joi.string().default( DEFAULT_OPTIONS.blogArchiveComponent, ), @@ -157,6 +166,9 @@ const PluginOptionSchema = Joi.object({ .disallow('') .allow(null, false) .default(() => DEFAULT_OPTIONS.tags), + authorsBasePath: Joi.string() + .default(DEFAULT_OPTIONS.authorsBasePath) + .disallow(''), onInlineAuthors: Joi.string() .equal('ignore', 'log', 'warn', 'throw') .default(DEFAULT_OPTIONS.onInlineAuthors), diff --git a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts index 589a5e93b169..e0e5617ce792 100644 --- a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts +++ b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts @@ -22,13 +22,7 @@ declare module '@docusaurus/plugin-content-blog' { export type Assets = { /** - * If `metadata.yarn workspace website typecheck -4 -yarn workspace v1.22.19yarn workspace website typecheck -4 -yarn workspace v1.22.19yarn workspace website typecheck -4 -yarn workspace v1.22.19image` is a collocated image path, this entry will be the + * If `metadata.image` is a collocated image path, this entry will be the * bundler-generated image path. Otherwise, it's empty, and the image URL * should be accessed through `frontMatter.image`. */ @@ -66,9 +60,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the [customAuthorSocialPlatform: string]: string; }; - export type Author = { - key?: string; // TODO temporary, need refactor - + export type AuthorAttributes = { /** * If `name` doesn't exist, an `imageURL` is expected. */ @@ -98,11 +90,45 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the */ socials?: AuthorSocials; /** - * Unknown keys are allowed, so that we can pass custom fields to authors, + * Description of the author. + */ + description?: string; + /** + * Unknown keys are allowed, so that we can pass custom fields to authors. */ [customAuthorAttribute: string]: unknown; }; + /** + * Metadata of the author's page, if it exists. + */ + export type AuthorPage = {permalink: string}; + + /** + * Normalized author metadata. + */ + export type Author = AuthorAttributes & { + /** + * Author key, if the author was loaded from the authors map. + * `null` means the author was declared inline. + */ + key: string | null; + /** + * Metadata of the author's page. + * `null` means the author doesn't have a dedicated author page. + */ + page: AuthorPage | null; + }; + + /** Authors coming from the AuthorsMap always have a key */ + export type AuthorWithKey = Author & {key: string}; + + /** What the authors list page should know about each author. */ + export type AuthorItemProp = AuthorWithKey & { + /** Number of blog posts with this author. */ + count: number; + }; + /** * Everything is partial/unnormalized, because front matter is always * preserved as-is. Default values will be applied when generating metadata @@ -194,7 +220,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the last_update?: FrontMatterLastUpdate; }; - export type BlogPostFrontMatterAuthor = Author & { + export type BlogPostFrontMatterAuthor = AuthorAttributes & { /** * Will be normalized into the `imageURL` prop. */ @@ -427,6 +453,10 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the blogTagsListComponent: string; /** Root component of the "posts containing tag" page. */ blogTagsPostsComponent: string; + /** Root component of the authors list page. */ + blogAuthorsListComponent: string; + /** Root component of the "posts containing author" page. */ + blogAuthorsPostsComponent: string; /** Root component of the blog archive page. */ blogArchiveComponent: string; /** Blog page title for better SEO. */ @@ -471,6 +501,8 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the * (filter, modify, delete, etc...). */ processBlogPosts: ProcessBlogPostsFn; + /* Base path for the authors page */ + authorsBasePath: string; /** The behavior of Docusaurus when it finds inline authors. */ onInlineAuthors: 'ignore' | 'log' | 'warn' | 'throw'; }; @@ -508,17 +540,22 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the items: BlogSidebarItem[]; }; + export type AuthorsMap = {[authorKey: string]: AuthorWithKey}; + export type BlogContent = { blogSidebarTitle: string; blogPosts: BlogPost[]; blogListPaginated: BlogPaginated[]; blogTags: BlogTags; blogTagsListPath: string; + authorsMap?: AuthorsMap; }; export type BlogMetadata = { /** the path to the base of the blog */ blogBasePath: string; + /** the path to the authors list page */ + authorsListPath: string; /** title of the overall blog */ blogTitle: string; }; @@ -679,6 +716,47 @@ declare module '@theme/BlogTagsListPage' { export default function BlogTagsListPage(props: Props): JSX.Element; } +declare module '@theme/Blog/Pages/BlogAuthorsListPage' { + import type { + AuthorItemProp, + BlogSidebar, + } from '@docusaurus/plugin-content-blog'; + + export interface Props { + /** Blog sidebar. */ + readonly sidebar: BlogSidebar; + /** All authors declared in this blog. */ + readonly authors: AuthorItemProp[]; + } + + export default function BlogAuthorsListPage(props: Props): JSX.Element; +} + +declare module '@theme/Blog/Pages/BlogAuthorsPostsPage' { + import type {Content} from '@theme/BlogPostPage'; + import type { + AuthorItemProp, + BlogSidebar, + BlogPaginatedMetadata, + } from '@docusaurus/plugin-content-blog'; + + export interface Props { + /** Blog sidebar. */ + readonly sidebar: BlogSidebar; + /** Metadata of this author. */ + readonly author: AuthorItemProp; + /** Looks exactly the same as the posts list page */ + readonly listMetadata: BlogPaginatedMetadata; + /** + * Array of blog posts included on this page. Every post's metadata is also + * available. + */ + readonly items: readonly {readonly content: Content}[]; + } + + export default function BlogAuthorsPostsPage(props: Props): JSX.Element; +} + declare module '@theme/BlogTagsPostsPage' { import type {Content} from '@theme/BlogPostPage'; import type { diff --git a/packages/docusaurus-plugin-content-blog/src/props.ts b/packages/docusaurus-plugin-content-blog/src/props.ts index b4d4ddf78c03..df7afbb42563 100644 --- a/packages/docusaurus-plugin-content-blog/src/props.ts +++ b/packages/docusaurus-plugin-content-blog/src/props.ts @@ -6,6 +6,8 @@ */ import type {TagsListItem, TagModule} from '@docusaurus/utils'; import type { + AuthorItemProp, + AuthorWithKey, BlogPost, BlogSidebar, BlogTag, @@ -40,6 +42,19 @@ export function toTagProp({ }; } +export function toAuthorItemProp({ + author, + count, +}: { + author: AuthorWithKey; + count: number; +}): AuthorItemProp { + return { + ...author, + count, + }; +} + export function toBlogSidebarProp({ blogSidebarTitle, blogPosts, diff --git a/packages/docusaurus-plugin-content-blog/src/routes.ts b/packages/docusaurus-plugin-content-blog/src/routes.ts index fef334b5cc14..ced92dc0fda9 100644 --- a/packages/docusaurus-plugin-content-blog/src/routes.ts +++ b/packages/docusaurus-plugin-content-blog/src/routes.ts @@ -11,9 +11,15 @@ import { docuHash, aliasedSitePathToRelativePath, } from '@docusaurus/utils'; -import {shouldBeListed} from './blogUtils'; +import {paginateBlogPosts, shouldBeListed} from './blogUtils'; -import {toBlogSidebarProp, toTagProp, toTagsProp} from './props'; +import { + toAuthorItemProp, + toBlogSidebarProp, + toTagProp, + toTagsProp, +} from './props'; +import {groupBlogPostsByAuthorKey} from './authors'; import type { PluginContentLoadedActions, RouteConfig, @@ -26,6 +32,7 @@ import type { BlogContent, PluginOptions, BlogPost, + AuthorWithKey, } from '@docusaurus/plugin-content-blog'; type CreateAllRoutesParam = { @@ -54,11 +61,16 @@ export async function buildAllRoutes({ blogListComponent, blogPostComponent, blogTagsListComponent, + blogAuthorsListComponent, + blogAuthorsPostsComponent, blogTagsPostsComponent, blogArchiveComponent, routeBasePath, archiveBasePath, blogTitle, + authorsBasePath, + postsPerPage, + blogDescription, } = options; const pluginId = options.id!; const {createData} = actions; @@ -68,8 +80,15 @@ export async function buildAllRoutes({ blogListPaginated, blogTags, blogTagsListPath, + authorsMap, } = content; + const authorsListPath = normalizeUrl([ + baseUrl, + routeBasePath, + authorsBasePath, + ]); + const listedBlogPosts = blogPosts.filter(shouldBeListed); const blogPostsById = _.keyBy(blogPosts, (post) => post.id); @@ -102,6 +121,7 @@ export async function buildAllRoutes({ const blogMetadata: BlogMetadata = { blogBasePath: normalizeUrl([baseUrl, routeBasePath]), blogTitle, + authorsListPath, }; const modulePath = await createData( `blogMetadata-${pluginId}.json`, @@ -249,10 +269,85 @@ export async function buildAllRoutes({ return [tagsListRoute, ...tagsPaginatedRoutes]; } + function createAuthorsRoutes(): RouteConfig[] { + if (authorsMap === undefined || Object.keys(authorsMap).length === 0) { + return []; + } + + const blogPostsByAuthorKey = groupBlogPostsByAuthorKey({ + authorsMap, + blogPosts, + }); + const authors = Object.values(authorsMap); + + return [ + createAuthorListRoute(), + ...authors.flatMap(createAuthorPaginatedRoute), + ]; + + function createAuthorListRoute(): RouteConfig { + return { + path: authorsListPath, + component: blogAuthorsListComponent, + exact: true, + modules: { + sidebar: sidebarModulePath, + }, + props: { + authors: authors.map((author) => + toAuthorItemProp({ + author, + count: blogPostsByAuthorKey[author.key]?.length ?? 0, + }), + ), + }, + context: { + blogMetadata: blogMetadataModulePath, + }, + }; + } + + function createAuthorPaginatedRoute(author: AuthorWithKey): RouteConfig[] { + const authorBlogPosts = blogPostsByAuthorKey[author.key] ?? []; + if (!author.page) { + return []; + } + + const pages = paginateBlogPosts({ + blogPosts: authorBlogPosts, + basePageUrl: author.page.permalink, + blogDescription, + blogTitle, + pageBasePath: authorsBasePath, + postsPerPageOption: postsPerPage, + }); + + return pages.map(({metadata, items}) => { + return { + path: metadata.permalink, + component: blogAuthorsPostsComponent, + exact: true, + modules: { + items: blogPostItemsModule(items), + sidebar: sidebarModulePath, + }, + props: { + author: toAuthorItemProp({author, count: authorBlogPosts.length}), + listMetadata: metadata, + }, + context: { + blogMetadata: blogMetadataModulePath, + }, + }; + }); + } + } + return [ ...createBlogPostRoutes(), ...createBlogPostsPaginatedRoutes(), ...createTagsRoutes(), ...createArchiveRoute(), + ...createAuthorsRoutes(), ]; } diff --git a/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts b/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts index 1b0a871197e2..60363ec4849e 100644 --- a/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts +++ b/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts @@ -127,6 +127,27 @@ export default function getSwizzleConfig(): SwizzleConfig { description: 'The object mapping admonition type to a React component.\nUse it to add custom admonition type components, or replace existing ones.\nCan be ejected or wrapped (only manually, see our documentation).', }, + Blog: { + actions: { + // Forbidden because it's a parent folder, makes the CLI crash atm + eject: 'forbidden', + wrap: 'forbidden', + }, + }, + 'Blog/Components': { + actions: { + // Forbidden because it's a parent folder, makes the CLI crash atm + eject: 'forbidden', + wrap: 'forbidden', + }, + }, + 'Blog/Pages': { + actions: { + // Forbidden because it's a parent folder, makes the CLI crash atm + eject: 'forbidden', + wrap: 'forbidden', + }, + }, CodeBlock: { actions: { eject: 'safe', diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 142a229862f9..c35e7615af26 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -185,6 +185,30 @@ declare module '@theme/BackToTopButton' { export default function BackToTopButton(): JSX.Element; } +declare module '@theme/Blog/Components/Author' { + import type {Author} from '@docusaurus/plugin-content-blog'; + + export interface Props { + readonly as?: 'h1' | 'h2'; + readonly author: Author; + readonly className?: string; + readonly count?: number; + } + + export default function BlogAuthor(props: Props): JSX.Element; +} + +declare module '@theme/Blog/Components/Author/Socials' { + import type {Author} from '@docusaurus/plugin-content-blog'; + + export interface Props { + readonly author: Author; + readonly className?: string; + } + + export default function BlogAuthorSocials(props: Props): JSX.Element; +} + declare module '@theme/BlogListPaginator' { import type {BlogPaginatedMetadata} from '@docusaurus/plugin-content-blog'; @@ -291,31 +315,6 @@ declare module '@theme/BlogPostItem/Header/Info' { export default function BlogPostItemHeaderInfo(): JSX.Element; } -declare module '@theme/BlogPostItem/Header/Author' { - import type {Author} from '@docusaurus/plugin-content-blog'; - - export interface Props { - readonly author: Author; - readonly singleAuthor: boolean; - readonly className?: string; - } - - export default function BlogPostItemHeaderAuthor(props: Props): JSX.Element; -} - -declare module '@theme/BlogPostItem/Header/Author/Socials' { - import type {Author} from '@docusaurus/plugin-content-blog'; - - export interface Props { - readonly author: Author; - readonly className?: string; - } - - export default function BlogPostItemHeaderAuthorSocials( - props: Props, - ): JSX.Element; -} - declare module '@theme/BlogPostItem/Header/Authors' { export interface Props { readonly className?: string; diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/Socials/index.tsx b/packages/docusaurus-theme-classic/src/theme/Blog/Components/Author/Socials/index.tsx similarity index 86% rename from packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/Socials/index.tsx rename to packages/docusaurus-theme-classic/src/theme/Blog/Components/Author/Socials/index.tsx index 55866b14f2c2..59bdca629b16 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/Socials/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Blog/Components/Author/Socials/index.tsx @@ -9,7 +9,7 @@ import type {ComponentType} from 'react'; import React from 'react'; import clsx from 'clsx'; import Link from '@docusaurus/Link'; -import type {Props} from '@theme/BlogPostItem/Header/Author/Socials'; +import type {Props} from '@theme/Blog/Components/Author/Socials'; import Twitter from '@theme/Icon/Socials/Twitter'; import GitHub from '@theme/Icon/Socials/GitHub'; @@ -50,10 +50,15 @@ function SocialLink({platform, link}: {platform: string; link: string}) { ); } -export default function AuthorSocials({author}: {author: Props['author']}) { +export default function BlogAuthorSocials({ + author, +}: { + author: Props['author']; +}): JSX.Element { + const entries = Object.entries(author.socials ?? {}); return (
- {Object.entries(author.socials ?? {}).map(([platform, linkUrl]) => { + {entries.map(([platform, linkUrl]) => { return ; })}
diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/Socials/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Blog/Components/Author/Socials/styles.module.css similarity index 75% rename from packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/Socials/styles.module.css rename to packages/docusaurus-theme-classic/src/theme/Blog/Components/Author/Socials/styles.module.css index 1fca8b7e385e..7c1ffc07365a 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/Socials/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/Blog/Components/Author/Socials/styles.module.css @@ -10,7 +10,12 @@ } .authorSocials { - margin-top: 0.2rem; + /* + This ensures that container takes height even if there's no social link + This keeps author names aligned even if only some have socials + */ + height: var(--docusaurus-blog-social-icon-size); + display: flex; flex-wrap: wrap; align-items: center; @@ -25,7 +30,7 @@ height: var(--docusaurus-blog-social-icon-size); width: var(--docusaurus-blog-social-icon-size); line-height: 0; - margin-right: 0.3rem; + margin-right: 0.4rem; } .authorSocialIcon { diff --git a/packages/docusaurus-theme-classic/src/theme/Blog/Components/Author/index.tsx b/packages/docusaurus-theme-classic/src/theme/Blog/Components/Author/index.tsx new file mode 100644 index 000000000000..5861b3da8091 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Blog/Components/Author/index.tsx @@ -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. + */ + +import React from 'react'; +import clsx from 'clsx'; +import Link, {type Props as LinkProps} from '@docusaurus/Link'; +import AuthorSocials from '@theme/Blog/Components/Author/Socials'; +import type {Props} from '@theme/Blog/Components/Author'; +import Heading from '@theme/Heading'; +import styles from './styles.module.css'; + +function MaybeLink(props: LinkProps): JSX.Element { + if (props.href) { + return ; + } + return <>{props.children}; +} + +function AuthorTitle({title}: {title: string}) { + return ( + + {title} + + ); +} + +function AuthorName({name, as}: {name: string; as: Props['as']}) { + if (!as) { + return {name}; + } else { + return ( + + {name} + + ); + } +} + +function AuthorBlogPostCount({count}: {count: number}) { + return {count}; +} + +// Note: in the future we might want to have multiple "BlogAuthor" components +// Creating different display modes with the "as" prop may not be the best idea +// Explainer: https://kyleshevlin.com/prefer-multiple-compositions/ +// For now, we almost use the same design for all cases, so it's good enough +export default function BlogAuthor({ + as, + author, + className, + count, +}: Props): JSX.Element { + const {name, title, url, imageURL, email, page} = author; + const link = + page?.permalink || url || (email && `mailto:${email}`) || undefined; + + return ( +
+ {imageURL && ( + + {name} + + )} + + {(name || title) && ( +
+
+ {name && ( + + + + )} + {count && } +
+ {!!title && } + + {/* + We always render AuthorSocials even if there's none + This keeps other things aligned with flexbox layout + */} + +
+ )} +
+ ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Blog/Components/Author/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Blog/Components/Author/styles.module.css new file mode 100644 index 000000000000..43feb0f2883e --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Blog/Components/Author/styles.module.css @@ -0,0 +1,74 @@ +/** + * 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. + */ + +.authorImage { + --ifm-avatar-photo-size: 3.6rem; +} + +.author-as-h1 .authorImage { + --ifm-avatar-photo-size: 7rem; +} + +.author-as-h2 .authorImage { + --ifm-avatar-photo-size: 5.4rem; +} + +.authorDetails { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-around; +} + +.authorName { + font-size: 1.1rem; + line-height: 1.1rem; + display: flex; + flex-direction: row; +} + +.author-as-h1 .authorName { + font-size: 2.4rem; + line-height: 2.4rem; + display: inline; +} + +.author-as-h2 .authorName { + font-size: 1.4rem; + line-height: 1.4rem; + display: inline; +} + +.authorTitle { + font-size: 0.8rem; + line-height: 0.8rem; + display: -webkit-box; + overflow: hidden; + line-clamp: 1; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; +} + +.author-as-h1 .authorTitle { + font-size: 1.2rem; + line-height: 1.2rem; +} + +.author-as-h2 .authorTitle { + font-size: 1rem; + line-height: 1rem; +} + +.authorBlogPostCount { + background: var(--ifm-color-secondary); + color: var(--ifm-color-black); + font-size: 0.8rem; + line-height: 1.2; + border-radius: var(--ifm-global-radius); + padding: 0.1rem 0.4rem; + margin-left: 0.3rem; +} diff --git a/packages/docusaurus-theme-classic/src/theme/Blog/Pages/BlogAuthorsListPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/Blog/Pages/BlogAuthorsListPage/index.tsx new file mode 100644 index 000000000000..71d60a0402a1 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Blog/Pages/BlogAuthorsListPage/index.tsx @@ -0,0 +1,62 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import { + PageMetadata, + HtmlClassNameProvider, + ThemeClassNames, +} from '@docusaurus/theme-common'; +import {translateBlogAuthorsListPageTitle} from '@docusaurus/theme-common/internal'; +import BlogLayout from '@theme/BlogLayout'; +import type {Props} from '@theme/Blog/Pages/BlogAuthorsListPage'; +import SearchMetadata from '@theme/SearchMetadata'; +import Heading from '@theme/Heading'; +import Author from '@theme/Blog/Components/Author'; +import type {AuthorItemProp} from '@docusaurus/plugin-content-blog'; +import styles from './styles.module.css'; + +function AuthorListItem({author}: {author: AuthorItemProp}) { + return ( +
  • + +
  • + ); +} + +function AuthorsList({authors}: {authors: Props['authors']}) { + return ( +
    +
      + {authors.map((author) => ( + + ))} +
    +
    + ); +} + +export default function BlogAuthorsListPage({ + authors, + sidebar, +}: Props): ReactNode { + const title: string = translateBlogAuthorsListPageTitle(); + return ( + + + + + {title} + + + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Blog/Pages/BlogAuthorsListPage/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Blog/Pages/BlogAuthorsListPage/styles.module.css new file mode 100644 index 000000000000..5c0d7455ceb9 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Blog/Pages/BlogAuthorsListPage/styles.module.css @@ -0,0 +1,11 @@ +/** + * 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. + */ + +.authorListItem { + list-style-type: none; + margin-bottom: 2rem; +} diff --git a/packages/docusaurus-theme-classic/src/theme/Blog/Pages/BlogAuthorsPostsPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/Blog/Pages/BlogAuthorsPostsPage/index.tsx new file mode 100644 index 000000000000..1d49c35b04a5 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Blog/Pages/BlogAuthorsPostsPage/index.tsx @@ -0,0 +1,73 @@ +/** + * 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 { + PageMetadata, + HtmlClassNameProvider, + ThemeClassNames, +} from '@docusaurus/theme-common'; +import { + useBlogAuthorPageTitle, + BlogAuthorsListViewAllLabel, +} from '@docusaurus/theme-common/internal'; +import Link from '@docusaurus/Link'; +import {useBlogMetadata} from '@docusaurus/plugin-content-blog/client'; +import BlogLayout from '@theme/BlogLayout'; +import BlogListPaginator from '@theme/BlogListPaginator'; +import SearchMetadata from '@theme/SearchMetadata'; +import type {Props} from '@theme/Blog/Pages/BlogAuthorsPostsPage'; +import BlogPostItems from '@theme/BlogPostItems'; +import Author from '@theme/Blog/Components/Author'; + +function Metadata({author}: Props): JSX.Element { + const title = useBlogAuthorPageTitle(author); + return ( + <> + + + + ); +} + +function ViewAllAuthorsLink() { + const {authorsListPath} = useBlogMetadata(); + return ( + + + + ); +} + +function Content({author, items, sidebar, listMetadata}: Props): JSX.Element { + return ( + +
    + + {author.description &&

    {author.description}

    } + +
    +
    + + +
    + ); +} + +export default function BlogAuthorsPostsPage(props: Props): JSX.Element { + return ( + + + + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/index.tsx deleted file mode 100644 index 0e81c7a42675..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/index.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from 'react'; -import clsx from 'clsx'; -import Link, {type Props as LinkProps} from '@docusaurus/Link'; -import AuthorSocials from '@theme/BlogPostItem/Header/Author/Socials'; - -import type {Props} from '@theme/BlogPostItem/Header/Author'; -import styles from './styles.module.css'; - -function MaybeLink(props: LinkProps): JSX.Element { - if (props.href) { - return ; - } - return <>{props.children}; -} - -function AuthorTitle({title}: {title: string}) { - return ( - - {title} - - ); -} - -export default function BlogPostItemHeaderAuthor({ - // singleAuthor, // may be useful in the future, or for swizzle users - author, - className, -}: Props): JSX.Element { - const {name, title, url, socials, imageURL, email} = author; - const link = url || (email && `mailto:${email}`) || undefined; - - const hasSocials = socials && Object.keys(socials).length > 0; - - return ( -
    - {imageURL && ( - - {name} - - )} - - {(name || title) && ( -
    -
    - - {name} - -
    - {!!title && } - {hasSocials && } -
    - )} -
    - ); -} diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/styles.module.css b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/styles.module.css deleted file mode 100644 index 21ea5d40dc89..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/styles.module.css +++ /dev/null @@ -1,21 +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. - */ - -.authorName { - font-size: 1.1rem; -} - -.authorTitle { - margin-top: 0.06rem; - font-size: 0.8rem; - line-height: 0.8rem; - display: -webkit-box; - overflow: hidden; - line-clamp: 1; - -webkit-line-clamp: 1; - -webkit-box-orient: vertical; -} diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Authors/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Authors/index.tsx index 7ee6a472b22b..fc7b313dd278 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Authors/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Authors/index.tsx @@ -8,7 +8,7 @@ import React from 'react'; import clsx from 'clsx'; import {useBlogPost} from '@docusaurus/plugin-content-blog/client'; -import BlogPostItemHeaderAuthor from '@theme/BlogPostItem/Header/Author'; +import BlogAuthor from '@theme/Blog/Components/Author'; import type {Props} from '@theme/BlogPostItem/Header/Authors'; import styles from './styles.module.css'; @@ -40,8 +40,7 @@ export default function BlogPostItemHeaderAuthors({ imageOnly ? styles.imageOnlyAuthorCol : styles.authorCol, )} key={idx}> - - selectMessage( - count, - translate( - { - id: 'theme.blog.post.plurals', - description: - 'Pluralized label for "{count} posts". 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: 'One post|{count} posts', - }, - {count}, - ), - ); -} - -function useBlogTagsPostsPageTitle(tag: Props['tag']): string { - const blogPostsPlural = useBlogPostsPlural(); - return translate( - { - id: 'theme.blog.tagTitle', - description: 'The title of the page for a blog tag', - message: '{nPosts} tagged with "{tagName}"', - }, - {nPosts: blogPostsPlural(tag.count), tagName: tag.label}, - ); -} - function BlogTagsPostsPageMetadata({tag}: Props): JSX.Element { const title = useBlogTagsPostsPageTitle(tag); return ( diff --git a/packages/docusaurus-theme-common/src/internal.ts b/packages/docusaurus-theme-common/src/internal.ts index a31b19269f34..8c3f5d6b7c72 100644 --- a/packages/docusaurus-theme-common/src/internal.ts +++ b/packages/docusaurus-theme-common/src/internal.ts @@ -90,3 +90,10 @@ export {useLockBodyScroll} from './hooks/useLockBodyScroll'; export {useCodeWordWrap} from './hooks/useCodeWordWrap'; export {getPrismCssVariables} from './utils/codeBlockUtils'; export {useBackToTopButton} from './hooks/useBackToTopButton'; + +export { + useBlogTagsPostsPageTitle, + useBlogAuthorPageTitle, + translateBlogAuthorsListPageTitle, + BlogAuthorsListViewAllLabel, +} from './translations/blogTranslations'; diff --git a/packages/docusaurus-theme-common/src/translations/blogTranslations.tsx b/packages/docusaurus-theme-common/src/translations/blogTranslations.tsx new file mode 100644 index 000000000000..0390ee063a10 --- /dev/null +++ b/packages/docusaurus-theme-common/src/translations/blogTranslations.tsx @@ -0,0 +1,79 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ReactNode} from 'react'; +import Translate, {translate} from '@docusaurus/Translate'; +import {usePluralForm} from '../utils/usePluralForm'; + +// Only used locally +function useBlogPostsPlural(): (count: number) => string { + const {selectMessage} = usePluralForm(); + return (count: number) => + selectMessage( + count, + translate( + { + id: 'theme.blog.post.plurals', + description: + 'Pluralized label for "{count} posts". 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: 'One post|{count} posts', + }, + {count}, + ), + ); +} + +export function useBlogTagsPostsPageTitle(tag: { + label: string; + count: number; +}): string { + const blogPostsPlural = useBlogPostsPlural(); + return translate( + { + id: 'theme.blog.tagTitle', + description: 'The title of the page for a blog tag', + message: '{nPosts} tagged with "{tagName}"', + }, + {nPosts: blogPostsPlural(tag.count), tagName: tag.label}, + ); +} + +export function useBlogAuthorPageTitle(author: { + key: string; + name?: string; + count: number; +}): string { + const blogPostsPlural = useBlogPostsPlural(); + return translate( + { + id: 'theme.blog.author.pageTitle', + description: 'The title of the page for a blog author', + message: '{authorName} - {nPosts}', + }, + { + nPosts: blogPostsPlural(author.count), + authorName: author.name || author.key, + }, + ); +} + +export const translateBlogAuthorsListPageTitle = (): string => + translate({ + id: 'theme.blog.authorsList.pageTitle', + message: 'Authors', + description: 'The title of the authors page', + }); + +export function BlogAuthorsListViewAllLabel(): ReactNode { + return ( + + View All Authors + + ); +} diff --git a/packages/docusaurus-theme-common/src/utils/ThemeClassNames.ts b/packages/docusaurus-theme-common/src/utils/ThemeClassNames.ts index 1cdcda3ce50a..413a2492fa4a 100644 --- a/packages/docusaurus-theme-common/src/utils/ThemeClassNames.ts +++ b/packages/docusaurus-theme-common/src/utils/ThemeClassNames.ts @@ -18,6 +18,8 @@ export const ThemeClassNames = { blogPostPage: 'blog-post-page', blogTagsListPage: 'blog-tags-list-page', blogTagPostListPage: 'blog-tags-post-list-page', + blogAuthorsListPage: 'blog-authors-list-page', + blogAuthorsPostsPage: 'blog-authors-posts-page', docsDocPage: 'docs-doc-page', docsTagsListPage: 'docs-tags-list-page', diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/tagUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/tagsUtils.test.ts similarity index 78% rename from packages/docusaurus-theme-common/src/utils/__tests__/tagUtils.test.ts rename to packages/docusaurus-theme-common/src/utils/__tests__/tagsUtils.test.ts index 4e1f614d7702..8e5fac8cef16 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/tagUtils.test.ts +++ b/packages/docusaurus-theme-common/src/utils/__tests__/tagsUtils.test.ts @@ -7,42 +7,47 @@ import _ from 'lodash'; import {listTagsByLetters} from '../tagsUtils'; +import type {TagsListItem} from '@docusaurus/utils'; describe('listTagsByLetters', () => { - type Param = Parameters[0]; - type Tag = Param[number]; type Result = ReturnType; it('creates letters list', () => { - const tag1: Tag = { + const tag1: TagsListItem = { label: 'tag1', permalink: '/tag1', count: 1, + description: '', }; - const tag2: Tag = { + const tag2: TagsListItem = { label: 'Tag2', permalink: '/tag2', count: 11, + description: '', }; - const tagZxy: Tag = { + const tagZxy: TagsListItem = { label: 'zxy', permalink: '/zxy', count: 987, + description: '', }; - const tagAbc: Tag = { + const tagAbc: TagsListItem = { label: 'Abc', permalink: '/abc', count: 123, + description: '', }; - const tagDef: Tag = { + const tagDef: TagsListItem = { label: 'def', permalink: '/def', count: 1, + description: '', }; - const tagAaa: Tag = { + const tagAaa: TagsListItem = { label: 'aaa', permalink: '/aaa', count: 10, + description: '', }; const expectedResult: Result = [ diff --git a/packages/docusaurus-theme-translations/locales/base/theme-common.json b/packages/docusaurus-theme-translations/locales/base/theme-common.json index fcaeb33e9a98..3cc0b8b8b369 100644 --- a/packages/docusaurus-theme-translations/locales/base/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/base/theme-common.json @@ -41,6 +41,9 @@ "theme.admonition.tip___DESCRIPTION": "The default label used for the Tip admonition (:::tip)", "theme.admonition.warning": "warning", "theme.admonition.warning___DESCRIPTION": "The default label used for the Warning admonition (:::warning)", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", "theme.blog.archive.description": "Archive", "theme.blog.archive.description___DESCRIPTION": "The page & hero description of the blog archive page", "theme.blog.archive.title": "Archive", diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/bad.json b/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/bad.json deleted file mode 100644 index 3c27e19a80d4..000000000000 --- a/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/bad.json +++ /dev/null @@ -1 +0,0 @@ -{"a": 2} diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/bad.yml b/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/bad.yml deleted file mode 100644 index 9dfc208dffa8..000000000000 --- a/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/bad.yml +++ /dev/null @@ -1 +0,0 @@ -a: 2 diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/valid.json b/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/valid.json deleted file mode 100644 index cb5b2f69babc..000000000000 --- a/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/valid.json +++ /dev/null @@ -1 +0,0 @@ -{"a": 1} diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/valid.yml b/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/valid.yml deleted file mode 100644 index a8926a52d8dc..000000000000 --- a/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/actualData/valid.yml +++ /dev/null @@ -1 +0,0 @@ -a: 1 diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/dataFile.json b/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/dataFile.json new file mode 100644 index 000000000000..f06ab684a504 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/dataFile.json @@ -0,0 +1 @@ +{"content": "json"} diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/dataFile.yml b/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/dataFile.yml new file mode 100644 index 000000000000..59b8b3c49617 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/dataFile.yml @@ -0,0 +1 @@ +content: original yaml diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/invalid.yml b/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/invalid.yml new file mode 100644 index 000000000000..54bd7745b096 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/invalid.yml @@ -0,0 +1 @@ +}{{{{12434665¨£%£%%£%£}}}} diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/localized/dataFile.yml b/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/localized/dataFile.yml new file mode 100644 index 000000000000..bf980e035eba --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/localized/dataFile.yml @@ -0,0 +1 @@ +content: localized yaml diff --git a/packages/docusaurus-utils/src/__tests__/dataFileUtils.test.ts b/packages/docusaurus-utils/src/__tests__/dataFileUtils.test.ts index 4d809a45c791..732442f05015 100644 --- a/packages/docusaurus-utils/src/__tests__/dataFileUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/dataFileUtils.test.ts @@ -10,7 +10,7 @@ import { findFolderContainingFile, getFolderContainingFile, getDataFilePath, - getDataFileData, + readDataFile, } from '../dataFileUtils'; describe('getDataFilePath', () => { @@ -125,46 +125,40 @@ describe('getDataFilePath', () => { }); describe('getDataFileData', () => { - const fixturesDir = path.join(__dirname, '__fixtures__/dataFiles/actualData'); - function readDataFile(filePath: string) { - return getDataFileData( - { - filePath, - contentPaths: {contentPath: fixturesDir, contentPathLocalized: ''}, - fileType: 'test', - }, - (content) => { - // @ts-expect-error: good enough - if (content.a !== 1) { - throw new Error('Nope'); - } - return content; - }, + function testFile(filePath: string) { + const contentPath = path.join( + __dirname, + '__fixtures__/dataFiles/dataFiles', ); + const contentPathLocalized = path.join(contentPath, 'localized'); + return readDataFile({ + filePath, + contentPaths: {contentPath, contentPathLocalized}, + }); } it('returns undefined for nonexistent file', async () => { - await expect(readDataFile('nonexistent.yml')).resolves.toBeUndefined(); - }); - - it('read valid yml author file', async () => { - await expect(readDataFile('valid.yml')).resolves.toEqual({a: 1}); + await expect(testFile('nonexistent.yml')).resolves.toBeUndefined(); }); it('read valid json author file', async () => { - await expect(readDataFile('valid.json')).resolves.toEqual({a: 1}); + await expect(testFile('dataFile.json')).resolves.toEqual({ + content: 'json', + }); }); - it('fail to read invalid yml', async () => { - await expect( - readDataFile('bad.yml'), - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Nope"`); + it('read valid yml author file using localized source in priority', async () => { + await expect(testFile('dataFile.yml')).resolves.toEqual({ + content: 'localized yaml', + }); }); - it('fail to read invalid json', async () => { + it('throw for invalid file', async () => { await expect( - readDataFile('bad.json'), - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Nope"`); + testFile('invalid.yml'), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"The file at "packages/docusaurus-utils/src/__tests__/__fixtures__/dataFiles/dataFiles/invalid.yml" looks invalid (not Yaml nor JSON)."`, + ); }); }); diff --git a/packages/docusaurus-utils/src/dataFileUtils.ts b/packages/docusaurus-utils/src/dataFileUtils.ts index ba671d6e8875..decd57da940b 100644 --- a/packages/docusaurus-utils/src/dataFileUtils.ts +++ b/packages/docusaurus-utils/src/dataFileUtils.ts @@ -43,31 +43,28 @@ export async function getDataFilePath({ } /** - * Looks up for a data file in the content paths, returns the object validated - * and normalized according to the `validate` callback. + * Looks up for a data file in the content paths + * Favors the localized content path over the base content path + * Currently supports Yaml and JSON data files + * It is the caller responsibility to validate and normalize the resulting data * * @returns `undefined` when file not found - * @throws Throws when validation fails, displaying a helpful context message. + * @throws Throws when data file can't be parsed */ -export async function getDataFileData( - params: DataFileParams & { - /** Used for the "The X file looks invalid" message. */ - fileType: string; - }, - validate: (content: unknown) => T, -): Promise { +export async function readDataFile(params: DataFileParams): Promise { const filePath = await getDataFilePath(params); if (!filePath) { return undefined; } try { const contentString = await fs.readFile(filePath, {encoding: 'utf8'}); - const unsafeContent = Yaml.load(contentString); - // TODO we shouldn't validate here: it makes validation harder to test - return validate(unsafeContent); + return Yaml.load(contentString); } catch (err) { - logger.error`The ${params.fileType} file at path=${filePath} looks invalid.`; - throw err; + const msg = logger.interpolate`The file at path=${path.relative( + process.cwd(), + filePath, + )} looks invalid (not Yaml nor JSON).`; + throw new Error(msg, {cause: err as Error}); } } diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index 7201f8eec1a8..d9a24a408655 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -109,7 +109,7 @@ export {escapeShellArg} from './shellUtils'; export {loadFreshModule} from './moduleUtils'; export { getDataFilePath, - getDataFileData, + readDataFile, getContentPathList, findFolderContainingFile, getFolderContainingFile, diff --git a/packages/docusaurus-utils/src/tags.ts b/packages/docusaurus-utils/src/tags.ts index fd06bf2fea25..cef2ea7dd6f6 100644 --- a/packages/docusaurus-utils/src/tags.ts +++ b/packages/docusaurus-utils/src/tags.ts @@ -45,6 +45,7 @@ export type TagsListItem = Tag & { /** What the tag's own page should know about the tag. */ export type TagModule = TagsListItem & { /** The tags list page's permalink. */ + // TODO move this global value to a shared docs/blog bundle allTagsPath: string; /** Is this tag unlisted? (when it only contains unlisted items) */ unlisted: boolean; diff --git a/project-words.txt b/project-words.txt index 8fcf07008feb..528d635318ee 100644 --- a/project-words.txt +++ b/project-words.txt @@ -173,6 +173,7 @@ Lorber's lqip LQIP lunrjs +marcey Marcey Marcey's markprompt diff --git a/website/_dogfooding/_blog tests/2024-07-03-dual-author.mdx b/website/_dogfooding/_blog tests/2024-07-03-dual-author.mdx index 0a7ff12b993b..13eb48e40a5e 100644 --- a/website/_dogfooding/_blog tests/2024-07-03-dual-author.mdx +++ b/website/_dogfooding/_blog tests/2024-07-03-dual-author.mdx @@ -4,17 +4,15 @@ authors: - name: Sébastien Lorber imageURL: https://github.com/slorber.png socials: - twitter: sebastienlorber - github: slorber - stackoverflow: 82609 - linkedin: sebastienlorber + twitter: https://twitter.com/sebastienlorber + github: https://github.com/slorber + linkedin: https://www.linkedin.com/in/sebastienlorber/ newsletter: https://thisweekinreact.com/newsletter - name: Sébastien Lorber imageURL: https://github.com/slorber.png socials: x: https://x.com/sebastienlorber github: https://github.com/slorber - stackoverflow: 82609 linkedin: https://www.linkedin.com/in/sebastienlorber/ newsletter: https://thisweekinreact.com/newsletter --- diff --git a/website/_dogfooding/_blog tests/2024-07-03-single-author.mdx b/website/_dogfooding/_blog tests/2024-07-03-single-author.mdx index 0a418f42e283..8b2d6c2d78a7 100644 --- a/website/_dogfooding/_blog tests/2024-07-03-single-author.mdx +++ b/website/_dogfooding/_blog tests/2024-07-03-single-author.mdx @@ -8,7 +8,6 @@ authors: x: https://x.com/sebastienlorber twitter: https://twitter.com/sebastienlorber github: https://github.com/slorber - stackoverflow: 82609 linkedin: https://www.linkedin.com/in/sebastienlorber/ newsletter: https://thisweekinreact.com/newsletter --- diff --git a/website/_dogfooding/_blog tests/authors.yml b/website/_dogfooding/_blog tests/authors.yml index fc6a50b90bc1..040b9899ee19 100644 --- a/website/_dogfooding/_blog tests/authors.yml +++ b/website/_dogfooding/_blog tests/authors.yml @@ -4,3 +4,8 @@ slorber: url: https://sebastienlorber.com image_url: https://github.com/slorber.png twitter: sebastienlorber + page: true + +ozaki: + name: ozaki + page: {permalink: '/custom/ozaki/permalink'} diff --git a/website/blog/authors.yml b/website/blog/authors.yml index 318effc9b46b..a20778060947 100644 --- a/website/blog/authors.yml +++ b/website/blog/authors.yml @@ -3,7 +3,7 @@ JMarcey: title: Developer Advocate at Meta url: https://twitter.com/JoelMarcey image_url: https://github.com/JoelMarcey.png - email: jimarcey@gmail.com + page: true socials: x: joelmarcey github: JoelMarcey @@ -13,6 +13,7 @@ zpao: title: Engineering Manager at Meta url: https://x.com/zpao image_url: https://github.com/zpao.png + page: true socials: x: zpao github: zpao @@ -22,6 +23,11 @@ slorber: title: Docusaurus maintainer, This Week In React editor url: https://thisweekinreact.com image_url: https://github.com/slorber.png + page: true + description: > + A freelance React and React-Native developer near Paris and Docusaurus maintainer. Also runs ThisWeekInReact.com, a newsletter to stay updated with the React ecosystem. + + socials: x: sebastienlorber linkedin: sebastienlorber @@ -33,7 +39,7 @@ yangshun: title: Front End Engineer at Meta url: https://github.com/yangshun image_url: https://github.com/yangshun.png - email: tay.yang.shun@gmail.com + page: true socials: x: yangshunz github: yangshun @@ -44,6 +50,7 @@ lex111: url: https://github.com/lex111 image_url: https://github.com/lex111.png email: lex@php.net + page: true Josh-Cena: name: Joshua Chen @@ -51,6 +58,7 @@ Josh-Cena: url: https://joshcena.com/ image_url: https://github.com/josh-cena.png email: sidachen2003@gmail.com + page: true endiliey: name: Endilie Yacop Sucipto diff --git a/website/docs/api/plugins/plugin-content-blog.mdx b/website/docs/api/plugins/plugin-content-blog.mdx index 2eb10ccfb42c..3ad31c590296 100644 --- a/website/docs/api/plugins/plugin-content-blog.mdx +++ b/website/docs/api/plugins/plugin-content-blog.mdx @@ -50,6 +50,7 @@ Accepted fields: | `tagsBasePath` | `string` | `'tags'` | URL route for the tags section of your blog. Will be appended to `routeBasePath`. | | `pageBasePath` | `string` | `'page'` | URL route for the pages section of your blog. Will be appended to `routeBasePath`. | | `archiveBasePath` | string \| null | `'archive'` | URL route for the archive section of your blog. Will be appended to `routeBasePath`. **DO NOT** include a trailing slash. Use `null` to disable generation of archive. | +| `authorsBasePath` | `string` | `'authors'` | URL route for the authors pages of your blog. Will be appended to `path`. | | `include` | `string[]` | `['**/*.{md,mdx}']` | Array of glob patterns matching Markdown files to be built, relative to the content path. | | `exclude` | `string[]` | _See example configuration_ | Array of glob patterns matching Markdown files to be excluded. Serves as refinement based on the `include` option. | | `postsPerPage` | number \| 'ALL' | `10` | Number of posts to show per page in the listing page. Use `'ALL'` to display all posts on one listing page. | @@ -58,6 +59,8 @@ Accepted fields: | `blogTagsListComponent` | `string` | `'@theme/BlogTagsListPage'` | Root component of the tags list page. | | `blogTagsPostsComponent` | `string` | `'@theme/BlogTagsPostsPage'` | Root component of the "posts containing tag" page. | | `blogArchiveComponent` | `string` | `'@theme/BlogArchivePage'` | Root component of the blog archive page. | +| `blogAuthorsPostsComponent` | `string` | `'@theme/Blog/Pages/BlogAuthorsPostsPage'` | Root component of the blog author page. | +| `blogAuthorsListComponent` | `string` | `'@theme/Blog/Pages/BlogAuthorsListPage'` | Root component of the blog authors page index. | | `remarkPlugins` | `any[]` | `[]` | Remark plugins passed to MDX. | | `rehypePlugins` | `any[]` | `[]` | Rehype plugins passed to MDX. | | `rehypePlugins` | `any[]` | `[]` | Recma plugins passed to MDX. | @@ -298,6 +301,72 @@ import TagsFileApiRefSection from './_partial-tags-file-api-ref-section.mdx'; +## Authors File {#authors-file} + +Use the [`authors` plugin option](#authors) to configure the path of a YAML authors file. + +By convention, the plugin will look for a `authors.yml` file at the root of your blog content folder(s). + +This file can contain a list of predefined [global blog authors](../../blog.mdx#global-authors). You can reference these authors by their keys in Markdown files thanks to the [`authors` front matter](#markdown-front-matter). + +### Types {#authors-file-types} + +The YAML content of the provided authors file should respect the following shape: + +```tsx +type AuthorsMapInput = { + [authorKey: string]: AuthorInput; +}; + +type AuthorInput = { + name?: string; + title?: string; + description?: string; + imageURL?: string; + url?: string; + email?: string; + page?: boolean | {permalink: string}; + socials?: Record; + [customAuthorAttribute: string]: unknown; +}; +``` + +### Example {#authors-file-example} + +```yml title="tags.yml" +slorber: + name: Sébastien Lorber + title: Docusaurus maintainer + url: https://sebastienlorber.com + image_url: https://github.com/slorber.png + page: true + socials: + x: sebastienlorber + github: slorber + +jmarcey: + name: Joel Marcey + title: Co-creator of Docusaurus 1 + url: https://github.com/JoelMarcey + image_url: https://github.com/JoelMarcey.png + email: jimarcey@gmail.com + page: + permalink: '/joel-marcey' + socials: + x: joelmarcey + github: JoelMarcey +``` + +```md title="blog/my-blog-post.md" +--- +authors: [slorber, jmarcey] +--- + +# My Blog Post + +Content +``` + ## i18n {#i18n} Read the [i18n introduction](../../i18n/i18n-introduction.mdx) first. diff --git a/website/docs/blog.mdx b/website/docs/blog.mdx index 8ec62d11fea7..e6cf18644e98 100644 --- a/website/docs/blog.mdx +++ b/website/docs/blog.mdx @@ -401,6 +401,39 @@ An author, either declared through front matter or through the authors map, need ::: +### Authors pages {#authors-pages} + +The authors pages feature is optional, and mainly useful for multi-author blogs. + +You can activate it independently for each author by adding a `page: true` attribute to the [global author configuration](#global-authors): + +```yml title="website/blog/authors.yml" +slorber: + name: Sébastien Lorber + // highlight-start + page: true # Turns the feature on - route will be /authors/slorber + // highlight-end + +jmarcey: + name: Joel Marcey + // highlight-start + page: + # Turns the feature on - route will be /authors/custom-author-url + permalink: '/custom-author-url' + // highlight-end +``` + +The blog plugin will now generate: + +- a dedicated author page for each author ([example](/blog/authors/slorber)) listing all the blog posts they contributed to +- an authors index page ([example](/blog/authors)) listing all these authors, in the order they appear in `authors.yml` + +:::warning About inline authors + +Only [global authors](#global-authors) can activate this feature. [Inline authors](#inline-authors) are not supported. + +::: + ## Blog post tags {#blog-post-tags} Tags are declared in the front matter and introduce another dimension of categorization. diff --git a/website/src/plugins/changelog/theme/ChangelogItem/Header/Author/index.tsx b/website/src/plugins/changelog/theme/ChangelogItem/Header/Author/index.tsx index 185505530d24..b37b0f5bf9e3 100644 --- a/website/src/plugins/changelog/theme/ChangelogItem/Header/Author/index.tsx +++ b/website/src/plugins/changelog/theme/ChangelogItem/Header/Author/index.tsx @@ -8,7 +8,7 @@ import React from 'react'; import clsx from 'clsx'; import Link from '@docusaurus/Link'; -import type {Props} from '@theme/BlogPostItem/Header/Author'; +import type {Props} from '@theme/Blog/Components/Author'; import styles from './styles.module.css'; From 08a893a2eba704fbeca122173c667a70de00ad96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Fri, 2 Aug 2024 14:01:02 +0200 Subject: [PATCH 04/10] chore: add prettier-xml plugin (#10364) --- .prettierignore | 3 +++ package.json | 3 ++- yarn.lock | 29 ++++++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/.prettierignore b/.prettierignore index aa68052ce19c..60603eea3fed 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,6 +5,9 @@ build coverage .docusaurus +.svg +*.svg + jest/vendor packages/lqip-loader/lib/ diff --git a/package.json b/package.json index 306e6e6c102b..0cc5a557dfb1 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ }, "devDependencies": { "@crowdin/cli": "^3.13.0", + "@prettier/plugin-xml": "^2.2.0", "@swc/core": "1.2.197", "@swc/jest": "^0.2.26", "@testing-library/react-hooks": "^8.0.1", @@ -105,7 +106,7 @@ "lint-staged": "^13.2.3", "lockfile-lint": "^4.14.0", "npm-run-all": "^4.1.5", - "prettier": "^2.8.4", + "prettier": "^2.8.8", "react": "^18.0.0", "react-dom": "^18.0.0", "react-helmet-async": "^1.3.0", diff --git a/yarn.lock b/yarn.lock index 461971db46db..9bd13eabfb91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2636,6 +2636,14 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== +"@prettier/plugin-xml@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@prettier/plugin-xml/-/plugin-xml-2.2.0.tgz#2bc2ae667aa817369fdb939aa7d36ea88105483d" + integrity sha512-UWRmygBsyj4bVXvDiqSccwT1kmsorcwQwaIy30yVh8T+Gspx4OlC0shX1y+ZuwXZvgnafmpRYKks0bAu9urJew== + dependencies: + "@xml-tools/parser" "^1.0.11" + prettier ">=2.4.0" + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -3980,6 +3988,13 @@ "@webassemblyjs/ast" "1.11.6" "@xtuc/long" "4.2.2" +"@xml-tools/parser@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@xml-tools/parser/-/parser-1.0.11.tgz#a118a14099ea5c3c537e4781fad2fc195b57f8ff" + integrity sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA== + dependencies: + chevrotain "7.1.1" + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -5094,6 +5109,13 @@ cheerio@^1.0.0-rc.12: parse5 "^7.0.0" parse5-htmlparser2-tree-adapter "^7.0.0" +chevrotain@7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/chevrotain/-/chevrotain-7.1.1.tgz#5122814eafd1585a9601f9180a7be9c42d5699c6" + integrity sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw== + dependencies: + regexp-to-ast "0.5.0" + chokidar@^3.4.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -13467,7 +13489,7 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier@^2.8.4: +prettier@>=2.4.0, prettier@^2.8.8: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== @@ -14156,6 +14178,11 @@ regexp-ast-analysis@^0.6.0: "@eslint-community/regexpp" "^4.5.0" refa "^0.11.0" +regexp-to-ast@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz#56c73856bee5e1fef7f73a00f1473452ab712a24" + integrity sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw== + regexp.prototype.flags@^1.4.3, regexp.prototype.flags@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" From 7be1feaa0a6fbfd18faec8fe0488d24a61b15596 Mon Sep 17 00:00:00 2001 From: Rohan Thakur Date: Fri, 2 Aug 2024 22:20:48 +0530 Subject: [PATCH 05/10] feat(blog): add feed xlst options to render beautiful RSS and Atom feeds (#9252) Co-authored-by: ozakione <29860391+OzakIOne@users.noreply.github.com> Co-authored-by: sebastien --- .cspell.json | 2 + .../classic-typescript/docusaurus.config.ts | 4 + .../templates/classic/docusaurus.config.js | 4 + .../assets/atom.css | 75 ++ .../assets/atom.xsl | 92 ++ .../assets/rss.css | 75 ++ .../assets/rss.xsl | 86 ++ .../package.json | 3 +- .../__fixtures__/website/blog/custom-atom.css | 76 ++ .../__fixtures__/website/blog/custom-atom.xsl | 65 + .../__fixtures__/website/blog/custom-rss.css | 76 ++ .../__fixtures__/website/blog/custom-rss.xsl | 66 ++ .../website/build-snap/blog/atom.css | 75 ++ .../website/build-snap/blog/atom.xsl | 92 ++ .../website/build-snap/blog/custom-atom.css | 76 ++ .../website/build-snap/blog/custom-atom.xsl | 65 + .../website/build-snap/blog/custom-rss.css | 76 ++ .../website/build-snap/blog/custom-rss.xsl | 66 ++ .../website/build-snap/blog/rss.css | 75 ++ .../website/build-snap/blog/rss.xsl | 86 ++ .../__tests__/__snapshots__/feed.test.ts.snap | 1050 ++++++++++++++++- .../__snapshots__/options.test.ts.snap | 2 +- .../src/__tests__/feed.test.ts | 131 +- .../src/__tests__/options.test.ts | 235 +++- .../src/feed.ts | 157 ++- .../src/index.ts | 1 + .../src/options.ts | 132 ++- .../src/plugin-content-blog.d.ts | 26 + project-words.txt | 2 + .../_dogfooding/_blog tests/custom-atom.css | 76 ++ .../_dogfooding/_blog tests/custom-atom.xsl | 94 ++ .../_dogfooding/_blog tests/custom-rss.css | 76 ++ .../_dogfooding/_blog tests/custom-rss.xsl | 92 ++ website/_dogfooding/dogfooding.config.ts | 4 + .../docs/api/plugins/plugin-content-blog.mdx | 20 + website/docs/blog.mdx | 11 +- website/docusaurus.config.ts | 3 + 37 files changed, 3229 insertions(+), 118 deletions(-) create mode 100644 packages/docusaurus-plugin-content-blog/assets/atom.css create mode 100644 packages/docusaurus-plugin-content-blog/assets/atom.xsl create mode 100644 packages/docusaurus-plugin-content-blog/assets/rss.css create mode 100644 packages/docusaurus-plugin-content-blog/assets/rss.xsl create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.css create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.xsl create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.css create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.xsl create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.css create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xsl create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.css create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.xsl create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.css create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.xsl create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.css create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.xsl create mode 100644 website/_dogfooding/_blog tests/custom-atom.css create mode 100644 website/_dogfooding/_blog tests/custom-atom.xsl create mode 100644 website/_dogfooding/_blog tests/custom-rss.css create mode 100644 website/_dogfooding/_blog tests/custom-rss.xsl diff --git a/.cspell.json b/.cspell.json index ed3031458ccf..82ebc2c41a41 100644 --- a/.cspell.json +++ b/.cspell.json @@ -32,6 +32,8 @@ "website/_dogfooding/_pages tests/diagrams.mdx", "*.xyz", "*.docx", + "*.xsl", + "*.xslt", "*.gitignore", "versioned_docs", "*.min.*", diff --git a/packages/create-docusaurus/templates/classic-typescript/docusaurus.config.ts b/packages/create-docusaurus/templates/classic-typescript/docusaurus.config.ts index 4058eac59e23..ae7cbf502e05 100644 --- a/packages/create-docusaurus/templates/classic-typescript/docusaurus.config.ts +++ b/packages/create-docusaurus/templates/classic-typescript/docusaurus.config.ts @@ -42,6 +42,10 @@ const config: Config = { }, blog: { showReadingTime: true, + feedOptions: { + type: ['rss', 'atom'], + xslt: true, + }, // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: diff --git a/packages/create-docusaurus/templates/classic/docusaurus.config.js b/packages/create-docusaurus/templates/classic/docusaurus.config.js index 7b8d5b1c7f11..d3b51858f497 100644 --- a/packages/create-docusaurus/templates/classic/docusaurus.config.js +++ b/packages/create-docusaurus/templates/classic/docusaurus.config.js @@ -48,6 +48,10 @@ const config = { }, blog: { showReadingTime: true, + feedOptions: { + type: ['rss', 'atom'], + xslt: true, + }, // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: diff --git a/packages/docusaurus-plugin-content-blog/assets/atom.css b/packages/docusaurus-plugin-content-blog/assets/atom.css new file mode 100644 index 000000000000..d2fc20b27416 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/assets/atom.css @@ -0,0 +1,75 @@ +/** + * 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. + */ + +main { + flex: 1 0 auto; + width: 100%; + margin: 2rem auto; + max-width: 800px; + /* stylelint-disable-next-line font-family-name-quotes */ + font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell; +} + +.info { + display: block; + margin: 2rem 0; + padding: 1.6rem 2.4rem; + border: 1px solid dodgerblue; + border-left-width: 0.5rem; + border-radius: 0.4rem; + background-color: #edf5ff; +} + +a { + color: #005aff; + text-decoration: none; +} + +h1 { + text-wrap: balance; + font-size: 3.4rem; + font-weight: 800; + margin-bottom: 2rem; + display: flex; + align-items: center; +} + +h1 .rss-icon { + height: 3.2rem; + width: 3.2rem; + margin-right: 1rem; +} + +h2 { + font-size: 2.2rem; + font-weight: 700; + margin-bottom: 0.2rem; +} + +h3 { + font-size: 1.8rem; + font-weight: 700; + margin-bottom: 0.1rem; +} + +.blog-description { + font-size: 1.4rem; + margin-bottom: 0.6rem; +} + +.blog-post-date { + font-size: 1rem; + line-height: 1.4rem; + font-style: italic; + color: #797b7e; +} + +.blog-post-description { + font-size: 1rem; + line-height: 1.4rem; + color: #434349; +} diff --git a/packages/docusaurus-plugin-content-blog/assets/atom.xsl b/packages/docusaurus-plugin-content-blog/assets/atom.xsl new file mode 100644 index 000000000000..271895cf7775 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/assets/atom.xsl @@ -0,0 +1,92 @@ + + + + + + + + Atom Feed | <xsl:value-of + select="atom:feed/atom:title" + /> + + + +
    +
    + +

    +
    + + + + + + + + + + +
    + +

    +

    + +

    +
    +

    Recent Posts

    +
    + +
    +

    + +
    + +
    +
    +
    +
    +
    + + + + diff --git a/packages/docusaurus-plugin-content-blog/assets/rss.css b/packages/docusaurus-plugin-content-blog/assets/rss.css new file mode 100644 index 000000000000..d2fc20b27416 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/assets/rss.css @@ -0,0 +1,75 @@ +/** + * 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. + */ + +main { + flex: 1 0 auto; + width: 100%; + margin: 2rem auto; + max-width: 800px; + /* stylelint-disable-next-line font-family-name-quotes */ + font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell; +} + +.info { + display: block; + margin: 2rem 0; + padding: 1.6rem 2.4rem; + border: 1px solid dodgerblue; + border-left-width: 0.5rem; + border-radius: 0.4rem; + background-color: #edf5ff; +} + +a { + color: #005aff; + text-decoration: none; +} + +h1 { + text-wrap: balance; + font-size: 3.4rem; + font-weight: 800; + margin-bottom: 2rem; + display: flex; + align-items: center; +} + +h1 .rss-icon { + height: 3.2rem; + width: 3.2rem; + margin-right: 1rem; +} + +h2 { + font-size: 2.2rem; + font-weight: 700; + margin-bottom: 0.2rem; +} + +h3 { + font-size: 1.8rem; + font-weight: 700; + margin-bottom: 0.1rem; +} + +.blog-description { + font-size: 1.4rem; + margin-bottom: 0.6rem; +} + +.blog-post-date { + font-size: 1rem; + line-height: 1.4rem; + font-style: italic; + color: #797b7e; +} + +.blog-post-description { + font-size: 1rem; + line-height: 1.4rem; + color: #434349; +} diff --git a/packages/docusaurus-plugin-content-blog/assets/rss.xsl b/packages/docusaurus-plugin-content-blog/assets/rss.xsl new file mode 100644 index 000000000000..e9695b298468 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/assets/rss.xsl @@ -0,0 +1,86 @@ + + + + + + + + RSS Feed | <xsl:value-of select="rss/channel/title" /> + + + +
    +
    +
    + This is an RSS feed. Subscribe by copying the URL + from the address bar into your newsreader. Visit + About Feeds to learn more + and get started. It’s free. +
    +

    +
    + + + + + + + + + + +
    + +

    +

    + +

    +
    +

    Recent Posts

    +
    + +
    +

    + +
    + +
    +
    +
    +
    +
    + + +
    +
    diff --git a/packages/docusaurus-plugin-content-blog/package.json b/packages/docusaurus-plugin-content-blog/package.json index 815413ff2cfd..ff88860d2ae4 100644 --- a/packages/docusaurus-plugin-content-blog/package.json +++ b/packages/docusaurus-plugin-content-blog/package.json @@ -59,6 +59,7 @@ "node": ">=18.0" }, "devDependencies": { - "@total-typescript/shoehorn": "^0.1.2" + "@total-typescript/shoehorn": "^0.1.2", + "tree-node-cli": "^1.6.0" } } diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.css b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.css new file mode 100644 index 000000000000..c016178d9007 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.css @@ -0,0 +1,76 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +* { + color: #0d1137; +} + +main { + flex: 1 0 auto; + width: 100%; + margin: 4rem auto; + padding: 1.5 rem; + max-width: 800px; + /* stylelint-disable-next-line font-family-name-quotes */ + font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell; +} + +.info { + display: block; + margin: 3rem 0; + padding: 2rem 3rem; + border: 1px solid dodgerblue; + border-left-width: 0.5rem; + border-radius: 0.4rem; + background-color: #e52165; +} + +.rss-icon { + height: 3.8rem; + width: 3.8rem; + margin-right: 1rem; +} + +.flex { + display: flex; +} + +.items-start { + align-items: flex-start; +} + +.pb-7 { + padding-bottom: 3rem; +} + +a { + color: #005aff; + text-decoration: none; +} + +h1 { + text-wrap: balance; + font-size: 3.8rem; + line-height: 1; + font-weight: 800; + margin-bottom: 4rem; +} + +h2 { + font-size: 3rem; + line-height: 1.2; + font-weight: 700; + margin-bottom: 3rem; +} + +h2:not(:first-child) { + margin-top: 5.8rem; +} + +.italic { + font-style: italic; +} diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.xsl b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.xsl new file mode 100644 index 000000000000..80406b153124 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-atom.xsl @@ -0,0 +1,65 @@ + + + + + + + + + Atom Feed | <xsl:value-of select="atom:feed/atom:title" /> + + + +
    +
    +
    + This is an Atom feed. Subscribe by copying the URL from the address + bar into your newsreader. Visit About Feeds to learn more + and get started. It’s free.
    +

    +
    + + + + + + + + + + +
    + Custom Atom Feed Preview

    +

    + +

    +

    Description:

    +
    +

    Recent Posts

    +
    + +
    + + + +
    Published on +
    +
    + +
    +
    +
    +
    +
    + + +
    + +
    diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.css b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.css new file mode 100644 index 000000000000..c016178d9007 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.css @@ -0,0 +1,76 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +* { + color: #0d1137; +} + +main { + flex: 1 0 auto; + width: 100%; + margin: 4rem auto; + padding: 1.5 rem; + max-width: 800px; + /* stylelint-disable-next-line font-family-name-quotes */ + font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell; +} + +.info { + display: block; + margin: 3rem 0; + padding: 2rem 3rem; + border: 1px solid dodgerblue; + border-left-width: 0.5rem; + border-radius: 0.4rem; + background-color: #e52165; +} + +.rss-icon { + height: 3.8rem; + width: 3.8rem; + margin-right: 1rem; +} + +.flex { + display: flex; +} + +.items-start { + align-items: flex-start; +} + +.pb-7 { + padding-bottom: 3rem; +} + +a { + color: #005aff; + text-decoration: none; +} + +h1 { + text-wrap: balance; + font-size: 3.8rem; + line-height: 1; + font-weight: 800; + margin-bottom: 4rem; +} + +h2 { + font-size: 3rem; + line-height: 1.2; + font-weight: 700; + margin-bottom: 3rem; +} + +h2:not(:first-child) { + margin-top: 5.8rem; +} + +.italic { + font-style: italic; +} diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.xsl b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.xsl new file mode 100644 index 000000000000..4d793963cba1 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/custom-rss.xsl @@ -0,0 +1,66 @@ + + + + + + + + + RSS Feed | <xsl:value-of select="rss/channel/title" /> + + + +
    +
    +
    + This is an RSS feed. Subscribe by copying the URL from the address + bar into your newsreader. Visit About Feeds to learn more + and get started. It’s free.
    +

    +
    + + + + + + + + + + +
    + Custom RSS Feed Preview

    +

    + +

    +

    Description:

    +
    +

    Recent Posts

    +
    + +
    + + + +
    Published on +
    +
    + +
    +
    +
    +
    +
    + + +
    + +
    diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.css b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.css new file mode 100644 index 000000000000..d2fc20b27416 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.css @@ -0,0 +1,75 @@ +/** + * 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. + */ + +main { + flex: 1 0 auto; + width: 100%; + margin: 2rem auto; + max-width: 800px; + /* stylelint-disable-next-line font-family-name-quotes */ + font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell; +} + +.info { + display: block; + margin: 2rem 0; + padding: 1.6rem 2.4rem; + border: 1px solid dodgerblue; + border-left-width: 0.5rem; + border-radius: 0.4rem; + background-color: #edf5ff; +} + +a { + color: #005aff; + text-decoration: none; +} + +h1 { + text-wrap: balance; + font-size: 3.4rem; + font-weight: 800; + margin-bottom: 2rem; + display: flex; + align-items: center; +} + +h1 .rss-icon { + height: 3.2rem; + width: 3.2rem; + margin-right: 1rem; +} + +h2 { + font-size: 2.2rem; + font-weight: 700; + margin-bottom: 0.2rem; +} + +h3 { + font-size: 1.8rem; + font-weight: 700; + margin-bottom: 0.1rem; +} + +.blog-description { + font-size: 1.4rem; + margin-bottom: 0.6rem; +} + +.blog-post-date { + font-size: 1rem; + line-height: 1.4rem; + font-style: italic; + color: #797b7e; +} + +.blog-post-description { + font-size: 1rem; + line-height: 1.4rem; + color: #434349; +} diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xsl b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xsl new file mode 100644 index 000000000000..271895cf7775 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xsl @@ -0,0 +1,92 @@ + + + + + + + + Atom Feed | <xsl:value-of + select="atom:feed/atom:title" + /> + + + +
    +
    +
    + This is an Atom feed. Subscribe by copying the URL + from the address bar into your newsreader. Visit + About Feeds to learn more + and get started. It’s free. +
    +

    +
    + + + + + + + + + + +
    + +

    +

    + +

    +
    +

    Recent Posts

    +
    + +
    +

    + +
    + +
    +
    +
    +
    +
    + + +
    +
    diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.css b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.css new file mode 100644 index 000000000000..c016178d9007 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.css @@ -0,0 +1,76 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +* { + color: #0d1137; +} + +main { + flex: 1 0 auto; + width: 100%; + margin: 4rem auto; + padding: 1.5 rem; + max-width: 800px; + /* stylelint-disable-next-line font-family-name-quotes */ + font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell; +} + +.info { + display: block; + margin: 3rem 0; + padding: 2rem 3rem; + border: 1px solid dodgerblue; + border-left-width: 0.5rem; + border-radius: 0.4rem; + background-color: #e52165; +} + +.rss-icon { + height: 3.8rem; + width: 3.8rem; + margin-right: 1rem; +} + +.flex { + display: flex; +} + +.items-start { + align-items: flex-start; +} + +.pb-7 { + padding-bottom: 3rem; +} + +a { + color: #005aff; + text-decoration: none; +} + +h1 { + text-wrap: balance; + font-size: 3.8rem; + line-height: 1; + font-weight: 800; + margin-bottom: 4rem; +} + +h2 { + font-size: 3rem; + line-height: 1.2; + font-weight: 700; + margin-bottom: 3rem; +} + +h2:not(:first-child) { + margin-top: 5.8rem; +} + +.italic { + font-style: italic; +} diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.xsl b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.xsl new file mode 100644 index 000000000000..80406b153124 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-atom.xsl @@ -0,0 +1,65 @@ + + + + + + + + + Atom Feed | <xsl:value-of select="atom:feed/atom:title" /> + + + +
    +
    +
    + This is an Atom feed. Subscribe by copying the URL from the address + bar into your newsreader. Visit About Feeds to learn more + and get started. It’s free.
    +

    +
    + + + + + + + + + + +
    + Custom Atom Feed Preview

    +

    + +

    +

    Description:

    +
    +

    Recent Posts

    +
    + +
    + + + +
    Published on +
    +
    + +
    +
    +
    +
    +
    + + +
    + +
    diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.css b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.css new file mode 100644 index 000000000000..c016178d9007 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.css @@ -0,0 +1,76 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +* { + color: #0d1137; +} + +main { + flex: 1 0 auto; + width: 100%; + margin: 4rem auto; + padding: 1.5 rem; + max-width: 800px; + /* stylelint-disable-next-line font-family-name-quotes */ + font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell; +} + +.info { + display: block; + margin: 3rem 0; + padding: 2rem 3rem; + border: 1px solid dodgerblue; + border-left-width: 0.5rem; + border-radius: 0.4rem; + background-color: #e52165; +} + +.rss-icon { + height: 3.8rem; + width: 3.8rem; + margin-right: 1rem; +} + +.flex { + display: flex; +} + +.items-start { + align-items: flex-start; +} + +.pb-7 { + padding-bottom: 3rem; +} + +a { + color: #005aff; + text-decoration: none; +} + +h1 { + text-wrap: balance; + font-size: 3.8rem; + line-height: 1; + font-weight: 800; + margin-bottom: 4rem; +} + +h2 { + font-size: 3rem; + line-height: 1.2; + font-weight: 700; + margin-bottom: 3rem; +} + +h2:not(:first-child) { + margin-top: 5.8rem; +} + +.italic { + font-style: italic; +} diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.xsl b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.xsl new file mode 100644 index 000000000000..4d793963cba1 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/custom-rss.xsl @@ -0,0 +1,66 @@ + + + + + + + + + RSS Feed | <xsl:value-of select="rss/channel/title" /> + + + +
    +
    +
    + This is an RSS feed. Subscribe by copying the URL from the address + bar into your newsreader. Visit About Feeds to learn more + and get started. It’s free.
    +

    +
    + + + + + + + + + + +
    + Custom RSS Feed Preview

    +

    + +

    +

    Description:

    +
    +

    Recent Posts

    +
    + +
    + + + +
    Published on +
    +
    + +
    +
    +
    +
    +
    + + +
    + +
    diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.css b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.css new file mode 100644 index 000000000000..d2fc20b27416 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.css @@ -0,0 +1,75 @@ +/** + * 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. + */ + +main { + flex: 1 0 auto; + width: 100%; + margin: 2rem auto; + max-width: 800px; + /* stylelint-disable-next-line font-family-name-quotes */ + font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell; +} + +.info { + display: block; + margin: 2rem 0; + padding: 1.6rem 2.4rem; + border: 1px solid dodgerblue; + border-left-width: 0.5rem; + border-radius: 0.4rem; + background-color: #edf5ff; +} + +a { + color: #005aff; + text-decoration: none; +} + +h1 { + text-wrap: balance; + font-size: 3.4rem; + font-weight: 800; + margin-bottom: 2rem; + display: flex; + align-items: center; +} + +h1 .rss-icon { + height: 3.2rem; + width: 3.2rem; + margin-right: 1rem; +} + +h2 { + font-size: 2.2rem; + font-weight: 700; + margin-bottom: 0.2rem; +} + +h3 { + font-size: 1.8rem; + font-weight: 700; + margin-bottom: 0.1rem; +} + +.blog-description { + font-size: 1.4rem; + margin-bottom: 0.6rem; +} + +.blog-post-date { + font-size: 1rem; + line-height: 1.4rem; + font-style: italic; + color: #797b7e; +} + +.blog-post-description { + font-size: 1rem; + line-height: 1.4rem; + color: #434349; +} diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.xsl b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.xsl new file mode 100644 index 000000000000..e9695b298468 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.xsl @@ -0,0 +1,86 @@ + + + + + + + + RSS Feed | <xsl:value-of select="rss/channel/title" /> + + + +
    +
    +
    + This is an RSS feed. Subscribe by copying the URL + from the address bar into your newsreader. Visit + About Feeds to learn more + and get started. It’s free. +
    +

    +
    + + + + + + + + + + +
    + +

    +

    + +

    +
    +

    Recent Posts

    +
    + +
    +

    + +
    + +
    +
    +
    +
    +
    + + +
    +
    diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap index 6b693d83fb2e..2c90688e2c56 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap @@ -92,6 +92,192 @@ exports[`atom filters to the first two entries using limit 1`] = ` ] `; +exports[`atom has custom xslt files for feed 1`] = ` +[ + [ + "/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xml", + " + + https://docusaurus.io/myBaseUrl/blog + Hello Blog + 2023-07-23T00:00:00.000Z + https://github.com/jpmonette/feed + + Hello Blog + https://docusaurus.io/myBaseUrl/image/favicon.ico + Copyright + + <![CDATA[test links]]> + https://docusaurus.io/myBaseUrl/blog/blog-with-links + + 2023-07-23T00:00:00.000Z + + absolute full url

    +

    absolute pathname

    +

    relative pathname

    +

    md link

    +

    anchor

    +

    relative pathname + anchor

    +

    +

    + +]]>
    +
    + + <![CDATA[MDX Blog Sample with require calls]]> + https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post + + 2021-03-06T00:00:00.000Z + + Test MDX with require calls

    + + + + +]]>
    +
    + + <![CDATA[Full Blog Sample]]> + https://docusaurus.io/myBaseUrl/blog/mdx-blog-post + + 2021-03-05T00:00:00.000Z + + HTML Heading 1 +

    HTML Heading 2

    +

    HTML Paragraph

    + + +

    Import DOM

    +

    Heading 1

    +

    Heading 2

    +

    Heading 3

    +

    Heading 4

    +
    Heading 5
    +
      +
    • list1
    • +
    • list2
    • +
    • list3
    • +
    +
      +
    • list1
    • +
    • list2
    • +
    • list3
    • +
    +

    Normal Text Italics Text Bold Text

    +

    link image

    ]]>
    +
    + + <![CDATA[Complex Slug]]> + https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô + + 2020-08-16T00:00:00.000Z + + complex url slug

    ]]>
    + + +
    + + <![CDATA[Simple Slug]]> + https://docusaurus.io/myBaseUrl/blog/simple/slug + + 2020-08-15T00:00:00.000Z + + simple url slug

    ]]>
    + + Sébastien Lorber + https://sebastienlorber.com + +
    + + <![CDATA[some heading]]> + https://docusaurus.io/myBaseUrl/blog/heading-as-title + + 2019-01-02T00:00:00.000Z + + + <![CDATA[date-matter]]> + https://docusaurus.io/myBaseUrl/blog/date-matter + + 2019-01-01T00:00:00.000Z + + date inside front matter

    ]]>
    + +
    + + <![CDATA[Happy 1st Birthday Slash! (translated)]]> + https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash + + 2018-12-14T00:00:00.000Z + + Happy birthday! (translated)

    ]]>
    + + Yangshun Tay (translated) + + + Sébastien Lorber (translated) + lorber.sebastien@gmail.com + + + +
    +
    ", + ], +] +`; + +exports[`atom has custom xslt files for feed: blog tree 1`] = ` +"blog +├── 2018 +│ └── 12 +│ └── 14 +│ └── Happy-First-Birthday-Slash +│ └── index.html +├── archive +│ └── index.html +├── atom.css +├── atom.xml +├── atom.xsl +├── blog-with-links +│ └── index.html +├── custom-atom.css +├── custom-atom.xsl +├── custom-rss.css +├── custom-rss.xsl +├── date-matter +│ └── index.html +├── feed.json +├── heading-as-title +│ └── index.html +├── hey +│ └── my super path +│ └── héllô +│ └── index.html +├── index.html +├── mdx-blog-post +│ └── index.html +├── mdx-require-blog-post +│ └── index.html +├── page +│ ├── 2 +│ │ └── index.html +│ └── 3 +│ └── index.html +├── rss.css +├── rss.xml +├── rss.xsl +├── simple +│ └── slug +│ └── index.html +├── tags +│ ├── complex +│ │ └── index.html +│ ├── date +│ │ └── index.html +│ └── index.html +└── unlisted + └── index.html" +`; + exports[`atom has feed item for each post - with trailing slash 1`] = ` [ " @@ -352,6 +538,192 @@ exports[`atom has feed item for each post 1`] = ` ] `; +exports[`atom has xslt files for feed 1`] = ` +[ + [ + "/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xml", + " + + https://docusaurus.io/myBaseUrl/blog + Hello Blog + 2023-07-23T00:00:00.000Z + https://github.com/jpmonette/feed + + Hello Blog + https://docusaurus.io/myBaseUrl/image/favicon.ico + Copyright + + <![CDATA[test links]]> + https://docusaurus.io/myBaseUrl/blog/blog-with-links + + 2023-07-23T00:00:00.000Z + + absolute full url

    +

    absolute pathname

    +

    relative pathname

    +

    md link

    +

    anchor

    +

    relative pathname + anchor

    +

    +

    + +]]>
    +
    + + <![CDATA[MDX Blog Sample with require calls]]> + https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post + + 2021-03-06T00:00:00.000Z + + Test MDX with require calls

    + + + + +]]>
    +
    + + <![CDATA[Full Blog Sample]]> + https://docusaurus.io/myBaseUrl/blog/mdx-blog-post + + 2021-03-05T00:00:00.000Z + + HTML Heading 1 +

    HTML Heading 2

    +

    HTML Paragraph

    + + +

    Import DOM

    +

    Heading 1

    +

    Heading 2

    +

    Heading 3

    +

    Heading 4

    +
    Heading 5
    +
      +
    • list1
    • +
    • list2
    • +
    • list3
    • +
    +
      +
    • list1
    • +
    • list2
    • +
    • list3
    • +
    +

    Normal Text Italics Text Bold Text

    +

    link image

    ]]>
    +
    + + <![CDATA[Complex Slug]]> + https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô + + 2020-08-16T00:00:00.000Z + + complex url slug

    ]]>
    + + +
    + + <![CDATA[Simple Slug]]> + https://docusaurus.io/myBaseUrl/blog/simple/slug + + 2020-08-15T00:00:00.000Z + + simple url slug

    ]]>
    + + Sébastien Lorber + https://sebastienlorber.com + +
    + + <![CDATA[some heading]]> + https://docusaurus.io/myBaseUrl/blog/heading-as-title + + 2019-01-02T00:00:00.000Z + + + <![CDATA[date-matter]]> + https://docusaurus.io/myBaseUrl/blog/date-matter + + 2019-01-01T00:00:00.000Z + + date inside front matter

    ]]>
    + +
    + + <![CDATA[Happy 1st Birthday Slash! (translated)]]> + https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash + + 2018-12-14T00:00:00.000Z + + Happy birthday! (translated)

    ]]>
    + + Yangshun Tay (translated) + + + Sébastien Lorber (translated) + lorber.sebastien@gmail.com + + + +
    +
    ", + ], +] +`; + +exports[`atom has xslt files for feed: blog tree 1`] = ` +"blog +├── 2018 +│ └── 12 +│ └── 14 +│ └── Happy-First-Birthday-Slash +│ └── index.html +├── archive +│ └── index.html +├── atom.css +├── atom.xml +├── atom.xsl +├── blog-with-links +│ └── index.html +├── custom-atom.css +├── custom-atom.xsl +├── custom-rss.css +├── custom-rss.xsl +├── date-matter +│ └── index.html +├── feed.json +├── heading-as-title +│ └── index.html +├── hey +│ └── my super path +│ └── héllô +│ └── index.html +├── index.html +├── mdx-blog-post +│ └── index.html +├── mdx-require-blog-post +│ └── index.html +├── page +│ ├── 2 +│ │ └── index.html +│ └── 3 +│ └── index.html +├── rss.css +├── rss.xml +├── rss.xsl +├── simple +│ └── slug +│ └── index.html +├── tags +│ ├── complex +│ │ └── index.html +│ ├── date +│ │ └── index.html +│ └── index.html +└── unlisted + └── index.html" +`; + exports[`json filters to the first two entries 1`] = ` [ "{ @@ -414,6 +786,161 @@ exports[`json filters to the first two entries using limit 1`] = ` ] `; +exports[`json has custom xslt files for feed 1`] = ` +[ + [ + "/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/feed.json", + "{ + "version": "https://jsonfeed.org/version/1", + "title": "Hello Blog", + "home_page_url": "https://docusaurus.io/myBaseUrl/blog", + "description": "Hello Blog", + "items": [ + { + "id": "https://docusaurus.io/myBaseUrl/blog/blog-with-links", + "content_html": "

    absolute full url

    /n

    absolute pathname

    /n

    relative pathname

    /n

    md link

    /n

    anchor

    /n

    relative pathname + anchor

    /n

    /n

    \\"\\"

    /n/n", + "url": "https://docusaurus.io/myBaseUrl/blog/blog-with-links", + "title": "test links", + "summary": "absolute full url", + "date_modified": "2023-07-23T00:00:00.000Z", + "tags": [] + }, + { + "id": "https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post", + "content_html": "

    Test MDX with require calls

    /n/n/n/n/n", + "url": "https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post", + "title": "MDX Blog Sample with require calls", + "summary": "Test MDX with require calls", + "date_modified": "2021-03-06T00:00:00.000Z", + "tags": [] + }, + { + "id": "https://docusaurus.io/myBaseUrl/blog/mdx-blog-post", + "content_html": "

    HTML Heading 1

    /n

    HTML Heading 2

    /n

    HTML Paragraph

    /n/n/n

    Import DOM

    /n

    Heading 1

    /n

    Heading 2

    /n

    Heading 3

    /n

    Heading 4

    /n
    Heading 5
    /n
      /n
    • list1
    • /n
    • list2
    • /n
    • list3
    • /n
    /n
      /n
    • list1
    • /n
    • list2
    • /n
    • list3
    • /n
    /n

    Normal Text Italics Text Bold Text

    /n

    link \\"image\\"

    ", + "url": "https://docusaurus.io/myBaseUrl/blog/mdx-blog-post", + "title": "Full Blog Sample", + "summary": "HTML Heading 1", + "date_modified": "2021-03-05T00:00:00.000Z", + "tags": [] + }, + { + "id": "https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô", + "content_html": "

    complex url slug

    ", + "url": "https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô", + "title": "Complex Slug", + "summary": "complex url slug", + "date_modified": "2020-08-16T00:00:00.000Z", + "tags": [ + "date", + "complex" + ] + }, + { + "id": "https://docusaurus.io/myBaseUrl/blog/simple/slug", + "content_html": "

    simple url slug

    ", + "url": "https://docusaurus.io/myBaseUrl/blog/simple/slug", + "title": "Simple Slug", + "summary": "simple url slug", + "date_modified": "2020-08-15T00:00:00.000Z", + "author": { + "name": "Sébastien Lorber", + "url": "https://sebastienlorber.com" + }, + "tags": [] + }, + { + "id": "https://docusaurus.io/myBaseUrl/blog/heading-as-title", + "content_html": "", + "url": "https://docusaurus.io/myBaseUrl/blog/heading-as-title", + "title": "some heading", + "date_modified": "2019-01-02T00:00:00.000Z", + "tags": [] + }, + { + "id": "https://docusaurus.io/myBaseUrl/blog/date-matter", + "content_html": "

    date inside front matter

    ", + "url": "https://docusaurus.io/myBaseUrl/blog/date-matter", + "title": "date-matter", + "summary": "date inside front matter", + "date_modified": "2019-01-01T00:00:00.000Z", + "tags": [ + "date" + ] + }, + { + "id": "https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash", + "content_html": "

    Happy birthday! (translated)

    ", + "url": "https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash", + "title": "Happy 1st Birthday Slash! (translated)", + "summary": "Happy birthday! (translated)", + "date_modified": "2018-12-14T00:00:00.000Z", + "author": { + "name": "Yangshun Tay (translated)" + }, + "tags": [ + "inlineTag", + "Global Tag label (en)" + ] + } + ] +}", + ], +] +`; + +exports[`json has custom xslt files for feed: blog tree 1`] = ` +"blog +├── 2018 +│ └── 12 +│ └── 14 +│ └── Happy-First-Birthday-Slash +│ └── index.html +├── archive +│ └── index.html +├── atom.css +├── atom.xml +├── atom.xsl +├── blog-with-links +│ └── index.html +├── custom-atom.css +├── custom-atom.xsl +├── custom-rss.css +├── custom-rss.xsl +├── date-matter +│ └── index.html +├── feed.json +├── heading-as-title +│ └── index.html +├── hey +│ └── my super path +│ └── héllô +│ └── index.html +├── index.html +├── mdx-blog-post +│ └── index.html +├── mdx-require-blog-post +│ └── index.html +├── page +│ ├── 2 +│ │ └── index.html +│ └── 3 +│ └── index.html +├── rss.css +├── rss.xml +├── rss.xsl +├── simple +│ └── slug +│ └── index.html +├── tags +│ ├── complex +│ │ └── index.html +│ ├── date +│ │ └── index.html +│ └── index.html +└── unlisted + └── index.html" +`; + exports[`json has feed item for each post - with trailing slash 1`] = ` [ "{ @@ -483,9 +1010,108 @@ exports[`json has feed item for each post - with trailing slash 1`] = ` "tags": [] }, { - "id": "https://docusaurus.io/myBaseUrl/blog/date-matter/", + "id": "https://docusaurus.io/myBaseUrl/blog/date-matter/", + "content_html": "

    date inside front matter

    ", + "url": "https://docusaurus.io/myBaseUrl/blog/date-matter/", + "title": "date-matter", + "summary": "date inside front matter", + "date_modified": "2019-01-01T00:00:00.000Z", + "tags": [ + "date" + ] + }, + { + "id": "https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/", + "content_html": "

    Happy birthday! (translated)

    ", + "url": "https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/", + "title": "Happy 1st Birthday Slash! (translated)", + "summary": "Happy birthday! (translated)", + "date_modified": "2018-12-14T00:00:00.000Z", + "author": { + "name": "Yangshun Tay (translated)" + }, + "tags": [ + "inlineTag", + "Global Tag label (en)" + ] + } + ] +}", +] +`; + +exports[`json has feed item for each post 1`] = ` +[ + "{ + "version": "https://jsonfeed.org/version/1", + "title": "Hello Blog", + "home_page_url": "https://docusaurus.io/myBaseUrl/blog", + "description": "Hello Blog", + "items": [ + { + "id": "https://docusaurus.io/myBaseUrl/blog/blog-with-links", + "content_html": "

    absolute full url

    /n

    absolute pathname

    /n

    relative pathname

    /n

    md link

    /n

    anchor

    /n

    relative pathname + anchor

    /n

    /n

    \\"\\"

    /n/n", + "url": "https://docusaurus.io/myBaseUrl/blog/blog-with-links", + "title": "test links", + "summary": "absolute full url", + "date_modified": "2023-07-23T00:00:00.000Z", + "tags": [] + }, + { + "id": "https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post", + "content_html": "

    Test MDX with require calls

    /n/n/n/n/n", + "url": "https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post", + "title": "MDX Blog Sample with require calls", + "summary": "Test MDX with require calls", + "date_modified": "2021-03-06T00:00:00.000Z", + "tags": [] + }, + { + "id": "https://docusaurus.io/myBaseUrl/blog/mdx-blog-post", + "content_html": "

    HTML Heading 1

    /n

    HTML Heading 2

    /n

    HTML Paragraph

    /n/n/n

    Import DOM

    /n

    Heading 1

    /n

    Heading 2

    /n

    Heading 3

    /n

    Heading 4

    /n
    Heading 5
    /n
      /n
    • list1
    • /n
    • list2
    • /n
    • list3
    • /n
    /n
      /n
    • list1
    • /n
    • list2
    • /n
    • list3
    • /n
    /n

    Normal Text Italics Text Bold Text

    /n

    link \\"image\\"

    ", + "url": "https://docusaurus.io/myBaseUrl/blog/mdx-blog-post", + "title": "Full Blog Sample", + "summary": "HTML Heading 1", + "date_modified": "2021-03-05T00:00:00.000Z", + "tags": [] + }, + { + "id": "https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô", + "content_html": "

    complex url slug

    ", + "url": "https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô", + "title": "Complex Slug", + "summary": "complex url slug", + "date_modified": "2020-08-16T00:00:00.000Z", + "tags": [ + "date", + "complex" + ] + }, + { + "id": "https://docusaurus.io/myBaseUrl/blog/simple/slug", + "content_html": "

    simple url slug

    ", + "url": "https://docusaurus.io/myBaseUrl/blog/simple/slug", + "title": "Simple Slug", + "summary": "simple url slug", + "date_modified": "2020-08-15T00:00:00.000Z", + "author": { + "name": "Sébastien Lorber", + "url": "https://sebastienlorber.com" + }, + "tags": [] + }, + { + "id": "https://docusaurus.io/myBaseUrl/blog/heading-as-title", + "content_html": "", + "url": "https://docusaurus.io/myBaseUrl/blog/heading-as-title", + "title": "some heading", + "date_modified": "2019-01-02T00:00:00.000Z", + "tags": [] + }, + { + "id": "https://docusaurus.io/myBaseUrl/blog/date-matter", "content_html": "

    date inside front matter

    ", - "url": "https://docusaurus.io/myBaseUrl/blog/date-matter/", + "url": "https://docusaurus.io/myBaseUrl/blog/date-matter", "title": "date-matter", "summary": "date inside front matter", "date_modified": "2019-01-01T00:00:00.000Z", @@ -494,9 +1120,9 @@ exports[`json has feed item for each post - with trailing slash 1`] = ` ] }, { - "id": "https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/", + "id": "https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash", "content_html": "

    Happy birthday! (translated)

    ", - "url": "https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash/", + "url": "https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash", "title": "Happy 1st Birthday Slash! (translated)", "summary": "Happy birthday! (translated)", "date_modified": "2018-12-14T00:00:00.000Z", @@ -513,9 +1139,11 @@ exports[`json has feed item for each post - with trailing slash 1`] = ` ] `; -exports[`json has feed item for each post 1`] = ` +exports[`json has xslt files for feed 1`] = ` [ - "{ + [ + "/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/feed.json", + "{ "version": "https://jsonfeed.org/version/1", "title": "Hello Blog", "home_page_url": "https://docusaurus.io/myBaseUrl/blog", @@ -609,9 +1237,63 @@ exports[`json has feed item for each post 1`] = ` } ] }", + ], ] `; +exports[`json has xslt files for feed: blog tree 1`] = ` +"blog +├── 2018 +│ └── 12 +│ └── 14 +│ └── Happy-First-Birthday-Slash +│ └── index.html +├── archive +│ └── index.html +├── atom.css +├── atom.xml +├── atom.xsl +├── blog-with-links +│ └── index.html +├── custom-atom.css +├── custom-atom.xsl +├── custom-rss.css +├── custom-rss.xsl +├── date-matter +│ └── index.html +├── feed.json +├── heading-as-title +│ └── index.html +├── hey +│ └── my super path +│ └── héllô +│ └── index.html +├── index.html +├── mdx-blog-post +│ └── index.html +├── mdx-require-blog-post +│ └── index.html +├── page +│ ├── 2 +│ │ └── index.html +│ └── 3 +│ └── index.html +├── rss.css +├── rss.xml +├── rss.xsl +├── simple +│ └── slug +│ └── index.html +├── tags +│ ├── complex +│ │ └── index.html +│ ├── date +│ │ └── index.html +│ └── index.html +└── unlisted + └── index.html" +`; + exports[`rss filters to the first two entries 1`] = ` [ " @@ -708,6 +1390,184 @@ exports[`rss filters to the first two entries using limit 1`] = ` ] `; +exports[`rss has custom xslt files for feed 1`] = ` +[ + [ + "/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.xml", + " + + + Hello Blog + https://docusaurus.io/myBaseUrl/blog + Hello Blog + Sun, 23 Jul 2023 00:00:00 GMT + https://validator.w3.org/feed/docs/rss2.html + https://github.com/jpmonette/feed + en + Copyright + + <![CDATA[test links]]> + https://docusaurus.io/myBaseUrl/blog/blog-with-links + https://docusaurus.io/myBaseUrl/blog/blog-with-links + Sun, 23 Jul 2023 00:00:00 GMT + + absolute full url

    +

    absolute pathname

    +

    relative pathname

    +

    md link

    +

    anchor

    +

    relative pathname + anchor

    +

    +

    + +]]>
    +
    + + <![CDATA[MDX Blog Sample with require calls]]> + https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post + https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post + Sat, 06 Mar 2021 00:00:00 GMT + + Test MDX with require calls

    + + + + +]]>
    +
    + + <![CDATA[Full Blog Sample]]> + https://docusaurus.io/myBaseUrl/blog/mdx-blog-post + https://docusaurus.io/myBaseUrl/blog/mdx-blog-post + Fri, 05 Mar 2021 00:00:00 GMT + + HTML Heading 1 +

    HTML Heading 2

    +

    HTML Paragraph

    + + +

    Import DOM

    +

    Heading 1

    +

    Heading 2

    +

    Heading 3

    +

    Heading 4

    +
    Heading 5
    +
      +
    • list1
    • +
    • list2
    • +
    • list3
    • +
    +
      +
    • list1
    • +
    • list2
    • +
    • list3
    • +
    +

    Normal Text Italics Text Bold Text

    +

    link image

    ]]>
    +
    + + <![CDATA[Complex Slug]]> + https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô + https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô + Sun, 16 Aug 2020 00:00:00 GMT + + complex url slug

    ]]>
    + date + complex +
    + + <![CDATA[Simple Slug]]> + https://docusaurus.io/myBaseUrl/blog/simple/slug + https://docusaurus.io/myBaseUrl/blog/simple/slug + Sat, 15 Aug 2020 00:00:00 GMT + + simple url slug

    ]]>
    +
    + + <![CDATA[some heading]]> + https://docusaurus.io/myBaseUrl/blog/heading-as-title + https://docusaurus.io/myBaseUrl/blog/heading-as-title + Wed, 02 Jan 2019 00:00:00 GMT + + + <![CDATA[date-matter]]> + https://docusaurus.io/myBaseUrl/blog/date-matter + https://docusaurus.io/myBaseUrl/blog/date-matter + Tue, 01 Jan 2019 00:00:00 GMT + + date inside front matter

    ]]>
    + date +
    + + <![CDATA[Happy 1st Birthday Slash! (translated)]]> + https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash + https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash + Fri, 14 Dec 2018 00:00:00 GMT + + Happy birthday! (translated)

    ]]>
    + lorber.sebastien@gmail.com (Sébastien Lorber (translated)) + inlineTag + Global Tag label (en) +
    +
    +
    ", + ], +] +`; + +exports[`rss has custom xslt files for feed: blog tree 1`] = ` +"blog +├── 2018 +│ └── 12 +│ └── 14 +│ └── Happy-First-Birthday-Slash +│ └── index.html +├── archive +│ └── index.html +├── atom.css +├── atom.xml +├── atom.xsl +├── blog-with-links +│ └── index.html +├── custom-atom.css +├── custom-atom.xsl +├── custom-rss.css +├── custom-rss.xsl +├── date-matter +│ └── index.html +├── feed.json +├── heading-as-title +│ └── index.html +├── hey +│ └── my super path +│ └── héllô +│ └── index.html +├── index.html +├── mdx-blog-post +│ └── index.html +├── mdx-require-blog-post +│ └── index.html +├── page +│ ├── 2 +│ │ └── index.html +│ └── 3 +│ └── index.html +├── rss.css +├── rss.xml +├── rss.xsl +├── simple +│ └── slug +│ └── index.html +├── tags +│ ├── complex +│ │ └── index.html +│ ├── date +│ │ └── index.html +│ └── index.html +└── unlisted + └── index.html" +`; + exports[`rss has feed item for each post - with trailing slash 1`] = ` [ " @@ -951,3 +1811,181 @@ exports[`rss has feed item for each post 1`] = ` ", ] `; + +exports[`rss has xslt files for feed 1`] = ` +[ + [ + "/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/rss.xml", + " + + + Hello Blog + https://docusaurus.io/myBaseUrl/blog + Hello Blog + Sun, 23 Jul 2023 00:00:00 GMT + https://validator.w3.org/feed/docs/rss2.html + https://github.com/jpmonette/feed + en + Copyright + + <![CDATA[test links]]> + https://docusaurus.io/myBaseUrl/blog/blog-with-links + https://docusaurus.io/myBaseUrl/blog/blog-with-links + Sun, 23 Jul 2023 00:00:00 GMT + + absolute full url

    +

    absolute pathname

    +

    relative pathname

    +

    md link

    +

    anchor

    +

    relative pathname + anchor

    +

    +

    + +]]>
    +
    + + <![CDATA[MDX Blog Sample with require calls]]> + https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post + https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post + Sat, 06 Mar 2021 00:00:00 GMT + + Test MDX with require calls

    + + + + +]]>
    +
    + + <![CDATA[Full Blog Sample]]> + https://docusaurus.io/myBaseUrl/blog/mdx-blog-post + https://docusaurus.io/myBaseUrl/blog/mdx-blog-post + Fri, 05 Mar 2021 00:00:00 GMT + + HTML Heading 1 +

    HTML Heading 2

    +

    HTML Paragraph

    + + +

    Import DOM

    +

    Heading 1

    +

    Heading 2

    +

    Heading 3

    +

    Heading 4

    +
    Heading 5
    +
      +
    • list1
    • +
    • list2
    • +
    • list3
    • +
    +
      +
    • list1
    • +
    • list2
    • +
    • list3
    • +
    +

    Normal Text Italics Text Bold Text

    +

    link image

    ]]>
    +
    + + <![CDATA[Complex Slug]]> + https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô + https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô + Sun, 16 Aug 2020 00:00:00 GMT + + complex url slug

    ]]>
    + date + complex +
    + + <![CDATA[Simple Slug]]> + https://docusaurus.io/myBaseUrl/blog/simple/slug + https://docusaurus.io/myBaseUrl/blog/simple/slug + Sat, 15 Aug 2020 00:00:00 GMT + + simple url slug

    ]]>
    +
    + + <![CDATA[some heading]]> + https://docusaurus.io/myBaseUrl/blog/heading-as-title + https://docusaurus.io/myBaseUrl/blog/heading-as-title + Wed, 02 Jan 2019 00:00:00 GMT + + + <![CDATA[date-matter]]> + https://docusaurus.io/myBaseUrl/blog/date-matter + https://docusaurus.io/myBaseUrl/blog/date-matter + Tue, 01 Jan 2019 00:00:00 GMT + + date inside front matter

    ]]>
    + date +
    + + <![CDATA[Happy 1st Birthday Slash! (translated)]]> + https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash + https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash + Fri, 14 Dec 2018 00:00:00 GMT + + Happy birthday! (translated)

    ]]>
    + lorber.sebastien@gmail.com (Sébastien Lorber (translated)) + inlineTag + Global Tag label (en) +
    +
    +
    ", + ], +] +`; + +exports[`rss has xslt files for feed: blog tree 1`] = ` +"blog +├── 2018 +│ └── 12 +│ └── 14 +│ └── Happy-First-Birthday-Slash +│ └── index.html +├── archive +│ └── index.html +├── atom.css +├── atom.xml +├── atom.xsl +├── blog-with-links +│ └── index.html +├── custom-atom.css +├── custom-atom.xsl +├── custom-rss.css +├── custom-rss.xsl +├── date-matter +│ └── index.html +├── feed.json +├── heading-as-title +│ └── index.html +├── hey +│ └── my super path +│ └── héllô +│ └── index.html +├── index.html +├── mdx-blog-post +│ └── index.html +├── mdx-require-blog-post +│ └── index.html +├── page +│ ├── 2 +│ │ └── index.html +│ └── 3 +│ └── index.html +├── rss.css +├── rss.xml +├── rss.xsl +├── simple +│ └── slug +│ └── index.html +├── tags +│ ├── complex +│ │ └── index.html +│ ├── date +│ │ └── index.html +│ └── index.html +└── unlisted + └── index.html" +`; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap index 4406ef7b72eb..ce4af321ff02 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`validateOptions throws Error in case of invalid feed type 1`] = `""feedOptions.type" does not match any of the allowed types"`; +exports[`validateOptions feed throws Error in case of invalid feed type 1`] = `""feedOptions.type" does not match any of the allowed types"`; exports[`validateOptions throws Error in case of invalid options 1`] = `""postsPerPage" must be greater than or equal to 1"`; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts index ef57b4c7d63e..3cca6f94aaf9 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts @@ -10,13 +10,15 @@ import path from 'path'; import fs from 'fs-extra'; import {DEFAULT_PARSE_FRONT_MATTER} from '@docusaurus/utils'; import {fromPartial} from '@total-typescript/shoehorn'; -import {DEFAULT_OPTIONS} from '../options'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; +import tree from 'tree-node-cli'; +import {DEFAULT_OPTIONS, validateOptions} from '../options'; import {generateBlogPosts} from '../blogUtils'; import {createBlogFeedFiles} from '../feed'; import {getAuthorsMap} from '../authorsMap'; -import type {LoadContext, I18n} from '@docusaurus/types'; +import type {LoadContext, I18n, Validate} from '@docusaurus/types'; import type {BlogContentPaths} from '../types'; -import type {PluginOptions} from '@docusaurus/plugin-content-blog'; +import type {Options, PluginOptions} from '@docusaurus/plugin-content-blog'; const DefaultI18N: I18n = { currentLocale: 'en', @@ -50,8 +52,16 @@ function getBlogContentPaths(siteDir: string): BlogContentPaths { async function testGenerateFeeds( context: LoadContext, - options: PluginOptions, + optionsInput: Options, ): Promise { + const options = validateOptions({ + validate: normalizePluginOptions as Validate< + Options | undefined, + PluginOptions + >, + options: optionsInput, + }); + const contentPaths = getBlogContentPaths(context.siteDir); const authorsMap = await getAuthorsMap({ contentPaths, @@ -72,10 +82,11 @@ async function testGenerateFeeds( siteConfig: context.siteConfig, outDir: context.outDir, locale: 'en', + contentPaths, }); } -describe.each(['atom', 'rss', 'json'])('%s', (feedType) => { +describe.each(['atom', 'rss', 'json'] as const)('%s', (feedType) => { const fsMock = jest.spyOn(fs, 'outputFile').mockImplementation(() => {}); it('does not get generated without posts', async () => { @@ -105,13 +116,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => { feedOptions: { type: [feedType], copyright: 'Copyright', + xslt: {atom: null, rss: null}, }, readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}), truncateMarker: //, onInlineTags: 'ignore', onInlineAuthors: 'ignore', - } as PluginOptions, + }, ); expect(fsMock).toHaveBeenCalledTimes(0); @@ -148,13 +160,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => { feedOptions: { type: [feedType], copyright: 'Copyright', + xslt: {atom: null, rss: null}, }, readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}), truncateMarker: //, onInlineTags: 'ignore', onInlineAuthors: 'ignore', - } as PluginOptions, + }, ); expect( @@ -203,13 +216,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => { ...rest, }); }, + xslt: {atom: null, rss: null}, }, readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}), truncateMarker: //, onInlineTags: 'ignore', onInlineAuthors: 'ignore', - } as PluginOptions, + }, ); expect( @@ -249,13 +263,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => { type: [feedType], copyright: 'Copyright', limit: 2, + xslt: {atom: null, rss: null}, }, readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}), truncateMarker: //, onInlineTags: 'ignore', onInlineAuthors: 'ignore', - } as PluginOptions, + }, ); expect( @@ -295,13 +310,14 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => { feedOptions: { type: [feedType], copyright: 'Copyright', + xslt: {atom: null, rss: null}, }, readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}), truncateMarker: //, onInlineTags: 'ignore', onInlineAuthors: 'ignore', - } as PluginOptions, + }, ); expect( @@ -309,4 +325,99 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => { ).toMatchSnapshot(); fsMock.mockClear(); }); + + it('has xslt files for feed', async () => { + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + const outDir = path.join(siteDir, 'build-snap'); + const siteConfig = { + title: 'Hello', + baseUrl: '/myBaseUrl/', + url: 'https://docusaurus.io', + favicon: 'image/favicon.ico', + markdown, + }; + + // Build is quite difficult to mock, so we built the blog beforehand and + // copied the output to the fixture... + await testGenerateFeeds( + fromPartial({ + siteDir, + siteConfig, + i18n: DefaultI18N, + outDir, + }), + { + path: 'blog', + routeBasePath: 'blog', + tagsBasePath: 'tags', + authorsMapPath: 'authors.yml', + include: DEFAULT_OPTIONS.include, + exclude: DEFAULT_OPTIONS.exclude, + feedOptions: { + type: [feedType], + copyright: 'Copyright', + xslt: true, + }, + readingTime: ({content, defaultReadingTime}) => + defaultReadingTime({content}), + truncateMarker: //, + onInlineTags: 'ignore', + onInlineAuthors: 'ignore', + }, + ); + + expect(tree(path.join(outDir, 'blog'))).toMatchSnapshot('blog tree'); + + expect(fsMock.mock.calls).toMatchSnapshot(); + fsMock.mockClear(); + }); + + it('has custom xslt files for feed', async () => { + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + const outDir = path.join(siteDir, 'build-snap'); + const siteConfig = { + title: 'Hello', + baseUrl: '/myBaseUrl/', + url: 'https://docusaurus.io', + favicon: 'image/favicon.ico', + markdown, + }; + + // Build is quite difficult to mock, so we built the blog beforehand and + // copied the output to the fixture... + await testGenerateFeeds( + fromPartial({ + siteDir, + siteConfig, + i18n: DefaultI18N, + outDir, + }), + { + path: 'blog', + routeBasePath: 'blog', + tagsBasePath: 'tags', + authorsMapPath: 'authors.yml', + include: DEFAULT_OPTIONS.include, + exclude: DEFAULT_OPTIONS.exclude, + feedOptions: { + type: [feedType], + copyright: 'Copyright', + xslt: { + rss: 'custom-rss.xsl', + atom: 'custom-atom.xsl', + }, + }, + readingTime: ({content, defaultReadingTime}) => + defaultReadingTime({content}), + truncateMarker: //, + onInlineTags: 'ignore', + onInlineAuthors: 'ignore', + }, + ); + + expect(tree(path.join(outDir, 'blog'))).toMatchSnapshot('blog tree'); + + expect(fsMock.mock.calls).toMatchSnapshot(); + fsMock.mockClear(); + }); }); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts index 050e2a016023..254d56b96b2a 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts @@ -6,8 +6,12 @@ */ import {normalizePluginOptions} from '@docusaurus/utils-validation'; -import {validateOptions, DEFAULT_OPTIONS} from '../options'; -import type {Options, PluginOptions} from '@docusaurus/plugin-content-blog'; +import {validateOptions, DEFAULT_OPTIONS, XSLTBuiltInPaths} from '../options'; +import type { + Options, + PluginOptions, + UserFeedOptions, +} from '@docusaurus/plugin-content-blog'; import type {Validate} from '@docusaurus/types'; function testValidate(options?: Options) { @@ -38,7 +42,10 @@ describe('validateOptions', () => { it('accepts correctly defined user options', () => { const userOptions: Options = { ...defaultOptions, - feedOptions: {type: 'rss' as const, title: 'myTitle'}, + feedOptions: { + type: 'rss' as const, + title: 'myTitle', + }, path: 'not_blog', routeBasePath: '/myBlog', postsPerPage: 5, @@ -48,7 +55,13 @@ describe('validateOptions', () => { }; expect(testValidate(userOptions)).toEqual({ ...userOptions, - feedOptions: {type: ['rss'], title: 'myTitle', copyright: '', limit: 20}, + feedOptions: { + type: ['rss'], + title: 'myTitle', + copyright: '', + limit: 20, + xslt: {rss: null, atom: null}, + }, }); }); @@ -79,60 +92,182 @@ describe('validateOptions', () => { ).toThrowErrorMatchingSnapshot(); }); - it('throws Error in case of invalid feed type', () => { - expect(() => - testValidate({ + describe('feed', () => { + it('throws Error in case of invalid feed type', () => { + expect(() => + testValidate({ + feedOptions: { + // @ts-expect-error: test + type: 'none', + }, + }), + ).toThrowErrorMatchingSnapshot(); + }); + + it('converts all feed type to array with other feed type', () => { + expect( + testValidate({ + feedOptions: {type: 'all'}, + }), + ).toEqual({ + ...defaultOptions, feedOptions: { - // @ts-expect-error: test - type: 'none', + type: ['rss', 'atom', 'json'], + copyright: '', + limit: 20, + xslt: {rss: null, atom: null}, }, - }), - ).toThrowErrorMatchingSnapshot(); - }); + }); + }); - it('converts all feed type to array with other feed type', () => { - expect( - testValidate({ - feedOptions: {type: 'all'}, - }), - ).toEqual({ - ...defaultOptions, - feedOptions: {type: ['rss', 'atom', 'json'], copyright: '', limit: 20}, + it('accepts null feed type and return same', () => { + expect( + testValidate({ + feedOptions: {type: null}, + }), + ).toEqual({ + ...defaultOptions, + feedOptions: { + type: null, + limit: 20, + xslt: {rss: null, atom: null}, + }, + }); }); - }); - it('accepts null type and return same', () => { - expect( - testValidate({ - feedOptions: {type: null}, - }), - ).toEqual({ - ...defaultOptions, - feedOptions: {type: null, limit: 20}, + it('contains array with rss + atom for missing feed type', () => { + expect( + testValidate({ + feedOptions: {}, + }), + ).toEqual(defaultOptions); }); - }); - it('contains array with rss + atom for missing feed type', () => { - expect( - testValidate({ - feedOptions: {}, - }), - ).toEqual(defaultOptions); - }); + it('has array with rss + atom, title for missing feed type', () => { + expect( + testValidate({ + feedOptions: {title: 'title'}, + }), + ).toEqual({ + ...defaultOptions, + feedOptions: { + type: ['rss', 'atom'], + title: 'title', + copyright: '', + limit: 20, + xslt: {rss: null, atom: null}, + }, + }); + }); - it('has array with rss + atom, title for missing feed type', () => { - expect( - testValidate({ - feedOptions: {title: 'title'}, - }), - ).toEqual({ - ...defaultOptions, - feedOptions: { - type: ['rss', 'atom'], - title: 'title', - copyright: '', - limit: 20, - }, + describe('feed xslt', () => { + function testXSLT(xslt: UserFeedOptions['xslt']) { + return testValidate({feedOptions: {xslt}}).feedOptions.xslt; + } + + it('accepts xslt: true', () => { + expect(testXSLT(true)).toEqual({ + rss: XSLTBuiltInPaths.rss, + atom: XSLTBuiltInPaths.atom, + }); + }); + + it('accepts xslt: false', () => { + expect(testXSLT(false)).toEqual({ + rss: null, + atom: null, + }); + }); + + it('accepts xslt: null', () => { + expect(testXSLT(null)).toEqual({ + rss: null, + atom: null, + }); + }); + + it('accepts xslt: undefined', () => { + expect(testXSLT(undefined)).toEqual({ + rss: null, + atom: null, + }); + }); + + it('accepts xslt: {rss: true}', () => { + expect(testXSLT({rss: true})).toEqual({ + rss: XSLTBuiltInPaths.rss, + atom: null, + }); + }); + + it('accepts xslt: {atom: true}', () => { + expect(testXSLT({atom: true})).toEqual({ + rss: null, + atom: XSLTBuiltInPaths.atom, + }); + }); + + it('accepts xslt: {rss: true, atom: true}', () => { + expect(testXSLT({rss: true, atom: true})).toEqual({ + rss: XSLTBuiltInPaths.rss, + atom: XSLTBuiltInPaths.atom, + }); + }); + + it('accepts xslt: {rss: "custom-path"}', () => { + expect(testXSLT({rss: 'custom-path'})).toEqual({ + rss: 'custom-path', + atom: null, + }); + }); + + it('accepts xslt: {rss: true, atom: "custom-path"}', () => { + expect(testXSLT({rss: true, atom: 'custom-path'})).toEqual({ + rss: XSLTBuiltInPaths.rss, + atom: 'custom-path', + }); + }); + + it('accepts xslt: {rss: null, atom: true}', () => { + expect(testXSLT({rss: null, atom: true})).toEqual({ + rss: null, + atom: XSLTBuiltInPaths.atom, + }); + }); + + it('accepts xslt: {rss: false, atom: null}', () => { + expect(testXSLT({rss: false, atom: null})).toEqual({ + rss: null, + atom: null, + }); + }); + + it('rejects xslt: 42', () => { + // @ts-expect-error: bad type + expect(() => testXSLT(42)).toThrowErrorMatchingInlineSnapshot( + `""feedOptions.xslt" must be one of [object, boolean]"`, + ); + }); + it('rejects xslt: []', () => { + // @ts-expect-error: bad type + expect(() => testXSLT([])).toThrowErrorMatchingInlineSnapshot( + `""feedOptions.xslt" must be one of [object, boolean]"`, + ); + }); + + it('rejects xslt: {rss: 42}', () => { + // @ts-expect-error: bad type + expect(() => testXSLT({rss: 42})).toThrowErrorMatchingInlineSnapshot( + `""feedOptions.xslt.rss" must be one of [string, boolean]"`, + ); + }); + + it('rejects xslt: {rss: []}', () => { + // @ts-expect-error: bad type + expect(() => testXSLT({rss: 42})).toThrowErrorMatchingInlineSnapshot( + `""feedOptions.xslt.rss" must be one of [string, boolean]"`, + ); + }); }); }); diff --git a/packages/docusaurus-plugin-content-blog/src/feed.ts b/packages/docusaurus-plugin-content-blog/src/feed.ts index b8bc8c1481c9..96f252f23070 100644 --- a/packages/docusaurus-plugin-content-blog/src/feed.ts +++ b/packages/docusaurus-plugin-content-blog/src/feed.ts @@ -7,15 +7,20 @@ import path from 'path'; import fs from 'fs-extra'; -import logger from '@docusaurus/logger'; import {Feed, type Author as FeedAuthor} from 'feed'; import * as srcset from 'srcset'; -import {normalizeUrl, readOutputHTMLFile} from '@docusaurus/utils'; +import { + getDataFilePath, + normalizeUrl, + readOutputHTMLFile, +} from '@docusaurus/utils'; import { blogPostContainerID, applyTrailingSlash, } from '@docusaurus/utils-common'; import {load as cheerioLoad} from 'cheerio'; +import logger from '@docusaurus/logger'; +import type {BlogContentPaths} from './types'; import type {DocusaurusConfig, HtmlTags, LoadContext} from '@docusaurus/types'; import type { FeedType, @@ -23,6 +28,8 @@ import type { Author, BlogPost, BlogFeedItem, + FeedOptions, + FeedXSLTOptions, } from '@docusaurus/plugin-content-blog'; async function generateBlogFeed({ @@ -180,32 +187,144 @@ async function defaultCreateFeedItems({ ); } +async function resolveXsltFilePaths({ + xsltFilePath, + contentPaths, +}: { + xsltFilePath: string; + contentPaths: BlogContentPaths; +}) { + const xsltAbsolutePath: string = path.isAbsolute(xsltFilePath) + ? xsltFilePath + : (await getDataFilePath({filePath: xsltFilePath, contentPaths})) ?? + path.resolve(contentPaths.contentPath, xsltFilePath); + + if (!(await fs.pathExists(xsltAbsolutePath))) { + throw new Error( + logger.interpolate`Blog feed XSLT file not found at path=${path.relative( + process.cwd(), + xsltAbsolutePath, + )}`, + ); + } + + const parsedPath = path.parse(xsltAbsolutePath); + const cssAbsolutePath = path.resolve( + parsedPath.dir, + `${parsedPath.name}.css`, + ); + if (!(await fs.pathExists(xsltAbsolutePath))) { + throw new Error( + logger.interpolate`Blog feed XSLT file was found at path=${path.relative( + process.cwd(), + xsltAbsolutePath, + )} +But its expected co-located CSS file could not be found at path=${path.relative( + process.cwd(), + cssAbsolutePath, + )} +If you want to provide a custom XSLT file, you must provide a CSS file with the exact same name.`, + ); + } + + return {xsltAbsolutePath, cssAbsolutePath}; +} + +async function generateXsltFiles({ + xsltFilePath, + generatePath, + contentPaths, +}: { + xsltFilePath: string; + generatePath: string; + contentPaths: BlogContentPaths; +}) { + const {xsltAbsolutePath, cssAbsolutePath} = await resolveXsltFilePaths({ + xsltFilePath, + contentPaths, + }); + const xsltOutputPath = path.join( + generatePath, + path.basename(xsltAbsolutePath), + ); + const cssOutputPath = path.join(generatePath, path.basename(cssAbsolutePath)); + await fs.copy(xsltAbsolutePath, xsltOutputPath); + await fs.copy(cssAbsolutePath, cssOutputPath); +} + +// This modifies the XML feed content to add a relative href to the XSLT file +// Good enough for now: we probably don't need a full XML parser just for that +// See also https://darekkay.com/blog/rss-styling/ +function injectXslt({ + feedContent, + xsltFilePath, +}: { + feedContent: string; + xsltFilePath: string; +}) { + return feedContent.replace( + '', + ``, + ); +} + +const FeedConfigs: Record< + FeedType, + { + outputFileName: string; + getContent: (feed: Feed) => string; + getXsltFilePath: (xslt: FeedXSLTOptions) => string | null; + } +> = { + rss: { + outputFileName: 'rss.xml', + getContent: (feed) => feed.rss2(), + getXsltFilePath: (xslt) => xslt.rss, + }, + atom: { + outputFileName: 'atom.xml', + getContent: (feed) => feed.atom1(), + getXsltFilePath: (xslt) => xslt.atom, + }, + json: { + outputFileName: 'feed.json', + getContent: (feed) => feed.json1(), + getXsltFilePath: () => null, + }, +}; + async function createBlogFeedFile({ feed, feedType, generatePath, + feedOptions, + contentPaths, }: { feed: Feed; feedType: FeedType; generatePath: string; + feedOptions: FeedOptions; + contentPaths: BlogContentPaths; }) { - const [feedContent, feedPath] = (() => { - switch (feedType) { - case 'rss': - return [feed.rss2(), 'rss.xml']; - case 'json': - return [feed.json1(), 'feed.json']; - case 'atom': - return [feed.atom1(), 'atom.xml']; - default: - throw new Error(`Feed type ${feedType} not supported.`); - } - })(); try { - await fs.outputFile(path.join(generatePath, feedPath), feedContent); + const feedConfig = FeedConfigs[feedType]; + + let feedContent = feedConfig.getContent(feed); + + const xsltFilePath = feedConfig.getXsltFilePath(feedOptions.xslt); + if (xsltFilePath) { + await generateXsltFiles({xsltFilePath, contentPaths, generatePath}); + feedContent = injectXslt({feedContent, xsltFilePath}); + } + + const outputPath = path.join(generatePath, feedConfig.outputFileName); + await fs.outputFile(outputPath, feedContent); } catch (err) { - logger.error(`Generating ${feedType} feed failed.`); - throw err; + throw new Error(`Generating ${feedType} feed failed.`, { + cause: err as Error, + }); } } @@ -222,12 +341,14 @@ export async function createBlogFeedFiles({ siteConfig, outDir, locale, + contentPaths, }: { blogPosts: BlogPost[]; options: PluginOptions; siteConfig: DocusaurusConfig; outDir: string; locale: string; + contentPaths: BlogContentPaths; }): Promise { const blogPosts = allBlogPosts.filter(shouldBeInFeed); @@ -250,6 +371,8 @@ export async function createBlogFeedFiles({ feed, feedType, generatePath: path.join(outDir, options.routeBasePath), + feedOptions: options.feedOptions, + contentPaths, }), ), ); diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts index 679924729e5f..ea2652c57fe8 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.ts +++ b/packages/docusaurus-plugin-content-blog/src/index.ts @@ -388,6 +388,7 @@ export default async function pluginContentBlog( outDir, siteConfig, locale: currentLocale, + contentPaths, }); }, diff --git a/packages/docusaurus-plugin-content-blog/src/options.ts b/packages/docusaurus-plugin-content-blog/src/options.ts index 20e0c3427948..e9d91d3bf449 100644 --- a/packages/docusaurus-plugin-content-blog/src/options.ts +++ b/packages/docusaurus-plugin-content-blog/src/options.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import path from 'path'; import { Joi, RemarkPluginsSchema, @@ -19,11 +20,20 @@ import type { PluginOptions, Options, FeedType, + FeedXSLTOptions, } from '@docusaurus/plugin-content-blog'; import type {OptionValidationContext} from '@docusaurus/types'; export const DEFAULT_OPTIONS: PluginOptions = { - feedOptions: {type: ['rss', 'atom'], copyright: '', limit: 20}, + feedOptions: { + type: ['rss', 'atom'], + copyright: '', + limit: 20, + xslt: { + rss: null, + atom: null, + }, + }, beforeDefaultRehypePlugins: [], beforeDefaultRemarkPlugins: [], admonitions: true, @@ -64,6 +74,94 @@ export const DEFAULT_OPTIONS: PluginOptions = { onInlineAuthors: 'warn', }; +export const XSLTBuiltInPaths = { + rss: path.resolve(__dirname, '..', 'assets', 'rss.xsl'), + atom: path.resolve(__dirname, '..', 'assets', 'atom.xsl'), +}; + +function normalizeXsltOption( + option: string | null | boolean, + type: 'rss' | 'atom', +): string | null { + if (typeof option === 'string') { + return option; + } + if (option === true) { + return XSLTBuiltInPaths[type]; + } + return null; +} + +function createXSLTFilePathSchema(type: 'atom' | 'rss') { + return Joi.alternatives() + .try( + Joi.string().required(), + Joi.boolean() + .allow(null, () => undefined) + .custom((val) => normalizeXsltOption(val, type)), + ) + .optional() + .default(null); +} + +const FeedXSLTOptionsSchema = Joi.alternatives() + .try( + Joi.object({ + rss: createXSLTFilePathSchema('rss'), + atom: createXSLTFilePathSchema('atom'), + }).required(), + Joi.boolean() + .allow(null, () => undefined) + .custom((val) => ({ + rss: normalizeXsltOption(val, 'rss'), + atom: normalizeXsltOption(val, 'atom'), + })), + ) + .optional() + .custom((val) => { + if (val === null) { + return { + rss: null, + atom: null, + }; + } + return val; + }) + .default(DEFAULT_OPTIONS.feedOptions.xslt); + +const FeedOptionsSchema = Joi.object({ + type: Joi.alternatives() + .try( + Joi.array().items(Joi.string().equal('rss', 'atom', 'json')), + Joi.alternatives().conditional( + Joi.string().equal('all', 'rss', 'atom', 'json'), + { + then: Joi.custom((val: FeedType | 'all') => + val === 'all' ? ['rss', 'atom', 'json'] : [val], + ), + }, + ), + ) + .allow(null) + .default(DEFAULT_OPTIONS.feedOptions.type), + xslt: FeedXSLTOptionsSchema, + title: Joi.string().allow(''), + description: Joi.string().allow(''), + // Only add default value when user actually wants a feed (type is not null) + copyright: Joi.when('type', { + is: Joi.any().valid(null), + then: Joi.string().optional(), + otherwise: Joi.string() + .allow('') + .default(DEFAULT_OPTIONS.feedOptions.copyright), + }), + language: Joi.string(), + createFeedItems: Joi.function(), + limit: Joi.alternatives() + .try(Joi.number(), Joi.valid(null), Joi.valid(false)) + .default(DEFAULT_OPTIONS.feedOptions.limit), +}).default(DEFAULT_OPTIONS.feedOptions); + const PluginOptionSchema = Joi.object({ path: Joi.string().default(DEFAULT_OPTIONS.path), archiveBasePath: Joi.string() @@ -116,37 +214,7 @@ const PluginOptionSchema = Joi.object({ beforeDefaultRehypePlugins: RehypePluginsSchema.default( DEFAULT_OPTIONS.beforeDefaultRehypePlugins, ), - feedOptions: Joi.object({ - type: Joi.alternatives() - .try( - Joi.array().items(Joi.string().equal('rss', 'atom', 'json')), - Joi.alternatives().conditional( - Joi.string().equal('all', 'rss', 'atom', 'json'), - { - then: Joi.custom((val: FeedType | 'all') => - val === 'all' ? ['rss', 'atom', 'json'] : [val], - ), - }, - ), - ) - .allow(null) - .default(DEFAULT_OPTIONS.feedOptions.type), - title: Joi.string().allow(''), - description: Joi.string().allow(''), - // Only add default value when user actually wants a feed (type is not null) - copyright: Joi.when('type', { - is: Joi.any().valid(null), - then: Joi.string().optional(), - otherwise: Joi.string() - .allow('') - .default(DEFAULT_OPTIONS.feedOptions.copyright), - }), - language: Joi.string(), - createFeedItems: Joi.function(), - limit: Joi.alternatives() - .try(Joi.number(), Joi.valid(null), Joi.valid(false)) - .default(DEFAULT_OPTIONS.feedOptions.limit), - }).default(DEFAULT_OPTIONS.feedOptions), + feedOptions: FeedOptionsSchema, authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath), readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime), sortPosts: Joi.string() diff --git a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts index e0e5617ce792..02e98f0b1e3b 100644 --- a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts +++ b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts @@ -315,10 +315,26 @@ declare module '@docusaurus/plugin-content-blog' { }) => string | undefined; export type FeedType = 'rss' | 'atom' | 'json'; + + export type FeedXSLTOptions = { + /** + * RSS XSLT file path, relative to the blog content folder. + * If null, no XSLT file is used and the feed will be displayed as raw XML. + */ + rss: string | null; + /** + * Atom XSLT file path, relative to the blog content folder. + * If null, no XSLT file is used and the feed will be displayed as raw XML. + */ + atom: string | null; + }; + /** * Normalized feed options used within code. */ export type FeedOptions = { + /** Enable feeds xslt stylesheets */ + xslt: FeedXSLTOptions; /** If `null`, no feed is generated. */ type?: FeedType[] | null; /** Title of generated feed. */ @@ -507,6 +523,14 @@ declare module '@docusaurus/plugin-content-blog' { onInlineAuthors: 'ignore' | 'log' | 'warn' | 'throw'; }; + export type UserFeedXSLTOptions = + | boolean + | null + | { + rss?: string | boolean | null; + atom?: string | boolean | null; + }; + /** * Feed options, as provided by user config. `type` accepts `all` as shortcut */ @@ -515,6 +539,8 @@ declare module '@docusaurus/plugin-content-blog' { { /** Type of feed to be generated. Use `null` to disable generation. */ type?: FeedOptions['type'] | 'all' | FeedType; + /** User-provided XSLT config for feeds, un-normalized */ + xslt?: UserFeedXSLTOptions; } >; /** diff --git a/project-words.txt b/project-words.txt index 528d635318ee..8ff86be90d0e 100644 --- a/project-words.txt +++ b/project-words.txt @@ -412,6 +412,8 @@ webpackbar webstorm Wolcott Xplorer +xslt +XSLT XSOAR Yacop yangshun diff --git a/website/_dogfooding/_blog tests/custom-atom.css b/website/_dogfooding/_blog tests/custom-atom.css new file mode 100644 index 000000000000..c016178d9007 --- /dev/null +++ b/website/_dogfooding/_blog tests/custom-atom.css @@ -0,0 +1,76 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +* { + color: #0d1137; +} + +main { + flex: 1 0 auto; + width: 100%; + margin: 4rem auto; + padding: 1.5 rem; + max-width: 800px; + /* stylelint-disable-next-line font-family-name-quotes */ + font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell; +} + +.info { + display: block; + margin: 3rem 0; + padding: 2rem 3rem; + border: 1px solid dodgerblue; + border-left-width: 0.5rem; + border-radius: 0.4rem; + background-color: #e52165; +} + +.rss-icon { + height: 3.8rem; + width: 3.8rem; + margin-right: 1rem; +} + +.flex { + display: flex; +} + +.items-start { + align-items: flex-start; +} + +.pb-7 { + padding-bottom: 3rem; +} + +a { + color: #005aff; + text-decoration: none; +} + +h1 { + text-wrap: balance; + font-size: 3.8rem; + line-height: 1; + font-weight: 800; + margin-bottom: 4rem; +} + +h2 { + font-size: 3rem; + line-height: 1.2; + font-weight: 700; + margin-bottom: 3rem; +} + +h2:not(:first-child) { + margin-top: 5.8rem; +} + +.italic { + font-style: italic; +} diff --git a/website/_dogfooding/_blog tests/custom-atom.xsl b/website/_dogfooding/_blog tests/custom-atom.xsl new file mode 100644 index 000000000000..560d535302b5 --- /dev/null +++ b/website/_dogfooding/_blog tests/custom-atom.xsl @@ -0,0 +1,94 @@ + + + + + + + + + Atom Feed | <xsl:value-of + select="atom:feed/atom:title" + /> + + + +
    +
    +
    + This is an Atom feed. Subscribe by copying the URL from the address + bar into your newsreader. Visit About Feeds to learn more + and get started. It’s free.
    +

    +
    + + + + + + + + + + +
    + Custom Atom Feed Preview

    +

    + +

    +

    Description:

    +
    +

    Recent Posts

    +
    + +
    + + + +
    Published on +
    +
    + +
    +
    +
    +
    +
    + + +
    + +
    diff --git a/website/_dogfooding/_blog tests/custom-rss.css b/website/_dogfooding/_blog tests/custom-rss.css new file mode 100644 index 000000000000..c016178d9007 --- /dev/null +++ b/website/_dogfooding/_blog tests/custom-rss.css @@ -0,0 +1,76 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +* { + color: #0d1137; +} + +main { + flex: 1 0 auto; + width: 100%; + margin: 4rem auto; + padding: 1.5 rem; + max-width: 800px; + /* stylelint-disable-next-line font-family-name-quotes */ + font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell; +} + +.info { + display: block; + margin: 3rem 0; + padding: 2rem 3rem; + border: 1px solid dodgerblue; + border-left-width: 0.5rem; + border-radius: 0.4rem; + background-color: #e52165; +} + +.rss-icon { + height: 3.8rem; + width: 3.8rem; + margin-right: 1rem; +} + +.flex { + display: flex; +} + +.items-start { + align-items: flex-start; +} + +.pb-7 { + padding-bottom: 3rem; +} + +a { + color: #005aff; + text-decoration: none; +} + +h1 { + text-wrap: balance; + font-size: 3.8rem; + line-height: 1; + font-weight: 800; + margin-bottom: 4rem; +} + +h2 { + font-size: 3rem; + line-height: 1.2; + font-weight: 700; + margin-bottom: 3rem; +} + +h2:not(:first-child) { + margin-top: 5.8rem; +} + +.italic { + font-style: italic; +} diff --git a/website/_dogfooding/_blog tests/custom-rss.xsl b/website/_dogfooding/_blog tests/custom-rss.xsl new file mode 100644 index 000000000000..dd868860c2d6 --- /dev/null +++ b/website/_dogfooding/_blog tests/custom-rss.xsl @@ -0,0 +1,92 @@ + + + + + + + + + RSS Feed | <xsl:value-of select="rss/channel/title" /> + + + +
    +
    +
    + This is an RSS feed. Subscribe by copying the URL from the address + bar into your newsreader. Visit About Feeds to learn more + and get started. It’s free.
    +

    +
    + + + + + + + + + + +
    + Custom RSS Feed Preview

    +

    + +

    +

    Description:

    +
    +

    Recent Posts

    +
    + +
    + + + +
    Published on +
    +
    + +
    +
    +
    +
    +
    + + +
    + +
    diff --git a/website/_dogfooding/dogfooding.config.ts b/website/_dogfooding/dogfooding.config.ts index f68020dca475..d31bce0776d7 100644 --- a/website/_dogfooding/dogfooding.config.ts +++ b/website/_dogfooding/dogfooding.config.ts @@ -88,6 +88,10 @@ export const dogfoodingPluginInstances: PluginConfig[] = [ type: 'all', title: 'Docusaurus Tests Blog', copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`, + xslt: { + rss: 'custom-rss.xsl', + atom: 'custom-atom.xsl', + }, }, readingTime: ({content, frontMatter, defaultReadingTime}) => frontMatter.hide_reading_time diff --git a/website/docs/api/plugins/plugin-content-blog.mdx b/website/docs/api/plugins/plugin-content-blog.mdx index 3ad31c590296..9b2d50d96d2e 100644 --- a/website/docs/api/plugins/plugin-content-blog.mdx +++ b/website/docs/api/plugins/plugin-content-blog.mdx @@ -77,6 +77,7 @@ Accepted fields: | `feedOptions.title` | `string` | `siteConfig.title` | Title of the feed. | | `feedOptions.description` | `string` | \`$\{siteConfig.title} Blog\` | Description of the feed. | | `feedOptions.copyright` | `string` | `undefined` | Copyright message. | +| `feedOptions.xslt` | boolean \| [FeedXSLTOptions](#FeedXSLTOptions) | `undefined` | Copyright message. | | `feedOptions.language` | `string` (See [documentation](http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes) for possible values) | `undefined` | Language metadata of the feed. | | `sortPosts` | 'descending' \| 'ascending' | `'descending'` | Governs the direction of blog post sorting. | | `processBlogPosts` | [ProcessBlogPostsFn](#ProcessBlogPostsFn) | `undefined` | An optional function which can be used to transform blog posts (filter, modify, delete, etc...). | @@ -129,6 +130,25 @@ type ReadingTimeFn = (params: { type FeedType = 'rss' | 'atom' | 'json'; ``` +#### `FeedXSLTOptions` {#FeedXSLTOptions} + +Permits to style the blog XML feeds so that browsers render them nicely with [XSLT](https://developer.mozilla.org/en-US/docs/Web/XSLT). + +- Use `true` to let the blog use its built-in `.xsl` and `.css` files to style the blog feed +- Use a falsy value (`undefined | null | false`) to disable the feature +- Use a `string` to provide a file path to a custom `.xsl` file relative to the blog content folder. By convention, you must provide a `.css` file with the exact same name. + +```ts +type FeedXSLTOptions = + | boolean + | undefined + | null + | { + rss?: string | boolean | null | undefined; + atom?: string | boolean | null | undefined; + }; +``` + #### `CreateFeedItemsFn` {#CreateFeedItemsFn} ```ts diff --git a/website/docs/blog.mdx b/website/docs/blog.mdx index e6cf18644e98..468e42f3b929 100644 --- a/website/docs/blog.mdx +++ b/website/docs/blog.mdx @@ -602,9 +602,18 @@ type BlogOptions = { title?: string; description?: string; copyright: string; + language?: string; // possible values: http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes limit?: number | false | null; // defaults to 20 - /** Allow control over the construction of BlogFeedItems */ + // XSLT permits browsers to style and render nicely the feed XML files + xslt?: + | boolean + | { + // + rss?: string | boolean; + atom?: string | boolean; + }; + // Allow control over the construction of BlogFeedItems createFeedItems?: (params: { blogPosts: BlogPost[]; siteConfig: DocusaurusConfig; diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 70d7fd33b78f..26a3e265f0af 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -487,7 +487,10 @@ export default async function createConfigAsync() { postsPerPage: 5, feedOptions: { type: 'all', + description: + 'Keep up to date with upcoming Docusaurus releases and articles by following our feed!', copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`, + xslt: true, }, blogTitle: 'Docusaurus blog', blogDescription: 'Read blog posts about Docusaurus from the team', From 087a32971fe022a8d94c0b106d82ed24f98ef1bc Mon Sep 17 00:00:00 2001 From: Ashiq Firoz <64600806+ashiq-firoz@users.noreply.github.com> Date: Thu, 8 Aug 2024 19:16:51 +0530 Subject: [PATCH 06/10] fix(cli): Fix bad docusaurus CLI behavior on for --version, -V, --help, -h (#10368) Co-authored-by: sebastien --- packages/docusaurus/bin/docusaurus.mjs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/docusaurus/bin/docusaurus.mjs b/packages/docusaurus/bin/docusaurus.mjs index 7429f4f4156e..11ac62cadbfb 100755 --- a/packages/docusaurus/bin/docusaurus.mjs +++ b/packages/docusaurus/bin/docusaurus.mjs @@ -222,7 +222,8 @@ cli cli.arguments('').action((cmd) => { cli.outputHelp(); - logger.error` Unknown command name=${cmd}.`; + logger.error`Unknown Docusaurus CLI command name=${cmd}.`; + process.exit(1); }); // === The above is the commander configuration === @@ -247,24 +248,24 @@ function isInternalCommand(command) { ); } -// process.argv always looks like this: -// [ -// '/path/to/node', -// '/path/to/docusaurus.mjs', -// '', -// ...subcommandArgs -// ] +/** + * @param {string | undefined} command + */ +function isExternalCommand(command) { + return !!(command && !isInternalCommand(command) && !command.startsWith('-')); +} -// There is no subcommand -// TODO: can we use commander to handle this case? -if (process.argv.length < 3 || process.argv[2]?.startsWith('--')) { +// No command? We print the help message because Commander doesn't +// Note argv looks like this: ['../node','../docusaurus.mjs','',...rest] +if (process.argv.length < 3) { cli.outputHelp(); + logger.error`Please provide a Docusaurus CLI command.`; process.exit(1); } // There is an unrecognized subcommand // Let plugins extend the CLI before parsing -if (!isInternalCommand(process.argv[2])) { +if (isExternalCommand(process.argv[2])) { // TODO: in this step, we must assume default site structure because there's // no way to know the siteDir/config yet. Maybe the root cli should be // responsible for parsing these arguments? From c58fcbdecd87cfb7418ff29f6a08ec4c14fdc71d Mon Sep 17 00:00:00 2001 From: Mohammad Bagher Abiyat Date: Thu, 8 Aug 2024 19:05:42 +0330 Subject: [PATCH 07/10] feat(ci): continuous releases for main and PRs with pkg.pr.new (#10369) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: sebastien Co-authored-by: Sébastien Lorber --- .github/workflows/continuous-releases.yml | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/continuous-releases.yml diff --git a/.github/workflows/continuous-releases.yml b/.github/workflows/continuous-releases.yml new file mode 100644 index 000000000000..80c1841f7ab8 --- /dev/null +++ b/.github/workflows/continuous-releases.yml @@ -0,0 +1,35 @@ +name: Continuous Releases + +on: + push: + branches: + - main + - docusaurus-v** + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + release: + name: Continuous Releases + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Installation + run: yarn + + - name: Build packages + run: yarn build:packages + + - name: Initialize fresh templates + run: | + yarn create-docusaurus template/docusaurus-classic-js classic --javascript -p npm + yarn create-docusaurus template/docusaurus-classic-ts classic --typescript -p npm + + - name: Release + run: npx pkg-pr-new publish './packages/*' --template './template/*' --compact --comment=off From 95ab9f8ee4f58edb03da8d408e9583a681e6ef14 Mon Sep 17 00:00:00 2001 From: ozaki <29860391+OzakIOne@users.noreply.github.com> Date: Thu, 8 Aug 2024 20:48:42 +0200 Subject: [PATCH 08/10] feat(theme): show unlisted/draft banners in dev mode (#10376) Co-authored-by: OzakIOne Co-authored-by: sebastien --- .../src/theme-classic.d.ts | 22 ++++++++++++++- .../src/theme/BlogPostPage/index.tsx | 6 ++--- .../src/theme/BlogTagsPostsPage/index.tsx | 2 +- .../theme/ContentVisibility/Draft/index.tsx | 27 +++++++++++++++++++ .../Unlisted/index.tsx | 2 +- .../src/theme/ContentVisibility/index.tsx | 27 +++++++++++++++++++ .../src/theme/DocItem/Layout/index.tsx | 8 +++--- .../src/theme/DocTagDocListPage/index.tsx | 2 +- .../src/theme/MDXPage/index.tsx | 23 +++++++--------- packages/docusaurus-theme-common/src/index.ts | 4 ++- .../contentVisibilityTranslations.tsx} | 27 +++++++++++++++++-- .../src/utils/ThemeClassNames.ts | 1 + .../locales/ar/theme-common.json | 11 +++++--- .../locales/base/theme-common.json | 23 ++++++++++------ .../locales/bg/theme-common.json | 11 +++++--- .../locales/bn/theme-common.json | 11 +++++--- .../locales/cs/theme-common.json | 11 +++++--- .../locales/da/theme-common.json | 11 +++++--- .../locales/de/theme-common.json | 11 +++++--- .../locales/es/theme-common.json | 11 +++++--- .../locales/et/theme-common.json | 11 +++++--- .../locales/fa/theme-common.json | 11 +++++--- .../locales/fil/theme-common.json | 11 +++++--- .../locales/fr/theme-common.json | 11 +++++--- .../locales/he/theme-common.json | 11 +++++--- .../locales/hi/theme-common.json | 11 +++++--- .../locales/hu/theme-common.json | 11 +++++--- .../locales/id/theme-common.json | 11 +++++--- .../locales/is/theme-common.json | 12 ++++++--- .../locales/it/theme-common.json | 11 +++++--- .../locales/ja/theme-common.json | 11 +++++--- .../locales/ko/theme-common.json | 11 +++++--- .../locales/nb/theme-common.json | 11 +++++--- .../locales/nl/theme-common.json | 11 +++++--- .../locales/pl/theme-common.json | 11 +++++--- .../locales/pt-BR/theme-common.json | 11 +++++--- .../locales/pt-PT/theme-common.json | 11 +++++--- .../locales/ru/theme-common.json | 11 +++++--- .../locales/sl/theme-common.json | 11 +++++--- .../locales/sr/theme-common.json | 11 +++++--- .../locales/sv/theme-common.json | 11 +++++--- .../locales/tk/theme-common.json | 11 +++++--- .../locales/tr/theme-common.json | 11 +++++--- .../locales/uk/theme-common.json | 11 +++++--- .../locales/vi/theme-common.json | 11 +++++--- .../locales/zh-Hans/theme-common.json | 11 +++++--- .../locales/zh-Hant/theme-common.json | 11 +++++--- project-words.txt | 2 -- 48 files changed, 411 insertions(+), 140 deletions(-) create mode 100644 packages/docusaurus-theme-classic/src/theme/ContentVisibility/Draft/index.tsx rename packages/docusaurus-theme-classic/src/theme/{ => ContentVisibility}/Unlisted/index.tsx (94%) create mode 100644 packages/docusaurus-theme-classic/src/theme/ContentVisibility/index.tsx rename packages/docusaurus-theme-common/src/{utils/unlistedUtils.tsx => translations/contentVisibilityTranslations.tsx} (53%) diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index c35e7615af26..e0d5f47a8e13 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -1602,7 +1602,19 @@ declare module '@theme/Tag' { export default function Tag(props: Props): JSX.Element; } -declare module '@theme/Unlisted' { +declare module '@theme/ContentVisibility' { + export interface Props { + readonly metadata: { + // the visibility metadata our 3 content plugins share in common + readonly unlisted: boolean; + readonly frontMatter: {draft?: boolean; unlisted?: boolean}; + }; + } + + export default function ContentVisibility(props: Props): JSX.Element; +} + +declare module '@theme/ContentVisibility/Unlisted' { export interface Props { className?: string; } @@ -1610,6 +1622,14 @@ declare module '@theme/Unlisted' { export default function Unlisted(props: Props): JSX.Element; } +declare module '@theme/ContentVisibility/Draft' { + export interface Props { + className?: string; + } + + export default function Draft(props: Props): JSX.Element; +} + declare module '@theme/prism-include-languages' { import type * as PrismNamespace from 'prismjs'; diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostPage/index.tsx index 2ceb7c5d16d4..07c96d47a548 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogPostPage/index.tsx @@ -18,8 +18,8 @@ import BlogPostPaginator from '@theme/BlogPostPaginator'; import BlogPostPageMetadata from '@theme/BlogPostPage/Metadata'; import BlogPostPageStructuredData from '@theme/BlogPostPage/StructuredData'; import TOC from '@theme/TOC'; +import ContentVisibility from '@theme/ContentVisibility'; import type {Props} from '@theme/BlogPostPage'; -import Unlisted from '@theme/Unlisted'; import type {BlogSidebar} from '@docusaurus/plugin-content-blog'; function BlogPostPageContent({ @@ -30,7 +30,7 @@ function BlogPostPageContent({ children: ReactNode; }): JSX.Element { const {metadata, toc} = useBlogPost(); - const {nextItem, prevItem, frontMatter, unlisted} = metadata; + const {nextItem, prevItem, frontMatter} = metadata; const { hide_table_of_contents: hideTableOfContents, toc_min_heading_level: tocMinHeadingLevel, @@ -48,7 +48,7 @@ function BlogPostPageContent({ /> ) : undefined }> - {unlisted && } + {children} diff --git a/packages/docusaurus-theme-classic/src/theme/BlogTagsPostsPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogTagsPostsPage/index.tsx index d6a716ff2d18..c050fbcf3e67 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogTagsPostsPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogTagsPostsPage/index.tsx @@ -20,7 +20,7 @@ import BlogListPaginator from '@theme/BlogListPaginator'; import SearchMetadata from '@theme/SearchMetadata'; import type {Props} from '@theme/BlogTagsPostsPage'; import BlogPostItems from '@theme/BlogPostItems'; -import Unlisted from '@theme/Unlisted'; +import Unlisted from '@theme/ContentVisibility/Unlisted'; import Heading from '@theme/Heading'; function BlogTagsPostsPageMetadata({tag}: Props): JSX.Element { diff --git a/packages/docusaurus-theme-classic/src/theme/ContentVisibility/Draft/index.tsx b/packages/docusaurus-theme-classic/src/theme/ContentVisibility/Draft/index.tsx new file mode 100644 index 000000000000..6595e2295fe8 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/ContentVisibility/Draft/index.tsx @@ -0,0 +1,27 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import clsx from 'clsx'; +import { + ThemeClassNames, + DraftBannerTitle, + DraftBannerMessage, +} from '@docusaurus/theme-common'; +import Admonition from '@theme/Admonition'; +import type {Props} from '@theme/ContentVisibility/Draft'; + +export default function Draft({className}: Props): JSX.Element | null { + return ( + } + className={clsx(className, ThemeClassNames.common.draftBanner)}> + + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Unlisted/index.tsx b/packages/docusaurus-theme-classic/src/theme/ContentVisibility/Unlisted/index.tsx similarity index 94% rename from packages/docusaurus-theme-classic/src/theme/Unlisted/index.tsx rename to packages/docusaurus-theme-classic/src/theme/ContentVisibility/Unlisted/index.tsx index 302456e5bf12..004f99d9ef83 100644 --- a/packages/docusaurus-theme-classic/src/theme/Unlisted/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/ContentVisibility/Unlisted/index.tsx @@ -14,7 +14,7 @@ import { UnlistedMetadata, } from '@docusaurus/theme-common'; import Admonition from '@theme/Admonition'; -import type {Props} from '@theme/Unlisted'; +import type {Props} from '@theme/ContentVisibility/Unlisted'; function UnlistedBanner({className}: Props) { return ( diff --git a/packages/docusaurus-theme-classic/src/theme/ContentVisibility/index.tsx b/packages/docusaurus-theme-classic/src/theme/ContentVisibility/index.tsx new file mode 100644 index 000000000000..f9c570e02c54 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/ContentVisibility/index.tsx @@ -0,0 +1,27 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; + +import type {Props} from '@theme/ContentVisibility'; +import Draft from '@theme/ContentVisibility/Draft'; +import Unlisted from '@theme/ContentVisibility/Unlisted'; + +export default function ContentVisibility({ + metadata, +}: Props): JSX.Element | null { + const {unlisted, frontMatter} = metadata; + // Reading draft/unlisted status from frontMatter is useful to display + // the banners in dev mode (in dev, metadata.unlisted is always false) + // See https://github.com/facebook/docusaurus/issues/8285 + return ( + <> + {(unlisted || frontMatter.unlisted) && } + {frontMatter.draft && } + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocItem/Layout/index.tsx index 864a7896f367..138b6ad45fbc 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocItem/Layout/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/Layout/index.tsx @@ -17,7 +17,7 @@ import DocItemTOCMobile from '@theme/DocItem/TOC/Mobile'; import DocItemTOCDesktop from '@theme/DocItem/TOC/Desktop'; import DocItemContent from '@theme/DocItem/Content'; import DocBreadcrumbs from '@theme/DocBreadcrumbs'; -import Unlisted from '@theme/Unlisted'; +import ContentVisibility from '@theme/ContentVisibility'; import type {Props} from '@theme/DocItem/Layout'; import styles from './styles.module.css'; @@ -48,13 +48,11 @@ function useDocTOC() { export default function DocItemLayout({children}: Props): JSX.Element { const docTOC = useDocTOC(); - const { - metadata: {unlisted}, - } = useDoc(); + const {metadata} = useDoc(); return (
    - {unlisted && } +
    diff --git a/packages/docusaurus-theme-classic/src/theme/DocTagDocListPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocTagDocListPage/index.tsx index aa56a14ef31d..32e1d642e30d 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocTagDocListPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocTagDocListPage/index.tsx @@ -17,7 +17,7 @@ import { import Translate, {translate} from '@docusaurus/Translate'; import SearchMetadata from '@theme/SearchMetadata'; import type {Props} from '@theme/DocTagDocListPage'; -import Unlisted from '@theme/Unlisted'; +import Unlisted from '@theme/ContentVisibility/Unlisted'; import Heading from '@theme/Heading'; // Very simple pluralization: probably good enough for now diff --git a/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx index 103dc3e69a1b..737c0bccbd62 100644 --- a/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx @@ -15,7 +15,7 @@ import { import Layout from '@theme/Layout'; import MDXContent from '@theme/MDXContent'; import TOC from '@theme/TOC'; -import Unlisted from '@theme/Unlisted'; +import ContentVisibility from '@theme/ContentVisibility'; import type {Props} from '@theme/MDXPage'; import EditMetaRow from '@theme/EditMetaRow'; @@ -23,18 +23,15 @@ import styles from './styles.module.css'; export default function MDXPage(props: Props): JSX.Element { const {content: MDXPageContent} = props; + const {metadata, assets} = MDXPageContent; const { - metadata: { - title, - editUrl, - description, - frontMatter, - unlisted, - lastUpdatedBy, - lastUpdatedAt, - }, - assets, - } = MDXPageContent; + title, + editUrl, + description, + frontMatter, + lastUpdatedBy, + lastUpdatedAt, + } = metadata; const { keywords, wrapperClassName, @@ -60,7 +57,7 @@ export default function MDXPage(props: Props): JSX.Element {
    - {unlisted && } +
    diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts index 593da9842b34..003289b5cb7b 100644 --- a/packages/docusaurus-theme-common/src/index.ts +++ b/packages/docusaurus-theme-common/src/index.ts @@ -123,7 +123,9 @@ export { UnlistedBannerTitle, UnlistedBannerMessage, UnlistedMetadata, -} from './utils/unlistedUtils'; + DraftBannerTitle, + DraftBannerMessage, +} from './translations/contentVisibilityTranslations'; export { ErrorBoundaryTryAgainButton, diff --git a/packages/docusaurus-theme-common/src/utils/unlistedUtils.tsx b/packages/docusaurus-theme-common/src/translations/contentVisibilityTranslations.tsx similarity index 53% rename from packages/docusaurus-theme-common/src/utils/unlistedUtils.tsx rename to packages/docusaurus-theme-common/src/translations/contentVisibilityTranslations.tsx index 8147e5987275..f331b922e3bd 100644 --- a/packages/docusaurus-theme-common/src/utils/unlistedUtils.tsx +++ b/packages/docusaurus-theme-common/src/translations/contentVisibilityTranslations.tsx @@ -12,7 +12,7 @@ import Head from '@docusaurus/Head'; export function UnlistedBannerTitle(): JSX.Element { return ( Unlisted page @@ -22,7 +22,7 @@ export function UnlistedBannerTitle(): JSX.Element { export function UnlistedBannerMessage(): JSX.Element { return ( This page is unlisted. Search engines will not index it, and only users having a direct link can access it. @@ -30,6 +30,8 @@ export function UnlistedBannerMessage(): JSX.Element { ); } +// TODO Docusaurus v4 breaking change (since it's v3 public theme-common API :/) +// Move this to theme/ContentVisibility/Unlisted export function UnlistedMetadata(): JSX.Element { return ( @@ -37,3 +39,24 @@ export function UnlistedMetadata(): JSX.Element { ); } + +export function DraftBannerTitle(): JSX.Element { + return ( + + Draft page + + ); +} + +export function DraftBannerMessage(): JSX.Element { + return ( + + This page is a draft. It will only be visible in dev and be excluded from + the production build. + + ); +} diff --git a/packages/docusaurus-theme-common/src/utils/ThemeClassNames.ts b/packages/docusaurus-theme-common/src/utils/ThemeClassNames.ts index 413a2492fa4a..78539578e6c9 100644 --- a/packages/docusaurus-theme-common/src/utils/ThemeClassNames.ts +++ b/packages/docusaurus-theme-common/src/utils/ThemeClassNames.ts @@ -43,6 +43,7 @@ export const ThemeClassNames = { codeBlock: 'theme-code-block', admonition: 'theme-admonition', unlistedBanner: 'theme-unlisted-banner', + draftBanner: 'theme-draft-banner', admonitionType: (type: string) => `theme-admonition-${type}`, }, diff --git a/packages/docusaurus-theme-translations/locales/ar/theme-common.json b/packages/docusaurus-theme-translations/locales/ar/theme-common.json index aad2e83c3dc4..e165598da48a 100644 --- a/packages/docusaurus-theme-translations/locales/ar/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/ar/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "أرشيف", "theme.blog.archive.title": "أرشيف", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "التنقل في صفحة قائمة المدونة", "theme.blog.paginator.newerEntries": "إدخالات أحدث", "theme.blog.paginator.olderEntries": "إدخالات أقدم", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "تعديل هذه الصفحة", "theme.common.headingLinkTitle": "ارتباط مباشر بالعنوان {heading}", "theme.common.skipToMainContent": "انتقل إلى المحتوى الرئيسي", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", "theme.docs.DocCard.categoryDescription.plurals": "{count} مواد", "theme.docs.breadcrumbs.home": "الرئيسية", "theme.docs.breadcrumbs.navAriaLabel": "التنقل التفصيلي", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "إصدارات", "theme.tags.tagsListLabel": "الوسوم:", "theme.tags.tagsPageLink": "عرض كل الوسوم", - "theme.tags.tagsPageTitle": "الوسوم", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.title": "Unlisted page" + "theme.tags.tagsPageTitle": "الوسوم" } diff --git a/packages/docusaurus-theme-translations/locales/base/theme-common.json b/packages/docusaurus-theme-translations/locales/base/theme-common.json index 3cc0b8b8b369..2d7d9768e358 100644 --- a/packages/docusaurus-theme-translations/locales/base/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/base/theme-common.json @@ -41,13 +41,16 @@ "theme.admonition.tip___DESCRIPTION": "The default label used for the Tip admonition (:::tip)", "theme.admonition.warning": "warning", "theme.admonition.warning___DESCRIPTION": "The default label used for the Warning admonition (:::warning)", - "theme.blog.authorsList.pageTitle": "Authors", - "theme.blog.authorsList.viewAll": "View All Authors", - "theme.blog.author.pageTitle": "{authorName} - {nPosts}", "theme.blog.archive.description": "Archive", "theme.blog.archive.description___DESCRIPTION": "The page & hero description of the blog archive page", "theme.blog.archive.title": "Archive", "theme.blog.archive.title___DESCRIPTION": "The page & hero title of the blog archive page", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.author.pageTitle___DESCRIPTION": "The title of the page for a blog author", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.pageTitle___DESCRIPTION": "The title of the authors page", + "theme.blog.authorsList.viewAll": "View All Authors", + "theme.blog.authorsList.viewAll___DESCRIPTION": "The label of the link targeting the blog authors page", "theme.blog.paginator.navAriaLabel": "Blog list page navigation", "theme.blog.paginator.navAriaLabel___DESCRIPTION": "The ARIA label for the blog pagination", "theme.blog.paginator.newerEntries": "Newer Entries", @@ -84,6 +87,14 @@ "theme.common.headingLinkTitle___DESCRIPTION": "Title for link to heading", "theme.common.skipToMainContent": "Skip to main content", "theme.common.skipToMainContent___DESCRIPTION": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.message___DESCRIPTION": "The draft content banner message", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.draftBanner.title___DESCRIPTION": "The draft content banner title", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.message___DESCRIPTION": "The unlisted content banner message", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", + "theme.contentVisibility.unlistedBanner.title___DESCRIPTION": "The unlisted content banner title", "theme.docs.DocCard.categoryDescription.plurals": "1 item|{count} items", "theme.docs.DocCard.categoryDescription.plurals___DESCRIPTION": "The default description for a category card in the generated index about how many items this category includes", "theme.docs.breadcrumbs.home": "Home page", @@ -140,9 +151,5 @@ "theme.tags.tagsPageLink": "View All Tags", "theme.tags.tagsPageLink___DESCRIPTION": "The label of the link targeting the tag list page", "theme.tags.tagsPageTitle": "Tags", - "theme.tags.tagsPageTitle___DESCRIPTION": "The title of the tag list page", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.message___DESCRIPTION": "The unlisted content banner message", - "theme.unlistedContent.title": "Unlisted page", - "theme.unlistedContent.title___DESCRIPTION": "The unlisted content banner title" + "theme.tags.tagsPageTitle___DESCRIPTION": "The title of the tag list page" } diff --git a/packages/docusaurus-theme-translations/locales/bg/theme-common.json b/packages/docusaurus-theme-translations/locales/bg/theme-common.json index 11cf230e1cea..0e137e9d01de 100644 --- a/packages/docusaurus-theme-translations/locales/bg/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/bg/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "Внимание", "theme.blog.archive.description": "Архив", "theme.blog.archive.title": "Архив", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Навигация в страницата със списък на блогове", "theme.blog.paginator.newerEntries": "По-нови записи", "theme.blog.paginator.olderEntries": "По-стари записи", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Редактирай тази страница", "theme.common.headingLinkTitle": "Директна връзка към {heading}", "theme.common.skipToMainContent": "Преминете към основното съдържание", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "Тази страница е скрита. Търсачките няма да я индексират и само потребители с директна връзка имат достъп до него.", + "theme.contentVisibility.unlistedBanner.title": "Скрита страница", "theme.docs.DocCard.categoryDescription.plurals": "един предмет|{count} предмета", "theme.docs.breadcrumbs.home": "Начална страница", "theme.docs.breadcrumbs.navAriaLabel": "Галета", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Версии", "theme.tags.tagsListLabel": "Етикети:", "theme.tags.tagsPageLink": "Вижте всички етикети", - "theme.tags.tagsPageTitle": "Етикети", - "theme.unlistedContent.message": "Тази страница е скрита. Търсачките няма да я индексират и само потребители с директна връзка имат достъп до него.", - "theme.unlistedContent.title": "Скрита страница" + "theme.tags.tagsPageTitle": "Етикети" } diff --git a/packages/docusaurus-theme-translations/locales/bn/theme-common.json b/packages/docusaurus-theme-translations/locales/bn/theme-common.json index a9f72207ac9c..a4422ff089ab 100644 --- a/packages/docusaurus-theme-translations/locales/bn/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/bn/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Archive", "theme.blog.archive.title": "Archive", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "ব্লগ তালিকা পেজ নেভিগেশন", "theme.blog.paginator.newerEntries": "নতুন এন্ট্রি", "theme.blog.paginator.olderEntries": "পুরানো এন্ট্রি", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "এই পেজটি এডিট করুন", "theme.common.headingLinkTitle": "{heading} এর সঙ্গে সরাসরি লিংকড", "theme.common.skipToMainContent": "স্কিপ করে মূল কন্টেন্ট এ যান", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", "theme.docs.DocCard.categoryDescription.plurals": "1 item|{count} items", "theme.docs.breadcrumbs.home": "Home page", "theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "ট্যাগ্স:", "theme.tags.tagsPageLink": "সমস্ত ট্যাগ্স দেখুন", - "theme.tags.tagsPageTitle": "ট্যাগ্স", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.title": "Unlisted page" + "theme.tags.tagsPageTitle": "ট্যাগ্স" } diff --git a/packages/docusaurus-theme-translations/locales/cs/theme-common.json b/packages/docusaurus-theme-translations/locales/cs/theme-common.json index f5859c4236db..21873c7ec0fe 100644 --- a/packages/docusaurus-theme-translations/locales/cs/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/cs/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Archive", "theme.blog.archive.title": "Archive", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Stránkování článků na blogu", "theme.blog.paginator.newerEntries": "Novější záznamy", "theme.blog.paginator.olderEntries": "Starší záznamy", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Upravit tuto stránku", "theme.common.headingLinkTitle": "Přímý odkaz na {heading}", "theme.common.skipToMainContent": "Přeskočit na hlavní obsah", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", "theme.docs.DocCard.categoryDescription.plurals": "1 item|{count} items", "theme.docs.breadcrumbs.home": "Home page", "theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "Tagy:", "theme.tags.tagsPageLink": "Zobrazit všechny tagy", - "theme.tags.tagsPageTitle": "Tagy", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.title": "Unlisted page" + "theme.tags.tagsPageTitle": "Tagy" } diff --git a/packages/docusaurus-theme-translations/locales/da/theme-common.json b/packages/docusaurus-theme-translations/locales/da/theme-common.json index 6b05c8a653c5..765fb67d1538 100644 --- a/packages/docusaurus-theme-translations/locales/da/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/da/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Archive", "theme.blog.archive.title": "Archive", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Blogoversigt navigation", "theme.blog.paginator.newerEntries": "Nyere indslag", "theme.blog.paginator.olderEntries": "Tidligere indslag", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Rediger denne side", "theme.common.headingLinkTitle": "Direkte link til {heading}", "theme.common.skipToMainContent": "Hop til hovedindhold", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", "theme.docs.DocCard.categoryDescription.plurals": "1 item|{count} items", "theme.docs.breadcrumbs.home": "Home page", "theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "Tags:", "theme.tags.tagsPageLink": "Se alle Tags", - "theme.tags.tagsPageTitle": "Tags", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.title": "Unlisted page" + "theme.tags.tagsPageTitle": "Tags" } diff --git a/packages/docusaurus-theme-translations/locales/de/theme-common.json b/packages/docusaurus-theme-translations/locales/de/theme-common.json index 918db9dd1475..ae52a2fe72ad 100644 --- a/packages/docusaurus-theme-translations/locales/de/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/de/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warnung", "theme.blog.archive.description": "Archiv", "theme.blog.archive.title": "Archiv", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Navigation der Blog-Listenseite", "theme.blog.paginator.newerEntries": "Neuere Einträge", "theme.blog.paginator.olderEntries": "Ältere Einträge", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Diese Seite bearbeiten", "theme.common.headingLinkTitle": "Direkter Link zur {heading}", "theme.common.skipToMainContent": "Zum Hauptinhalt springen", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", "theme.docs.DocCard.categoryDescription.plurals": "1 Eintrag|{count} Einträge", "theme.docs.breadcrumbs.home": "Home page", "theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versionen", "theme.tags.tagsListLabel": "Tags:", "theme.tags.tagsPageLink": "Alle Tags anzeigen", - "theme.tags.tagsPageTitle": "Tags", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.title": "Unlisted page" + "theme.tags.tagsPageTitle": "Tags" } diff --git a/packages/docusaurus-theme-translations/locales/es/theme-common.json b/packages/docusaurus-theme-translations/locales/es/theme-common.json index 29fe4934840c..95543c0ee177 100644 --- a/packages/docusaurus-theme-translations/locales/es/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/es/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "aviso", "theme.blog.archive.description": "Archivo", "theme.blog.archive.title": "Archivo", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Navegación por la página de la lista de blogs ", "theme.blog.paginator.newerEntries": "Entradas más recientes", "theme.blog.paginator.olderEntries": "Entradas más antiguas", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Editar esta página", "theme.common.headingLinkTitle": "Enlace directo al {heading}", "theme.common.skipToMainContent": "Saltar al contenido principal", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "Esta página está sin clasificar. Los motores de búsqueda no la indexaran, y solo los usuarios con el enlace directo podrán acceder a esta.", + "theme.contentVisibility.unlistedBanner.title": "Página sin clasificar", "theme.docs.DocCard.categoryDescription.plurals": "1 artículo|{count} artículos", "theme.docs.breadcrumbs.home": "Página de Inicio", "theme.docs.breadcrumbs.navAriaLabel": "Migas de pan", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versiones", "theme.tags.tagsListLabel": "Etiquetas:", "theme.tags.tagsPageLink": "Ver Todas las Etiquetas", - "theme.tags.tagsPageTitle": "Etiquetas", - "theme.unlistedContent.message": "Esta página está sin clasificar. Los motores de búsqueda no la indexaran, y solo los usuarios con el enlace directo podrán acceder a esta.", - "theme.unlistedContent.title": "Página sin clasificar" + "theme.tags.tagsPageTitle": "Etiquetas" } diff --git a/packages/docusaurus-theme-translations/locales/et/theme-common.json b/packages/docusaurus-theme-translations/locales/et/theme-common.json index 92acc305a1cc..1d0f8427c70e 100644 --- a/packages/docusaurus-theme-translations/locales/et/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/et/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "hoiatus", "theme.blog.archive.description": "Arhiiv", "theme.blog.archive.title": "Arhiiv", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Blogi lehekülje navigatsioon", "theme.blog.paginator.newerEntries": "Uuemad sissekanded", "theme.blog.paginator.olderEntries": "Vanemad sissekanded", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Redigeeri seda lehte", "theme.common.headingLinkTitle": "Link {heading}", "theme.common.skipToMainContent": "Liigu peamise sisu juurde", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "See leht ei ole avalik. Otsingumootorid ei indekseeri seda. Sellele lehele pääseb ainult lingiga ligi.", + "theme.contentVisibility.unlistedBanner.title": "avalikustamata leht", "theme.docs.DocCard.categoryDescription.plurals": "1 ese|{count} eset", "theme.docs.breadcrumbs.home": "Koduleht", "theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versioonid", "theme.tags.tagsListLabel": "Märked:", "theme.tags.tagsPageLink": "Näaita Kõiki Märkeid", - "theme.tags.tagsPageTitle": "Märked", - "theme.unlistedContent.message": "See leht ei ole avalik. Otsingumootorid ei indekseeri seda. Sellele lehele pääseb ainult lingiga ligi.", - "theme.unlistedContent.title": "avalikustamata leht" + "theme.tags.tagsPageTitle": "Märked" } diff --git a/packages/docusaurus-theme-translations/locales/fa/theme-common.json b/packages/docusaurus-theme-translations/locales/fa/theme-common.json index f82cd55b6702..45908abd75a4 100644 --- a/packages/docusaurus-theme-translations/locales/fa/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/fa/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "هشدار", "theme.blog.archive.description": "آرشیو", "theme.blog.archive.title": "آرشیو", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "کنترل لیست مطالب وبلاگ", "theme.blog.paginator.newerEntries": "مطالب جدید‌تر", "theme.blog.paginator.olderEntries": "مطالب قدیمی‌تر", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "ویرایش مطالب این صفحه", "theme.common.headingLinkTitle": "لینک مستقیم به {heading}", "theme.common.skipToMainContent": "پرش به مطلب اصلی", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "این صفحه فهرست نشده است. موتورهای جستجو آن را ایندکس نمی کنند و فقط کاربرانی که لینک مستقیم دارند می توانند به آن دسترسی داشته باشند.", + "theme.contentVisibility.unlistedBanner.title": "صفحه فهرست نشده", "theme.docs.DocCard.categoryDescription.plurals": "{count} مورد", "theme.docs.breadcrumbs.home": "صفحه اصلی", "theme.docs.breadcrumbs.navAriaLabel": "نشانگر صفحات", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "نسخه‌ها", "theme.tags.tagsListLabel": "برچسب‌ها:", "theme.tags.tagsPageLink": "مشاهده تمام برچسب‌ها", - "theme.tags.tagsPageTitle": "برچسب‌ها", - "theme.unlistedContent.message": "این صفحه فهرست نشده است. موتورهای جستجو آن را ایندکس نمی کنند و فقط کاربرانی که لینک مستقیم دارند می توانند به آن دسترسی داشته باشند.", - "theme.unlistedContent.title": "صفحه فهرست نشده" + "theme.tags.tagsPageTitle": "برچسب‌ها" } diff --git a/packages/docusaurus-theme-translations/locales/fil/theme-common.json b/packages/docusaurus-theme-translations/locales/fil/theme-common.json index 866b297b7858..50a06a24e1bd 100644 --- a/packages/docusaurus-theme-translations/locales/fil/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/fil/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Archive", "theme.blog.archive.title": "Archive", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Nabegasyón para sa pahina na listahan ng blog", "theme.blog.paginator.newerEntries": "Mas bagong mga éntri", "theme.blog.paginator.olderEntries": "Mas lumang mga éntri", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "I-edit ang page", "theme.common.headingLinkTitle": "Direktang link patungo sa {heading}", "theme.common.skipToMainContent": "Lumaktaw patungo sa pangunahing content", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", "theme.docs.DocCard.categoryDescription.plurals": "1 item|{count} items", "theme.docs.breadcrumbs.home": "Home page", "theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "Mga Tag:", "theme.tags.tagsPageLink": "Tingnan Lahat ng mga Tag", - "theme.tags.tagsPageTitle": "Mga Tag", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.title": "Unlisted page" + "theme.tags.tagsPageTitle": "Mga Tag" } diff --git a/packages/docusaurus-theme-translations/locales/fr/theme-common.json b/packages/docusaurus-theme-translations/locales/fr/theme-common.json index e309ee30c976..c68bc8d4670f 100644 --- a/packages/docusaurus-theme-translations/locales/fr/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/fr/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "attention", "theme.blog.archive.description": "Archive", "theme.blog.archive.title": "Archive", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Pagination de la liste des articles du blog", "theme.blog.paginator.newerEntries": "Nouvelles entrées", "theme.blog.paginator.olderEntries": "Anciennes entrées", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Éditer cette page", "theme.common.headingLinkTitle": "Lien direct vers {heading}", "theme.common.skipToMainContent": "Aller au contenu principal", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "Cette page n'est pas répertoriée. Les moteurs de recherche ne l'indexeront pas, et seuls les utilisateurs ayant un lien direct peuvent y accéder.", + "theme.contentVisibility.unlistedBanner.title": "Page non répertoriée", "theme.docs.DocCard.categoryDescription.plurals": "1 élément|{count} éléments", "theme.docs.breadcrumbs.home": "Page d'accueil", "theme.docs.breadcrumbs.navAriaLabel": "Fil d'Ariane", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "Tags :", "theme.tags.tagsPageLink": "Voir tous les tags", - "theme.tags.tagsPageTitle": "Tags", - "theme.unlistedContent.message": "Cette page n'est pas répertoriée. Les moteurs de recherche ne l'indexeront pas, et seuls les utilisateurs ayant un lien direct peuvent y accéder.", - "theme.unlistedContent.title": "Page non répertoriée" + "theme.tags.tagsPageTitle": "Tags" } diff --git a/packages/docusaurus-theme-translations/locales/he/theme-common.json b/packages/docusaurus-theme-translations/locales/he/theme-common.json index 7571401c6443..48f2a4941aa2 100644 --- a/packages/docusaurus-theme-translations/locales/he/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/he/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Archive", "theme.blog.archive.title": "Archive", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "רשימת דפי הבלוג", "theme.blog.paginator.newerEntries": "הכי חדש", "theme.blog.paginator.olderEntries": "ישן יותר", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "ערוך דף זה", "theme.common.headingLinkTitle": "קישור ישיר אל {heading}", "theme.common.skipToMainContent": "דלג לתוכן הראשי", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", "theme.docs.DocCard.categoryDescription.plurals": "1 item|{count} items", "theme.docs.breadcrumbs.home": "Home page", "theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "תגיות:", "theme.tags.tagsPageLink": "כל התגיות", - "theme.tags.tagsPageTitle": "תגיות", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.title": "Unlisted page" + "theme.tags.tagsPageTitle": "תגיות" } diff --git a/packages/docusaurus-theme-translations/locales/hi/theme-common.json b/packages/docusaurus-theme-translations/locales/hi/theme-common.json index bcace79c483b..2590a754a907 100644 --- a/packages/docusaurus-theme-translations/locales/hi/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/hi/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Archive", "theme.blog.archive.title": "Archive", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "ब्लॉग सूची पेज नेविगेशन", "theme.blog.paginator.newerEntries": "नए एंट्रीज़", "theme.blog.paginator.olderEntries": "पुराने एंट्रीज़", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "इस पेज को बदलें", "theme.common.headingLinkTitle": "{heading} का सीधा लिंक", "theme.common.skipToMainContent": "मुख्य कंटेंट तक स्किप करें", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", "theme.docs.DocCard.categoryDescription.plurals": "1 item|{count} items", "theme.docs.breadcrumbs.home": "Home page", "theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "टैग:", "theme.tags.tagsPageLink": "सारे टैग देखें", - "theme.tags.tagsPageTitle": "टैग", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.title": "Unlisted page" + "theme.tags.tagsPageTitle": "टैग" } diff --git a/packages/docusaurus-theme-translations/locales/hu/theme-common.json b/packages/docusaurus-theme-translations/locales/hu/theme-common.json index c7ed7977057d..28e4f51e7c0c 100644 --- a/packages/docusaurus-theme-translations/locales/hu/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/hu/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "vigyázat", "theme.blog.archive.description": "Archívum", "theme.blog.archive.title": "Archívum", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Bloglista oldalának navigációja", "theme.blog.paginator.newerEntries": "Újabb bejegyzések", "theme.blog.paginator.olderEntries": "Régebbi bejegyzések", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Szerkesztés GitHub-on", "theme.common.headingLinkTitle": "Közvetlen hivatkozás erre: {heading}", "theme.common.skipToMainContent": "Ugrás a fő tartalomhoz", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "Ez az oldal nem nyilvános. A keresőmotorok nem indexelik, és csak a közvetlen hivatkozással rendelkező felhasználók érhetik el.", + "theme.contentVisibility.unlistedBanner.title": "Nem nyilvános oldal", "theme.docs.DocCard.categoryDescription.plurals": "1 elem|{count} elemek", "theme.docs.breadcrumbs.home": "Kezdőlap", "theme.docs.breadcrumbs.navAriaLabel": "Navigációs sáv a jelenlegi oldalhoz", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Verziók", "theme.tags.tagsListLabel": "Címkék:", "theme.tags.tagsPageLink": "Összes címke megtekintése", - "theme.tags.tagsPageTitle": "Címkék", - "theme.unlistedContent.message": "Ez az oldal nem nyilvános. A keresőmotorok nem indexelik, és csak a közvetlen hivatkozással rendelkező felhasználók érhetik el.", - "theme.unlistedContent.title": "Nem nyilvános oldal" + "theme.tags.tagsPageTitle": "Címkék" } diff --git a/packages/docusaurus-theme-translations/locales/id/theme-common.json b/packages/docusaurus-theme-translations/locales/id/theme-common.json index 3e6919d8df93..9bea29c704c6 100644 --- a/packages/docusaurus-theme-translations/locales/id/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/id/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "peringatan", "theme.blog.archive.description": "Arsip", "theme.blog.archive.title": "Arsip", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Navigasi entri blog", "theme.blog.paginator.newerEntries": "Entri lebih baru", "theme.blog.paginator.olderEntries": "Entri lebih lama", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Sunting halaman ini", "theme.common.headingLinkTitle": "Taut langsung ke {heading}", "theme.common.skipToMainContent": "Lewati ke konten utama", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "Halaman ini tidak terdaftar. Mesin pencari tidak akan mengindeksnya, dan hanya pengguna yang memiliki tautan langsung yang dapat mengaksesnya.", + "theme.contentVisibility.unlistedBanner.title": "Halaman tak terdaftar", "theme.docs.DocCard.categoryDescription.plurals": "1 butir|{count} butir", "theme.docs.breadcrumbs.home": "Halaman utama", "theme.docs.breadcrumbs.navAriaLabel": "Runut navigasi", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versi", "theme.tags.tagsListLabel": "Tag:", "theme.tags.tagsPageLink": "Lihat Semua Tag", - "theme.tags.tagsPageTitle": "Tag", - "theme.unlistedContent.message": "Halaman ini tidak terdaftar. Mesin pencari tidak akan mengindeksnya, dan hanya pengguna yang memiliki tautan langsung yang dapat mengaksesnya.", - "theme.unlistedContent.title": "Halaman tak terdaftar" + "theme.tags.tagsPageTitle": "Tag" } diff --git a/packages/docusaurus-theme-translations/locales/is/theme-common.json b/packages/docusaurus-theme-translations/locales/is/theme-common.json index c77a3c07ae2a..8cf8e8be3901 100644 --- a/packages/docusaurus-theme-translations/locales/is/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/is/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "aðvörun", "theme.blog.archive.description": "Skjalasafn", "theme.blog.archive.title": "Skjalasafn", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Blogg listsíðu yfirlit", "theme.blog.paginator.newerEntries": "Nýrri færslur", "theme.blog.paginator.olderEntries": "Eldri færslur", @@ -40,7 +43,12 @@ "theme.common.editThisPage": "Breyttu þessari síðu", "theme.common.headingLinkTitle": "Beinn hlekkur að {heading}", "theme.common.skipToMainContent": "Hoppa yfir á aðal efni", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "Þessi síða er ólistuð. Leitarvélar munu ekki skrá hana, eingöngu notendur með beinan hlekk geta opnað hana.", + "theme.contentVisibility.unlistedBanner.title": "Óskráð síða", "theme.docs.DocCard.categoryDescription": "{count} atriði", + "theme.docs.DocCard.categoryDescription.plurals": "1 item|{count} items", "theme.docs.breadcrumbs.home": "Heimasíða", "theme.docs.breadcrumbs.navAriaLabel": "Brauðteningar", "theme.docs.paginator.navAriaLabel": "Skjala síður", @@ -68,7 +76,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Útgáfur", "theme.tags.tagsListLabel": "Merki:", "theme.tags.tagsPageLink": "Skoða Öll Merki", - "theme.tags.tagsPageTitle": "Merki", - "theme.unlistedContent.message": "Þessi síða er ólistuð. Leitarvélar munu ekki skrá hana, eingöngu notendur með beinan hlekk geta opnað hana.", - "theme.unlistedContent.title": "Óskráð síða" + "theme.tags.tagsPageTitle": "Merki" } diff --git a/packages/docusaurus-theme-translations/locales/it/theme-common.json b/packages/docusaurus-theme-translations/locales/it/theme-common.json index f9a94c926190..c030e0df48c6 100644 --- a/packages/docusaurus-theme-translations/locales/it/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/it/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Archivio", "theme.blog.archive.title": "Archivio", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Navigazione nella pagina dei post del blog ", "theme.blog.paginator.newerEntries": "Post più recenti", "theme.blog.paginator.olderEntries": "Post più vecchi", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Modifica questa pagina", "theme.common.headingLinkTitle": "Link diretto a {heading}", "theme.common.skipToMainContent": "Passa al contenuto principale", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "Questa pagina non è in elenco. I motori di ricerca non lo indicheranno e solo gli utenti con collegamento diretto possono accedervi.", + "theme.contentVisibility.unlistedBanner.title": "Pagina non in elenco", "theme.docs.DocCard.categoryDescription.plurals": "1 elemento|{count} elementi", "theme.docs.breadcrumbs.home": "Pagina principale", "theme.docs.breadcrumbs.navAriaLabel": "Briciole di pane", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versioni", "theme.tags.tagsListLabel": "Etichette:", "theme.tags.tagsPageLink": "Guarda tutte le etichette", - "theme.tags.tagsPageTitle": "Etichette", - "theme.unlistedContent.message": "Questa pagina non è in elenco. I motori di ricerca non lo indicheranno e solo gli utenti con collegamento diretto possono accedervi.", - "theme.unlistedContent.title": "Pagina non in elenco" + "theme.tags.tagsPageTitle": "Etichette" } diff --git a/packages/docusaurus-theme-translations/locales/ja/theme-common.json b/packages/docusaurus-theme-translations/locales/ja/theme-common.json index 17873d8fda07..fc09e90e9507 100644 --- a/packages/docusaurus-theme-translations/locales/ja/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/ja/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "警告", "theme.blog.archive.description": "アーカイブ", "theme.blog.archive.title": "アーカイブ", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "ブログ記事一覧のナビゲーション", "theme.blog.paginator.newerEntries": "新しい記事", "theme.blog.paginator.olderEntries": "過去の記事", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "このページを編集", "theme.common.headingLinkTitle": "{heading} への直接リンク", "theme.common.skipToMainContent": "メインコンテンツまでスキップ", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "このページは非公開です。 検索対象外となり、このページのリンクに直接アクセスできるユーザーのみに公開されます。", + "theme.contentVisibility.unlistedBanner.title": "非公開のページ", "theme.docs.DocCard.categoryDescription.plurals": "{count}項目", "theme.docs.breadcrumbs.home": "ホームページ", "theme.docs.breadcrumbs.navAriaLabel": "パンくずリストのナビゲーション", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "他のバージョン", "theme.tags.tagsListLabel": "タグ:", "theme.tags.tagsPageLink": "全てのタグを見る", - "theme.tags.tagsPageTitle": "タグ", - "theme.unlistedContent.message": "このページは非公開です。 検索対象外となり、このページのリンクに直接アクセスできるユーザーのみに公開されます。", - "theme.unlistedContent.title": "非公開のページ" + "theme.tags.tagsPageTitle": "タグ" } diff --git a/packages/docusaurus-theme-translations/locales/ko/theme-common.json b/packages/docusaurus-theme-translations/locales/ko/theme-common.json index 291e4eb0a595..daf247cc4974 100644 --- a/packages/docusaurus-theme-translations/locales/ko/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/ko/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "경고", "theme.blog.archive.description": "게시물 목록", "theme.blog.archive.title": "게시물 목록", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "블로그 게시물 목록 탐색", "theme.blog.paginator.newerEntries": "이전 페이지", "theme.blog.paginator.olderEntries": "다음 페이지", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "페이지 편집", "theme.common.headingLinkTitle": "{heading}에 대한 직접 링크", "theme.common.skipToMainContent": "본문으로 건너뛰기", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "이 문서는 색인되지 않습니다. 검색 엔진이 이 문서를 색인하지 않으며, 주소를 알고 있는 사용자만 접근할 수 있습니다.", + "theme.contentVisibility.unlistedBanner.title": "색인되지 않은 문서", "theme.docs.DocCard.categoryDescription.plurals": "{count} 항목", "theme.docs.breadcrumbs.home": "홈", "theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "버전", "theme.tags.tagsListLabel": "태그:", "theme.tags.tagsPageLink": "모든 태그 보기", - "theme.tags.tagsPageTitle": "태그", - "theme.unlistedContent.message": "이 문서는 색인되지 않습니다. 검색 엔진이 이 문서를 색인하지 않으며, 주소를 알고 있는 사용자만 접근할 수 있습니다.", - "theme.unlistedContent.title": "색인되지 않은 문서" + "theme.tags.tagsPageTitle": "태그" } diff --git a/packages/docusaurus-theme-translations/locales/nb/theme-common.json b/packages/docusaurus-theme-translations/locales/nb/theme-common.json index 3a11f6f55a5d..9d3aed888dc8 100644 --- a/packages/docusaurus-theme-translations/locales/nb/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/nb/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Arkiv", "theme.blog.archive.title": "Arkiv", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Navigering av bloggliste", "theme.blog.paginator.newerEntries": "Nyere innlegg", "theme.blog.paginator.olderEntries": "Eldre innlegg", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Rediger denne siden", "theme.common.headingLinkTitle": "Direkte lenke til {heading}", "theme.common.skipToMainContent": "Gå til hovedinnhold", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "Denne siden er ikke oppført. Søkemotorer vil ikke indeksere den, og bare brukere som har en direkte lenke kan få tilgang til den.", + "theme.contentVisibility.unlistedBanner.title": "Uoppført side", "theme.docs.DocCard.categoryDescription.plurals": "1 artikkel|{count} artikler", "theme.docs.breadcrumbs.home": "Hjemmeside", "theme.docs.breadcrumbs.navAriaLabel": "Søkvei", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versjoner", "theme.tags.tagsListLabel": "Tagger:", "theme.tags.tagsPageLink": "Vis alle tagger", - "theme.tags.tagsPageTitle": "Tagger", - "theme.unlistedContent.message": "Denne siden er ikke oppført. Søkemotorer vil ikke indeksere den, og bare brukere som har en direkte lenke kan få tilgang til den.", - "theme.unlistedContent.title": "Uoppført side" + "theme.tags.tagsPageTitle": "Tagger" } diff --git a/packages/docusaurus-theme-translations/locales/nl/theme-common.json b/packages/docusaurus-theme-translations/locales/nl/theme-common.json index 798d3590c490..81f60d8a83cc 100644 --- a/packages/docusaurus-theme-translations/locales/nl/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/nl/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Archief", "theme.blog.archive.title": "Archief", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Paginanavigatie blog", "theme.blog.paginator.newerEntries": "Nieuwere items", "theme.blog.paginator.olderEntries": "Oudere items", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Bewerk deze pagina", "theme.common.headingLinkTitle": "Direct link naar {heading}", "theme.common.skipToMainContent": "Ga naar hoofdinhoud", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", "theme.docs.DocCard.categoryDescription.plurals": "1 artikel|{count} artikelen", "theme.docs.breadcrumbs.home": "Homepagina", "theme.docs.breadcrumbs.navAriaLabel": "Broodkruimels", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versies", "theme.tags.tagsListLabel": "Tags:", "theme.tags.tagsPageLink": "Laat alle tags zien", - "theme.tags.tagsPageTitle": "Tags", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.title": "Unlisted page" + "theme.tags.tagsPageTitle": "Tags" } diff --git a/packages/docusaurus-theme-translations/locales/pl/theme-common.json b/packages/docusaurus-theme-translations/locales/pl/theme-common.json index 402235f6a3bb..e3833c44d361 100644 --- a/packages/docusaurus-theme-translations/locales/pl/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/pl/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Archiwum", "theme.blog.archive.title": "Archiwum", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Nawigacja na stronie listy wpisów na blogu", "theme.blog.paginator.newerEntries": "Nowsze wpisy", "theme.blog.paginator.olderEntries": "Starsze wpisy", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Edytuj tę stronę", "theme.common.headingLinkTitle": "Bezpośredni link do {heading}", "theme.common.skipToMainContent": "Przejdź do głównej zawartości", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "Ta strona jest niepubliczna. Wyszukiwarki nie będą jej indeksować, a dostęp do niej będą mieli tylko użytkownicy posiadający bezpośredni link.", + "theme.contentVisibility.unlistedBanner.title": "Niepubliczna strona", "theme.docs.DocCard.categoryDescription.plurals": "1 element|{count} elementy|{count} elementów", "theme.docs.breadcrumbs.home": "Strona główna", "theme.docs.breadcrumbs.navAriaLabel": "Pasek nawigacji", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Wersje", "theme.tags.tagsListLabel": "Tagi:", "theme.tags.tagsPageLink": "Wyświetl wszystkie tagi", - "theme.tags.tagsPageTitle": "Tagi", - "theme.unlistedContent.message": "Ta strona jest niepubliczna. Wyszukiwarki nie będą jej indeksować, a dostęp do niej będą mieli tylko użytkownicy posiadający bezpośredni link.", - "theme.unlistedContent.title": "Niepubliczna strona" + "theme.tags.tagsPageTitle": "Tagi" } diff --git a/packages/docusaurus-theme-translations/locales/pt-BR/theme-common.json b/packages/docusaurus-theme-translations/locales/pt-BR/theme-common.json index 7bcab9cbbbfe..ebc6b167738c 100644 --- a/packages/docusaurus-theme-translations/locales/pt-BR/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/pt-BR/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "atenção", "theme.blog.archive.description": "Arquivo", "theme.blog.archive.title": "Arquivo", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Navegação da página de listagem do blog", "theme.blog.paginator.newerEntries": "Conteúdo mais novo", "theme.blog.paginator.olderEntries": "Conteúdo mais antigo", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Editar essa página", "theme.common.headingLinkTitle": "Link direto para {heading}", "theme.common.skipToMainContent": "Pular para o conteúdo principal", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "Esta página não está listada. Mecanismos de busca não armazenarão nenhuma informação, e somente usuários que possuam o link direto poderão acessá-la", + "theme.contentVisibility.unlistedBanner.title": "Página não listada", "theme.docs.DocCard.categoryDescription.plurals": "1 item|{count} items", "theme.docs.breadcrumbs.home": "Página Inicial", "theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versões", "theme.tags.tagsListLabel": "Marcadores:", "theme.tags.tagsPageLink": "Ver todas os Marcadores", - "theme.tags.tagsPageTitle": "Marcadores", - "theme.unlistedContent.message": "Esta página não está listada. Mecanismos de busca não armazenarão nenhuma informação, e somente usuários que possuam o link direto poderão acessá-la", - "theme.unlistedContent.title": "Página não listada" + "theme.tags.tagsPageTitle": "Marcadores" } diff --git a/packages/docusaurus-theme-translations/locales/pt-PT/theme-common.json b/packages/docusaurus-theme-translations/locales/pt-PT/theme-common.json index 8daa2bead68a..e048d58adf3c 100644 --- a/packages/docusaurus-theme-translations/locales/pt-PT/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/pt-PT/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Archive", "theme.blog.archive.title": "Archive", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Navegação da página de listagem do blog", "theme.blog.paginator.newerEntries": "Publicações mais recentes", "theme.blog.paginator.olderEntries": "Publicações mais antigas", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Editar esta página", "theme.common.headingLinkTitle": "Link direto para {heading}", "theme.common.skipToMainContent": "Saltar para o conteúdo principal", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", "theme.docs.DocCard.categoryDescription.plurals": "1 item|{count} items", "theme.docs.breadcrumbs.home": "Home page", "theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "Tags:", "theme.tags.tagsPageLink": "Ver todas as Tags", - "theme.tags.tagsPageTitle": "Tags", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.title": "Unlisted page" + "theme.tags.tagsPageTitle": "Tags" } diff --git a/packages/docusaurus-theme-translations/locales/ru/theme-common.json b/packages/docusaurus-theme-translations/locales/ru/theme-common.json index f0a8f7f88146..dbc0f23e1c27 100644 --- a/packages/docusaurus-theme-translations/locales/ru/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/ru/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Архив", "theme.blog.archive.title": "Архив", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Навигация по странице списка блогов", "theme.blog.paginator.newerEntries": "Следующие записи", "theme.blog.paginator.olderEntries": "Предыдущие записи", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Отредактировать эту страницу", "theme.common.headingLinkTitle": "Прямая ссылка на {heading}", "theme.common.skipToMainContent": "Перейти к основному содержимому", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", "theme.docs.DocCard.categoryDescription.plurals": "{count} элемент|{count} элемента|{count} элементов", "theme.docs.breadcrumbs.home": "Главная страница", "theme.docs.breadcrumbs.navAriaLabel": "Навигационная цепочка текущей страницы", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Версии", "theme.tags.tagsListLabel": "Теги:", "theme.tags.tagsPageLink": "Посмотреть все теги", - "theme.tags.tagsPageTitle": "Теги", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.title": "Unlisted page" + "theme.tags.tagsPageTitle": "Теги" } diff --git a/packages/docusaurus-theme-translations/locales/sl/theme-common.json b/packages/docusaurus-theme-translations/locales/sl/theme-common.json index e9c8eb0f411f..03bb69ac1363 100644 --- a/packages/docusaurus-theme-translations/locales/sl/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/sl/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Arhiv", "theme.blog.archive.title": "Arhiv", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Navigacija kazala po blogu", "theme.blog.paginator.newerEntries": "Novejši prispevki", "theme.blog.paginator.olderEntries": "Starejši prispevki", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Uredi to stran", "theme.common.headingLinkTitle": "Direktna povezava na {heading}", "theme.common.skipToMainContent": "Preskoči na vsebino", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "Ta stran ni zabeležena. Iskalniki je ne bodo indeksirali, do nje bodo uporabniki lahko dostopali le z direktno povezavo.", + "theme.contentVisibility.unlistedBanner.title": "Nezabeležena stran", "theme.docs.DocCard.categoryDescription.plurals": "1 vnos|2 vnosy|{count} vnosy|{count} vnosov", "theme.docs.breadcrumbs.home": "Domača stran", "theme.docs.breadcrumbs.navAriaLabel": "Drobtine", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Verzije", "theme.tags.tagsListLabel": "Oznake:", "theme.tags.tagsPageLink": "Poglej vse oznake", - "theme.tags.tagsPageTitle": "Oznake", - "theme.unlistedContent.message": "Ta stran ni zabeležena. Iskalniki je ne bodo indeksirali, do nje bodo uporabniki lahko dostopali le z direktno povezavo.", - "theme.unlistedContent.title": "Nezabeležena stran" + "theme.tags.tagsPageTitle": "Oznake" } diff --git a/packages/docusaurus-theme-translations/locales/sr/theme-common.json b/packages/docusaurus-theme-translations/locales/sr/theme-common.json index 696e96728c9e..7e446ec3c2a7 100644 --- a/packages/docusaurus-theme-translations/locales/sr/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/sr/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Архива", "theme.blog.archive.title": "Архива", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Навигација за странице блога", "theme.blog.paginator.newerEntries": "Нови постови", "theme.blog.paginator.olderEntries": "Стари постови", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Уреди ову страницу", "theme.common.headingLinkTitle": "Веза до {heading}", "theme.common.skipToMainContent": "Пређи на главни садржај", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", "theme.docs.DocCard.categoryDescription.plurals": "1 item|{count} items", "theme.docs.breadcrumbs.home": "Home page", "theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Верзије", "theme.tags.tagsListLabel": "Ознаке:", "theme.tags.tagsPageLink": "Погледај све ознаке", - "theme.tags.tagsPageTitle": "Ознаке", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.title": "Unlisted page" + "theme.tags.tagsPageTitle": "Ознаке" } diff --git a/packages/docusaurus-theme-translations/locales/sv/theme-common.json b/packages/docusaurus-theme-translations/locales/sv/theme-common.json index c5f0dc31604d..03b86feefda5 100644 --- a/packages/docusaurus-theme-translations/locales/sv/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/sv/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Arkiv", "theme.blog.archive.title": "Arkiv", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Navigation av Blogglista", "theme.blog.paginator.newerEntries": "Nyare Inlägg", "theme.blog.paginator.olderEntries": "Äldre Inlägg", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Redigera denna sida", "theme.common.headingLinkTitle": "Direktlänk till {heading}", "theme.common.skipToMainContent": "Hoppa till huvudinnehåll", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", "theme.docs.DocCard.categoryDescription.plurals": "1 artikel|{count} artiklar", "theme.docs.breadcrumbs.home": "Hemsida", "theme.docs.breadcrumbs.navAriaLabel": "Sökväg", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versioner", "theme.tags.tagsListLabel": "Taggar:", "theme.tags.tagsPageLink": "Visa Alla Taggar", - "theme.tags.tagsPageTitle": "Taggar", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.title": "Unlisted page" + "theme.tags.tagsPageTitle": "Taggar" } diff --git a/packages/docusaurus-theme-translations/locales/tk/theme-common.json b/packages/docusaurus-theme-translations/locales/tk/theme-common.json index aad0c8d53b0d..4888f960f7e3 100644 --- a/packages/docusaurus-theme-translations/locales/tk/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/tk/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "duýduryş", "theme.blog.archive.description": "Arhiw", "theme.blog.archive.title": "Arhiw", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Blog sahypasynda nawigasiýa", "theme.blog.paginator.newerEntries": "Täze ýazgylar", "theme.blog.paginator.olderEntries": "Köne ýazgylar", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Bu sahypany üýtgetmek", "theme.common.headingLinkTitle": "{heading} sahypa göni geçiň", "theme.common.skipToMainContent": "Esasy mazmuna geç", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "Bu sahypa sanawda ýok. Bu sahypa gözleg enjamlarynda indekslemezler, diňe göni baglanyşygy bolan ulanyjylar elýeterli bolar.", + "theme.contentVisibility.unlistedBanner.title": "Sanawda ýok sahypa", "theme.docs.DocCard.categoryDescription.plurals": "{count} element|{count} elementler", "theme.docs.breadcrumbs.home": "Baş sahypa", "theme.docs.breadcrumbs.navAriaLabel": "Häzirki sahypa üçin nawigasiýa", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Wersiýalar", "theme.tags.tagsListLabel": "Tegler:", "theme.tags.tagsPageLink": "Ähli tegleri gör", - "theme.tags.tagsPageTitle": "Tegler", - "theme.unlistedContent.message": "Bu sahypa sanawda ýok. Bu sahypa gözleg enjamlarynda indekslemezler, diňe göni baglanyşygy bolan ulanyjylar elýeterli bolar.", - "theme.unlistedContent.title": "Sanawda ýok sahypa" + "theme.tags.tagsPageTitle": "Tegler" } diff --git a/packages/docusaurus-theme-translations/locales/tr/theme-common.json b/packages/docusaurus-theme-translations/locales/tr/theme-common.json index 9d1abe76ef14..484e88b6e89d 100644 --- a/packages/docusaurus-theme-translations/locales/tr/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/tr/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Arşiv", "theme.blog.archive.title": "Arşiv", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Blog gönderi sayfası navigasyonu", "theme.blog.paginator.newerEntries": "Yeni Girdiler", "theme.blog.paginator.olderEntries": "Eski Girdiler", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Bu sayfayı düzenle", "theme.common.headingLinkTitle": "{heading} doğrudan bağlantı", "theme.common.skipToMainContent": "Ana içeriğe geç", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "Bu sayfa listelenmemiş. Arama motorları dizine eklemez ve yalnızca doğrudan bağlantısı olan kullanıcılar buna erişebilir.", + "theme.contentVisibility.unlistedBanner.title": "Listelenmeyen sayfa", "theme.docs.DocCard.categoryDescription.plurals": "1 öğe|{count} öğe", "theme.docs.breadcrumbs.home": "Ana sayfa", "theme.docs.breadcrumbs.navAriaLabel": "Breadcrumbs", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Versiyonlar", "theme.tags.tagsListLabel": "Etiketler:", "theme.tags.tagsPageLink": "Tüm Etiketleri Görüntüle", - "theme.tags.tagsPageTitle": "Etiketler", - "theme.unlistedContent.message": "Bu sayfa listelenmemiş. Arama motorları dizine eklemez ve yalnızca doğrudan bağlantısı olan kullanıcılar buna erişebilir.", - "theme.unlistedContent.title": "Listelenmeyen sayfa" + "theme.tags.tagsPageTitle": "Etiketler" } diff --git a/packages/docusaurus-theme-translations/locales/uk/theme-common.json b/packages/docusaurus-theme-translations/locales/uk/theme-common.json index 12c35e144e03..399f958a01d4 100644 --- a/packages/docusaurus-theme-translations/locales/uk/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/uk/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Архів", "theme.blog.archive.title": "Архів", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Навігація по сторінці списку блогів", "theme.blog.paginator.newerEntries": "Наступні записи", "theme.blog.paginator.olderEntries": "Попередні записи", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Відредагувати цю сторінку", "theme.common.headingLinkTitle": "Пряме посилання на {heading}", "theme.common.skipToMainContent": "Перейти до основного вмісту", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", + "theme.contentVisibility.unlistedBanner.title": "Unlisted page", "theme.docs.DocCard.categoryDescription.plurals": "{count} елемент|{count} елементи|{count} елементів", "theme.docs.breadcrumbs.home": "Головна сторінка", "theme.docs.breadcrumbs.navAriaLabel": "Навігаційний ланцюжок поточної сторінки", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Версії", "theme.tags.tagsListLabel": "Теги:", "theme.tags.tagsPageLink": "Переглянути всі теги", - "theme.tags.tagsPageTitle": "Теги", - "theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "theme.unlistedContent.title": "Unlisted page" + "theme.tags.tagsPageTitle": "Теги" } diff --git a/packages/docusaurus-theme-translations/locales/vi/theme-common.json b/packages/docusaurus-theme-translations/locales/vi/theme-common.json index 0230f422dfb6..443cf03997df 100644 --- a/packages/docusaurus-theme-translations/locales/vi/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/vi/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "warning", "theme.blog.archive.description": "Lưu trữ", "theme.blog.archive.title": "Lưu trữ", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "Thanh điều hướng của trang danh sách bài viết", "theme.blog.paginator.newerEntries": "Các bài mới hơn", "theme.blog.paginator.olderEntries": "Các bài cũ hơn", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "Sửa trang này", "theme.common.headingLinkTitle": "Đường dẫn trực tiếp tới {heading}", "theme.common.skipToMainContent": "Nhảy tới nội dung", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "Trang này không được công khai. Công cụ tìm kiếm sẽ không đánh chỉ mục cho trang này và chỉ những người có liên kết mới có thể truy cập được trang.", + "theme.contentVisibility.unlistedBanner.title": "Trang không công khai", "theme.docs.DocCard.categoryDescription.plurals": "{count} mục", "theme.docs.breadcrumbs.home": "Trang chủ", "theme.docs.breadcrumbs.navAriaLabel": "Liên kết điều hướng", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "Phiên bản", "theme.tags.tagsListLabel": "Thẻ:", "theme.tags.tagsPageLink": "Xem tất cả Thẻ", - "theme.tags.tagsPageTitle": "Thẻ", - "theme.unlistedContent.message": "Trang này không được công khai. Công cụ tìm kiếm sẽ không đánh chỉ mục cho trang này và chỉ những người có liên kết mới có thể truy cập được trang.", - "theme.unlistedContent.title": "Trang không công khai" + "theme.tags.tagsPageTitle": "Thẻ" } diff --git a/packages/docusaurus-theme-translations/locales/zh-Hans/theme-common.json b/packages/docusaurus-theme-translations/locales/zh-Hans/theme-common.json index c382bc186f73..3bdf5970c212 100644 --- a/packages/docusaurus-theme-translations/locales/zh-Hans/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/zh-Hans/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "注意", "theme.blog.archive.description": "历史博文", "theme.blog.archive.title": "历史博文", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "博文列表分页导航", "theme.blog.paginator.newerEntries": "较新的博文", "theme.blog.paginator.olderEntries": "较旧的博文", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "编辑此页", "theme.common.headingLinkTitle": "{heading}的直接链接", "theme.common.skipToMainContent": "跳到主要内容", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "此页面未列出。搜索引擎不会对其索引,只有拥有直接链接的用户才能访问。", + "theme.contentVisibility.unlistedBanner.title": "未列出页", "theme.docs.DocCard.categoryDescription.plurals": "{count} 个项目", "theme.docs.breadcrumbs.home": "主页面", "theme.docs.breadcrumbs.navAriaLabel": "页面路径", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "选择版本", "theme.tags.tagsListLabel": "标签:", "theme.tags.tagsPageLink": "查看所有标签", - "theme.tags.tagsPageTitle": "标签", - "theme.unlistedContent.message": "此页面未列出。搜索引擎不会对其索引,只有拥有直接链接的用户才能访问。", - "theme.unlistedContent.title": "未列出页" + "theme.tags.tagsPageTitle": "标签" } diff --git a/packages/docusaurus-theme-translations/locales/zh-Hant/theme-common.json b/packages/docusaurus-theme-translations/locales/zh-Hant/theme-common.json index cbb172b61645..be927a604850 100644 --- a/packages/docusaurus-theme-translations/locales/zh-Hant/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/zh-Hant/theme-common.json @@ -22,6 +22,9 @@ "theme.admonition.warning": "注意", "theme.blog.archive.description": "歷史文章", "theme.blog.archive.title": "歷史文章", + "theme.blog.author.pageTitle": "{authorName} - {nPosts}", + "theme.blog.authorsList.pageTitle": "Authors", + "theme.blog.authorsList.viewAll": "View All Authors", "theme.blog.paginator.navAriaLabel": "部落格文章列表分頁導覽", "theme.blog.paginator.newerEntries": "較新的文章", "theme.blog.paginator.olderEntries": "較舊的文章", @@ -40,6 +43,10 @@ "theme.common.editThisPage": "編輯此頁", "theme.common.headingLinkTitle": "{heading}的直接連結", "theme.common.skipToMainContent": "跳至主要内容", + "theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.", + "theme.contentVisibility.draftBanner.title": "Draft page", + "theme.contentVisibility.unlistedBanner.message": "此頁面未列出。搜索引擎不會對其索引,只有擁有直接連結的用戶才能訪問。", + "theme.contentVisibility.unlistedBanner.title": "未列出頁", "theme.docs.DocCard.categoryDescription.plurals": "{count} 個項目", "theme.docs.breadcrumbs.home": "主頁面", "theme.docs.breadcrumbs.navAriaLabel": "頁面路徑", @@ -68,7 +75,5 @@ "theme.navbar.mobileVersionsDropdown.label": "選擇版本", "theme.tags.tagsListLabel": "標籤:", "theme.tags.tagsPageLink": "檢視所有標籤", - "theme.tags.tagsPageTitle": "標籤", - "theme.unlistedContent.message": "此頁面未列出。搜索引擎不會對其索引,只有擁有直接連結的用戶才能訪問。", - "theme.unlistedContent.title": "未列出頁" + "theme.tags.tagsPageTitle": "標籤" } diff --git a/project-words.txt b/project-words.txt index 8ff86be90d0e..528d635318ee 100644 --- a/project-words.txt +++ b/project-words.txt @@ -412,8 +412,6 @@ webpackbar webstorm Wolcott Xplorer -xslt -XSLT XSOAR Yacop yangshun From 44ddada37ac571c5ac7d447b51bfcad18533cf5e Mon Sep 17 00:00:00 2001 From: Bharatesh <64769271+bharateshwq@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:02:21 +0530 Subject: [PATCH 09/10] fix(docs): the _category_.json description attribute should display on generated index pages (#10324) Co-authored-by: sebastien --- .../sidebars/__tests__/__snapshots__/generator.test.ts.snap | 1 + .../src/sidebars/__tests__/generator.test.ts | 1 + .../src/sidebars/__tests__/validation.test.ts | 1 + .../docusaurus-plugin-content-docs/src/sidebars/generator.ts | 3 +++ .../src/sidebars/postProcessor.ts | 1 + packages/docusaurus-plugin-content-docs/src/sidebars/types.ts | 1 + .../docusaurus-plugin-content-docs/src/sidebars/validation.ts | 1 + .../_docs tests/tests/category-links/_category_.json | 1 + website/_dogfooding/docs-tests-sidebars.js | 1 + 9 files changed, 11 insertions(+) diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/generator.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/generator.test.ts.snap index 29dd570e2366..63430fd3632c 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/generator.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/generator.test.ts.snap @@ -217,6 +217,7 @@ exports[`DefaultSidebarItemsGenerator uses explicit link over the index/readme.{ { "collapsed": undefined, "collapsible": undefined, + "description": "Category description", "items": [ { "id": "parent/doc2", diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/generator.test.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/generator.test.ts index 2bb401457537..de104a1a254a 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/generator.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/generator.test.ts @@ -331,6 +331,7 @@ describe('DefaultSidebarItemsGenerator', () => { categoriesMetadata: { Category: { label: 'Category label', + description: 'Category description', link: { type: 'doc', id: 'doc3', // Using a "local doc id" ("doc1" instead of "parent/doc1") on purpose diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/validation.test.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/validation.test.ts index 8186601285a0..6620705ff712 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/validation.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/validation.test.ts @@ -282,6 +282,7 @@ describe('validateCategoryMetadataFile', () => { const content: CategoryMetadataFile = { className: 'className', label: 'Category Label', + description: 'Category Description', link: { type: 'generated-index', slug: 'slug', diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts index 0edf909bb9c8..20383b003ac8 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts @@ -249,6 +249,9 @@ Available doc IDs: ...(customProps !== undefined && {customProps}), ...(className !== undefined && {className}), items, + ...(categoryMetadata?.description && { + description: categoryMetadata?.description, + }), ...(link && {link}), }; } diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/postProcessor.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/postProcessor.ts index 3ec8a6f48124..b5cdc34aa913 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/postProcessor.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/postProcessor.ts @@ -64,6 +64,7 @@ function postProcessSidebarItem( .map((subItem) => postProcessSidebarItem(subItem, params)) .filter((v): v is SidebarItem => Boolean(v)), }; + // If the current category doesn't have subitems, we render a normal link // instead. if (category.items.length === 0) { diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/types.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/types.ts index 555a2b471843..9dbd23415d3f 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/types.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/types.ts @@ -217,6 +217,7 @@ export type PropSidebarBreadcrumbsItem = export type CategoryMetadataFile = { label?: string; position?: number; + description?: string; collapsed?: boolean; collapsible?: boolean; className?: string; diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/validation.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/validation.ts index e65eb41960f3..33492dc309c7 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/validation.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/validation.ts @@ -167,6 +167,7 @@ export function validateSidebars(sidebars: { const categoryMetadataFileSchema = Joi.object({ label: Joi.string(), + description: Joi.string(), position: Joi.number(), collapsed: Joi.boolean(), collapsible: Joi.boolean(), diff --git a/website/_dogfooding/_docs tests/tests/category-links/_category_.json b/website/_dogfooding/_docs tests/tests/category-links/_category_.json index 8d0c8da76ae0..7166d0a75525 100644 --- a/website/_dogfooding/_docs tests/tests/category-links/_category_.json +++ b/website/_dogfooding/_docs tests/tests/category-links/_category_.json @@ -1,5 +1,6 @@ { "label": "Category Links", + "description": "Category Links - Custom Description", "link": { "type": "generated-index", "slug": "/category-links-generated-index-slug" diff --git a/website/_dogfooding/docs-tests-sidebars.js b/website/_dogfooding/docs-tests-sidebars.js index c6f7ca8d5ba9..1e37a44d7c90 100644 --- a/website/_dogfooding/docs-tests-sidebars.js +++ b/website/_dogfooding/docs-tests-sidebars.js @@ -41,6 +41,7 @@ const sidebars = { { type: 'category', label: 'Sidebar item description tests', + description: 'Some custom category description', link: { type: 'generated-index', }, From a2e30bebc46314566d572f54b2af57d62f0dfd04 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 9 Aug 2024 11:35:50 +0200 Subject: [PATCH 10/10] fix(search): fix algolia search ignore ctrl + F in search input (#10342) --- .../src/theme/SearchBar/index.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx index 1325a2c2e9dd..aea9f0601361 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx @@ -156,6 +156,10 @@ function DocSearch({ const handleInput = useCallback( (event: KeyboardEvent) => { + if (event.key === 'f' && (event.metaKey || event.ctrlKey)) { + // ignore browser's ctrl+f + return; + } // prevents duplicate key insertion in the modal input event.preventDefault(); setInitialQuery(event.key);