Merge branch 'netlify-cms'
This commit is contained in:
commit
c3ce1dd09e
27 changed files with 2153 additions and 1339 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -13,3 +13,5 @@ build
|
|||
.DS_Store
|
||||
.env
|
||||
npm-debug.log
|
||||
|
||||
src/data.json
|
||||
|
|
|
|||
30
content/pages/about.md
Normal file
30
content/pages/about.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
title: About page title
|
||||
subtitle: <About />
|
||||
featuredImage: /images/uploads/isabella-juskova-260426.jpg
|
||||
section1: |-
|
||||
## Hello World!
|
||||
|
||||
Netlify CMS works with both `.md` and `.json`.
|
||||
|
||||

|
||||
section2: >-
|
||||
## This is a Container component
|
||||
|
||||
|
||||
A sem vel nec sodales mi vivamus senectus sed potenti a parturient nascetur
|
||||
tincidunt nisi pulvinar rhoncus a. Risus imperdiet taciti suspendisse facilisi
|
||||
a per metus cubilia varius a nostra adipiscing amet ultrices quisque ac mi a.
|
||||
Dictumst a ultrices mi a dignissim ad fermentum eget a nam et a blandit
|
||||
scelerisque. Taciti lorem tempor quam vestibulum dis habitasse vestibulum diam
|
||||
vel est ut proin dis auctor. Suscipit scelerisque orci magna interdum vel
|
||||
bibendum duis netus a consectetur dui magnis ac aliquet sem posuere tincidunt
|
||||
vestibulum.
|
||||
|
||||
|
||||
The image below will have a `srcset` attribute generated:
|
||||
|
||||
|
||||

