From 5ba18f32f8fc82dd894e13b8e00849a498539784 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:21:46 +0100 Subject: [PATCH 01/43] feat(blog): add LastUpdateAuthor & LastUpdateTime --- .../src/blogUtils.ts | 57 +++++++++++++++++++ .../src/frontMatter.ts | 12 ++++ .../src/lastUpdate.ts | 52 +++++++++++++++++ .../src/options.ts | 6 ++ .../src/plugin-content-blog.d.ts | 23 +++++++- .../theme/BlogPostItem/Header/Info/index.tsx | 9 ++- website/docusaurus.config.ts | 2 + 7 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 packages/docusaurus-plugin-content-blog/src/lastUpdate.ts diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index e0c6de3b06fc..3412a7abd5a4 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -29,6 +29,7 @@ import { } from '@docusaurus/utils'; import {validateBlogPostFrontMatter} from './frontMatter'; import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors'; +import {getFileLastUpdate} from './lastUpdate'; import type {LoadContext, ParseFrontMatter} from '@docusaurus/types'; import type { PluginOptions, @@ -36,6 +37,8 @@ import type { BlogPost, BlogTags, BlogPaginated, + FileChange, + LastUpdateData, } from '@docusaurus/plugin-content-blog'; import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types'; @@ -51,6 +54,52 @@ export function getSourceToPermalink(blogPosts: BlogPost[]): { ); } +type LastUpdateOptions = Pick< + PluginOptions, + 'showLastUpdateAuthor' | 'showLastUpdateTime' +>; + +async function readLastUpdateData( + filePath: string, + options: LastUpdateOptions, + lastUpdateFrontMatter: FileChange | undefined, +): Promise { + const {showLastUpdateAuthor, showLastUpdateTime} = options; + if (showLastUpdateAuthor || showLastUpdateTime) { + const frontMatterTimestamp = lastUpdateFrontMatter?.date + ? new Date(lastUpdateFrontMatter.date).getTime() / 1000 + : undefined; + + if (lastUpdateFrontMatter?.author && lastUpdateFrontMatter.date) { + return { + lastUpdatedAt: frontMatterTimestamp, + lastUpdatedBy: lastUpdateFrontMatter.author, + }; + } + + // Use fake data in dev for faster development. + const fileLastUpdateData = + process.env.NODE_ENV === 'production' + ? await getFileLastUpdate(filePath) + : { + author: 'Author', + timestamp: 1539502055, + }; + const {author, timestamp} = fileLastUpdateData ?? {}; + + return { + lastUpdatedBy: showLastUpdateAuthor + ? lastUpdateFrontMatter?.author ?? author + : undefined, + lastUpdatedAt: showLastUpdateTime + ? frontMatterTimestamp ?? timestamp + : undefined, + }; + } + + return {}; +} + export function paginateBlogPosts({ blogPosts, basePageUrl, @@ -231,6 +280,12 @@ async function processBlogSourceFile( const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir); + const lastUpdate = await readLastUpdateData( + blogSourceAbsolute, + options, + frontMatter.last_update, + ); + const draft = isDraft({frontMatter}); const unlisted = isUnlisted({frontMatter}); @@ -337,6 +392,8 @@ async function processBlogSourceFile( authors, frontMatter, unlisted, + lastUpdatedAt: lastUpdate.lastUpdatedAt, + lastUpdatedBy: lastUpdate.lastUpdatedBy, }, content, }; diff --git a/packages/docusaurus-plugin-content-blog/src/frontMatter.ts b/packages/docusaurus-plugin-content-blog/src/frontMatter.ts index 73b4d37d25ee..b7649bea41e1 100644 --- a/packages/docusaurus-plugin-content-blog/src/frontMatter.ts +++ b/packages/docusaurus-plugin-content-blog/src/frontMatter.ts @@ -28,6 +28,9 @@ const BlogPostFrontMatterAuthorSchema = Joi.object({ const FrontMatterAuthorErrorMessage = '{{#label}} does not look like a valid blog post author. Please use an author key or an author object (with a key and/or name).'; +const FrontMatterLastUpdateErrorMessage = + '{{#label}} does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).'; + const BlogFrontMatterSchema = Joi.object({ id: Joi.string(), title: Joi.string().allow(''), @@ -69,6 +72,15 @@ const BlogFrontMatterSchema = Joi.object({ hide_table_of_contents: Joi.boolean(), ...FrontMatterTOCHeadingLevels, + last_update: Joi.object({ + author: Joi.string(), + date: Joi.date().raw(), + }) + .or('author', 'date') + .messages({ + 'object.missing': FrontMatterLastUpdateErrorMessage, + 'object.base': FrontMatterLastUpdateErrorMessage, + }), }) .messages({ 'deprecate.error': diff --git a/packages/docusaurus-plugin-content-blog/src/lastUpdate.ts b/packages/docusaurus-plugin-content-blog/src/lastUpdate.ts new file mode 100644 index 000000000000..2a0fefd5b6fa --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/lastUpdate.ts @@ -0,0 +1,52 @@ +/** + * 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 logger from '@docusaurus/logger'; +import { + getFileCommitDate, + FileNotTrackedError, + GitNotFoundError, +} from '@docusaurus/utils'; + +let showedGitRequirementError = false; +let showedFileNotTrackedError = false; + +export async function getFileLastUpdate( + filePath: string, +): Promise<{timestamp: number; author: string} | null> { + if (!filePath) { + return null; + } + + // Wrap in try/catch in case the shell commands fail + // (e.g. project doesn't use Git, etc). + try { + const result = await getFileCommitDate(filePath, { + age: 'newest', + includeAuthor: true, + }); + + return {timestamp: result.timestamp, author: result.author}; + } catch (err) { + if (err instanceof GitNotFoundError) { + if (!showedGitRequirementError) { + logger.warn('Sorry, the docs plugin last update options require Git.'); + showedGitRequirementError = true; + } + } else if (err instanceof FileNotTrackedError) { + if (!showedFileNotTrackedError) { + logger.warn( + 'Cannot infer the update date for some files, as they are not tracked by git.', + ); + showedFileNotTrackedError = true; + } + } else { + logger.warn(err); + } + return null; + } +} diff --git a/packages/docusaurus-plugin-content-blog/src/options.ts b/packages/docusaurus-plugin-content-blog/src/options.ts index d1e6e9cc4547..7429dc696f7d 100644 --- a/packages/docusaurus-plugin-content-blog/src/options.ts +++ b/packages/docusaurus-plugin-content-blog/src/options.ts @@ -51,6 +51,8 @@ export const DEFAULT_OPTIONS: PluginOptions = { authorsMapPath: 'authors.yml', readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}), sortPosts: 'descending', + showLastUpdateTime: false, + showLastUpdateAuthor: false, }; const PluginOptionSchema = Joi.object({ @@ -134,6 +136,10 @@ const PluginOptionSchema = Joi.object({ sortPosts: Joi.string() .valid('descending', 'ascending') .default(DEFAULT_OPTIONS.sortPosts), + showLastUpdateTime: Joi.bool().default(DEFAULT_OPTIONS.showLastUpdateTime), + showLastUpdateAuthor: Joi.bool().default( + DEFAULT_OPTIONS.showLastUpdateAuthor, + ), }).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 6506e5dc3f95..d4715c61d9f8 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 @@ -69,6 +69,14 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the [key: string]: unknown; }; + export type FileChange = { + author?: string; + /** Date can be any + * [parsable date string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse). + */ + date?: Date | string; + }; + /** * Everything is partial/unnormalized, because front matter is always * preserved as-is. Default values will be applied when generating metadata @@ -156,6 +164,8 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the toc_min_heading_level?: number; /** Maximum TOC heading level. Must be between 2 and 6. */ toc_max_heading_level?: number; + /** Allows overriding the last updated author and/or date. */ + last_update?: FileChange; }; export type BlogPostFrontMatterAuthor = Author & { @@ -180,7 +190,14 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the | BlogPostFrontMatterAuthor | (string | BlogPostFrontMatterAuthor)[]; - export type BlogPostMetadata = { + export type LastUpdateData = { + /** A timestamp in **seconds**, directly acquired from `git log`. */ + lastUpdatedAt?: number; + /** The author's name directly acquired from `git log`. */ + lastUpdatedBy?: string; + }; + + export type BlogPostMetadata = LastUpdateData & { /** Path to the Markdown source, with `@site` alias. */ readonly source: string; /** @@ -421,6 +438,10 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the 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; }; /** diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Info/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Info/index.tsx index 9b5308bbc73a..d425b986cf09 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Info/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Info/index.tsx @@ -15,6 +15,7 @@ import { } from '@docusaurus/theme-common/internal'; import type {Props} from '@theme/BlogPostItem/Header/Info'; +import LastUpdated from '@theme/LastUpdated'; import styles from './styles.module.css'; // Very simple pluralization: probably good enough for now @@ -60,7 +61,8 @@ export default function BlogPostItemHeaderInfo({ className, }: Props): JSX.Element { const {metadata} = useBlogPost(); - const {date, readingTime} = metadata; + const {date, readingTime, lastUpdatedAt, lastUpdatedBy} = metadata; + console.log('metadata:', metadata); const dateTimeFormat = useDateTimeFormat({ day: 'numeric', @@ -79,6 +81,11 @@ export default function BlogPostItemHeaderInfo({ <> + + )} diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 0d5127874378..181749890913 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -441,6 +441,8 @@ export default async function createConfigAsync() { blog: { // routeBasePath: '/', path: 'blog', + showLastUpdateAuthor: true, + showLastUpdateTime: true, editUrl: ({locale, blogDirPath, blogPath}) => { if (locale !== defaultLocale) { return `https://crowdin.com/project/docusaurus-v2/${locale}`; From 5967a6fce93568047a7920e22ab3320d1ff00378 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:39:32 +0100 Subject: [PATCH 02/43] display if values exists --- .../src/theme/BlogPostItem/Header/Info/index.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Info/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Info/index.tsx index d425b986cf09..10a64b9f0a66 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Info/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Info/index.tsx @@ -82,10 +82,12 @@ export default function BlogPostItemHeaderInfo({ - + {(lastUpdatedAt || lastUpdatedBy) && ( + + )} )} From dc305e31953cdbcf2b1501e01b387fae708a1cf9 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Wed, 6 Mar 2024 00:58:20 +0100 Subject: [PATCH 03/43] wip shared code --- .../src/blogUtils.ts | 8 +-- .../src/lastUpdate.ts | 52 ------------------- .../src/plugin-content-blog.d.ts | 17 +----- .../src/__tests__/lastUpdate.test.ts | 3 +- .../src/docs.ts | 3 +- .../src/lastUpdate.ts | 52 ------------------- packages/docusaurus-utils/src/index.ts | 6 +++ 7 files changed, 13 insertions(+), 128 deletions(-) delete mode 100644 packages/docusaurus-plugin-content-blog/src/lastUpdate.ts delete mode 100644 packages/docusaurus-plugin-content-docs/src/lastUpdate.ts diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index 3412a7abd5a4..6a45018eca58 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -26,10 +26,12 @@ import { getContentPathList, isUnlisted, isDraft, + getFileLastUpdate, + type FrontMatterLastUpdate, + type LastUpdateData, } from '@docusaurus/utils'; import {validateBlogPostFrontMatter} from './frontMatter'; import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors'; -import {getFileLastUpdate} from './lastUpdate'; import type {LoadContext, ParseFrontMatter} from '@docusaurus/types'; import type { PluginOptions, @@ -37,8 +39,6 @@ import type { BlogPost, BlogTags, BlogPaginated, - FileChange, - LastUpdateData, } from '@docusaurus/plugin-content-blog'; import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types'; @@ -62,7 +62,7 @@ type LastUpdateOptions = Pick< async function readLastUpdateData( filePath: string, options: LastUpdateOptions, - lastUpdateFrontMatter: FileChange | undefined, + lastUpdateFrontMatter: FrontMatterLastUpdate | undefined, ): Promise { const {showLastUpdateAuthor, showLastUpdateTime} = options; if (showLastUpdateAuthor || showLastUpdateTime) { diff --git a/packages/docusaurus-plugin-content-blog/src/lastUpdate.ts b/packages/docusaurus-plugin-content-blog/src/lastUpdate.ts deleted file mode 100644 index 2a0fefd5b6fa..000000000000 --- a/packages/docusaurus-plugin-content-blog/src/lastUpdate.ts +++ /dev/null @@ -1,52 +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 logger from '@docusaurus/logger'; -import { - getFileCommitDate, - FileNotTrackedError, - GitNotFoundError, -} from '@docusaurus/utils'; - -let showedGitRequirementError = false; -let showedFileNotTrackedError = false; - -export async function getFileLastUpdate( - filePath: string, -): Promise<{timestamp: number; author: string} | null> { - if (!filePath) { - return null; - } - - // Wrap in try/catch in case the shell commands fail - // (e.g. project doesn't use Git, etc). - try { - const result = await getFileCommitDate(filePath, { - age: 'newest', - includeAuthor: true, - }); - - return {timestamp: result.timestamp, author: result.author}; - } catch (err) { - if (err instanceof GitNotFoundError) { - if (!showedGitRequirementError) { - logger.warn('Sorry, the docs plugin last update options require Git.'); - showedGitRequirementError = true; - } - } else if (err instanceof FileNotTrackedError) { - if (!showedFileNotTrackedError) { - logger.warn( - 'Cannot infer the update date for some files, as they are not tracked by git.', - ); - showedFileNotTrackedError = true; - } - } else { - logger.warn(err); - } - return null; - } -} 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 d4715c61d9f8..14084ff63647 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 @@ -10,7 +10,7 @@ declare module '@docusaurus/plugin-content-blog' { import type {LoadedMDXContent} from '@docusaurus/mdx-loader'; import type {MDXOptions} from '@docusaurus/mdx-loader'; - import type {FrontMatterTag, Tag} from '@docusaurus/utils'; + import type {FrontMatterTag, Tag, LastUpdateData} from '@docusaurus/utils'; import type {DocusaurusConfig, Plugin, LoadContext} from '@docusaurus/types'; import type {Item as FeedItem} from 'feed'; import type {Overwrite} from 'utility-types'; @@ -69,14 +69,6 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the [key: string]: unknown; }; - export type FileChange = { - author?: string; - /** Date can be any - * [parsable date string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse). - */ - date?: Date | string; - }; - /** * Everything is partial/unnormalized, because front matter is always * preserved as-is. Default values will be applied when generating metadata @@ -190,13 +182,6 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the | BlogPostFrontMatterAuthor | (string | BlogPostFrontMatterAuthor)[]; - export type LastUpdateData = { - /** A timestamp in **seconds**, directly acquired from `git log`. */ - lastUpdatedAt?: number; - /** The author's name directly acquired from `git log`. */ - lastUpdatedBy?: string; - }; - export type BlogPostMetadata = LastUpdateData & { /** Path to the Markdown source, with `@site` alias. */ readonly source: string; diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts index 53f2827f2dc7..db9bf66d6e9a 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts @@ -10,8 +10,7 @@ import fs from 'fs-extra'; import path from 'path'; import shell from 'shelljs'; import {createTempRepo} from '@testing-utils/git'; - -import {getFileLastUpdate} from '../lastUpdate'; +import {getFileLastUpdate} from '@docusaurus/utils'; describe('getFileLastUpdate', () => { const existingFilePath = path.join( diff --git a/packages/docusaurus-plugin-content-docs/src/docs.ts b/packages/docusaurus-plugin-content-docs/src/docs.ts index 158f949ef370..bd272a36353c 100644 --- a/packages/docusaurus-plugin-content-docs/src/docs.ts +++ b/packages/docusaurus-plugin-content-docs/src/docs.ts @@ -20,9 +20,8 @@ import { normalizeFrontMatterTags, isUnlisted, isDraft, + getFileLastUpdate, } from '@docusaurus/utils'; - -import {getFileLastUpdate} from './lastUpdate'; import getSlug from './slug'; import {stripPathNumberPrefixes} from './numberPrefix'; import {validateDocFrontMatter} from './frontMatter'; diff --git a/packages/docusaurus-plugin-content-docs/src/lastUpdate.ts b/packages/docusaurus-plugin-content-docs/src/lastUpdate.ts deleted file mode 100644 index 2a0fefd5b6fa..000000000000 --- a/packages/docusaurus-plugin-content-docs/src/lastUpdate.ts +++ /dev/null @@ -1,52 +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 logger from '@docusaurus/logger'; -import { - getFileCommitDate, - FileNotTrackedError, - GitNotFoundError, -} from '@docusaurus/utils'; - -let showedGitRequirementError = false; -let showedFileNotTrackedError = false; - -export async function getFileLastUpdate( - filePath: string, -): Promise<{timestamp: number; author: string} | null> { - if (!filePath) { - return null; - } - - // Wrap in try/catch in case the shell commands fail - // (e.g. project doesn't use Git, etc). - try { - const result = await getFileCommitDate(filePath, { - age: 'newest', - includeAuthor: true, - }); - - return {timestamp: result.timestamp, author: result.author}; - } catch (err) { - if (err instanceof GitNotFoundError) { - if (!showedGitRequirementError) { - logger.warn('Sorry, the docs plugin last update options require Git.'); - showedGitRequirementError = true; - } - } else if (err instanceof FileNotTrackedError) { - if (!showedFileNotTrackedError) { - logger.warn( - 'Cannot infer the update date for some files, as they are not tracked by git.', - ); - showedFileNotTrackedError = true; - } - } else { - logger.warn(err); - } - return null; - } -} diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index dc5fc1e1bf39..f32d1d9e4240 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -118,3 +118,9 @@ export { export {isDraft, isUnlisted} from './contentVisibilityUtils'; export {escapeRegexp} from './regExpUtils'; export {askPreferredLanguage} from './cliUtils'; + +export { + getFileLastUpdate, + type LastUpdateData, + type FrontMatterLastUpdate, +} from './lastUpdateUtils'; From 6f098d8e66ba0368994b4725caeb2066574beb2c Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Wed, 6 Mar 2024 00:59:09 +0100 Subject: [PATCH 04/43] wip shared code --- .../docusaurus-utils/src/lastUpdateUtils.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 packages/docusaurus-utils/src/lastUpdateUtils.ts diff --git a/packages/docusaurus-utils/src/lastUpdateUtils.ts b/packages/docusaurus-utils/src/lastUpdateUtils.ts new file mode 100644 index 000000000000..ce0b5a7f094d --- /dev/null +++ b/packages/docusaurus-utils/src/lastUpdateUtils.ts @@ -0,0 +1,67 @@ +/** + * 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 logger from '@docusaurus/logger'; +import { + FileNotTrackedError, + GitNotFoundError, + getFileCommitDate, +} from './gitUtils'; + +export type LastUpdateData = { + /** A timestamp in **seconds**, directly acquired from `git log`. */ + lastUpdatedAt?: number; + /** The author's name directly acquired from `git log`. */ + lastUpdatedBy?: string; +}; + +export type FrontMatterLastUpdate = { + author?: string; + /** Date can be any + * [parsable date string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse). + */ + date?: Date | string; +}; + +let showedGitRequirementError = false; +let showedFileNotTrackedError = false; + +export async function getFileLastUpdate( + filePath: string, +): Promise<{timestamp: number; author: string} | null> { + if (!filePath) { + return null; + } + + // Wrap in try/catch in case the shell commands fail + // (e.g. project doesn't use Git, etc). + try { + const result = await getFileCommitDate(filePath, { + age: 'newest', + includeAuthor: true, + }); + + return {timestamp: result.timestamp, author: result.author}; + } catch (err) { + if (err instanceof GitNotFoundError) { + if (!showedGitRequirementError) { + logger.warn('Sorry, the docs plugin last update options require Git.'); + showedGitRequirementError = true; + } + } else if (err instanceof FileNotTrackedError) { + if (!showedFileNotTrackedError) { + logger.warn( + 'Cannot infer the update date for some files, as they are not tracked by git.', + ); + showedFileNotTrackedError = true; + } + } else { + logger.warn(err); + } + return null; + } +} From 8b196770653cb197b8244dd070f6894624ea408f Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Wed, 6 Mar 2024 12:06:10 +0100 Subject: [PATCH 05/43] wip share code & footer --- .../src/blogUtils.ts | 50 +------------------ .../src/plugin-content-blog.d.ts | 4 +- .../src/docs.ts | 50 +------------------ .../src/plugin-content-docs.d.ts | 4 +- .../src/theme/BlogPostItem/Footer/index.tsx | 22 +++++++- .../BlogPostItem/Footer/styles.module.css | 5 ++ .../theme/BlogPostItem/Header/Info/index.tsx | 11 +--- .../docusaurus-utils/src/lastUpdateUtils.ts | 47 +++++++++++++++++ 8 files changed, 79 insertions(+), 114 deletions(-) diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index 6a45018eca58..5bd209b7baa8 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -26,10 +26,8 @@ import { getContentPathList, isUnlisted, isDraft, - getFileLastUpdate, - type FrontMatterLastUpdate, - type LastUpdateData, } from '@docusaurus/utils'; +import {readLastUpdateData} from '@docusaurus/utils/lib/lastUpdateUtils'; import {validateBlogPostFrontMatter} from './frontMatter'; import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors'; import type {LoadContext, ParseFrontMatter} from '@docusaurus/types'; @@ -54,52 +52,6 @@ export function getSourceToPermalink(blogPosts: BlogPost[]): { ); } -type LastUpdateOptions = Pick< - PluginOptions, - 'showLastUpdateAuthor' | 'showLastUpdateTime' ->; - -async function readLastUpdateData( - filePath: string, - options: LastUpdateOptions, - lastUpdateFrontMatter: FrontMatterLastUpdate | undefined, -): Promise { - const {showLastUpdateAuthor, showLastUpdateTime} = options; - if (showLastUpdateAuthor || showLastUpdateTime) { - const frontMatterTimestamp = lastUpdateFrontMatter?.date - ? new Date(lastUpdateFrontMatter.date).getTime() / 1000 - : undefined; - - if (lastUpdateFrontMatter?.author && lastUpdateFrontMatter.date) { - return { - lastUpdatedAt: frontMatterTimestamp, - lastUpdatedBy: lastUpdateFrontMatter.author, - }; - } - - // Use fake data in dev for faster development. - const fileLastUpdateData = - process.env.NODE_ENV === 'production' - ? await getFileLastUpdate(filePath) - : { - author: 'Author', - timestamp: 1539502055, - }; - const {author, timestamp} = fileLastUpdateData ?? {}; - - return { - lastUpdatedBy: showLastUpdateAuthor - ? lastUpdateFrontMatter?.author ?? author - : undefined, - lastUpdatedAt: showLastUpdateTime - ? frontMatterTimestamp ?? timestamp - : undefined, - }; - } - - return {}; -} - export function paginateBlogPosts({ blogPosts, basePageUrl, 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 14084ff63647..a9914b04336d 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 @@ -424,9 +424,9 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the /** Governs the direction of blog post sorting. */ sortPosts: 'ascending' | 'descending'; /** Whether to display the last date the doc was updated. */ - showLastUpdateTime?: boolean; + showLastUpdateTime: boolean; /** Whether to display the author who last updated the doc. */ - showLastUpdateAuthor?: boolean; + showLastUpdateAuthor: boolean; }; /** diff --git a/packages/docusaurus-plugin-content-docs/src/docs.ts b/packages/docusaurus-plugin-content-docs/src/docs.ts index bd272a36353c..ebc5e9c06bd7 100644 --- a/packages/docusaurus-plugin-content-docs/src/docs.ts +++ b/packages/docusaurus-plugin-content-docs/src/docs.ts @@ -20,8 +20,8 @@ import { normalizeFrontMatterTags, isUnlisted, isDraft, - getFileLastUpdate, } from '@docusaurus/utils'; +import {readLastUpdateData} from '@docusaurus/utils/lib/lastUpdateUtils'; import getSlug from './slug'; import {stripPathNumberPrefixes} from './numberPrefix'; import {validateDocFrontMatter} from './frontMatter'; @@ -33,61 +33,13 @@ import type { DocMetadataBase, DocMetadata, PropNavigationLink, - LastUpdateData, VersionMetadata, LoadedVersion, - FileChange, } from '@docusaurus/plugin-content-docs'; import type {LoadContext} from '@docusaurus/types'; import type {SidebarsUtils} from './sidebars/utils'; import type {DocFile} from './types'; -type LastUpdateOptions = Pick< - PluginOptions, - 'showLastUpdateAuthor' | 'showLastUpdateTime' ->; - -async function readLastUpdateData( - filePath: string, - options: LastUpdateOptions, - lastUpdateFrontMatter: FileChange | undefined, -): Promise { - const {showLastUpdateAuthor, showLastUpdateTime} = options; - if (showLastUpdateAuthor || showLastUpdateTime) { - const frontMatterTimestamp = lastUpdateFrontMatter?.date - ? new Date(lastUpdateFrontMatter.date).getTime() / 1000 - : undefined; - - if (lastUpdateFrontMatter?.author && lastUpdateFrontMatter.date) { - return { - lastUpdatedAt: frontMatterTimestamp, - lastUpdatedBy: lastUpdateFrontMatter.author, - }; - } - - // Use fake data in dev for faster development. - const fileLastUpdateData = - process.env.NODE_ENV === 'production' - ? await getFileLastUpdate(filePath) - : { - author: 'Author', - timestamp: 1539502055, - }; - const {author, timestamp} = fileLastUpdateData ?? {}; - - return { - lastUpdatedBy: showLastUpdateAuthor - ? lastUpdateFrontMatter?.author ?? author - : undefined, - lastUpdatedAt: showLastUpdateTime - ? frontMatterTimestamp ?? timestamp - : undefined, - }; - } - - return {}; -} - export async function readDocFile( versionMetadata: Pick< VersionMetadata, diff --git a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts index dc0db6e40368..4ac3cb93c1a3 100644 --- a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts +++ b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts @@ -93,9 +93,9 @@ declare module '@docusaurus/plugin-content-docs' { */ editLocalizedFiles: boolean; /** Whether to display the last date the doc was updated. */ - showLastUpdateTime?: boolean; + showLastUpdateTime: boolean; /** Whether to display the author who last updated the doc. */ - showLastUpdateAuthor?: boolean; + showLastUpdateAuthor: boolean; /** * Custom parsing logic to extract number prefixes from file names. Use * `false` to disable this behavior and leave the docs untouched, and `true` diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/index.tsx index 314faacf2d98..7a4a7d842f67 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/index.tsx @@ -12,11 +12,19 @@ import EditThisPage from '@theme/EditThisPage'; import TagsListInline from '@theme/TagsListInline'; import ReadMoreLink from '@theme/BlogPostItem/Footer/ReadMoreLink'; +import LastUpdated from '@theme/LastUpdated'; import styles from './styles.module.css'; export default function BlogPostItemFooter(): JSX.Element | null { const {metadata, isBlogPostPage} = useBlogPost(); - const {tags, title, editUrl, hasTruncateMarker} = metadata; + const { + tags, + title, + editUrl, + hasTruncateMarker, + lastUpdatedBy, + lastUpdatedAt, + } = metadata; // A post is truncated if it's in the "list view" and it has a truncate marker const truncatedPost = !isBlogPostPage && hasTruncateMarker; @@ -42,8 +50,18 @@ export default function BlogPostItemFooter(): JSX.Element | null { )} {isBlogPostPage && editUrl && ( -
+
+ {(lastUpdatedAt || lastUpdatedBy) && ( + + )}
)} diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/styles.module.css b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/styles.module.css index f9272fb53b69..9e9386b701e2 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/styles.module.css @@ -8,3 +8,8 @@ .blogPostFooterDetailsFull { flex-direction: column; } + +.blogPostFooterLastUpdated { + display: flex; + justify-content: space-between; +} diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Info/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Info/index.tsx index 10a64b9f0a66..9b5308bbc73a 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Info/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Info/index.tsx @@ -15,7 +15,6 @@ import { } from '@docusaurus/theme-common/internal'; import type {Props} from '@theme/BlogPostItem/Header/Info'; -import LastUpdated from '@theme/LastUpdated'; import styles from './styles.module.css'; // Very simple pluralization: probably good enough for now @@ -61,8 +60,7 @@ export default function BlogPostItemHeaderInfo({ className, }: Props): JSX.Element { const {metadata} = useBlogPost(); - const {date, readingTime, lastUpdatedAt, lastUpdatedBy} = metadata; - console.log('metadata:', metadata); + const {date, readingTime} = metadata; const dateTimeFormat = useDateTimeFormat({ day: 'numeric', @@ -81,13 +79,6 @@ export default function BlogPostItemHeaderInfo({ <> - - {(lastUpdatedAt || lastUpdatedBy) && ( - - )} )}
diff --git a/packages/docusaurus-utils/src/lastUpdateUtils.ts b/packages/docusaurus-utils/src/lastUpdateUtils.ts index ce0b5a7f094d..e6b7153668f2 100644 --- a/packages/docusaurus-utils/src/lastUpdateUtils.ts +++ b/packages/docusaurus-utils/src/lastUpdateUtils.ts @@ -11,6 +11,7 @@ import { GitNotFoundError, getFileCommitDate, } from './gitUtils'; +import type {PluginOptions} from '@docusaurus/types'; export type LastUpdateData = { /** A timestamp in **seconds**, directly acquired from `git log`. */ @@ -65,3 +66,49 @@ export async function getFileLastUpdate( return null; } } + +type LastUpdateOptions = Pick< + PluginOptions, + 'showLastUpdateAuthor' | 'showLastUpdateTime' +>; + +export async function readLastUpdateData( + filePath: string, + options: LastUpdateOptions, + lastUpdateFrontMatter: FrontMatterLastUpdate | undefined, +): Promise { + const {showLastUpdateAuthor, showLastUpdateTime} = options; + if (showLastUpdateAuthor || showLastUpdateTime) { + const frontMatterTimestamp = lastUpdateFrontMatter?.date + ? new Date(lastUpdateFrontMatter.date).getTime() / 1000 + : undefined; + + if (lastUpdateFrontMatter?.author && lastUpdateFrontMatter.date) { + return { + lastUpdatedAt: frontMatterTimestamp, + lastUpdatedBy: lastUpdateFrontMatter.author, + }; + } + + // Use fake data in dev for faster development. + const fileLastUpdateData = + process.env.NODE_ENV === 'production' + ? await getFileLastUpdate(filePath) + : { + author: 'Author', + timestamp: 1539502055, + }; + const {author, timestamp} = fileLastUpdateData ?? {}; + + return { + lastUpdatedBy: showLastUpdateAuthor + ? lastUpdateFrontMatter?.author ?? author + : undefined, + lastUpdatedAt: showLastUpdateTime + ? frontMatterTimestamp ?? timestamp + : undefined, + }; + } + + return {}; +} From fb2a3b6f9790666cf2c3822f41044c38821eb647 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Wed, 6 Mar 2024 12:38:06 +0100 Subject: [PATCH 06/43] wip tests --- .../blog/another-simple-slug-with-tags.md | 1 + .../__snapshots__/index.test.ts.snap | 186 ++++++++++++++++++ .../src/__tests__/index.test.ts | 20 ++ 3 files changed, 207 insertions(+) diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-simple-slug-with-tags.md b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-simple-slug-with-tags.md index 8cfe5ab9459d..dab93b3ddd74 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-simple-slug-with-tags.md +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-simple-slug-with-tags.md @@ -6,6 +6,7 @@ date: 2020-08-15 author: Sébastien Lorber author_title: Docusaurus maintainer author_url: https://sebastienlorber.com +last_updated: 2022-08-15 tags: [tag1] --- diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap index 441e7e6ef590..2e79099c890e 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap @@ -1,5 +1,184 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`blog plugin lastupdated author time posts load content 1`] = ` +[ + { + "content": "with tag", + "id": "/another/blog-with-tags-unlisted", + "metadata": { + "authors": [], + "date": 2020-08-19T00:00:00.000Z, + "description": "with tag", + "editUrl": "https://baseEditUrl.com/edit/blog/another-with-tags-unlisted.md", + "frontMatter": { + "date": 2020-08-19T00:00:00.000Z, + "slug": "/another/blog-with-tags-unlisted", + "tags": [ + "unlisted", + ], + "title": "Another Blog With Tag - unlisted", + "unlisted": true, + }, + "hasTruncateMarker": false, + "lastUpdatedAt": 1539502055, + "lastUpdatedBy": "Author", + "nextItem": { + "permalink": "/blog/simple/slug/another", + "title": "Another Simple Slug", + }, + "permalink": "/blog/another/blog-with-tags-unlisted", + "readingTime": 0.01, + "source": "@site/blog/another-with-tags-unlisted.md", + "tags": [ + { + "label": "unlisted", + "permalink": "/blog/tags/unlisted", + }, + ], + "title": "Another Blog With Tag - unlisted", + "unlisted": false, + }, + }, + { + "content": "simple url slug", + "id": "/simple/slug/another", + "metadata": { + "authors": [ + { + "imageURL": undefined, + "name": "Sébastien Lorber", + "title": "Docusaurus maintainer", + "url": "https://sebastienlorber.com", + }, + ], + "date": 2020-08-15T00:00:00.000Z, + "description": "simple url slug", + "editUrl": "https://baseEditUrl.com/edit/blog/another-simple-slug-with-tags.md", + "frontMatter": { + "author": "Sébastien Lorber", + "author_title": "Docusaurus maintainer", + "author_url": "https://sebastienlorber.com", + "date": 2020-08-15T00:00:00.000Z, + "last_updated": 2022-08-15T00:00:00.000Z, + "slug": "/simple/slug/another", + "tags": [ + "tag1", + ], + "title": "Another Simple Slug", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": 1539502055, + "lastUpdatedBy": "Author", + "nextItem": { + "permalink": "/blog/another/tags", + "title": "Another With Tag", + }, + "permalink": "/blog/simple/slug/another", + "prevItem": { + "permalink": "/blog/another/blog-with-tags-unlisted", + "title": "Another Blog With Tag - unlisted", + }, + "readingTime": 0.015, + "source": "@site/blog/another-simple-slug-with-tags.md", + "tags": [ + { + "label": "tag1", + "permalink": "/blog/tags/tag-1", + }, + ], + "title": "Another Simple Slug", + "unlisted": false, + }, + }, + { + "content": "with tag", + "id": "/another/tags", + "metadata": { + "authors": [], + "date": 2020-08-15T00:00:00.000Z, + "description": "with tag", + "editUrl": "https://baseEditUrl.com/edit/blog/another-with-tags.md", + "frontMatter": { + "date": 2020-08-15T00:00:00.000Z, + "slug": "/another/tags", + "tags": [ + "tag1", + "tag2", + ], + "title": "Another With Tag", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": 1539502055, + "lastUpdatedBy": "Author", + "nextItem": { + "permalink": "/blog/another/tags2", + "title": "Another With Tag", + }, + "permalink": "/blog/another/tags", + "prevItem": { + "permalink": "/blog/simple/slug/another", + "title": "Another Simple Slug", + }, + "readingTime": 0.01, + "source": "@site/blog/another-with-tags.md", + "tags": [ + { + "label": "tag1", + "permalink": "/blog/tags/tag-1", + }, + { + "label": "tag2", + "permalink": "/blog/tags/tag-2", + }, + ], + "title": "Another With Tag", + "unlisted": false, + }, + }, + { + "content": "with tag", + "id": "/another/tags2", + "metadata": { + "authors": [], + "date": 2020-08-15T00:00:00.000Z, + "description": "with tag", + "editUrl": "https://baseEditUrl.com/edit/blog/another-with-tags2.md", + "frontMatter": { + "date": 2020-08-15T00:00:00.000Z, + "slug": "/another/tags2", + "tags": [ + "tag1", + "tag2", + ], + "title": "Another With Tag", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": 1539502055, + "lastUpdatedBy": "Author", + "permalink": "/blog/another/tags2", + "prevItem": { + "permalink": "/blog/another/tags", + "title": "Another With Tag", + }, + "readingTime": 0.01, + "source": "@site/blog/another-with-tags2.md", + "tags": [ + { + "label": "tag1", + "permalink": "/blog/tags/tag-1", + }, + { + "label": "tag2", + "permalink": "/blog/tags/tag-2", + }, + ], + "title": "Another With Tag", + "unlisted": false, + }, + }, +] +`; + exports[`blog plugin process blog posts load content 1`] = ` { "/blog/tags/tag-1": { @@ -130,6 +309,7 @@ exports[`blog plugin process blog posts load content 2`] = ` "author_title": "Docusaurus maintainer", "author_url": "https://sebastienlorber.com", "date": 2020-08-15T00:00:00.000Z, + "last_updated": 2022-08-15T00:00:00.000Z, "slug": "/simple/slug/another", "tags": [ "tag1", @@ -137,6 +317,8 @@ exports[`blog plugin process blog posts load content 2`] = ` "title": "Another Simple Slug", }, "hasTruncateMarker": false, + "lastUpdatedAt": undefined, + "lastUpdatedBy": undefined, "nextItem": { "permalink": "/blog/another/tags", "title": "Another With Tag", @@ -172,6 +354,8 @@ exports[`blog plugin process blog posts load content 2`] = ` "title": "Another With Tag", }, "hasTruncateMarker": false, + "lastUpdatedAt": undefined, + "lastUpdatedBy": undefined, "nextItem": { "permalink": "/blog/another/tags2", "title": "Another With Tag", @@ -215,6 +399,8 @@ exports[`blog plugin process blog posts load content 2`] = ` "title": "Another With Tag", }, "hasTruncateMarker": false, + "lastUpdatedAt": undefined, + "lastUpdatedBy": undefined, "permalink": "/blog/another/tags2", "prevItem": { "permalink": "/blog/another/tags", diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index 73ecb63a2567..49eb7fdc5671 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -525,4 +525,24 @@ describe('blog plugin', () => { expect(blogPosts).toHaveLength(3); expect(blogPosts).toMatchSnapshot(); }); + + it('lastupdated author time posts load content', async () => { + const siteDir = path.join( + __dirname, + '__fixtures__', + 'website-blog-with-tags', + ); + const plugin = await getPlugin( + siteDir, + { + postsPerPage: 1, + showLastUpdateAuthor: true, + showLastUpdateTime: true, + }, + DefaultI18N, + ); + const {blogPosts} = (await plugin.loadContent!())!; + + expect(blogPosts).toMatchSnapshot(); + }); }); From 713054677c92f8153af830997d441873c3164ad8 Mon Sep 17 00:00:00 2001 From: OzakIOne Date: Wed, 6 Mar 2024 11:42:37 +0000 Subject: [PATCH 07/43] refactor: apply lint autofix --- project-words.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/project-words.txt b/project-words.txt index f3fd6a5256a2..31716b09226d 100644 --- a/project-words.txt +++ b/project-words.txt @@ -155,6 +155,7 @@ Knapen Koyeb Koyeb's Lamana +lastupdated Lifecycles lifecycles Linkify From 29653595be3714a11c722635b6e80ce946aa9d62 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:35:11 +0100 Subject: [PATCH 08/43] update type & doc --- .../src/plugin-content-blog.d.ts | 9 +++++++-- .../src/plugin-content-docs.d.ts | 11 ++--------- website/docs/api/plugins/plugin-content-docs.mdx | 4 ++-- 3 files changed, 11 insertions(+), 13 deletions(-) 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 c5236d6d0941..a1f580466ebe 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 @@ -10,7 +10,12 @@ declare module '@docusaurus/plugin-content-blog' { import type {LoadedMDXContent} from '@docusaurus/mdx-loader'; import type {MDXOptions} from '@docusaurus/mdx-loader'; - import type {FrontMatterTag, Tag, LastUpdateData} from '@docusaurus/utils'; + import type { + FrontMatterTag, + Tag, + LastUpdateData, + FrontMatterLastUpdate, + } from '@docusaurus/utils'; import type {DocusaurusConfig, Plugin, LoadContext} from '@docusaurus/types'; import type {Item as FeedItem} from 'feed'; import type {Overwrite} from 'utility-types'; @@ -157,7 +162,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the /** Maximum TOC heading level. Must be between 2 and 6. */ toc_max_heading_level?: number; /** Allows overriding the last updated author and/or date. */ - last_update?: FileChange; + last_update?: FrontMatterLastUpdate; }; export type BlogPostFrontMatterAuthor = Author & { diff --git a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts index 4ac3cb93c1a3..38606e51168b 100644 --- a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts +++ b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts @@ -16,6 +16,7 @@ declare module '@docusaurus/plugin-content-docs' { TagsListItem, TagModule, Tag, + FrontMatterLastUpdate, } from '@docusaurus/utils'; import type {Plugin, LoadContext} from '@docusaurus/types'; import type {Overwrite, Required} from 'utility-types'; @@ -24,14 +25,6 @@ declare module '@docusaurus/plugin-content-docs' { image?: string; }; - export type FileChange = { - author?: string; - /** Date can be any - * [parsable date string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse). - */ - date?: Date | string; - }; - /** * Custom callback for parsing number prefixes from file/folder names. */ @@ -401,7 +394,7 @@ declare module '@docusaurus/plugin-content-docs' { /** Should this doc be accessible but hidden in production builds? */ unlisted?: boolean; /** Allows overriding the last updated author and/or date. */ - last_update?: FileChange; + last_update?: FrontMatterLastUpdate; }; export type LastUpdateData = { diff --git a/website/docs/api/plugins/plugin-content-docs.mdx b/website/docs/api/plugins/plugin-content-docs.mdx index ce6b56b5513a..143517435adc 100644 --- a/website/docs/api/plugins/plugin-content-docs.mdx +++ b/website/docs/api/plugins/plugin-content-docs.mdx @@ -296,7 +296,7 @@ Accepted fields: | `tags` | `Tag[]` | `undefined` | A list of strings or objects of two string fields `label` and `permalink` to tag to your docs. | | `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` | `FileChange` | `undefined` | Allows overriding the last updated author and/or date. Date can be any [parsable date string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse). | +| `last_update` | `FrontMatterLastUpdate` | `undefined` | Allows overriding the last updated author and/or date. Date can be any [parsable date string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse). | ```mdx-code-block @@ -307,7 +307,7 @@ type Tag = string | {label: string; permalink: string}; ``` ```ts -type FileChange = {date: string; author: string}; +type FrontMatterLastUpdate = {date: string; author: string}; ``` Example: From 6331811116161794e6d09f6b2c6629d1ebc4667c Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:47:42 +0100 Subject: [PATCH 09/43] wip share code --- .../docusaurus-plugin-content-blog/src/frontMatter.ts | 10 ++++------ .../docusaurus-plugin-content-docs/src/frontMatter.ts | 4 +--- packages/docusaurus-utils/src/lastUpdateUtils.ts | 6 ++++++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/docusaurus-plugin-content-blog/src/frontMatter.ts b/packages/docusaurus-plugin-content-blog/src/frontMatter.ts index b7649bea41e1..c256e66920ed 100644 --- a/packages/docusaurus-plugin-content-blog/src/frontMatter.ts +++ b/packages/docusaurus-plugin-content-blog/src/frontMatter.ts @@ -13,6 +13,10 @@ import { FrontMatterTOCHeadingLevels, ContentVisibilitySchema, } from '@docusaurus/utils-validation'; +import { + FrontMatterAuthorErrorMessage, + FrontMatterLastUpdateErrorMessage, +} from '@docusaurus/utils/lib/lastUpdateUtils'; import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog'; const BlogPostFrontMatterAuthorSchema = Joi.object({ @@ -25,12 +29,6 @@ const BlogPostFrontMatterAuthorSchema = Joi.object({ .or('key', 'name', 'imageURL') .rename('image_url', 'imageURL', {alias: true}); -const FrontMatterAuthorErrorMessage = - '{{#label}} does not look like a valid blog post author. Please use an author key or an author object (with a key and/or name).'; - -const FrontMatterLastUpdateErrorMessage = - '{{#label}} does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).'; - const BlogFrontMatterSchema = Joi.object({ id: Joi.string(), title: Joi.string().allow(''), diff --git a/packages/docusaurus-plugin-content-docs/src/frontMatter.ts b/packages/docusaurus-plugin-content-docs/src/frontMatter.ts index 1cffac35f32c..2475e692ab57 100644 --- a/packages/docusaurus-plugin-content-docs/src/frontMatter.ts +++ b/packages/docusaurus-plugin-content-docs/src/frontMatter.ts @@ -13,11 +13,9 @@ import { validateFrontMatter, ContentVisibilitySchema, } from '@docusaurus/utils-validation'; +import {FrontMatterLastUpdateErrorMessage} from '@docusaurus/utils/lib/lastUpdateUtils'; import type {DocFrontMatter} from '@docusaurus/plugin-content-docs'; -const FrontMatterLastUpdateErrorMessage = - '{{#label}} does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).'; - // NOTE: we don't add any default value on purpose here // We don't want default values to magically appear in doc metadata and props // While the user did not provide those values explicitly diff --git a/packages/docusaurus-utils/src/lastUpdateUtils.ts b/packages/docusaurus-utils/src/lastUpdateUtils.ts index e6b7153668f2..cd7894548647 100644 --- a/packages/docusaurus-utils/src/lastUpdateUtils.ts +++ b/packages/docusaurus-utils/src/lastUpdateUtils.ts @@ -112,3 +112,9 @@ export async function readLastUpdateData( return {}; } + +export const FrontMatterAuthorErrorMessage = + '{{#label}} does not look like a valid blog post author. Please use an author key or an author object (with a key and/or name).'; + +export const FrontMatterLastUpdateErrorMessage = + '{{#label}} does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).'; From 3a88750d06c1c6f0476b55e8325d70dfa20c88a2 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:27:21 +0100 Subject: [PATCH 10/43] use throw error --- .../src/__tests__/lastUpdate.test.ts | 7 +++---- packages/docusaurus-utils/src/lastUpdateUtils.ts | 5 ++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts index db9bf66d6e9a..5ed81e80b631 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts @@ -55,11 +55,10 @@ describe('getFileLastUpdate', () => { '__fixtures__', nonExistingFileName, ); - await expect(getFileLastUpdate(nonExistingFilePath)).resolves.toBeNull(); - expect(consoleMock).toHaveBeenCalledTimes(1); - expect(consoleMock).toHaveBeenLastCalledWith( - expect.stringMatching(/because the file does not exist./), + await expect(getFileLastUpdate(nonExistingFilePath)).rejects.toThrow( + /An error occurred when trying to get the last update date/, ); + expect(consoleMock).toHaveBeenCalledTimes(0); consoleMock.mockRestore(); }); diff --git a/packages/docusaurus-utils/src/lastUpdateUtils.ts b/packages/docusaurus-utils/src/lastUpdateUtils.ts index cd7894548647..6ca7f0e783d9 100644 --- a/packages/docusaurus-utils/src/lastUpdateUtils.ts +++ b/packages/docusaurus-utils/src/lastUpdateUtils.ts @@ -61,7 +61,10 @@ export async function getFileLastUpdate( showedFileNotTrackedError = true; } } else { - logger.warn(err); + throw new Error( + `An error occurred when trying to get the last update date`, + {cause: err}, + ); } return null; } From 87fcdf0a1f16c9e065d36d1ac70df7c7b9e53fa7 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:35:17 +0100 Subject: [PATCH 11/43] seo itemprop datemodified --- .../docusaurus-theme-classic/src/theme/LastUpdated/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docusaurus-theme-classic/src/theme/LastUpdated/index.tsx b/packages/docusaurus-theme-classic/src/theme/LastUpdated/index.tsx index 69ec6d1e2128..1264ab4e3013 100644 --- a/packages/docusaurus-theme-classic/src/theme/LastUpdated/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/LastUpdated/index.tsx @@ -34,7 +34,7 @@ function LastUpdatedAtDate({ values={{ date: ( - From 5dc4c042c41fbffb747a40857010c7f435365bc5 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:28:43 +0100 Subject: [PATCH 12/43] wip: git track --- .../website-blog-with-last-update/blog/author.md | 7 +++++++ .../website-blog-with-last-update/blog/both.md | 9 +++++++++ .../website-blog-with-last-update/blog/lastUpdateDate.md | 8 ++++++++ .../website-blog-with-last-update/blog/nothing.md | 6 ++++++ 4 files changed, 30 insertions(+) create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/author.md create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/both.md create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/lastUpdateDate.md create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/nothing.md diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/author.md b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/author.md new file mode 100644 index 000000000000..7b3341ee3685 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/author.md @@ -0,0 +1,7 @@ +--- +title: Author +slug: author +author: ozaki +--- + +author diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/both.md b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/both.md new file mode 100644 index 000000000000..5d317f71ad92 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/both.md @@ -0,0 +1,9 @@ +--- +title: Both +slug: both +date: 2020-01-01 +last_update: 2021-01-01 +author: ozaki +--- + +last update date diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/lastUpdateDate.md b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/lastUpdateDate.md new file mode 100644 index 000000000000..8c877dcdc1d8 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/lastUpdateDate.md @@ -0,0 +1,8 @@ +--- +title: Last update date +slug: lastUpdateDate +date: 2020-01-01 +last_update: 2021-01-01 +--- + +last update date diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/nothing.md b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/nothing.md new file mode 100644 index 000000000000..1f78115cf7b7 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/nothing.md @@ -0,0 +1,6 @@ +--- +title: Nothing +slug: nothing +--- + +nothing From e0ac624a52aedf9f863740434ccc9bae17337798 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Thu, 7 Mar 2024 22:23:15 +0100 Subject: [PATCH 13/43] refactor: tests & shared code --- .../blog/author.md | 2 + .../blog/both.md | 4 +- .../blog/lastUpdateDate.md | 3 +- .../blog/another-simple-slug-with-tags.md | 1 - .../__snapshots__/index.test.ts.snap | 776 ++++++++++++++---- .../src/__tests__/frontMatter.test.ts | 2 +- .../src/__tests__/index.test.ts | 55 +- .../src/blogUtils.ts | 2 +- .../src/frontMatter.ts | 93 --- .../src/__tests__/frontMatter.test.ts | 2 +- .../src/docs.ts | 2 +- .../src/frontMatter.ts | 63 -- .../docusaurus-utils-validation/src/index.ts | 5 + .../src/validationSchemas.ts | 122 +++ .../docusaurus-utils/src/lastUpdateUtils.ts | 6 - 15 files changed, 782 insertions(+), 356 deletions(-) delete mode 100644 packages/docusaurus-plugin-content-blog/src/frontMatter.ts delete mode 100644 packages/docusaurus-plugin-content-docs/src/frontMatter.ts diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/author.md b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/author.md index 7b3341ee3685..53dd2c41e934 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/author.md +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/author.md @@ -2,6 +2,8 @@ title: Author slug: author author: ozaki +last_update: + author: seb --- author diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/both.md b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/both.md index 5d317f71ad92..4171ca894fb2 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/both.md +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/both.md @@ -2,7 +2,9 @@ title: Both slug: both date: 2020-01-01 -last_update: 2021-01-01 +last_update: + date: 2021-01-01 + author: seb author: ozaki --- diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/lastUpdateDate.md b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/lastUpdateDate.md index 8c877dcdc1d8..81e8a1bb8267 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/lastUpdateDate.md +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-last-update/blog/lastUpdateDate.md @@ -2,7 +2,8 @@ title: Last update date slug: lastUpdateDate date: 2020-01-01 -last_update: 2021-01-01 +last_update: + date: 2021-01-01 --- last update date diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-simple-slug-with-tags.md b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-simple-slug-with-tags.md index dab93b3ddd74..8cfe5ab9459d 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-simple-slug-with-tags.md +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-simple-slug-with-tags.md @@ -6,7 +6,6 @@ date: 2020-08-15 author: Sébastien Lorber author_title: Docusaurus maintainer author_url: https://sebastienlorber.com -last_updated: 2022-08-15 tags: [tag1] --- diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap index 2e79099c890e..a9b375ef4019 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap @@ -1,184 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`blog plugin lastupdated author time posts load content 1`] = ` -[ - { - "content": "with tag", - "id": "/another/blog-with-tags-unlisted", - "metadata": { - "authors": [], - "date": 2020-08-19T00:00:00.000Z, - "description": "with tag", - "editUrl": "https://baseEditUrl.com/edit/blog/another-with-tags-unlisted.md", - "frontMatter": { - "date": 2020-08-19T00:00:00.000Z, - "slug": "/another/blog-with-tags-unlisted", - "tags": [ - "unlisted", - ], - "title": "Another Blog With Tag - unlisted", - "unlisted": true, - }, - "hasTruncateMarker": false, - "lastUpdatedAt": 1539502055, - "lastUpdatedBy": "Author", - "nextItem": { - "permalink": "/blog/simple/slug/another", - "title": "Another Simple Slug", - }, - "permalink": "/blog/another/blog-with-tags-unlisted", - "readingTime": 0.01, - "source": "@site/blog/another-with-tags-unlisted.md", - "tags": [ - { - "label": "unlisted", - "permalink": "/blog/tags/unlisted", - }, - ], - "title": "Another Blog With Tag - unlisted", - "unlisted": false, - }, - }, - { - "content": "simple url slug", - "id": "/simple/slug/another", - "metadata": { - "authors": [ - { - "imageURL": undefined, - "name": "Sébastien Lorber", - "title": "Docusaurus maintainer", - "url": "https://sebastienlorber.com", - }, - ], - "date": 2020-08-15T00:00:00.000Z, - "description": "simple url slug", - "editUrl": "https://baseEditUrl.com/edit/blog/another-simple-slug-with-tags.md", - "frontMatter": { - "author": "Sébastien Lorber", - "author_title": "Docusaurus maintainer", - "author_url": "https://sebastienlorber.com", - "date": 2020-08-15T00:00:00.000Z, - "last_updated": 2022-08-15T00:00:00.000Z, - "slug": "/simple/slug/another", - "tags": [ - "tag1", - ], - "title": "Another Simple Slug", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": 1539502055, - "lastUpdatedBy": "Author", - "nextItem": { - "permalink": "/blog/another/tags", - "title": "Another With Tag", - }, - "permalink": "/blog/simple/slug/another", - "prevItem": { - "permalink": "/blog/another/blog-with-tags-unlisted", - "title": "Another Blog With Tag - unlisted", - }, - "readingTime": 0.015, - "source": "@site/blog/another-simple-slug-with-tags.md", - "tags": [ - { - "label": "tag1", - "permalink": "/blog/tags/tag-1", - }, - ], - "title": "Another Simple Slug", - "unlisted": false, - }, - }, - { - "content": "with tag", - "id": "/another/tags", - "metadata": { - "authors": [], - "date": 2020-08-15T00:00:00.000Z, - "description": "with tag", - "editUrl": "https://baseEditUrl.com/edit/blog/another-with-tags.md", - "frontMatter": { - "date": 2020-08-15T00:00:00.000Z, - "slug": "/another/tags", - "tags": [ - "tag1", - "tag2", - ], - "title": "Another With Tag", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": 1539502055, - "lastUpdatedBy": "Author", - "nextItem": { - "permalink": "/blog/another/tags2", - "title": "Another With Tag", - }, - "permalink": "/blog/another/tags", - "prevItem": { - "permalink": "/blog/simple/slug/another", - "title": "Another Simple Slug", - }, - "readingTime": 0.01, - "source": "@site/blog/another-with-tags.md", - "tags": [ - { - "label": "tag1", - "permalink": "/blog/tags/tag-1", - }, - { - "label": "tag2", - "permalink": "/blog/tags/tag-2", - }, - ], - "title": "Another With Tag", - "unlisted": false, - }, - }, - { - "content": "with tag", - "id": "/another/tags2", - "metadata": { - "authors": [], - "date": 2020-08-15T00:00:00.000Z, - "description": "with tag", - "editUrl": "https://baseEditUrl.com/edit/blog/another-with-tags2.md", - "frontMatter": { - "date": 2020-08-15T00:00:00.000Z, - "slug": "/another/tags2", - "tags": [ - "tag1", - "tag2", - ], - "title": "Another With Tag", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": 1539502055, - "lastUpdatedBy": "Author", - "permalink": "/blog/another/tags2", - "prevItem": { - "permalink": "/blog/another/tags", - "title": "Another With Tag", - }, - "readingTime": 0.01, - "source": "@site/blog/another-with-tags2.md", - "tags": [ - { - "label": "tag1", - "permalink": "/blog/tags/tag-1", - }, - { - "label": "tag2", - "permalink": "/blog/tags/tag-2", - }, - ], - "title": "Another With Tag", - "unlisted": false, - }, - }, -] -`; - exports[`blog plugin process blog posts load content 1`] = ` { "/blog/tags/tag-1": { @@ -309,7 +130,6 @@ exports[`blog plugin process blog posts load content 2`] = ` "author_title": "Docusaurus maintainer", "author_url": "https://sebastienlorber.com", "date": 2020-08-15T00:00:00.000Z, - "last_updated": 2022-08-15T00:00:00.000Z, "slug": "/simple/slug/another", "tags": [ "tag1", @@ -617,3 +437,599 @@ exports[`blog plugin works with blog tags 1`] = ` }, } `; + +exports[`last update author and time 1`] = ` +[ + { + "content": "author", + "id": "author", + "metadata": { + "authors": [ + { + "imageURL": undefined, + "name": "ozaki", + "title": undefined, + "url": undefined, + }, + ], + "date": 2024-03-07T17:28:43.000Z, + "description": "author", + "editUrl": "https://baseEditUrl.com/edit/blog/author.md", + "frontMatter": { + "author": "ozaki", + "last_update": { + "author": "seb", + }, + "slug": "author", + "title": "Author", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": 1539502055, + "lastUpdatedBy": "seb", + "nextItem": { + "permalink": "/blog/nothing", + "title": "Nothing", + }, + "permalink": "/blog/author", + "readingTime": 0.005, + "source": "@site/blog/author.md", + "tags": [], + "title": "Author", + "unlisted": false, + }, + }, + { + "content": "nothing", + "id": "nothing", + "metadata": { + "authors": [], + "date": 2024-03-07T17:28:43.000Z, + "description": "nothing", + "editUrl": "https://baseEditUrl.com/edit/blog/nothing.md", + "frontMatter": { + "slug": "nothing", + "title": "Nothing", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": 1539502055, + "lastUpdatedBy": "Author", + "nextItem": { + "permalink": "/blog/both", + "title": "Both", + }, + "permalink": "/blog/nothing", + "prevItem": { + "permalink": "/blog/author", + "title": "Author", + }, + "readingTime": 0.005, + "source": "@site/blog/nothing.md", + "tags": [], + "title": "Nothing", + "unlisted": false, + }, + }, + { + "content": "last update date", + "id": "both", + "metadata": { + "authors": [ + { + "imageURL": undefined, + "name": "ozaki", + "title": undefined, + "url": undefined, + }, + ], + "date": 2020-01-01T00:00:00.000Z, + "description": "last update date", + "editUrl": "https://baseEditUrl.com/edit/blog/both.md", + "frontMatter": { + "author": "ozaki", + "date": 2020-01-01T00:00:00.000Z, + "last_update": { + "author": "seb", + "date": 2021-01-01T00:00:00.000Z, + }, + "slug": "both", + "title": "Both", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": 1609459200, + "lastUpdatedBy": "seb", + "nextItem": { + "permalink": "/blog/lastUpdateDate", + "title": "Last update date", + }, + "permalink": "/blog/both", + "prevItem": { + "permalink": "/blog/nothing", + "title": "Nothing", + }, + "readingTime": 0.015, + "source": "@site/blog/both.md", + "tags": [], + "title": "Both", + "unlisted": false, + }, + }, + { + "content": "last update date", + "id": "lastUpdateDate", + "metadata": { + "authors": [], + "date": 2020-01-01T00:00:00.000Z, + "description": "last update date", + "editUrl": "https://baseEditUrl.com/edit/blog/lastUpdateDate.md", + "frontMatter": { + "date": 2020-01-01T00:00:00.000Z, + "last_update": { + "date": 2021-01-01T00:00:00.000Z, + }, + "slug": "lastUpdateDate", + "title": "Last update date", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": 1609459200, + "lastUpdatedBy": "Author", + "permalink": "/blog/lastUpdateDate", + "prevItem": { + "permalink": "/blog/both", + "title": "Both", + }, + "readingTime": 0.015, + "source": "@site/blog/lastUpdateDate.md", + "tags": [], + "title": "Last update date", + "unlisted": false, + }, + }, +] +`; + +exports[`last update author only 1`] = ` +[ + { + "content": "author", + "id": "author", + "metadata": { + "authors": [ + { + "imageURL": undefined, + "name": "ozaki", + "title": undefined, + "url": undefined, + }, + ], + "date": 2024-03-07T17:28:43.000Z, + "description": "author", + "editUrl": "https://baseEditUrl.com/edit/blog/author.md", + "frontMatter": { + "author": "ozaki", + "last_update": { + "author": "seb", + }, + "slug": "author", + "title": "Author", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": 1539502055, + "lastUpdatedBy": undefined, + "nextItem": { + "permalink": "/blog/nothing", + "title": "Nothing", + }, + "permalink": "/blog/author", + "readingTime": 0.005, + "source": "@site/blog/author.md", + "tags": [], + "title": "Author", + "unlisted": false, + }, + }, + { + "content": "nothing", + "id": "nothing", + "metadata": { + "authors": [], + "date": 2024-03-07T17:28:43.000Z, + "description": "nothing", + "editUrl": "https://baseEditUrl.com/edit/blog/nothing.md", + "frontMatter": { + "slug": "nothing", + "title": "Nothing", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": 1539502055, + "lastUpdatedBy": undefined, + "nextItem": { + "permalink": "/blog/both", + "title": "Both", + }, + "permalink": "/blog/nothing", + "prevItem": { + "permalink": "/blog/author", + "title": "Author", + }, + "readingTime": 0.005, + "source": "@site/blog/nothing.md", + "tags": [], + "title": "Nothing", + "unlisted": false, + }, + }, + { + "content": "last update date", + "id": "both", + "metadata": { + "authors": [ + { + "imageURL": undefined, + "name": "ozaki", + "title": undefined, + "url": undefined, + }, + ], + "date": 2020-01-01T00:00:00.000Z, + "description": "last update date", + "editUrl": "https://baseEditUrl.com/edit/blog/both.md", + "frontMatter": { + "author": "ozaki", + "date": 2020-01-01T00:00:00.000Z, + "last_update": { + "author": "seb", + "date": 2021-01-01T00:00:00.000Z, + }, + "slug": "both", + "title": "Both", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": 1609459200, + "lastUpdatedBy": "seb", + "nextItem": { + "permalink": "/blog/lastUpdateDate", + "title": "Last update date", + }, + "permalink": "/blog/both", + "prevItem": { + "permalink": "/blog/nothing", + "title": "Nothing", + }, + "readingTime": 0.015, + "source": "@site/blog/both.md", + "tags": [], + "title": "Both", + "unlisted": false, + }, + }, + { + "content": "last update date", + "id": "lastUpdateDate", + "metadata": { + "authors": [], + "date": 2020-01-01T00:00:00.000Z, + "description": "last update date", + "editUrl": "https://baseEditUrl.com/edit/blog/lastUpdateDate.md", + "frontMatter": { + "date": 2020-01-01T00:00:00.000Z, + "last_update": { + "date": 2021-01-01T00:00:00.000Z, + }, + "slug": "lastUpdateDate", + "title": "Last update date", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": 1609459200, + "lastUpdatedBy": undefined, + "permalink": "/blog/lastUpdateDate", + "prevItem": { + "permalink": "/blog/both", + "title": "Both", + }, + "readingTime": 0.015, + "source": "@site/blog/lastUpdateDate.md", + "tags": [], + "title": "Last update date", + "unlisted": false, + }, + }, +] +`; + +exports[`last update none 1`] = ` +[ + { + "content": "author", + "id": "author", + "metadata": { + "authors": [ + { + "imageURL": undefined, + "name": "ozaki", + "title": undefined, + "url": undefined, + }, + ], + "date": 2024-03-07T17:28:43.000Z, + "description": "author", + "editUrl": "https://baseEditUrl.com/edit/blog/author.md", + "frontMatter": { + "author": "ozaki", + "last_update": { + "author": "seb", + }, + "slug": "author", + "title": "Author", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": undefined, + "lastUpdatedBy": undefined, + "nextItem": { + "permalink": "/blog/nothing", + "title": "Nothing", + }, + "permalink": "/blog/author", + "readingTime": 0.005, + "source": "@site/blog/author.md", + "tags": [], + "title": "Author", + "unlisted": false, + }, + }, + { + "content": "nothing", + "id": "nothing", + "metadata": { + "authors": [], + "date": 2024-03-07T17:28:43.000Z, + "description": "nothing", + "editUrl": "https://baseEditUrl.com/edit/blog/nothing.md", + "frontMatter": { + "slug": "nothing", + "title": "Nothing", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": undefined, + "lastUpdatedBy": undefined, + "nextItem": { + "permalink": "/blog/both", + "title": "Both", + }, + "permalink": "/blog/nothing", + "prevItem": { + "permalink": "/blog/author", + "title": "Author", + }, + "readingTime": 0.005, + "source": "@site/blog/nothing.md", + "tags": [], + "title": "Nothing", + "unlisted": false, + }, + }, + { + "content": "last update date", + "id": "both", + "metadata": { + "authors": [ + { + "imageURL": undefined, + "name": "ozaki", + "title": undefined, + "url": undefined, + }, + ], + "date": 2020-01-01T00:00:00.000Z, + "description": "last update date", + "editUrl": "https://baseEditUrl.com/edit/blog/both.md", + "frontMatter": { + "author": "ozaki", + "date": 2020-01-01T00:00:00.000Z, + "last_update": { + "author": "seb", + "date": 2021-01-01T00:00:00.000Z, + }, + "slug": "both", + "title": "Both", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": undefined, + "lastUpdatedBy": undefined, + "nextItem": { + "permalink": "/blog/lastUpdateDate", + "title": "Last update date", + }, + "permalink": "/blog/both", + "prevItem": { + "permalink": "/blog/nothing", + "title": "Nothing", + }, + "readingTime": 0.015, + "source": "@site/blog/both.md", + "tags": [], + "title": "Both", + "unlisted": false, + }, + }, + { + "content": "last update date", + "id": "lastUpdateDate", + "metadata": { + "authors": [], + "date": 2020-01-01T00:00:00.000Z, + "description": "last update date", + "editUrl": "https://baseEditUrl.com/edit/blog/lastUpdateDate.md", + "frontMatter": { + "date": 2020-01-01T00:00:00.000Z, + "last_update": { + "date": 2021-01-01T00:00:00.000Z, + }, + "slug": "lastUpdateDate", + "title": "Last update date", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": undefined, + "lastUpdatedBy": undefined, + "permalink": "/blog/lastUpdateDate", + "prevItem": { + "permalink": "/blog/both", + "title": "Both", + }, + "readingTime": 0.015, + "source": "@site/blog/lastUpdateDate.md", + "tags": [], + "title": "Last update date", + "unlisted": false, + }, + }, +] +`; + +exports[`last update time only 1`] = ` +[ + { + "content": "author", + "id": "author", + "metadata": { + "authors": [ + { + "imageURL": undefined, + "name": "ozaki", + "title": undefined, + "url": undefined, + }, + ], + "date": 2024-03-07T17:28:43.000Z, + "description": "author", + "editUrl": "https://baseEditUrl.com/edit/blog/author.md", + "frontMatter": { + "author": "ozaki", + "last_update": { + "author": "seb", + }, + "slug": "author", + "title": "Author", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": undefined, + "lastUpdatedBy": "seb", + "nextItem": { + "permalink": "/blog/nothing", + "title": "Nothing", + }, + "permalink": "/blog/author", + "readingTime": 0.005, + "source": "@site/blog/author.md", + "tags": [], + "title": "Author", + "unlisted": false, + }, + }, + { + "content": "nothing", + "id": "nothing", + "metadata": { + "authors": [], + "date": 2024-03-07T17:28:43.000Z, + "description": "nothing", + "editUrl": "https://baseEditUrl.com/edit/blog/nothing.md", + "frontMatter": { + "slug": "nothing", + "title": "Nothing", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": undefined, + "lastUpdatedBy": "Author", + "nextItem": { + "permalink": "/blog/both", + "title": "Both", + }, + "permalink": "/blog/nothing", + "prevItem": { + "permalink": "/blog/author", + "title": "Author", + }, + "readingTime": 0.005, + "source": "@site/blog/nothing.md", + "tags": [], + "title": "Nothing", + "unlisted": false, + }, + }, + { + "content": "last update date", + "id": "both", + "metadata": { + "authors": [ + { + "imageURL": undefined, + "name": "ozaki", + "title": undefined, + "url": undefined, + }, + ], + "date": 2020-01-01T00:00:00.000Z, + "description": "last update date", + "editUrl": "https://baseEditUrl.com/edit/blog/both.md", + "frontMatter": { + "author": "ozaki", + "date": 2020-01-01T00:00:00.000Z, + "last_update": { + "author": "seb", + "date": 2021-01-01T00:00:00.000Z, + }, + "slug": "both", + "title": "Both", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": 1609459200, + "lastUpdatedBy": "seb", + "nextItem": { + "permalink": "/blog/lastUpdateDate", + "title": "Last update date", + }, + "permalink": "/blog/both", + "prevItem": { + "permalink": "/blog/nothing", + "title": "Nothing", + }, + "readingTime": 0.015, + "source": "@site/blog/both.md", + "tags": [], + "title": "Both", + "unlisted": false, + }, + }, + { + "content": "last update date", + "id": "lastUpdateDate", + "metadata": { + "authors": [], + "date": 2020-01-01T00:00:00.000Z, + "description": "last update date", + "editUrl": "https://baseEditUrl.com/edit/blog/lastUpdateDate.md", + "frontMatter": { + "date": 2020-01-01T00:00:00.000Z, + "last_update": { + "date": 2021-01-01T00:00:00.000Z, + }, + "slug": "lastUpdateDate", + "title": "Last update date", + }, + "hasTruncateMarker": false, + "lastUpdatedAt": undefined, + "lastUpdatedBy": "Author", + "permalink": "/blog/lastUpdateDate", + "prevItem": { + "permalink": "/blog/both", + "title": "Both", + }, + "readingTime": 0.015, + "source": "@site/blog/lastUpdateDate.md", + "tags": [], + "title": "Last update date", + "unlisted": false, + }, + }, +] +`; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts index 9c05dcca944a..b5b253fa527d 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts @@ -6,7 +6,7 @@ */ import {escapeRegexp} from '@docusaurus/utils'; -import {validateBlogPostFrontMatter} from '../frontMatter'; +import {validateBlogPostFrontMatter} from '@docusaurus/utils-validation'; import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog'; // TODO this abstraction reduce verbosity but it makes it harder to debug diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index 49eb7fdc5671..f957ce6f0a25 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -525,17 +525,18 @@ describe('blog plugin', () => { expect(blogPosts).toHaveLength(3); expect(blogPosts).toMatchSnapshot(); }); +}); - it('lastupdated author time posts load content', async () => { - const siteDir = path.join( - __dirname, - '__fixtures__', - 'website-blog-with-tags', - ); +describe('last update', () => { + const siteDir = path.join( + __dirname, + '__fixtures__', + 'website-blog-with-last-update', + ); + it('author and time', async () => { const plugin = await getPlugin( siteDir, { - postsPerPage: 1, showLastUpdateAuthor: true, showLastUpdateTime: true, }, @@ -543,6 +544,46 @@ describe('blog plugin', () => { ); const {blogPosts} = (await plugin.loadContent!())!; + expect(blogPosts).toMatchSnapshot(); + }); + it('author only', async () => { + const plugin = await getPlugin( + siteDir, + { + showLastUpdateAuthor: false, + showLastUpdateTime: true, + }, + DefaultI18N, + ); + const {blogPosts} = (await plugin.loadContent!())!; + + expect(blogPosts).toMatchSnapshot(); + }); + it('time only', async () => { + const plugin = await getPlugin( + siteDir, + { + showLastUpdateAuthor: true, + showLastUpdateTime: false, + }, + DefaultI18N, + ); + const {blogPosts} = (await plugin.loadContent!())!; + + expect(blogPosts).toMatchSnapshot(); + }); + + it('none', async () => { + const plugin = await getPlugin( + siteDir, + { + showLastUpdateAuthor: false, + showLastUpdateTime: false, + }, + DefaultI18N, + ); + const {blogPosts} = (await plugin.loadContent!())!; + expect(blogPosts).toMatchSnapshot(); }); }); diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index ad4f485d0783..611edad9b513 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -28,7 +28,7 @@ import { isDraft, } from '@docusaurus/utils'; import {readLastUpdateData} from '@docusaurus/utils/lib/lastUpdateUtils'; -import {validateBlogPostFrontMatter} from './frontMatter'; +import {validateBlogPostFrontMatter} from '@docusaurus/utils-validation'; import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors'; import type {LoadContext, ParseFrontMatter} from '@docusaurus/types'; import type { diff --git a/packages/docusaurus-plugin-content-blog/src/frontMatter.ts b/packages/docusaurus-plugin-content-blog/src/frontMatter.ts deleted file mode 100644 index c256e66920ed..000000000000 --- a/packages/docusaurus-plugin-content-blog/src/frontMatter.ts +++ /dev/null @@ -1,93 +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 { - JoiFrontMatter as Joi, // Custom instance for front matter - URISchema, - validateFrontMatter, - FrontMatterTagsSchema, - FrontMatterTOCHeadingLevels, - ContentVisibilitySchema, -} from '@docusaurus/utils-validation'; -import { - FrontMatterAuthorErrorMessage, - FrontMatterLastUpdateErrorMessage, -} from '@docusaurus/utils/lib/lastUpdateUtils'; -import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog'; - -const BlogPostFrontMatterAuthorSchema = Joi.object({ - key: Joi.string(), - name: Joi.string(), - title: Joi.string(), - url: URISchema, - imageURL: Joi.string(), -}) - .or('key', 'name', 'imageURL') - .rename('image_url', 'imageURL', {alias: true}); - -const BlogFrontMatterSchema = Joi.object({ - id: Joi.string(), - title: Joi.string().allow(''), - description: Joi.string().allow(''), - tags: FrontMatterTagsSchema, - date: Joi.date().raw(), - - // New multi-authors front matter: - authors: Joi.alternatives() - .try( - Joi.string(), - BlogPostFrontMatterAuthorSchema, - Joi.array() - .items(Joi.string(), BlogPostFrontMatterAuthorSchema) - .messages({ - 'array.sparse': FrontMatterAuthorErrorMessage, - 'array.includes': FrontMatterAuthorErrorMessage, - }), - ) - .messages({ - 'alternatives.match': FrontMatterAuthorErrorMessage, - }), - // Legacy author front matter - author: Joi.string(), - author_title: Joi.string(), - author_url: URISchema, - author_image_url: URISchema, - // TODO enable deprecation warnings later - authorURL: URISchema, - // .warning('deprecate.error', { alternative: '"author_url"'}), - authorTitle: Joi.string(), - // .warning('deprecate.error', { alternative: '"author_title"'}), - authorImageURL: URISchema, - // .warning('deprecate.error', { alternative: '"author_image_url"'}), - - slug: Joi.string(), - image: URISchema, - keywords: Joi.array().items(Joi.string().required()), - hide_table_of_contents: Joi.boolean(), - - ...FrontMatterTOCHeadingLevels, - last_update: Joi.object({ - author: Joi.string(), - date: Joi.date().raw(), - }) - .or('author', 'date') - .messages({ - 'object.missing': FrontMatterLastUpdateErrorMessage, - 'object.base': FrontMatterLastUpdateErrorMessage, - }), -}) - .messages({ - 'deprecate.error': - '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.', - }) - .concat(ContentVisibilitySchema); - -export function validateBlogPostFrontMatter(frontMatter: { - [key: string]: unknown; -}): BlogPostFrontMatter { - return validateFrontMatter(frontMatter, BlogFrontMatterSchema); -} diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts index 8589a707681b..65b691f11cb8 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts @@ -6,7 +6,7 @@ */ import {escapeRegexp} from '@docusaurus/utils'; -import {validateDocFrontMatter} from '../frontMatter'; +import {validateDocFrontMatter} from '@docusaurus/utils-validation'; import type {DocFrontMatter} from '@docusaurus/plugin-content-docs'; function testField(params: { diff --git a/packages/docusaurus-plugin-content-docs/src/docs.ts b/packages/docusaurus-plugin-content-docs/src/docs.ts index ebc5e9c06bd7..4f23177b4547 100644 --- a/packages/docusaurus-plugin-content-docs/src/docs.ts +++ b/packages/docusaurus-plugin-content-docs/src/docs.ts @@ -22,9 +22,9 @@ import { isDraft, } from '@docusaurus/utils'; import {readLastUpdateData} from '@docusaurus/utils/lib/lastUpdateUtils'; +import {validateDocFrontMatter} from '@docusaurus/utils-validation'; import getSlug from './slug'; import {stripPathNumberPrefixes} from './numberPrefix'; -import {validateDocFrontMatter} from './frontMatter'; import {toDocNavigationLink, toNavigationLink} from './sidebars/utils'; import type { MetadataOptions, diff --git a/packages/docusaurus-plugin-content-docs/src/frontMatter.ts b/packages/docusaurus-plugin-content-docs/src/frontMatter.ts deleted file mode 100644 index 2475e692ab57..000000000000 --- a/packages/docusaurus-plugin-content-docs/src/frontMatter.ts +++ /dev/null @@ -1,63 +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 { - JoiFrontMatter as Joi, // Custom instance for front matter - URISchema, - FrontMatterTagsSchema, - FrontMatterTOCHeadingLevels, - validateFrontMatter, - ContentVisibilitySchema, -} from '@docusaurus/utils-validation'; -import {FrontMatterLastUpdateErrorMessage} from '@docusaurus/utils/lib/lastUpdateUtils'; -import type {DocFrontMatter} from '@docusaurus/plugin-content-docs'; - -// NOTE: we don't add any default value on purpose here -// We don't want default values to magically appear in doc metadata and props -// While the user did not provide those values explicitly -// We use default values in code instead -const DocFrontMatterSchema = Joi.object({ - id: Joi.string(), - // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 - title: Joi.string().allow(''), - hide_title: Joi.boolean(), - hide_table_of_contents: Joi.boolean(), - keywords: Joi.array().items(Joi.string().required()), - image: URISchema, - // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 - description: Joi.string().allow(''), - slug: Joi.string(), - sidebar_label: Joi.string(), - sidebar_position: Joi.number(), - sidebar_class_name: Joi.string(), - sidebar_custom_props: Joi.object().unknown(), - displayed_sidebar: Joi.string().allow(null), - tags: FrontMatterTagsSchema, - pagination_label: Joi.string(), - custom_edit_url: URISchema.allow('', null), - parse_number_prefixes: Joi.boolean(), - pagination_next: Joi.string().allow(null), - pagination_prev: Joi.string().allow(null), - ...FrontMatterTOCHeadingLevels, - last_update: Joi.object({ - author: Joi.string(), - date: Joi.date().raw(), - }) - .or('author', 'date') - .messages({ - 'object.missing': FrontMatterLastUpdateErrorMessage, - 'object.base': FrontMatterLastUpdateErrorMessage, - }), -}) - .unknown() - .concat(ContentVisibilitySchema); - -export function validateDocFrontMatter(frontMatter: { - [key: string]: unknown; -}): DocFrontMatter { - return validateFrontMatter(frontMatter, DocFrontMatterSchema); -} diff --git a/packages/docusaurus-utils-validation/src/index.ts b/packages/docusaurus-utils-validation/src/index.ts index 8c1dfb5ae09e..2cf0038b7427 100644 --- a/packages/docusaurus-utils-validation/src/index.ts +++ b/packages/docusaurus-utils-validation/src/index.ts @@ -26,4 +26,9 @@ export { FrontMatterTagsSchema, FrontMatterTOCHeadingLevels, ContentVisibilitySchema, + FrontMatterAuthorErrorMessage, + FrontMatterLastUpdateErrorMessage, + FrontMatterLastUpdateSchema, + validateBlogPostFrontMatter, + validateDocFrontMatter, } from './validationSchemas'; diff --git a/packages/docusaurus-utils-validation/src/validationSchemas.ts b/packages/docusaurus-utils-validation/src/validationSchemas.ts index a0a8c72f16b7..e729b14c9db1 100644 --- a/packages/docusaurus-utils-validation/src/validationSchemas.ts +++ b/packages/docusaurus-utils-validation/src/validationSchemas.ts @@ -13,6 +13,9 @@ import { } from '@docusaurus/utils'; import Joi from './Joi'; import {JoiFrontMatter} from './JoiFrontMatter'; +import {validateFrontMatter} from '.'; +import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog'; +import type {DocFrontMatter} from '@docusaurus/plugin-content-docs'; export const PluginIdSchema = Joi.string() .regex(/^[\w-]+$/) @@ -167,3 +170,122 @@ export const ContentVisibilitySchema = JoiFrontMatter.object( "Can't be draft and unlisted at the same time.", }) .unknown(); + +export const FrontMatterAuthorErrorMessage = + '{{#label}} does not look like a valid blog post author. Please use an author key or an author object (with a key and/or name).'; + +export const FrontMatterLastUpdateErrorMessage = + '{{#label}} does not look like a valid last update object. Please use an author key with a string or a date with a string or Date.'; + +export const FrontMatterLastUpdateSchema = Joi.object({ + author: Joi.string(), + date: Joi.date().raw(), +}) + .or('author', 'date') + .messages({ + 'object.missing': FrontMatterLastUpdateErrorMessage, + 'object.base': FrontMatterLastUpdateErrorMessage, + }); + +const BlogPostFrontMatterAuthorSchema = Joi.object({ + key: Joi.string(), + name: Joi.string(), + title: Joi.string(), + url: URISchema, + imageURL: Joi.string(), +}) + .or('key', 'name', 'imageURL') + .rename('image_url', 'imageURL', {alias: true}); + +// NOTE: we don't add any default value on purpose here +// We don't want default values to magically appear in doc metadata and props +// While the user did not provide those values explicitly +// We use default values in code instead +const DocFrontMatterSchema = Joi.object({ + id: Joi.string(), + // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 + title: Joi.string().allow(''), + hide_title: Joi.boolean(), + hide_table_of_contents: Joi.boolean(), + keywords: Joi.array().items(Joi.string().required()), + image: URISchema, + // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 + description: Joi.string().allow(''), + slug: Joi.string(), + sidebar_label: Joi.string(), + sidebar_position: Joi.number(), + sidebar_class_name: Joi.string(), + sidebar_custom_props: Joi.object().unknown(), + displayed_sidebar: Joi.string().allow(null), + tags: FrontMatterTagsSchema, + pagination_label: Joi.string(), + custom_edit_url: URISchema.allow('', null), + parse_number_prefixes: Joi.boolean(), + pagination_next: Joi.string().allow(null), + pagination_prev: Joi.string().allow(null), + ...FrontMatterTOCHeadingLevels, + last_update: FrontMatterLastUpdateSchema, +}) + .unknown() + .concat(ContentVisibilitySchema); + +const BlogFrontMatterSchema = Joi.object({ + id: Joi.string(), + title: Joi.string().allow(''), + description: Joi.string().allow(''), + tags: FrontMatterTagsSchema, + date: Joi.date().raw(), + + // New multi-authors front matter: + authors: Joi.alternatives() + .try( + Joi.string(), + BlogPostFrontMatterAuthorSchema, + Joi.array() + .items(Joi.string(), BlogPostFrontMatterAuthorSchema) + .messages({ + 'array.sparse': FrontMatterAuthorErrorMessage, + 'array.includes': FrontMatterAuthorErrorMessage, + }), + ) + .messages({ + 'alternatives.match': FrontMatterAuthorErrorMessage, + }), + // Legacy author front matter + author: Joi.string(), + author_title: Joi.string(), + author_url: URISchema, + author_image_url: URISchema, + // TODO enable deprecation warnings later + authorURL: URISchema, + // .warning('deprecate.error', { alternative: '"author_url"'}), + authorTitle: Joi.string(), + // .warning('deprecate.error', { alternative: '"author_title"'}), + authorImageURL: URISchema, + // .warning('deprecate.error', { alternative: '"author_image_url"'}), + + slug: Joi.string(), + image: URISchema, + keywords: Joi.array().items(Joi.string().required()), + hide_table_of_contents: Joi.boolean(), + + ...FrontMatterTOCHeadingLevels, + last_update: FrontMatterLastUpdateSchema, +}) + .messages({ + 'deprecate.error': + '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.', + }) + .concat(ContentVisibilitySchema); + +export function validateBlogPostFrontMatter(frontMatter: { + [key: string]: unknown; +}): BlogPostFrontMatter { + return validateFrontMatter(frontMatter, BlogFrontMatterSchema); +} + +export function validateDocFrontMatter(frontMatter: { + [key: string]: unknown; +}): DocFrontMatter { + return validateFrontMatter(frontMatter, DocFrontMatterSchema); +} diff --git a/packages/docusaurus-utils/src/lastUpdateUtils.ts b/packages/docusaurus-utils/src/lastUpdateUtils.ts index 6ca7f0e783d9..56cc28d78cb3 100644 --- a/packages/docusaurus-utils/src/lastUpdateUtils.ts +++ b/packages/docusaurus-utils/src/lastUpdateUtils.ts @@ -115,9 +115,3 @@ export async function readLastUpdateData( return {}; } - -export const FrontMatterAuthorErrorMessage = - '{{#label}} does not look like a valid blog post author. Please use an author key or an author object (with a key and/or name).'; - -export const FrontMatterLastUpdateErrorMessage = - '{{#label}} does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).'; From 7bb0310b504a557a4078a129193d9843749ae79f Mon Sep 17 00:00:00 2001 From: OzakIOne Date: Thu, 7 Mar 2024 21:28:13 +0000 Subject: [PATCH 14/43] refactor: apply lint autofix --- project-words.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project-words.txt b/project-words.txt index 31716b09226d..0c2fcf44cbc6 100644 --- a/project-words.txt +++ b/project-words.txt @@ -155,7 +155,6 @@ Knapen Koyeb Koyeb's Lamana -lastupdated Lifecycles lifecycles Linkify @@ -226,6 +225,7 @@ orta Outerbounds outerbounds overrideable +ozaki O’Shannessy pageview Palenight From ba1d743e7694f8384b52d6637ab8d46a3a0b7b6e Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:02:27 +0100 Subject: [PATCH 15/43] refactor: review comments --- .../src/__tests__/frontMatter.test.ts | 2 +- .../src/blogUtils.ts | 4 +- .../src/frontMatter.ts | 82 ++++++++++++++ .../src/__tests__/frontMatter.test.ts | 10 +- .../src/docs.ts | 4 +- .../src/frontMatter.ts | 54 +++++++++ .../docusaurus-utils-validation/src/index.ts | 2 - .../src/validationSchemas.ts | 106 ------------------ packages/docusaurus-utils/src/index.ts | 1 + 9 files changed, 147 insertions(+), 118 deletions(-) create mode 100644 packages/docusaurus-plugin-content-blog/src/frontMatter.ts create mode 100644 packages/docusaurus-plugin-content-docs/src/frontMatter.ts diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts index b5b253fa527d..9c05dcca944a 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts @@ -6,7 +6,7 @@ */ import {escapeRegexp} from '@docusaurus/utils'; -import {validateBlogPostFrontMatter} from '@docusaurus/utils-validation'; +import {validateBlogPostFrontMatter} from '../frontMatter'; import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog'; // TODO this abstraction reduce verbosity but it makes it harder to debug diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index 611edad9b513..ff8b5b892915 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -26,9 +26,9 @@ import { getContentPathList, isUnlisted, isDraft, + readLastUpdateData, } from '@docusaurus/utils'; -import {readLastUpdateData} from '@docusaurus/utils/lib/lastUpdateUtils'; -import {validateBlogPostFrontMatter} from '@docusaurus/utils-validation'; +import {validateBlogPostFrontMatter} from './frontMatter'; import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors'; import type {LoadContext, ParseFrontMatter} from '@docusaurus/types'; import type { diff --git a/packages/docusaurus-plugin-content-blog/src/frontMatter.ts b/packages/docusaurus-plugin-content-blog/src/frontMatter.ts new file mode 100644 index 000000000000..992a3be7f125 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/frontMatter.ts @@ -0,0 +1,82 @@ +/** + * 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 { + ContentVisibilitySchema, + FrontMatterAuthorErrorMessage, + FrontMatterLastUpdateSchema, + FrontMatterTOCHeadingLevels, + FrontMatterTagsSchema, + JoiFrontMatter as Joi, // Custom instance for front matter + URISchema, + validateFrontMatter, +} from '@docusaurus/utils-validation'; +import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog'; + +const BlogPostFrontMatterAuthorSchema = Joi.object({ + key: Joi.string(), + name: Joi.string(), + title: Joi.string(), + url: URISchema, + imageURL: Joi.string(), +}) + .or('key', 'name', 'imageURL') + .rename('image_url', 'imageURL', {alias: true}); + +const BlogFrontMatterSchema = Joi.object({ + id: Joi.string(), + title: Joi.string().allow(''), + description: Joi.string().allow(''), + tags: FrontMatterTagsSchema, + date: Joi.date().raw(), + + // New multi-authors front matter: + authors: Joi.alternatives() + .try( + Joi.string(), + BlogPostFrontMatterAuthorSchema, + Joi.array() + .items(Joi.string(), BlogPostFrontMatterAuthorSchema) + .messages({ + 'array.sparse': FrontMatterAuthorErrorMessage, + 'array.includes': FrontMatterAuthorErrorMessage, + }), + ) + .messages({ + 'alternatives.match': FrontMatterAuthorErrorMessage, + }), + // Legacy author front matter + author: Joi.string(), + author_title: Joi.string(), + author_url: URISchema, + author_image_url: URISchema, + // TODO enable deprecation warnings later + authorURL: URISchema, + // .warning('deprecate.error', { alternative: '"author_url"'}), + authorTitle: Joi.string(), + // .warning('deprecate.error', { alternative: '"author_title"'}), + authorImageURL: URISchema, + // .warning('deprecate.error', { alternative: '"author_image_url"'}), + + slug: Joi.string(), + image: URISchema, + keywords: Joi.array().items(Joi.string().required()), + hide_table_of_contents: Joi.boolean(), + + ...FrontMatterTOCHeadingLevels, + last_update: FrontMatterLastUpdateSchema, +}) + .messages({ + 'deprecate.error': + '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.', + }) + .concat(ContentVisibilitySchema); + +export function validateBlogPostFrontMatter(frontMatter: { + [key: string]: unknown; +}): BlogPostFrontMatter { + return validateFrontMatter(frontMatter, BlogFrontMatterSchema); +} diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts index 65b691f11cb8..2219ad86af55 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts @@ -6,7 +6,7 @@ */ import {escapeRegexp} from '@docusaurus/utils'; -import {validateDocFrontMatter} from '@docusaurus/utils-validation'; +import {validateDocFrontMatter} from '../frontMatter'; import type {DocFrontMatter} from '@docusaurus/plugin-content-docs'; function testField(params: { @@ -444,19 +444,19 @@ describe('validateDocFrontMatter last_update', () => { invalidFrontMatters: [ [ {last_update: null}, - 'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).', + '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', ], [ {last_update: {}}, - 'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).', + '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', ], [ {last_update: ''}, - 'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).', + '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', ], [ {last_update: {invalid: 'key'}}, - 'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).', + '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', ], [ {last_update: {author: 'test author', date: 'I am not a date :('}}, diff --git a/packages/docusaurus-plugin-content-docs/src/docs.ts b/packages/docusaurus-plugin-content-docs/src/docs.ts index 4f23177b4547..7c94593fa0cf 100644 --- a/packages/docusaurus-plugin-content-docs/src/docs.ts +++ b/packages/docusaurus-plugin-content-docs/src/docs.ts @@ -20,9 +20,9 @@ import { normalizeFrontMatterTags, isUnlisted, isDraft, + readLastUpdateData, } from '@docusaurus/utils'; -import {readLastUpdateData} from '@docusaurus/utils/lib/lastUpdateUtils'; -import {validateDocFrontMatter} from '@docusaurus/utils-validation'; +import {validateDocFrontMatter} from './frontMatter'; import getSlug from './slug'; import {stripPathNumberPrefixes} from './numberPrefix'; import {toDocNavigationLink, toNavigationLink} from './sidebars/utils'; diff --git a/packages/docusaurus-plugin-content-docs/src/frontMatter.ts b/packages/docusaurus-plugin-content-docs/src/frontMatter.ts new file mode 100644 index 000000000000..de315cddab9c --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/frontMatter.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { + JoiFrontMatter as Joi, // Custom instance for front matter + URISchema, + FrontMatterTagsSchema, + FrontMatterTOCHeadingLevels, + validateFrontMatter, + ContentVisibilitySchema, + FrontMatterLastUpdateSchema, +} from '@docusaurus/utils-validation'; +import type {DocFrontMatter} from '@docusaurus/plugin-content-docs'; + +// NOTE: we don't add any default value on purpose here +// We don't want default values to magically appear in doc metadata and props +// While the user did not provide those values explicitly +// We use default values in code instead +export const DocFrontMatterSchema = Joi.object({ + id: Joi.string(), + // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 + title: Joi.string().allow(''), + hide_title: Joi.boolean(), + hide_table_of_contents: Joi.boolean(), + keywords: Joi.array().items(Joi.string().required()), + image: URISchema, + // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 + description: Joi.string().allow(''), + slug: Joi.string(), + sidebar_label: Joi.string(), + sidebar_position: Joi.number(), + sidebar_class_name: Joi.string(), + sidebar_custom_props: Joi.object().unknown(), + displayed_sidebar: Joi.string().allow(null), + tags: FrontMatterTagsSchema, + pagination_label: Joi.string(), + custom_edit_url: URISchema.allow('', null), + parse_number_prefixes: Joi.boolean(), + pagination_next: Joi.string().allow(null), + pagination_prev: Joi.string().allow(null), + ...FrontMatterTOCHeadingLevels, + last_update: FrontMatterLastUpdateSchema, +}) + .unknown() + .concat(ContentVisibilitySchema); + +export function validateDocFrontMatter(frontMatter: { + [key: string]: unknown; +}): DocFrontMatter { + return validateFrontMatter(frontMatter, DocFrontMatterSchema); +} diff --git a/packages/docusaurus-utils-validation/src/index.ts b/packages/docusaurus-utils-validation/src/index.ts index 2cf0038b7427..af3ad320043e 100644 --- a/packages/docusaurus-utils-validation/src/index.ts +++ b/packages/docusaurus-utils-validation/src/index.ts @@ -29,6 +29,4 @@ export { FrontMatterAuthorErrorMessage, FrontMatterLastUpdateErrorMessage, FrontMatterLastUpdateSchema, - validateBlogPostFrontMatter, - validateDocFrontMatter, } from './validationSchemas'; diff --git a/packages/docusaurus-utils-validation/src/validationSchemas.ts b/packages/docusaurus-utils-validation/src/validationSchemas.ts index e729b14c9db1..d0ee733a09ef 100644 --- a/packages/docusaurus-utils-validation/src/validationSchemas.ts +++ b/packages/docusaurus-utils-validation/src/validationSchemas.ts @@ -13,9 +13,6 @@ import { } from '@docusaurus/utils'; import Joi from './Joi'; import {JoiFrontMatter} from './JoiFrontMatter'; -import {validateFrontMatter} from '.'; -import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog'; -import type {DocFrontMatter} from '@docusaurus/plugin-content-docs'; export const PluginIdSchema = Joi.string() .regex(/^[\w-]+$/) @@ -186,106 +183,3 @@ export const FrontMatterLastUpdateSchema = Joi.object({ 'object.missing': FrontMatterLastUpdateErrorMessage, 'object.base': FrontMatterLastUpdateErrorMessage, }); - -const BlogPostFrontMatterAuthorSchema = Joi.object({ - key: Joi.string(), - name: Joi.string(), - title: Joi.string(), - url: URISchema, - imageURL: Joi.string(), -}) - .or('key', 'name', 'imageURL') - .rename('image_url', 'imageURL', {alias: true}); - -// NOTE: we don't add any default value on purpose here -// We don't want default values to magically appear in doc metadata and props -// While the user did not provide those values explicitly -// We use default values in code instead -const DocFrontMatterSchema = Joi.object({ - id: Joi.string(), - // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 - title: Joi.string().allow(''), - hide_title: Joi.boolean(), - hide_table_of_contents: Joi.boolean(), - keywords: Joi.array().items(Joi.string().required()), - image: URISchema, - // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 - description: Joi.string().allow(''), - slug: Joi.string(), - sidebar_label: Joi.string(), - sidebar_position: Joi.number(), - sidebar_class_name: Joi.string(), - sidebar_custom_props: Joi.object().unknown(), - displayed_sidebar: Joi.string().allow(null), - tags: FrontMatterTagsSchema, - pagination_label: Joi.string(), - custom_edit_url: URISchema.allow('', null), - parse_number_prefixes: Joi.boolean(), - pagination_next: Joi.string().allow(null), - pagination_prev: Joi.string().allow(null), - ...FrontMatterTOCHeadingLevels, - last_update: FrontMatterLastUpdateSchema, -}) - .unknown() - .concat(ContentVisibilitySchema); - -const BlogFrontMatterSchema = Joi.object({ - id: Joi.string(), - title: Joi.string().allow(''), - description: Joi.string().allow(''), - tags: FrontMatterTagsSchema, - date: Joi.date().raw(), - - // New multi-authors front matter: - authors: Joi.alternatives() - .try( - Joi.string(), - BlogPostFrontMatterAuthorSchema, - Joi.array() - .items(Joi.string(), BlogPostFrontMatterAuthorSchema) - .messages({ - 'array.sparse': FrontMatterAuthorErrorMessage, - 'array.includes': FrontMatterAuthorErrorMessage, - }), - ) - .messages({ - 'alternatives.match': FrontMatterAuthorErrorMessage, - }), - // Legacy author front matter - author: Joi.string(), - author_title: Joi.string(), - author_url: URISchema, - author_image_url: URISchema, - // TODO enable deprecation warnings later - authorURL: URISchema, - // .warning('deprecate.error', { alternative: '"author_url"'}), - authorTitle: Joi.string(), - // .warning('deprecate.error', { alternative: '"author_title"'}), - authorImageURL: URISchema, - // .warning('deprecate.error', { alternative: '"author_image_url"'}), - - slug: Joi.string(), - image: URISchema, - keywords: Joi.array().items(Joi.string().required()), - hide_table_of_contents: Joi.boolean(), - - ...FrontMatterTOCHeadingLevels, - last_update: FrontMatterLastUpdateSchema, -}) - .messages({ - 'deprecate.error': - '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.', - }) - .concat(ContentVisibilitySchema); - -export function validateBlogPostFrontMatter(frontMatter: { - [key: string]: unknown; -}): BlogPostFrontMatter { - return validateFrontMatter(frontMatter, BlogFrontMatterSchema); -} - -export function validateDocFrontMatter(frontMatter: { - [key: string]: unknown; -}): DocFrontMatter { - return validateFrontMatter(frontMatter, DocFrontMatterSchema); -} diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index f32d1d9e4240..316b7136d005 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -123,4 +123,5 @@ export { getFileLastUpdate, type LastUpdateData, type FrontMatterLastUpdate, + readLastUpdateData, } from './lastUpdateUtils'; From b0a22d661a448dcfae4467653d43034a6a7cad3e Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:07:36 +0100 Subject: [PATCH 16/43] move tests --- .../src/__tests__/lastUpdate.test.ts | 36 ------------------- .../src/__tests__/gitUtils.test.ts | 33 +++++++++++++++++ 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts index 5ed81e80b631..6f8b9d9c60ef 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts @@ -6,10 +6,8 @@ */ import {jest} from '@jest/globals'; -import fs from 'fs-extra'; import path from 'path'; import shell from 'shelljs'; -import {createTempRepo} from '@testing-utils/git'; import {getFileLastUpdate} from '@docusaurus/utils'; describe('getFileLastUpdate', () => { @@ -62,40 +60,6 @@ describe('getFileLastUpdate', () => { consoleMock.mockRestore(); }); - it('temporary created file that is not tracked by git', async () => { - const consoleMock = jest - .spyOn(console, 'warn') - .mockImplementation(() => {}); - const {repoDir} = createTempRepo(); - const tempFilePath = path.join(repoDir, 'file.md'); - await fs.writeFile(tempFilePath, 'Lorem ipsum :)'); - await expect(getFileLastUpdate(tempFilePath)).resolves.toBeNull(); - expect(consoleMock).toHaveBeenCalledTimes(1); - expect(consoleMock).toHaveBeenLastCalledWith( - expect.stringMatching(/not tracked by git./), - ); - await fs.unlink(tempFilePath); - }); - - it('multiple files not tracked by git', async () => { - const consoleMock = jest - .spyOn(console, 'warn') - .mockImplementation(() => {}); - const {repoDir} = createTempRepo(); - const tempFilePath1 = path.join(repoDir, 'file1.md'); - const tempFilePath2 = path.join(repoDir, 'file2.md'); - await fs.writeFile(tempFilePath1, 'Lorem ipsum :)'); - await fs.writeFile(tempFilePath2, 'Lorem ipsum :)'); - await expect(getFileLastUpdate(tempFilePath1)).resolves.toBeNull(); - await expect(getFileLastUpdate(tempFilePath2)).resolves.toBeNull(); - expect(consoleMock).toHaveBeenCalledTimes(1); - expect(consoleMock).toHaveBeenLastCalledWith( - expect.stringMatching(/not tracked by git./), - ); - await fs.unlink(tempFilePath1); - await fs.unlink(tempFilePath2); - }); - it('git does not exist', async () => { const mock = jest.spyOn(shell, 'which').mockImplementationOnce(() => null); const consoleMock = jest diff --git a/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts b/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts index f99d3ea3df55..6118e4058964 100644 --- a/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts @@ -9,6 +9,7 @@ import fs from 'fs-extra'; import path from 'path'; import {createTempRepo} from '@testing-utils/git'; import {FileNotTrackedError, getFileCommitDate} from '../gitUtils'; +import {getFileLastUpdate} from '../lastUpdateUtils'; /* eslint-disable no-restricted-properties */ function initializeTempRepo() { @@ -136,4 +137,36 @@ describe('getFileCommitDate', () => { /Failed to retrieve git history for ".*nonexistent.txt" because the file does not exist./, ); }); + + it('temporary created file that is not tracked by git', async () => { + const consoleMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + const tempFilePath = path.join(repoDir, 'file.md'); + await fs.writeFile(tempFilePath, 'Lorem ipsum :)'); + await expect(getFileLastUpdate(tempFilePath)).resolves.toBeNull(); + expect(consoleMock).toHaveBeenCalledTimes(1); + expect(consoleMock).toHaveBeenLastCalledWith( + expect.stringMatching(/not tracked by git./), + ); + await fs.unlink(tempFilePath); + }); + + it('multiple files not tracked by git', async () => { + const consoleMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + const tempFilePath1 = path.join(repoDir, 'file1.md'); + const tempFilePath2 = path.join(repoDir, 'file2.md'); + await fs.writeFile(tempFilePath1, 'Lorem ipsum :)'); + await fs.writeFile(tempFilePath2, 'Lorem ipsum :)'); + await expect(getFileLastUpdate(tempFilePath1)).resolves.toBeNull(); + await expect(getFileLastUpdate(tempFilePath2)).resolves.toBeNull(); + expect(consoleMock).toHaveBeenCalledTimes(1); + expect(consoleMock).toHaveBeenLastCalledWith( + expect.stringMatching(/not tracked by git./), + ); + await fs.unlink(tempFilePath1); + await fs.unlink(tempFilePath2); + }); }); From 92ef13f7fcb03bc882541da45fb0414627bade3a Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Mon, 11 Mar 2024 15:41:28 +0100 Subject: [PATCH 17/43] wip review --- .../src/frontMatter.ts | 4 +- .../src/__tests__/lastUpdate.test.ts | 79 ------- .../docusaurus-utils-validation/src/index.ts | 1 - .../src/validationSchemas.ts | 3 - .../simple-site/doc with space.md | 1 + .../__fixtures__/simple-site/hello.md | 7 + .../src/__tests__/gitUtils.test.ts | 14 -- .../src/__tests__/lastUpdateUtils.test.ts | 206 ++++++++++++++++++ 8 files changed, 217 insertions(+), 98 deletions(-) delete mode 100644 packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts create mode 100644 packages/docusaurus-utils/src/__tests__/__fixtures__/simple-site/doc with space.md create mode 100644 packages/docusaurus-utils/src/__tests__/__fixtures__/simple-site/hello.md create mode 100644 packages/docusaurus-utils/src/__tests__/lastUpdateUtils.test.ts diff --git a/packages/docusaurus-plugin-content-blog/src/frontMatter.ts b/packages/docusaurus-plugin-content-blog/src/frontMatter.ts index 992a3be7f125..bb1b19acf908 100644 --- a/packages/docusaurus-plugin-content-blog/src/frontMatter.ts +++ b/packages/docusaurus-plugin-content-blog/src/frontMatter.ts @@ -6,7 +6,6 @@ */ import { ContentVisibilitySchema, - FrontMatterAuthorErrorMessage, FrontMatterLastUpdateSchema, FrontMatterTOCHeadingLevels, FrontMatterTagsSchema, @@ -16,6 +15,9 @@ import { } from '@docusaurus/utils-validation'; import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog'; +const FrontMatterAuthorErrorMessage = + '{{#label}} does not look like a valid blog post author. Please use an author key or an author object (with a key and/or name).'; + const BlogPostFrontMatterAuthorSchema = Joi.object({ key: Joi.string(), name: Joi.string(), diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts deleted file mode 100644 index 6f8b9d9c60ef..000000000000 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts +++ /dev/null @@ -1,79 +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 {jest} from '@jest/globals'; -import path from 'path'; -import shell from 'shelljs'; -import {getFileLastUpdate} from '@docusaurus/utils'; - -describe('getFileLastUpdate', () => { - const existingFilePath = path.join( - __dirname, - '__fixtures__/simple-site/docs/hello.md', - ); - it('existing test file in repository with Git timestamp', async () => { - const lastUpdateData = await getFileLastUpdate(existingFilePath); - expect(lastUpdateData).not.toBeNull(); - - const {author, timestamp} = lastUpdateData!; - expect(author).not.toBeNull(); - expect(typeof author).toBe('string'); - - expect(timestamp).not.toBeNull(); - expect(typeof timestamp).toBe('number'); - }); - - it('existing test file with spaces in path', async () => { - const filePathWithSpace = path.join( - __dirname, - '__fixtures__/simple-site/docs/doc with space.md', - ); - const lastUpdateData = await getFileLastUpdate(filePathWithSpace); - expect(lastUpdateData).not.toBeNull(); - - const {author, timestamp} = lastUpdateData!; - expect(author).not.toBeNull(); - expect(typeof author).toBe('string'); - - expect(timestamp).not.toBeNull(); - expect(typeof timestamp).toBe('number'); - }); - - it('non-existing file', async () => { - const consoleMock = jest - .spyOn(console, 'warn') - .mockImplementation(() => {}); - const nonExistingFileName = '.nonExisting'; - const nonExistingFilePath = path.join( - __dirname, - '__fixtures__', - nonExistingFileName, - ); - await expect(getFileLastUpdate(nonExistingFilePath)).rejects.toThrow( - /An error occurred when trying to get the last update date/, - ); - expect(consoleMock).toHaveBeenCalledTimes(0); - consoleMock.mockRestore(); - }); - - it('git does not exist', async () => { - const mock = jest.spyOn(shell, 'which').mockImplementationOnce(() => null); - const consoleMock = jest - .spyOn(console, 'warn') - .mockImplementation(() => {}); - const lastUpdateData = await getFileLastUpdate(existingFilePath); - expect(lastUpdateData).toBeNull(); - expect(consoleMock).toHaveBeenLastCalledWith( - expect.stringMatching( - /.*\[WARNING\].* Sorry, the docs plugin last update options require Git\..*/, - ), - ); - - consoleMock.mockRestore(); - mock.mockRestore(); - }); -}); diff --git a/packages/docusaurus-utils-validation/src/index.ts b/packages/docusaurus-utils-validation/src/index.ts index af3ad320043e..eff1cb28d24f 100644 --- a/packages/docusaurus-utils-validation/src/index.ts +++ b/packages/docusaurus-utils-validation/src/index.ts @@ -26,7 +26,6 @@ export { FrontMatterTagsSchema, FrontMatterTOCHeadingLevels, ContentVisibilitySchema, - FrontMatterAuthorErrorMessage, FrontMatterLastUpdateErrorMessage, FrontMatterLastUpdateSchema, } from './validationSchemas'; diff --git a/packages/docusaurus-utils-validation/src/validationSchemas.ts b/packages/docusaurus-utils-validation/src/validationSchemas.ts index d0ee733a09ef..2e624458f5bc 100644 --- a/packages/docusaurus-utils-validation/src/validationSchemas.ts +++ b/packages/docusaurus-utils-validation/src/validationSchemas.ts @@ -168,9 +168,6 @@ export const ContentVisibilitySchema = JoiFrontMatter.object( }) .unknown(); -export const FrontMatterAuthorErrorMessage = - '{{#label}} does not look like a valid blog post author. Please use an author key or an author object (with a key and/or name).'; - export const FrontMatterLastUpdateErrorMessage = '{{#label}} does not look like a valid last update object. Please use an author key with a string or a date with a string or Date.'; diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/simple-site/doc with space.md b/packages/docusaurus-utils/src/__tests__/__fixtures__/simple-site/doc with space.md new file mode 100644 index 000000000000..2b2a616da3fc --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/simple-site/doc with space.md @@ -0,0 +1 @@ +# Hoo hoo, if this path tricks you... diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/simple-site/hello.md b/packages/docusaurus-utils/src/__tests__/__fixtures__/simple-site/hello.md new file mode 100644 index 000000000000..38e44ab76c1b --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/simple-site/hello.md @@ -0,0 +1,7 @@ +--- +id: hello +title: Hello, World ! +slug: / +--- + +Hello diff --git a/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts b/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts index 6118e4058964..7f47e3d35b22 100644 --- a/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts @@ -138,20 +138,6 @@ describe('getFileCommitDate', () => { ); }); - it('temporary created file that is not tracked by git', async () => { - const consoleMock = jest - .spyOn(console, 'warn') - .mockImplementation(() => {}); - const tempFilePath = path.join(repoDir, 'file.md'); - await fs.writeFile(tempFilePath, 'Lorem ipsum :)'); - await expect(getFileLastUpdate(tempFilePath)).resolves.toBeNull(); - expect(consoleMock).toHaveBeenCalledTimes(1); - expect(consoleMock).toHaveBeenLastCalledWith( - expect.stringMatching(/not tracked by git./), - ); - await fs.unlink(tempFilePath); - }); - it('multiple files not tracked by git', async () => { const consoleMock = jest .spyOn(console, 'warn') diff --git a/packages/docusaurus-utils/src/__tests__/lastUpdateUtils.test.ts b/packages/docusaurus-utils/src/__tests__/lastUpdateUtils.test.ts new file mode 100644 index 000000000000..934734298692 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/lastUpdateUtils.test.ts @@ -0,0 +1,206 @@ +/** + * 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 {jest} from '@jest/globals'; +import fs from 'fs-extra'; +import path from 'path'; +import {createTempRepo} from '@testing-utils/git'; +import shell from 'shelljs'; +import {getFileLastUpdate, readLastUpdateData} from '@docusaurus/utils'; + +const convertDate = (date: number | undefined) => + typeof date === 'number' ? date * 1000 : undefined; + +describe('getFileLastUpdate', () => { + const {repoDir} = createTempRepo(); + + const existingFilePath = path.join( + __dirname, + '__fixtures__/simple-site/hello.md', + ); + it('existing test file in repository with Git timestamp', async () => { + const lastUpdateData = await getFileLastUpdate(existingFilePath); + expect(lastUpdateData).not.toBeNull(); + + const {author, timestamp} = lastUpdateData!; + expect(author).not.toBeNull(); + expect(typeof author).toBe('string'); + + expect(timestamp).not.toBeNull(); + expect(typeof timestamp).toBe('number'); + }); + + it('existing test file with spaces in path', async () => { + const filePathWithSpace = path.join( + __dirname, + '__fixtures__/simple-site/doc with space.md', + ); + const lastUpdateData = await getFileLastUpdate(filePathWithSpace); + expect(lastUpdateData).not.toBeNull(); + + const {author, timestamp} = lastUpdateData!; + expect(author).not.toBeNull(); + expect(typeof author).toBe('string'); + + expect(timestamp).not.toBeNull(); + expect(typeof timestamp).toBe('number'); + }); + + it('non-existing file', async () => { + const consoleMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + const nonExistingFileName = '.nonExisting'; + const nonExistingFilePath = path.join( + __dirname, + '__fixtures__', + nonExistingFileName, + ); + await expect(getFileLastUpdate(nonExistingFilePath)).rejects.toThrow( + /An error occurred when trying to get the last update date/, + ); + expect(consoleMock).toHaveBeenCalledTimes(0); + consoleMock.mockRestore(); + }); + + it('git does not exist', async () => { + const mock = jest.spyOn(shell, 'which').mockImplementationOnce(() => null); + const consoleMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + const lastUpdateData = await getFileLastUpdate(existingFilePath); + expect(lastUpdateData).toBeNull(); + expect(consoleMock).toHaveBeenLastCalledWith( + expect.stringMatching( + /.*\[WARNING\].* Sorry, the docs plugin last update options require Git\..*/, + ), + ); + + consoleMock.mockRestore(); + mock.mockRestore(); + }); + + it('temporary created file that is not tracked by git', async () => { + const consoleMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + const tempFilePath = path.join(repoDir, 'file.md'); + await fs.writeFile(tempFilePath, 'Lorem ipsum :)'); + await expect(getFileLastUpdate(tempFilePath)).resolves.toBeNull(); + expect(consoleMock).toHaveBeenCalledTimes(1); + expect(consoleMock).toHaveBeenLastCalledWith( + expect.stringMatching(/not tracked by git./), + ); + await fs.unlink(tempFilePath); + }); +}); + +describe('readLastUpdateData', () => { + const defaultDevDate = 1539502055; + const defaultDevAuthor = 'Author'; + const testDate = '2021-01-01'; + const testDateTime = new Date(testDate).getTime(); + const testAuthor = 'ozaki'; + + it('read last time show author time', async () => { + const {lastUpdatedAt, lastUpdatedBy} = await readLastUpdateData( + '', + {showLastUpdateAuthor: true, showLastUpdateTime: true}, + {date: testDate}, + ); + expect(convertDate(lastUpdatedAt)).toEqual(testDateTime); + expect(lastUpdatedBy).toBe(defaultDevAuthor); + }); + + it('read last author show author time', async () => { + const {lastUpdatedAt, lastUpdatedBy} = await readLastUpdateData( + '', + {showLastUpdateAuthor: true, showLastUpdateTime: true}, + {author: testAuthor}, + ); + expect(lastUpdatedBy).toEqual(testAuthor); + expect(lastUpdatedAt).toBe(defaultDevDate); + }); + + it('read last all show author time', async () => { + const {lastUpdatedAt, lastUpdatedBy} = await readLastUpdateData( + '', + {showLastUpdateAuthor: true, showLastUpdateTime: true}, + {author: testAuthor, date: testDate}, + ); + expect(lastUpdatedBy).toEqual(testAuthor); + expect(convertDate(lastUpdatedAt)).toEqual(testDateTime); + }); + + it('read last default show none', async () => { + const lastUpdate = await readLastUpdateData( + '', + {showLastUpdateAuthor: false, showLastUpdateTime: false}, + {}, + ); + expect(lastUpdate).toEqual({}); + }); + + it('read last author show none', async () => { + const lastUpdate = await readLastUpdateData( + '', + {showLastUpdateAuthor: false, showLastUpdateTime: false}, + {author: testAuthor}, + ); + expect(lastUpdate).toEqual({}); + }); + + it('read last time show author', async () => { + const {lastUpdatedAt, lastUpdatedBy} = await readLastUpdateData( + '', + {showLastUpdateAuthor: true, showLastUpdateTime: false}, + {date: testDate}, + ); + expect(lastUpdatedBy).toBe(defaultDevAuthor); + expect(lastUpdatedAt).toBeUndefined(); + }); + + it('read last author show author', async () => { + const {lastUpdatedAt, lastUpdatedBy} = await readLastUpdateData( + '', + {showLastUpdateAuthor: true, showLastUpdateTime: false}, + {author: testAuthor}, + ); + expect(lastUpdatedBy).toBe('ozaki'); + expect(lastUpdatedAt).toBeUndefined(); + }); + + it('read last default show author default', async () => { + const {lastUpdatedAt, lastUpdatedBy} = await readLastUpdateData( + '', + {showLastUpdateAuthor: true, showLastUpdateTime: false}, + {}, + ); + expect(lastUpdatedBy).toBe(defaultDevAuthor); + expect(lastUpdatedAt).toBeUndefined(); + }); + + it('read last time show time', async () => { + const {lastUpdatedAt, lastUpdatedBy} = await readLastUpdateData( + '', + {showLastUpdateAuthor: false, showLastUpdateTime: true}, + {date: testDate}, + ); + expect(lastUpdatedBy).toBeUndefined(); + expect(convertDate(lastUpdatedAt)).toEqual(testDateTime); + }); + + it('read last author show time', async () => { + const {lastUpdatedAt, lastUpdatedBy} = await readLastUpdateData( + '', + {showLastUpdateAuthor: false, showLastUpdateTime: true}, + {author: testAuthor}, + ); + expect(lastUpdatedBy).toBeUndefined(); + expect(lastUpdatedAt).toEqual(defaultDevDate); + }); +}); From f6df2556e66c2b504af816a658ce36f5e6526984 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Mon, 11 Mar 2024 15:58:04 +0100 Subject: [PATCH 18/43] remove snapshot for inline test --- .../__snapshots__/index.test.ts.snap | 596 ------------------ .../src/__tests__/index.test.ts | 58 +- 2 files changed, 52 insertions(+), 602 deletions(-) diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap index a9b375ef4019..983054187fee 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap @@ -437,599 +437,3 @@ exports[`blog plugin works with blog tags 1`] = ` }, } `; - -exports[`last update author and time 1`] = ` -[ - { - "content": "author", - "id": "author", - "metadata": { - "authors": [ - { - "imageURL": undefined, - "name": "ozaki", - "title": undefined, - "url": undefined, - }, - ], - "date": 2024-03-07T17:28:43.000Z, - "description": "author", - "editUrl": "https://baseEditUrl.com/edit/blog/author.md", - "frontMatter": { - "author": "ozaki", - "last_update": { - "author": "seb", - }, - "slug": "author", - "title": "Author", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": 1539502055, - "lastUpdatedBy": "seb", - "nextItem": { - "permalink": "/blog/nothing", - "title": "Nothing", - }, - "permalink": "/blog/author", - "readingTime": 0.005, - "source": "@site/blog/author.md", - "tags": [], - "title": "Author", - "unlisted": false, - }, - }, - { - "content": "nothing", - "id": "nothing", - "metadata": { - "authors": [], - "date": 2024-03-07T17:28:43.000Z, - "description": "nothing", - "editUrl": "https://baseEditUrl.com/edit/blog/nothing.md", - "frontMatter": { - "slug": "nothing", - "title": "Nothing", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": 1539502055, - "lastUpdatedBy": "Author", - "nextItem": { - "permalink": "/blog/both", - "title": "Both", - }, - "permalink": "/blog/nothing", - "prevItem": { - "permalink": "/blog/author", - "title": "Author", - }, - "readingTime": 0.005, - "source": "@site/blog/nothing.md", - "tags": [], - "title": "Nothing", - "unlisted": false, - }, - }, - { - "content": "last update date", - "id": "both", - "metadata": { - "authors": [ - { - "imageURL": undefined, - "name": "ozaki", - "title": undefined, - "url": undefined, - }, - ], - "date": 2020-01-01T00:00:00.000Z, - "description": "last update date", - "editUrl": "https://baseEditUrl.com/edit/blog/both.md", - "frontMatter": { - "author": "ozaki", - "date": 2020-01-01T00:00:00.000Z, - "last_update": { - "author": "seb", - "date": 2021-01-01T00:00:00.000Z, - }, - "slug": "both", - "title": "Both", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": 1609459200, - "lastUpdatedBy": "seb", - "nextItem": { - "permalink": "/blog/lastUpdateDate", - "title": "Last update date", - }, - "permalink": "/blog/both", - "prevItem": { - "permalink": "/blog/nothing", - "title": "Nothing", - }, - "readingTime": 0.015, - "source": "@site/blog/both.md", - "tags": [], - "title": "Both", - "unlisted": false, - }, - }, - { - "content": "last update date", - "id": "lastUpdateDate", - "metadata": { - "authors": [], - "date": 2020-01-01T00:00:00.000Z, - "description": "last update date", - "editUrl": "https://baseEditUrl.com/edit/blog/lastUpdateDate.md", - "frontMatter": { - "date": 2020-01-01T00:00:00.000Z, - "last_update": { - "date": 2021-01-01T00:00:00.000Z, - }, - "slug": "lastUpdateDate", - "title": "Last update date", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": 1609459200, - "lastUpdatedBy": "Author", - "permalink": "/blog/lastUpdateDate", - "prevItem": { - "permalink": "/blog/both", - "title": "Both", - }, - "readingTime": 0.015, - "source": "@site/blog/lastUpdateDate.md", - "tags": [], - "title": "Last update date", - "unlisted": false, - }, - }, -] -`; - -exports[`last update author only 1`] = ` -[ - { - "content": "author", - "id": "author", - "metadata": { - "authors": [ - { - "imageURL": undefined, - "name": "ozaki", - "title": undefined, - "url": undefined, - }, - ], - "date": 2024-03-07T17:28:43.000Z, - "description": "author", - "editUrl": "https://baseEditUrl.com/edit/blog/author.md", - "frontMatter": { - "author": "ozaki", - "last_update": { - "author": "seb", - }, - "slug": "author", - "title": "Author", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": 1539502055, - "lastUpdatedBy": undefined, - "nextItem": { - "permalink": "/blog/nothing", - "title": "Nothing", - }, - "permalink": "/blog/author", - "readingTime": 0.005, - "source": "@site/blog/author.md", - "tags": [], - "title": "Author", - "unlisted": false, - }, - }, - { - "content": "nothing", - "id": "nothing", - "metadata": { - "authors": [], - "date": 2024-03-07T17:28:43.000Z, - "description": "nothing", - "editUrl": "https://baseEditUrl.com/edit/blog/nothing.md", - "frontMatter": { - "slug": "nothing", - "title": "Nothing", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": 1539502055, - "lastUpdatedBy": undefined, - "nextItem": { - "permalink": "/blog/both", - "title": "Both", - }, - "permalink": "/blog/nothing", - "prevItem": { - "permalink": "/blog/author", - "title": "Author", - }, - "readingTime": 0.005, - "source": "@site/blog/nothing.md", - "tags": [], - "title": "Nothing", - "unlisted": false, - }, - }, - { - "content": "last update date", - "id": "both", - "metadata": { - "authors": [ - { - "imageURL": undefined, - "name": "ozaki", - "title": undefined, - "url": undefined, - }, - ], - "date": 2020-01-01T00:00:00.000Z, - "description": "last update date", - "editUrl": "https://baseEditUrl.com/edit/blog/both.md", - "frontMatter": { - "author": "ozaki", - "date": 2020-01-01T00:00:00.000Z, - "last_update": { - "author": "seb", - "date": 2021-01-01T00:00:00.000Z, - }, - "slug": "both", - "title": "Both", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": 1609459200, - "lastUpdatedBy": "seb", - "nextItem": { - "permalink": "/blog/lastUpdateDate", - "title": "Last update date", - }, - "permalink": "/blog/both", - "prevItem": { - "permalink": "/blog/nothing", - "title": "Nothing", - }, - "readingTime": 0.015, - "source": "@site/blog/both.md", - "tags": [], - "title": "Both", - "unlisted": false, - }, - }, - { - "content": "last update date", - "id": "lastUpdateDate", - "metadata": { - "authors": [], - "date": 2020-01-01T00:00:00.000Z, - "description": "last update date", - "editUrl": "https://baseEditUrl.com/edit/blog/lastUpdateDate.md", - "frontMatter": { - "date": 2020-01-01T00:00:00.000Z, - "last_update": { - "date": 2021-01-01T00:00:00.000Z, - }, - "slug": "lastUpdateDate", - "title": "Last update date", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": 1609459200, - "lastUpdatedBy": undefined, - "permalink": "/blog/lastUpdateDate", - "prevItem": { - "permalink": "/blog/both", - "title": "Both", - }, - "readingTime": 0.015, - "source": "@site/blog/lastUpdateDate.md", - "tags": [], - "title": "Last update date", - "unlisted": false, - }, - }, -] -`; - -exports[`last update none 1`] = ` -[ - { - "content": "author", - "id": "author", - "metadata": { - "authors": [ - { - "imageURL": undefined, - "name": "ozaki", - "title": undefined, - "url": undefined, - }, - ], - "date": 2024-03-07T17:28:43.000Z, - "description": "author", - "editUrl": "https://baseEditUrl.com/edit/blog/author.md", - "frontMatter": { - "author": "ozaki", - "last_update": { - "author": "seb", - }, - "slug": "author", - "title": "Author", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": undefined, - "lastUpdatedBy": undefined, - "nextItem": { - "permalink": "/blog/nothing", - "title": "Nothing", - }, - "permalink": "/blog/author", - "readingTime": 0.005, - "source": "@site/blog/author.md", - "tags": [], - "title": "Author", - "unlisted": false, - }, - }, - { - "content": "nothing", - "id": "nothing", - "metadata": { - "authors": [], - "date": 2024-03-07T17:28:43.000Z, - "description": "nothing", - "editUrl": "https://baseEditUrl.com/edit/blog/nothing.md", - "frontMatter": { - "slug": "nothing", - "title": "Nothing", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": undefined, - "lastUpdatedBy": undefined, - "nextItem": { - "permalink": "/blog/both", - "title": "Both", - }, - "permalink": "/blog/nothing", - "prevItem": { - "permalink": "/blog/author", - "title": "Author", - }, - "readingTime": 0.005, - "source": "@site/blog/nothing.md", - "tags": [], - "title": "Nothing", - "unlisted": false, - }, - }, - { - "content": "last update date", - "id": "both", - "metadata": { - "authors": [ - { - "imageURL": undefined, - "name": "ozaki", - "title": undefined, - "url": undefined, - }, - ], - "date": 2020-01-01T00:00:00.000Z, - "description": "last update date", - "editUrl": "https://baseEditUrl.com/edit/blog/both.md", - "frontMatter": { - "author": "ozaki", - "date": 2020-01-01T00:00:00.000Z, - "last_update": { - "author": "seb", - "date": 2021-01-01T00:00:00.000Z, - }, - "slug": "both", - "title": "Both", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": undefined, - "lastUpdatedBy": undefined, - "nextItem": { - "permalink": "/blog/lastUpdateDate", - "title": "Last update date", - }, - "permalink": "/blog/both", - "prevItem": { - "permalink": "/blog/nothing", - "title": "Nothing", - }, - "readingTime": 0.015, - "source": "@site/blog/both.md", - "tags": [], - "title": "Both", - "unlisted": false, - }, - }, - { - "content": "last update date", - "id": "lastUpdateDate", - "metadata": { - "authors": [], - "date": 2020-01-01T00:00:00.000Z, - "description": "last update date", - "editUrl": "https://baseEditUrl.com/edit/blog/lastUpdateDate.md", - "frontMatter": { - "date": 2020-01-01T00:00:00.000Z, - "last_update": { - "date": 2021-01-01T00:00:00.000Z, - }, - "slug": "lastUpdateDate", - "title": "Last update date", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": undefined, - "lastUpdatedBy": undefined, - "permalink": "/blog/lastUpdateDate", - "prevItem": { - "permalink": "/blog/both", - "title": "Both", - }, - "readingTime": 0.015, - "source": "@site/blog/lastUpdateDate.md", - "tags": [], - "title": "Last update date", - "unlisted": false, - }, - }, -] -`; - -exports[`last update time only 1`] = ` -[ - { - "content": "author", - "id": "author", - "metadata": { - "authors": [ - { - "imageURL": undefined, - "name": "ozaki", - "title": undefined, - "url": undefined, - }, - ], - "date": 2024-03-07T17:28:43.000Z, - "description": "author", - "editUrl": "https://baseEditUrl.com/edit/blog/author.md", - "frontMatter": { - "author": "ozaki", - "last_update": { - "author": "seb", - }, - "slug": "author", - "title": "Author", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": undefined, - "lastUpdatedBy": "seb", - "nextItem": { - "permalink": "/blog/nothing", - "title": "Nothing", - }, - "permalink": "/blog/author", - "readingTime": 0.005, - "source": "@site/blog/author.md", - "tags": [], - "title": "Author", - "unlisted": false, - }, - }, - { - "content": "nothing", - "id": "nothing", - "metadata": { - "authors": [], - "date": 2024-03-07T17:28:43.000Z, - "description": "nothing", - "editUrl": "https://baseEditUrl.com/edit/blog/nothing.md", - "frontMatter": { - "slug": "nothing", - "title": "Nothing", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": undefined, - "lastUpdatedBy": "Author", - "nextItem": { - "permalink": "/blog/both", - "title": "Both", - }, - "permalink": "/blog/nothing", - "prevItem": { - "permalink": "/blog/author", - "title": "Author", - }, - "readingTime": 0.005, - "source": "@site/blog/nothing.md", - "tags": [], - "title": "Nothing", - "unlisted": false, - }, - }, - { - "content": "last update date", - "id": "both", - "metadata": { - "authors": [ - { - "imageURL": undefined, - "name": "ozaki", - "title": undefined, - "url": undefined, - }, - ], - "date": 2020-01-01T00:00:00.000Z, - "description": "last update date", - "editUrl": "https://baseEditUrl.com/edit/blog/both.md", - "frontMatter": { - "author": "ozaki", - "date": 2020-01-01T00:00:00.000Z, - "last_update": { - "author": "seb", - "date": 2021-01-01T00:00:00.000Z, - }, - "slug": "both", - "title": "Both", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": 1609459200, - "lastUpdatedBy": "seb", - "nextItem": { - "permalink": "/blog/lastUpdateDate", - "title": "Last update date", - }, - "permalink": "/blog/both", - "prevItem": { - "permalink": "/blog/nothing", - "title": "Nothing", - }, - "readingTime": 0.015, - "source": "@site/blog/both.md", - "tags": [], - "title": "Both", - "unlisted": false, - }, - }, - { - "content": "last update date", - "id": "lastUpdateDate", - "metadata": { - "authors": [], - "date": 2020-01-01T00:00:00.000Z, - "description": "last update date", - "editUrl": "https://baseEditUrl.com/edit/blog/lastUpdateDate.md", - "frontMatter": { - "date": 2020-01-01T00:00:00.000Z, - "last_update": { - "date": 2021-01-01T00:00:00.000Z, - }, - "slug": "lastUpdateDate", - "title": "Last update date", - }, - "hasTruncateMarker": false, - "lastUpdatedAt": undefined, - "lastUpdatedBy": "Author", - "permalink": "/blog/lastUpdateDate", - "prevItem": { - "permalink": "/blog/both", - "title": "Both", - }, - "readingTime": 0.015, - "source": "@site/blog/lastUpdateDate.md", - "tags": [], - "title": "Last update date", - "unlisted": false, - }, - }, -] -`; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index f957ce6f0a25..3f276b3e72b9 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -544,9 +544,20 @@ describe('last update', () => { ); const {blogPosts} = (await plugin.loadContent!())!; - expect(blogPosts).toMatchSnapshot(); + expect(blogPosts[0].metadata.lastUpdatedBy).toBe('seb'); + expect(blogPosts[0].metadata.lastUpdatedAt).toBe(1539502055); + + expect(blogPosts[1].metadata.lastUpdatedBy).toBe('Author'); + expect(blogPosts[1].metadata.lastUpdatedAt).toBe(1539502055); + + expect(blogPosts[2].metadata.lastUpdatedBy).toBe('seb'); + expect(blogPosts[2].metadata.lastUpdatedAt).toBe(1609459200); + + expect(blogPosts[3].metadata.lastUpdatedBy).toBe('Author'); + expect(blogPosts[3].metadata.lastUpdatedAt).toBe(1609459200); }); - it('author only', async () => { + + it('time only', async () => { const plugin = await getPlugin( siteDir, { @@ -557,9 +568,22 @@ describe('last update', () => { ); const {blogPosts} = (await plugin.loadContent!())!; - expect(blogPosts).toMatchSnapshot(); + expect(blogPosts[0].metadata.lastUpdatedBy).toBeUndefined(); + expect(blogPosts[0].metadata.lastUpdatedAt).toBe(1539502055); + + expect(blogPosts[1].metadata.lastUpdatedBy).toBeUndefined(); + expect(blogPosts[1].metadata.lastUpdatedAt).toBe(1539502055); + + // TODO author should be undefined instead of 'seb' + // as showLastUpdateAuthor is set to false + expect(blogPosts[2].metadata.lastUpdatedBy).toBe('seb'); + expect(blogPosts[2].metadata.lastUpdatedAt).toBe(1609459200); + + expect(blogPosts[3].metadata.lastUpdatedBy).toBeUndefined(); + expect(blogPosts[3].metadata.lastUpdatedAt).toBe(1609459200); }); - it('time only', async () => { + + it('author only', async () => { const plugin = await getPlugin( siteDir, { @@ -570,7 +594,19 @@ describe('last update', () => { ); const {blogPosts} = (await plugin.loadContent!())!; - expect(blogPosts).toMatchSnapshot(); + expect(blogPosts[0].metadata.lastUpdatedBy).toBe('seb'); + expect(blogPosts[0].metadata.lastUpdatedAt).toBeUndefined(); + + expect(blogPosts[1].metadata.lastUpdatedBy).toBe('Author'); + expect(blogPosts[1].metadata.lastUpdatedAt).toBeUndefined(); + + // TODO time should be undefined instead of 1609459200 + // as showLastUpdateTime is set to false + expect(blogPosts[2].metadata.lastUpdatedBy).toBe('seb'); + expect(blogPosts[2].metadata.lastUpdatedAt).toBe(1609459200); + + expect(blogPosts[3].metadata.lastUpdatedBy).toBe('Author'); + expect(blogPosts[3].metadata.lastUpdatedAt).toBeUndefined(); }); it('none', async () => { @@ -584,6 +620,16 @@ describe('last update', () => { ); const {blogPosts} = (await plugin.loadContent!())!; - expect(blogPosts).toMatchSnapshot(); + expect(blogPosts[0].metadata.lastUpdatedBy).toBeUndefined(); + expect(blogPosts[0].metadata.lastUpdatedAt).toBeUndefined(); + + expect(blogPosts[1].metadata.lastUpdatedBy).toBeUndefined(); + expect(blogPosts[1].metadata.lastUpdatedAt).toBeUndefined(); + + expect(blogPosts[2].metadata.lastUpdatedBy).toBeUndefined(); + expect(blogPosts[2].metadata.lastUpdatedAt).toBeUndefined(); + + expect(blogPosts[3].metadata.lastUpdatedBy).toBeUndefined(); + expect(blogPosts[3].metadata.lastUpdatedAt).toBeUndefined(); }); }); From 7636376e6a9d0537cecda12408eb291df4a60030 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:46:47 +0100 Subject: [PATCH 19/43] wip footer shared code --- .../src/theme-classic.d.ts | 17 ++++++ .../src/theme/BlogPostItem/Footer/index.tsx | 24 +++++--- .../BlogPostItem/Footer/styles.module.css | 5 -- .../src/theme/DocItem/Footer/index.tsx | 56 ++++++++++--------- .../src/theme/EditMetaRow/index.tsx | 35 ++++++++++++ .../src/theme/EditMetaRow/styles.module.css | 6 ++ 6 files changed, 102 insertions(+), 41 deletions(-) create mode 100644 packages/docusaurus-theme-classic/src/theme/EditMetaRow/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/EditMetaRow/styles.module.css diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 6afed49abcfc..619356afad7a 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -676,6 +676,23 @@ declare module '@theme/DocVersionSuggestions' { export default function DocVersionSuggestions(): JSX.Element; } +declare module '@theme/EditMetaRow' { + import {type DocContextValue} from '@docusaurus/theme-common/internal'; + + // type EditMetaRowProps = Pick< + // DocContextValue['metadata'], + // 'editUrl' | 'lastUpdatedAt' | 'lastUpdatedBy' + // >; + + export interface EditMetaRowProps { + readonly className: string; + readonly editUrl: DocContextValue['metadata']['editUrl']; + readonly lastUpdatedAt: DocContextValue['metadata']['lastUpdatedAt']; + readonly lastUpdatedBy: DocContextValue['metadata']['lastUpdatedBy']; + } + export default function EditMetaRow(props: Props): JSX.Element; +} + declare module '@theme/EditThisPage' { export interface Props { readonly editUrl: string; diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/index.tsx index 7a4a7d842f67..b3709e355fe2 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/index.tsx @@ -8,11 +8,12 @@ import React from 'react'; import clsx from 'clsx'; import {useBlogPost} from '@docusaurus/theme-common/internal'; -import EditThisPage from '@theme/EditThisPage'; +// import EditThisPage from '@theme/EditThisPage'; +import EditMetaRow from '@theme/EditMetaRow'; import TagsListInline from '@theme/TagsListInline'; import ReadMoreLink from '@theme/BlogPostItem/Footer/ReadMoreLink'; -import LastUpdated from '@theme/LastUpdated'; +// import LastUpdated from '@theme/LastUpdated'; import styles from './styles.module.css'; export default function BlogPostItemFooter(): JSX.Element | null { @@ -37,6 +38,8 @@ export default function BlogPostItemFooter(): JSX.Element | null { return null; } + const canDisplayEditMetaRow = !!(editUrl || lastUpdatedAt || lastUpdatedBy); + return (