Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(blog): add LastUpdateAuthor & LastUpdateTime #9912

Merged
merged 45 commits into from Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
5ba18f3
feat(blog): add LastUpdateAuthor & LastUpdateTime
OzakIOne Mar 5, 2024
5967a6f
display if values exists
OzakIOne Mar 5, 2024
dc305e3
wip shared code
OzakIOne Mar 5, 2024
6f098d8
wip shared code
OzakIOne Mar 5, 2024
8b19677
wip share code & footer
OzakIOne Mar 6, 2024
4db29f9
Merge branch 'main' into ozaki/blogPostLastUpdate
OzakIOne Mar 6, 2024
fb2a3b6
wip tests
OzakIOne Mar 6, 2024
7130546
refactor: apply lint autofix
OzakIOne Mar 6, 2024
2965359
update type & doc
OzakIOne Mar 6, 2024
6331811
wip share code
OzakIOne Mar 6, 2024
3a88750
use throw error
OzakIOne Mar 6, 2024
87fcdf0
seo itemprop datemodified
OzakIOne Mar 6, 2024
5dc4c04
wip: git track
OzakIOne Mar 7, 2024
e0ac624
refactor: tests & shared code
OzakIOne Mar 7, 2024
7bb0310
refactor: apply lint autofix
OzakIOne Mar 7, 2024
ba1d743
refactor: review comments
OzakIOne Mar 8, 2024
b0a22d6
move tests
OzakIOne Mar 8, 2024
92ef13f
wip review
OzakIOne Mar 11, 2024
f6df255
remove snapshot for inline test
OzakIOne Mar 11, 2024
7636376
wip footer shared code
OzakIOne Mar 11, 2024
cc70ae1
EditMetaRow css
OzakIOne Mar 11, 2024
cf8a9ad
wip fix margin right mobile view
OzakIOne Mar 12, 2024
173d922
fix type definition
OzakIOne Mar 12, 2024
4602164
remove doc plugin hint from error
OzakIOne Mar 12, 2024
40d5163
css review
OzakIOne Mar 12, 2024
6754b74
refactor: apply lint autofix
OzakIOne Mar 12, 2024
a2b9a1e
fix typo
OzakIOne Mar 12, 2024
64644c4
refactor: apply lint autofix
OzakIOne Mar 12, 2024
9e0c233
avoid git diff
OzakIOne Mar 12, 2024
e72b315
constant & optional chaining undefined lint error
OzakIOne Mar 12, 2024
c60ae69
add frontMatterLastUpdateSchema test
OzakIOne Mar 12, 2024
6169d68
wip structured data
OzakIOne Mar 14, 2024
9e5f53e
return empty string instead of undefined
OzakIOne Mar 14, 2024
b5f5617
refactor: apply lint autofix
OzakIOne Mar 14, 2024
7801a60
fix typo
OzakIOne Mar 14, 2024
fad3994
wip style to fix argos
OzakIOne Mar 14, 2024
8485d21
refactor review
OzakIOne Mar 14, 2024
bd59b1b
refactor: apply lint autofix
OzakIOne Mar 14, 2024
0d5a109
fix readLastUpdateData algorithm bug
slorber Mar 14, 2024
daf88ab
Merge remote-tracking branch 'origin/ozaki/blogPostLastUpdate' into o…
slorber Mar 14, 2024
a794d06
add missing structured data type
slorber Mar 14, 2024
7b8cabb
fix blog tests
slorber Mar 14, 2024
1d28fa4
docs: update blog according to doc
OzakIOne Mar 14, 2024
718c7c3
minor docs changes
slorber Mar 14, 2024
cb7a7ee
Refactor a bit theme code and CSS
slorber Mar 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.js
Expand Up @@ -91,7 +91,7 @@ module.exports = {
'no-constant-binary-expression': ERROR,
'no-continue': OFF,
'no-control-regex': WARNING,
'no-else-return': [WARNING, {allowElseIf: true}],
'no-else-return': OFF,
'no-empty': [WARNING, {allowEmptyCatch: true}],
'no-lonely-if': WARNING,
'no-nested-ternary': WARNING,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Expand Up @@ -137,6 +137,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",
Expand Down Expand Up @@ -172,6 +174,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",
Expand Down Expand Up @@ -215,6 +219,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",
Expand Down
143 changes: 141 additions & 2 deletions packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts
Expand Up @@ -8,7 +8,12 @@
import {jest} from '@jest/globals';
import path from 'path';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import {posixPath, getFileCommitDate} from '@docusaurus/utils';
import {
posixPath,
getFileCommitDate,
GIT_FALLBACK_LAST_UPDATE_DATE,
GIT_FALLBACK_LAST_UPDATE_AUTHOR,
} from '@docusaurus/utils';
import pluginContentBlog from '../index';
import {validateOptions} from '../options';
import type {
Expand Down Expand Up @@ -510,7 +515,7 @@ describe('blog plugin', () => {
{
postsPerPage: 1,
processBlogPosts: async ({blogPosts}) =>
blogPosts.filter((blog) => blog.metadata.tags[0].label === 'tag1'),
blogPosts.filter((blog) => blog.metadata.tags[0]?.label === 'tag1'),
},
DefaultI18N,
);
Expand All @@ -526,3 +531,137 @@ describe('blog plugin', () => {
expect(blogPosts).toMatchSnapshot();
});
});

describe('last update', () => {
const siteDir = path.join(
__dirname,
'__fixtures__',
'website-blog-with-last-update',
);

const lastUpdateFor = (date: string) => new Date(date).getTime() / 1000;

it('author and time', async () => {
const plugin = await getPlugin(
siteDir,
{
showLastUpdateAuthor: true,
showLastUpdateTime: true,
},
DefaultI18N,
);
const {blogPosts} = (await plugin.loadContent!())!;

expect(blogPosts[0]?.metadata.lastUpdatedBy).toBe('seb');
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBe(
GIT_FALLBACK_LAST_UPDATE_DATE,
);

expect(blogPosts[1]?.metadata.lastUpdatedBy).toBe(
GIT_FALLBACK_LAST_UPDATE_AUTHOR,
);
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBe(
GIT_FALLBACK_LAST_UPDATE_DATE,
);

expect(blogPosts[2]?.metadata.lastUpdatedBy).toBe('seb');
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBe(
lastUpdateFor('2021-01-01'),
);

expect(blogPosts[3]?.metadata.lastUpdatedBy).toBe(
GIT_FALLBACK_LAST_UPDATE_AUTHOR,
);
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBe(
lastUpdateFor('2021-01-01'),
);
});

it('time only', async () => {
const plugin = await getPlugin(
siteDir,
{
showLastUpdateAuthor: false,
showLastUpdateTime: true,
},
DefaultI18N,
);
const {blogPosts} = (await plugin.loadContent!())!;

expect(blogPosts[0]?.metadata.title).toBe('Author');
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBe(
GIT_FALLBACK_LAST_UPDATE_DATE,
);

expect(blogPosts[1]?.metadata.title).toBe('Nothing');
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBe(
GIT_FALLBACK_LAST_UPDATE_DATE,
);

expect(blogPosts[2]?.metadata.title).toBe('Both');
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBe(
lastUpdateFor('2021-01-01'),
);

expect(blogPosts[3]?.metadata.title).toBe('Last update date');
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBeUndefined();
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBe(
lastUpdateFor('2021-01-01'),
);
});

it('author only', async () => {
const plugin = await getPlugin(
siteDir,
{
showLastUpdateAuthor: true,
showLastUpdateTime: false,
},
DefaultI18N,
);
const {blogPosts} = (await plugin.loadContent!())!;

expect(blogPosts[0]?.metadata.lastUpdatedBy).toBe('seb');
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBeUndefined();

expect(blogPosts[1]?.metadata.lastUpdatedBy).toBe(
GIT_FALLBACK_LAST_UPDATE_AUTHOR,
);
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBeUndefined();

expect(blogPosts[2]?.metadata.lastUpdatedBy).toBe('seb');
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBeUndefined();

expect(blogPosts[3]?.metadata.lastUpdatedBy).toBe(
GIT_FALLBACK_LAST_UPDATE_AUTHOR,
);
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBeUndefined();
});

it('none', async () => {
const plugin = await getPlugin(
siteDir,
{
showLastUpdateAuthor: false,
showLastUpdateTime: false,
},
DefaultI18N,
);
const {blogPosts} = (await plugin.loadContent!())!;

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();
});
});
9 changes: 9 additions & 0 deletions packages/docusaurus-plugin-content-blog/src/blogUtils.ts
Expand Up @@ -26,6 +26,7 @@ import {
getContentPathList,
isUnlisted,
isDraft,
readLastUpdateData,
} from '@docusaurus/utils';
import {validateBlogPostFrontMatter} from './frontMatter';
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
Expand Down Expand Up @@ -231,6 +232,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});

Expand Down Expand Up @@ -337,6 +344,8 @@ async function processBlogSourceFile(
authors,
frontMatter,
unlisted,
lastUpdatedAt: lastUpdate.lastUpdatedAt,
lastUpdatedBy: lastUpdate.lastUpdatedBy,
},
content,
};
Expand Down
9 changes: 5 additions & 4 deletions packages/docusaurus-plugin-content-blog/src/frontMatter.ts
Expand Up @@ -4,14 +4,14 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {
ContentVisibilitySchema,
FrontMatterLastUpdateSchema,
FrontMatterTOCHeadingLevels,
FrontMatterTagsSchema,
JoiFrontMatter as Joi, // Custom instance for front matter
URISchema,
validateFrontMatter,
FrontMatterTagsSchema,
FrontMatterTOCHeadingLevels,
ContentVisibilitySchema,
} from '@docusaurus/utils-validation';
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';

Expand Down Expand Up @@ -69,6 +69,7 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
hide_table_of_contents: Joi.boolean(),

...FrontMatterTOCHeadingLevels,
last_update: FrontMatterLastUpdateSchema,
})
.messages({
'deprecate.error':
Expand Down
6 changes: 6 additions & 0 deletions packages/docusaurus-plugin-content-blog/src/options.ts
Expand Up @@ -51,6 +51,8 @@ export const DEFAULT_OPTIONS: PluginOptions = {
authorsMapPath: 'authors.yml',
readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}),
sortPosts: 'descending',
showLastUpdateTime: false,
showLastUpdateAuthor: false,
processBlogPosts: async () => undefined,
};

