├── .prettierignore
├── src
├── assets
│ └── images
│ │ ├── about.jpg
│ │ └── duck_avatar.jpeg
├── data
│ ├── authors.yaml
│ └── content.json
├── utils
│ ├── enzyme.js
│ ├── feather.js
│ ├── media.js
│ └── typography.js
├── components
│ ├── Footer
│ │ ├── footer.test.js
│ │ ├── index.js
│ │ └── __snapshots__
│ │ │ └── footer.test.js.snap
│ ├── Layout
│ │ └── index.js
│ ├── Misc
│ │ └── index.js
│ ├── Navigation
│ │ ├── mobile.js
│ │ └── index.js
│ └── Buttons
│ │ └── index.js
├── pages
│ ├── blog
│ │ ├── post.sh
│ │ ├── example-post-1
│ │ │ └── index.md
│ │ ├── example-post-2
│ │ │ └── index.md
│ │ ├── example-post-3
│ │ │ └── index.md
│ │ └── index.js
│ ├── about.js
│ ├── terms.js
│ ├── privacy.js
│ └── index.js
├── layouts
│ └── index.js
└── templates
│ └── blog-post-template.js
├── .prettierrc
├── .babelrc
├── .eslintrc.json
├── README.md
├── LICENSE
├── .gitignore
├── gatsby-config.js
├── gatsby-node.js
└── package.json
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .cache/
3 |
--------------------------------------------------------------------------------
/src/assets/images/about.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Airtable/airtable-gatsbyjs-blog/HEAD/src/assets/images/about.jpg
--------------------------------------------------------------------------------
/src/data/authors.yaml:
--------------------------------------------------------------------------------
1 | - id: Donald Duck
2 | bio: "He is a duck"
3 | avatar: '../assets/images/duck_avatar.jpeg'
4 |
--------------------------------------------------------------------------------
/src/assets/images/duck_avatar.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Airtable/airtable-gatsbyjs-blog/HEAD/src/assets/images/duck_avatar.jpeg
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "jsxBracketSameLine": false,
6 | "parser": "babylon"
7 | }
8 |
--------------------------------------------------------------------------------
/src/data/content.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "index": {
4 | "title": "Hello World",
5 | "subtitle": "This is a simple gatsby-starter"
6 | }
7 | }
8 | ]
9 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | // This babelrc is only needed for jest to work properly
2 | {
3 | "env": {
4 | "test": {
5 | "presets": ["es2015", "react"],
6 | "plugins": "babel-plugin-emotion"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/utils/enzyme.js:
--------------------------------------------------------------------------------
1 | // configuring enzyme to use the adapter for React 16
2 | import Enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 |
5 | export default Enzyme.configure({ adapter: new Adapter() });
6 |
--------------------------------------------------------------------------------
/src/components/Footer/footer.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import toJson from 'enzyme-to-json';
4 | import Footer from './index';
5 | import enzymeconf from '../../utils/enzyme';
6 |
7 | it('renders correctly', () => {
8 | const tree = shallow();
9 | expect(toJson(tree)).toMatchSnapshot();
10 | });
11 |
--------------------------------------------------------------------------------
/src/utils/feather.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import React from 'react';
3 | import feather from 'feather-icons';
4 |
5 | export default (name, measureArray, styles) => {
6 | const featherString = feather.toSvg(name, {
7 | class: styles,
8 | width: measureArray[0],
9 | height: measureArray[1]
10 | });
11 | return
;
12 | };
13 |
--------------------------------------------------------------------------------
/src/utils/media.js:
--------------------------------------------------------------------------------
1 | import { css } from 'react-emotion';
2 |
3 | const sizes = {
4 | large: 1024,
5 | mid: 832,
6 | small: 640
7 | };
8 |
9 | // Iterate through the sizes and create a media template
10 | const media = Object.keys(sizes).reduce((acc, label) => {
11 | acc[label] = (...args) => css`
12 | @media (min-width: ${sizes[label] / 16}em) {
13 | ${css(...args)};
14 | }
15 | `;
16 |
17 | return acc;
18 | }, {});
19 |
20 | export default media;
21 |
--------------------------------------------------------------------------------
/src/components/Layout/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'react-emotion';
2 | import {
3 | space,
4 | width,
5 | // fontSize,
6 | color,
7 | textAlign,
8 | justifyContent,
9 | responsiveStyle
10 | } from 'styled-system';
11 |
12 | const wrap = responsiveStyle({
13 | prop: 'wrap',
14 | cssProperty: 'flexWrap'
15 | });
16 |
17 | export const Box = styled.div`
18 | overflow: hidden;
19 | ${space} ${width} ${color} ${textAlign};
20 | `;
21 |
22 | // ${space} ${width} ${fontSize} ${color} ${textAlign};
23 | export const Flex = styled.div`
24 | display: flex;
25 | ${justifyContent} ${wrap};
26 | `;
27 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true
5 | },
6 | "extends": [
7 | "airbnb",
8 | "prettier"
9 | ],
10 | "parserOptions": {
11 | "ecmaFeatures": {
12 | "experimentalObjectRestSpread": true,
13 | "jsx": true
14 | },
15 | "sourceType": "module"
16 | },
17 | "plugins": [
18 | "react",
19 | "prettier"
20 | ],
21 | "rules": {
22 | "prettier/prettier": "error",
23 | "import/no-extraneous-dependencies": "off",
24 | "react/jsx-filename-extension": "off",
25 | "jsx-a11y/anchor-is-valid": "off"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/pages/blog/post.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Ask for the post title
4 | echo -n "Name of the post?: "
5 | read post
6 |
7 | # Check if post title is empty, if so end the script without doing anything
8 | if [ -z "$post" ]; then
9 | echo "You need to specify a post title"
10 | exit 0
11 | else
12 | #Create a folder with the title; replace space with '-'
13 | notempty=${post// /-}
14 | lower=${notempty,,}
15 | mkdir "$lower"
16 | cd $lower
17 | touch index.md
18 | echo "---" >> index.md
19 | echo "title: $post" >> index.md
20 | echo "author: " >> index.md
21 | echo "date: $(date +'%Y-%m-%d')" >> index.md
22 | echo "---" >> index.md
23 | exit 0
24 | fi
25 |
--------------------------------------------------------------------------------
/src/pages/blog/example-post-1/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Example Post 1
3 | author: Donald Duck
4 | date: 2017-10-19
5 | ---
6 |
7 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
8 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
9 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd
10 | gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum
11 | dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
12 | invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero
13 | eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
14 | sea takimata sanctus est Lorem ipsum dolor sit amet.
15 |
--------------------------------------------------------------------------------
/src/pages/blog/example-post-2/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Example Post 2
3 | author: Donald Duck
4 | date: 2017-10-19
5 | ---
6 |
7 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
8 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
9 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd
10 | gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum
11 | dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
12 | invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero
13 | eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
14 | sea takimata sanctus est Lorem ipsum dolor sit amet.
15 |
--------------------------------------------------------------------------------
/src/pages/blog/example-post-3/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Example Post 3
3 | author: Donald Duck
4 | date: 2017-10-19
5 | ---
6 |
7 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
8 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
9 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd
10 | gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum
11 | dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
12 | invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero
13 | eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
14 | sea takimata sanctus est Lorem ipsum dolor sit amet.
15 |
--------------------------------------------------------------------------------
/src/components/Misc/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled, { css } from 'react-emotion';
3 | import GatsbyLink from 'gatsby-link';
4 |
5 | const timestamp = css`
6 | color: #767676;
7 | font-size: 0.9rem;
8 | font-family: 'Cousine', monospace;
9 | margin: 0 0 4px;
10 | `;
11 |
12 | const Timestamp = styled.time`
13 | ${timestamp};
14 | `;
15 |
16 | const link = css`
17 | box-shadow: inset 0 -5px 0 rgba(0, 96, 193, 0.25);
18 | -webkit-transition: box-shadow 0.4s ease-in-out;
19 | transition: box-shadow 0.4s ease-in-out;
20 | &:hover {
21 | box-shadow: inset 0 -300px 0 rgba(0, 96, 193, 0.25);
22 | }
23 | &:focus {
24 | outline: 0;
25 | }
26 | `;
27 |
28 | /* eslint-disable react/prop-types */
29 | const Link = props => (
30 |
31 | {props.children}
32 |
33 | );
34 | /* eslint-enable */
35 |
36 | export { Timestamp, Link };
37 |
--------------------------------------------------------------------------------
/src/utils/typography.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import Typography from 'typography';
3 |
4 | const typography = new Typography({
5 | baseFontSize: '16px',
6 | baseLineHeight: 1.7,
7 | googleFonts: [
8 | {
9 | name: 'Libre Franklin',
10 | styles: ['400', '400i', '700', '700i', '900']
11 | },
12 | {
13 | name: 'Cousine',
14 | styles: ['400', '400i', '700']
15 | }
16 | ],
17 | headerFontFamily: ['Libre Franklin', 'sans-serif'],
18 | bodyFontFamily: ['Cousine', 'monospace'],
19 | headerColor: 'black',
20 | bodyColor: '#333',
21 | headerWeight: 700,
22 | bodyWeight: 400,
23 | boldWeight: 700,
24 | overrideStyles: ({adjustFontSizeTo, scale, rhythm }, options) => ({
25 | 'h1': {
26 | fontSize: '4.2rem',
27 | fontWeight: 900
28 | },
29 | 'h2': {
30 | fontSize: '2.6rem',
31 | fontWeight: 900
32 | },
33 | 'h3': {
34 | fontSize: '1.6rem',
35 | marginBottom: '0.74rem'
36 | }
37 | })
38 | });
39 |
40 | /* eslint-enable */
41 | export default typography;
42 |
--------------------------------------------------------------------------------
/src/layouts/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { injectGlobal, css } from 'react-emotion';
3 | import PropTypes from 'prop-types';
4 | import { TiHome } from 'react-icons/lib/ti';
5 | import Navigation from '../components/Navigation';
6 | import { Link } from '../components/Misc';
7 |
8 | /* eslint-disable */
9 | injectGlobal`
10 | * {
11 | box-sizing: border-box;
12 | };
13 | a {
14 | text-decoration: none;
15 | color: inherit;
16 | };
17 | a:hover {
18 | cursor: pointer;
19 | };
20 | `;
21 | /* eslint-enable */
22 |
23 | const homeStyle = css`
24 | box-shadow: none;
25 | font-size: 2rem;
26 | z-index: 1000;
27 | position: absolute;
28 | left: 72px;
29 | top: 16px;
30 | `;
31 |
32 | const Layout = ({ children }) => (
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | {children()}
41 |
42 | );
43 |
44 | Layout.propTypes = {
45 | children: PropTypes.func.isRequired
46 | };
47 |
48 | export default Layout;
49 |
--------------------------------------------------------------------------------
/src/pages/about.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types */
2 | import React from 'react';
3 | import Img from 'gatsby-image';
4 | import { css } from 'react-emotion';
5 | import { Box } from '../components/Layout';
6 |
7 | const imgStyle = css`
8 | border-radius: 5px;
9 | margin-bottom: 1.7rem;
10 | `;
11 |
12 | const About = ({ data }) => {
13 | const { imageSharp } = data;
14 | return (
15 |
16 |
21 | About
22 |
23 | This is an example showing the use of "gatsby-image".
24 |
25 |
26 | );
27 | };
28 |
29 | export const pageQuery = graphql`
30 | query AboutQuery {
31 | imageSharp(id: { regex: "/about/" }) {
32 | sizes(maxWidth: 1000) {
33 | ...GatsbyImageSharpSizes
34 | }
35 | }
36 | }
37 | `;
38 |
39 | export default About;
40 |
--------------------------------------------------------------------------------
/src/components/Navigation/mobile.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import PropTypes from 'prop-types';
4 |
5 | class MobileNav extends Component {
6 | constructor(props) {
7 | super(props);
8 | this.mobileContainer = document.createElement('div');
9 | document.body.appendChild(this.mobileContainer);
10 | }
11 |
12 | componentWillUnmount() {
13 | document.body.removeChild(this.mobileContainer);
14 | }
15 |
16 | render() {
17 | return ReactDOM.createPortal(
18 |
19 |
25 | X
26 |
27 | {this.props.children}
28 | ,
29 | this.mobileContainer
30 | );
31 | }
32 | }
33 |
34 | MobileNav.propTypes = {
35 | mobileStyle: PropTypes.string.isRequired,
36 | toggleNav: PropTypes.func.isRequired,
37 | children: PropTypes.element.isRequired
38 | };
39 |
40 | export default MobileNav;
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # airtable-gatsbyjs-blog
2 |
3 | A basic [GatsbyJS](https://www.gatsbyjs.org) blog that uses [Airtable](https://airtable.com) as a CMS.
4 |
5 | For more detailed instructions on how to set this up, be sure to read our [how-to blog post](https://blog.airtable.com/build-your-own-custom-blog-cms-with-airtable-and-gatsbyjs).
6 |
7 | This example uses the [Hampton theme](https://github.com/davad/gatsby-hampton-theme).
8 |
9 | ## Setup
10 |
11 | Start by installing the project's dependencies and the Gatsby CLI:
12 | ```
13 | $ yarn
14 | $ yarn global add gatsby-cli
15 | ```
16 |
17 | Next, add your Airtable API key and base ID to `gatsby-config.js`. You can find this information via our [interactive API documentation](https://airtable.com/api).
18 |
19 | If you're following along with the blog post, you don't need to change anything else. Otherwise, you may have to change the table/view name, GraphQL queries, and template variables to match your own base schema.
20 |
21 | To start your development server:
22 | ```
23 | $ gatsby develop
24 | ```
25 |
26 | Your blog should now be visible at [localhost:8000](https://localhost:8000)!
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 Airtable https://airtable.com
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/pages/terms.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types */
2 | import React from 'react';
3 | import { Box } from '../components/Layout';
4 |
5 | const Terms = () => (
6 |
7 |
12 | Terms of Service
13 |
14 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
15 | eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
16 | voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
17 | clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit
18 | amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
19 | nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
20 | sed diam voluptua. At vero eos et accusam et justo duo dolores et ea
21 | rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem
22 | ipsum dolor sit amet.
23 |
24 |
25 |
26 | );
27 |
28 | export default Terms;
29 |
--------------------------------------------------------------------------------
/src/components/Buttons/index.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'react-emotion';
2 |
3 | const colors = {
4 | primary: 'black',
5 | secondary: 'rgba(0, 96, 193, 0.25)'
6 | };
7 |
8 | const buttonBasic = css`
9 | width: 100%;
10 | padding: 0.5rem 1.25rem;
11 | border-radius: 5px;
12 | border: 2px solid ${colors.secondary};
13 | `;
14 |
15 | const buttonPrimary = css`
16 | ${buttonBasic};
17 | background-color: ${colors.secondary};
18 | color: ${colors.primary};
19 | transition: all 0.3s ease;
20 |
21 | &:hover {
22 | background-color: transparent;
23 | color: ${colors.secondary};
24 | cursor: pointer;
25 | }
26 | `;
27 |
28 | const buttonSecondary = css`
29 | ${buttonBasic};
30 | background-color: transparent;
31 | color: ${colors.secondary};
32 | transition: all 0.3s ease;
33 |
34 | &:hover {
35 | background-color: ${colors.secondary};
36 | color: ${colors.primary};
37 | cursor: pointer;
38 | }
39 | `;
40 |
41 | const ButtonPrimary = styled.button`
42 | ${buttonPrimary};
43 | `;
44 |
45 | export const ButtonSecondary = styled.button`
46 | ${buttonSecondary};
47 | `;
48 |
49 | export default ButtonPrimary;
50 |
--------------------------------------------------------------------------------
/src/pages/privacy.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types */
2 | import React from 'react';
3 | import { Box } from '../components/Layout';
4 |
5 | const Privacy = () => (
6 |
7 |
12 | Privacy Policy
13 |
14 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
15 | eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
16 | voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
17 | clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit
18 | amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
19 | nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
20 | sed diam voluptua. At vero eos et accusam et justo duo dolores et ea
21 | rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem
22 | ipsum dolor sit amet.
23 |
24 |
25 |
26 | );
27 |
28 | export default Privacy;
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # gatsby specific folders
61 | public/
62 | .cache/
63 |
64 |
--------------------------------------------------------------------------------
/gatsby-config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | pathPrefix: '/gatsby-hampton-theme',
3 | siteMetadata: {
4 | title: 'My Gatsby Site'
5 | },
6 | mapping: {
7 | 'MarkdownRemark.frontmatter.author': 'AuthorsYaml'
8 | },
9 | plugins: [
10 | // Adding various source folders to the GraphQL layer.
11 | {
12 | resolve: 'gatsby-source-airtable',
13 | options: {
14 | apiKey: 'YOUR_API_KEY',
15 | baseId: 'YOUR_BASE_ID',
16 | tableName: 'CMS',
17 | tableView: 'published',
18 | queryName: ''
19 | }
20 | },
21 | {
22 | resolve: `gatsby-source-filesystem`,
23 | options: {
24 | name: `pages`,
25 | path: `${__dirname}/src/pages/`
26 | }
27 | },
28 | {
29 | resolve: `gatsby-source-filesystem`,
30 | options: {
31 | name: `data`,
32 | path: `${__dirname}/src/data/`
33 | }
34 | },
35 | {
36 | resolve: `gatsby-source-filesystem`,
37 | options: {
38 | name: `images`,
39 | path: `${__dirname}/src/assets/images/`
40 | }
41 | },
42 | {
43 | resolve: `gatsby-plugin-google-analytics`,
44 | options: {
45 | trackingId: ``
46 | }
47 | },
48 | 'gatsby-transformer-remark',
49 | 'gatsby-transformer-json',
50 | 'gatsby-transformer-yaml',
51 | 'gatsby-plugin-sharp',
52 | 'gatsby-transformer-sharp',
53 | 'gatsby-plugin-offline',
54 | 'gatsby-plugin-emotion',
55 | 'gatsby-plugin-react-next',
56 | {
57 | resolve: 'gatsby-plugin-typography',
58 | options: {
59 | pathToConfigModule: 'src/utils/typography.js'
60 | }
61 | }
62 | ]
63 | };
64 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types */
2 | import React from 'react';
3 | import Link from 'gatsby-link';
4 | import { Box, Flex } from '../components/Layout';
5 | import ButtonPrimary, { ButtonSecondary } from '../components/Buttons';
6 |
7 | export default ({ data }) => {
8 | const myData = data.allContentJson.edges[0].node.index;
9 | return (
10 |
11 |
12 |
18 | {myData.title}
19 | {myData.subtitle}
20 |
21 |
22 |
23 | About
24 |
25 |
26 |
27 |
28 | About
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | This could be another section
37 | With another text object
38 |
39 |
40 |
41 | );
42 | };
43 |
44 | export const pageQuery = graphql`
45 | query contentQuery {
46 | allContentJson {
47 | edges {
48 | node {
49 | index {
50 | title
51 | subtitle
52 | }
53 | }
54 | }
55 | }
56 | }
57 | `;
58 |
--------------------------------------------------------------------------------
/gatsby-node.js:
--------------------------------------------------------------------------------
1 | const parseFilepath = require('parse-filepath');
2 | const path = require('path');
3 | const slash = require('slash');
4 |
5 | exports.modifyWebpackConfig = ({ config, stage }) => {
6 | switch (stage) {
7 | case 'develop':
8 | config.preLoader('eslint-loader', {
9 | test: /\.(js|jsx)$/,
10 | exclude: /node_modules/
11 | });
12 |
13 | break;
14 | }
15 | return config;
16 | };
17 |
18 | exports.onCreateNode = ({ node, boundActionCreators, getNode }) => {
19 | const { createNodeField } = boundActionCreators;
20 | if (node.internal.type === 'MarkdownRemark') {
21 | const fileNode = getNode(node.parent);
22 | const parsedFilePath = parseFilepath(fileNode.relativePath);
23 |
24 | const slug = `/${parsedFilePath.dir}`;
25 | createNodeField({ node, name: 'slug', value: slug });
26 | }
27 | };
28 |
29 | exports.createPages = ({ graphql, boundActionCreators }) => {
30 | const { createPage } = boundActionCreators;
31 | return new Promise((resolve, reject) => {
32 | const blogPostTemplate = path.resolve(
33 | 'src/templates/blog-post-template.js'
34 | );
35 | resolve(
36 | graphql(
37 | `
38 | {
39 | allAirtable {
40 | edges {
41 | node {
42 | slug
43 | }
44 | }
45 | }
46 | }
47 | `
48 | ).then(result => {
49 | if (result.error) {
50 | reject(result.error);
51 | }
52 |
53 | result.data.allAirtable.edges.forEach(edge => {
54 | createPage({
55 | path: `${edge.node.slug}`,
56 | component: slash(blogPostTemplate),
57 | context: {
58 | slug: edge.node.slug
59 | }
60 | });
61 | });
62 | })
63 | );
64 | });
65 | };
66 |
--------------------------------------------------------------------------------
/src/components/Navigation/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { css } from 'react-emotion';
3 | import { slide as Menu } from 'react-burger-menu';
4 | import { Link } from '../Misc';
5 |
6 | const linkStyles = css`
7 | box-shadow: none;
8 | `;
9 |
10 | const menuStyles = {
11 | bmBurgerButton: {
12 | position: 'fixed',
13 | width: '25px',
14 | height: '18px',
15 | left: '36px',
16 | top: '36px'
17 | },
18 | bmBurgerBars: {
19 | background: '#333',
20 | height: 1.5,
21 | opacity: 1
22 | },
23 | bmCrossButton: {
24 | height: '24px',
25 | width: '24px'
26 | },
27 | bmCross: {
28 | background: '#333'
29 | },
30 | bmMenu: {
31 | background: 'white',
32 | padding: '2.5em 1.5em 0',
33 | fontSize: '1.25rem',
34 | lineHeight: '2.125rem'
35 | },
36 | bmMorphShape: {
37 | fill: '#373a47'
38 | },
39 | bmItemList: {
40 | color: 'black',
41 | padding: '0.8em'
42 | },
43 | bmOverlay: {
44 | background: 'rgba(0, 0, 0, 0.3)'
45 | }
46 | };
47 |
48 | class Navigation extends Component {
49 | constructor(props) {
50 | super(props);
51 | this.state = {
52 | mobileActive: false
53 | };
54 | this.toggleNav = this.toggleNav.bind(this);
55 | }
56 |
57 | toggleNav() {
58 | if (this.state.mobileActive) {
59 | this.setState({ mobileActive: false });
60 | } else {
61 | this.setState({ mobileActive: true });
62 | }
63 | }
64 |
65 | render() {
66 | return (
67 |
68 |
69 | Home
70 |
71 |
72 | About
73 |
74 |
75 | Blog
76 |
77 |
78 | );
79 | }
80 | }
81 |
82 | export default Navigation;
83 |
--------------------------------------------------------------------------------
/src/templates/blog-post-template.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types, react/no-danger */
2 | import React from 'react';
3 | import unified from 'unified';
4 | import markdown from 'remark-parse';
5 | import html from 'remark-html';
6 | import styled from 'react-emotion';
7 | import { Box } from '../components/Layout';
8 | import { Timestamp, Link } from '../components/Misc';
9 |
10 | const Back = styled.div`
11 | color: #666;
12 | float: right;
13 | position: relative;
14 | bottom: 1.5rem;
15 | `;
16 |
17 | const Template = ({ data }) => {
18 | const { airtable: post } = data;
19 | return (
20 |
21 |
27 |
28 | ← Blog
29 |
30 | {post.title}
31 | {post.date}
32 | Written by {post.author}
33 |
44 |
52 |
53 |
54 | );
55 | };
56 |
57 | export const pageQuery = graphql`
58 | query BlogPostByPath($slug: String!) {
59 | airtable(slug: { eq: $slug }) {
60 | slug
61 | title
62 | author
63 | PostMarkdown
64 | image {
65 | url
66 | }
67 | date
68 | }
69 | }
70 | `;
71 |
72 | export default Template;
73 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-hampton-theme",
3 | "author": "David Landry ",
4 | "version": "0.0.1",
5 | "main": "index.js",
6 | "license": "MIT",
7 | "dependencies": {
8 | "emotion": "^8.0.8",
9 | "emotion-server": "^8.0.8",
10 | "feather-icons": "^3.3.0",
11 | "gatsby": "^1.9.76",
12 | "gatsby-image": "^1.0.13",
13 | "gatsby-link": "^1.6.21",
14 | "gatsby-plugin-emotion": "^1.1.8",
15 | "gatsby-plugin-google-analytics": "^1.0.10",
16 | "gatsby-plugin-offline": "^1.0.10",
17 | "gatsby-plugin-react-next": "^1.0.4",
18 | "gatsby-plugin-sharp": "^1.6.9",
19 | "gatsby-plugin-typography": "^1.7.19",
20 | "gatsby-source-airtable": "^0.0.6",
21 | "gatsby-source-filesystem": "^1.5.5",
22 | "gatsby-transformer-json": "^1.0.11",
23 | "gatsby-transformer-remark": "^1.7.17",
24 | "gatsby-transformer-sharp": "^1.6.10",
25 | "gatsby-transformer-yaml": "^1.5.11",
26 | "gh-pages": "^1.1.0",
27 | "normalize.css": "^7.0.0",
28 | "prop-types": "^15.6.0",
29 | "react-burger-menu": "^2.2.3",
30 | "react-emotion": "^8.0.8",
31 | "react-icons": "^2.2.7",
32 | "remark-html": "^7.0.0",
33 | "styled-system": "^1.0.8",
34 | "unified": "^7.0.0"
35 | },
36 | "scripts": {
37 | "dev": "node_modules/.bin/gatsby develop",
38 | "build": "node_modules/.bin/gatsby build",
39 | "deploy": "gatsby build --prefix-paths && gh-pages -d public",
40 | "test": "jest",
41 | "test:watch": "jest --watch",
42 | "prettify": "prettier --write '{./,__{tests,mocks}__}/**/*.+(js|jsx)'"
43 | },
44 | "jest": {
45 | "transform": {
46 | ".(js|jsx)": "babel-jest"
47 | },
48 | "testRegex": "(\\.(test|spec))\\.(jsx|js)$",
49 | "testPathIgnorePatterns": [
50 | "/node_modules/",
51 | "/.cache/"
52 | ],
53 | "moduleFileExtensions": [
54 | "jsx",
55 | "js"
56 | ],
57 | "collectCoverage": true,
58 | "coverageReporters": [
59 | "lcov",
60 | "text",
61 | "html"
62 | ]
63 | },
64 | "devDependencies": {
65 | "enzyme": "^3.1.0",
66 | "enzyme-adapter-react-16": "^1.0.2",
67 | "enzyme-to-json": "^3.1.4",
68 | "eslint": "^4.9.0",
69 | "eslint-config-airbnb": "^16.1.0",
70 | "eslint-config-prettier": "^2.6.0",
71 | "eslint-loader": "^1.9.0",
72 | "eslint-plugin-import": "^2.7.0",
73 | "eslint-plugin-jsx-a11y": "^6.0.2",
74 | "eslint-plugin-prettier": "^2.3.1",
75 | "eslint-plugin-react": "^7.4.0",
76 | "jest": "^21.2.1",
77 | "prettier": "1.7.4"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/pages/blog/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import React from 'react';
3 | import unified from 'unified';
4 | import markdown from 'remark-parse';
5 | import html from 'remark-html';
6 | import { Link, Timestamp } from '../../components/Misc';
7 | import { Box } from '../../components/Layout';
8 | import { css } from 'react-emotion';
9 |
10 | const linkStyles = css`
11 | box-shadow: none;
12 | `;
13 |
14 | const BlogIndex = ({ data }) => {
15 | const { edges: posts } = data.allAirtable;
16 | return (
17 |
18 |
23 | Blog
24 |
25 | {posts
26 | .filter(post => post.node.title.length > 0)
27 | .map(({ node: post }, index) => {
28 | return (
29 |
30 |
31 | {post.date}
32 | {post.title}
33 |
41 |
42 |
47 |
54 |
55 |
56 |
57 | );
58 | })}
59 |
60 |
61 |
62 | );
63 | };
64 |
65 | export const pageQuery = graphql`
66 | query BlogQuery {
67 | allAirtable(sort: { fields: [date], order: DESC }) {
68 | edges {
69 | node {
70 | slug
71 | title
72 | PostMarkdown
73 | image {
74 | url
75 | }
76 | date
77 | }
78 | }
79 | }
80 | }
81 | `;
82 | /* eslint-enable */
83 |
84 | export default BlogIndex;
85 |
--------------------------------------------------------------------------------
/src/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled, { css } from 'react-emotion';
3 | import Link from 'gatsby-link';
4 | import PropTypes from 'prop-types';
5 | import { Box, Flex } from '../Layout';
6 | import feather from '../../utils/feather';
7 | import media from '../../utils/media';
8 |
9 | const footerStyle = css`
10 | overflow: hidden;
11 | text-align: center;
12 |
13 | & img {
14 | display: block;
15 | margin: 0;
16 | }
17 | & p {
18 | color: rgba(255, 255, 255, 0.5);
19 | }
20 | `;
21 |
22 | const ulStyle = css`
23 | list-style-type: none;
24 | margin: 0;
25 | margin-bottom: 1.45rem;
26 | text-transform: uppercase;
27 | padding: 0;
28 | & a {
29 | cursor: pointer;
30 | opacity: 0.5;
31 | transition: opacity 0.15s ease-in;
32 | transition: color 0.15s ease-in;
33 |
34 | &:hover {
35 | text-decoration: none;
36 | box-shadow: none;
37 | opacity: 1;
38 | transition: opacity 0.15s ease-in;
39 | }
40 | }
41 | & li {
42 | margin: 0;
43 | }
44 | `;
45 |
46 | const Wrapper = styled.div`
47 | width: 100%;
48 | margin: 0;
49 | & p {
50 | margin: 0;
51 | margin-bottom: 1.45rem;
52 | }
53 | `;
54 |
55 | const socialList = css`
56 | display: flex;
57 | justify-content: space-between;
58 | list-style-type: none;
59 | margin: 0;
60 | padding: 0;
61 |
62 | ${media.large`
63 | width: 50%;
64 | margin-left: auto;
65 | `};
66 | `;
67 |
68 | const svgStyles = css`
69 | opacity: 0.5;
70 | transition: opacity 0.15s ease-in;
71 | transition: color 0.15s ease-in;
72 |
73 | &:hover {
74 | text-decoration: none;
75 | box-shadow: none;
76 | opacity: 1;
77 | transition: opacity 0.15s ease-in;
78 | }
79 | `;
80 |
81 | const Footer = props => (
82 |
83 |
84 |
85 |
86 |
87 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | Home
97 |
98 |
99 | Privacy Policy
100 |
101 |
102 | Terms of Service
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
117 | {feather('facebook', ['30', '30'], svgStyles)}
118 |
119 |
120 |
121 |
127 | {feather('twitter', ['30', '30'], svgStyles)}
128 |
129 |
130 |
131 |
137 | {feather('instagram', ['30', '30'], svgStyles)}
138 |
139 |
140 |
141 |
142 | {feather('mail', ['30', '30'], svgStyles)}
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 | {`Copyright © 2017 ${props.name}. All rights reserved.`}
152 |
153 |
154 | );
155 |
156 | Footer.propTypes = {
157 | name: PropTypes.string,
158 | facebook: PropTypes.string,
159 | twitter: PropTypes.string,
160 | instagram: PropTypes.string,
161 | email: PropTypes.string
162 | };
163 |
164 | Footer.defaultProps = {
165 | name: 'Gatbsythemes.com starter',
166 | facebook: 'https://facebook.com',
167 | twitter: 'https://twitter.com',
168 | instagram: 'https://instagram',
169 | email: ''
170 | };
171 |
172 | export default Footer;
173 |
--------------------------------------------------------------------------------
/src/components/Footer/__snapshots__/footer.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`renders correctly 1`] = `
4 |
7 |
18 |
28 |
37 |
38 |
39 |
40 | Gatbsythemes.com starter
41 |
42 |
43 |
44 |
45 |
54 |
55 |
58 |
59 |
62 | Home
63 |
64 |
65 |
66 |
69 | Privacy Policy
70 |
71 |
72 |
73 |
76 | Terms of Service
77 |
78 |
79 |
80 |
81 |
82 |
91 |
92 |