mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-17 19:16:58 +02:00
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:
parent
2cd326fe20
commit
c0c7457e01
9 changed files with 84 additions and 54 deletions
|
@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue