This commit is contained in:
2024-03-22 03:47:51 +05:30
parent 8bcf3d211e
commit 89819f6fe2
28440 changed files with 3211033 additions and 2 deletions

View File

@@ -0,0 +1,23 @@
/**
* 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 type { BlogContentPaths } from './types';
import type { Author, BlogPostFrontMatter } from '@docusaurus/plugin-content-blog';
export type AuthorsMap = {
[authorKey: string]: Author;
};
export declare function validateAuthorsMap(content: unknown): AuthorsMap;
export declare function getAuthorsMap(params: {
authorsMapPath: string;
contentPaths: BlogContentPaths;
}): Promise<AuthorsMap | undefined>;
type AuthorsParam = {
frontMatter: BlogPostFrontMatter;
authorsMap: AuthorsMap | undefined;
baseUrl: string;
};
export declare function getBlogPostAuthors(params: AuthorsParam): Author[];
export {};

View File

@@ -0,0 +1,137 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getBlogPostAuthors = exports.getAuthorsMap = exports.validateAuthorsMap = void 0;
const utils_1 = require("@docusaurus/utils");
const utils_validation_1 = require("@docusaurus/utils-validation");
const AuthorsMapSchema = utils_validation_1.Joi.object()
.pattern(utils_validation_1.Joi.string(), utils_validation_1.Joi.object({
name: utils_validation_1.Joi.string(),
url: utils_validation_1.URISchema,
imageURL: utils_validation_1.URISchema,
title: utils_validation_1.Joi.string(),
email: utils_validation_1.Joi.string(),
})
.rename('image_url', 'imageURL')
.or('name', 'imageURL')
.unknown()
.required()
.messages({
'object.base': '{#label} should be an author object containing properties like name, title, and imageURL.',
'any.required': '{#label} cannot be undefined. It should be an author object containing properties like name, title, and imageURL.',
}))
.messages({
'object.base': "The authors map file should contain an object where each entry contains an author key and the corresponding author's data.",
});
function validateAuthorsMap(content) {
const { error, value } = AuthorsMapSchema.validate(content);
if (error) {
throw error;
}
return value;
}
exports.validateAuthorsMap = validateAuthorsMap;
async function getAuthorsMap(params) {
return (0, utils_1.getDataFileData)({
filePath: params.authorsMapPath,
contentPaths: params.contentPaths,
fileType: 'authors map',
}, validateAuthorsMap);
}
exports.getAuthorsMap = getAuthorsMap;
function normalizeImageUrl({ imageURL, baseUrl, }) {
return imageURL?.startsWith('/')
? (0, utils_1.normalizeUrl)([baseUrl, imageURL])
: imageURL;
}
// Legacy v1/early-v2 front matter fields
// We may want to deprecate those in favor of using only frontMatter.authors
function getFrontMatterAuthorLegacy({ baseUrl, frontMatter, }) {
const name = frontMatter.author;
const title = frontMatter.author_title ?? frontMatter.authorTitle;
const url = frontMatter.author_url ?? frontMatter.authorURL;
const imageURL = normalizeImageUrl({
imageURL: frontMatter.author_image_url ?? frontMatter.authorImageURL,
baseUrl,
});
if (name || title || url || imageURL) {
return {
name,
title,
url,
imageURL,
};
}
return undefined;
}
function normalizeFrontMatterAuthors(frontMatterAuthors = []) {
function normalizeAuthor(authorInput) {
if (typeof authorInput === 'string') {
// Technically, we could allow users to provide an author's name here, but
// we only support keys, otherwise, a typo in a key would fallback to
// becoming a name and may end up unnoticed
return { key: authorInput };
}
return authorInput;
}
return Array.isArray(frontMatterAuthors)
? frontMatterAuthors.map(normalizeAuthor)
: [normalizeAuthor(frontMatterAuthors)];
}
function getFrontMatterAuthors(params) {
const { authorsMap } = params;
const frontMatterAuthors = normalizeFrontMatterAuthors(params.frontMatter.authors);
function getAuthorsMapAuthor(key) {
if (key) {
if (!authorsMap || Object.keys(authorsMap).length === 0) {
throw new Error(`Can't reference blog post authors by a key (such as '${key}') because no authors map file could be loaded.
Please double-check your blog plugin config (in particular 'authorsMapPath'), ensure the file exists at the configured path, is not empty, and is valid!`);
}
const author = authorsMap[key];
if (!author) {
throw Error(`Blog author with key "${key}" not found in the authors map file.
Valid author keys are:
${Object.keys(authorsMap)
.map((validKey) => `- ${validKey}`)
.join('\n')}`);
}
return author;
}
return undefined;
}
function toAuthor(frontMatterAuthor) {
return {
// Author def from authorsMap can be locally overridden by front matter
...getAuthorsMapAuthor(frontMatterAuthor.key),
...frontMatterAuthor,
};
}
return frontMatterAuthors.map(toAuthor);
}
function fixAuthorImageBaseURL(authors, { baseUrl }) {
return authors.map((author) => ({
...author,
imageURL: normalizeImageUrl({ imageURL: author.imageURL, baseUrl }),
}));
}
function getBlogPostAuthors(params) {
const authorLegacy = getFrontMatterAuthorLegacy(params);
const authors = getFrontMatterAuthors(params);
const updatedAuthors = fixAuthorImageBaseURL(authors, params);
if (authorLegacy) {
// Technically, we could allow mixing legacy/authors front matter, but do we
// really want to?
if (updatedAuthors.length > 0) {
throw new Error(`To declare blog post authors, use the 'authors' front matter in priority.
Don't mix 'authors' with other existing 'author_*' front matter. Choose one or the other, not both at the same time.`);
}
return [authorLegacy];
}
return updatedAuthors;
}
exports.getBlogPostAuthors = getBlogPostAuthors;

View File

@@ -0,0 +1,40 @@
/**
* 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 type { LoadContext } from '@docusaurus/types';
import type { PluginOptions, BlogPost, BlogTags, BlogPaginated } from '@docusaurus/plugin-content-blog';
import type { BlogContentPaths, BlogMarkdownLoaderOptions } from './types';
export declare function truncate(fileString: string, truncateMarker: RegExp): string;
export declare function getSourceToPermalink(blogPosts: BlogPost[]): {
[aliasedPath: string]: string;
};
export declare function paginateBlogPosts({ blogPosts, basePageUrl, blogTitle, blogDescription, postsPerPageOption, }: {
blogPosts: BlogPost[];
basePageUrl: string;
blogTitle: string;
blogDescription: string;
postsPerPageOption: number | 'ALL';
}): BlogPaginated[];
export declare function shouldBeListed(blogPost: BlogPost): boolean;
export declare function getBlogTags({ blogPosts, ...params }: {
blogPosts: BlogPost[];
blogTitle: string;
blogDescription: string;
postsPerPageOption: number | 'ALL';
}): BlogTags;
type ParsedBlogFileName = {
date: Date | undefined;
text: string;
slug: string;
};
export declare function parseBlogFileName(blogSourceRelative: string): ParsedBlogFileName;
export declare function generateBlogPosts(contentPaths: BlogContentPaths, context: LoadContext, options: PluginOptions): Promise<BlogPost[]>;
export type LinkifyParams = {
filePath: string;
fileString: string;
} & Pick<BlogMarkdownLoaderOptions, 'sourceToPermalink' | 'siteDir' | 'contentPaths' | 'onBrokenMarkdownLink'>;
export declare function linkify({ filePath, contentPaths, fileString, siteDir, sourceToPermalink, onBrokenMarkdownLink, }: LinkifyParams): string;
export {};

View File

@@ -0,0 +1,280 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.linkify = exports.generateBlogPosts = exports.parseBlogFileName = exports.getBlogTags = exports.shouldBeListed = exports.paginateBlogPosts = exports.getSourceToPermalink = exports.truncate = void 0;
const tslib_1 = require("tslib");
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const path_1 = tslib_1.__importDefault(require("path"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const reading_time_1 = tslib_1.__importDefault(require("reading-time"));
const utils_1 = require("@docusaurus/utils");
const frontMatter_1 = require("./frontMatter");
const authors_1 = require("./authors");
function truncate(fileString, truncateMarker) {
return fileString.split(truncateMarker, 1).shift();
}
exports.truncate = truncate;
function getSourceToPermalink(blogPosts) {
return Object.fromEntries(blogPosts.map(({ metadata: { source, permalink } }) => [source, permalink]));
}
exports.getSourceToPermalink = getSourceToPermalink;
function paginateBlogPosts({ blogPosts, basePageUrl, blogTitle, blogDescription, postsPerPageOption, }) {
const totalCount = blogPosts.length;
const postsPerPage = postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption;
const numberOfPages = Math.ceil(totalCount / postsPerPage);
const pages = [];
function permalink(page) {
return page > 0
? (0, utils_1.normalizeUrl)([basePageUrl, `page/${page + 1}`])
: basePageUrl;
}
for (let page = 0; page < numberOfPages; page += 1) {
pages.push({
items: blogPosts
.slice(page * postsPerPage, (page + 1) * postsPerPage)
.map((item) => item.id),
metadata: {
permalink: permalink(page),
page: page + 1,
postsPerPage,
totalPages: numberOfPages,
totalCount,
previousPage: page !== 0 ? permalink(page - 1) : undefined,
nextPage: page < numberOfPages - 1 ? permalink(page + 1) : undefined,
blogDescription,
blogTitle,
},
});
}
return pages;
}
exports.paginateBlogPosts = paginateBlogPosts;
function shouldBeListed(blogPost) {
return !blogPost.metadata.unlisted;
}
exports.shouldBeListed = shouldBeListed;
function getBlogTags({ blogPosts, ...params }) {
const groups = (0, utils_1.groupTaggedItems)(blogPosts, (blogPost) => blogPost.metadata.tags);
return lodash_1.default.mapValues(groups, ({ tag, items: tagBlogPosts }) => {
const tagVisibility = (0, utils_1.getTagVisibility)({
items: tagBlogPosts,
isUnlisted: (item) => item.metadata.unlisted,
});
return {
label: tag.label,
items: tagVisibility.listedItems.map((item) => item.id),
permalink: tag.permalink,
pages: paginateBlogPosts({
blogPosts: tagVisibility.listedItems,
basePageUrl: tag.permalink,
...params,
}),
unlisted: tagVisibility.unlisted,
};
});
}
exports.getBlogTags = getBlogTags;
const DATE_FILENAME_REGEX = /^(?<folder>.*)(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(?:\/index)?.mdx?$/;
function parseBlogFileName(blogSourceRelative) {
const dateFilenameMatch = blogSourceRelative.match(DATE_FILENAME_REGEX);
if (dateFilenameMatch) {
const { folder, text, date: dateString } = dateFilenameMatch.groups;
// Always treat dates as UTC by adding the `Z`
const date = new Date(`${dateString}Z`);
const slugDate = dateString.replace(/-/g, '/');
const slug = `/${slugDate}/${folder}${text}`;
return { date, text: text, slug };
}
const text = blogSourceRelative.replace(/(?:\/index)?\.mdx?$/, '');
const slug = `/${text}`;
return { date: undefined, text, slug };
}
exports.parseBlogFileName = parseBlogFileName;
function formatBlogPostDate(locale, date, calendar) {
try {
return new Intl.DateTimeFormat(locale, {
day: 'numeric',
month: 'long',
year: 'numeric',
timeZone: 'UTC',
calendar,
}).format(date);
}
catch (err) {
logger_1.default.error `Can't format blog post date "${String(date)}"`;
throw err;
}
}
async function parseBlogPostMarkdownFile({ filePath, parseFrontMatter, }) {
const fileContent = await fs_extra_1.default.readFile(filePath, 'utf-8');
try {
const result = await (0, utils_1.parseMarkdownFile)({
filePath,
fileContent,
parseFrontMatter,
removeContentTitle: true,
});
return {
...result,
frontMatter: (0, frontMatter_1.validateBlogPostFrontMatter)(result.frontMatter),
};
}
catch (err) {
logger_1.default.error `Error while parsing blog post file path=${filePath}.`;
throw err;
}
}
const defaultReadingTime = ({ content, options }) => (0, reading_time_1.default)(content, options).minutes;
async function processBlogSourceFile(blogSourceRelative, contentPaths, context, options, authorsMap) {
const { siteConfig: { baseUrl, markdown: { parseFrontMatter }, }, siteDir, i18n, } = context;
const { routeBasePath, tagsBasePath: tagsRouteBasePath, truncateMarker, showReadingTime, editUrl, } = options;
// Lookup in localized folder in priority
const blogDirPath = await (0, utils_1.getFolderContainingFile)((0, utils_1.getContentPathList)(contentPaths), blogSourceRelative);
const blogSourceAbsolute = path_1.default.join(blogDirPath, blogSourceRelative);
const { frontMatter, content, contentTitle, excerpt } = await parseBlogPostMarkdownFile({
filePath: blogSourceAbsolute,
parseFrontMatter,
});
const aliasedSource = (0, utils_1.aliasedSitePath)(blogSourceAbsolute, siteDir);
const draft = (0, utils_1.isDraft)({ frontMatter });
const unlisted = (0, utils_1.isUnlisted)({ frontMatter });
if (draft) {
return undefined;
}
if (frontMatter.id) {
logger_1.default.warn `name=${'id'} header option is deprecated in path=${blogSourceRelative} file. Please use name=${'slug'} option instead.`;
}
const parsedBlogFileName = parseBlogFileName(blogSourceRelative);
async function getDate() {
// Prefer user-defined date.
if (frontMatter.date) {
if (typeof frontMatter.date === 'string') {
// Always treat dates as UTC by adding the `Z`
return new Date(`${frontMatter.date}Z`);
}
// YAML only converts YYYY-MM-DD to dates and leaves others as strings.
return frontMatter.date;
}
else if (parsedBlogFileName.date) {
return parsedBlogFileName.date;
}
try {
const result = (0, utils_1.getFileCommitDate)(blogSourceAbsolute, {
age: 'oldest',
includeAuthor: false,
});
return result.date;
}
catch (err) {
logger_1.default.warn(err);
return (await fs_extra_1.default.stat(blogSourceAbsolute)).birthtime;
}
}
const date = await getDate();
const formattedDate = formatBlogPostDate(i18n.currentLocale, date, i18n.localeConfigs[i18n.currentLocale].calendar);
const title = frontMatter.title ?? contentTitle ?? parsedBlogFileName.text;
const description = frontMatter.description ?? excerpt ?? '';
const slug = frontMatter.slug ?? parsedBlogFileName.slug;
const permalink = (0, utils_1.normalizeUrl)([baseUrl, routeBasePath, slug]);
function getBlogEditUrl() {
const blogPathRelative = path_1.default.relative(blogDirPath, path_1.default.resolve(blogSourceAbsolute));
if (typeof editUrl === 'function') {
return editUrl({
blogDirPath: (0, utils_1.posixPath)(path_1.default.relative(siteDir, blogDirPath)),
blogPath: (0, utils_1.posixPath)(blogPathRelative),
permalink,
locale: i18n.currentLocale,
});
}
else if (typeof editUrl === 'string') {
const isLocalized = blogDirPath === contentPaths.contentPathLocalized;
const fileContentPath = isLocalized && options.editLocalizedFiles
? contentPaths.contentPathLocalized
: contentPaths.contentPath;
const contentPathEditUrl = (0, utils_1.normalizeUrl)([
editUrl,
(0, utils_1.posixPath)(path_1.default.relative(siteDir, fileContentPath)),
]);
return (0, utils_1.getEditUrl)(blogPathRelative, contentPathEditUrl);
}
return undefined;
}
const tagsBasePath = (0, utils_1.normalizeUrl)([
baseUrl,
routeBasePath,
tagsRouteBasePath,
]);
const authors = (0, authors_1.getBlogPostAuthors)({ authorsMap, frontMatter, baseUrl });
return {
id: slug,
metadata: {
permalink,
editUrl: getBlogEditUrl(),
source: aliasedSource,
title,
description,
date,
formattedDate,
tags: (0, utils_1.normalizeFrontMatterTags)(tagsBasePath, frontMatter.tags),
readingTime: showReadingTime
? options.readingTime({
content,
frontMatter,
defaultReadingTime,
})
: undefined,
hasTruncateMarker: truncateMarker.test(content),
authors,
frontMatter,
unlisted,
},
content,
};
}
async function generateBlogPosts(contentPaths, context, options) {
const { include, exclude } = options;
if (!(await fs_extra_1.default.pathExists(contentPaths.contentPath))) {
return [];
}
const blogSourceFiles = await (0, utils_1.Globby)(include, {
cwd: contentPaths.contentPath,
ignore: exclude,
});
const authorsMap = await (0, authors_1.getAuthorsMap)({
contentPaths,
authorsMapPath: options.authorsMapPath,
});
async function doProcessBlogSourceFile(blogSourceFile) {
try {
return await processBlogSourceFile(blogSourceFile, contentPaths, context, options, authorsMap);
}
catch (err) {
throw new Error(`Processing of blog source file path=${blogSourceFile} failed.`, { cause: err });
}
}
const blogPosts = (await Promise.all(blogSourceFiles.map(doProcessBlogSourceFile))).filter(Boolean);
blogPosts.sort((a, b) => b.metadata.date.getTime() - a.metadata.date.getTime());
if (options.sortPosts === 'ascending') {
return blogPosts.reverse();
}
return blogPosts;
}
exports.generateBlogPosts = generateBlogPosts;
function linkify({ filePath, contentPaths, fileString, siteDir, sourceToPermalink, onBrokenMarkdownLink, }) {
const { newContent, brokenMarkdownLinks } = (0, utils_1.replaceMarkdownLinks)({
siteDir,
fileString,
filePath,
contentPaths,
sourceToPermalink,
});
brokenMarkdownLinks.forEach((l) => onBrokenMarkdownLink(l));
return newContent;
}
exports.linkify = linkify;

View File

@@ -0,0 +1,15 @@
/**
* 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 type { DocusaurusConfig } from '@docusaurus/types';
import type { PluginOptions, BlogPost } from '@docusaurus/plugin-content-blog';
export declare function createBlogFeedFiles({ blogPosts: allBlogPosts, options, siteConfig, outDir, locale, }: {
blogPosts: BlogPost[];
options: PluginOptions;
siteConfig: DocusaurusConfig;
outDir: string;
locale: string;
}): Promise<void>;

View File

@@ -0,0 +1,147 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createBlogFeedFiles = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const feed_1 = require("feed");
const srcset = tslib_1.__importStar(require("srcset"));
const utils_1 = require("@docusaurus/utils");
const utils_common_1 = require("@docusaurus/utils-common");
const cheerio_1 = require("cheerio");
async function generateBlogFeed({ blogPosts, options, siteConfig, outDir, locale, }) {
if (!blogPosts.length) {
return null;
}
const { feedOptions, routeBasePath } = options;
const { url: siteUrl, baseUrl, title, favicon } = siteConfig;
const blogBaseUrl = (0, utils_1.normalizeUrl)([siteUrl, baseUrl, routeBasePath]);
const blogPostsForFeed = feedOptions.limit === false || feedOptions.limit === null
? blogPosts
: blogPosts.slice(0, feedOptions.limit);
const updated = blogPostsForFeed[0]?.metadata.date;
const feed = new feed_1.Feed({
id: blogBaseUrl,
title: feedOptions.title ?? `${title} Blog`,
updated,
language: feedOptions.language ?? locale,
link: blogBaseUrl,
description: feedOptions.description ?? `${siteConfig.title} Blog`,
favicon: favicon ? (0, utils_1.normalizeUrl)([siteUrl, baseUrl, favicon]) : undefined,
copyright: feedOptions.copyright,
});
const createFeedItems = options.feedOptions.createFeedItems ?? defaultCreateFeedItems;
const feedItems = await createFeedItems({
blogPosts: blogPostsForFeed,
siteConfig,
outDir,
defaultCreateFeedItems,
});
feedItems.forEach(feed.addItem);
return feed;
}
async function defaultCreateFeedItems({ blogPosts, siteConfig, outDir, }) {
const { url: siteUrl } = siteConfig;
function toFeedAuthor(author) {
return { name: author.name, link: author.url, email: author.email };
}
return Promise.all(blogPosts.map(async (post) => {
const { metadata: { title: metadataTitle, permalink, date, description, authors, tags, }, } = post;
const content = await (0, utils_1.readOutputHTMLFile)(permalink.replace(siteConfig.baseUrl, ''), outDir, siteConfig.trailingSlash);
const $ = (0, cheerio_1.load)(content);
const blogPostAbsoluteUrl = (0, utils_1.normalizeUrl)([siteUrl, permalink]);
const toAbsoluteUrl = (src) => String(new URL(src, blogPostAbsoluteUrl));
// Make links and image urls absolute
// See https://github.com/facebook/docusaurus/issues/9136
$(`div#${utils_common_1.blogPostContainerID} a, div#${utils_common_1.blogPostContainerID} img`).each((_, elm) => {
if (elm.tagName === 'a') {
const { href } = elm.attribs;
if (href) {
elm.attribs.href = toAbsoluteUrl(href);
}
}
else if (elm.tagName === 'img') {
const { src, srcset: srcsetAttr } = elm.attribs;
if (src) {
elm.attribs.src = toAbsoluteUrl(src);
}
if (srcsetAttr) {
elm.attribs.srcset = srcset.stringify(srcset.parse(srcsetAttr).map((props) => ({
...props,
url: toAbsoluteUrl(props.url),
})));
}
}
});
const feedItem = {
title: metadataTitle,
id: blogPostAbsoluteUrl,
link: blogPostAbsoluteUrl,
date,
description,
// Atom feed demands the "term", while other feeds use "name"
category: tags.map((tag) => ({ name: tag.label, term: tag.label })),
content: $(`#${utils_common_1.blogPostContainerID}`).html(),
};
// json1() method takes the first item of authors array
// it causes an error when authors array is empty
const feedItemAuthors = authors.map(toFeedAuthor);
if (feedItemAuthors.length > 0) {
feedItem.author = feedItemAuthors;
}
return feedItem;
}));
}
async function createBlogFeedFile({ feed, feedType, generatePath, }) {
const [feedContent, feedPath] = (() => {
switch (feedType) {
case 'rss':
return [feed.rss2(), 'rss.xml'];
case 'json':
return [feed.json1(), 'feed.json'];
case 'atom':
return [feed.atom1(), 'atom.xml'];
default:
throw new Error(`Feed type ${feedType} not supported.`);
}
})();
try {
await fs_extra_1.default.outputFile(path_1.default.join(generatePath, feedPath), feedContent);
}
catch (err) {
logger_1.default.error(`Generating ${feedType} feed failed.`);
throw err;
}
}
function shouldBeInFeed(blogPost) {
const excluded = blogPost.metadata.frontMatter.draft ||
blogPost.metadata.frontMatter.unlisted;
return !excluded;
}
async function createBlogFeedFiles({ blogPosts: allBlogPosts, options, siteConfig, outDir, locale, }) {
const blogPosts = allBlogPosts.filter(shouldBeInFeed);
const feed = await generateBlogFeed({
blogPosts,
options,
siteConfig,
outDir,
locale,
});
const feedTypes = options.feedOptions.type;
if (!feed || !feedTypes) {
return;
}
await Promise.all(feedTypes.map((feedType) => createBlogFeedFile({
feed,
feedType,
generatePath: path_1.default.join(outDir, options.routeBasePath),
})));
}
exports.createBlogFeedFiles = createBlogFeedFiles;

View File

@@ -0,0 +1,10 @@
/**
* 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 type { BlogPostFrontMatter } from '@docusaurus/plugin-content-blog';
export declare function validateBlogPostFrontMatter(frontMatter: {
[key: string]: unknown;
}): BlogPostFrontMatter;

View File

@@ -0,0 +1,63 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateBlogPostFrontMatter = void 0;
const utils_validation_1 = require("@docusaurus/utils-validation");
const BlogPostFrontMatterAuthorSchema = utils_validation_1.JoiFrontMatter.object({
key: utils_validation_1.JoiFrontMatter.string(),
name: utils_validation_1.JoiFrontMatter.string(),
title: utils_validation_1.JoiFrontMatter.string(),
url: utils_validation_1.URISchema,
imageURL: utils_validation_1.JoiFrontMatter.string(),
})
.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 BlogFrontMatterSchema = utils_validation_1.JoiFrontMatter.object({
id: utils_validation_1.JoiFrontMatter.string(),
title: utils_validation_1.JoiFrontMatter.string().allow(''),
description: utils_validation_1.JoiFrontMatter.string().allow(''),
tags: utils_validation_1.FrontMatterTagsSchema,
date: utils_validation_1.JoiFrontMatter.date().raw(),
// New multi-authors front matter:
authors: utils_validation_1.JoiFrontMatter.alternatives()
.try(utils_validation_1.JoiFrontMatter.string(), BlogPostFrontMatterAuthorSchema, utils_validation_1.JoiFrontMatter.array()
.items(utils_validation_1.JoiFrontMatter.string(), BlogPostFrontMatterAuthorSchema)
.messages({
'array.sparse': FrontMatterAuthorErrorMessage,
'array.includes': FrontMatterAuthorErrorMessage,
}))
.messages({
'alternatives.match': FrontMatterAuthorErrorMessage,
}),
// Legacy author front matter
author: utils_validation_1.JoiFrontMatter.string(),
author_title: utils_validation_1.JoiFrontMatter.string(),
author_url: utils_validation_1.URISchema,
author_image_url: utils_validation_1.URISchema,
// TODO enable deprecation warnings later
authorURL: utils_validation_1.URISchema,
// .warning('deprecate.error', { alternative: '"author_url"'}),
authorTitle: utils_validation_1.JoiFrontMatter.string(),
// .warning('deprecate.error', { alternative: '"author_title"'}),
authorImageURL: utils_validation_1.URISchema,
// .warning('deprecate.error', { alternative: '"author_image_url"'}),
slug: utils_validation_1.JoiFrontMatter.string(),
image: utils_validation_1.URISchema,
keywords: utils_validation_1.JoiFrontMatter.array().items(utils_validation_1.JoiFrontMatter.string().required()),
hide_table_of_contents: utils_validation_1.JoiFrontMatter.boolean(),
...utils_validation_1.FrontMatterTOCHeadingLevels,
})
.messages({
'deprecate.error': '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
})
.concat(utils_validation_1.ContentVisibilitySchema);
function validateBlogPostFrontMatter(frontMatter) {
return (0, utils_validation_1.validateFrontMatter)(frontMatter, BlogFrontMatterSchema);
}
exports.validateBlogPostFrontMatter = validateBlogPostFrontMatter;

View File

@@ -0,0 +1,10 @@
/**
* 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 type { LoadContext, Plugin } from '@docusaurus/types';
import type { PluginOptions, BlogContent } from '@docusaurus/plugin-content-blog';
export default function pluginContentBlog(context: LoadContext, options: PluginOptions): Promise<Plugin<BlogContent>>;
export { validateOptions } from './options';

View File

@@ -0,0 +1,366 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateOptions = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("@docusaurus/utils");
const blogUtils_1 = require("./blogUtils");
const footnoteIDFixer_1 = tslib_1.__importDefault(require("./remark/footnoteIDFixer"));
const translations_1 = require("./translations");
const feed_1 = require("./feed");
const props_1 = require("./props");
async function pluginContentBlog(context, options) {
const { siteDir, siteConfig, generatedFilesDir, localizationDir, i18n: { currentLocale }, } = context;
const { onBrokenMarkdownLinks, baseUrl } = siteConfig;
const contentPaths = {
contentPath: path_1.default.resolve(siteDir, options.path),
contentPathLocalized: (0, utils_1.getPluginI18nPath)({
localizationDir,
pluginName: 'docusaurus-plugin-content-blog',
pluginId: options.id,
}),
};
const pluginId = options.id ?? utils_1.DEFAULT_PLUGIN_ID;
const pluginDataDirRoot = path_1.default.join(generatedFilesDir, 'docusaurus-plugin-content-blog');
const dataDir = path_1.default.join(pluginDataDirRoot, pluginId);
const aliasedSource = (source) => `~blog/${(0, utils_1.posixPath)(path_1.default.relative(pluginDataDirRoot, source))}`;
const authorsMapFilePath = await (0, utils_1.getDataFilePath)({
filePath: options.authorsMapPath,
contentPaths,
});
return {
name: 'docusaurus-plugin-content-blog',
getPathsToWatch() {
const { include } = options;
const contentMarkdownGlobs = (0, utils_1.getContentPathList)(contentPaths).flatMap((contentPath) => include.map((pattern) => `${contentPath}/${pattern}`));
return [authorsMapFilePath, ...contentMarkdownGlobs].filter(Boolean);
},
getTranslationFiles() {
return (0, translations_1.getTranslationFiles)(options);
},
// Fetches blog contents and returns metadata for the necessary routes.
async loadContent() {
const { postsPerPage: postsPerPageOption, routeBasePath, tagsBasePath, blogDescription, blogTitle, blogSidebarTitle, } = options;
const baseBlogUrl = (0, utils_1.normalizeUrl)([baseUrl, routeBasePath]);
const blogTagsListPath = (0, utils_1.normalizeUrl)([baseBlogUrl, tagsBasePath]);
const blogPosts = await (0, blogUtils_1.generateBlogPosts)(contentPaths, context, options);
const listedBlogPosts = blogPosts.filter(blogUtils_1.shouldBeListed);
if (!blogPosts.length) {
return {
blogSidebarTitle,
blogPosts: [],
blogListPaginated: [],
blogTags: {},
blogTagsListPath,
blogTagsPaginated: [],
};
}
// Colocate next and prev metadata.
listedBlogPosts.forEach((blogPost, index) => {
const prevItem = index > 0 ? listedBlogPosts[index - 1] : null;
if (prevItem) {
blogPost.metadata.prevItem = {
title: prevItem.metadata.title,
permalink: prevItem.metadata.permalink,
};
}
const nextItem = index < listedBlogPosts.length - 1
? listedBlogPosts[index + 1]
: null;
if (nextItem) {
blogPost.metadata.nextItem = {
title: nextItem.metadata.title,
permalink: nextItem.metadata.permalink,
};
}
});
const blogListPaginated = (0, blogUtils_1.paginateBlogPosts)({
blogPosts: listedBlogPosts,
blogTitle,
blogDescription,
postsPerPageOption,
basePageUrl: baseBlogUrl,
});
const blogTags = (0, blogUtils_1.getBlogTags)({
blogPosts,
postsPerPageOption,
blogDescription,
blogTitle,
});
return {
blogSidebarTitle,
blogPosts,
blogListPaginated,
blogTags,
blogTagsListPath,
};
},
async contentLoaded({ content: blogContents, actions }) {
const { blogListComponent, blogPostComponent, blogTagsListComponent, blogTagsPostsComponent, blogArchiveComponent, routeBasePath, archiveBasePath, } = options;
const { addRoute, createData } = actions;
const { blogSidebarTitle, blogPosts, blogListPaginated, blogTags, blogTagsListPath, } = blogContents;
const listedBlogPosts = blogPosts.filter(blogUtils_1.shouldBeListed);
const blogItemsToMetadata = {};
const sidebarBlogPosts = options.blogSidebarCount === 'ALL'
? blogPosts
: blogPosts.slice(0, options.blogSidebarCount);
function blogPostItemsModule(items) {
return items.map((postId) => {
const blogPostMetadata = blogItemsToMetadata[postId];
return {
content: {
__import: true,
path: blogPostMetadata.source,
query: {
truncated: true,
},
},
};
});
}
if (archiveBasePath && listedBlogPosts.length) {
const archiveUrl = (0, utils_1.normalizeUrl)([
baseUrl,
routeBasePath,
archiveBasePath,
]);
// Create a blog archive route
const archiveProp = await createData(`${(0, utils_1.docuHash)(archiveUrl)}.json`, JSON.stringify({ blogPosts: listedBlogPosts }, null, 2));
addRoute({
path: archiveUrl,
component: blogArchiveComponent,
exact: true,
modules: {
archive: aliasedSource(archiveProp),
},
});
}
// This prop is useful to provide the blog list sidebar
const sidebarProp = await createData(
// Note that this created data path must be in sync with
// metadataPath provided to mdx-loader.
`blog-post-list-prop-${pluginId}.json`, JSON.stringify({
title: blogSidebarTitle,
items: sidebarBlogPosts.map((blogPost) => ({
title: blogPost.metadata.title,
permalink: blogPost.metadata.permalink,
unlisted: blogPost.metadata.unlisted,
})),
}, null, 2));
// Create routes for blog entries.
await Promise.all(blogPosts.map(async (blogPost) => {
const { id, metadata } = blogPost;
await createData(
// Note that this created data path must be in sync with
// metadataPath provided to mdx-loader.
`${(0, utils_1.docuHash)(metadata.source)}.json`, JSON.stringify(metadata, null, 2));
addRoute({
path: metadata.permalink,
component: blogPostComponent,
exact: true,
modules: {
sidebar: aliasedSource(sidebarProp),
content: metadata.source,
},
});
blogItemsToMetadata[id] = metadata;
}));
// Create routes for blog's paginated list entries.
await Promise.all(blogListPaginated.map(async (listPage) => {
const { metadata, items } = listPage;
const { permalink } = metadata;
const pageMetadataPath = await createData(`${(0, utils_1.docuHash)(permalink)}.json`, JSON.stringify(metadata, null, 2));
addRoute({
path: permalink,
component: blogListComponent,
exact: true,
modules: {
sidebar: aliasedSource(sidebarProp),
items: blogPostItemsModule(items),
metadata: aliasedSource(pageMetadataPath),
},
});
}));
// Tags. This is the last part so we early-return if there are no tags.
if (Object.keys(blogTags).length === 0) {
return;
}
async function createTagsListPage() {
const tagsPropPath = await createData(`${(0, utils_1.docuHash)(`${blogTagsListPath}-tags`)}.json`, JSON.stringify((0, props_1.toTagsProp)({ blogTags }), null, 2));
addRoute({
path: blogTagsListPath,
component: blogTagsListComponent,
exact: true,
modules: {
sidebar: aliasedSource(sidebarProp),
tags: aliasedSource(tagsPropPath),
},
});
}
async function createTagPostsListPage(tag) {
await Promise.all(tag.pages.map(async (blogPaginated) => {
const { metadata, items } = blogPaginated;
const tagPropPath = await createData(`${(0, utils_1.docuHash)(metadata.permalink)}.json`, JSON.stringify((0, props_1.toTagProp)({ tag, blogTagsListPath }), null, 2));
const listMetadataPath = await createData(`${(0, utils_1.docuHash)(metadata.permalink)}-list.json`, JSON.stringify(metadata, null, 2));
addRoute({
path: metadata.permalink,
component: blogTagsPostsComponent,
exact: true,
modules: {
sidebar: aliasedSource(sidebarProp),
items: blogPostItemsModule(items),
tag: aliasedSource(tagPropPath),
listMetadata: aliasedSource(listMetadataPath),
},
});
}));
}
await createTagsListPage();
await Promise.all(Object.values(blogTags).map(createTagPostsListPage));
},
translateContent({ content, translationFiles }) {
return (0, translations_1.translateContent)(content, translationFiles);
},
configureWebpack(_config, isServer, utils, content) {
const { admonitions, rehypePlugins, remarkPlugins, truncateMarker, beforeDefaultRemarkPlugins, beforeDefaultRehypePlugins, } = options;
const markdownLoaderOptions = {
siteDir,
contentPaths,
truncateMarker,
sourceToPermalink: (0, blogUtils_1.getSourceToPermalink)(content.blogPosts),
onBrokenMarkdownLink: (brokenMarkdownLink) => {
if (onBrokenMarkdownLinks === 'ignore') {
return;
}
logger_1.default.report(onBrokenMarkdownLinks) `Blog markdown link couldn't be resolved: (url=${brokenMarkdownLink.link}) in path=${brokenMarkdownLink.filePath}`;
},
};
const contentDirs = (0, utils_1.getContentPathList)(contentPaths);
return {
resolve: {
alias: {
'~blog': pluginDataDirRoot,
},
},
module: {
rules: [
{
test: /\.mdx?$/i,
include: contentDirs
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
.map(utils_1.addTrailingPathSeparator),
use: [
{
loader: require.resolve('@docusaurus/mdx-loader'),
options: {
admonitions,
remarkPlugins,
rehypePlugins,
beforeDefaultRemarkPlugins: [
footnoteIDFixer_1.default,
...beforeDefaultRemarkPlugins,
],
beforeDefaultRehypePlugins,
staticDirs: siteConfig.staticDirectories.map((dir) => path_1.default.resolve(siteDir, dir)),
siteDir,
isMDXPartial: (0, utils_1.createAbsoluteFilePathMatcher)(options.exclude, contentDirs),
metadataPath: (mdxPath) => {
// Note that metadataPath must be the same/in-sync as
// the path from createData for each MDX.
const aliasedPath = (0, utils_1.aliasedSitePath)(mdxPath, siteDir);
return path_1.default.join(dataDir, `${(0, utils_1.docuHash)(aliasedPath)}.json`);
},
// For blog posts a title in markdown is always removed
// Blog posts title are rendered separately
removeContentTitle: true,
// Assets allow to convert some relative images paths to
// require() calls
createAssets: ({ frontMatter, metadata, }) => ({
image: frontMatter.image,
authorsImageUrls: metadata.authors.map((author) => author.imageURL),
}),
markdownConfig: siteConfig.markdown,
},
},
{
loader: path_1.default.resolve(__dirname, './markdownLoader.js'),
options: markdownLoaderOptions,
},
].filter(Boolean),
},
],
},
};
},
async postBuild({ outDir, content }) {
if (!options.feedOptions.type) {
return;
}
const { blogPosts } = content;
if (!blogPosts.length) {
return;
}
await (0, feed_1.createBlogFeedFiles)({
blogPosts,
options,
outDir,
siteConfig,
locale: currentLocale,
});
},
injectHtmlTags({ content }) {
if (!content.blogPosts.length || !options.feedOptions.type) {
return {};
}
const feedTypes = options.feedOptions.type;
const feedTitle = options.feedOptions.title ?? context.siteConfig.title;
const feedsConfig = {
rss: {
type: 'application/rss+xml',
path: 'rss.xml',
title: `${feedTitle} RSS Feed`,
},
atom: {
type: 'application/atom+xml',
path: 'atom.xml',
title: `${feedTitle} Atom Feed`,
},
json: {
type: 'application/json',
path: 'feed.json',
title: `${feedTitle} JSON Feed`,
},
};
const headTags = [];
feedTypes.forEach((feedType) => {
const { type, path: feedConfigPath, title: feedConfigTitle, } = feedsConfig[feedType];
headTags.push({
tagName: 'link',
attributes: {
rel: 'alternate',
type,
href: (0, utils_1.normalizeUrl)([
baseUrl,
options.routeBasePath,
feedConfigPath,
]),
title: feedConfigTitle,
},
});
});
return {
headTags,
};
},
};
}
exports.default = pluginContentBlog;
var options_1 = require("./options");
Object.defineProperty(exports, "validateOptions", { enumerable: true, get: function () { return options_1.validateOptions; } });

View File

@@ -0,0 +1,9 @@
/**
* 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 type { BlogMarkdownLoaderOptions } from './types';
import type { LoaderContext } from 'webpack';
export default function markdownLoader(this: LoaderContext<BlogMarkdownLoaderOptions>, source: string): void;

View File

@@ -0,0 +1,30 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const blogUtils_1 = require("./blogUtils");
function markdownLoader(source) {
const filePath = this.resourcePath;
const fileString = source;
const callback = this.async();
const markdownLoaderOptions = this.getOptions();
// Linkify blog posts
let finalContent = (0, blogUtils_1.linkify)({
fileString,
filePath,
...markdownLoaderOptions,
});
// Truncate content if requested (e.g: file.md?truncated=true).
const truncated = this.resourceQuery
? !!new URLSearchParams(this.resourceQuery.slice(1)).get('truncated')
: undefined;
if (truncated) {
finalContent = (0, blogUtils_1.truncate)(finalContent, markdownLoaderOptions.truncateMarker);
}
return callback(null, finalContent);
}
exports.default = markdownLoader;

View File

@@ -0,0 +1,10 @@
/**
* 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 type { PluginOptions, Options } from '@docusaurus/plugin-content-blog';
import type { OptionValidationContext } from '@docusaurus/types';
export declare const DEFAULT_OPTIONS: PluginOptions;
export declare function validateOptions({ validate, options, }: OptionValidationContext<Options | undefined, PluginOptions>): PluginOptions;

View File

@@ -0,0 +1,109 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateOptions = exports.DEFAULT_OPTIONS = void 0;
const utils_validation_1 = require("@docusaurus/utils-validation");
const utils_1 = require("@docusaurus/utils");
exports.DEFAULT_OPTIONS = {
feedOptions: { type: ['rss', 'atom'], copyright: '', limit: 20 },
beforeDefaultRehypePlugins: [],
beforeDefaultRemarkPlugins: [],
admonitions: true,
truncateMarker: /<!--\s*truncate\s*-->|\{\/\*\s*truncate\s*\*\/\}/,
rehypePlugins: [],
remarkPlugins: [],
showReadingTime: true,
blogTagsPostsComponent: '@theme/BlogTagsPostsPage',
blogTagsListComponent: '@theme/BlogTagsListPage',
blogPostComponent: '@theme/BlogPostPage',
blogListComponent: '@theme/BlogListPage',
blogArchiveComponent: '@theme/BlogArchivePage',
blogDescription: 'Blog',
blogTitle: 'Blog',
blogSidebarCount: 5,
blogSidebarTitle: 'Recent posts',
postsPerPage: 10,
include: ['**/*.{md,mdx}'],
exclude: utils_1.GlobExcludeDefault,
routeBasePath: 'blog',
tagsBasePath: 'tags',
archiveBasePath: 'archive',
path: 'blog',
editLocalizedFiles: false,
authorsMapPath: 'authors.yml',
readingTime: ({ content, defaultReadingTime }) => defaultReadingTime({ content }),
sortPosts: 'descending',
};
const PluginOptionSchema = utils_validation_1.Joi.object({
path: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.path),
archiveBasePath: utils_validation_1.Joi.string()
.default(exports.DEFAULT_OPTIONS.archiveBasePath)
.allow(null),
routeBasePath: utils_validation_1.RouteBasePathSchema.default(exports.DEFAULT_OPTIONS.routeBasePath),
tagsBasePath: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.tagsBasePath),
include: utils_validation_1.Joi.array().items(utils_validation_1.Joi.string()).default(exports.DEFAULT_OPTIONS.include),
exclude: utils_validation_1.Joi.array().items(utils_validation_1.Joi.string()).default(exports.DEFAULT_OPTIONS.exclude),
postsPerPage: utils_validation_1.Joi.alternatives()
.try(utils_validation_1.Joi.equal('ALL').required(), utils_validation_1.Joi.number().integer().min(1).required())
.default(exports.DEFAULT_OPTIONS.postsPerPage),
blogListComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogListComponent),
blogPostComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogPostComponent),
blogTagsListComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogTagsListComponent),
blogTagsPostsComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogTagsPostsComponent),
blogArchiveComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogArchiveComponent),
blogTitle: utils_validation_1.Joi.string().allow('').default(exports.DEFAULT_OPTIONS.blogTitle),
blogDescription: utils_validation_1.Joi.string()
.allow('')
.default(exports.DEFAULT_OPTIONS.blogDescription),
blogSidebarCount: utils_validation_1.Joi.alternatives()
.try(utils_validation_1.Joi.equal('ALL').required(), utils_validation_1.Joi.number().integer().min(0).required())
.default(exports.DEFAULT_OPTIONS.blogSidebarCount),
blogSidebarTitle: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogSidebarTitle),
showReadingTime: utils_validation_1.Joi.bool().default(exports.DEFAULT_OPTIONS.showReadingTime),
remarkPlugins: utils_validation_1.RemarkPluginsSchema.default(exports.DEFAULT_OPTIONS.remarkPlugins),
rehypePlugins: utils_validation_1.RehypePluginsSchema.default(exports.DEFAULT_OPTIONS.rehypePlugins),
admonitions: utils_validation_1.AdmonitionsSchema.default(exports.DEFAULT_OPTIONS.admonitions),
editUrl: utils_validation_1.Joi.alternatives().try(utils_validation_1.URISchema, utils_validation_1.Joi.function()),
editLocalizedFiles: utils_validation_1.Joi.boolean().default(exports.DEFAULT_OPTIONS.editLocalizedFiles),
truncateMarker: utils_validation_1.Joi.object().default(exports.DEFAULT_OPTIONS.truncateMarker),
beforeDefaultRemarkPlugins: utils_validation_1.RemarkPluginsSchema.default(exports.DEFAULT_OPTIONS.beforeDefaultRemarkPlugins),
beforeDefaultRehypePlugins: utils_validation_1.RehypePluginsSchema.default(exports.DEFAULT_OPTIONS.beforeDefaultRehypePlugins),
feedOptions: utils_validation_1.Joi.object({
type: utils_validation_1.Joi.alternatives()
.try(utils_validation_1.Joi.array().items(utils_validation_1.Joi.string().equal('rss', 'atom', 'json')), utils_validation_1.Joi.alternatives().conditional(utils_validation_1.Joi.string().equal('all', 'rss', 'atom', 'json'), {
then: utils_validation_1.Joi.custom((val) => val === 'all' ? ['rss', 'atom', 'json'] : [val]),
}))
.allow(null)
.default(exports.DEFAULT_OPTIONS.feedOptions.type),
title: utils_validation_1.Joi.string().allow(''),
description: utils_validation_1.Joi.string().allow(''),
// Only add default value when user actually wants a feed (type is not null)
copyright: utils_validation_1.Joi.when('type', {
is: utils_validation_1.Joi.any().valid(null),
then: utils_validation_1.Joi.string().optional(),
otherwise: utils_validation_1.Joi.string()
.allow('')
.default(exports.DEFAULT_OPTIONS.feedOptions.copyright),
}),
language: utils_validation_1.Joi.string(),
createFeedItems: utils_validation_1.Joi.function(),
limit: utils_validation_1.Joi.alternatives()
.try(utils_validation_1.Joi.number(), utils_validation_1.Joi.valid(null), utils_validation_1.Joi.valid(false))
.default(exports.DEFAULT_OPTIONS.feedOptions.limit),
}).default(exports.DEFAULT_OPTIONS.feedOptions),
authorsMapPath: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.authorsMapPath),
readingTime: utils_validation_1.Joi.function().default(() => exports.DEFAULT_OPTIONS.readingTime),
sortPosts: utils_validation_1.Joi.string()
.valid('descending', 'ascending')
.default(exports.DEFAULT_OPTIONS.sortPosts),
}).default(exports.DEFAULT_OPTIONS);
function validateOptions({ validate, options, }) {
const validatedOptions = validate(PluginOptionSchema, options);
return validatedOptions;
}
exports.validateOptions = validateOptions;

