feat(v2): allow specifying meta image for blog posts (#2856)

* feat(v2): allow specifying meta image for blog posts

* Update docs [skip ci]
This commit is contained in:
Alexey Pyltsyn 2020-06-02 10:48:22 +03:00 committed by GitHub
parent 2cd326fe20
commit c0c7457e01
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 84 additions and 54 deletions

View file

@ -9,8 +9,10 @@ import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import {MDXProvider} from '@mdx-js/react'; import {MDXProvider} from '@mdx-js/react';
import Head from '@docusaurus/Head';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
import MDXComponents from '@theme/MDXComponents'; import MDXComponents from '@theme/MDXComponents';
import useBaseUrl from '@docusaurus/useBaseUrl';
import styles from './styles.module.css'; import styles from './styles.module.css';
@ -38,12 +40,13 @@ function BlogPostItem(props) {
isBlogPostPage = false, isBlogPostPage = false,
} = props; } = props;
const {date, permalink, tags, readingTime} = metadata; const {date, permalink, tags, readingTime} = metadata;
const {author, title} = frontMatter; const {author, title, image} = frontMatter;
const authorURL = frontMatter.author_url || frontMatter.authorURL; const authorURL = frontMatter.author_url || frontMatter.authorURL;
const authorTitle = frontMatter.author_title || frontMatter.authorTitle; const authorTitle = frontMatter.author_title || frontMatter.authorTitle;
const authorImageURL = const authorImageURL =
frontMatter.author_image_url || frontMatter.authorImageURL; frontMatter.author_image_url || frontMatter.authorImageURL;
const imageUrl = useBaseUrl(image, {absolute: true});
const renderPostHeader = () => { const renderPostHeader = () => {
const TitleHeading = isBlogPostPage ? 'h1' : 'h2'; const TitleHeading = isBlogPostPage ? 'h1' : 'h2';
@ -92,38 +95,48 @@ function BlogPostItem(props) {
}; };
return ( return (
<article className={!isBlogPostPage ? 'margin-bottom--xl' : undefined}> <>
{renderPostHeader()} <Head>
<section className="markdown"> {image && <meta property="og:image" content={imageUrl} />}
<MDXProvider components={MDXComponents}>{children}</MDXProvider> {image && <meta property="twitter:image" content={imageUrl} />}
</section> {image && (
{(tags.length > 0 || truncated) && ( <meta name="twitter:image:alt" content={`Image for ${title}`} />
<footer className="row margin-vert--lg"> )}
{tags.length > 0 && ( </Head>
<div className="col">
<strong>Tags:</strong> <article className={!isBlogPostPage ? 'margin-bottom--xl' : undefined}>
{tags.map(({label, permalink: tagPermalink}) => ( {renderPostHeader()}
<section className="markdown">
<MDXProvider components={MDXComponents}>{children}</MDXProvider>
</section>
{(tags.length > 0 || truncated) && (
<footer className="row margin-vert--lg">
{tags.length > 0 && (
<div className="col">
<strong>Tags:</strong>
{tags.map(({label, permalink: tagPermalink}) => (
<Link
key={tagPermalink}
className="margin-horiz--sm"
to={tagPermalink}>
{label}
</Link>
))}
</div>
)}
{truncated && (
<div className="col text--right">
<Link <Link
key={tagPermalink} to={metadata.permalink}
className="margin-horiz--sm" aria-label={`Read more about ${title}`}>
to={tagPermalink}> <strong>Read More</strong>
{label}
</Link> </Link>
))} </div>
</div> )}
)} </footer>
{truncated && ( )}
<div className="col text--right"> </article>
<Link </>
to={metadata.permalink}
aria-label={`Read more about ${title}`}>
<strong>Read More</strong>
</Link>
</div>
)}
</footer>
)}
</article>
); );
} }

View file

