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:
- name: Yangshun Tay
- slorber
tags: [birthday]
tags: [birthday,inlineTag,globalTag]
---
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:
- name: Yangshun Tay (translated)
- slorber
tags: [inlineTag,globalTag]
---
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`] = `
[
"<?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>
<email>lorber.sebastien@gmail.com</email>
</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>
</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`] = `
[
"{
@ -595,7 +503,109 @@ exports[`json has feed item for each post - with trailing slash 1`] = `
"author": {
"name": "Yangshun Tay (translated)"
},
"tags": [
"inlineTag",
"Global Tag label (en)"
]
}
]
}",
]
`;
exports[`json has feed item for each post 1`] = `
[
"{
"version": "https://jsonfeed.org/version/1",
"title": "Hello Blog",
"home_page_url": "https://docusaurus.io/myBaseUrl/blog",
"description": "Hello Blog",
"items": [
{
"id": "https://docusaurus.io/myBaseUrl/blog/blog-with-links",
"content_html": "<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": [
"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`] = `
[
"<?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>
<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>
</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>
</channel>
</rss>",

View file

@ -3,6 +3,8 @@
exports[`blog plugin process blog posts load content 1`] = `
{
"/blog/tags/tag-1": {
"description": undefined,
"inline": true,
"items": [
"/simple/slug/another",
"/another/tags",
@ -63,6 +65,8 @@ exports[`blog plugin process blog posts load content 1`] = `
"unlisted": false,
},
"/blog/tags/tag-2": {
"description": undefined,
"inline": true,
"items": [
"/another/tags",
"/another/tags2",
@ -148,6 +152,8 @@ exports[`blog plugin process blog posts load content 2`] = `
"source": "@site/blog/another-simple-slug-with-tags.md",
"tags": [
{
"description": undefined,
"inline": true,
"label": "tag1",
"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",
"tags": [
{
"description": undefined,
"inline": true,
"label": "tag1",
"permalink": "/blog/tags/tag-1",
},
{
"description": undefined,
"inline": true,
"label": "tag2",
"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",
"tags": [
{
"description": undefined,
"inline": true,
"label": "tag1",
"permalink": "/blog/tags/tag-1",
},
{
"description": undefined,
"inline": true,
"label": "tag2",
"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`] = `
{
"/blog/tags/tag-1": {
"description": undefined,
"inline": true,
"items": [
"/simple/slug/another",
"/another/tags",
@ -278,6 +294,8 @@ exports[`blog plugin works on blog tags without pagination 1`] = `
"unlisted": false,
},
"/blog/tags/tag-2": {
"description": undefined,
"inline": true,
"items": [
"/another/tags",
"/another/tags2",
@ -306,6 +324,8 @@ exports[`blog plugin works on blog tags without pagination 1`] = `
"unlisted": false,
},
"/blog/tags/unlisted": {
"description": undefined,
"inline": true,
"items": [
"/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`] = `
{
"/blog/tags/tag-1": {
"description": undefined,
"inline": true,
"items": [
"/simple/slug/another",
"/another/tags",
@ -382,6 +404,8 @@ exports[`blog plugin works with blog tags 1`] = `
"unlisted": false,
},
"/blog/tags/tag-2": {
"description": undefined,
"inline": true,
"items": [
"/another/tags",
"/another/tags2",
@ -410,6 +434,8 @@ exports[`blog plugin works with blog tags 1`] = `
"unlisted": false,
},
"/blog/tags/unlisted": {
"description": undefined,
"inline": true,
"items": [
"/another/blog-with-tags-unlisted",
],

View file

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

View file

@ -160,6 +160,8 @@ describe('blog plugin', () => {
);
expect(relativePathsToWatch).toEqual([
'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}',
'blog/**/*.{md,mdx}',
]);
@ -188,6 +190,8 @@ describe('blog plugin', () => {
prevItem: undefined,
tags: [
{
description: undefined,
inline: true,
label: 'date',
permalink: '/blog/tags/date',
},
@ -232,9 +236,23 @@ describe('blog plugin', () => {
},
'slorber',
],
tags: ['inlineTag', 'globalTag'],
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: {
permalink: '/blog/date-matter',
title: 'date-matter',
@ -269,10 +287,14 @@ describe('blog plugin', () => {
},
tags: [
{
description: undefined,
inline: true,
label: 'date',
permalink: '/blog/tags/date',
},
{
description: undefined,
inline: true,
label: 'complex',
permalink: '/blog/tags/complex',
},
@ -516,6 +538,8 @@ describe('blog plugin', () => {
postsPerPage: 1,
processBlogPosts: async ({blogPosts}) =>
blogPosts.filter((blog) => blog.metadata.tags[0]?.label === 'tag1'),
onInlineTags: 'ignore',
tags: false,
},
DefaultI18N,
);

View file

@ -36,13 +36,15 @@ describe('validateOptions', () => {
});
it('accepts correctly defined user options', () => {
const userOptions = {
const userOptions: Options = {
...defaultOptions,
feedOptions: {type: 'rss' as const, title: 'myTitle'},
path: 'not_blog',
routeBasePath: '/myBlog',
postsPerPage: 5,
include: ['api/*', 'docs/*'],
tags: 'customTags.yml',
onInlineTags: 'warn',
};
expect(testValidate(userOptions)).toEqual({
...userOptions,
@ -172,4 +174,68 @@ describe('validateOptions', () => {
`""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,
posixPath,
Globby,
normalizeFrontMatterTags,
groupTaggedItems,
getTagVisibility,
getFileCommitDate,
@ -26,9 +25,12 @@ import {
isUnlisted,
isDraft,
readLastUpdateData,
normalizeTags,
} from '@docusaurus/utils';
import {getTagsFile} from '@docusaurus/utils-validation';
import {validateBlogPostFrontMatter} from './frontMatter';
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
import type {TagsFile} from '@docusaurus/utils';
import type {LoadContext, ParseFrontMatter} from '@docusaurus/types';
import type {
PluginOptions,
@ -125,9 +127,11 @@ export function getBlogTags({
isUnlisted: (item) => item.metadata.unlisted,
});
return {
inline: tag.inline,
label: tag.label,
items: tagVisibility.listedItems.map((item) => item.id),
permalink: tag.permalink,
description: tag.description,
items: tagVisibility.listedItems.map((item) => item.id),
pages: paginateBlogPosts({
blogPosts: tagVisibility.listedItems,
basePageUrl: tag.permalink,
@ -197,6 +201,7 @@ async function processBlogSourceFile(
contentPaths: BlogContentPaths,
context: LoadContext,
options: PluginOptions,
tagsFile: TagsFile | null,
authorsMap?: AuthorsMap,
): Promise<BlogPost | undefined> {
const {
@ -315,13 +320,21 @@ async function processBlogSourceFile(
return undefined;
}
const tagsBasePath = normalizeUrl([
const tagsBaseRoutePath = normalizeUrl([
baseUrl,
routeBasePath,
tagsRouteBasePath,
]);
const authors = getBlogPostAuthors({authorsMap, frontMatter, baseUrl});
const tags = normalizeTags({
options,
source: blogSourceRelative,
frontMatterTags: frontMatter.tags,
tagsBaseRoutePath,
tagsFile,
});
return {
id: slug,
metadata: {
@ -331,7 +344,7 @@ async function processBlogSourceFile(
title,
description,
date,
tags: normalizeFrontMatterTags(tagsBasePath, frontMatter.tags),
tags,
readingTime: showReadingTime
? options.readingTime({
content,
@ -371,6 +384,8 @@ export async function generateBlogPosts(
authorsMapPath: options.authorsMapPath,
});
const tagsFile = await getTagsFile({contentPaths, tags: options.tags});
async function doProcessBlogSourceFile(blogSourceFile: string) {
try {
return await processBlogSourceFile(
@ -378,6 +393,7 @@ export async function generateBlogPosts(
contentPaths,
context,
options,
tagsFile,
authorsMap,
);
} catch (err) {

View file

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

View file

@ -54,6 +54,8 @@ export const DEFAULT_OPTIONS: PluginOptions = {
showLastUpdateTime: false,
showLastUpdateAuthor: false,
processBlogPosts: async () => undefined,
onInlineTags: 'warn',
tags: undefined,
};
const PluginOptionSchema = Joi.object<PluginOptions>({
@ -144,6 +146,13 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
processBlogPosts: Joi.function()
.optional()
.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);
export function validateOptions({

View file

@ -4,7 +4,6 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/// <reference types="@docusaurus/module-type-aliases" />
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 {
FrontMatterTag,
Tag,
TagMetadata,
LastUpdateData,
FrontMatterLastUpdate,
TagsPluginOptions,
} from '@docusaurus/utils';
import type {DocusaurusConfig, Plugin, LoadContext} from '@docusaurus/types';
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. */
readonly frontMatter: BlogPostFrontMatter & {[key: string]: unknown};
/** Tags, normalized. */
readonly tags: Tag[];
readonly tags: TagMetadata[];
/**
* 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.
*/
export type PluginOptions = MDXOptions & {
/** Plugin ID. */
id?: string;
/**
* Path to the blog content directory on the file system, relative to site
* directory.
*/
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.
*/
routeBasePath: string;
/**
* URL route for the tags section of your blog. Will be appended to
* `routeBasePath`.
*/
tagsBasePath: string;
/**
* URL route for the pages section of your blog. Will be appended to
* `routeBasePath`.
*/
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
* disable generation of archive.
*/
archiveBasePath: string | null;
/**
* Array of glob patterns matching Markdown files to be built, relative to
* the content path.
*/
include: string[];
/**
* Array of glob patterns matching Markdown files to be excluded. Serves as
* refinement based on the `include` option.
*/
exclude: string[];
/**
* 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. */
blogListComponent: string;
/** Root component of each blog post page. */
blogPostComponent: string;
/** Root component of the tags list page. */
blogTagsListComponent: string;
/** Root component of the "posts containing tag" page. */
blogTagsPostsComponent: string;
/** Root component of the blog archive page. */
blogArchiveComponent: string;
/** Blog page title for better SEO. */
blogTitle: 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.
*/
blogSidebarCount: number | 'ALL';
/** Title of the blog sidebar. */
blogSidebarTitle: string;
/** Truncate marker marking where the summary ends. */
truncateMarker: RegExp;
/** Show estimated reading time for the blog post. */
showReadingTime: boolean;
/** 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
* file. Omitting this variable entirely will disable edit links.
*/
editUrl?: string | EditUrlFunction;
/**
* 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. */
authorsMapPath: string;
/** A callback to customize the reading time number displayed. */
readingTime: ReadingTimeFunctionOption;
/** Governs the direction of blog post sorting. */
sortPosts: 'ascending' | 'descending';
/** Whether to display the last date the doc was updated. */
showLastUpdateTime: boolean;
/** Whether to display the author who last updated the doc. */
showLastUpdateAuthor: boolean;
/** An optional function which can be used to transform blog posts
* (filter, modify, delete, etc...).
*/
processBlogPosts: ProcessBlogPostsFn;
};
export type PluginOptions = MDXOptions &
TagsPluginOptions & {
/** Plugin ID. */
id?: string;
/**
* Path to the blog content directory on the file system, relative to site
* directory.
*/
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.
*/
routeBasePath: string;
/**
* URL route for the tags section of your blog. Will be appended to
* `routeBasePath`.
*/
tagsBasePath: string;
/**
* URL route for the pages section of your blog. Will be appended to
* `routeBasePath`.
*/
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
* disable generation of archive.
*/
archiveBasePath: string | null;
/**
* Array of glob patterns matching Markdown files to be built, relative to
* the content path.
*/
include: string[];
/**
* Array of glob patterns matching Markdown files to be excluded. Serves as
* refinement based on the `include` option.
*/
exclude: string[];
/**
* 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. */
blogListComponent: string;
/** Root component of each blog post page. */
blogPostComponent: string;
/** Root component of the tags list page. */
blogTagsListComponent: string;
/** Root component of the "posts containing tag" page. */
blogTagsPostsComponent: string;
/** Root component of the blog archive page. */
blogArchiveComponent: string;
/** Blog page title for better SEO. */
blogTitle: 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.
*/
blogSidebarCount: number | 'ALL';
/** Title of the blog sidebar. */
blogSidebarTitle: string;
/** Truncate marker marking where the summary ends. */
truncateMarker: RegExp;
/** Show estimated reading time for the blog post. */
showReadingTime: boolean;
/** 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
* file. Omitting this variable entirely will disable edit links.
*/
editUrl?: string | EditUrlFunction;
/**
* 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. */
authorsMapPath: string;
/** A callback to customize the reading time number displayed. */
readingTime: ReadingTimeFunctionOption;
/** Governs the direction of blog post sorting. */
sortPosts: 'ascending' | 'descending';
/** Whether to display the last date the doc was updated. */
showLastUpdateTime: boolean;
/** Whether to display the author who last updated the doc. */
showLastUpdateAuthor: boolean;
/** An optional function which can be used to transform blog posts
* (filter, modify, delete, etc...).
*/
processBlogPosts: ProcessBlogPostsFn;
};
/**
* 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;
};
export type BlogTag = Tag & {
export type BlogTag = TagMetadata & {
/** Blog post permalinks. */
items: string[];
pages: BlogPaginated[];

View file

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

View file

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

View file

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

View file

@ -70,6 +70,8 @@ describe('normalizeDocsPluginOptions', () => {
disableVersioning: true,
editCurrentVersion: true,
editLocalizedFiles: true,
tags: 'docsTags.yml',
onInlineTags: 'throw',
versions: {
current: {
path: 'next',
@ -266,4 +268,68 @@ describe('normalizeDocsPluginOptions', () => {
}).sidebarCollapsed,
).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,
posixPath,
Globby,
normalizeFrontMatterTags,
isUnlisted,
isDraft,
readLastUpdateData,
normalizeTags,
} from '@docusaurus/utils';
import {validateDocFrontMatter} from './frontMatter';
import getSlug from './slug';
import {stripPathNumberPrefixes} from './numberPrefix';
import {toDocNavigationLink, toNavigationLink} from './sidebars/utils';
import type {TagsFile} from '@docusaurus/utils';
import type {
MetadataOptions,
PluginOptions,
@ -82,12 +83,14 @@ async function doProcessDocMetadata({
context,
options,
env,
tagsFile,
}: {
docFile: DocFile;
versionMetadata: VersionMetadata;
context: LoadContext;
options: MetadataOptions;
env: DocEnv;
tagsFile: TagsFile | null;
}): Promise<DocMetadataBase> {
const {source, content, contentPath, filePath} = docFile;
const {
@ -206,6 +209,14 @@ async function doProcessDocMetadata({
const draft = isDraft({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
// NodeJS optimization.
// Adding properties to object after instantiation will cause hidden
@ -221,7 +232,7 @@ async function doProcessDocMetadata({
draft,
unlisted,
editUrl: customEditURL !== undefined ? customEditURL : getDocEditUrl(),
tags: normalizeFrontMatterTags(versionMetadata.tagsPath, frontMatter.tags),
tags,
version: versionMetadata.versionName,
lastUpdatedBy: lastUpdate.lastUpdatedBy,
lastUpdatedAt: lastUpdate.lastUpdatedAt,
@ -236,6 +247,7 @@ export async function processDocMetadata(args: {
context: LoadContext;
options: MetadataOptions;
env: DocEnv;
tagsFile: TagsFile | null;
}): Promise<DocMetadataBase> {
try {
return await doProcessDocMetadata(args);

View file

@ -20,6 +20,10 @@ import {
resolveMarkdownLinkPathname,
DEFAULT_PLUGIN_ID,
} from '@docusaurus/utils';
import {
getTagsFile,
getTagsFilePathsToWatch,
} from '@docusaurus/utils-validation';
import {loadSidebars, resolveSidebarPathOption} from './sidebars';
import {CategoryMetadataFilenamePattern} from './sidebars/generator';
import {
@ -43,6 +47,7 @@ import {
} from './translations';
import {createAllRoutes} from './routes';
import {createSidebarsUtils} from './sidebars/utils';
import type {TagsFile} from '@docusaurus/utils';
import type {Options as MDXLoaderOptions} from '@docusaurus/mdx-loader';
import type {
@ -119,6 +124,10 @@ export default async function pluginContentDocs(
(docsDirPath) => `${docsDirPath}/${pattern}`,
),
),
...getTagsFilePathsToWatch({
contentPaths: version,
tags: options.tags,
}),
`${version.contentPath}/**/${CategoryMetadataFilenamePattern}`,
];
if (typeof version.sidebarFilePath === 'string') {
@ -133,6 +142,7 @@ export default async function pluginContentDocs(
async loadContent() {
async function loadVersionDocsBase(
versionMetadata: VersionMetadata,
tagsFile: TagsFile | null,
): Promise<DocMetadataBase[]> {
const docFiles = await readVersionDocs(versionMetadata, options);
if (docFiles.length === 0) {
@ -152,6 +162,7 @@ export default async function pluginContentDocs(
context,
options,
env,
tagsFile,
});
}
return Promise.all(docFiles.map(processVersionDoc));
@ -160,8 +171,14 @@ export default async function pluginContentDocs(
async function doLoadVersion(
versionMetadata: VersionMetadata,
): Promise<LoadedVersion> {
const tagsFile = await getTagsFile({
contentPaths: versionMetadata,
tags: options.tags,
});
const docsBase: DocMetadataBase[] = await loadVersionDocsBase(
versionMetadata,
tagsFile,
);
// 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,
sidebarCollapsed: true,
breadcrumbs: true,
onInlineTags: 'warn',
tags: undefined,
};
const VersionOptionsSchema = Joi.object({
@ -140,6 +142,13 @@ const OptionsSchema = Joi.object<PluginOptions>({
lastVersion: Joi.string().optional(),
versions: VersionsOptionsSchema,
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({

View file

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

View file

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

View file

@ -11,19 +11,20 @@ import {
docuHash,
normalizeUrl,
aliasedSitePathToRelativePath,
groupTaggedItems,
getTagVisibility,
} from '@docusaurus/utils';
import {
toTagDocListProp,
toTagsListTagsProp,
toVersionMetadataProp,
} from './props';
import {getVersionTags} from './tags';
import type {
PluginContentLoadedActions,
RouteConfig,
RouteMetadata,
} from '@docusaurus/types';
import type {FullVersion, VersionTag} from './types';
import type {FullVersion, VersionTag, VersionTags} from './types';
import type {
CategoryGeneratedIndexMetadata,
DocMetadata,
@ -112,6 +113,23 @@ async function buildVersionSidebarRoute(param: BuildVersionRoutesParam) {
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(
param: BuildVersionRoutesParam,
@ -120,8 +138,9 @@ async function buildVersionTagsRoutes(
const versionTags = getVersionTags(version.docs);
async function buildTagsListRoute(): Promise<RouteConfig | null> {
const tags = toTagsListTagsProp(versionTags);
// 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 {
@ -129,7 +148,7 @@ async function buildVersionTagsRoutes(
exact: true,
component: options.docTagsListComponent,
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.
*/
import type {Tag} from '@docusaurus/utils';
import type {TagMetadata} from '@docusaurus/utils';
import type {
LoadedVersion,
CategoryGeneratedIndexMetadata,
@ -23,7 +23,7 @@ export type SourceToPermalink = {
[source: string]: string;
};
export type VersionTag = Tag & {
export type VersionTag = TagMetadata & {
/** All doc ids having this tag. */
docIds: string[];
unlisted: boolean;

View file

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

View file

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

View file

@ -12,10 +12,16 @@ import type {Props} from '@theme/Tag';
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 (
<Link
href={permalink}
title={description}
className={clsx(
styles.tag,
count ? styles.tagWithCount : styles.tagRegular,

View file

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

View file

@ -21,10 +21,15 @@
"@docusaurus/logger": "3.3.2",
"@docusaurus/utils": "3.3.2",
"@docusaurus/utils-common": "3.3.2",
"fs-extra": "^11.2.0",
"joi": "^17.9.2",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"tslib": "^2.6.0"
},
"devDependencies": {
"tmp-promise": "^3.0.3"
},
"engines": {
"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,
FrontMatterLastUpdateSchema,
} 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.
*/
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 Joi from './Joi';
import {JoiFrontMatter} from './JoiFrontMatter';
@ -113,7 +117,9 @@ export const RouteBasePathSchema = Joi
const FrontMatterTagSchema = JoiFrontMatter.alternatives()
.try(
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(),
permalink: JoiFrontMatter.string().required(),
}).required(),

View file

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

View file

@ -6,117 +6,284 @@
*/
import {
normalizeFrontMatterTags,
reportInlineTags,
groupTaggedItems,
type Tag,
getTagVisibility,
} from '../tags';
} from '@docusaurus/utils';
import {normalizeTag} from '../tags';
import type {Tag, TagMetadata, FrontMatterTag, TagsFile} from '../tags';
describe('normalizeFrontMatterTags', () => {
it('normalizes simple string tag', () => {
const tagsPath = '/all/tags';
const input = 'tag';
const expectedOutput = {
label: 'tag',
permalink: `${tagsPath}/tag`,
describe('normalizeTag', () => {
const tagsBaseRoutePath = '/all/tags';
describe('inline', () => {
it('normalizes simple string tag', () => {
const input: FrontMatterTag = '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', () => {
const tagsPath = '/all/tags';
const input = 'some more Complex_tag';
const expectedOutput = {
label: 'some more Complex_tag',
permalink: `${tagsPath}/some-more-complex-tag`,
};
expect(normalizeFrontMatterTags(tagsPath, [input])).toEqual([
expectedOutput,
]);
it('warn when docs has invalid tags', () => {
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
reportInlineTags({
tags: [
{
label: 'hello',
permalink: 'hello',
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', () => {
const tagsPath = '/all/tags';
const input = {label: 'tag', permalink: 'tagPermalink'};
const expectedOutput = {
label: 'tag',
permalink: `${tagsPath}/tagPermalink`,
};
expect(normalizeFrontMatterTags(tagsPath, [input])).toEqual([
expectedOutput,
]);
it('ignore when docs has invalid tags', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
reportInlineTags({
tags: [
{
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', () => {
const tagsPath = '/all/tags';
const input = {
label: 'tag complex Label',
permalink: '/MoreComplex/Permalink',
};
const expectedOutput = {
label: 'tag complex Label',
permalink: `${tagsPath}/MoreComplex/Permalink`,
};
expect(normalizeFrontMatterTags(tagsPath, [input])).toEqual([
expectedOutput,
]);
it('throw for unknown string and object tag', () => {
const frontmatter = ['open', 'world'];
const tags = frontmatter.map((tag) =>
normalizeTag({
tagsBaseRoutePath: '/tags',
tagsFile,
tag,
}),
);
const testFn = () =>
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];
type Output = ReturnType<typeof normalizeFrontMatterTags>;
it('normalizes string list', () => {
const tagsPath = '/all/tags';
const input: Input = ['tag 1', 'tag-1', 'tag 3', 'tag1', 'tag-2'];
// 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-2',
permalink: `${tagsPath}/tag-2`,
},
];
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);
it('does not throw when docs has valid tags', () => {
const frontmatter = ['open'];
const tags = frontmatter.map((tag) =>
normalizeTag({
tagsBaseRoutePath: '/tags',
tagsFile,
tag,
}),
);
const testFn = () =>
reportInlineTags({
tags,
source: 'wrong.md',
options: {
onInlineTags: 'throw',
tags: 'tags.yml',
},
});
expect(testFn).not.toThrow();
});
});
@ -135,14 +302,23 @@ describe('groupTaggedItems', () => {
type Output = ReturnType<typeof groupItems>;
it('groups items by tag permalink', () => {
const tagGuide = {label: 'Guide', permalink: '/guide'};
const tagTutorial = {label: 'Tutorial', permalink: '/tutorial'};
const tagAPI = {label: 'API', permalink: '/api'};
const tagGuide = {
label: 'Guide',
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
const tagTutorialOtherLabel = {
label: 'TutorialOtherLabel',
permalink: '/tutorial',
description: undefined,
};
const item1: SomeTaggedItem = {

View file

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

View file

@ -6,13 +6,34 @@
*/
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {normalizeUrl} from './urlUtils';
import type {Optional} from 'utility-types';
/** What the user configures. */
export type Tag = {
/** The display label of a tag */
label: string;
/** Permalink to this tag's page, without the `/tags/` base path. */
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. */
@ -29,62 +50,126 @@ export type TagModule = TagsListItem & {
unlisted: boolean;
};
export type FrontMatterTag = string | Tag;
export type FrontMatterTag = string | Optional<Tag, 'description'>;
function normalizeFrontMatterTag(
tagsPath: string,
// We always apply tagsBaseRoutePath on purpose. For versioned docs, v1/doc.md
// 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,
): Tag {
function toTagObject(tagString: string): Tag {
): TagMetadata {
function toTagObject(tagString: string): TagMetadata {
return {
inline: true,
label: 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 =
typeof frontMatterTag === 'string'
? toTagObject(frontMatterTag)
: frontMatterTag;
: {...frontMatterTag, description: frontMatterTag.description};
return {
inline: true,
label: tag.label,
permalink: normalizeTagPermalink(tag.permalink),
permalink: normalizeTagPermalink({
permalink: tag.permalink,
tagsBaseRoutePath,
}),
description: tag.description,
};
}
/**
* Takes tag objects as they are defined in front matter, and normalizes each
* into a standard tag object. The permalink is created by appending the
* sluggified label to `tagsPath`. Front matter tags already containing
* permalinks would still have `tagsPath` prepended.
*
* The result will always be unique by permalinks. The behavior with colliding
* permalinks is undetermined.
*/
export function normalizeFrontMatterTags(
/** Base path to append the tag permalinks to. */
tagsPath: string,
/** Can be `undefined`, so that we can directly pipe in `frontMatter.tags`. */
frontMatterTags: FrontMatterTag[] | undefined = [],
): Tag[] {
const tags = frontMatterTags.map((tag) =>
normalizeFrontMatterTag(tagsPath, tag),
);
export function normalizeTag({
tag,
tagsFile,
tagsBaseRoutePath,
}: {
tag: FrontMatterTag;
tagsBaseRoutePath: string;
tagsFile: TagsFile | null;
}): TagMetadata {
if (typeof tag === 'string') {
const tagDescription = tagsFile?.[tag];
if (tagDescription) {
// pre-defined tag from tags.yml
return {
inline: false,
label: tagDescription.label,
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> = {
tag: Tag;
tag: TagMetadata;
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
* simply getting it from some metadata of the current item.
*/
getItemTags: (item: Item) => readonly Tag[],
getItemTags: (item: Item) => readonly TagMetadata[],
): {[permalink: string]: TaggedItemGroup<Item>} {
const result: {[permalink: string]: TaggedItemGroup<Item>} = {};

View file

@ -123,6 +123,7 @@ héllô
hideable
Hideable
hola
Hola
Hostman
hoverable
Husain
@ -320,7 +321,6 @@ showinfo
Sida
Simen
slorber
sluggified
sluggifies
sluggify
solana
@ -353,6 +353,7 @@ Supabase
svgr
SVGR
swizzlable
Tagkey
Teik
templating
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: /
tags: [a, b, c, some tag]
tags: [a, b, c]
unlisted: true # Makes the navbar link disappear in prod
id: index
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

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,
},
},
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
path: '_dogfooding/_docs tests',
@ -81,6 +83,8 @@ export const dogfoodingPluginInstances: PluginConfig[] = [
frontMatter.hide_reading_time
? undefined
: defaultReadingTime({content, options: {wordsPerMinute: 5}}),
onInlineTags: 'warn',
tags: 'tags.yml',
} 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...). |
| `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`. |
| `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
</APITable>
@ -224,7 +226,7 @@ Accepted fields:
| `author_title` | `string` | `undefined` | ⚠️ Prefer using `authors`. A description of the author. |
| `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. |
| `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. |
| `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. |
@ -272,7 +274,7 @@ authors:
title: Co-creator of Docusaurus 1
url: https://github.com/JoelMarcey
image_url: https://github.com/JoelMarcey.png
tags: [hello, docusaurus-v2]
tags: [docusaurus]
description: This is my first post on Docusaurus.
image: https://i.imgur.com/mErPwqL.png
hide_table_of_contents: false
@ -281,6 +283,10 @@ hide_table_of_contents: false
A Markdown blog post
```
import TagsFileApiRefSection from './_partial-tags-file-api-ref-section.mdx';
<TagsFileApiRefSection />
## i18n {#i18n}
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. |
| `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. |
| `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
</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. |
| `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: /`. |
| `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. |
| `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). |
@ -324,6 +326,7 @@ description: How do I find you when I cannot solve this problem
keywords:
- docs
- docusaurus
tags: [docusaurus]
image: https://i.imgur.com/mErPwqL.png
slug: /myDoc
last_update:
@ -336,6 +339,10 @@ last_update:
My Document Markdown content
```
import TagsFileApiRefSection from './_partial-tags-file-api-ref-section.mdx';
<TagsFileApiRefSection />
## i18n {#i18n}
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}
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}
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:
- Demo
- Getting started
- Releases
- docusaurus
---
# Title
Content
```
```yml title="docs/tags.yml"
docusaurus:
label: 'Docusaurus'
permalink: '/docusaurus'
description: 'Docs related to the Docusaurus framework'
```
:::tip

View file

@ -492,6 +492,7 @@ export default async function createConfigAsync() {
blogDescription: 'Read blog posts about Docusaurus from the team',
blogSidebarCount: 'ALL',
blogSidebarTitle: 'All our posts',
onInlineTags: 'throw',
} satisfies BlogOptions,
pages: {
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"
universalify "^2.0.0"
fs-extra@^11.1.0, fs-extra@^11.1.1:
version "11.1.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d"
integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==
fs-extra@^11.1.0, fs-extra@^11.1.1, fs-extra@^11.2.0:
version "11.2.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b"
integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
@ -15205,16 +15205,7 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
"string-width-cjs@npm:string-width@^4.2.0":
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:
"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==
@ -15313,14 +15304,7 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
"strip-ansi-cjs@npm: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:
"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==
@ -16990,7 +16974,7 @@ workbox-window@7.0.0, workbox-window@^7.0.0:
"@types/trusted-types" "^2.0.2"
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"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@ -17008,15 +16992,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.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:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"