docusaurus/packages/docusaurus-theme-common/src/contexts/blogPost.tsx
2023-01-10 15:22:19 -05:00

80 lines
2.3 KiB
TypeScript

/**
* 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 React, {useMemo, type ReactNode, useContext} from 'react';
import {ReactContextError} from '../utils/reactUtils';
import type {PropBlogPostContent} from '@docusaurus/plugin-content-blog';
/**
* The React context value returned by the `useBlogPost()` hook.
* It contains useful data related to the currently browsed blog post.
*/
export type BlogPostContextValue = Pick<
PropBlogPostContent,
'metadata' | 'frontMatter' | 'assets' | 'toc'
> & {
readonly isBlogPostPage: boolean;
};
const Context = React.createContext<BlogPostContextValue | null>(null);
/**
* Note: we don't use `PropBlogPostContent` as context value on purpose.
* Metadata is currently stored inside the MDX component, but we may want to
* change that in the future.
*/
function useContextValue({
content,
isBlogPostPage,
}: {
content: PropBlogPostContent;
isBlogPostPage: boolean;
}): BlogPostContextValue {
return useMemo(
() => ({
metadata: content.metadata,
frontMatter: content.frontMatter,
assets: content.assets,
toc: content.toc,
isBlogPostPage,
}),
[content, isBlogPostPage],
);
}
/**
* This is a very thin layer around the `content` received from the MDX loader.
* It provides metadata about the blog post to the children tree.
*/
export function BlogPostProvider({
children,
content,
isBlogPostPage = false,
}: {
children: ReactNode;
content: PropBlogPostContent;
isBlogPostPage?: boolean;
}): JSX.Element {
const contextValue = useContextValue({content, isBlogPostPage});
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}
/**
* Returns the data of the currently browsed blog post. Gives access to
* front matter, metadata, TOC, etc.
* When swizzling a low-level component (e.g. the "Edit this page" link)
* and you need some extra metadata, you don't have to drill the props
* all the way through the component tree: simply use this hook instead.
*/
export function useBlogPost(): BlogPostContextValue {
const blogPost = useContext(Context);
if (blogPost === null) {
throw new ReactContextError('BlogPostProvider');
}
return blogPost;
}