From a096bbc0b9e19e8bed8ac05df8e71e6d359df8eb Mon Sep 17 00:00:00 2001 From: ozaki <29860391+OzakIOne@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:48:44 +0200 Subject: [PATCH] feat(blog): add `onUntruncatedBlogPosts` blog options (#10375) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: OzakIOne Co-authored-by: Sébastien Lorber Co-authored-by: sebastien --- .../src/__tests__/blogUtils.test.ts | 105 ++++++++++++++++++ .../src/__tests__/options.test.ts | 42 +++++++ .../src/blogUtils.ts | 23 ++++ .../src/index.ts | 5 + .../src/options.ts | 4 + .../src/plugin-content-blog.d.ts | 2 + project-words.txt | 2 + website/_dogfooding/dogfooding.config.ts | 1 + .../docs/api/plugins/plugin-content-blog.mdx | 1 + website/docusaurus.config.ts | 4 + 10 files changed, 189 insertions(+) diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts index a340ec61e7..0402406411 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts @@ -5,12 +5,14 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import {fromPartial} from '@total-typescript/shoehorn'; import { truncate, parseBlogFileName, paginateBlogPosts, applyProcessBlogPosts, + reportUntruncatedBlogPosts, } from '../blogUtils'; import type {BlogPost} from '@docusaurus/plugin-content-blog'; @@ -32,6 +34,109 @@ describe('truncate', () => { }); }); +describe('reportUntruncatedBlogPosts', () => { + function testPost({ + source, + hasTruncateMarker, + }: { + source: string; + hasTruncateMarker: boolean; + }): BlogPost { + return fromPartial({ + metadata: { + source, + hasTruncateMarker, + }, + }); + } + + it('throw for untruncated blog posts', () => { + const blogPosts = [ + testPost({source: '@site/blog/post1.md', hasTruncateMarker: false}), + testPost({source: '@site/blog/post2.md', hasTruncateMarker: true}), + testPost({ + source: '@site/blog/subDir/post3.md', + hasTruncateMarker: false, + }), + ]; + expect(() => + reportUntruncatedBlogPosts({blogPosts, onUntruncatedBlogPosts: 'throw'}), + ).toThrowErrorMatchingInlineSnapshot(` + "Docusaurus found blog posts without truncation markers: + - "blog/post1.md" + - "blog/subDir/post3.md" + + We recommend using truncation markers (\`\` or \`{/* truncate */}\`) in blog posts to create shorter previews on blog paginated lists. + Tip: turn this security off with the \`onUntruncatedBlogPosts: 'ignore'\` blog plugin option." + `); + }); + + it('warn for untruncated blog posts', () => { + const consoleMock = jest.spyOn(console, 'warn'); + + const blogPosts = [ + testPost({source: '@site/blog/post1.md', hasTruncateMarker: false}), + testPost({source: '@site/blog/post2.md', hasTruncateMarker: true}), + testPost({ + source: '@site/blog/subDir/post3.md', + hasTruncateMarker: false, + }), + ]; + expect(() => + reportUntruncatedBlogPosts({blogPosts, onUntruncatedBlogPosts: 'warn'}), + ).not.toThrow(); + + expect(consoleMock.mock.calls).toMatchInlineSnapshot(` + [ + [ + "[WARNING] Docusaurus found blog posts without truncation markers: + - "blog/post1.md" + - "blog/subDir/post3.md" + + We recommend using truncation markers (\`\` or \`{/* truncate */}\`) in blog posts to create shorter previews on blog paginated lists. + Tip: turn this security off with the \`onUntruncatedBlogPosts: 'ignore'\` blog plugin option.", + ], + ] + `); + consoleMock.mockRestore(); + }); + + it('ignore untruncated blog posts', () => { + const logMock = jest.spyOn(console, 'log'); + const warnMock = jest.spyOn(console, 'warn'); + const errorMock = jest.spyOn(console, 'error'); + + const blogPosts = [ + testPost({source: '@site/blog/post1.md', hasTruncateMarker: false}), + testPost({source: '@site/blog/post2.md', hasTruncateMarker: true}), + testPost({ + source: '@site/blog/subDir/post3.md', + hasTruncateMarker: false, + }), + ]; + expect(() => + reportUntruncatedBlogPosts({blogPosts, onUntruncatedBlogPosts: 'ignore'}), + ).not.toThrow(); + + expect(logMock).not.toHaveBeenCalled(); + expect(warnMock).not.toHaveBeenCalled(); + expect(errorMock).not.toHaveBeenCalled(); + logMock.mockRestore(); + warnMock.mockRestore(); + errorMock.mockRestore(); + }); + + it('does not throw for truncated posts', () => { + const blogPosts = [ + testPost({source: '@site/blog/post1.md', hasTruncateMarker: true}), + testPost({source: '@site/blog/post2.md', hasTruncateMarker: true}), + ]; + expect(() => + reportUntruncatedBlogPosts({blogPosts, onUntruncatedBlogPosts: 'throw'}), + ).not.toThrow(); + }); +}); + describe('paginateBlogPosts', () => { const blogPosts = [ {id: 'post1', metadata: {}, content: 'Foo 1'}, diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts index 254d56b96b..b2de2306f6 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts @@ -374,4 +374,46 @@ describe('validateOptions', () => { ); }); }); + + describe('onUntruncatedBlogPosts', () => { + it('accepts onUntruncatedBlogPosts - undefined', () => { + expect( + testValidate({onUntruncatedBlogPosts: undefined}) + .onUntruncatedBlogPosts, + ).toBe('warn'); + }); + + it('accepts onUntruncatedBlogPosts - "throw"', () => { + expect( + testValidate({onUntruncatedBlogPosts: 'throw'}).onUntruncatedBlogPosts, + ).toBe('throw'); + }); + + it('rejects onUntruncatedBlogPosts - "trace"', () => { + expect(() => + // @ts-expect-error: test + testValidate({onUntruncatedBlogPosts: 'trace'}), + ).toThrowErrorMatchingInlineSnapshot( + `""onUntruncatedBlogPosts" must be one of [ignore, log, warn, throw]"`, + ); + }); + + it('rejects onUntruncatedBlogPosts - null', () => { + expect(() => + // @ts-expect-error: test + testValidate({onUntruncatedBlogPosts: 42}), + ).toThrowErrorMatchingInlineSnapshot( + `""onUntruncatedBlogPosts" must be one of [ignore, log, warn, throw]"`, + ); + }); + + it('rejects onUntruncatedBlogPosts - 42', () => { + expect(() => + // @ts-expect-error: test + testValidate({onUntruncatedBlogPosts: 42}), + ).toThrowErrorMatchingInlineSnapshot( + `""onUntruncatedBlogPosts" must be one of [ignore, log, warn, throw]"`, + ); + }); + }); }); diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index d26d319c0f..ab7426eac5 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -26,6 +26,7 @@ import { isDraft, readLastUpdateData, normalizeTags, + aliasedSitePathToRelativePath, } from '@docusaurus/utils'; import {getTagsFile} from '@docusaurus/utils-validation'; import {validateBlogPostFrontMatter} from './frontMatter'; @@ -47,6 +48,28 @@ export function truncate(fileString: string, truncateMarker: RegExp): string { return fileString.split(truncateMarker, 1).shift()!; } +export function reportUntruncatedBlogPosts({ + blogPosts, + onUntruncatedBlogPosts, +}: { + blogPosts: BlogPost[]; + onUntruncatedBlogPosts: PluginOptions['onUntruncatedBlogPosts']; +}): void { + const untruncatedBlogPosts = blogPosts.filter( + (p) => !p.metadata.hasTruncateMarker, + ); + if (onUntruncatedBlogPosts !== 'ignore' && untruncatedBlogPosts.length > 0) { + const message = logger.interpolate`Docusaurus found blog posts without truncation markers: +- ${untruncatedBlogPosts + .map((p) => logger.path(aliasedSitePathToRelativePath(p.metadata.source))) + .join('\n- ')} + +We recommend using truncation markers (code=${``} or code=${`{/* truncate */}`}) in blog posts to create shorter previews on blog paginated lists. +Tip: turn this security off with the code=${`onUntruncatedBlogPosts: 'ignore'`} blog plugin option.`; + logger.report(onUntruncatedBlogPosts)(message); + } +} + export function paginateBlogPosts({ blogPosts, basePageUrl, diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts index ea2652c57f..7ae3b5a54c 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.ts +++ b/packages/docusaurus-plugin-content-blog/src/index.ts @@ -28,6 +28,7 @@ import { shouldBeListed, applyProcessBlogPosts, generateBlogPosts, + reportUntruncatedBlogPosts, } from './blogUtils'; import footnoteIDFixer from './remark/footnoteIDFixer'; import {translateContent, getTranslationFiles} from './translations'; @@ -189,6 +190,10 @@ export default async function pluginContentBlog( blogPosts, processBlogPosts: options.processBlogPosts, }); + reportUntruncatedBlogPosts({ + blogPosts, + onUntruncatedBlogPosts: options.onUntruncatedBlogPosts, + }); const listedBlogPosts = blogPosts.filter(shouldBeListed); if (!blogPosts.length) { diff --git a/packages/docusaurus-plugin-content-blog/src/options.ts b/packages/docusaurus-plugin-content-blog/src/options.ts index e9d91d3bf4..11e912b681 100644 --- a/packages/docusaurus-plugin-content-blog/src/options.ts +++ b/packages/docusaurus-plugin-content-blog/src/options.ts @@ -72,6 +72,7 @@ export const DEFAULT_OPTIONS: PluginOptions = { tags: undefined, authorsBasePath: 'authors', onInlineAuthors: 'warn', + onUntruncatedBlogPosts: 'warn', }; export const XSLTBuiltInPaths = { @@ -240,6 +241,9 @@ const PluginOptionSchema = Joi.object({ onInlineAuthors: Joi.string() .equal('ignore', 'log', 'warn', 'throw') .default(DEFAULT_OPTIONS.onInlineAuthors), + onUntruncatedBlogPosts: Joi.string() + .equal('ignore', 'log', 'warn', 'throw') + .default(DEFAULT_OPTIONS.onUntruncatedBlogPosts), }).default(DEFAULT_OPTIONS); export function validateOptions({ diff --git a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts index 02e98f0b1e..578688a459 100644 --- a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts +++ b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts @@ -521,6 +521,8 @@ declare module '@docusaurus/plugin-content-blog' { authorsBasePath: string; /** The behavior of Docusaurus when it finds inline authors. */ onInlineAuthors: 'ignore' | 'log' | 'warn' | 'throw'; + /** The behavior of Docusaurus when it finds untruncated blog posts. */ + onUntruncatedBlogPosts: 'ignore' | 'log' | 'warn' | 'throw'; }; export type UserFeedXSLTOptions = diff --git a/project-words.txt b/project-words.txt index 528d635318..d8bda0b3be 100644 --- a/project-words.txt +++ b/project-words.txt @@ -391,6 +391,8 @@ unlocalized Unlocalized unnormalized unswizzle +untruncated +Untruncated upvotes urlset Vannicatte diff --git a/website/_dogfooding/dogfooding.config.ts b/website/_dogfooding/dogfooding.config.ts index d31bce0776..94b142cae2 100644 --- a/website/_dogfooding/dogfooding.config.ts +++ b/website/_dogfooding/dogfooding.config.ts @@ -99,6 +99,7 @@ export const dogfoodingPluginInstances: PluginConfig[] = [ : defaultReadingTime({content, options: {wordsPerMinute: 5}}), onInlineTags: 'warn', onInlineAuthors: 'ignore', + onUntruncatedBlogPosts: 'ignore', tags: 'tags.yml', } satisfies BlogOptions, ], diff --git a/website/docs/api/plugins/plugin-content-blog.mdx b/website/docs/api/plugins/plugin-content-blog.mdx index 9b2d50d96d..50a268c2fd 100644 --- a/website/docs/api/plugins/plugin-content-blog.mdx +++ b/website/docs/api/plugins/plugin-content-blog.mdx @@ -85,6 +85,7 @@ Accepted fields: | `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`). | +| `onUntruncatedBlogPosts` | `'ignore' \| 'log' \| 'warn' \| 'throw'` | `warn` | The plugin behavior when blog posts do not contain a truncate marker. | ```mdx-code-block diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 26a3e265f0..8668adef3a 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -496,6 +496,10 @@ export default async function createConfigAsync() { blogDescription: 'Read blog posts about Docusaurus from the team', blogSidebarCount: 'ALL', blogSidebarTitle: 'All our posts', + onUntruncatedBlogPosts: + process.env.DOCUSAURUS_CURRENT_LOCALE !== defaultLocale + ? 'warn' + : 'throw', onInlineTags: process.env.DOCUSAURUS_CURRENT_LOCALE !== defaultLocale ? 'warn'