|
||||
---
|
||||
|
||||
1
content/pages/contact.json
Normal file
1
content/pages/contact.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"title":"Contact","subtitle":"<Contact />","body":"# Example contact form\n\nThis form is setup to use Netlify's form handling:\n\n- the form action is set to the current absolute url: `action: '/contact/'`\n- a name attribute is sent with the form's data `'form-name': 'Contact'`\n- netlify data attributes are added to the form `data-netlify data-netlify-honeypot`\n\nFind out more in the [Netlify Docs](https://www.netlify.com/docs/form-handling/).\n"}
|
||||
1
content/pages/home.json
Normal file
1
content/pages/home.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"title":"Home","subtitle":"<Home />","body":"# 🍉 HyperStatic\n\nA not-so-static site boilerplate:\n\n* **Create React App** for simplicity\n* **Styled Components** for component-based css\n* **React Router** for routing (v4)\n* **React Helmet** for document titles, descriptions, meta\n* **React Snapshot** for pre-rendering to static html so it works without Javascript ⭐️\n* [**Netlify CMS**](https://netlifycms.org)"}
|
||||
1
content/settings/global-yaml.yml
Normal file
1
content/settings/global-yaml.yml
Normal file
|
|
@ -0,0 +1 @@
|
|||
siteTitle: HyperStatic
|
||||
3
content/settings/global.json
Normal file
3
content/settings/global.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"siteTitle": "HyperStatic"
|
||||
}
|
||||
89
functions/parse-content.js
Normal file
89
functions/parse-content.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const _set = require('lodash/set')
|
||||
const _mergeWith = require('lodash/mergeWith')
|
||||
const _isArray = require('lodash/isArray')
|
||||
const globCb = require('glob')
|
||||
const util = require('util')
|
||||
|
||||
const glob = util.promisify(globCb)
|
||||
const readFile = util.promisify(fs.readFile)
|
||||
const matter = require('gray-matter')
|
||||
const yaml = require('js-yaml')
|
||||
|
||||
const options = {
|
||||
contentDir: './content/',
|
||||
outputFile: './src/data.json'
|
||||
}
|
||||
|
||||
const getCollectionType = filePath => {
|
||||
const pathParsed = path.parse(filePath)
|
||||
const objectKey = pathParsed.dir
|
||||
.replace(options.contentDir, '')
|
||||
.replace(/\//g, '.')
|
||||
return `${objectKey}`
|
||||
}
|
||||
|
||||
const getDocumentName = filePath => {
|
||||
const pathParsed = path.parse(filePath)
|
||||
return `${pathParsed.name}`
|
||||
}
|
||||
|
||||
const getDocumentExt = filePath => {
|
||||
const pathParsed = path.parse(filePath)
|
||||
return `${pathParsed.ext}`
|
||||
}
|
||||
|
||||
const parseMarkdown = data => {
|
||||
data = matter(data)
|
||||
data = { ...data, ...data.data }
|
||||
delete data.data
|
||||
return JSON.stringify(data)
|
||||
}
|
||||
|
||||
const parseYaml = data => {
|
||||
try {
|
||||
data = yaml.safeLoad(data, 'utf8')
|
||||
return JSON.stringify(data)
|
||||
} catch (e) {
|
||||
return console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
const getFileContents = filePath => {
|
||||
return readFile(filePath, 'utf8').then(data => {
|
||||
if (getDocumentExt(filePath) === '.md') {
|
||||
data = parseMarkdown(data)
|
||||
}
|
||||
if (['.yaml', '.yml'].includes(getDocumentExt(filePath))) {
|
||||
data = parseYaml(data)
|
||||
}
|
||||
let documentData = JSON.parse(data)
|
||||
documentData.name = getDocumentName(filePath)
|
||||
let obj = {}
|
||||
_set(obj, getCollectionType(filePath), [documentData])
|
||||
console.log(`✨ Processed ${filePath}`)
|
||||
return obj
|
||||
})
|
||||
}
|
||||
|
||||
const readFiles = async paths => Promise.all(paths.map(getFileContents))
|
||||
|
||||
const combineJSON = async () => {
|
||||
// mergeCustomiser concats arrays items
|
||||
const mergeCustomiser = (objValue, srcValue) =>
|
||||
_isArray(objValue) ? objValue.concat(srcValue) : objValue
|
||||
console.log(`✨ Reading JSON files in ${options.contentDir}`)
|
||||
const paths = await glob(`${options.contentDir}/**/**.+(json|md|yaml|yml)`)
|
||||
const results = await readFiles(paths)
|
||||
const data = _mergeWith({}, ...results, mergeCustomiser)
|
||||
return JSON.stringify(data, null, 2)
|
||||
}
|
||||
|
||||
const writeJSON = async () => {
|
||||
const json = await combineJSON()
|
||||
fs.writeFileSync(options.outputFile, json)
|
||||
console.log(`✅ Data saved to ${options.outputFile}`)
|
||||
}
|
||||
|
||||
writeJSON()
|
||||
83
functions/resize-images.js
Normal file
83
functions/resize-images.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const globCb = require('glob')
|
||||
const util = require('util')
|
||||
const sharp = require('sharp')
|
||||
|
||||
const glob = util.promisify(globCb)
|
||||
const readFile = util.promisify(fs.readFile)
|
||||
|
||||
const options = {
|
||||
inputDir: './public/images/uploads',
|
||||
outputDir: './public/images/uploads/resized',
|
||||
sizes: [300, 600, 1200, 1800],
|
||||
imageFormats: ['jpg', 'jpeg', 'png', 'gif', 'webp']
|
||||
}
|
||||
|
||||
const saveImage = ({ buffer, size, outputFile }) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
sharp(buffer)
|
||||
.resize(size)
|
||||
.toFile(outputFile, err => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
} else {
|
||||
return resolve(console.log(`✅ Saved ${outputFile}`))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const saveImages = ({ buffer, filename }) => {
|
||||
console.log(`🎞 Processing ${filename}`)
|
||||
return Promise.all(
|
||||
options.sizes.map(async size => {
|
||||
const extname = path.extname(filename)
|
||||
const newFilename = `${path.basename(
|
||||
filename,
|
||||
extname
|
||||
)}.${size}${extname}`
|
||||
const outputFile = `${options.outputDir}/${newFilename}`
|
||||
const fileExists = await doesFileExist({ filename: outputFile })
|
||||
if (fileExists) return console.log(`↩️ ${outputFile} exists, skipping`)
|
||||
return saveImage({ buffer, size, outputFile })
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const readFiles = files =>
|
||||
Promise.all(
|
||||
files.map(async filename => {
|
||||
const buffer = await readFile(filename)
|
||||
return { filename, buffer }
|
||||
})
|
||||
)
|
||||
|
||||
const doesFileExist = async ({ filename }) => {
|
||||
try {
|
||||
await readFile(filename)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const resizeImages = async () => {
|
||||
console.log(`✨ Reading image files in ${options.inputDir}`)
|
||||
try {
|
||||
const fileGlob = `${options.inputDir}/**/**.+(${options.imageFormats.join(
|
||||
'|'
|
||||
)})`
|
||||
const files = await glob(fileGlob)
|
||||
const ignore = new RegExp(
|
||||
`(${options.sizes.join('|')}).(${options.imageFormats.join('|')})$`
|
||||
)
|
||||
const filesToResize = files.filter(filename => !filename.match(ignore))
|
||||
const imageFiles = await readFiles(filesToResize)
|
||||
imageFiles.map(saveImages)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
resizeImages()
|
||||
19
package.json
19
package.json
|
|
@ -12,13 +12,18 @@
|
|||
"eslint-plugin-promise": "^3.5.0",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"glob": "^7.1.2",
|
||||
"gray-matter": "^3.1.1",
|
||||
"js-yaml": "^3.10.0",
|
||||
"react-scripts": "^1.0.10",
|
||||
"sharp": "^0.18.4",
|
||||
"snazzy": "^7.0.0",
|
||||
"standard": "^10.0.2",
|
||||
"sw-precache": "^5.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"polished": "1.7.0",
|
||||
"netlify-identity-widget": "^1.2.0",
|
||||
"polished": "^1.7.0",
|
||||
"react": "^16.0.0",
|
||||
"react-dom": "^16.0.0",
|
||||
"react-helmet": "^5.1.3",
|
||||
|
|
@ -29,15 +34,17 @@
|
|||
"styled-components": "^2.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build && react-snapshot && npm run sw",
|
||||
"start":
|
||||
"npm run parse-content && npm run resize-images && react-scripts start",
|
||||
"build":
|
||||
"npm run parse-content && npm run resize-images && react-scripts build && react-snapshot && npm run sw",
|
||||
"parse-content": "node ./functions/parse-content.js",
|
||||
"resize-images": "node ./functions/resize-images.js",
|
||||
"sw": "sw-precache --config='sw-precache-config.js'",
|
||||
"test": "standard | snazzy && react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"reactSnapshot": {
|
||||
"include": [
|
||||
"/404"
|
||||
]
|
||||
"include": ["/404"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
52
public/admin/config.yml
Normal file
52
public/admin/config.yml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# See https://github.com/netlify/netlify-cms/blob/master/example/config.yml
|
||||
backend:
|
||||
name: git-gateway
|
||||
branch: netlify-cms # Branch to update (optional; defaults to master)
|
||||
|
||||
media_folder: "public/images/uploads" # Media files will be stored in the repo under static/images/uploads
|
||||
public_folder: "/images/uploads" # The src attribute for uploaded media will begin with /images/uploads
|
||||
|
||||
collections: # A list of collections the CMS should be able to edit
|
||||
- name: "settings"
|
||||
label: "Settings"
|
||||
delete: false # Prevent users from deleting documents in this collection
|
||||
editor:
|
||||
preview: false
|
||||
files:
|
||||
- file: "content/settings/global.json"
|
||||
label: "Global Settings"
|
||||
name: "global-settings"
|
||||
fields:
|
||||
- {label: Site Title, name: siteTitle, widget: string}
|
||||
- file: "content/settings/global-yaml.yml"
|
||||
label: "Global Settings - Yaml"
|
||||
name: "global-settings-yaml"
|
||||
fields:
|
||||
- {label: Site Title, name: siteTitle, widget: string}
|
||||
- name: "pages"
|
||||
label: "Pages"
|
||||
files:
|
||||
- file: "content/pages/home.json"
|
||||
label: "Home Page"
|
||||
name: "home-page"
|
||||
fields:
|
||||
- {label: Title, name: title, widget: string}
|
||||
- {label: Subtitle, name: subtitle, widget: string}
|
||||
- {label: Body, name: body, widget: markdown}
|
||||
# markdown files are ok
|
||||
- file: "content/pages/about.md"
|
||||
label: "About Page"
|
||||
name: "about-page"
|
||||
fields:
|
||||
- {label: Title, name: title, widget: string}
|
||||
- {label: Subtitle, name: subtitle, widget: string}
|
||||
- {label: Featured Image, name: featuredImage, widget: image}
|
||||
- {label: Section 1, name: section1, widget: markdown}
|
||||
- {label: Section 2, name: section2, widget: markdown}
|
||||
- file: "content/pages/contact.json"
|
||||
label: "Contact Page"
|
||||
name: "contact-page"
|
||||
fields:
|
||||
- {label: Title, name: title, widget: string}
|
||||
- {label: Subtitle, name: subtitle, widget: string}
|
||||
- {label: Body, name: body, widget: markdown}
|
||||
24
public/admin/index.html
Normal file
24
public/admin/index.html
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Content Manager</title>
|
||||
|
||||
<!-- Include the styles for the Netlify CMS UI, after your own styles -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/netlify-cms/dist/cms.css" />
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<!-- Include the script that builds the page and powers Netlify CMS -->
|
||||
<script src="https://identity-js.netlify.com/v1/netlify-identity-widget.js"></script>
|
||||
<script src="https://unpkg.com/netlify-cms@0.7.3/dist/cms.js"></script>
|
||||
|
||||
<script>
|
||||
// redirect when logging out
|
||||
window.netlifyIdentity.on('logout', function () {
|
||||
document.location.href = '/'
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
public/images/uploads/isabella-juskova-260426.jpg
Normal file
BIN
public/images/uploads/isabella-juskova-260426.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
2
public/images/uploads/resized/.gitignore
vendored
Normal file
2
public/images/uploads/resized/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
BIN
public/images/uploads/tim-marshall-155597.jpg
Normal file
BIN
public/images/uploads/tim-marshall-155597.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 920 KiB |
BIN
public/images/uploads/unsplash4.jpg
Normal file
BIN
public/images/uploads/unsplash4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 283 KiB |
69
src/App.js
69
src/App.js
|
|
@ -8,63 +8,46 @@ import About from './views/About'
|
|||
import Contact from './views/Contact'
|
||||
import NoMatch from './views/NoMatch'
|
||||
import Nav from './components/Nav'
|
||||
import NavLink from './components/NavLink'
|
||||
import Logo from './components/Logo'
|
||||
import GithubCorner from './components/GithubCorner'
|
||||
import ServiceWorkerNotifications from './components/ServiceWorkerNotifications'
|
||||
import globalStyles from './globalStyles'
|
||||
|
||||
export const siteTitle = 'HyperStatic'
|
||||
const routes = [
|
||||
{
|
||||
title: 'Home',
|
||||
path: '/',
|
||||
comp: Home,
|
||||
exact: true
|
||||
}, {
|
||||
title: 'About',
|
||||
path: '/about/',
|
||||
comp: About,
|
||||
exact: true
|
||||
}, {
|
||||
title: 'Contact',
|
||||
path: '/contact/',
|
||||
comp: Contact,
|
||||
exact: true
|
||||
}
|
||||
]
|
||||
import data from './data.json'
|
||||
|
||||
class App extends Component {
|
||||
componentWillMount () {
|
||||
globalStyles()
|
||||
state = {
|
||||
data
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
globalStyles()
|
||||
import('./netlifyIdentity')
|
||||
}
|
||||
|
||||
getDocument = (collection, name) =>
|
||||
this.state.data[collection] && this.state.data[collection].filter(page => page.name === name)[0]
|
||||
|
||||
getDocuments = (collection) => this.state.data[collection]
|
||||
|
||||
render () {
|
||||
const site = this.getDocument('settings', 'global')
|
||||
return (
|
||||
<Router>
|
||||
<div>
|
||||
<ScrollToTop />
|
||||
<ServiceWorkerNotifications
|
||||
readyMessage='This message is displayed when the SW is registered'
|
||||
/>
|
||||
<ServiceWorkerNotifications />
|
||||
<GithubCorner url='https://github.com/Jinksi/hyperstatic' />
|
||||
<Helmet titleTemplate={`${siteTitle} | %s`} />
|
||||
<Nav>
|
||||
<Logo>
|
||||
<span role='img' aria-label='Watermelon'>🍉</span>
|
||||
</Logo>
|
||||
{routes.map((route, i) => (
|
||||
<NavLink key={i} {...route} />
|
||||
))}
|
||||
</Nav>
|
||||
<Helmet titleTemplate={`${site.siteTitle} | %s`} />
|
||||
<Nav />
|
||||
<Switch>
|
||||
{routes.map((route, i) => (
|
||||
<Route
|
||||
{...route}
|
||||
key={i}
|
||||
render={() => <route.comp {...route} />}
|
||||
/>
|
||||
))}
|
||||
<Route path='/' exact
|
||||
render={(props) => <Home page={this.getDocument('pages', 'home')} {...props} />}
|
||||
/>
|
||||
<Route path='/about/' exact
|
||||
render={(props) => <About page={this.getDocument('pages', 'about')} {...props} />}
|
||||
/>
|
||||
<Route path='/contact/' exact
|
||||
render={(props) => <Contact page={this.getDocument('pages', 'contact')} site={site} {...props} />}
|
||||
/>
|
||||
<Route component={NoMatch} />
|
||||
</Switch>
|
||||
</div>
|
||||
|
|
|
|||
26
src/components/Content.js
Normal file
26
src/components/Content.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Marked from 'react-markdown'
|
||||
import { getImageSrc, getImageSrcset } from '../util/getImageUrl'
|
||||
|
||||
export default ({ source }) => (
|
||||
<Marked
|
||||
source={source}
|
||||
renderers={{
|
||||
Image: ImageWithSrcset
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
const Image = styled.img`
|
||||
width: 100%;
|
||||
height: auto;
|
||||
`
|
||||
|
||||
const ImageWithSrcset = props => (
|
||||
<Image
|
||||
{...props}
|
||||
src={getImageSrc(props.src)}
|
||||
srcSet={getImageSrcset(props.src)}
|
||||
/>
|
||||
)
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { Container, Flex } from './common'
|
||||
import NavLink from './NavLink'
|
||||
import Logo from './Logo'
|
||||
|
||||
const Nav = styled.div`
|
||||
background: white;
|
||||
|
|
@ -11,7 +13,14 @@ const Nav = styled.div`
|
|||
export default (props) => (
|
||||
<Nav>
|
||||
<Container>
|
||||
<Flex alignCenter {...props} />
|
||||
<Flex alignCenter>
|
||||
<Logo>
|
||||
<span role='img' aria-label='Watermelon'>🍉</span>
|
||||
</Logo>
|
||||
<NavLink to='/' exact>Home</NavLink>
|
||||
<NavLink to='/about/' exact>About</NavLink>
|
||||
<NavLink to='/contact/' exact>Contact</NavLink>
|
||||
</Flex>
|
||||
</Container>
|
||||
</Nav>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ const NavLink = styled.span`
|
|||
}
|
||||
`
|
||||
|
||||
export default ({ path, exact, ...props }) => (
|
||||
<Route path={path} exact={exact} children={({match}) => (
|
||||
export default ({ to, exact, match, children }) => (
|
||||
<Route path={to} exact={exact} children={({match}) => (
|
||||
<NavLink active={match}>
|
||||
<Link to={path}>{props.title}</Link>
|
||||
<Link to={to}>{children}</Link>
|
||||
</NavLink>
|
||||
)} />
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import React, { Component } from 'react'
|
|||
import styled from 'styled-components'
|
||||
import { stringify } from 'qs'
|
||||
import { color } from '../globalStyles'
|
||||
import { siteTitle } from '../App'
|
||||
const fetch = window.fetch
|
||||
|
||||
class Form extends Component {
|
||||
|
|
@ -10,7 +9,7 @@ class Form extends Component {
|
|||
name: '',
|
||||
email: '',
|
||||
message: '',
|
||||
subject: `New Submission from ${siteTitle}!`,
|
||||
subject: `New Submission from ${this.props.siteTitle}!`,
|
||||
_gotcha: '',
|
||||
disabled: false,
|
||||
alert: '',
|
||||
|
|
@ -60,7 +59,7 @@ class Form extends Component {
|
|||
name: '',
|
||||
email: '',
|
||||
message: '',
|
||||
subject: `New Submission from ${siteTitle}!`,
|
||||
subject: `New Submission from ${this.props.siteTitle}!`,
|
||||
_gotcha: ''
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import { Section, Container } from './common'
|
||||
|
||||
import { Section, Container, BackgroundImage } from './common'
|
||||
import { getImageSrc } from '../util/getImageUrl'
|
||||
import { color } from '../globalStyles'
|
||||
|
||||
const Header = styled(Section)`
|
||||
|
|
@ -10,14 +11,21 @@ const Header = styled(Section)`
|
|||
color: ${color.primary};
|
||||
background: ${color.primary};
|
||||
color: white;
|
||||
h2{
|
||||
|
||||
h2 {
|
||||
font-weight: 200;
|
||||
}
|
||||
`
|
||||
|
||||
const PageHeader = ({ title, subtitle }) => (
|
||||
<Header>
|
||||
<Container>
|
||||
const PageHeader = ({ title, subtitle, backgroundImage }) => (
|
||||
<Header relative>
|
||||
{backgroundImage && (
|
||||
<BackgroundImage
|
||||
image={getImageSrc(backgroundImage, '1200')}
|
||||
opacity={0.5}
|
||||
/>
|
||||
)}
|
||||
<Container relative>
|
||||
<h1>{title}</h1>
|
||||
{subtitle ? <h2>{subtitle}</h2> : ''}
|
||||
</Container>
|
||||
|
|
|
|||
14
src/netlifyIdentity.js
Normal file
14
src/netlifyIdentity.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import netlifyIdentity from 'netlify-identity-widget'
|
||||
|
||||
// check for netlifyIdentity, redirect to admin if user is logging in
|
||||
netlifyIdentity.on('init', user => {
|
||||
if (!user) {
|
||||
netlifyIdentity.on('login', () => {
|
||||
document.location.href = '/admin/'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (window.localStorage) {
|
||||
netlifyIdentity.init()
|
||||
}
|
||||
43
src/util/getImageUrl.js
Normal file
43
src/util/getImageUrl.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
const sizes = [300, 600, 1200, 1800]
|
||||
const outputDir = '/images/uploads/'
|
||||
const resizedDir = '/images/uploads/resized/'
|
||||
|
||||
const getImageSrcset = path => {
|
||||
if (path.indexOf('http') >= 0) {
|
||||
return console.warn('Cannot get srcset for external image: ' + path)
|
||||
}
|
||||
const filename = path.split('.').shift()
|
||||
const extname = path.split('.').pop()
|
||||
const pathname = encodeURI(filename.replace(outputDir, resizedDir))
|
||||
|
||||
const srcset = sizes
|
||||
.map(size => `${pathname}.${size}.${extname} ${size}w`)
|
||||
.join(', ')
|
||||
return srcset
|
||||
}
|
||||
|
||||
const getImageSrc = (path, sizeRequested) => {
|
||||
if (path.indexOf('http') >= 0) return path
|
||||
sizeRequested = parseInt(sizeRequested, 10)
|
||||
let size
|
||||
if (sizeRequested) {
|
||||
// rounds up to nearest size or returns largest
|
||||
size =
|
||||
sizes.filter(num => num >= sizeRequested)[0] || sizes[sizes.length - 1]
|
||||
} else {
|
||||
// get the middle size
|
||||
size = sizes[Math.ceil(sizes.length / 2)]
|
||||
}
|
||||
|
||||
const extname = path.split('.').pop()
|
||||
const filename = path.split('.').shift()
|
||||
const pathname = encodeURI(filename.replace(outputDir, resizedDir))
|
||||
return `${pathname}.${size}.${extname}`
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getImageSrcset,
|
||||
getImageSrc,
|
||||
sizes,
|
||||
outputDir
|
||||
}
|
||||
|
|
@ -1,26 +1,30 @@
|
|||
import React from 'react'
|
||||
import Helmet from 'react-helmet'
|
||||
|
||||
import Page from '../components/Page'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import { Container, Section } from '../components/common'
|
||||
import Content from '../components/Content.js'
|
||||
|
||||
export default ({ title }) => (
|
||||
export default ({ page }) => (
|
||||
<Page>
|
||||
<PageHeader title={title} subtitle='<About />' />
|
||||
<Helmet>
|
||||
<title>{page.title}</title>
|
||||
</Helmet>
|
||||
<PageHeader
|
||||
title={page.title}
|
||||
subtitle={page.subtitle}
|
||||
backgroundImage={page.featuredImage}
|
||||
/>
|
||||
<Section thin>
|
||||
<Container>
|
||||
<h1>Hello World!</h1>
|
||||
<p>A sem vel nec sodales mi vivamus senectus sed potenti a parturient nascetur tincidunt nisi pulvinar rhoncus a. Risus imperdiet taciti suspendisse facilisi a per metus cubilia varius a nostra adipiscing amet ultrices quisque ac mi a. Dictumst a ultrices mi a dignissim ad fermentum eget a nam et a blandit scelerisque. Taciti lorem tempor quam vestibulum dis habitasse vestibulum diam vel est ut proin dis auctor. Suscipit scelerisque orci magna interdum vel bibendum duis netus a consectetur dui magnis ac aliquet sem posuere tincidunt vestibulum.</p>
|
||||
<Content source={page.section1} />
|
||||
</Container>
|
||||
</Section>
|
||||
<Section thin>
|
||||
<Container taCenter skinny>
|
||||
<h1>This is a skinny center-aligned {'<Container />'}</h1>
|
||||
<p>A sem vel nec sodales mi vivamus senectus sed potenti a parturient nascetur tincidunt nisi pulvinar rhoncus a. Risus imperdiet taciti suspendisse facilisi a per metus cubilia varius a nostra adipiscing amet ultrices quisque ac mi a. Dictumst a ultrices mi a dignissim ad fermentum eget a nam et a blandit scelerisque. Taciti lorem tempor quam vestibulum dis habitasse vestibulum diam vel est ut proin dis auctor. Suscipit scelerisque orci magna interdum vel bibendum duis netus a consectetur dui magnis ac aliquet sem posuere tincidunt vestibulum.</p>
|
||||
<Container>
|
||||
<Content source={page.section2} />
|
||||
</Container>
|
||||
</Section>
|
||||
<Helmet>
|
||||
<title>{title}</title>
|
||||
</Helmet>
|
||||
</Page>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,24 +7,12 @@ import NetlifySimpleForm from '../components/NetlifySimpleForm'
|
|||
import { Container, Section } from '../components/common'
|
||||
import Marked from 'react-markdown'
|
||||
|
||||
const content = `
|
||||
# Example contact form
|
||||
|
||||
This form is setup to use Netlify's form handling:
|
||||
|
||||
- the form action is set to the current absolute url: \`action: '/contact/'\`
|
||||
- a name attribute is sent with the form's data \`'form-name': 'Contact'\`
|
||||
- netlify data attributes are added to the form \`data-netlify data-netlify-honeypot\`
|
||||
|
||||
Find out more in the [Netlify Docs](https://www.netlify.com/docs/form-handling/).
|
||||
`
|
||||
|
||||
export default ({ title }) => (
|
||||
export default ({ page, site }) => (
|
||||
<Page>
|
||||
<PageHeader title={title} subtitle='<Contact />' />
|
||||
<PageHeader title={page.title} subtitle='<Contact />' />
|
||||
<Section thin>
|
||||
<Container>
|
||||
<Marked source={content} />
|
||||
<Marked source={page.body} />
|
||||
<br />
|
||||
<h3>{'<NetlifyControlledForm />'}</h3>
|
||||
<NetlifyControlledForm />
|
||||
|
|
@ -37,7 +25,7 @@ export default ({ title }) => (
|
|||
</Container>
|
||||
</Section>
|
||||
<Helmet>
|
||||
<title>{title}</title>
|
||||
<title>{page.title}</title>
|
||||
</Helmet>
|
||||
</Page>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,29 +2,17 @@ import React from 'react'
|
|||
import Helmet from 'react-helmet'
|
||||
import Page from '../components/Page'
|
||||
import { Container, Section } from '../components/common'
|
||||
import Content from '../components/Content'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import Marked from 'react-markdown'
|
||||
|
||||
const content = `
|
||||
# 🍉 HyperStatic
|
||||
|
||||
A not-so-static site boilerplate:
|
||||
- **Create React App** for simplicity
|
||||
- **Styled Components** for component-based css
|
||||
- **React Router** for routing (v4)
|
||||
- **React Helmet** for document titles, descriptions, meta
|
||||
- **React Snapshot** for pre-rendering to static html so it works without Javascript ⭐️
|
||||
|
||||
[](https://app.netlify.com/start/deploy?repository=https://github.com/Jinksi/hyperstatic)
|
||||
`
|
||||
|
||||
export default ({ title }) => {
|
||||
export default ({ page }) => {
|
||||
const { title, subtitle } = page
|
||||
return (
|
||||
<Page>
|
||||
<PageHeader title={title} subtitle='<Home />' />
|
||||
<PageHeader title={title} subtitle={subtitle} />
|
||||
<Section thin>
|
||||
<Container>
|
||||
<Marked source={content} />
|
||||
<Content source={page.body} />
|
||||
</Container>
|
||||
</Section>
|
||||
<Helmet>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue