feat(docs, blog): add support for tags.yml, predefined list of tags (#10137)

Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
Co-authored-by: OzakIOne <OzakIOne@users.noreply.github.com>
Co-authored-by: sebastien <lorber.sebastien@gmail.com>
Co-authored-by: slorber <slorber@users.noreply.github.com>
This commit is contained in:
ozaki 2024-05-31 17:32:08 +02:00 committed by GitHub
parent 1049294ba6
commit 0eb7b64aac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 2597 additions and 722 deletions

View file

@ -0,0 +1,16 @@
facebook:
label: Facebook
permalink: /facebook
description: Facebook tag description
hello:
label: Hello
permalink: /hello
description: Hello tag description
docusaurus:
label: Docusaurus
permalink: /docusaurus
description: Docusaurus tag description
hola:
label: Hola
permalink: /hola
description: Hola tag description

View file

@ -3,7 +3,7 @@ title: Happy 1st Birthday Slash!
authors: authors:
- name: Yangshun Tay - name: Yangshun Tay
- slorber - slorber
tags: [birthday] tags: [birthday,inlineTag,globalTag]
--- ---
Happy birthday! Happy birthday!

View file

@ -0,0 +1,5 @@
globalTag:
label: 'Global Tag label'
description: 'Global Tag description'
permalink: '/tags/global-tag-permalink'

View file

@ -3,6 +3,7 @@ title: Happy 1st Birthday Slash! (translated)
authors: authors:
- name: Yangshun Tay (translated) - name: Yangshun Tay (translated)
- slorber - slorber
tags: [inlineTag,globalTag]
--- ---
Happy birthday! (translated) Happy birthday! (translated)

View file

@ -0,0 +1,5 @@
globalTag:
label: 'Global Tag label (en)'
description: 'Global Tag description (en)'
permalink: 'global-tag-permalink (en)'

View file

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

View file

@ -3,6 +3,8 @@
exports[`blog plugin process blog posts load content 1`] = ` exports[`blog plugin process blog posts load content 1`] = `
{ {
"/blog/tags/tag-1": { "/blog/tags/tag-1": {
"description": undefined,
"inline": true,
"items": [ "items": [
"/simple/slug/another", "/simple/slug/another",
"/another/tags", "/another/tags",
@ -63,6 +65,8 @@ exports[`blog plugin process blog posts load content 1`] = `
"unlisted": false, "unlisted": false,
}, },
"/blog/tags/tag-2": { "/blog/tags/tag-2": {
"description": undefined,
"inline": true,
"items": [ "items": [
"/another/tags", "/another/tags",
"/another/tags2", "/another/tags2",
@ -148,6 +152,8 @@ exports[`blog plugin process blog posts load content 2`] = `
"source": "@site/blog/another-simple-slug-with-tags.md", "source": "@site/blog/another-simple-slug-with-tags.md",
"tags": [ "tags": [
{ {
"description": undefined,
"inline": true,
"label": "tag1", "label": "tag1",
"permalink": "/blog/tags/tag-1", "permalink": "/blog/tags/tag-1",
}, },
@ -189,10 +195,14 @@ exports[`blog plugin process blog posts load content 2`] = `
"source": "@site/blog/another-with-tags.md", "source": "@site/blog/another-with-tags.md",
"tags": [ "tags": [
{ {
"description": undefined,
"inline": true,
"label": "tag1", "label": "tag1",
"permalink": "/blog/tags/tag-1", "permalink": "/blog/tags/tag-1",
}, },
{ {
"description": undefined,
"inline": true,
"label": "tag2", "label": "tag2",
"permalink": "/blog/tags/tag-2", "permalink": "/blog/tags/tag-2",
}, },
@ -230,10 +240,14 @@ exports[`blog plugin process blog posts load content 2`] = `
"source": "@site/blog/another-with-tags2.md", "source": "@site/blog/another-with-tags2.md",
"tags": [ "tags": [
{ {
"description": undefined,
"inline": true,
"label": "tag1", "label": "tag1",
"permalink": "/blog/tags/tag-1", "permalink": "/blog/tags/tag-1",
}, },
{ {
"description": undefined,
"inline": true,
"label": "tag2", "label": "tag2",
"permalink": "/blog/tags/tag-2", "permalink": "/blog/tags/tag-2",
}, },
@ -248,6 +262,8 @@ exports[`blog plugin process blog posts load content 2`] = `
exports[`blog plugin works on blog tags without pagination 1`] = ` exports[`blog plugin works on blog tags without pagination 1`] = `
{ {
"/blog/tags/tag-1": { "/blog/tags/tag-1": {
"description": undefined,
"inline": true,
"items": [ "items": [
"/simple/slug/another", "/simple/slug/another",
"/another/tags", "/another/tags",
@ -278,6 +294,8 @@ exports[`blog plugin works on blog tags without pagination 1`] = `
"unlisted": false, "unlisted": false,
}, },
"/blog/tags/tag-2": { "/blog/tags/tag-2": {
"description": undefined,
"inline": true,
"items": [ "items": [
"/another/tags", "/another/tags",
"/another/tags2", "/another/tags2",
@ -306,6 +324,8 @@ exports[`blog plugin works on blog tags without pagination 1`] = `
"unlisted": false, "unlisted": false,
}, },
"/blog/tags/unlisted": { "/blog/tags/unlisted": {
"description": undefined,
"inline": true,
"items": [ "items": [
"/another/blog-with-tags-unlisted", "/another/blog-with-tags-unlisted",
], ],
@ -337,6 +357,8 @@ exports[`blog plugin works on blog tags without pagination 1`] = `
exports[`blog plugin works with blog tags 1`] = ` exports[`blog plugin works with blog tags 1`] = `
{ {
"/blog/tags/tag-1": { "/blog/tags/tag-1": {
"description": undefined,
"inline": true,
"items": [ "items": [
"/simple/slug/another", "/simple/slug/another",
"/another/tags", "/another/tags",
@ -382,6 +404,8 @@ exports[`blog plugin works with blog tags 1`] = `
"unlisted": false, "unlisted": false,
}, },
"/blog/tags/tag-2": { "/blog/tags/tag-2": {
"description": undefined,
"inline": true,
"items": [ "items": [
"/another/tags", "/another/tags",
"/another/tags2", "/another/tags2",
@ -410,6 +434,8 @@ exports[`blog plugin works with blog tags 1`] = `
"unlisted": false, "unlisted": false,
}, },
"/blog/tags/unlisted": { "/blog/tags/unlisted": {
"description": undefined,
"inline": true,
"items": [ "items": [
"/another/blog-with-tags-unlisted", "/another/blog-with-tags-unlisted",
], ],

View file

@ -100,6 +100,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
readingTime: ({content, defaultReadingTime}) => readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}), defaultReadingTime({content}),
truncateMarker: /<!--\s*truncate\s*-->/, truncateMarker: /<!--\s*truncate\s*-->/,
onInlineTags: 'ignore',
} as PluginOptions, } as PluginOptions,
); );
@ -141,6 +142,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
readingTime: ({content, defaultReadingTime}) => readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}), defaultReadingTime({content}),
truncateMarker: /<!--\s*truncate\s*-->/, truncateMarker: /<!--\s*truncate\s*-->/,
onInlineTags: 'ignore',
} as PluginOptions, } as PluginOptions,
); );
@ -194,6 +196,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
readingTime: ({content, defaultReadingTime}) => readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}), defaultReadingTime({content}),
truncateMarker: /<!--\s*truncate\s*-->/, truncateMarker: /<!--\s*truncate\s*-->/,
onInlineTags: 'ignore',
} as PluginOptions, } as PluginOptions,
); );
@ -238,6 +241,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
readingTime: ({content, defaultReadingTime}) => readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}), defaultReadingTime({content}),
truncateMarker: /<!--\s*truncate\s*-->/, truncateMarker: /<!--\s*truncate\s*-->/,
onInlineTags: 'ignore',
} as PluginOptions, } as PluginOptions,
); );
@ -282,6 +286,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
readingTime: ({content, defaultReadingTime}) => readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}), defaultReadingTime({content}),
truncateMarker: /<!--\s*truncate\s*-->/, truncateMarker: /<!--\s*truncate\s*-->/,
onInlineTags: 'ignore',
} as PluginOptions, } as PluginOptions,
); );

View file

@ -160,6 +160,8 @@ describe('blog plugin', () => {
); );
expect(relativePathsToWatch).toEqual([ expect(relativePathsToWatch).toEqual([
'i18n/en/docusaurus-plugin-content-blog/authors.yml', 'i18n/en/docusaurus-plugin-content-blog/authors.yml',
'i18n/en/docusaurus-plugin-content-blog/tags.yml',
'blog/tags.yml',
'i18n/en/docusaurus-plugin-content-blog/**/*.{md,mdx}', 'i18n/en/docusaurus-plugin-content-blog/**/*.{md,mdx}',
'blog/**/*.{md,mdx}', 'blog/**/*.{md,mdx}',
]); ]);
@ -188,6 +190,8 @@ describe('blog plugin', () => {
prevItem: undefined, prevItem: undefined,
tags: [ tags: [
{ {
description: undefined,
inline: true,
label: 'date', label: 'date',
permalink: '/blog/tags/date', permalink: '/blog/tags/date',
}, },
@ -232,9 +236,23 @@ describe('blog plugin', () => {
}, },
'slorber', 'slorber',
], ],
tags: ['inlineTag', 'globalTag'],
title: 'Happy 1st Birthday Slash! (translated)', title: 'Happy 1st Birthday Slash! (translated)',
}, },
tags: [], tags: [
{
description: undefined,
inline: true,
label: 'inlineTag',
permalink: '/blog/tags/inline-tag',
},
{
description: 'Global Tag description (en)',
inline: false,
label: 'Global Tag label (en)',
permalink: '/blog/tags/global-tag-permalink (en)',
},
],
prevItem: { prevItem: {
permalink: '/blog/date-matter', permalink: '/blog/date-matter',
title: 'date-matter', title: 'date-matter',
@ -269,10 +287,14 @@ describe('blog plugin', () => {
}, },
tags: [ tags: [
{ {
description: undefined,
inline: true,
label: 'date', label: 'date',
permalink: '/blog/tags/date', permalink: '/blog/tags/date',
}, },
{ {
description: undefined,
inline: true,
label: 'complex', label: 'complex',
permalink: '/blog/tags/complex', permalink: '/blog/tags/complex',
}, },
@ -516,6 +538,8 @@ describe('blog plugin', () => {
postsPerPage: 1, postsPerPage: 1,
processBlogPosts: async ({blogPosts}) => processBlogPosts: async ({blogPosts}) =>
blogPosts.filter((blog) => blog.metadata.tags[0]?.label === 'tag1'), blogPosts.filter((blog) => blog.metadata.tags[0]?.label === 'tag1'),
onInlineTags: 'ignore',
tags: false,
}, },
DefaultI18N, DefaultI18N,
); );

View file

@ -36,13 +36,15 @@ describe('validateOptions', () => {
}); });
it('accepts correctly defined user options', () => { it('accepts correctly defined user options', () => {
const userOptions = { const userOptions: Options = {
...defaultOptions, ...defaultOptions,
feedOptions: {type: 'rss' as const, title: 'myTitle'}, feedOptions: {type: 'rss' as const, title: 'myTitle'},
path: 'not_blog', path: 'not_blog',
routeBasePath: '/myBlog', routeBasePath: '/myBlog',
postsPerPage: 5, postsPerPage: 5,
include: ['api/*', 'docs/*'], include: ['api/*', 'docs/*'],
tags: 'customTags.yml',
onInlineTags: 'warn',
}; };
expect(testValidate(userOptions)).toEqual({ expect(testValidate(userOptions)).toEqual({
...userOptions, ...userOptions,
@ -172,4 +174,68 @@ describe('validateOptions', () => {
`""blogSidebarTitle" must be a string"`, `""blogSidebarTitle" must be a string"`,
); );
}); });
describe('tags', () => {
it('accepts tags - undefined', () => {
expect(testValidate({tags: undefined}).tags).toBeUndefined();
});
it('accepts tags - null', () => {
expect(testValidate({tags: null}).tags).toBeNull();
});
it('accepts tags - false', () => {
expect(testValidate({tags: false}).tags).toBeFalsy();
});
it('accepts tags - customTags.yml', () => {
expect(testValidate({tags: 'customTags.yml'}).tags).toBe(
'customTags.yml',
);
});
it('rejects tags - 42', () => {
// @ts-expect-error: test
expect(() => testValidate({tags: 42})).toThrowErrorMatchingInlineSnapshot(
`""tags" must be a string"`,
);
});
});
describe('onInlineTags', () => {
it('accepts onInlineTags - undefined', () => {
expect(testValidate({onInlineTags: undefined}).onInlineTags).toBe('warn');
});
it('accepts onInlineTags - "throw"', () => {
expect(testValidate({onInlineTags: 'throw'}).onInlineTags).toBe('throw');
});
it('rejects onInlineTags - "trace"', () => {
expect(() =>
// @ts-expect-error: test
testValidate({onInlineTags: 'trace'}),
).toThrowErrorMatchingInlineSnapshot(
`""onInlineTags" must be one of [ignore, log, warn, throw]"`,
);
});
it('rejects onInlineTags - null', () => {
expect(() =>
// @ts-expect-error: test
testValidate({onInlineTags: 42}),
).toThrowErrorMatchingInlineSnapshot(
`""onInlineTags" must be one of [ignore, log, warn, throw]"`,
);
});
it('rejects onInlineTags - 42', () => {
expect(() =>
// @ts-expect-error: test
testValidate({onInlineTags: 42}),
).toThrowErrorMatchingInlineSnapshot(
`""onInlineTags" must be one of [ignore, log, warn, throw]"`,
);
});
});
}); });

View file

