* Improve reusability of Meta component

* Rename page prop to fields on pages / posts

* Update config.yml with meta object for each item
This commit is contained in:
Eric Jinks 2018-06-06 14:36:47 +10:00 committed by GitHub
parent 7bd0b54b6d
commit 7b4780c581
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 186 additions and 158 deletions

View file

@ -1,3 +1,9 @@
## 0.4.0 - 2018-06-06
* Improve reusability of <Meta /> component
* Rename `page` prop to `fields` on pages / posts
* Update config.yml with meta object for each item
## 0.3.1 - 2018-06-01 ## 0.3.1 - 2018-06-01
* Encode CMS image urls * Encode CMS image urls

View file

@ -25,19 +25,19 @@ const posts = getDocuments('posts')
// Preview Templates // Preview Templates
CMS.registerPreviewTemplate('home-page', ({ entry }) => ( CMS.registerPreviewTemplate('home-page', ({ entry }) => (
<Home page={entry.toJS().data} /> <Home fields={entry.toJS().data} />
)) ))
CMS.registerPreviewTemplate('about-page', ({ entry }) => ( CMS.registerPreviewTemplate('about-page', ({ entry }) => (
<About page={entry.toJS().data} /> <About fields={entry.toJS().data} />
)) ))
CMS.registerPreviewTemplate('contact-page', ({ entry }) => ( CMS.registerPreviewTemplate('contact-page', ({ entry }) => (
<Contact page={entry.toJS().data} siteTitle={globalSettings.siteTitle} /> <Contact fields={entry.toJS().data} siteTitle={globalSettings.siteTitle} />
)) ))
CMS.registerPreviewTemplate('blog-page', ({ entry }) => ( CMS.registerPreviewTemplate('blog-page', ({ entry }) => (
<Blog page={entry.toJS().data} posts={posts} /> <Blog fields={entry.toJS().data} posts={posts} />
)) ))
CMS.registerPreviewTemplate('posts', ({ entry }) => ( CMS.registerPreviewTemplate('posts', ({ entry }) => (
<SinglePost singlePost={entry.toJS().data} /> <SinglePost fields={entry.toJS().data} />
)) ))
// Return to home when user logging out // Return to home when user logging out

View file

@ -1,3 +1,7 @@
--- ---
title: Updates title: Updates
meta:
description: Desc
title: Test Updates Title
--- ---

View file

@ -1,8 +1,6 @@
--- ---
title: Hello World! title: Hello World!
status: Featured / Published status: Featured / Published
categories:
- category: News
date: '2018-03-27' date: '2018-03-27'
postFeaturedImage: /images/uploads/tim-marshall-155597.jpg postFeaturedImage: /images/uploads/tim-marshall-155597.jpg
excerpt: >- excerpt: >-
@ -10,6 +8,13 @@ excerpt: >-
interdum arcu vitae augue pulvinar sodales. Sed non dui diam. Quisque lectus interdum arcu vitae augue pulvinar sodales. Sed non dui diam. Quisque lectus
est, lobortis ac efficitur vitae, posuere a mauris. Phasellus ac dui est, lobortis ac efficitur vitae, posuere a mauris. Phasellus ac dui
pellentesque, lacinia risus ut, imperdiet eros. pellentesque, lacinia risus ut, imperdiet eros.
categories:
- category: News
meta:
canonicalLink: ''
description: ''
noindex: false
title: ''
--- ---
Paragraphs are separated by a blank line. Paragraphs are separated by a blank line.

View file

