diff --git a/gatsby-config.js b/gatsby-config.js
index 59cd2f0..2b15c1e 100644
--- a/gatsby-config.js
+++ b/gatsby-config.js
@@ -63,7 +63,7 @@ module.exports = {
{
resolve: 'gatsby-remark-images',
options: {
- maxWidth: 960,
+ maxWidth: 1280,
},
},
'gatsby-remark-autolink-headers', // must precede prismjs!
diff --git a/gatsby-node.js b/gatsby-node.js
index e1485e2..60379bf 100644
--- a/gatsby-node.js
+++ b/gatsby-node.js
@@ -27,7 +27,8 @@ exports.onCreateNode = ({ node, getNode, actions }) => {
getNode,
basePath,
}).replace(directory, '');
- const pathname = `/${baseURI}/${relativeFilePath}`.replace(multiSlashRE, '/');
+ const slug = node.frontmatter && node.frontmatter.slug;
+ const pathname = `/${baseURI}/${slug || relativeFilePath}`.replace(multiSlashRE, '/');
// Add a field to each markdown node to indicate its source instance. This
// is used by the gatsby-remark-prefix-relative-links plugin.
@@ -75,10 +76,28 @@ exports.createPages = async ({ graphql, actions }) => {
if (pathname === '/' && sourceInstanceName !== 'content') return;
const component = path.resolve(template);
createPage({
- path: node.fields.pathname,
+ path: pathname,
component,
// Context is available in page queries as GraphQL variables.
context: { pathname, sourceInstanceName, template },
});
});
};
+
+exports.createSchemaCustomization = ({ actions }) => {
+ const { createTypes } = actions
+ const typeDefs = `
+ type MarkdownRemark implements Node {
+ frontmatter: Frontmatter
+ }
+ type Frontmatter {
+ author: String
+ canonical: String
+ # Date is the only required (non-nullable) field.
+ date: Date! @dateformat
+ slug: String
+ title: String
+ }
+ `
+ createTypes(typeDefs)
+}
diff --git a/lib/navigation.js b/lib/navigation.js
index 384b23f..a51f253 100644
--- a/lib/navigation.js
+++ b/lib/navigation.js
@@ -146,11 +146,11 @@ const insertChildNode = (nav, child, indices) => {
};
const createSourceNav = (source) => {
- const { title, config, baseURI } = source;
+ const { title, config, baseURI, page = null } = source;
let nav = {
title,
key: baseURI,
- page: null,
+ page,
children: [],
};
const configYaml = loadConfigYaml(config);
diff --git a/lib/sources.js b/lib/sources.js
index e930b22..e6accba 100644
--- a/lib/sources.js
+++ b/lib/sources.js
@@ -1,4 +1,5 @@
const path = require('path');
+const { assoc, compose, evolve, has, ifElse } = require('ramda');
const validateSource = (source = {}) => {
const { name, config, baseURI, remote, local } = source;
@@ -20,20 +21,16 @@ const validateSource = (source = {}) => {
return isValid;
};
-const prepareSource = git => source => {
- if (!source.remote) {
- return {
- ...source,
- basePath: source.local,
- };
- }
- const { config, directory = '', name } = source;
- return {
- ...source,
- basePath: path.resolve(git, name, directory),
- config: path.resolve(git, name, config),
- };
-};
+const prepareSource = git => (s) => ifElse(
+ has('remote'),
+ compose(
+ evolve({
+ config: c => path.resolve(git, s.name, c),
+ }),
+ assoc('basePath', path.resolve(git, s.name, s.directory || '')),
+ ),
+ assoc('basePath', s.local),
+)(s);
const prepareSources = (sources, git) => {
return sources.filter(validateSource).map(prepareSource(git));
diff --git a/package-lock.json b/package-lock.json
index e5cc5ef..eca5f42 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,10 +16,10 @@
"gatsby-plugin-image": "^1.14.0",
"gatsby-plugin-manifest": "^3.14.0",
"gatsby-plugin-react-helmet": "^4.14.0",
- "gatsby-plugin-sharp": "^3.14.0",
+ "gatsby-plugin-sharp": "^3.14.3",
"gatsby-plugin-sitemap": "^4.10.0",
"gatsby-remark-autolink-headers": "^4.11.0",
- "gatsby-remark-images": "^4.0.0",
+ "gatsby-remark-images": "^4.2.0",
"gatsby-remark-prismjs": "^5.11.0",
"gatsby-source-filesystem": "^3.14.0",
"gatsby-source-git": "^1.1.0",
diff --git a/package.json b/package.json
index 9c66a60..8c6693b 100644
--- a/package.json
+++ b/package.json
@@ -24,10 +24,10 @@
"gatsby-plugin-image": "^1.14.0",
"gatsby-plugin-manifest": "^3.14.0",
"gatsby-plugin-react-helmet": "^4.14.0",
- "gatsby-plugin-sharp": "^3.14.0",
+ "gatsby-plugin-sharp": "^3.14.3",
"gatsby-plugin-sitemap": "^4.10.0",
"gatsby-remark-autolink-headers": "^4.11.0",
- "gatsby-remark-images": "^4.0.0",
+ "gatsby-remark-images": "^4.2.0",
"gatsby-remark-prismjs": "^5.11.0",
"gatsby-source-filesystem": "^3.14.0",
"gatsby-source-git": "^1.1.0",
diff --git a/site-data.js b/site-data.js
index 273eb57..85f8729 100644
--- a/site-data.js
+++ b/site-data.js
@@ -6,6 +6,7 @@
* designated `directory`. The default is `DOCS_TEMPLATE`.
*/
const DOCS_TEMPLATE = './src/templates/docs.js';
+const BLOG_TEMPLATE = './src/templates/blog.js';
module.exports = {
siteMetadata: {
@@ -57,5 +58,18 @@ module.exports = {
branch: '1.x',
directory: 'docs/',
},
+ {
+ name: 'blog',
+ title: 'Blog',
+ baseURI: '/blog',
+ page: {
+ title: 'Blog',
+ pathname: '/blog',
+ },
+ remote: 'https://github.com/farmOS/farmOS-community-blog.git',
+ branch: 'main',
+ directory: 'posts/',
+ template: BLOG_TEMPLATE,
+ },
],
};
diff --git a/src/components/seo.js b/src/components/seo.js
index 4c54313..e9158f3 100644
--- a/src/components/seo.js
+++ b/src/components/seo.js
@@ -4,7 +4,10 @@ import { Helmet } from "react-helmet"
import { useLocation } from "@reach/router"
import { useStaticQuery, graphql } from "gatsby"
-const SEO = ({ title, description, image, article }) => {
+const SEO = (props) => {
+ const {
+ article, canonical, description, image, title,
+ } = props;
const { pathname, origin } = useLocation()
const { site } = useStaticQuery(query)
@@ -56,6 +59,8 @@ const SEO = ({ title, description, image, article }) => {
)}
{seo.image && }
+
+ {canonical && }
)
}
@@ -67,6 +72,7 @@ SEO.propTypes = {
description: PropTypes.string,
image: PropTypes.string,
article: PropTypes.bool,
+ canonical: PropTypes.string,
}
SEO.defaultProps = {
@@ -74,6 +80,7 @@ SEO.defaultProps = {
description: null,
image: null,
article: false,
+ canonical: null,
}
const query = graphql`
diff --git a/src/pages/blog.js b/src/pages/blog.js
new file mode 100644
index 0000000..aa8df23
--- /dev/null
+++ b/src/pages/blog.js
@@ -0,0 +1,108 @@
+import * as React from 'react';
+import { Link } from 'gatsby-material-ui-components';
+import { makeStyles } from '@material-ui/core/styles';
+import { Box, Typography } from '@material-ui/core';
+import Seo from '../components/seo';
+import theme from '../theme';
+import { graphql } from 'gatsby';
+
+const DEFAULT_AUTHOR = 'the farmOS Community';
+const DEFAULT_TITLE = 'farmOS Community Blog';
+
+const useStyles = makeStyles({
+ main: {
+ '& h1': {
+ fontWeight: 300,
+ fontSize: '3rem',
+ lineHeight: 1.3,
+ letterSpacing: '-.01em',
+ margin: '0 0 1.25rem',
+ },
+ },
+ post: {
+ color: 'inherit',
+ textDecoration: 'none',
+ '& div': { paddingBottom: '2rem' },
+ '& h3': {
+ color: theme.palette.primary.light,
+ fontSize: '2rem',
+ lineHeight: 1.3,
+ },
+ '& h5': {
+ fontSize: '1rem',
+ lineHeight: 1.3,
+ '& span': {
+ fontWeight: 700,
+ },
+ },
+ '& p': {
+ color: theme.palette.text.hint,
+ },
+ '&:hover': {
+ textDecoration: 'none',
+ '& h3': { color: theme.palette.warning.main },
+ },
+ },
+});
+
+const BlogIndex = ({ data: { allMarkdownRemark } }) => {
+ const classes = useStyles();
+ const posts = allMarkdownRemark.edges.map(({ node }, i) => {
+ const {
+ excerpt, fields: { pathname }, frontmatter, headings,
+ } = node;
+ const { author, date, title } = frontmatter;
+ const h1 = headings.find(({ depth }) => depth === 1);
+ return (
+
+
+
+ {title || h1?.value || DEFAULT_TITLE}
+
+
+ {date} by {author || DEFAULT_AUTHOR}
+
+ {excerpt}
+
+
+ );
+ });
+ return (
+ <>
+
+
+
+ Community Blog
+
+ {posts}
+
+ >
+ );
+};
+
+export const query = graphql`query BlogIndex {
+ allMarkdownRemark(
+ filter: { fields: { template: { eq: "./src/templates/blog.js" } } }
+ sort: {fields: frontmatter___date, order: DESC}
+ ) {
+ edges {
+ node {
+ frontmatter {
+ author
+ date(formatString: "MMMM DD, YYYY")
+ title
+ }
+ excerpt
+ fields {
+ pathname
+ }
+ headings {
+ value
+ depth
+ }
+ }
+ }
+ }
+}`;
+
+export default BlogIndex
diff --git a/src/templates/blog.js b/src/templates/blog.js
new file mode 100644
index 0000000..ede0d15
--- /dev/null
+++ b/src/templates/blog.js
@@ -0,0 +1,90 @@
+import React, { useEffect, useState } from 'react';
+import { graphql } from 'gatsby';
+import { makeStyles } from '@material-ui/core/styles';
+import { Box, Typography } from '@material-ui/core';
+import 'prismjs/themes/prism.css';
+import theme from '../theme';
+import Markdown from '../components/markdown';
+import Seo from '../components/seo';
+
+const DEFAULT_AUTHOR = 'the farmOS Community';
+const DEFAULT_TITLE = 'farmOS Community Blog';
+
+const useStyles = makeStyles({
+ heading: {
+ margin: '0 0 1.25rem',
+ },
+ headline: {
+ fontWeight: 300,
+ fontSize: '3rem',
+ lineHeight: 1.3,
+ },
+ byline: {
+ fontSize: '1rem',
+ lineHeight: 1.3,
+ color: theme.palette.text.secondary,
+ },
+ dateline: {
+ fontWeight: 700,
+ },
+});
+
+export default function BlogTemplate({ data }) {
+ const classes = useStyles();
+ const { markdownRemark: { frontmatter = {}, headings, html: initHtml } } = data;
+ const { canonical, date } = frontmatter;
+ let { author, title } = frontmatter;
+ if (!author) author = DEFAULT_AUTHOR;
+ const h1 = headings.find(({ depth }) => depth === 1);
+ if (!title && h1) title = h1.value;
+ if (!title && !h1) title = DEFAULT_TITLE;
+ const [html, setHtml] = useState(initHtml);
+ useEffect(() => {
+ if (h1) {
+ const div = document.createElement('div');
+ div.innerHTML = html;
+ const el = div.querySelector(`#${h1.id}`);
+ if (el) {
+ el.remove();
+ setHtml(div.outerHTML);
+ }
+ }
+ }, [h1, html]);
+
+ return (
+ <>
+
+
+
+
+ {title}
+
+
+ {date} by {author}
+
+
+
+
+ >
+ );
+};
+
+export const query = graphql`
+ query($pathname: String!) {
+ markdownRemark(fields: { pathname: { eq: $pathname } }) {
+ frontmatter {
+ author
+ canonical
+ date(formatString: "MMMM DD, YYYY")
+ slug
+ title
+ }
+ html
+ headings {
+ id
+ value
+ depth
+ }
+ }
+ }
+`;