@ -18,7 +18,6 @@ import {
getFolderContainingFile, getFolderContainingFile,
posixPath, posixPath,
Globby, Globby,
normalizeFrontMatterTags,
groupTaggedItems, groupTaggedItems,
getTagVisibility, getTagVisibility,
getFileCommitDate, getFileCommitDate,
@ -26,9 +25,12 @@ import {
isUnlisted, isUnlisted,
isDraft, isDraft,
readLastUpdateData, readLastUpdateData,
normalizeTags,
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import {getTagsFile} from '@docusaurus/utils-validation';
import {validateBlogPostFrontMatter} from './frontMatter'; import {validateBlogPostFrontMatter} from './frontMatter';
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors'; import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
import type {TagsFile} from '@docusaurus/utils';
import type {LoadContext, ParseFrontMatter} from '@docusaurus/types'; import type {LoadContext, ParseFrontMatter} from '@docusaurus/types';
import type { import type {
PluginOptions, PluginOptions,
@ -125,9 +127,11 @@ export function getBlogTags({
isUnlisted: (item) => item.metadata.unlisted, isUnlisted: (item) => item.metadata.unlisted,
}); });
return { return {
inline: tag.inline,
label: tag.label, label: tag.label,
items: tagVisibility.listedItems.map((item) => item.id),
permalink: tag.permalink, permalink: tag.permalink,
description: tag.description,
items: tagVisibility.listedItems.map((item) => item.id),
pages: paginateBlogPosts({ pages: paginateBlogPosts({
blogPosts: tagVisibility.listedItems, blogPosts: tagVisibility.listedItems,
basePageUrl: tag.permalink, basePageUrl: tag.permalink,
@ -197,6 +201,7 @@ async function processBlogSourceFile(
contentPaths: BlogContentPaths, contentPaths: BlogContentPaths,
context: LoadContext, context: LoadContext,
options: PluginOptions, options: PluginOptions,
tagsFile: TagsFile | null,
authorsMap?: AuthorsMap, authorsMap?: AuthorsMap,
): Promise<BlogPost | undefined> { ): Promise<BlogPost | undefined> {
const { const {
@ -315,13 +320,21 @@ async function processBlogSourceFile(
return undefined; return undefined;
} }
const tagsBasePath = normalizeUrl([ const tagsBaseRoutePath = normalizeUrl([
baseUrl, baseUrl,
routeBasePath, routeBasePath,
tagsRouteBasePath, tagsRouteBasePath,
]); ]);
const authors = getBlogPostAuthors({authorsMap, frontMatter, baseUrl}); const authors = getBlogPostAuthors({authorsMap, frontMatter, baseUrl});
const tags = normalizeTags({
options,
source: blogSourceRelative,
frontMatterTags: frontMatter.tags,
tagsBaseRoutePath,
tagsFile,
});
return { return {
id: slug, id: slug,
metadata: { metadata: {
@ -331,7 +344,7 @@ async function processBlogSourceFile(
title, title,
description, description,
date, date,
tags: normalizeFrontMatterTags(tagsBasePath, frontMatter.tags), tags,
readingTime: showReadingTime readingTime: showReadingTime
? options.readingTime({ ? options.readingTime({
content, content,
@ -371,6 +384,8 @@ export async function generateBlogPosts(
authorsMapPath: options.authorsMapPath, authorsMapPath: options.authorsMapPath,
}); });
const tagsFile = await getTagsFile({contentPaths, tags: options.tags});
async function doProcessBlogSourceFile(blogSourceFile: string) { async function doProcessBlogSourceFile(blogSourceFile: string) {
try { try {
return await processBlogSourceFile( return await processBlogSourceFile(
@ -378,6 +393,7 @@ export async function generateBlogPosts(
contentPaths, contentPaths,
context, context,
options, options,
tagsFile,
authorsMap, authorsMap,
); );
} catch (err) { } catch (err) {

View file

@ -20,6 +20,7 @@ import {
DEFAULT_PLUGIN_ID, DEFAULT_PLUGIN_ID,
resolveMarkdownLinkPathname, resolveMarkdownLinkPathname,
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import {getTagsFilePathsToWatch} from '@docusaurus/utils-validation';
import { import {
getSourceToPermalink, getSourceToPermalink,
getBlogTags, getBlogTags,
@ -104,9 +105,16 @@ export default async function pluginContentBlog(
(contentPath) => include.map((pattern) => `${contentPath}/${pattern}`), (contentPath) => include.map((pattern) => `${contentPath}/${pattern}`),
); );
return [authorsMapFilePath, ...contentMarkdownGlobs].filter( const tagsFilePaths = getTagsFilePathsToWatch({
Boolean, contentPaths,
) as string[]; tags: options.tags,
});
return [
authorsMapFilePath,
...tagsFilePaths,
...contentMarkdownGlobs,
].filter(Boolean) as string[];
}, },
getTranslationFiles() { getTranslationFiles() {

View file

@ -54,6 +54,8 @@ export const DEFAULT_OPTIONS: PluginOptions = {
showLastUpdateTime: false, showLastUpdateTime: false,
showLastUpdateAuthor: false, showLastUpdateAuthor: false,
processBlogPosts: async () => undefined, processBlogPosts: async () => undefined,
onInlineTags: 'warn',
tags: undefined,
}; };
const PluginOptionSchema = Joi.object<PluginOptions>({ const PluginOptionSchema = Joi.object<PluginOptions>({
@ -144,6 +146,13 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
processBlogPosts: Joi.function() processBlogPosts: Joi.function()
.optional() .optional()
.default(() => DEFAULT_OPTIONS.processBlogPosts), .default(() => DEFAULT_OPTIONS.processBlogPosts),
onInlineTags: Joi.string()
.equal('ignore', 'log', 'warn', 'throw')
.default(DEFAULT_OPTIONS.onInlineTags),
tags: Joi.string()
.disallow('')
.allow(null, false)
.default(() => DEFAULT_OPTIONS.tags),
}).default(DEFAULT_OPTIONS); }).default(DEFAULT_OPTIONS);
export function validateOptions({ export function validateOptions({

View file

@ -4,7 +4,6 @@
* This source code is licensed under the MIT license found in the * This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
/// <reference types="@docusaurus/module-type-aliases" /> /// <reference types="@docusaurus/module-type-aliases" />
declare module '@docusaurus/plugin-content-blog' { declare module '@docusaurus/plugin-content-blog' {
@ -12,9 +11,10 @@ declare module '@docusaurus/plugin-content-blog' {
import type {MDXOptions} from '@docusaurus/mdx-loader'; import type {MDXOptions} from '@docusaurus/mdx-loader';
import type { import type {
FrontMatterTag, FrontMatterTag,
Tag, TagMetadata,
LastUpdateData, LastUpdateData,
FrontMatterLastUpdate, FrontMatterLastUpdate,
TagsPluginOptions,
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import type {DocusaurusConfig, Plugin, LoadContext} from '@docusaurus/types'; import type {DocusaurusConfig, Plugin, LoadContext} from '@docusaurus/types';
import type {Item as FeedItem} from 'feed'; import type {Item as FeedItem} from 'feed';
@ -236,7 +236,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
/** Front matter, as-is. */ /** Front matter, as-is. */
readonly frontMatter: BlogPostFrontMatter & {[key: string]: unknown}; readonly frontMatter: BlogPostFrontMatter & {[key: string]: unknown};
/** Tags, normalized. */ /** Tags, normalized. */
readonly tags: Tag[]; readonly tags: TagMetadata[];
/** /**
* Marks the post as unlisted and visibly hides it unless directly accessed. * Marks the post as unlisted and visibly hides it unless directly accessed.
*/ */
@ -345,103 +345,104 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
/** /**
* Plugin options after normalization. * Plugin options after normalization.
*/ */
export type PluginOptions = MDXOptions & { export type PluginOptions = MDXOptions &
/** Plugin ID. */ TagsPluginOptions & {
id?: string; /** Plugin ID. */
/** id?: string;
* Path to the blog content directory on the file system, relative to site /**
* directory. * Path to the blog content directory on the file system, relative to site
*/ * directory.
path: string; */
/** path: string;
* URL route for the blog section of your site. **DO NOT** include a /**
* trailing slash. Use `/` to put the blog at root path. * URL route for the blog section of your site. **DO NOT** include a
*/ * trailing slash. Use `/` to put the blog at root path.
routeBasePath: string; */
/** routeBasePath: string;
* URL route for the tags section of your blog. Will be appended to /**
* `routeBasePath`. * URL route for the tags section of your blog. Will be appended to
*/ * `routeBasePath`.
tagsBasePath: string; */
/** tagsBasePath: string;
* URL route for the pages section of your blog. Will be appended to /**
* `routeBasePath`. * URL route for the pages section of your blog. Will be appended to
*/ * `routeBasePath`.
pageBasePath: string; */
/** pageBasePath: string;
* URL route for the archive section of your blog. Will be appended to /**
* `routeBasePath`. **DO NOT** include a trailing slash. Use `null` to * URL route for the archive section of your blog. Will be appended to
* disable generation of archive. * `routeBasePath`. **DO NOT** include a trailing slash. Use `null` to
*/ * disable generation of archive.
archiveBasePath: string | null; */
/** archiveBasePath: string | null;
* Array of glob patterns matching Markdown files to be built, relative to /**
* the content path. * Array of glob patterns matching Markdown files to be built, relative to
*/ * the content path.
include: string[]; */
/** include: string[];
* Array of glob patterns matching Markdown files to be excluded. Serves as /**
* refinement based on the `include` option. * Array of glob patterns matching Markdown files to be excluded. Serves as
*/ * refinement based on the `include` option.
exclude: string[]; */
/** exclude: string[];
* Number of posts to show per page in the listing page. Use `'ALL'` to /**
* display all posts on one listing page. * Number of posts to show per page in the listing page. Use `'ALL'` to
*/ * display all posts on one listing page.
postsPerPage: number | 'ALL'; */
/** Root component of the blog listing page. */ postsPerPage: number | 'ALL';
blogListComponent: string; /** Root component of the blog listing page. */
/** Root component of each blog post page. */ blogListComponent: string;
blogPostComponent: string; /** Root component of each blog post page. */
/** Root component of the tags list page. */ blogPostComponent: string;
blogTagsListComponent: string; /** Root component of the tags list page. */
/** Root component of the "posts containing tag" page. */ blogTagsListComponent: string;
blogTagsPostsComponent: string; /** Root component of the "posts containing tag" page. */
/** Root component of the blog archive page. */ blogTagsPostsComponent: string;
blogArchiveComponent: string; /** Root component of the blog archive page. */
/** Blog page title for better SEO. */ blogArchiveComponent: string;
blogTitle: string; /** Blog page title for better SEO. */
/** Blog page meta description for better SEO. */ blogTitle: string;
blogDescription: string; /** Blog page meta description for better SEO. */
/** blogDescription: string;
* Number of blog post elements to show in the blog sidebar. `'ALL'` to show /**
* all blog posts; `0` to disable. * Number of blog post elements to show in the blog sidebar. `'ALL'` to show
*/ * all blog posts; `0` to disable.
blogSidebarCount: number | 'ALL'; */
/** Title of the blog sidebar. */ blogSidebarCount: number | 'ALL';
blogSidebarTitle: string; /** Title of the blog sidebar. */
/** Truncate marker marking where the summary ends. */ blogSidebarTitle: string;
truncateMarker: RegExp; /** Truncate marker marking where the summary ends. */
/** Show estimated reading time for the blog post. */ truncateMarker: RegExp;
showReadingTime: boolean; /** Show estimated reading time for the blog post. */
/** Blog feed. */ showReadingTime: boolean;
feedOptions: FeedOptions; /** Blog feed. */
/** feedOptions: FeedOptions;
* Base URL to edit your site. The final URL is computed by `editUrl + /**
* relativePostPath`. Using a function allows more nuanced control for each * Base URL to edit your site. The final URL is computed by `editUrl +
* file. Omitting this variable entirely will disable edit links. * relativePostPath`. Using a function allows more nuanced control for each
*/ * file. Omitting this variable entirely will disable edit links.
editUrl?: string | EditUrlFunction; */
/** editUrl?: string | EditUrlFunction;
* The edit URL will target the localized file, instead of the original /**
* unlocalized file. Ignored when `editUrl` is a function. * The edit URL will target the localized file, instead of the original
*/ * unlocalized file. Ignored when `editUrl` is a function.
editLocalizedFiles?: boolean; */
/** Path to the authors map file, relative to the blog content directory. */ editLocalizedFiles?: boolean;
authorsMapPath: string; /** Path to the authors map file, relative to the blog content directory. */
/** A callback to customize the reading time number displayed. */ authorsMapPath: string;
readingTime: ReadingTimeFunctionOption; /** A callback to customize the reading time number displayed. */
/** Governs the direction of blog post sorting. */ readingTime: ReadingTimeFunctionOption;
sortPosts: 'ascending' | 'descending'; /** Governs the direction of blog post sorting. */
/** Whether to display the last date the doc was updated. */ sortPosts: 'ascending' | 'descending';
showLastUpdateTime: boolean; /** Whether to display the last date the doc was updated. */
/** Whether to display the author who last updated the doc. */ showLastUpdateTime: boolean;
showLastUpdateAuthor: boolean; /** Whether to display the author who last updated the doc. */
/** An optional function which can be used to transform blog posts showLastUpdateAuthor: boolean;
* (filter, modify, delete, etc...). /** An optional function which can be used to transform blog posts
*/ * (filter, modify, delete, etc...).
processBlogPosts: ProcessBlogPostsFn; */
}; processBlogPosts: ProcessBlogPostsFn;
};
/** /**
* Feed options, as provided by user config. `type` accepts `all` as shortcut * Feed options, as provided by user config. `type` accepts `all` as shortcut
@ -494,7 +495,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
[permalink: string]: BlogTag; [permalink: string]: BlogTag;
}; };
export type BlogTag = Tag & { export type BlogTag = TagMetadata & {
/** Blog post permalinks. */ /** Blog post permalinks. */
items: string[]; items: string[];
pages: BlogPaginated[]; pages: BlogPaginated[];

View file

@ -13,6 +13,7 @@ export function toTagsProp({blogTags}: {blogTags: BlogTags}): TagsListItem[] {
.map((tag) => ({ .map((tag) => ({
label: tag.label, label: tag.label,
permalink: tag.permalink, permalink: tag.permalink,
description: tag.description,
count: tag.items.length, count: tag.items.length,
})); }));
} }
@ -27,6 +28,7 @@ export function toTagProp({
return { return {
label: tag.label, label: tag.label,
permalink: tag.permalink, permalink: tag.permalink,
description: tag.description,
allTagsPath: blogTagsListPath, allTagsPath: blogTagsListPath,
count: tag.items.length, count: tag.items.length,
unlisted: tag.unlisted, unlisted: tag.unlisted,

View file

@ -5,6 +5,7 @@ slug: bazSlug.html
pagination_label: baz pagination_label pagination_label: baz pagination_label
tags: tags:
- tag 1 - tag 1
- globalTag1
- tag-1 - tag-1
- label: tag 2 - label: tag 2
permalink: tag2-custom-permalink permalink: tag2-custom-permalink

View file

@ -0,0 +1,5 @@
globalTag1:
label: 'Global Tag 1 label'
description: 'Global Tag 1 description'
permalink: 'global-tag-1-permalink'

View file

@ -1,4 +1,6 @@
--- ---
slug: / slug: /
tags: [inlineTag-v1.0.0, globalTag-v1.0.0]
--- ---
Hello `1.0.0` ! (translated en) Hello `1.0.0` ! (translated en)

View file

@ -0,0 +1,5 @@
globalTag-v1.0.0:
label: 'globalTag-v1.0.0 label (en)'
description: 'globalTag-v1.0.0 description (en)'
permalink: 'globalTag-v1.0.0 permalink (en)'

View file

@ -1,4 +1,6 @@
--- ---
slug: / slug: /
tags: [inlineTag-v1.0.0, globalTag-v1.0.0]
--- ---
Hello `1.0.0` ! (translated fr) Hello `1.0.0` ! (translated fr)

View file

@ -0,0 +1,5 @@
globalTag-v1.0.0:
label: 'globalTag-v1.0.0 label (fr)'
description: 'globalTag-v1.0.0 description (fr)'
permalink: 'globalTag-v1.0.0 permalink (fr)'

View file

@ -1,4 +1,6 @@
--- ---
slug: / slug: /
tags: [inlineTag-v1.0.0, globalTag-v1.0.0]
--- ---
Hello `1.0.0` ! Hello `1.0.0` !

View file

@ -0,0 +1,5 @@
globalTag-v1.0.0:
label: 'globalTag-v1.0.0 label'
description: 'globalTag-v1.0.0 description'
permalink: 'globalTag-v1.0.0 permalink'

View file

@ -1,4 +1,6 @@
--- ---
slug: / slug: /
tags: [inlineTag-v1.0.1, globalTag-v1.0.1]
--- ---
Hello `1.0.1` ! Hello `1.0.1` !

View file

@ -0,0 +1,5 @@
globalTag-v1.0.1:
label: 'globalTag-v1.0.1 label'
description: 'globalTag-v1.0.1 description'
permalink: 'globalTag-v1.0.1 permalink'

View file

@ -58,6 +58,7 @@ exports[`simple website content 1`] = `
"slug": "bazSlug.html", "slug": "bazSlug.html",
"tags": [ "tags": [
"tag 1", "tag 1",
"globalTag1",
"tag-1", "tag-1",
{ {
"label": "tag 2", "label": "tag 2",
@ -85,10 +86,26 @@ exports[`simple website content 1`] = `
"sourceDirName": "foo", "sourceDirName": "foo",
"tags": [ "tags": [
{ {
"description": undefined,
"inline": true,
"label": "tag 1", "label": "tag 1",
"permalink": "/docs/tags/tag-1", "permalink": "/docs/tags/tag-1",
}, },
{ {
"description": "Global Tag 1 description",
"inline": false,
"label": "Global Tag 1 label",
"permalink": "/docs/tags/global-tag-1-permalink",
},
{
"description": undefined,
"inline": true,
"label": "tag-1",
"permalink": "/docs/tags/tag-1",
},
{
"description": undefined,
"inline": true,
"label": "tag 2", "label": "tag 2",
"permalink": "/docs/tags/tag2-custom-permalink", "permalink": "/docs/tags/tag2-custom-permalink",
}, },
@ -130,10 +147,14 @@ exports[`simple website content 2`] = `
"sourceDirName": ".", "sourceDirName": ".",
"tags": [ "tags": [
{ {
"description": undefined,
"inline": true,
"label": "tag-1", "label": "tag-1",
"permalink": "/docs/tags/tag-1", "permalink": "/docs/tags/tag-1",
}, },
{ {
"description": undefined,
"inline": true,
"label": "tag 3", "label": "tag 3",
"permalink": "/docs/tags/tag-3", "permalink": "/docs/tags/tag-3",
}, },
@ -567,6 +588,7 @@ exports[`simple website content: data 1`] = `
"slug": "bazSlug.html", "slug": "bazSlug.html",
"tags": [ "tags": [
"tag 1", "tag 1",
"globalTag1",
"tag-1", "tag-1",
{ {
"label": "tag 2", "label": "tag 2",
@ -594,10 +616,26 @@ exports[`simple website content: data 1`] = `
"sourceDirName": "foo", "sourceDirName": "foo",
"tags": [ "tags": [
{ {
"description": undefined,
"inline": true,
"label": "tag 1", "label": "tag 1",
"permalink": "/docs/tags/tag-1", "permalink": "/docs/tags/tag-1",
}, },
{ {
"description": "Global Tag 1 description",
"inline": false,
"label": "Global Tag 1 label",
"permalink": "/docs/tags/global-tag-1-permalink",
},
{
"description": undefined,
"inline": true,
"label": "tag-1",
"permalink": "/docs/tags/tag-1",
},
{
"description": undefined,
"inline": true,
"label": "tag 2", "label": "tag 2",
"permalink": "/docs/tags/tag2-custom-permalink", "permalink": "/docs/tags/tag2-custom-permalink",
}, },
@ -663,10 +701,14 @@ exports[`simple website content: data 1`] = `
"sourceDirName": ".", "sourceDirName": ".",
"tags": [ "tags": [
{ {
"description": undefined,
"inline": true,
"label": "tag-1", "label": "tag-1",
"permalink": "/docs/tags/tag-1", "permalink": "/docs/tags/tag-1",
}, },
{ {
"description": undefined,
"inline": true,
"label": "tag 3", "label": "tag 3",
"permalink": "/docs/tags/tag-3", "permalink": "/docs/tags/tag-3",
}, },
@ -1520,22 +1562,54 @@ exports[`simple website content: route config 1`] = `
"tags": [ "tags": [
{ {
"count": 2, "count": 2,
"description": undefined,
"label": "tag 1", "label": "tag 1",
"permalink": "/docs/tags/tag-1", "permalink": "/docs/tags/tag-1",
}, },
{ {
"count": 1, "count": 1,
"description": "Global Tag 1 description",
"label": "Global Tag 1 label",
"permalink": "/docs/tags/global-tag-1-permalink",
},
{
"count": 1,
"description": undefined,
"label": "tag 2", "label": "tag 2",
"permalink": "/docs/tags/tag2-custom-permalink", "permalink": "/docs/tags/tag2-custom-permalink",
}, },
{ {
"count": 1, "count": 1,
"description": undefined,
"label": "tag 3", "label": "tag 3",
"permalink": "/docs/tags/tag-3", "permalink": "/docs/tags/tag-3",
}, },
], ],
}, },
}, },
{
"component": "@theme/DocTagDocListPage",
"exact": true,
"path": "/docs/tags/global-tag-1-permalink",
"props": {
"tag": {
"allTagsPath": "/docs/tags",
"count": 1,
"description": "Global Tag 1 description",
"items": [
{
"description": "Images",
"id": "foo/baz",
"permalink": "/docs/foo/bazSlug.html",
"title": "baz",
},
],
"label": "Global Tag 1 label",
"permalink": "/docs/tags/global-tag-1-permalink",
"unlisted": false,
},
},
},
{ {
"component": "@theme/DocTagDocListPage", "component": "@theme/DocTagDocListPage",
"exact": true, "exact": true,
@ -1544,6 +1618,7 @@ exports[`simple website content: route config 1`] = `
"tag": { "tag": {
"allTagsPath": "/docs/tags", "allTagsPath": "/docs/tags",
"count": 2, "count": 2,
"description": undefined,
"items": [ "items": [
{ {
"description": "Images", "description": "Images",
@ -1572,6 +1647,7 @@ exports[`simple website content: route config 1`] = `
"tag": { "tag": {
"allTagsPath": "/docs/tags", "allTagsPath": "/docs/tags",
"count": 1, "count": 1,
"description": undefined,
"items": [ "items": [
{ {
"description": "Hi, Endilie here :)", "description": "Hi, Endilie here :)",
@ -1594,6 +1670,7 @@ exports[`simple website content: route config 1`] = `
"tag": { "tag": {
"allTagsPath": "/docs/tags", "allTagsPath": "/docs/tags",
"count": 1, "count": 1,
"description": undefined,
"items": [ "items": [
{ {
"description": "Images", "description": "Images",
@ -1929,6 +2006,8 @@ exports[`simple website getPathToWatch 1`] = `
"sidebars.json", "sidebars.json",
"i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}", "i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}",
"docs/**/*.{md,mdx}", "docs/**/*.{md,mdx}",
"i18n/en/docusaurus-plugin-content-docs/current/tags.yml",
"docs/tags.yml",
"docs/**/_category_.{json,yml,yaml}", "docs/**/_category_.{json,yml,yaml}",
] ]
`; `;
@ -3249,10 +3328,14 @@ exports[`versioned website (community) getPathToWatch 1`] = `
"community_sidebars.json", "community_sidebars.json",
"i18n/en/docusaurus-plugin-content-docs-community/current/**/*.{md,mdx}", "i18n/en/docusaurus-plugin-content-docs-community/current/**/*.{md,mdx}",
"community/**/*.{md,mdx}", "community/**/*.{md,mdx}",
"i18n/en/docusaurus-plugin-content-docs-community/current/tags.yml",
"community/tags.yml",
"community/**/_category_.{json,yml,yaml}", "community/**/_category_.{json,yml,yaml}",
"community_versioned_sidebars/version-1.0.0-sidebars.json", "community_versioned_sidebars/version-1.0.0-sidebars.json",
"i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0/**/*.{md,mdx}", "i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0/**/*.{md,mdx}",
"community_versioned_docs/version-1.0.0/**/*.{md,mdx}", "community_versioned_docs/version-1.0.0/**/*.{md,mdx}",
"i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0/tags.yml",
"community_versioned_docs/version-1.0.0/tags.yml",
"community_versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}", "community_versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}",
] ]
`; `;
@ -3289,14 +3372,20 @@ exports[`versioned website content 1`] = `
"sourceDirName": "foo", "sourceDirName": "foo",
"tags": [ "tags": [
{ {
"description": undefined,
"inline": true,
"label": "barTag 1", "label": "barTag 1",
"permalink": "/docs/next/tags/bar-tag-1", "permalink": "/docs/next/tags/bar-tag-1",
}, },
{ {
"description": undefined,
"inline": true,
"label": "barTag-2", "label": "barTag-2",
"permalink": "/docs/next/tags/bar-tag-2", "permalink": "/docs/next/tags/bar-tag-2",
}, },
{ {
"description": undefined,
"inline": true,
"label": "barTag 3", "label": "barTag 3",
"permalink": "/docs/next/tags/barTag-3-permalink", "permalink": "/docs/next/tags/barTag-3-permalink",
}, },
@ -3370,6 +3459,10 @@ exports[`versioned website content 4`] = `
"editUrl": undefined, "editUrl": undefined,
"frontMatter": { "frontMatter": {
"slug": "/", "slug": "/",
"tags": [
"inlineTag-v1.0.1",
"globalTag-v1.0.1",
],
}, },
"id": "hello", "id": "hello",
"lastUpdatedAt": undefined, "lastUpdatedAt": undefined,
@ -3385,7 +3478,20 @@ exports[`versioned website content 4`] = `
"slug": "/", "slug": "/",
"source": "@site/versioned_docs/version-1.0.1/hello.md", "source": "@site/versioned_docs/version-1.0.1/hello.md",
"sourceDirName": ".", "sourceDirName": ".",
"tags": [], "tags": [
{
"description": undefined,
"inline": true,
"label": "inlineTag-v1.0.1",
"permalink": "/docs/tags/inline-tag-v-1-0-1",
},
{
"description": "globalTag-v1.0.1 description",
"inline": false,
"label": "globalTag-v1.0.1 label",
"permalink": "/docs/tags/globalTag-v1.0.1 permalink",
},
],
"title": "hello", "title": "hello",
"unlisted": false, "unlisted": false,
"version": "1.0.1", "version": "1.0.1",
@ -3558,14 +3664,20 @@ exports[`versioned website content: data 1`] = `
"sourceDirName": "foo", "sourceDirName": "foo",
"tags": [ "tags": [
{ {
"description": undefined,
"inline": true,
"label": "barTag 1", "label": "barTag 1",
"permalink": "/docs/next/tags/bar-tag-1", "permalink": "/docs/next/tags/bar-tag-1",
}, },
{ {
"description": undefined,
"inline": true,
"label": "barTag-2", "label": "barTag-2",
"permalink": "/docs/next/tags/bar-tag-2", "permalink": "/docs/next/tags/bar-tag-2",
}, },
{ {
"description": undefined,
"inline": true,
"label": "barTag 3", "label": "barTag 3",
"permalink": "/docs/next/tags/barTag-3-permalink", "permalink": "/docs/next/tags/barTag-3-permalink",
}, },
@ -3698,6 +3810,10 @@ exports[`versioned website content: data 1`] = `
"editUrl": undefined, "editUrl": undefined,
"frontMatter": { "frontMatter": {
"slug": "/", "slug": "/",
"tags": [
"inlineTag-v1.0.0",
"globalTag-v1.0.0",
],
}, },
"id": "hello", "id": "hello",
"lastUpdatedAt": undefined, "lastUpdatedAt": undefined,
@ -3713,7 +3829,20 @@ exports[`versioned website content: data 1`] = `
"slug": "/", "slug": "/",
"source": "@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md", "source": "@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md",
"sourceDirName": ".", "sourceDirName": ".",
"tags": [], "tags": [
{
"description": undefined,
"inline": true,
"label": "inlineTag-v1.0.0",
"permalink": "/docs/1.0.0/tags/inline-tag-v-1-0-0",
},
{
"description": "globalTag-v1.0.0 description (en)",
"inline": false,
"label": "globalTag-v1.0.0 label (en)",
"permalink": "/docs/1.0.0/tags/globalTag-v1.0.0 permalink (en)",
},
],
"title": "hello", "title": "hello",
"unlisted": false, "unlisted": false,
"version": "1.0.0", "version": "1.0.0",
@ -3801,6 +3930,10 @@ exports[`versioned website content: data 1`] = `
"editUrl": undefined, "editUrl": undefined,
"frontMatter": { "frontMatter": {
"slug": "/", "slug": "/",
"tags": [
"inlineTag-v1.0.1",
"globalTag-v1.0.1",
],
}, },
"id": "hello", "id": "hello",
"lastUpdatedAt": undefined, "lastUpdatedAt": undefined,
@ -3816,7 +3949,20 @@ exports[`versioned website content: data 1`] = `
"slug": "/", "slug": "/",
"source": "@site/versioned_docs/version-1.0.1/hello.md", "source": "@site/versioned_docs/version-1.0.1/hello.md",
"sourceDirName": ".", "sourceDirName": ".",
"tags": [], "tags": [
{
"description": undefined,
"inline": true,
"label": "inlineTag-v1.0.1",
"permalink": "/docs/tags/inline-tag-v-1-0-1",
},
{
"description": "globalTag-v1.0.1 description",
"inline": false,
"label": "globalTag-v1.0.1 label",
"permalink": "/docs/tags/globalTag-v1.0.1 permalink",
},
],
"title": "hello", "title": "hello",
"unlisted": false, "unlisted": false,
"version": "1.0.1", "version": "1.0.1",
@ -4280,6 +4426,73 @@ exports[`versioned website content: route config 1`] = `
}, },
}, },
"routes": [ "routes": [
{
"component": "@theme/DocTagsListPage",
"exact": true,
"path": "/docs/1.0.0/tags",
"props": {
"tags": [
{
"count": 1,
"description": undefined,
"label": "inlineTag-v1.0.0",
"permalink": "/docs/1.0.0/tags/inline-tag-v-1-0-0",
},
{
"count": 1,
"description": "globalTag-v1.0.0 description (en)",
"label": "globalTag-v1.0.0 label (en)",
"permalink": "/docs/1.0.0/tags/globalTag-v1.0.0 permalink (en)",
},
],
},
},
{
"component": "@theme/DocTagDocListPage",
"exact": true,
"path": "/docs/1.0.0/tags/globalTag-v1.0.0 permalink (en)",
"props": {
"tag": {
"allTagsPath": "/docs/1.0.0/tags",
"count": 1,
"description": "globalTag-v1.0.0 description (en)",
"items": [
{
"description": "Hello 1.0.0 ! (translated en)",
"id": "hello",
"permalink": "/docs/1.0.0/",
"title": "hello",
},
],
"label": "globalTag-v1.0.0 label (en)",
"permalink": "/docs/1.0.0/tags/globalTag-v1.0.0 permalink (en)",
"unlisted": false,
},
},
},
{
"component": "@theme/DocTagDocListPage",
"exact": true,
"path": "/docs/1.0.0/tags/inline-tag-v-1-0-0",
"props": {
"tag": {
"allTagsPath": "/docs/1.0.0/tags",
"count": 1,
"description": undefined,
"items": [
{
"description": "Hello 1.0.0 ! (translated en)",
"id": "hello",
"permalink": "/docs/1.0.0/",
"title": "hello",
},
],
"label": "inlineTag-v1.0.0",
"permalink": "/docs/1.0.0/tags/inline-tag-v-1-0-0",
"unlisted": false,
},
},
},
{ {
"component": "@theme/DocRoot", "component": "@theme/DocRoot",
"exact": false, "exact": false,
@ -4430,16 +4643,19 @@ exports[`versioned website content: route config 1`] = `
"tags": [ "tags": [
{ {
"count": 1, "count": 1,
"description": undefined,
"label": "barTag 1", "label": "barTag 1",
"permalink": "/docs/next/tags/bar-tag-1", "permalink": "/docs/next/tags/bar-tag-1",
}, },
{ {
"count": 1, "count": 1,
"description": undefined,
"label": "barTag-2", "label": "barTag-2",
"permalink": "/docs/next/tags/bar-tag-2", "permalink": "/docs/next/tags/bar-tag-2",
}, },
{ {
"count": 1, "count": 1,
"description": undefined,
"label": "barTag 3", "label": "barTag 3",
"permalink": "/docs/next/tags/barTag-3-permalink", "permalink": "/docs/next/tags/barTag-3-permalink",
}, },
@ -4454,6 +4670,7 @@ exports[`versioned website content: route config 1`] = `
"tag": { "tag": {
"allTagsPath": "/docs/next/tags", "allTagsPath": "/docs/next/tags",
"count": 1, "count": 1,
"description": undefined,
"items": [ "items": [
{ {
"description": "This is next version of bar.", "description": "This is next version of bar.",
@ -4476,6 +4693,7 @@ exports[`versioned website content: route config 1`] = `
"tag": { "tag": {
"allTagsPath": "/docs/next/tags", "allTagsPath": "/docs/next/tags",
"count": 1, "count": 1,
"description": undefined,
"items": [ "items": [
{ {
"description": "This is next version of bar.", "description": "This is next version of bar.",
@ -4498,6 +4716,7 @@ exports[`versioned website content: route config 1`] = `
"tag": { "tag": {
"allTagsPath": "/docs/next/tags", "allTagsPath": "/docs/next/tags",
"count": 1, "count": 1,
"description": undefined,
"items": [ "items": [
{ {
"description": "This is next version of bar.", "description": "This is next version of bar.",
@ -4860,6 +5079,73 @@ exports[`versioned website content: route config 1`] = `
}, },
}, },
"routes": [ "routes": [
{
"component": "@theme/DocTagsListPage",
"exact": true,
"path": "/docs/tags",
"props": {
"tags": [
{
"count": 1,
"description": undefined,
"label": "inlineTag-v1.0.1",
"permalink": "/docs/tags/inline-tag-v-1-0-1",
},
{
"count": 1,
"description": "globalTag-v1.0.1 description",
"label": "globalTag-v1.0.1 label",
"permalink": "/docs/tags/globalTag-v1.0.1 permalink",
},
],
},
},
{
"component": "@theme/DocTagDocListPage",
"exact": true,
"path": "/docs/tags/globalTag-v1.0.1 permalink",
"props": {
"tag": {
"allTagsPath": "/docs/tags",
"count": 1,
"description": "globalTag-v1.0.1 description",
"items": [
{
"description": "Hello 1.0.1 !",
"id": "hello",
"permalink": "/docs/",
"title": "hello",
},
],
"label": "globalTag-v1.0.1 label",
"permalink": "/docs/tags/globalTag-v1.0.1 permalink",
"unlisted": false,
},
},
},
{
"component": "@theme/DocTagDocListPage",
"exact": true,
"path": "/docs/tags/inline-tag-v-1-0-1",
"props": {
"tag": {
"allTagsPath": "/docs/tags",
"count": 1,
"description": undefined,
"items": [
{
"description": "Hello 1.0.1 !",
"id": "hello",
"permalink": "/docs/",
"title": "hello",
},
],
"label": "inlineTag-v1.0.1",
"permalink": "/docs/tags/inline-tag-v-1-0-1",
"unlisted": false,
},
},
},
{ {
"component": "@theme/DocRoot", "component": "@theme/DocRoot",
"exact": false, "exact": false,
@ -4925,18 +5211,26 @@ exports[`versioned website getPathToWatch 1`] = `
"sidebars.json", "sidebars.json",
"i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}", "i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}",
"docs/**/*.{md,mdx}", "docs/**/*.{md,mdx}",
"i18n/en/docusaurus-plugin-content-docs/current/tags.yml",
"docs/tags.yml",
"docs/**/_category_.{json,yml,yaml}", "docs/**/_category_.{json,yml,yaml}",
"versioned_sidebars/version-1.0.1-sidebars.json", "versioned_sidebars/version-1.0.1-sidebars.json",
"i18n/en/docusaurus-plugin-content-docs/version-1.0.1/**/*.{md,mdx}", "i18n/en/docusaurus-plugin-content-docs/version-1.0.1/**/*.{md,mdx}",
"versioned_docs/version-1.0.1/**/*.{md,mdx}", "versioned_docs/version-1.0.1/**/*.{md,mdx}",
"i18n/en/docusaurus-plugin-content-docs/version-1.0.1/tags.yml",
"versioned_docs/version-1.0.1/tags.yml",
"versioned_docs/version-1.0.1/**/_category_.{json,yml,yaml}", "versioned_docs/version-1.0.1/**/_category_.{json,yml,yaml}",
"versioned_sidebars/version-1.0.0-sidebars.json", "versioned_sidebars/version-1.0.0-sidebars.json",
"i18n/en/docusaurus-plugin-content-docs/version-1.0.0/**/*.{md,mdx}", "i18n/en/docusaurus-plugin-content-docs/version-1.0.0/**/*.{md,mdx}",
"versioned_docs/version-1.0.0/**/*.{md,mdx}", "versioned_docs/version-1.0.0/**/*.{md,mdx}",
"i18n/en/docusaurus-plugin-content-docs/version-1.0.0/tags.yml",
"versioned_docs/version-1.0.0/tags.yml",
"versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}", "versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}",
"versioned_sidebars/version-withSlugs-sidebars.json", "versioned_sidebars/version-withSlugs-sidebars.json",
"i18n/en/docusaurus-plugin-content-docs/version-withSlugs/**/*.{md,mdx}", "i18n/en/docusaurus-plugin-content-docs/version-withSlugs/**/*.{md,mdx}",
"versioned_docs/version-withSlugs/**/*.{md,mdx}", "versioned_docs/version-withSlugs/**/*.{md,mdx}",
"i18n/en/docusaurus-plugin-content-docs/version-withSlugs/tags.yml",
"versioned_docs/version-withSlugs/tags.yml",
"versioned_docs/version-withSlugs/**/_category_.{json,yml,yaml}", "versioned_docs/version-withSlugs/**/_category_.{json,yml,yaml}",
] ]
`; `;

View file

@ -6,7 +6,7 @@
*/ */
import {jest} from '@jest/globals'; import {jest} from '@jest/globals';
import path from 'path'; import * as path from 'path';
import {loadContext} from '@docusaurus/core/src/server/site'; import {loadContext} from '@docusaurus/core/src/server/site';
import { import {
createSlugger, createSlugger,
@ -14,6 +14,7 @@ import {
DEFAULT_PLUGIN_ID, DEFAULT_PLUGIN_ID,
LAST_UPDATE_FALLBACK, LAST_UPDATE_FALLBACK,
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import {getTagsFile} from '@docusaurus/utils-validation';
import {createSidebarsUtils} from '../sidebars/utils'; import {createSidebarsUtils} from '../sidebars/utils';
import { import {
processDocMetadata, processDocMetadata,
@ -73,13 +74,18 @@ type TestUtilsArg = {
env?: DocEnv; env?: DocEnv;
}; };
function createTestUtils({ async function createTestUtils({
siteDir, siteDir,
context, context,
versionMetadata, versionMetadata,
options, options,
env = 'production', env = 'production',
}: TestUtilsArg) { }: TestUtilsArg) {
const tagsFile = await getTagsFile({
contentPaths: versionMetadata,
tags: options.tags,
});
async function readDoc(docFileSource: string) { async function readDoc(docFileSource: string) {
return readDocFile(versionMetadata, docFileSource); return readDocFile(versionMetadata, docFileSource);
} }
@ -93,6 +99,7 @@ function createTestUtils({
options, options,
context, context,
env, env,
tagsFile,
}); });
} }
@ -139,6 +146,7 @@ function createTestUtils({
context, context,
options, options,
env, env,
tagsFile: null,
}); });
expect(metadata.permalink).toEqual(expectedPermalink); expect(metadata.permalink).toEqual(expectedPermalink);
} }
@ -159,6 +167,7 @@ function createTestUtils({
context, context,
options, options,
env, env,
tagsFile: null,
}), }),
), ),
); );
@ -181,7 +190,6 @@ function createTestUtils({
pagination: addDocNavigation({ pagination: addDocNavigation({
docs: rawDocs, docs: rawDocs,
sidebarsUtils, sidebarsUtils,
sidebarFilePath: versionMetadata.sidebarFilePath as string,
}).map((doc) => ({prev: doc.previous, next: doc.next, id: doc.id})), }).map((doc) => ({prev: doc.previous, next: doc.next, id: doc.id})),
sidebars, sidebars,
}; };
@ -214,7 +222,7 @@ describe('simple site', () => {
expect(versionsMetadata).toHaveLength(1); expect(versionsMetadata).toHaveLength(1);
const currentVersion = versionsMetadata[0]!; const currentVersion = versionsMetadata[0]!;
function createTestUtilsPartial(args: Partial<TestUtilsArg>) { async function createTestUtilsPartial(args: Partial<TestUtilsArg>) {
return createTestUtils({ return createTestUtils({
siteDir, siteDir,
context, context,
@ -224,7 +232,7 @@ describe('simple site', () => {
}); });
} }
const defaultTestUtils = createTestUtilsPartial({}); const defaultTestUtils = await createTestUtilsPartial({});
return { return {
siteDir, siteDir,
@ -296,6 +304,7 @@ describe('simple site', () => {
slug: '/', slug: '/',
title: 'Hello, World !', title: 'Hello, World !',
description: `Hi, Endilie here :)`, description: `Hi, Endilie here :)`,
sidebarPosition: undefined,
frontMatter: { frontMatter: {
id: 'hello', id: 'hello',
title: 'Hello, World !', title: 'Hello, World !',
@ -306,11 +315,15 @@ describe('simple site', () => {
tags: [ tags: [
{ {
label: 'tag-1', label: 'tag-1',
inline: true,
permalink: '/docs/tags/tag-1', permalink: '/docs/tags/tag-1',
description: undefined,
}, },
{ {
label: 'tag 3', label: 'tag 3',
inline: true,
permalink: '/docs/tags/tag-3', permalink: '/docs/tags/tag-3',
description: undefined,
}, },
], ],
unlisted: false, unlisted: false,
@ -325,7 +338,7 @@ describe('simple site', () => {
}, },
}); });
const testUtilsLocal = createTestUtilsPartial({ const testUtilsLocal = await createTestUtilsPartial({
siteDir, siteDir,
context, context,
options, options,
@ -339,6 +352,7 @@ describe('simple site', () => {
permalink: '/docs/foo/bazSlug.html', permalink: '/docs/foo/bazSlug.html',
slug: '/foo/bazSlug.html', slug: '/foo/bazSlug.html',
title: 'baz', title: 'baz',
sidebarPosition: undefined,
editUrl: editUrl:
'https://github.com/facebook/docusaurus/edit/main/website/docs/foo/baz.md', 'https://github.com/facebook/docusaurus/edit/main/website/docs/foo/baz.md',
description: 'Images', description: 'Images',
@ -349,17 +363,34 @@ describe('simple site', () => {
pagination_label: 'baz pagination_label', pagination_label: 'baz pagination_label',
tags: [ tags: [
'tag 1', 'tag 1',
'globalTag1',
'tag-1', 'tag-1',
{label: 'tag 2', permalink: 'tag2-custom-permalink'}, {label: 'tag 2', permalink: 'tag2-custom-permalink'},
], ],
}, },
tags: [ tags: [
{ {
description: undefined,
inline: true,
label: 'tag 1', label: 'tag 1',
permalink: '/docs/tags/tag-1', permalink: '/docs/tags/tag-1',
}, },
{
description: 'Global Tag 1 description',
inline: false,
label: 'Global Tag 1 label',
permalink: '/docs/tags/global-tag-1-permalink',
},
{
description: undefined,
inline: true,
label: 'tag-1',
permalink: '/docs/tags/tag-1',
},
{ {
label: 'tag 2', label: 'tag 2',
description: undefined,
inline: true,
permalink: '/docs/tags/tag2-custom-permalink', permalink: '/docs/tags/tag2-custom-permalink',
}, },
], ],
@ -400,7 +431,7 @@ describe('simple site', () => {
}, },
}); });
const testUtilsLocal = createTestUtilsPartial({ const testUtilsLocal = await createTestUtilsPartial({
siteDir, siteDir,
context, context,
options, options,
@ -416,6 +447,7 @@ describe('simple site', () => {
title: 'baz', title: 'baz',
editUrl: hardcodedEditUrl, editUrl: hardcodedEditUrl,
description: 'Images', description: 'Images',
sidebarPosition: undefined,
frontMatter: { frontMatter: {
id: 'baz', id: 'baz',
slug: 'bazSlug.html', slug: 'bazSlug.html',
@ -423,16 +455,33 @@ describe('simple site', () => {
pagination_label: 'baz pagination_label', pagination_label: 'baz pagination_label',
tags: [ tags: [
'tag 1', 'tag 1',
'globalTag1',
'tag-1', 'tag-1',
{label: 'tag 2', permalink: 'tag2-custom-permalink'}, {label: 'tag 2', permalink: 'tag2-custom-permalink'},
], ],
}, },
tags: [ tags: [
{ {
description: undefined,
inline: true,
label: 'tag 1', label: 'tag 1',
permalink: '/docs/tags/tag-1', permalink: '/docs/tags/tag-1',
}, },
{ {
description: 'Global Tag 1 description',
inline: false,
label: 'Global Tag 1 label',
permalink: '/docs/tags/global-tag-1-permalink',
},
{
description: undefined,
inline: true,
label: 'tag-1',
permalink: '/docs/tags/tag-1',
},
{
description: undefined,
inline: true,
label: 'tag 2', label: 'tag 2',
permalink: '/docs/tags/tag2-custom-permalink', permalink: '/docs/tags/tag2-custom-permalink',
}, },
@ -459,7 +508,7 @@ describe('simple site', () => {
}, },
}); });
const testUtilsLocal = createTestUtilsPartial({ const testUtilsLocal = await createTestUtilsPartial({
siteDir, siteDir,
context, context,
options, options,
@ -489,7 +538,7 @@ describe('simple site', () => {
it('docs with draft frontmatter', async () => { it('docs with draft frontmatter', async () => {
const {createTestUtilsPartial} = await loadSite(); const {createTestUtilsPartial} = await loadSite();
const testUtilsProd = createTestUtilsPartial({ const testUtilsProd = await createTestUtilsPartial({
env: 'production', env: 'production',
}); });
await expect( await expect(
@ -498,7 +547,7 @@ describe('simple site', () => {
draft: true, draft: true,
}); });
const testUtilsDev = createTestUtilsPartial({ const testUtilsDev = await createTestUtilsPartial({
env: 'development', env: 'development',
}); });
await expect( await expect(
@ -526,7 +575,7 @@ describe('simple site', () => {
tags: [], tags: [],
}; };
const testUtilsProd = createTestUtilsPartial({ const testUtilsProd = await createTestUtilsPartial({
env: 'production', env: 'production',
}); });
@ -535,7 +584,7 @@ describe('simple site', () => {
unlisted: true, unlisted: true,
}); });
const testUtilsDev = createTestUtilsPartial({ const testUtilsDev = await createTestUtilsPartial({
env: 'development', env: 'development',
}); });
@ -554,7 +603,7 @@ describe('simple site', () => {
}, },
}); });
const testUtilsLocal = createTestUtilsPartial({ const testUtilsLocal = await createTestUtilsPartial({
siteDir, siteDir,
context, context,
options, options,
@ -593,7 +642,7 @@ describe('simple site', () => {
}, },
}); });
const testUtilsLocal = createTestUtilsPartial({ const testUtilsLocal = await createTestUtilsPartial({
siteDir, siteDir,
context, context,
options, options,
@ -631,7 +680,7 @@ describe('simple site', () => {
}, },
}); });
const testUtilsLocal = createTestUtilsPartial({ const testUtilsLocal = await createTestUtilsPartial({
siteDir, siteDir,
context, context,
options, options,
@ -669,7 +718,7 @@ describe('simple site', () => {
}, },
}); });
const testUtilsLocal = createTestUtilsPartial({ const testUtilsLocal = await createTestUtilsPartial({
siteDir, siteDir,
context, context,
options, options,
@ -761,7 +810,7 @@ describe('simple site', () => {
it('custom pagination - production', async () => { it('custom pagination - production', async () => {
const {createTestUtilsPartial, options, versionsMetadata} = const {createTestUtilsPartial, options, versionsMetadata} =
await loadSite(); await loadSite();
const testUtils = createTestUtilsPartial({env: 'production'}); const testUtils = await createTestUtilsPartial({env: 'production'});
const docs = await readVersionDocs(versionsMetadata[0]!, options); const docs = await readVersionDocs(versionsMetadata[0]!, options);
await expect(testUtils.generateNavigation(docs)).resolves.toMatchSnapshot(); await expect(testUtils.generateNavigation(docs)).resolves.toMatchSnapshot();
}); });
@ -769,7 +818,7 @@ describe('simple site', () => {
it('custom pagination - development', async () => { it('custom pagination - development', async () => {
const {createTestUtilsPartial, options, versionsMetadata} = const {createTestUtilsPartial, options, versionsMetadata} =
await loadSite(); await loadSite();
const testUtils = createTestUtilsPartial({env: 'development'}); const testUtils = await createTestUtilsPartial({env: 'development'});
const docs = await readVersionDocs(versionsMetadata[0]!, options); const docs = await readVersionDocs(versionsMetadata[0]!, options);
await expect(testUtils.generateNavigation(docs)).resolves.toMatchSnapshot(); await expect(testUtils.generateNavigation(docs)).resolves.toMatchSnapshot();
}); });
@ -818,27 +867,27 @@ describe('versioned site', () => {
const version100 = versionsMetadata[2]!; const version100 = versionsMetadata[2]!;
const versionWithSlugs = versionsMetadata[3]!; const versionWithSlugs = versionsMetadata[3]!;
const currentVersionTestUtils = createTestUtils({ const currentVersionTestUtils = await createTestUtils({
siteDir, siteDir,
context, context,
options, options,
versionMetadata: currentVersion, versionMetadata: currentVersion,
}); });
const version101TestUtils = createTestUtils({ const version101TestUtils = await createTestUtils({
siteDir, siteDir,
context, context,
options, options,
versionMetadata: version101, versionMetadata: version101,
}); });
const version100TestUtils = createTestUtils({ const version100TestUtils = await createTestUtils({
siteDir, siteDir,
context, context,
options, options,
versionMetadata: version100, versionMetadata: version100,
}); });
const versionWithSlugsTestUtils = createTestUtils({ const versionWithSlugsTestUtils = await createTestUtils({
siteDir, siteDir,
context, context,
options, options,
@ -869,6 +918,7 @@ describe('versioned site', () => {
slug: '/foo/barSlug', slug: '/foo/barSlug',
title: 'bar', title: 'bar',
description: 'This is next version of bar.', description: 'This is next version of bar.',
sidebarPosition: undefined,
frontMatter: { frontMatter: {
slug: 'barSlug', slug: 'barSlug',
tags: [ tags: [
@ -883,15 +933,21 @@ describe('versioned site', () => {
tags: [ tags: [
{ {
label: 'barTag 1', label: 'barTag 1',
inline: true,
permalink: '/docs/next/tags/bar-tag-1', permalink: '/docs/next/tags/bar-tag-1',
description: undefined,
}, },
{ {
label: 'barTag-2', label: 'barTag-2',
inline: true,
permalink: '/docs/next/tags/bar-tag-2', permalink: '/docs/next/tags/bar-tag-2',
description: undefined,
}, },
{ {
label: 'barTag 3', label: 'barTag 3',
inline: true,
permalink: '/docs/next/tags/barTag-3-permalink', permalink: '/docs/next/tags/barTag-3-permalink',
description: undefined,
}, },
], ],
unlisted: false, unlisted: false,
@ -936,11 +992,25 @@ describe('versioned site', () => {
description: 'Hello 1.0.0 ! (translated en)', description: 'Hello 1.0.0 ! (translated en)',
frontMatter: { frontMatter: {
slug: '/', slug: '/',
tags: ['inlineTag-v1.0.0', 'globalTag-v1.0.0'],
}, },
version: '1.0.0', version: '1.0.0',
source: source:
'@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md', '@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
tags: [], tags: [
{
description: undefined,
inline: true,
label: 'inlineTag-v1.0.0',
permalink: '/docs/1.0.0/tags/inline-tag-v-1-0-0',
},
{
description: 'globalTag-v1.0.0 description (en)',
inline: false,
label: 'globalTag-v1.0.0 label (en)',
permalink: '/docs/1.0.0/tags/globalTag-v1.0.0 permalink (en)',
},
],
unlisted: false, unlisted: false,
}); });
await version101TestUtils.testMeta(path.join('foo', 'bar.md'), { await version101TestUtils.testMeta(path.join('foo', 'bar.md'), {
@ -965,8 +1035,22 @@ describe('versioned site', () => {
version: '1.0.1', version: '1.0.1',
frontMatter: { frontMatter: {
slug: '/', slug: '/',
tags: ['inlineTag-v1.0.1', 'globalTag-v1.0.1'],
}, },
tags: [], tags: [
{
description: undefined,
inline: true,
label: 'inlineTag-v1.0.1',
permalink: '/docs/tags/inline-tag-v-1-0-1',
},
{
description: 'globalTag-v1.0.1 description',
inline: false,
label: 'globalTag-v1.0.1 label',
permalink: '/docs/tags/globalTag-v1.0.1 permalink',
},
],
unlisted: false, unlisted: false,
}); });
}); });
@ -1041,7 +1125,7 @@ describe('versioned site', () => {
}, },
}); });
const testUtilsLocal = createTestUtils({ const testUtilsLocal = await createTestUtils({
siteDir, siteDir,
context, context,
options, options,
@ -1057,12 +1141,26 @@ describe('versioned site', () => {
description: 'Hello 1.0.0 ! (translated en)', description: 'Hello 1.0.0 ! (translated en)',
frontMatter: { frontMatter: {
slug: '/', slug: '/',
tags: ['inlineTag-v1.0.0', 'globalTag-v1.0.0'],
}, },
version: '1.0.0', version: '1.0.0',
source: source:
'@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md', '@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
editUrl: hardcodedEditUrl, editUrl: hardcodedEditUrl,
tags: [], tags: [
{
description: undefined,
inline: true,
label: 'inlineTag-v1.0.0',
permalink: '/docs/1.0.0/tags/inline-tag-v-1-0-0',
},
{
description: 'globalTag-v1.0.0 description (en)',
inline: false,
label: 'globalTag-v1.0.0 label (en)',
permalink: '/docs/1.0.0/tags/globalTag-v1.0.0 permalink (en)',
},
],
unlisted: false, unlisted: false,
}); });
@ -1083,7 +1181,7 @@ describe('versioned site', () => {
}, },
}); });
const testUtilsLocal = createTestUtils({ const testUtilsLocal = await createTestUtils({
siteDir, siteDir,
context, context,
options, options,
@ -1099,13 +1197,27 @@ describe('versioned site', () => {
description: 'Hello 1.0.0 ! (translated en)', description: 'Hello 1.0.0 ! (translated en)',
frontMatter: { frontMatter: {
slug: '/', slug: '/',
tags: ['inlineTag-v1.0.0', 'globalTag-v1.0.0'],
}, },
version: '1.0.0', version: '1.0.0',
source: source:
'@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md', '@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
editUrl: editUrl:
'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-1.0.0/hello.md', 'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-1.0.0/hello.md',
tags: [], tags: [
{
description: undefined,
inline: true,
label: 'inlineTag-v1.0.0',
permalink: '/docs/1.0.0/tags/inline-tag-v-1-0-0',
},
{
description: 'globalTag-v1.0.0 description (en)',
inline: false,
label: 'globalTag-v1.0.0 label (en)',
permalink: '/docs/1.0.0/tags/globalTag-v1.0.0 permalink (en)',
},
],
unlisted: false, unlisted: false,
}); });
}); });
@ -1118,7 +1230,7 @@ describe('versioned site', () => {
}, },
}); });
const testUtilsLocal = createTestUtils({ const testUtilsLocal = await createTestUtils({
siteDir, siteDir,
context, context,
options, options,
@ -1134,13 +1246,27 @@ describe('versioned site', () => {
description: 'Hello 1.0.0 ! (translated en)', description: 'Hello 1.0.0 ! (translated en)',
frontMatter: { frontMatter: {
slug: '/', slug: '/',
tags: ['inlineTag-v1.0.0', 'globalTag-v1.0.0'],
}, },
version: '1.0.0', version: '1.0.0',
source: source:
'@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md', '@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
editUrl: editUrl:
'https://github.com/facebook/docusaurus/edit/main/website/docs/hello.md', 'https://github.com/facebook/docusaurus/edit/main/website/docs/hello.md',
tags: [], tags: [
{
description: undefined,
inline: true,
label: 'inlineTag-v1.0.0',
permalink: '/docs/1.0.0/tags/inline-tag-v-1-0-0',
},
{
description: 'globalTag-v1.0.0 description (en)',
inline: false,
label: 'globalTag-v1.0.0 label (en)',
permalink: '/docs/1.0.0/tags/globalTag-v1.0.0 permalink (en)',
},
],
unlisted: false, unlisted: false,
}); });
}); });
@ -1154,7 +1280,7 @@ describe('versioned site', () => {
locale: 'fr', locale: 'fr',
}); });
const testUtilsLocal = createTestUtils({ const testUtilsLocal = await createTestUtils({
siteDir, siteDir,
context, context,
options, options,
@ -1170,13 +1296,27 @@ describe('versioned site', () => {
description: 'Hello 1.0.0 ! (translated fr)', description: 'Hello 1.0.0 ! (translated fr)',
frontMatter: { frontMatter: {
slug: '/', slug: '/',
tags: ['inlineTag-v1.0.0', 'globalTag-v1.0.0'],
}, },
version: '1.0.0', version: '1.0.0',
source: source:
'@site/i18n/fr/docusaurus-plugin-content-docs/version-1.0.0/hello.md', '@site/i18n/fr/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
editUrl: editUrl:
'https://github.com/facebook/docusaurus/edit/main/website/i18n/fr/docusaurus-plugin-content-docs/version-1.0.0/hello.md', 'https://github.com/facebook/docusaurus/edit/main/website/i18n/fr/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
tags: [], tags: [
{
description: undefined,
inline: true,
label: 'inlineTag-v1.0.0',
permalink: '/fr/docs/1.0.0/tags/inline-tag-v-1-0-0',
},
{
description: 'globalTag-v1.0.0 description (fr)',
inline: false,
label: 'globalTag-v1.0.0 label (fr)',
permalink: '/fr/docs/1.0.0/tags/globalTag-v1.0.0 permalink (fr)',
},
],
unlisted: false, unlisted: false,
}); });
}); });
@ -1191,7 +1331,7 @@ describe('versioned site', () => {
locale: 'fr', locale: 'fr',
}); });
const testUtilsLocal = createTestUtils({ const testUtilsLocal = await createTestUtils({
siteDir, siteDir,
context, context,
options, options,
@ -1207,13 +1347,27 @@ describe('versioned site', () => {
description: 'Hello 1.0.0 ! (translated fr)', description: 'Hello 1.0.0 ! (translated fr)',
frontMatter: { frontMatter: {
slug: '/', slug: '/',
tags: ['inlineTag-v1.0.0', 'globalTag-v1.0.0'],
}, },
version: '1.0.0', version: '1.0.0',
source: source:
'@site/i18n/fr/docusaurus-plugin-content-docs/version-1.0.0/hello.md', '@site/i18n/fr/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
editUrl: editUrl:
'https://github.com/facebook/docusaurus/edit/main/website/i18n/fr/docusaurus-plugin-content-docs/current/hello.md', 'https://github.com/facebook/docusaurus/edit/main/website/i18n/fr/docusaurus-plugin-content-docs/current/hello.md',
tags: [], tags: [
{
description: undefined,
inline: true,
label: 'inlineTag-v1.0.0',
permalink: '/fr/docs/1.0.0/tags/inline-tag-v-1-0-0',
},
{
description: 'globalTag-v1.0.0 description (fr)',
inline: false,
label: 'globalTag-v1.0.0 label (fr)',
permalink: '/fr/docs/1.0.0/tags/globalTag-v1.0.0 permalink (fr)',
},
],
unlisted: false, unlisted: false,
}); });
}); });

View file

@ -70,6 +70,8 @@ describe('normalizeDocsPluginOptions', () => {
disableVersioning: true, disableVersioning: true,
editCurrentVersion: true, editCurrentVersion: true,
editLocalizedFiles: true, editLocalizedFiles: true,
tags: 'docsTags.yml',
onInlineTags: 'throw',
versions: { versions: {
current: { current: {
path: 'next', path: 'next',
@ -266,4 +268,68 @@ describe('normalizeDocsPluginOptions', () => {
}).sidebarCollapsed, }).sidebarCollapsed,
).toBe(false); ).toBe(false);
}); });
describe('tags', () => {
it('accepts tags - undefined', () => {
expect(testValidate({tags: undefined}).tags).toBeUndefined();
});
it('accepts tags - null', () => {
expect(testValidate({tags: null}).tags).toBeNull();
});
it('accepts tags - false', () => {
expect(testValidate({tags: false}).tags).toBeFalsy();
});
it('accepts tags - customTags.yml', () => {
expect(testValidate({tags: 'customTags.yml'}).tags).toBe(
'customTags.yml',
);
});
it('rejects tags - 42', () => {
// @ts-expect-error: test
expect(() => testValidate({tags: 42})).toThrowErrorMatchingInlineSnapshot(
`""tags" must be a string"`,
);
});
});
describe('onInlineTags', () => {
it('accepts onInlineTags - undefined', () => {
expect(testValidate({onInlineTags: undefined}).onInlineTags).toBe('warn');
});
it('accepts onInlineTags - "throw"', () => {
expect(testValidate({onInlineTags: 'throw'}).onInlineTags).toBe('throw');
});
it('rejects onInlineTags - "trace"', () => {
expect(() =>
// @ts-expect-error: test
testValidate({onInlineTags: 'trace'}),
).toThrowErrorMatchingInlineSnapshot(
`""onInlineTags" must be one of [ignore, log, warn, throw]"`,
);
});
it('rejects onInlineTags - null', () => {
expect(() =>
// @ts-expect-error: test
testValidate({onInlineTags: 42}),
).toThrowErrorMatchingInlineSnapshot(
`""onInlineTags" must be one of [ignore, log, warn, throw]"`,
);
});
it('rejects onInlineTags - 42', () => {
expect(() =>
// @ts-expect-error: test
testValidate({onInlineTags: 42}),
).toThrowErrorMatchingInlineSnapshot(
`""onInlineTags" must be one of [ignore, log, warn, throw]"`,
);
});
});
}); });

View file

@ -17,15 +17,16 @@ import {
parseMarkdownFile, parseMarkdownFile,
posixPath, posixPath,
Globby, Globby,
normalizeFrontMatterTags,
isUnlisted, isUnlisted,
isDraft, isDraft,
readLastUpdateData, readLastUpdateData,
normalizeTags,
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import {validateDocFrontMatter} from './frontMatter'; import {validateDocFrontMatter} from './frontMatter';
import getSlug from './slug'; import getSlug from './slug';
import {stripPathNumberPrefixes} from './numberPrefix'; import {stripPathNumberPrefixes} from './numberPrefix';
import {toDocNavigationLink, toNavigationLink} from './sidebars/utils'; import {toDocNavigationLink, toNavigationLink} from './sidebars/utils';
import type {TagsFile} from '@docusaurus/utils';
import type { import type {
MetadataOptions, MetadataOptions,
PluginOptions, PluginOptions,
@ -82,12 +83,14 @@ async function doProcessDocMetadata({
context, context,
options, options,
env, env,
tagsFile,
}: { }: {
docFile: DocFile; docFile: DocFile;
versionMetadata: VersionMetadata; versionMetadata: VersionMetadata;
context: LoadContext; context: LoadContext;
options: MetadataOptions; options: MetadataOptions;
env: DocEnv; env: DocEnv;
tagsFile: TagsFile | null;
}): Promise<DocMetadataBase> { }): Promise<DocMetadataBase> {
const {source, content, contentPath, filePath} = docFile; const {source, content, contentPath, filePath} = docFile;
const { const {
@ -206,6 +209,14 @@ async function doProcessDocMetadata({
const draft = isDraft({env, frontMatter}); const draft = isDraft({env, frontMatter});
const unlisted = isUnlisted({env, frontMatter}); const unlisted = isUnlisted({env, frontMatter});
const tags = normalizeTags({
options,
source,
frontMatterTags: frontMatter.tags,
tagsBaseRoutePath: versionMetadata.tagsPath,
tagsFile,
});
// Assign all of object properties during instantiation (if possible) for // Assign all of object properties during instantiation (if possible) for
// NodeJS optimization. // NodeJS optimization.
// Adding properties to object after instantiation will cause hidden // Adding properties to object after instantiation will cause hidden
@ -221,7 +232,7 @@ async function doProcessDocMetadata({
draft, draft,
unlisted, unlisted,
editUrl: customEditURL !== undefined ? customEditURL : getDocEditUrl(), editUrl: customEditURL !== undefined ? customEditURL : getDocEditUrl(),
tags: normalizeFrontMatterTags(versionMetadata.tagsPath, frontMatter.tags), tags,
version: versionMetadata.versionName, version: versionMetadata.versionName,
lastUpdatedBy: lastUpdate.lastUpdatedBy, lastUpdatedBy: lastUpdate.lastUpdatedBy,
lastUpdatedAt: lastUpdate.lastUpdatedAt, lastUpdatedAt: lastUpdate.lastUpdatedAt,
@ -236,6 +247,7 @@ export async function processDocMetadata(args: {
context: LoadContext; context: LoadContext;
options: MetadataOptions; options: MetadataOptions;
env: DocEnv; env: DocEnv;
tagsFile: TagsFile | null;
}): Promise<DocMetadataBase> { }): Promise<DocMetadataBase> {
try { try {
return await doProcessDocMetadata(args); return await doProcessDocMetadata(args);

View file

@ -20,6 +20,10 @@ import {
resolveMarkdownLinkPathname, resolveMarkdownLinkPathname,
DEFAULT_PLUGIN_ID, DEFAULT_PLUGIN_ID,
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import {
getTagsFile,
getTagsFilePathsToWatch,
} from '@docusaurus/utils-validation';
import {loadSidebars, resolveSidebarPathOption} from './sidebars'; import {loadSidebars, resolveSidebarPathOption} from './sidebars';
import {CategoryMetadataFilenamePattern} from './sidebars/generator'; import {CategoryMetadataFilenamePattern} from './sidebars/generator';
import { import {
@ -43,6 +47,7 @@ import {
} from './translations'; } from './translations';
import {createAllRoutes} from './routes'; import {createAllRoutes} from './routes';
import {createSidebarsUtils} from './sidebars/utils'; import {createSidebarsUtils} from './sidebars/utils';
import type {TagsFile} from '@docusaurus/utils';
import type {Options as MDXLoaderOptions} from '@docusaurus/mdx-loader'; import type {Options as MDXLoaderOptions} from '@docusaurus/mdx-loader';
import type { import type {
@ -119,6 +124,10 @@ export default async function pluginContentDocs(
(docsDirPath) => `${docsDirPath}/${pattern}`, (docsDirPath) => `${docsDirPath}/${pattern}`,
), ),
), ),
...getTagsFilePathsToWatch({
contentPaths: version,
tags: options.tags,
}),
`${version.contentPath}/**/${CategoryMetadataFilenamePattern}`, `${version.contentPath}/**/${CategoryMetadataFilenamePattern}`,
]; ];
if (typeof version.sidebarFilePath === 'string') { if (typeof version.sidebarFilePath === 'string') {
@ -133,6 +142,7 @@ export default async function pluginContentDocs(
async loadContent() { async loadContent() {
async function loadVersionDocsBase( async function loadVersionDocsBase(
versionMetadata: VersionMetadata, versionMetadata: VersionMetadata,
tagsFile: TagsFile | null,
): Promise<DocMetadataBase[]> { ): Promise<DocMetadataBase[]> {
const docFiles = await readVersionDocs(versionMetadata, options); const docFiles = await readVersionDocs(versionMetadata, options);
if (docFiles.length === 0) { if (docFiles.length === 0) {
@ -152,6 +162,7 @@ export default async function pluginContentDocs(
context, context,
options, options,
env, env,
tagsFile,
}); });
} }
return Promise.all(docFiles.map(processVersionDoc)); return Promise.all(docFiles.map(processVersionDoc));
@ -160,8 +171,14 @@ export default async function pluginContentDocs(
async function doLoadVersion( async function doLoadVersion(
versionMetadata: VersionMetadata, versionMetadata: VersionMetadata,
): Promise<LoadedVersion> { ): Promise<LoadedVersion> {
const tagsFile = await getTagsFile({
contentPaths: versionMetadata,
tags: options.tags,
});
const docsBase: DocMetadataBase[] = await loadVersionDocsBase( const docsBase: DocMetadataBase[] = await loadVersionDocsBase(
versionMetadata, versionMetadata,
tagsFile,
); );
// TODO we only ever need draftIds in further code, not full draft items // TODO we only ever need draftIds in further code, not full draft items

View file

@ -54,6 +54,8 @@ export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id' | 'sidebarPath'> = {
sidebarCollapsible: true, sidebarCollapsible: true,
sidebarCollapsed: true, sidebarCollapsed: true,
breadcrumbs: true, breadcrumbs: true,
onInlineTags: 'warn',
tags: undefined,
}; };
const VersionOptionsSchema = Joi.object({ const VersionOptionsSchema = Joi.object({
@ -140,6 +142,13 @@ const OptionsSchema = Joi.object<PluginOptions>({
lastVersion: Joi.string().optional(), lastVersion: Joi.string().optional(),
versions: VersionsOptionsSchema, versions: VersionsOptionsSchema,
breadcrumbs: Joi.bool().default(DEFAULT_OPTIONS.breadcrumbs), breadcrumbs: Joi.bool().default(DEFAULT_OPTIONS.breadcrumbs),
onInlineTags: Joi.string()
.equal('ignore', 'log', 'warn', 'throw')
.default(DEFAULT_OPTIONS.onInlineTags),
tags: Joi.string()
.disallow('')
.allow(null, false)
.default(() => DEFAULT_OPTIONS.tags),
}); });
export function validateOptions({ export function validateOptions({

View file

@ -15,9 +15,10 @@ declare module '@docusaurus/plugin-content-docs' {
FrontMatterTag, FrontMatterTag,
TagsListItem, TagsListItem,
TagModule, TagModule,
Tag,
FrontMatterLastUpdate, FrontMatterLastUpdate,
LastUpdateData, LastUpdateData,
TagMetadata,
TagsPluginOptions,
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import type {Plugin, LoadContext} from '@docusaurus/types'; import type {Plugin, LoadContext} from '@docusaurus/types';
import type {Overwrite, Required} from 'utility-types'; import type {Overwrite, Required} from 'utility-types';
@ -64,7 +65,7 @@ declare module '@docusaurus/plugin-content-docs' {
locale: string; locale: string;
}) => string | undefined; }) => string | undefined;
export type MetadataOptions = { export type MetadataOptions = TagsPluginOptions & {
/** /**
* URL route for the docs section of your site. **DO NOT** include a * URL route for the docs section of your site. **DO NOT** include a
* trailing slash. Use `/` for shipping docs without base path. * trailing slash. Use `/` for shipping docs without base path.
@ -446,7 +447,7 @@ declare module '@docusaurus/plugin-content-docs' {
*/ */
editUrl?: string | null; editUrl?: string | null;
/** Tags, normalized. */ /** Tags, normalized. */
tags: Tag[]; tags: TagMetadata[];
/** Front matter, as-is. */ /** Front matter, as-is. */
frontMatter: DocFrontMatter & {[key: string]: unknown}; frontMatter: DocFrontMatter & {[key: string]: unknown};
}; };

View file

@ -206,6 +206,7 @@ export function toTagDocListProp({
return { return {
label: tag.label, label: tag.label,
permalink: tag.permalink, permalink: tag.permalink,
description: tag.description,
allTagsPath, allTagsPath,
count: tag.docIds.length, count: tag.docIds.length,
items: toDocListProp(), items: toDocListProp(),
@ -221,6 +222,7 @@ export function toTagsListTagsProp(
.map((tagValue) => ({ .map((tagValue) => ({
label: tagValue.label, label: tagValue.label,
permalink: tagValue.permalink, permalink: tagValue.permalink,
description: tagValue.description,
count: tagValue.docIds.length, count: tagValue.docIds.length,
})); }));
} }

View file

@ -11,19 +11,20 @@ import {
docuHash, docuHash,
normalizeUrl, normalizeUrl,
aliasedSitePathToRelativePath, aliasedSitePathToRelativePath,
groupTaggedItems,
getTagVisibility,
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import { import {
toTagDocListProp, toTagDocListProp,
toTagsListTagsProp, toTagsListTagsProp,
toVersionMetadataProp, toVersionMetadataProp,
} from './props'; } from './props';
import {getVersionTags} from './tags';
import type { import type {
PluginContentLoadedActions, PluginContentLoadedActions,
RouteConfig, RouteConfig,
RouteMetadata, RouteMetadata,
} from '@docusaurus/types'; } from '@docusaurus/types';
import type {FullVersion, VersionTag} from './types'; import type {FullVersion, VersionTag, VersionTags} from './types';
import type { import type {
CategoryGeneratedIndexMetadata, CategoryGeneratedIndexMetadata,
DocMetadata, DocMetadata,
@ -112,6 +113,23 @@ async function buildVersionSidebarRoute(param: BuildVersionRoutesParam) {
routes: subRoutes, routes: subRoutes,
}; };
} }
function getVersionTags(docs: DocMetadata[]): VersionTags {
const groups = groupTaggedItems(docs, (doc) => doc.tags);
return _.mapValues(groups, ({tag, items: tagDocs}) => {
const tagVisibility = getTagVisibility({
items: tagDocs,
isUnlisted: (item) => item.unlisted,
});
return {
inline: tag.inline,
label: tag.label,
permalink: tag.permalink,
description: tag.description,
docIds: tagVisibility.listedItems.map((item) => item.id),
unlisted: tagVisibility.unlisted,
};
});
}
async function buildVersionTagsRoutes( async function buildVersionTagsRoutes(
param: BuildVersionRoutesParam, param: BuildVersionRoutesParam,
@ -120,8 +138,9 @@ async function buildVersionTagsRoutes(
const versionTags = getVersionTags(version.docs); const versionTags = getVersionTags(version.docs);
async function buildTagsListRoute(): Promise<RouteConfig | null> { async function buildTagsListRoute(): Promise<RouteConfig | null> {
const tags = toTagsListTagsProp(versionTags);
// Don't create a tags list page if there's no tag // Don't create a tags list page if there's no tag
if (Object.keys(versionTags).length === 0) { if (tags.length === 0) {
return null; return null;
} }
return { return {
@ -129,7 +148,7 @@ async function buildVersionTagsRoutes(
exact: true, exact: true,
component: options.docTagsListComponent, component: options.docTagsListComponent,
props: { props: {
tags: toTagsListTagsProp(versionTags), tags,
}, },
}; };
} }

View file

@ -1,27 +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 _ from 'lodash';
import {getTagVisibility, groupTaggedItems} from '@docusaurus/utils';
import type {VersionTags} from './types';
import type {DocMetadata} from '@docusaurus/plugin-content-docs';
export function getVersionTags(docs: DocMetadata[]): VersionTags {
const groups = groupTaggedItems(docs, (doc) => doc.tags);
return _.mapValues(groups, ({tag, items: tagDocs}) => {
const tagVisibility = getTagVisibility({
items: tagDocs,
isUnlisted: (item) => item.unlisted,
});
return {
label: tag.label,
docIds: tagVisibility.listedItems.map((item) => item.id),
permalink: tag.permalink,
unlisted: tagVisibility.unlisted,
};
});
}

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import type {Tag} from '@docusaurus/utils'; import type {TagMetadata} from '@docusaurus/utils';
import type { import type {
LoadedVersion, LoadedVersion,
CategoryGeneratedIndexMetadata, CategoryGeneratedIndexMetadata,
@ -23,7 +23,7 @@ export type SourceToPermalink = {
[source: string]: string; [source: string]: string;
}; };
export type VersionTag = Tag & { export type VersionTag = TagMetadata & {
/** All doc ids having this tag. */ /** All doc ids having this tag. */
docIds: string[]; docIds: string[];
unlisted: boolean; unlisted: boolean;

View file

@ -57,7 +57,7 @@ function BlogTagsPostsPageMetadata({tag}: Props): JSX.Element {
const title = useBlogTagsPostsPageTitle(tag); const title = useBlogTagsPostsPageTitle(tag);
return ( return (
<> <>
<PageMetadata title={title} /> <PageMetadata title={title} description={tag.description} />
<SearchMetadata tag="blog_tags_posts" /> <SearchMetadata tag="blog_tags_posts" />
</> </>
); );
@ -75,6 +75,7 @@ function BlogTagsPostsPageContent({
{tag.unlisted && <Unlisted />} {tag.unlisted && <Unlisted />}
<header className="margin-bottom--xl"> <header className="margin-bottom--xl">
<Heading as="h1">{title}</Heading> <Heading as="h1">{title}</Heading>
{tag.description && <p>{tag.description}</p>}
<Link href={tag.allTagsPath}> <Link href={tag.allTagsPath}>
<Translate <Translate
id="theme.tags.tagsPageLink" id="theme.tags.tagsPageLink"

View file

@ -63,10 +63,11 @@ function DocItem({doc}: {doc: Props['tag']['items'][number]}): JSX.Element {
function DocTagDocListPageMetadata({ function DocTagDocListPageMetadata({
title, title,
tag,
}: Props & {title: string}): JSX.Element { }: Props & {title: string}): JSX.Element {
return ( return (
<> <>
<PageMetadata title={title} /> <PageMetadata title={title} description={tag.description} />
<SearchMetadata tag="doc_tag_doc_list" /> <SearchMetadata tag="doc_tag_doc_list" />
</> </>
); );
@ -85,6 +86,7 @@ function DocTagDocListPageContent({
{tag.unlisted && <Unlisted />} {tag.unlisted && <Unlisted />}
<header className="margin-bottom--xl"> <header className="margin-bottom--xl">
<Heading as="h1">{title}</Heading> <Heading as="h1">{title}</Heading>
{tag.description && <p>{tag.description}</p>}
<Link href={tag.allTagsPath}> <Link href={tag.allTagsPath}>
<Translate <Translate
id="theme.tags.tagsPageLink" id="theme.tags.tagsPageLink"

View file

@ -12,10 +12,16 @@ import type {Props} from '@theme/Tag';
import styles from './styles.module.css'; import styles from './styles.module.css';
export default function Tag({permalink, label, count}: Props): JSX.Element { export default function Tag({
permalink,
label,
count,
description,
}: Props): JSX.Element {
return ( return (
<Link <Link
href={permalink} href={permalink}
title={description}
className={clsx( className={clsx(
styles.tag, styles.tag,
count ? styles.tagWithCount : styles.tagRegular, count ? styles.tagWithCount : styles.tagRegular,

View file

@ -24,9 +24,9 @@ export default function TagsListInline({tags}: Props): JSX.Element {
</Translate> </Translate>
</b> </b>
<ul className={clsx(styles.tags, 'padding--none', 'margin-left--sm')}> <ul className={clsx(styles.tags, 'padding--none', 'margin-left--sm')}>
{tags.map(({label, permalink: tagPermalink}) => ( {tags.map((tag) => (
<li key={tagPermalink} className={styles.tag}> <li key={tag.permalink} className={styles.tag}>
<Tag label={label} permalink={tagPermalink} /> <Tag {...tag} />
</li> </li>
))} ))}
</ul> </ul>

View file

@ -21,10 +21,15 @@
"@docusaurus/logger": "3.3.2", "@docusaurus/logger": "3.3.2",
"@docusaurus/utils": "3.3.2", "@docusaurus/utils": "3.3.2",
"@docusaurus/utils-common": "3.3.2", "@docusaurus/utils-common": "3.3.2",
"fs-extra": "^11.2.0",
"joi": "^17.9.2", "joi": "^17.9.2",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"tslib": "^2.6.0" "tslib": "^2.6.0"
}, },
"devDependencies": {
"tmp-promise": "^3.0.3"
},
"engines": { "engines": {
"node": ">=18.0" "node": ">=18.0"
} }

View file

@ -0,0 +1,538 @@
/**
* 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 * as path from 'path';
import * as fs from 'fs-extra';
import * as tmp from 'tmp-promise';
import * as YAML from 'js-yaml';
import {
ensureUniquePermalinks,
getTagsFile,
getTagsFilePathsToWatch,
normalizeTagsFile,
} from '../tagsFile';
import type {TagsFile, TagsFileInput} from '@docusaurus/utils';
describe('ensureUniquePermalinks', () => {
it('throw when one duplicate permalink found', () => {
const definedTags: TagsFile = {
open: {
label: 'Open Source',
permalink: '/custom-open-source',
description: 'Learn about the open source',
},
closed: {
label: 'Closed Source',
permalink: '/custom-open-source',
description: 'Learn about the closed source',
},
};
expect(() => ensureUniquePermalinks(definedTags))
.toThrowErrorMatchingInlineSnapshot(`
"Duplicate permalinks found in tags file:
- /custom-open-source"
`);
});
it('throw when multiple duplicate permalink found', () => {
const definedTags: TagsFile = {
open: {
label: 'Open Source',
permalink: '/custom-open-source',
description: 'Learn about the open source',
},
closed: {
label: 'Closed Source',
permalink: '/custom-open-source',
description: 'Learn about the closed source',
},
hello: {
label: 'Hello',
permalink: '/hello',
description: 'Learn about the hello',
},
world: {
label: 'Hello',
permalink: '/hello',
description: 'Learn about the world',
},
};
expect(() => ensureUniquePermalinks(definedTags))
.toThrowErrorMatchingInlineSnapshot(`
"Duplicate permalinks found in tags file:
- /custom-open-source
- /hello"
`);
});
it('do not throw when no duplicate permalink found', () => {
const definedTags: TagsFile = {
open: {
label: 'Open Source',
permalink: '/open-source',
description: 'Learn about the open source',
},
closed: {
label: 'Closed Source',
permalink: '/closed-source',
description: 'Learn about the closed source',
},
};
expect(() => ensureUniquePermalinks(definedTags)).not.toThrow();
});
});
describe('normalizeTagsFile', () => {
it('normalize null tag', () => {
const input: TagsFileInput = {
'kebab case test': null,
};
const expectedOutput: TagsFile = {
'kebab case test': {
description: undefined,
label: 'Kebab case test',
permalink: '/kebab-case-test',
},
};
expect(normalizeTagsFile(input)).toEqual(expectedOutput);
});
it('normalize partial tag with label', () => {
const input: TagsFileInput = {
world: {label: 'WORLD'},
};
const expectedOutput: TagsFile = {
world: {
description: undefined,
label: 'WORLD',
permalink: '/world',
},
};
expect(normalizeTagsFile(input)).toEqual(expectedOutput);
});
it('normalize partial tag with description', () => {
const input: TagsFileInput = {
world: {description: 'World description test'},
};
const expectedOutput: TagsFile = {
world: {
description: 'World description test',
label: 'World',
permalink: '/world',
},
};
expect(normalizeTagsFile(input)).toEqual(expectedOutput);
});
it('normalize partial tag with permalink', () => {
const input: TagsFileInput = {
world: {permalink: 'world'},
};
const expectedOutput: TagsFile = {
world: {
description: undefined,
label: 'World',
permalink: 'world',
},
};
expect(normalizeTagsFile(input)).toEqual(expectedOutput);
});
it('does not modify fully defined tags', () => {
const input: TagsFileInput = {
tag1: {
label: 'Custom Label',
description: 'Custom Description',
permalink: 'custom-permalink',
},
};
expect(normalizeTagsFile(input)).toEqual(input);
});
it('handle special characters in keys', () => {
const input: TagsFileInput = {
'special@char$!key': null,
};
const expectedOutput: TagsFile = {
'special@char$!key': {
description: undefined,
label: 'Special@char$!key',
permalink: '/special-char-key',
},
};
expect(normalizeTagsFile(input)).toEqual(expectedOutput);
});
it('handle special characters in keys with chinese characters', () => {
const input: TagsFileInput = {
特殊字符测试: null,
};
const expectedOutput: TagsFile = {
: {
description: undefined,
label: '特殊字符测试',
permalink: '/特殊字符测试',
},
};
expect(normalizeTagsFile(input)).toEqual(expectedOutput);
});
it('normalize test', () => {
const input: TagsFileInput = {
world: {permalink: 'aze'},
hello: {permalink: 'h e l l o'},
};
const expectedOutput = {
world: {
description: undefined,
label: 'World',
permalink: 'aze',
},
hello: {
description: undefined,
label: 'Hello',
permalink: 'h e l l o',
},
};
expect(normalizeTagsFile(input)).toEqual(expectedOutput);
});
});
describe('getTagsFilePathsToWatch', () => {
it('returns tags file paths - tags undefined', () => {
expect(
getTagsFilePathsToWatch({
tags: undefined,
contentPaths: {
contentPath: '/user/blog',
contentPathLocalized: '/i18n/blog',
},
}),
).toEqual(['/i18n/blog/tags.yml', '/user/blog/tags.yml']);
});
it('returns tags file paths - tags.yml', () => {
expect(
getTagsFilePathsToWatch({
tags: 'tags.yml',
contentPaths: {
contentPath: '/user/blog',
contentPathLocalized: '/i18n/blog',
},
}),
).toEqual(['/i18n/blog/tags.yml', '/user/blog/tags.yml']);
});
it('returns tags file paths - customTags.yml', () => {
expect(
getTagsFilePathsToWatch({
tags: 'customTags.yml',
contentPaths: {
contentPath: '/user/blog',
contentPathLocalized: '/i18n/blog',
},
}),
).toEqual(['/i18n/blog/customTags.yml', '/user/blog/customTags.yml']);
});
it('returns [] - tags: null', () => {
expect(
getTagsFilePathsToWatch({
tags: null,
contentPaths: {
contentPath: '/user/blog',
contentPathLocalized: '/i18n/blog',
},
}),
).toEqual([]);
});
it('returns [] - tags: false', () => {
expect(
getTagsFilePathsToWatch({
tags: false,
contentPaths: {
contentPath: '/user/blog',
contentPathLocalized: '/i18n/blog',
},
}),
).toEqual([]);
});
});
describe('getTagsFile', () => {
async function createTestTagsFile({
filePath,
tagsFileInput,
}: {
filePath: string;
tagsFileInput: TagsFileInput;
}): Promise<{dir: string}> {
async function createTmpDir() {
return (
await tmp.dir({
prefix: 'jest-createTmpSiteDir',
})
).path;
}
const contentPath = await createTmpDir();
const finalFilePath = path.join(contentPath, filePath);
const fileContent = YAML.dump(tagsFileInput);
await fs.writeFile(finalFilePath, fileContent);
return {dir: contentPath};
}
type Params = Parameters<typeof getTagsFile>[0];
it('reads tags file - regular', async () => {
const {dir} = await createTestTagsFile({
filePath: 'tags.yml',
tagsFileInput: {
tag1: {label: 'Tag1 Label'},
tag2: {description: 'Tag2 Description'},
tag3: {
label: 'Tag3 Label',
permalink: '/tag-3',
description: 'Tag3 Description',
},
},
});
const params: Params = {
contentPaths: {contentPath: dir, contentPathLocalized: dir},
tags: 'tags.yml',
};
await expect(getTagsFile(params)).resolves.toMatchInlineSnapshot(`
{
"tag1": {
"description": undefined,
"label": "Tag1 Label",
"permalink": "/tag-1",
},
"tag2": {
"description": "Tag2 Description",
"label": "Tag2",
"permalink": "/tag-2",
},
"tag3": {
"description": "Tag3 Description",
"label": "Tag3 Label",
"permalink": "/tag-3",
},
}
`);
});
it('reads tags file - only keys', async () => {
const {dir} = await createTestTagsFile({
filePath: 'tags.yml',
tagsFileInput: {
tagKey: null,
},
});
const params: Params = {
contentPaths: {contentPath: dir, contentPathLocalized: dir},
tags: 'tags.yml',
};
await expect(getTagsFile(params)).resolves.toMatchInlineSnapshot(`
{
"tagKey": {
"description": undefined,
"label": "Tagkey",
"permalink": "/tag-key",
},
}
`);
});
it('reads tags file - tags option undefined', async () => {
const {dir} = await createTestTagsFile({
filePath: 'tags.yml',
tagsFileInput: {
tag: {label: 'tag label'},
},
});
const params: Params = {
contentPaths: {contentPath: dir, contentPathLocalized: dir},
tags: undefined,
};
await expect(getTagsFile(params)).resolves.toMatchInlineSnapshot(`
{
"tag": {
"description": undefined,
"label": "tag label",
"permalink": "/tag",
},
}
`);
});
it('reads tags file - empty file', async () => {
const {dir} = await createTestTagsFile({
filePath: 'tags.yml',
tagsFileInput: {},
});
const params: Params = {
contentPaths: {contentPath: dir, contentPathLocalized: dir},
tags: undefined,
};
await expect(getTagsFile(params)).resolves.toEqual({});
});
it('reads tags file - prioritizes reading from localized content path', async () => {
const {dir} = await createTestTagsFile({
filePath: 'tags.yml',
tagsFileInput: {
tag: {label: 'tag label'},
},
});
const {dir: dirLocalized} = await createTestTagsFile({
filePath: 'tags.yml',
tagsFileInput: {
tag: {label: 'tag label (localized)'},
},
});
const params: Params = {
contentPaths: {contentPath: dir, contentPathLocalized: dirLocalized},
tags: undefined,
};
await expect(getTagsFile(params)).resolves.toMatchInlineSnapshot(`
{
"tag": {
"description": undefined,
"label": "tag label (localized)",
"permalink": "/tag",
},
}
`);
});
it('reads tags file - custom tags file path', async () => {
const {dir} = await createTestTagsFile({
filePath: 'custom-tags-path.yml',
tagsFileInput: {
tag: {label: 'tag label'},
},
});
const params: Params = {
contentPaths: {contentPath: dir, contentPathLocalized: dir},
tags: 'custom-tags-path.yml',
};
await expect(getTagsFile(params)).resolves.toMatchInlineSnapshot(`
{
"tag": {
"description": undefined,
"label": "tag label",
"permalink": "/tag",
},
}
`);
});
it('throws if duplicate permalink', async () => {
const {dir} = await createTestTagsFile({
filePath: 'tags.yml',
tagsFileInput: {
tag1: {permalink: '/duplicate'},
tag2: {permalink: '/duplicate'},
},
});
const params: Params = {
contentPaths: {contentPath: dir, contentPathLocalized: dir},
tags: undefined,
};
await expect(getTagsFile(params)).rejects.toMatchInlineSnapshot(`
[Error: Duplicate permalinks found in tags file:
- /duplicate]
`);
});
it('throws if custom tags file path does not exist', async () => {
const params: Params = {
contentPaths: {contentPath: 'any', contentPathLocalized: 'localizedAny'},
tags: 'custom-tags-path.yml',
};
await expect(getTagsFile(params)).rejects
.toThrowErrorMatchingInlineSnapshot(`
"No tags file 'custom-tags-path.yml' could be found in any of those directories:
- localizedAny
- any"
`);
});
it('does not read tags file - tags option null/false', async () => {
const {dir} = await createTestTagsFile({
filePath: 'tags.yml',
tagsFileInput: {
tag: {label: 'tag label'},
},
});
await expect(
getTagsFile({
contentPaths: {contentPath: dir, contentPathLocalized: dir},
tags: null,
}),
).resolves.toBeNull();
await expect(
getTagsFile({
contentPaths: {contentPath: dir, contentPathLocalized: dir},
tags: false,
}),
).resolves.toBeNull();
});
it('does not read tags file - tags files has non-default name', async () => {
const {dir} = await createTestTagsFile({
filePath: 'bad-tags-file-name.yml',
tagsFileInput: {
tag: {label: 'tag label'},
},
});
const params: Params = {
contentPaths: {contentPath: dir, contentPathLocalized: dir},
tags: undefined,
};
await expect(getTagsFile(params)).resolves.toBeNull();
});
});

View file

@ -29,3 +29,4 @@ export {
FrontMatterLastUpdateErrorMessage, FrontMatterLastUpdateErrorMessage,
FrontMatterLastUpdateSchema, FrontMatterLastUpdateSchema,
} from './validationSchemas'; } from './validationSchemas';
export {getTagsFilePathsToWatch, getTagsFile} from './tagsFile';

View file

@ -0,0 +1,131 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import fs from 'fs-extra';
import path from 'node:path';
import _ from 'lodash';
import Joi from 'joi';
import YAML from 'js-yaml';
import {getContentPathList, getDataFilePath} from '@docusaurus/utils';
import type {
ContentPaths,
TagsFile,
TagsFileInput,
TagsPluginOptions,
} from '@docusaurus/utils';
const TagsFileInputSchema = Joi.object<TagsFileInput>().pattern(
Joi.string(),
Joi.object({
label: Joi.string(),
description: Joi.string(),
permalink: Joi.string(),
}).allow(null),
);
export function ensureUniquePermalinks(tags: TagsFile): void {
const permalinks = new Set<string>();
const duplicates = new Set<string>();
for (const [, tag] of Object.entries(tags)) {
const {permalink} = tag;
if (permalinks.has(permalink)) {
duplicates.add(permalink);
} else {
permalinks.add(permalink);
}
}
if (duplicates.size > 0) {
const duplicateList = Array.from(duplicates)
.map((permalink) => ` - ${permalink}`)
.join('\n');
throw new Error(
`Duplicate permalinks found in tags file:\n${duplicateList}`,
);
}
}
export function normalizeTagsFile(data: TagsFileInput): TagsFile {
return _.mapValues(data, (tag, key) => {
return {
label: tag?.label || _.capitalize(key),
description: tag?.description,
permalink: tag?.permalink || `/${_.kebabCase(key)}`,
};
});
}
type GetTagsFileParams = {
tags: TagsPluginOptions['tags'];
contentPaths: ContentPaths;
};
const DefaultTagsFileName = 'tags.yml';
export function getTagsFilePathsToWatch({
tags,
contentPaths,
}: GetTagsFileParams): string[] {
if (tags === false || tags === null) {
return [];
}
const relativeFilePath = tags ?? DefaultTagsFileName;
return getContentPathList(contentPaths).map((contentPath) =>
path.posix.join(contentPath, relativeFilePath),
);
}
export async function getTagsFile({
tags,
contentPaths,
}: GetTagsFileParams): Promise<TagsFile | null> {
if (tags === false || tags === null) {
return null;
}
const relativeFilePath = tags ?? DefaultTagsFileName;
// if returned path is defined, the file exists (localized or not)
const yamlFilePath = await getDataFilePath({
contentPaths,
filePath: relativeFilePath,
});
// If the tags option is undefined, don't throw when the file does not exist
// Retro-compatible behavior: existing sites do not yet have tags.yml
if (tags === undefined && !yamlFilePath) {
return null;
}
if (!yamlFilePath) {
throw new Error(
`No tags file '${relativeFilePath}' could be found in any of those directories:\n- ${getContentPathList(
contentPaths,
).join('\n- ')}`,
);
}
const tagDefinitionContent = await fs.readFile(yamlFilePath, 'utf-8');
if (!tagDefinitionContent.trim()) {
return {};
}
const yamlContent = YAML.load(tagDefinitionContent);
const tagsFileInputResult = TagsFileInputSchema.validate(yamlContent);
if (tagsFileInputResult.error) {
throw new Error(
`There was an error extracting tags from file: ${tagsFileInputResult.error.message}`,
{cause: tagsFileInputResult},
);
}
const tagsFile = normalizeTagsFile(tagsFileInputResult.value);
ensureUniquePermalinks(tagsFile);
return tagsFile;
}

View file

@ -5,7 +5,11 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {isValidPathname, DEFAULT_PLUGIN_ID, type Tag} from '@docusaurus/utils'; import {
isValidPathname,
DEFAULT_PLUGIN_ID,
type FrontMatterTag,
} from '@docusaurus/utils';
import {addLeadingSlash} from '@docusaurus/utils-common'; import {addLeadingSlash} from '@docusaurus/utils-common';
import Joi from './Joi'; import Joi from './Joi';
import {JoiFrontMatter} from './JoiFrontMatter'; import {JoiFrontMatter} from './JoiFrontMatter';
@ -113,7 +117,9 @@ export const RouteBasePathSchema = Joi
const FrontMatterTagSchema = JoiFrontMatter.alternatives() const FrontMatterTagSchema = JoiFrontMatter.alternatives()
.try( .try(
JoiFrontMatter.string().required(), JoiFrontMatter.string().required(),
JoiFrontMatter.object<Tag>({ // TODO Docusaurus v4 remove this legacy front matter tag object form
// users should use tags.yml instead
JoiFrontMatter.object<FrontMatterTag>({
label: JoiFrontMatter.string().required(), label: JoiFrontMatter.string().required(),
permalink: JoiFrontMatter.string().required(), permalink: JoiFrontMatter.string().required(),
}).required(), }).required(),

View file

@ -36,6 +36,7 @@
"shelljs": "^0.8.5", "shelljs": "^0.8.5",
"tslib": "^2.6.0", "tslib": "^2.6.0",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"utility-types": "^3.10.0",
"webpack": "^5.88.1" "webpack": "^5.88.1"
}, },
"engines": { "engines": {

View file

@ -6,117 +6,284 @@
*/ */
import { import {
normalizeFrontMatterTags, reportInlineTags,
groupTaggedItems, groupTaggedItems,
type Tag,
getTagVisibility, getTagVisibility,
} from '../tags'; } from '@docusaurus/utils';
import {normalizeTag} from '../tags';
import type {Tag, TagMetadata, FrontMatterTag, TagsFile} from '../tags';
describe('normalizeFrontMatterTags', () => { describe('normalizeTag', () => {
it('normalizes simple string tag', () => { const tagsBaseRoutePath = '/all/tags';
const tagsPath = '/all/tags';
const input = 'tag'; describe('inline', () => {
const expectedOutput = { it('normalizes simple string tag', () => {
label: 'tag', const input: FrontMatterTag = 'tag';
permalink: `${tagsPath}/tag`, const expectedOutput: TagMetadata = {
inline: true,
label: 'tag',
permalink: `${tagsBaseRoutePath}/tag`,
description: undefined,
};
expect(
normalizeTag({tagsBaseRoutePath, tagsFile: null, tag: input}),
).toEqual(expectedOutput);
});
it('normalizes complex string tag', () => {
const input: FrontMatterTag = 'some more Complex_tag';
const expectedOutput: TagMetadata = {
inline: true,
label: 'some more Complex_tag',
permalink: `${tagsBaseRoutePath}/some-more-complex-tag`,
description: undefined,
};
expect(
normalizeTag({tagsBaseRoutePath, tagsFile: null, tag: input}),
).toEqual(expectedOutput);
});
it('normalizes simple object tag', () => {
const input: FrontMatterTag = {
label: 'tag',
permalink: 'tagPermalink',
};
const expectedOutput: TagMetadata = {
inline: true,
label: 'tag',
permalink: `${tagsBaseRoutePath}/tagPermalink`,
description: undefined,
};
expect(
normalizeTag({tagsBaseRoutePath, tagsFile: null, tag: input}),
).toEqual(expectedOutput);
});
it('normalizes complex string tag with object tag', () => {
const input: FrontMatterTag = {
label: 'tag complex Label',
permalink: '/MoreComplex/Permalink',
};
const expectedOutput: TagMetadata = {
inline: true,
label: 'tag complex Label',
permalink: `${tagsBaseRoutePath}/MoreComplex/Permalink`,
description: undefined,
};
expect(
normalizeTag({tagsBaseRoutePath, tagsFile: null, tag: input}),
).toEqual(expectedOutput);
});
});
describe('with tags file', () => {
const tagsFile: TagsFile = {
tag1: {
label: 'Tag 1 label',
permalink: 'tag-1-permalink',
description: 'Tag 1 description',
},
tag2: {
label: 'Tag 2 label',
permalink: '/tag-2-permalink',
description: undefined,
},
}; };
expect(normalizeFrontMatterTags(tagsPath, [input])).toEqual([
expectedOutput, it('normalizes tag1 ref', () => {
]); const input: FrontMatterTag = 'tag1';
const expectedOutput: TagMetadata = {
inline: false,
label: tagsFile.tag1.label,
description: tagsFile.tag1.description,
permalink: `${tagsBaseRoutePath}/tag-1-permalink`,
};
expect(normalizeTag({tagsBaseRoutePath, tagsFile, tag: input})).toEqual(
expectedOutput,
);
});
it('normalizes tag2 ref', () => {
const input: FrontMatterTag = 'tag2';
const expectedOutput: TagMetadata = {
inline: false,
label: tagsFile.tag2.label,
description: tagsFile.tag2.description,
permalink: `${tagsBaseRoutePath}/tag-2-permalink`,
};
expect(normalizeTag({tagsBaseRoutePath, tagsFile, tag: input})).toEqual(
expectedOutput,
);
});
it('normalizes inline tag not declared in tags file', () => {
const input: FrontMatterTag = 'inlineTag';
const expectedOutput: TagMetadata = {
inline: true,
label: 'inlineTag',
description: undefined,
permalink: `${tagsBaseRoutePath}/inline-tag`,
};
expect(normalizeTag({tagsBaseRoutePath, tagsFile, tag: input})).toEqual(
expectedOutput,
);
});
});
});
describe('reportInlineTags', () => {
const tagsFile: TagsFile = {
hello: {
label: 'Hello',
permalink: '/hello',
description: undefined,
},
test: {
label: 'Test',
permalink: '/test',
description: undefined,
},
open: {
label: 'Open Source',
permalink: '/open',
description: undefined,
},
};
it('throw when inline tags found', () => {
const testFn = () =>
reportInlineTags({
tags: [
{
label: 'hello',
permalink: 'hello',
inline: true,
description: undefined,
},
{
label: 'world',
permalink: 'world',
inline: true,
description: undefined,
},
],
source: 'wrong.md',
options: {onInlineTags: 'throw', tags: 'tags.yml'},
});
expect(testFn).toThrowErrorMatchingInlineSnapshot(
`"Tags [hello, world] used in wrong.md are not defined in tags.yml"`,
);
}); });
it('normalizes complex string tag', () => { it('warn when docs has invalid tags', () => {
const tagsPath = '/all/tags'; const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const input = 'some more Complex_tag';
const expectedOutput = { reportInlineTags({
label: 'some more Complex_tag', tags: [
permalink: `${tagsPath}/some-more-complex-tag`, {
}; label: 'hello',
expect(normalizeFrontMatterTags(tagsPath, [input])).toEqual([ permalink: 'hello',
expectedOutput, inline: false,
]); description: undefined,
},
{
label: 'world',
permalink: 'world',
inline: true,
description: undefined,
},
],
source: 'wrong.md',
options: {onInlineTags: 'warn', tags: 'tags.yml'},
});
expect(warnSpy).toHaveBeenCalledTimes(1);
expect(warnSpy.mock.calls).toMatchInlineSnapshot(`
[
[
"[WARNING] Tags [world] used in wrong.md are not defined in tags.yml",
],
]
`);
warnSpy.mockRestore();
}); });
it('normalizes simple object tag', () => { it('ignore when docs has invalid tags', () => {
const tagsPath = '/all/tags'; const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const input = {label: 'tag', permalink: 'tagPermalink'}; const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const expectedOutput = { const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
label: 'tag',
permalink: `${tagsPath}/tagPermalink`, reportInlineTags({
}; tags: [
expect(normalizeFrontMatterTags(tagsPath, [input])).toEqual([ {
expectedOutput, label: 'hello',
]); permalink: 'hello',
inline: false,
description: undefined,
},
{
label: 'world',
permalink: 'world',
inline: true,
description: undefined,
},
],
source: 'wrong.md',
options: {onInlineTags: 'ignore', tags: 'tags.yml'},
});
expect(errorSpy).not.toHaveBeenCalled();
expect(warnSpy).not.toHaveBeenCalled();
expect(logSpy).not.toHaveBeenCalled();
errorSpy.mockRestore();
warnSpy.mockRestore();
logSpy.mockRestore();
}); });
it('normalizes complex string tag with object tag', () => { it('throw for unknown string and object tag', () => {
const tagsPath = '/all/tags'; const frontmatter = ['open', 'world'];
const input = { const tags = frontmatter.map((tag) =>
label: 'tag complex Label', normalizeTag({
permalink: '/MoreComplex/Permalink', tagsBaseRoutePath: '/tags',
}; tagsFile,
const expectedOutput = { tag,
label: 'tag complex Label', }),
permalink: `${tagsPath}/MoreComplex/Permalink`, );
};
expect(normalizeFrontMatterTags(tagsPath, [input])).toEqual([ const testFn = () =>
expectedOutput, reportInlineTags({
]); tags,
source: 'default.md',
options: {
onInlineTags: 'throw',
tags: 'tags.yml',
},
});
expect(testFn).toThrowErrorMatchingInlineSnapshot(
`"Tags [world] used in default.md are not defined in tags.yml"`,
);
}); });
type Input = Parameters<typeof normalizeFrontMatterTags>[1]; it('does not throw when docs has valid tags', () => {
type Output = ReturnType<typeof normalizeFrontMatterTags>; const frontmatter = ['open'];
const tags = frontmatter.map((tag) =>
it('normalizes string list', () => { normalizeTag({
const tagsPath = '/all/tags'; tagsBaseRoutePath: '/tags',
const input: Input = ['tag 1', 'tag-1', 'tag 3', 'tag1', 'tag-2']; tagsFile,
// Keep user input order but remove tags that lead to same permalink tag,
const expectedOutput: Output = [ }),
{ );
label: 'tag 1', const testFn = () =>
permalink: `${tagsPath}/tag-1`, reportInlineTags({
}, tags,
{ source: 'wrong.md',
label: 'tag 3', options: {
permalink: `${tagsPath}/tag-3`, onInlineTags: 'throw',
}, tags: 'tags.yml',
{ },
label: 'tag-2', });
permalink: `${tagsPath}/tag-2`, expect(testFn).not.toThrow();
},
];
expect(normalizeFrontMatterTags(tagsPath, input)).toEqual(expectedOutput);
});
it('succeeds for empty list', () => {
expect(normalizeFrontMatterTags('/foo')).toEqual([]);
});
it('normalizes complex mixed list', () => {
const tagsPath = '/all/tags';
const input: Input = [
'tag 1',
{label: 'tag-1', permalink: '/tag-1'},
'tag 3',
'tag1',
{label: 'tag 4', permalink: '/tag4Permalink'},
];
// Keep user input order but remove tags that lead to same permalink
const expectedOutput: Output = [
{
label: 'tag 1',
permalink: `${tagsPath}/tag-1`,
},
{
label: 'tag 3',
permalink: `${tagsPath}/tag-3`,
},
{
label: 'tag 4',
permalink: `${tagsPath}/tag4Permalink`,
},
];
expect(normalizeFrontMatterTags(tagsPath, input)).toEqual(expectedOutput);
}); });
}); });
@ -135,14 +302,23 @@ describe('groupTaggedItems', () => {
type Output = ReturnType<typeof groupItems>; type Output = ReturnType<typeof groupItems>;
it('groups items by tag permalink', () => { it('groups items by tag permalink', () => {
const tagGuide = {label: 'Guide', permalink: '/guide'}; const tagGuide = {
const tagTutorial = {label: 'Tutorial', permalink: '/tutorial'}; label: 'Guide',
const tagAPI = {label: 'API', permalink: '/api'}; permalink: '/guide',
description: undefined,
};
const tagTutorial = {
label: 'Tutorial',
permalink: '/tutorial',
description: undefined,
};
const tagAPI = {label: 'API', permalink: '/api', description: undefined};
// This one will be grouped under same permalink and label is ignored // This one will be grouped under same permalink and label is ignored
const tagTutorialOtherLabel = { const tagTutorialOtherLabel = {
label: 'TutorialOtherLabel', label: 'TutorialOtherLabel',
permalink: '/tutorial', permalink: '/tutorial',
description: undefined,
}; };
const item1: SomeTaggedItem = { const item1: SomeTaggedItem = {

View file

@ -55,10 +55,13 @@ export {
export type {URLPath} from './urlUtils'; export type {URLPath} from './urlUtils';
export { export {
type Tag, type Tag,
type TagsFile,
type TagsFileInput,
type TagMetadata,
type TagsListItem, type TagsListItem,
type TagModule, type TagModule,
type FrontMatterTag, type FrontMatterTag,
normalizeFrontMatterTags, type TagsPluginOptions,
groupTaggedItems, groupTaggedItems,
getTagVisibility, getTagVisibility,
} from './tags'; } from './tags';
@ -120,3 +123,5 @@ export {
type LastUpdateData, type LastUpdateData,
type FrontMatterLastUpdate, type FrontMatterLastUpdate,
} from './lastUpdateUtils'; } from './lastUpdateUtils';
export {normalizeTags, reportInlineTags} from './tags';

View file

@ -6,13 +6,34 @@
*/ */
import _ from 'lodash'; import _ from 'lodash';
import logger from '@docusaurus/logger';
import {normalizeUrl} from './urlUtils'; import {normalizeUrl} from './urlUtils';
import type {Optional} from 'utility-types';
/** What the user configures. */
export type Tag = { export type Tag = {
/** The display label of a tag */
label: string; label: string;
/** Permalink to this tag's page, without the `/tags/` base path. */ /** Permalink to this tag's page, without the `/tags/` base path. */
permalink: string; permalink: string;
/** An optional description of the tag */
description: string | undefined;
};
export type TagsFileInput = Record<string, Partial<Tag> | null>;
export type TagsFile = Record<string, Tag>;
// Tags plugins options shared between docs/blog
export type TagsPluginOptions = {
// TODO allow option tags later? | TagsFile;
/** Path to the tags file. */
tags: string | false | null | undefined;
/** The behavior of Docusaurus when it found inline tags. */
onInlineTags: 'ignore' | 'log' | 'warn' | 'throw';
};
export type TagMetadata = Tag & {
inline: boolean;
}; };
/** What the tags list page should know about each tag. */ /** What the tags list page should know about each tag. */
@ -29,62 +50,126 @@ export type TagModule = TagsListItem & {
unlisted: boolean; unlisted: boolean;
}; };
export type FrontMatterTag = string | Tag; export type FrontMatterTag = string | Optional<Tag, 'description'>;
function normalizeFrontMatterTag( // We always apply tagsBaseRoutePath on purpose. For versioned docs, v1/doc.md
tagsPath: string, // and v2/doc.md tags with custom permalinks don't lead to the same created
// page. tagsBaseRoutePath is different for each doc version
function normalizeTagPermalink({
tagsBaseRoutePath,
permalink,
}: {
tagsBaseRoutePath: string;
permalink: string;
}): string {
return normalizeUrl([tagsBaseRoutePath, permalink]);
}
function normalizeInlineTag(
tagsBaseRoutePath: string,
frontMatterTag: FrontMatterTag, frontMatterTag: FrontMatterTag,
): Tag { ): TagMetadata {
function toTagObject(tagString: string): Tag { function toTagObject(tagString: string): TagMetadata {
return { return {
inline: true,
label: tagString, label: tagString,
permalink: _.kebabCase(tagString), permalink: _.kebabCase(tagString),
description: undefined,
}; };
} }
// TODO maybe make ensure the permalink is valid url path?
function normalizeTagPermalink(permalink: string): string {
// Note: we always apply tagsPath on purpose. For versioned docs, v1/doc.md
// and v2/doc.md tags with custom permalinks don't lead to the same created
// page. tagsPath is different for each doc version
return normalizeUrl([tagsPath, permalink]);
}
const tag: Tag = const tag: Tag =
typeof frontMatterTag === 'string' typeof frontMatterTag === 'string'
? toTagObject(frontMatterTag) ? toTagObject(frontMatterTag)
: frontMatterTag; : {...frontMatterTag, description: frontMatterTag.description};
return { return {
inline: true,
label: tag.label, label: tag.label,
permalink: normalizeTagPermalink(tag.permalink), permalink: normalizeTagPermalink({
permalink: tag.permalink,
tagsBaseRoutePath,
}),
description: tag.description,
}; };
} }
/** export function normalizeTag({
* Takes tag objects as they are defined in front matter, and normalizes each tag,
* into a standard tag object. The permalink is created by appending the tagsFile,
* sluggified label to `tagsPath`. Front matter tags already containing tagsBaseRoutePath,
* permalinks would still have `tagsPath` prepended. }: {
* tag: FrontMatterTag;
* The result will always be unique by permalinks. The behavior with colliding tagsBaseRoutePath: string;
* permalinks is undetermined. tagsFile: TagsFile | null;
*/ }): TagMetadata {
export function normalizeFrontMatterTags( if (typeof tag === 'string') {
/** Base path to append the tag permalinks to. */ const tagDescription = tagsFile?.[tag];
tagsPath: string, if (tagDescription) {
/** Can be `undefined`, so that we can directly pipe in `frontMatter.tags`. */ // pre-defined tag from tags.yml
frontMatterTags: FrontMatterTag[] | undefined = [], return {
): Tag[] { inline: false,
const tags = frontMatterTags.map((tag) => label: tagDescription.label,
normalizeFrontMatterTag(tagsPath, tag), permalink: normalizeTagPermalink({
); permalink: tagDescription.permalink,
tagsBaseRoutePath,
}),
description: tagDescription.description,
};
}
}
// legacy inline tag object, always inline, unknown because isn't a string
return normalizeInlineTag(tagsBaseRoutePath, tag);
}
return _.uniqBy(tags, (tag) => tag.permalink); export function normalizeTags({
options,
source,
frontMatterTags,
tagsBaseRoutePath,
tagsFile,
}: {
options: TagsPluginOptions;
source: string;
frontMatterTags: FrontMatterTag[] | undefined;
tagsBaseRoutePath: string;
tagsFile: TagsFile | null;
}): TagMetadata[] {
const tags = (frontMatterTags ?? []).map((tag) =>
normalizeTag({tag, tagsBaseRoutePath, tagsFile}),
);
if (tagsFile !== null) {
reportInlineTags({tags, source, options});
}
return tags;
}
export function reportInlineTags({
tags,
source,
options,
}: {
tags: TagMetadata[];
source: string;
options: TagsPluginOptions;
}): void {
if (options.onInlineTags === 'ignore') {
return;
}
const inlineTags = tags.filter((tag) => tag.inline);
if (inlineTags.length > 0) {
const uniqueUnknownTags = [...new Set(inlineTags.map((tag) => tag.label))];
const tagListString = uniqueUnknownTags.join(', ');
logger.report(options.onInlineTags)(
`Tags [${tagListString}] used in ${source} are not defined in ${
options.tags ?? 'tags.yml'
}`,
);
}
} }
type TaggedItemGroup<Item> = { type TaggedItemGroup<Item> = {
tag: Tag; tag: TagMetadata;
items: Item[]; items: Item[];
}; };
@ -102,7 +187,7 @@ export function groupTaggedItems<Item>(
* A callback telling me how to get the tags list of the current item. Usually * A callback telling me how to get the tags list of the current item. Usually
* simply getting it from some metadata of the current item. * simply getting it from some metadata of the current item.
*/ */
getItemTags: (item: Item) => readonly Tag[], getItemTags: (item: Item) => readonly TagMetadata[],
): {[permalink: string]: TaggedItemGroup<Item>} { ): {[permalink: string]: TaggedItemGroup<Item>} {
const result: {[permalink: string]: TaggedItemGroup<Item>} = {}; const result: {[permalink: string]: TaggedItemGroup<Item>} = {};

View file

@ -123,6 +123,7 @@ héllô
hideable hideable
Hideable Hideable
hola hola
Hola
Hostman Hostman
hoverable hoverable
Husain Husain
@ -320,7 +321,6 @@ showinfo
Sida Sida
Simen Simen
slorber slorber
sluggified
sluggifies sluggifies
sluggify sluggify
solana solana
@ -353,6 +353,7 @@ Supabase
svgr svgr
SVGR SVGR
swizzlable swizzlable
Tagkey
Teik Teik
templating templating
Thanos Thanos

View file

@ -0,0 +1,12 @@
paginated-tag:
blog:
docusaurus:
long:
long-long:
long-long-long:
long-long-long-long:
long-long-long-long-long:
visibility:
unlisted:
draft:
new:

View file

@ -1,6 +1,6 @@
--- ---
slug: / slug: /
tags: [a, b, c, some tag] tags: [a, b, c]
unlisted: true # Makes the navbar link disappear in prod unlisted: true # Makes the navbar link disappear in prod
id: index id: index
sidebar_label: Docs tests # TODO why is this required? sidebar_label: Docs tests # TODO why is this required?

View file

@ -1,5 +1,5 @@
--- ---
tags: [a, e, some-tag, some_tag] tags: [a, e, some-tag]
--- ---
# Another test page # Another test page

View file

@ -0,0 +1,16 @@
a:
description: 'Description for tag a'
b:
label: 'Label for tag b'
c:
permalink: '/permalink-for-tag-c'
e:
label: 'Label for tag e'
description: 'Description for tag e'
permalink: '/permalink-for-tag-e'
some-tag:
visibility:
draft:
listed:
unlisted:
d-custom-permalink:

View file

@ -40,6 +40,8 @@ export const dogfoodingPluginInstances: PluginConfig[] = [
noIndex: true, noIndex: true,
}, },
}, },
onInlineTags: 'warn',
tags: 'tags.yml',
// Using a _ prefix to test against an edge case regarding MDX partials: https://github.com/facebook/docusaurus/discussions/5181#discussioncomment-1018079 // Using a _ prefix to test against an edge case regarding MDX partials: https://github.com/facebook/docusaurus/discussions/5181#discussioncomment-1018079
path: '_dogfooding/_docs tests', path: '_dogfooding/_docs tests',
@ -81,6 +83,8 @@ export const dogfoodingPluginInstances: PluginConfig[] = [
frontMatter.hide_reading_time frontMatter.hide_reading_time
? undefined ? undefined
: defaultReadingTime({content, options: {wordsPerMinute: 5}}), : defaultReadingTime({content, options: {wordsPerMinute: 5}}),
onInlineTags: 'warn',
tags: 'tags.yml',
} satisfies BlogOptions, } satisfies BlogOptions,
], ],

18
website/blog/tags.yml Normal file
View file

@ -0,0 +1,18 @@
blog:
release:
description: "Blog posts about Docusaurus' new releases"
recap:
description: "Blog posts about Docusaurus' year recaps"
birth:
endi:
tribute:
i18n:
beta:
search:
maintenance:
documentation:
docusaurus:
profilo:
adoption:
unlisted:
new:

View file

@ -0,0 +1,54 @@
## Tags File {#tags-file}
Use the [`tags` plugin option](#tags) to configure the path of a YAML tags file.
By convention, the plugin will look for a `tags.yml` file at the root of your content folder(s).
This file can contain a list of predefined tags. You can reference these tags by their keys in Markdown files thanks to the [`tags` front matter](#markdown-front-matter).
:::tip Keeping tags consistent
Using a tags file, you can ensure that your tags usage is consistent across your plugin content set. Use the [`onInlineTags: 'throw'`](#onInlineTags) plugin option to enforce this consistency and prevent usage of inline tags declared on the fly.
:::
### Types {#tags-file-types}
The YAML content of the provided tags file should respect the following shape:
```tsx
type Tag = {
label?: string; // Tag display label
permalink?: string; // Tag URL pathname segment
description?: string; // Tag description displayed in the tag page
};
type TagsFileInput = Record<string, Partial<Tag> | null>;
```
### Example {#tags-file-example}
```yml title="tags.yml"
releases:
label: 'Product releases'
permalink: '/product-releases'
description: 'Content related to product releases.'
# A partial tag definition is also valid
announcements:
label: 'Announcements'
# An empty tag definition is also valid
# Other attributes will be inferred from the key
emptyTag:
```
```md title="content.md"
---
tags: [releases, announcements, emptyTag]
---
# Title
Content
```

View file

@ -78,6 +78,8 @@ Accepted fields:
| `processBlogPosts` | <code>[ProcessBlogPostsFn](#ProcessBlogPostsFn)</code> | `undefined` | An optional function which can be used to transform blog posts (filter, modify, delete, etc...). | | `processBlogPosts` | <code>[ProcessBlogPostsFn](#ProcessBlogPostsFn)</code> | `undefined` | An optional function which can be used to transform blog posts (filter, modify, delete, etc...). |
| `showLastUpdateAuthor` | `boolean` | `false` | Whether to display the author who last updated the blog post. | | `showLastUpdateAuthor` | `boolean` | `false` | Whether to display the author who last updated the blog post. |
| `showLastUpdateTime` | `boolean` | `false` | Whether to display the last date the blog post was updated. This requires access to git history during the build, so will not work correctly with shallow clones (a common default for CI systems). With GitHub `actions/checkout`, use`fetch-depth: 0`. | | `showLastUpdateTime` | `boolean` | `false` | Whether to display the last date the blog post was updated. This requires access to git history during the build, so will not work correctly with shallow clones (a common default for CI systems). With GitHub `actions/checkout`, use`fetch-depth: 0`. |
| `tags` | `string \| false \| null \| undefined` | `tags.yml` | Path to the YAML tags file listing pre-defined tags. Relative to the blog content directory. |
| `onInlineTags` | `'ignore' \| 'log' \| 'warn' \| 'throw'` | `warn` | The plugin behavior when blog posts contain inline tags (not appearing in the list of pre-defined tags, usually `tags.yml`). |
```mdx-code-block ```mdx-code-block
</APITable> </APITable>
@ -224,7 +226,7 @@ Accepted fields:
| `author_title` | `string` | `undefined` | ⚠️ Prefer using `authors`. A description of the author. | | `author_title` | `string` | `undefined` | ⚠️ Prefer using `authors`. A description of the author. |
| `title` | `string` | Markdown title | The blog post title. | | `title` | `string` | Markdown title | The blog post title. |
| `date` | `string` | File name or file creation time | The blog post creation date. If not specified, this can be extracted from the file or folder name, e.g, `2021-04-15-blog-post.mdx`, `2021-04-15-blog-post/index.mdx`, `2021/04/15/blog-post.mdx`. Otherwise, it is the Markdown file creation time. | | `date` | `string` | File name or file creation time | The blog post creation date. If not specified, this can be extracted from the file or folder name, e.g, `2021-04-15-blog-post.mdx`, `2021-04-15-blog-post/index.mdx`, `2021/04/15/blog-post.mdx`. Otherwise, it is the Markdown file creation time. |
| `tags` | `Tag[]` | `undefined` | A list of strings or objects of two string fields `label` and `permalink` to tag to your post. | | `tags` | `Tag[]` | `undefined` | A list of strings or objects of two string fields `label` and `permalink` to tag to your post. Strings can be a reference to keys of a [tags file](#tags-file) (usually `tags.yml`) |
| `draft` | `boolean` | `false` | Draft blog posts will only be available during development. | | `draft` | `boolean` | `false` | Draft blog posts will only be available during development. |
| `unlisted` | `boolean` | `false` | Unlisted blog posts will be available in both development and production. They will be "hidden" in production, not indexed, excluded from sitemaps, and can only be accessed by users having a direct link. | | `unlisted` | `boolean` | `false` | Unlisted blog posts will be available in both development and production. They will be "hidden" in production, not indexed, excluded from sitemaps, and can only be accessed by users having a direct link. |
| `hide_table_of_contents` | `boolean` | `false` | Whether to hide the table of contents to the right. | | `hide_table_of_contents` | `boolean` | `false` | Whether to hide the table of contents to the right. |
@ -272,7 +274,7 @@ authors:
title: Co-creator of Docusaurus 1 title: Co-creator of Docusaurus 1
url: https://github.com/JoelMarcey url: https://github.com/JoelMarcey
image_url: https://github.com/JoelMarcey.png image_url: https://github.com/JoelMarcey.png
tags: [hello, docusaurus-v2] tags: [docusaurus]
description: This is my first post on Docusaurus. description: This is my first post on Docusaurus.
image: https://i.imgur.com/mErPwqL.png image: https://i.imgur.com/mErPwqL.png
hide_table_of_contents: false hide_table_of_contents: false
@ -281,6 +283,10 @@ hide_table_of_contents: false
A Markdown blog post A Markdown blog post
``` ```
import TagsFileApiRefSection from './_partial-tags-file-api-ref-section.mdx';
<TagsFileApiRefSection />
## i18n {#i18n} ## i18n {#i18n}
Read the [i18n introduction](../../i18n/i18n-introduction.mdx) first. Read the [i18n introduction](../../i18n/i18n-introduction.mdx) first.

View file

@ -65,6 +65,8 @@ Accepted fields:
| `lastVersion` | `string` | First version in `versions.json` | The version navigated to in priority and displayed by default for docs navbar items. | | `lastVersion` | `string` | First version in `versions.json` | The version navigated to in priority and displayed by default for docs navbar items. |
| `onlyIncludeVersions` | `string[]` | All versions available | Only include a subset of all available versions. | | `onlyIncludeVersions` | `string[]` | All versions available | Only include a subset of all available versions. |
| `versions` | <code>[VersionsConfig](#VersionsConfig)</code> | `{}` | Independent customization of each version's properties. | | `versions` | <code>[VersionsConfig](#VersionsConfig)</code> | `{}` | Independent customization of each version's properties. |
| `tags` | `string \| false \| null \| undefined` | `tags.yml` | Path to a YAML file listing pre-defined tags. Relative to the docs version content directories. |
| `onInlineTags` | `'ignore' \| 'log' \| 'warn' \| 'throw'` | `warn` | The plugin behavior when docs contain inline tags (not appearing in the list of pre-defined tags, usually `docs/tags.yml`). |
```mdx-code-block ```mdx-code-block
</APITable> </APITable>
@ -293,7 +295,7 @@ Accepted fields:
| `description` | `string` | The first line of Markdown content | The description of your document, which will become the `<meta name="description" content="..."/>` and `<meta property="og:description" content="..."/>` in `<head>`, used by search engines. | | `description` | `string` | The first line of Markdown content | The description of your document, which will become the `<meta name="description" content="..."/>` and `<meta property="og:description" content="..."/>` in `<head>`, used by search engines. |
| `image` | `string` | `undefined` | Cover or thumbnail image that will be used as the `<meta property="og:image" content="..."/>` in the `<head>`, enhancing link previews on social media and messaging platforms. | | `image` | `string` | `undefined` | Cover or thumbnail image that will be used as the `<meta property="og:image" content="..."/>` in the `<head>`, enhancing link previews on social media and messaging platforms. |
| `slug` | `string` | File path | Allows to customize the document URL (`/<routeBasePath>/<slug>`). Support multiple patterns: `slug: my-doc`, `slug: /my/path/myDoc`, `slug: /`. | | `slug` | `string` | File path | Allows to customize the document URL (`/<routeBasePath>/<slug>`). Support multiple patterns: `slug: my-doc`, `slug: /my/path/myDoc`, `slug: /`. |
| `tags` | `Tag[]` | `undefined` | A list of strings or objects of two string fields `label` and `permalink` to tag to your docs. | | `tags` | `Tag[]` | `undefined` | A list of strings or objects of two string fields `label` and `permalink` to tag to your docs. Strings can be a reference to keys of a [tags file](#tags-file) (usually `tags.yml`) |
| `draft` | `boolean` | `false` | Draft documents will only be available during development. | | `draft` | `boolean` | `false` | Draft documents will only be available during development. |
| `unlisted` | `boolean` | `false` | Unlisted documents will be available in both development and production. They will be "hidden" in production, not indexed, excluded from sitemaps, and can only be accessed by users having a direct link. | | `unlisted` | `boolean` | `false` | Unlisted documents will be available in both development and production. They will be "hidden" in production, not indexed, excluded from sitemaps, and can only be accessed by users having a direct link. |
| `last_update` | `FrontMatterLastUpdate` | `undefined` | Allows overriding the last update author/date. Date can be any [parsable date string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse). | | `last_update` | `FrontMatterLastUpdate` | `undefined` | Allows overriding the last update author/date. Date can be any [parsable date string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse). |
@ -324,6 +326,7 @@ description: How do I find you when I cannot solve this problem
keywords: keywords:
- docs - docs
- docusaurus - docusaurus
tags: [docusaurus]
image: https://i.imgur.com/mErPwqL.png image: https://i.imgur.com/mErPwqL.png
slug: /myDoc slug: /myDoc
last_update: last_update:
@ -336,6 +339,10 @@ last_update:
My Document Markdown content My Document Markdown content
``` ```
import TagsFileApiRefSection from './_partial-tags-file-api-ref-section.mdx';
<TagsFileApiRefSection />
## i18n {#i18n} ## i18n {#i18n}
Read the [i18n introduction](../../i18n/i18n-introduction.mdx) first. Read the [i18n introduction](../../i18n/i18n-introduction.mdx) first.

View file

@ -380,6 +380,35 @@ An author, either declared through front matter or through the authors map, need
::: :::
## Blog post tags {#blog-post-tags}
Tags are declared in the front matter and introduce another dimension of categorization.
It is possible to define tags inline, or to reference predefined tags declared in a [`tags file`](api/plugins/plugin-content-blog.mdx#tags-file) (optional, usually `blog/tags.yml`).
In the following example:
- `docusaurus` references a predefined tag key declared in `blog/tags.yml`
- `Releases` is an inline tag, because it does not exist in `blog/tags.yml`
```md title="blog/my-post.md"
---
title: 'My blog post'
tags:
- Releases
- docusaurus
---
Content
```
```yml title="blog/tags.yml"
docusaurus:
label: 'Docusaurus'
permalink: '/docusaurus'
description: 'Blog posts related to the Docusaurus framework'
```
## Reading time {#reading-time} ## Reading time {#reading-time}
Docusaurus generates a reading time estimation for each blog post based on word count. We provide an option to customize this. Docusaurus generates a reading time estimation for each blog post based on word count. We provide an option to customize this.

View file

@ -60,16 +60,32 @@ The [front matter](../markdown-features/markdown-features-intro.mdx#front-matter
## Doc tags {#doc-tags} ## Doc tags {#doc-tags}
Optionally, you can add tags to your doc pages, which introduces another dimension of categorization in addition to the [docs sidebar](./sidebar/index.mdx). Tags are passed in the front matter as a list of labels: Tags are declared in the front matter and introduce another dimension of categorization in addition to the [docs sidebar](./sidebar/index.mdx).
```md "your-doc-page.md" It is possible to define tags inline, or to reference predefined tags declared in a [`tags file`](../../api/plugins/plugin-content-docs.mdx#tags-file) (optional, usually `docs/tags.yml`).
In the following example:
- `docusaurus` references a predefined tag key declared in `docs/tags.yml`
- `Releases` is an inline tag, because it does not exist in `docs/tags.yml`
```md title="docs/my-doc.md"
--- ---
id: doc-with-tags
title: A doc with tags
tags: tags:
- Demo - Releases
- Getting started - docusaurus
--- ---
# Title
Content
```
```yml title="docs/tags.yml"
docusaurus:
label: 'Docusaurus'
permalink: '/docusaurus'
description: 'Docs related to the Docusaurus framework'
``` ```
:::tip :::tip

View file

@ -492,6 +492,7 @@ export default async function createConfigAsync() {
blogDescription: 'Read blog posts about Docusaurus from the team', blogDescription: 'Read blog posts about Docusaurus from the team',
blogSidebarCount: 'ALL', blogSidebarCount: 'ALL',
blogSidebarTitle: 'All our posts', blogSidebarTitle: 'All our posts',
onInlineTags: 'throw',
} satisfies BlogOptions, } satisfies BlogOptions,
pages: { pages: {
remarkPlugins: [npm2yarn], remarkPlugins: [npm2yarn],

View file

@ -7847,10 +7847,10 @@ fs-extra@9.1.0, fs-extra@^9.0.0, fs-extra@^9.0.1, fs-extra@^9.1.0:
jsonfile "^6.0.1" jsonfile "^6.0.1"
universalify "^2.0.0" universalify "^2.0.0"
fs-extra@^11.1.0, fs-extra@^11.1.1: fs-extra@^11.1.0, fs-extra@^11.1.1, fs-extra@^11.2.0:
version "11.1.1" version "11.2.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b"
integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ== integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==
dependencies: dependencies:
graceful-fs "^4.2.0" graceful-fs "^4.2.0"
jsonfile "^6.0.1" jsonfile "^6.0.1"
@ -15205,16 +15205,7 @@ string-length@^4.0.1:
char-regex "^1.0.2" char-regex "^1.0.2"
strip-ansi "^6.0.0" strip-ansi "^6.0.0"
"string-width-cjs@npm:string-width@^4.2.0": "string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3" version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -15313,14 +15304,7 @@ stringify-object@^3.3.0:
is-obj "^1.0.1" is-obj "^1.0.1"
is-regexp "^1.0.0" is-regexp "^1.0.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1": "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@ -16990,7 +16974,7 @@ workbox-window@7.0.0, workbox-window@^7.0.0:
"@types/trusted-types" "^2.0.2" "@types/trusted-types" "^2.0.2"
workbox-core "7.0.0" workbox-core "7.0.0"
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@ -17008,15 +16992,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0" string-width "^4.1.0"
strip-ansi "^6.0.0" strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
version "8.1.0" version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"