@ -25,6 +25,14 @@ collections: # A list of collections the CMS should be able to edit
- {label: Address, name: address, widget: text} - {label: Address, name: address, widget: text}
- {label: Phone, name: phone, widget: string} - {label: Phone, name: phone, widget: string}
- {label: Email Address, name: email, widget: string} - {label: Email Address, name: email, widget: string}
- label: "Meta"
name: "meta"
widget: "object"
fields:
- {label: Title, name: title, widget: string, required: false}
- {label: Description, name: description, widget: text, required: false, pattern: [".{,300}", "Must not exceed 300 characters"]}
- {label: Canonical Link, name: canonicalLink, widget: string, required: false}
- {label: Noindex, name: noindex, widget: boolean, required: false}
- file: "content/pages/blog.md" - file: "content/pages/blog.md"
label: "Blog Page" label: "Blog Page"
@ -33,6 +41,14 @@ collections: # A list of collections the CMS should be able to edit
- {label: Title, name: title, widget: string} - {label: Title, name: title, widget: string}
- {label: Featured Image, name: featuredImage, widget: image} - {label: Featured Image, name: featuredImage, widget: image}
- {label: Subtitle, name: subtitle, widget: markdown} - {label: Subtitle, name: subtitle, widget: markdown}
- label: "Meta"
name: "meta"
widget: "object"
fields:
- {label: Title, name: title, widget: string, required: false}
- {label: Description, name: description, widget: text, required: false, pattern: [".{,300}", "Must not exceed 300 characters"]}
- {label: Canonical Link, name: canonicalLink, widget: string, required: false}
- {label: Noindex, name: noindex, widget: boolean, required: false}
- file: "content/pages/about.md" - file: "content/pages/about.md"
label: "About Page" label: "About Page"
@ -43,6 +59,14 @@ collections: # A list of collections the CMS should be able to edit
- {label: Subtitle, name: subtitle, widget: markdown} - {label: Subtitle, name: subtitle, widget: markdown}
- {label: Section 1, name: section1, widget: markdown} - {label: Section 1, name: section1, widget: markdown}
- {label: Section 2, name: section2, widget: markdown} - {label: Section 2, name: section2, widget: markdown}
- label: "Meta"
name: "meta"
widget: "object"
fields:
- {label: Title, name: title, widget: string, required: false}
- {label: Description, name: description, widget: text, required: false, pattern: [".{,300}", "Must not exceed 300 characters"]}
- {label: Canonical Link, name: canonicalLink, widget: string, required: false}
- {label: Noindex, name: noindex, widget: boolean, required: false}
- file: "content/pages/home.md" - file: "content/pages/home.md"
label: "Home Page" label: "Home Page"
@ -52,6 +76,14 @@ collections: # A list of collections the CMS should be able to edit
- {label: Featured Image, name: featuredImage, widget: image} - {label: Featured Image, name: featuredImage, widget: image}
- {label: Subtitle, name: subtitle, widget: markdown} - {label: Subtitle, name: subtitle, widget: markdown}
- {label: Body, name: body, widget: markdown} - {label: Body, name: body, widget: markdown}
- label: "Meta"
name: "meta"
widget: "object"
fields:
- {label: Title, name: title, widget: string, required: false}
- {label: Description, name: description, widget: text, required: false, pattern: [".{,300}", "Must not exceed 300 characters"]}
- {label: Canonical Link, name: canonicalLink, widget: string, required: false}
- {label: Noindex, name: noindex, widget: boolean, required: false}
- name: posts - name: posts
@ -83,6 +115,14 @@ collections: # A list of collections the CMS should be able to edit
collection: "postCategories" collection: "postCategories"
searchFields: ["title"] searchFields: ["title"]
valueField: "title" valueField: "title"
- label: "Meta"
name: "meta"
widget: "object"
fields:
- {label: Title, name: title, widget: string, required: false}
- {label: Description, name: description, widget: text, required: false, pattern: [".{,300}", "Must not exceed 300 characters"]}
- {label: Canonical Link, name: canonicalLink, widget: string, required: false}
- {label: Noindex, name: noindex, widget: boolean, required: false}
- name: postCategories - name: postCategories
@ -94,6 +134,14 @@ collections: # A list of collections the CMS should be able to edit
create: true # Allow users to create new documents in this collection create: true # Allow users to create new documents in this collection
fields: # The fields each document in this collection have fields: # The fields each document in this collection have
- {label: Title, name: title, widget: string} - {label: Title, name: title, widget: string}
- label: "Meta"
name: "meta"
widget: "object"
fields:
- {label: Title, name: title, widget: string, required: false}
- {label: Description, name: description, widget: text, required: false, pattern: [".{,300}", "Must not exceed 300 characters"]}
- {label: Canonical Link, name: canonicalLink, widget: string, required: false}
- {label: Noindex, name: noindex, widget: boolean, required: false}
- name: "settings" - name: "settings"

View file