Expand Down Expand Up @@ -135,6 +137,10 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
sortPosts: Joi.string()
.valid('descending', 'ascending')
.default(DEFAULT_OPTIONS.sortPosts),
showLastUpdateTime: Joi.bool().default(DEFAULT_OPTIONS.showLastUpdateTime),
showLastUpdateAuthor: Joi.bool().default(
DEFAULT_OPTIONS.showLastUpdateAuthor,
),
processBlogPosts: Joi.function()
.optional()
.default(() => DEFAULT_OPTIONS.processBlogPosts),
Expand Down
Expand Up @@ -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} 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';
Expand Down Expand Up @@ -156,6 +161,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?: FrontMatterLastUpdate;
};

export type BlogPostFrontMatterAuthor = Author & {
Expand All @@ -180,7 +187,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
| BlogPostFrontMatterAuthor
| (string | BlogPostFrontMatterAuthor)[];

export type BlogPostMetadata = {
export type BlogPostMetadata = LastUpdateData & {
/** Path to the Markdown source, with `@site` alias. */
readonly source: string;
/**
Expand Down Expand Up @@ -426,6 +433,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;
/** An optional function which can be used to transform blog posts
* (filter, modify, delete, etc...).
*/
Expand Down
Expand Up @@ -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 :('}},
Expand Down