@ -8,7 +8,6 @@
import React from 'react'; import React from 'react';
import Head from '@docusaurus/Head'; import Head from '@docusaurus/Head';
import isInternalUrl from '@docusaurus/isInternalUrl';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import useBaseUrl from '@docusaurus/useBaseUrl'; import useBaseUrl from '@docusaurus/useBaseUrl';
import DocPaginator from '@theme/DocPaginator'; import DocPaginator from '@theme/DocPaginator';
@ -80,10 +79,7 @@ function DocItem(props) {
} = DocContent; } = DocContent;
const metaTitle = title ? `${title} | ${siteTitle}` : siteTitle; const metaTitle = title ? `${title} | ${siteTitle}` : siteTitle;
let metaImageUrl = siteUrl + useBaseUrl(metaImage); const metaImageUrl = useBaseUrl(metaImage, {absolute: true});
if (!isInternalUrl(metaImage)) {
metaImageUrl = metaImage;
}
return ( return (
<> <>

View file

@ -15,7 +15,7 @@ import styles from './styles.module.css';
function FooterLink({to, href, label, prependBaseUrlToHref, ...props}) { function FooterLink({to, href, label, prependBaseUrlToHref, ...props}) {
const toUrl = useBaseUrl(to); const toUrl = useBaseUrl(to);
const normalizedHref = useBaseUrl(href, true); const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});
return ( return (
<Link <Link

View file

@ -7,7 +7,6 @@
import React from 'react'; import React from 'react';
import Head from '@docusaurus/Head'; import Head from '@docusaurus/Head';
import isInternalUrl from '@docusaurus/isInternalUrl';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import useBaseUrl from '@docusaurus/useBaseUrl'; import useBaseUrl from '@docusaurus/useBaseUrl';
@ -39,13 +38,8 @@ function Layout(props) {
version, version,
} = props; } = props;
const metaTitle = title ? `${title} | ${siteTitle}` : siteTitle; const metaTitle = title ? `${title} | ${siteTitle}` : siteTitle;
const metaImage = image || defaultImage; const metaImage = image || defaultImage;
let metaImageUrl = siteUrl + useBaseUrl(metaImage); const metaImageUrl = useBaseUrl(metaImage, {absolute: true});
if (!isInternalUrl(metaImage)) {
metaImageUrl = metaImage;
}
const faviconUrl = useBaseUrl(favicon); const faviconUrl = useBaseUrl(favicon);
return ( return (

View file

@ -32,7 +32,7 @@ function NavLink({
}) { }) {
const toUrl = useBaseUrl(to); const toUrl = useBaseUrl(to);
const activeBaseUrl = useBaseUrl(activeBasePath); const activeBaseUrl = useBaseUrl(activeBasePath);
const normalizedHref = useBaseUrl(href, true); const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});
return ( return (
<Link <Link

View file

@ -16,6 +16,7 @@ describe('useBaseUrl', () => {
mockedContext.mockImplementation(() => ({ mockedContext.mockImplementation(() => ({
siteConfig: { siteConfig: {
baseUrl: '/', baseUrl: '/',
url: 'https://v2.docusaurus.io',
}, },
})); }));
@ -29,12 +30,19 @@ describe('useBaseUrl', () => {
expect(useBaseUrl('/hello/byebye/')).toEqual('/hello/byebye/'); expect(useBaseUrl('/hello/byebye/')).toEqual('/hello/byebye/');
expect(useBaseUrl('https://github.com')).toEqual('https://github.com'); expect(useBaseUrl('https://github.com')).toEqual('https://github.com');
expect(useBaseUrl('//reactjs.org')).toEqual('//reactjs.org'); expect(useBaseUrl('//reactjs.org')).toEqual('//reactjs.org');
expect(useBaseUrl('https://site.com', {forcePrependBaseUrl: true})).toEqual(
'/https://site.com',
);
expect(useBaseUrl('/hello/byebye', {absolute: true})).toEqual(
'https://v2.docusaurus.io/hello/byebye',
);
}); });
test('non-empty base URL', () => { test('non-empty base URL', () => {
mockedContext.mockImplementation(() => ({ mockedContext.mockImplementation(() => ({
siteConfig: { siteConfig: {
baseUrl: '/docusaurus/', baseUrl: '/docusaurus/',
url: 'https://v2.docusaurus.io',
}, },
})); }));
@ -48,5 +56,11 @@ describe('useBaseUrl', () => {
expect(useBaseUrl('/hello/byebye/')).toEqual('/docusaurus/hello/byebye/'); expect(useBaseUrl('/hello/byebye/')).toEqual('/docusaurus/hello/byebye/');
expect(useBaseUrl('https://github.com')).toEqual('https://github.com'); expect(useBaseUrl('https://github.com')).toEqual('https://github.com');
expect(useBaseUrl('//reactjs.org')).toEqual('//reactjs.org'); expect(useBaseUrl('//reactjs.org')).toEqual('//reactjs.org');
expect(useBaseUrl('https://site.com', {forcePrependBaseUrl: true})).toEqual(
'/docusaurus/https://site.com',
);
expect(useBaseUrl('/hello/byebye', {absolute: true})).toEqual(
'https://v2.docusaurus.io/docusaurus/hello/byebye',
);
}); });
}); });

View file

@ -6,13 +6,20 @@
*/ */
import useDocusaurusContext from './useDocusaurusContext'; import useDocusaurusContext from './useDocusaurusContext';
import isInternalUrl from './isInternalUrl';
type BaseUrlOptions = {
forcePrependBaseUrl: boolean;
absolute: boolean;
};
export default function useBaseUrl( export default function useBaseUrl(
url: string, url: string,
forcePrependBaseUrl: boolean = false, {forcePrependBaseUrl = false, absolute = false}: Partial<BaseUrlOptions> = {},
): string { ): string {
const {siteConfig} = useDocusaurusContext(); const {
const {baseUrl = '/'} = siteConfig || {}; siteConfig: {baseUrl = '/', url: siteUrl} = {},
} = useDocusaurusContext();
if (!url) { if (!url) {
return url; return url;
@ -22,14 +29,11 @@ export default function useBaseUrl(
return baseUrl + url; return baseUrl + url;
} }
const externalRegex = /^(https?:|\/\/)/; if (!isInternalUrl(url)) {
if (externalRegex.test(url)) {
return url; return url;
} }
if (url.startsWith('/')) { const basePath = baseUrl + url.replace(/^\//, '');
return baseUrl + url.slice(1);
}
return baseUrl + url; return absolute ? siteUrl + basePath : basePath;
} }

View file

@ -38,6 +38,7 @@ author_title: Co-creator of Docusaurus 1
author_url: https://github.com/JoelMarcey author_url: https://github.com/JoelMarcey
author_image_url: https://graph.facebook.com/611217057/picture/?height=200&width=200 author_image_url: https://graph.facebook.com/611217057/picture/?height=200&width=200
tags: [hello, docusaurus-v2] tags: [hello, docusaurus-v2]
image: https://i.imgur.com/mErPwqL.png
--- ---
Welcome to this blog. This blog is created with [**Docusaurus 2 alpha**](https://v2.docusaurus.io/). Welcome to this blog. This blog is created with [**Docusaurus 2 alpha**](https://v2.docusaurus.io/).
@ -59,6 +60,7 @@ The only required field is `title`; however, we provide options to add author in
- `title` - The blog post title. - `title` - The blog post title.
- `tags` - A list of strings to tag to your post. - `tags` - A list of strings to tag to your post.
- `draft` - A boolean flag to indicate that the blog post is work in process and therefore should not be published yet. However, draft blog posts will be displayed during development. - `draft` - A boolean flag to indicate that the blog post is work in process and therefore should not be published yet. However, draft blog posts will be displayed during development.
- `image`: Cover or thumbnail image that will be used when displaying the link to your post.
## Summary truncation ## Summary truncation

View file

@ -153,6 +153,13 @@ const Test = () => {
React hook to automatically prepend `baseUrl` to a string automatically. This is particularly useful if you don't want to hardcode your config's `baseUrl`. We highly recommend you to use this. React hook to automatically prepend `baseUrl` to a string automatically. This is particularly useful if you don't want to hardcode your config's `baseUrl`. We highly recommend you to use this.
```ts
type BaseUrlOptions = {
forcePrependBaseUrl: boolean;
absolute: boolean;
};
```
Example usage: Example usage:
```jsx {3,11} ```jsx {3,11}