View File

@@ -0,0 +1,15 @@
/**
* 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 type { TagsListItem, TagModule } from '@docusaurus/utils';
import type { BlogTag, BlogTags } from '@docusaurus/plugin-content-blog';
export declare function toTagsProp({ blogTags }: {
blogTags: BlogTags;
}): TagsListItem[];
export declare function toTagProp({ blogTagsListPath, tag, }: {
blogTagsListPath: string;
tag: BlogTag;
}): TagModule;

View File

@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.toTagProp = exports.toTagsProp = void 0;
function toTagsProp({ blogTags }) {
return Object.values(blogTags)
.filter((tag) => !tag.unlisted)
.map((tag) => ({
label: tag.label,
permalink: tag.permalink,
count: tag.items.length,
}));
}
exports.toTagsProp = toTagsProp;
function toTagProp({ blogTagsListPath, tag, }) {
return {
label: tag.label,
permalink: tag.permalink,
allTagsPath: blogTagsListPath,
count: tag.items.length,
unlisted: tag.unlisted,
};
}
exports.toTagProp = toTagProp;

View File

@@ -0,0 +1,14 @@
/**
* 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 type { Transformer } from 'unified';
/**
* In the blog list view, each post will be compiled separately. However, they
* may use the same footnote IDs. This leads to duplicated DOM IDs and inability
* to navigate to footnote references. This plugin fixes it by appending a
* unique hash to each reference/definition.
*/
export default function plugin(): Transformer;