@ -1,7 +1,6 @@
import React, { Component } from 'react' import React, { Component, Fragment } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import Helmet from 'react-helmet' import Helmet from 'react-helmet'
import _findIndex from 'lodash/findIndex'
import ScrollToTop from './components/ScrollToTop' import ScrollToTop from './components/ScrollToTop'
import Meta from './components/Meta' import Meta from './components/Meta'
@ -19,6 +18,18 @@ import data from './data.json'
import { slugify } from './util/url' import { slugify } from './util/url'
import { documentHasTerm, getCollectionTerms } from './util/collection' import { documentHasTerm, getCollectionTerms } from './util/collection'
const RouteWithMeta = ({ component: Component, ...props }) => (
<Route
{...props}
render={routeProps => (
<Fragment>
<Meta {...props} />
<Component {...routeProps} {...props} />
</Fragment>
)}
/>
)
class App extends Component { class App extends Component {
state = { state = {
data data
@ -28,7 +39,7 @@ class App extends Component {
this.state.data[collection] && this.state.data[collection] &&
this.state.data[collection].filter(page => page.name === name)[0] this.state.data[collection].filter(page => page.name === name)[0]
getDocuments = collection => this.state.data[collection] getDocuments = collection => this.state.data[collection] || []
render () { render () {
const globalSettings = this.getDocument('settings', 'global') const globalSettings = this.getDocument('settings', 'global')
@ -59,9 +70,7 @@ class App extends Component {
titleTemplate={`${siteTitle} | %s`} titleTemplate={`${siteTitle} | %s`}
/> />
<Meta <Meta
title={siteTitle} headerScripts={headerScripts}
url={siteUrl}
description={siteDescription}
absoluteImageUrl={ absoluteImageUrl={
socialMediaCard && socialMediaCard &&
socialMediaCard.image && socialMediaCard.image &&
@ -73,95 +82,75 @@ class App extends Component {
twitterSiteAccount={ twitterSiteAccount={
socialMediaCard && socialMediaCard.twitterSiteAccount socialMediaCard && socialMediaCard.twitterSiteAccount
} }
headerScripts={headerScripts}
/> />
<Nav /> <Nav />
<Switch> <Switch>
<Route <RouteWithMeta
path='/' path='/'
exact exact
render={props => ( component={Home}
<Home page={this.getDocument('pages', 'home')} {...props} /> description={siteDescription}
)} fields={this.getDocument('pages', 'home')}
/> />
<Route <RouteWithMeta
path='/about/' path='/about/'
exact exact
render={props => ( component={About}
<About page={this.getDocument('pages', 'about')} {...props} /> fields={this.getDocument('pages', 'about')}
)}
/> />
<Route <RouteWithMeta
path='/contact/' path='/contact/'
exact exact
render={props => ( component={Contact}
<Contact fields={this.getDocument('pages', 'contact')}
page={this.getDocument('pages', 'contact')} siteTitle={siteTitle}
siteTitle={siteTitle}
{...props}
/>
)}
/> />
<RouteWithMeta
{/* Blog Routes */}
<Route
path='/blog/' path='/blog/'
exact exact
render={props => ( component={Blog}
<Blog fields={this.getDocument('pages', 'blog')}
page={this.getDocument('pages', 'blog')} posts={posts}
posts={posts} postCategories={postCategories}
postCategories={postCategories} />
{...props}
{posts.map((post, index) => {
const path = slugify(`/blog/${post.title}`)
const nextPost = posts[index - 1]
const prevPost = posts[index + 1]
return (
<RouteWithMeta
key={path}
path={path}
exact
component={SinglePost}
fields={post}
nextPostURL={nextPost && slugify(`/blog/${nextPost.title}/`)}
prevPostURL={prevPost && slugify(`/blog/${prevPost.title}/`)}
/> />
)} )
/> })}
<Route
path='/blog/category/:slug/' {postCategories.map(postCategory => {
render={props => { const slug = slugify(postCategory.title)
// help needed const path = slugify(`/blog/category/${slug}`)
const slug = props.match.params.slug const categoryPosts = posts.filter(post =>
const categoryPosts = posts.filter(post => documentHasTerm(post, 'categories', slug)
documentHasTerm(post, 'categories', slug) )
) return (
if (!categoryPosts.length) return <NoMatch siteUrl={siteUrl} /> <RouteWithMeta
return ( key={path}
<Blog path={path}
page={this.getDocument('pages', 'blog')} exact
posts={categoryPosts} component={Blog}
postCategories={postCategories} fields={this.getDocument('pages', 'blog')}
showFeatured={false} posts={categoryPosts}
{...props} postCategories={postCategories}
/> />
) )
}} })}
/>
<Route
path='/blog/:slug/'
render={props => {
const slug = props.match.params.slug
const singlePostID = _findIndex(
posts,
item => slugify(item.title) === slug
)
const singlePost = posts[singlePostID]
const nextPost = posts[singlePostID - 1]
const prevPost = posts[singlePostID + 1]
if (!singlePost) return <NoMatch siteUrl={siteUrl} />
return (
<SinglePost
singlePost={singlePost}
nextPostURL={
nextPost && `/blog/${slugify(nextPost.title)}/`
}
prevPostURL={
prevPost && `/blog/${slugify(prevPost.title)}/`
}
{...props}
/>
)
}}
/>
<Route render={() => <NoMatch siteUrl={siteUrl} />} /> <Route render={() => <NoMatch siteUrl={siteUrl} />} />
</Switch> </Switch>

View file

@ -1,58 +1,56 @@
import React from 'react' import React from 'react'
import Helmet from 'react-helmet' import Helmet from 'react-helmet'
import _get from 'lodash/get'
import _pickBy from 'lodash/pickBy'
const Meta = ({ const onlyTruthyValues = obj => _pickBy(obj, item => item)
title,
url, const Meta = props => {
description, const {
absoluteImageUrl, title,
twitterSiteAccount, url,
twitterCreatorAccount, description,
headerScripts absoluteImageUrl,
}) => { twitterSiteAccount,
twitterCreatorAccount,
headerScripts,
noindex,
canonicalLink
// overwrite { title, description } if in fields or fields.meta
} = {
...props,
...onlyTruthyValues(_get(props, 'fields')),
...onlyTruthyValues(_get(props, 'fields.meta'))
}
// write headerScripts
const headerScriptsElement = document.head.querySelector('#headerScripts') const headerScriptsElement = document.head.querySelector('#headerScripts')
if (headerScripts && headerScriptsElement) { if (headerScripts && headerScriptsElement) {
headerScriptsElement.outerHTML = headerScripts headerScriptsElement.outerHTML = headerScripts
} }
const runChecks = () => {
if (process.env.NODE_ENV !== 'development') return
// Site Url Check
if (url === 'https://netlify-cms-react-starter.netlify.com') {
console.warn(`Site url may need updating (${url})`)
}
// Title
if (!title) {
console.error(`Missing Site Title`)
}
// Description
if (!description) {
console.error(`Missing Site Description`)
}
}
runChecks()
return ( return (
<Helmet> <Helmet>
<meta name='description' content={description} /> {title && <title>{title}</title>}
<meta property='og:title' content={title} /> {title && <meta property='og:title' content={title} />}
<meta property='og:type' content='website' /> {description && <meta name='description' content={description} />}
<meta property='og:url' content={url} /> {description && <meta property='og:description' content={description} />}
<meta property='og:description' content={description} /> {url && <meta property='og:type' content='website' />}
{url && <meta property='og:url' content={url} />}
{absoluteImageUrl && (
<meta name='twitter:card' content='summary_large_image' />
)}
{absoluteImageUrl && ( {absoluteImageUrl && (
<meta property='og:image' content={absoluteImageUrl} /> <meta property='og:image' content={absoluteImageUrl} />
)} )}
<meta name='twitter:card' content='summary_large_image' />
{twitterSiteAccount && ( {twitterSiteAccount && (
<meta name='twitter:site' content={twitterSiteAccount} /> <meta name='twitter:site' content={twitterSiteAccount} />
)} )}
{twitterCreatorAccount && ( {twitterCreatorAccount && (
<meta name='twitter:creator' content={twitterCreatorAccount} /> <meta name='twitter:creator' content={twitterCreatorAccount} />
)} )}
{noindex && <meta name='robots' content='noindex' />}
{canonicalLink && <link rel='canonical' href={canonicalLink} />}
</Helmet> </Helmet>
) )
} }

View file

@ -1,18 +1,14 @@
import React from 'react' import React from 'react'
import Helmet from 'react-helmet'
import PageHeader from '../components/PageHeader' import PageHeader from '../components/PageHeader'
import LazyImage from '../components/LazyImage' import LazyImage from '../components/LazyImage'
import Content from '../components/Content.js' import Content from '../components/Content.js'
import './About.css' import './About.css'
export default ({ page }) => { export default ({ fields }) => {
const { title, subtitle, featuredImage, section1, section2 } = page const { title, subtitle, featuredImage, section1, section2 } = fields
return ( return (
<div className='About'> <div className='About'>
<Helmet>
<title>{title}</title>
</Helmet>
<PageHeader <PageHeader
title={title} title={title}
subtitle={subtitle} subtitle={subtitle}

View file

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import Helmet from 'react-helmet'
import _sortBy from 'lodash/sortBy' import _sortBy from 'lodash/sortBy'
import PageHeader from '../components/PageHeader' import PageHeader from '../components/PageHeader'
@ -9,20 +8,16 @@ import PostSection from '../components/PostSection'
import './Blog.css' import './Blog.css'
export default ({ export default ({
page, fields,
posts = [], posts = [],
postCategories = [], postCategories = [],
showFeatured = true showFeatured = true
}) => { }) => {
const { title, subtitle, featuredImage } = page const { title, subtitle, featuredImage } = fields
posts = _sortBy(posts, ['date']).reverse() posts = _sortBy(posts, ['date']).reverse()
return ( return (
<main className='Blog'> <main className='Blog'>
<Helmet>
<title>{title}</title>
</Helmet>
<PageHeader <PageHeader
title={title} title={title}
subtitle={subtitle} subtitle={subtitle}
@ -32,6 +27,7 @@ export default ({
{!!postCategories.length && ( {!!postCategories.length && (
<PostCategoriesNav categories={postCategories} /> <PostCategoriesNav categories={postCategories} />
)} )}
{!!posts.length && <PostSection posts={posts} />} {!!posts.length && <PostSection posts={posts} />}
</main> </main>
) )

View file

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import Helmet from 'react-helmet'
import { MapPin, Smartphone, Mail } from 'react-feather' import { MapPin, Smartphone, Mail } from 'react-feather'
import PageHeader from '../components/PageHeader' import PageHeader from '../components/PageHeader'
@ -7,14 +6,10 @@ import EnquiryFormSimpleAjax from '../components/EnquiryFormSimpleAjax'
import Content from '../components/Content' import Content from '../components/Content'
import './Contact.css' import './Contact.css'
export default ({ page }) => { export default ({ fields }) => {
const { title, subtitle, featuredImage, address, phone, email } = page const { body, title, subtitle, featuredImage, address, phone, email } = fields
return ( return (
<div className='Contact'> <div className='Contact'>
<Helmet>
<title>{page.title}</title>
</Helmet>
<PageHeader <PageHeader
title={title} title={title}
subtitle={subtitle} subtitle={subtitle}
@ -24,7 +19,7 @@ export default ({ page }) => {
<div className='section Contact--Section1'> <div className='section Contact--Section1'>
<div className='container Contact--Section1--Container'> <div className='container Contact--Section1--Container'>
<div> <div>
<Content source={page.body} /> <Content source={body} />
<div className='Contact--Details'> <div className='Contact--Details'>
{address && ( {address && (

View file

@ -1,23 +1,20 @@
import React from 'react' import React from 'react'
import Helmet from 'react-helmet'
import Content from '../components/Content' import Content from '../components/Content'
import PageHeader from '../components/PageHeader' import PageHeader from '../components/PageHeader'
import './Home.css' import './Home.css'
export default ({ page }) => { export default ({ fields }) => {
const { title, subtitle, featuredImage, body } = page const { title, subtitle, featuredImage, body } = fields
return ( return (
<main className='Home'> <main className='Home'>
<Helmet>
<title>{title}</title>
</Helmet>
<PageHeader <PageHeader
large large
title={title} title={title}
subtitle={subtitle} subtitle={subtitle}
backgroundImage={featuredImage} backgroundImage={featuredImage}
/> />
<div className='section'> <div className='section'>
<div className='container'> <div className='container'>
<Content source={body} /> <Content source={body} />

View file

@ -1,22 +1,16 @@
import React from 'react' import React from 'react'
import Helmet from 'react-helmet'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { ChevronLeft } from 'react-feather' import { ChevronLeft } from 'react-feather'
import Content from '../components/Content' import Content from '../components/Content'
import BackgroundImage from '../components/BackgroundImage' import BackgroundImage from '../components/BackgroundImage'
import { dateFormatted } from '../util/date' import { dateFormatted } from '../util/date'
import './SinglePost.css' import './SinglePost.css'
export default ({ singlePost, nextPostURL, prevPostURL }) => { export default ({ fields, nextPostURL, prevPostURL }) => {
const { title, date, postFeaturedImage, body, categories = [] } = singlePost const { title, date, postFeaturedImage, body, categories = [] } = fields
return ( return (
<article className='SinglePost section light'> <article className='SinglePost section light'>
<Helmet>
<title>{title}</title>
</Helmet>
{postFeaturedImage && ( {postFeaturedImage && (
<BackgroundImage <BackgroundImage
className='SinglePost--BackgroundImage' className='SinglePost--BackgroundImage'