View File

@@ -0,0 +1,28 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("@docusaurus/utils");
/**
* In the blog list view, each post will be compiled separately. However, they
* may use the same footnote IDs. This leads to duplicated DOM IDs and inability
* to navigate to footnote references. This plugin fixes it by appending a
* unique hash to each reference/definition.
*/
function plugin() {
return async (root, vfile) => {
const { visit } = await import('unist-util-visit');
const suffix = `-${(0, utils_1.simpleHash)(vfile.path, 6)}`;
visit(root, 'footnoteReference', (node) => {
node.identifier += suffix;
});
visit(root, 'footnoteDefinition', (node) => {
node.identifier += suffix;
});
};
}
exports.default = plugin;

View File

@@ -0,0 +1,10 @@
/**
* 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 type { TranslationFile } from '@docusaurus/types';
import type { PluginOptions, BlogContent } from '@docusaurus/plugin-content-blog';
export declare function getTranslationFiles(options: PluginOptions): TranslationFile[];
export declare function translateContent(content: BlogContent, translationFiles: TranslationFile[]): BlogContent;

View File

@@ -0,0 +1,53 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.translateContent = exports.getTranslationFiles = void 0;
function translateListPage(blogListPaginated, translations) {
return blogListPaginated.map((page) => {
const { items, metadata } = page;
return {
items,
metadata: {
...metadata,
blogTitle: translations.title?.message ?? page.metadata.blogTitle,
blogDescription: translations.description?.message ?? page.metadata.blogDescription,
},
};
});
}
function getTranslationFiles(options) {
return [
{
path: 'options',
content: {
title: {
message: options.blogTitle,
description: 'The title for the blog used in SEO',
},
description: {
message: options.blogDescription,
description: 'The description for the blog used in SEO',
},
'sidebar.title': {
message: options.blogSidebarTitle,
description: 'The label for the left sidebar',
},
},
},
];
}
exports.getTranslationFiles = getTranslationFiles;
function translateContent(content, translationFiles) {
const { content: optionsTranslations } = translationFiles[0];
return {
...content,
blogSidebarTitle: optionsTranslations['sidebar.title']?.message ?? content.blogSidebarTitle,
blogListPaginated: translateListPage(content.blogListPaginated, optionsTranslations),
};
}
exports.translateContent = translateContent;

View File

@@ -0,0 +1,18 @@
/**
* 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 type { BrokenMarkdownLink, ContentPaths } from '@docusaurus/utils';
export type BlogContentPaths = ContentPaths;
export type BlogBrokenMarkdownLink = BrokenMarkdownLink<BlogContentPaths>;
export type BlogMarkdownLoaderOptions = {
siteDir: string;
contentPaths: BlogContentPaths;
truncateMarker: RegExp;
sourceToPermalink: {
[aliasedPath: string]: string;
};
onBrokenMarkdownLink: (brokenMarkdownLink: BlogBrokenMarkdownLink) => void;
};

View File

@@ -0,0 +1,8 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });