@dinghystudio/gatsby-source-cockpit-generic
v1.7.3
Published
GatsbyJS source plugin for fetchting collections from a Cockpit CMS API.
Downloads
5
Maintainers
Readme
Gatsby Source Cockpit Generic
GatsbyJS source plugin for fetchting collections and singletons from a Cockpit CMS API.
Current Featureset
- Asset loading and linking
- Collection relations
- i18n
- (naive) Repeater fields
- Black- / Whitelisting of Cockpit Collections, Singletons, Media Assets
Usage
- Installation, configuration options & defaults
- Fetching contents
- Creating pages
- Generating slugs
- Multilingual setup
- Serving remote files / documents
- Black- / Whitelisting
Installation, configuration & defaults
Install via npm:
#!/bin/bash
npm install --save @dinghystudio/gatsby-source-cockpit-generic
Add @dinghystudio/gatsby-source-cockpit-generic
to gatsby-config.js
:
module.exports = {
plugins: [
{
resolve: '@dinghystudio/gatsby-source-cockpit-generic',
options: {
host: `${process.env.COCKPIT_HOST}`,
accessToken: `${process.env.COCKPIT_ACCESS_TOKEN}`,
l10n: {
default: "en",
},
},
},
],
}
Add l10n
and define a default language code that may be used to access field’s default values. Additionally the plugin will create an entry alternates
with relations to the node in all available languages.
Fetching contents
Use environment variables to configure access to the Cockpit API:
#!/bin/bash
COCKPIT_HOST="https://www.url-of-your-gatsby-site.com" COCKPIT_ACCESS_TOKEN="[access token as configured in Cockpit]" gatsby develop
Creating pages
Create pages based on Cockpit data by hooking into createPages
in gatsby-node.js
:
// gatsby-node.js
const path = require(`path`)
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
const component = path.resolve('src/templates/page.js')
const query = `
{
allCockpitGenericCollectionEntries {
edges {
node {
slug
meta {
id
}
}
}
}
}
`
return graphql(query).then(result => {
if (result.errors) {
throw result.errors
}
result.data.allCockpitGenericCollectionEntries.edges.forEach(
({ node }) => {
const { slug, meta: { id } } = node
createPage({
path: slug,
component,
context: {
id,
},
})
}
)
})
}
The page template should then query for nodes using the id from context:
// src/templates/page.js
import React from 'react'
import { graphql } from 'gatsby'
const Entry = ({ data: { entry } }) => (
<>
<h1>Entry "{entry.title}"</h1>
<pre>{JSON.stringify(entry, null, 2)}</pre>
</>
)
export default Entry
export const query = graphql`
query Entry($id: String!) {
entry: cockpitGenericCollectionEntries(
meta: { id: { eq: $id } }
) {
title
}
}
`
Generating slugs
Add custom slugs to Cockpit sourced nodes by hooking into onCreateNode
:
// gatsby-node.js
exports.onCreateNode = ({ node, actions }) => {
const { createNodeField } = actions
if (node.internal.type === 'CockpitGenericCollectionEntries') {
const value = slugify(node.title)
createNodeField({ node, name: 'slug', value })
}
}
// minimal slugify taken from https://gist.github.com/mathewbyrne/1280286
// for production probably better to use https://www.npmjs.com/package/slugify
function slugify(text) {
return text.toString().toLowerCase().trim()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/&/g, '-and-') // Replace & with 'and'
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\--+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, '') // Trim - from end of text
}
The slug field can then be used in page creation:
const query = `
{
allCockpitGenericCollectionEntries {
edges {
node {
- slug
+ fields {
+ slug
+ }
meta {
id
}
}
}
}
}
`
// …
result.data.allCockpitGenericCollectionEntries.edges.forEach(
({ node }) => {
- const { slug, meta: { id } } = node
+ const { fields: { slug }, meta: { id } } = node
createPage({
path: slug,
component,
context: {
id,
},
})
}
)
Multilingual setup
If Cockpit is configured for multiple languages, alternates can be created by updating configuration and sourcing like this:
// gatsby-config.js
module.exports = {
plugins: [
{
resolve: '@dinghystudio/gatsby-source-cockpit-generic',
options: {
host: `${process.env.COCKPIT_HOST}`,
accessToken: `${process.env.COCKPIT_ACCESS_TOKEN}`,
l10n: {
default: "en",
+ languages: ['de', 'en'],
},
},
},
],
}
// gatsby-node.js
+ const { isObject, merge } = require('lodash')
+
+ const PATH_LOCALIZATIONS = {
+ exceptions: ['/404/', '/404.html', '/dev-404-page/'],
+ }
+
+ function getLanguageConfig(config) {
+ // return the l10n configuration from gatsby-source-cockpit-generic
+ const {
+ options: { l10n },
+ } = config.plugins.find(
+ p => isObject(p) && p.resolve === '@dinghystudio/gatsby-source-cockpit-generic'
+ )
+ return l10n
+ }
// …
result.data.allCockpitGenericCollectionArtworks.edges.forEach(
({ node }) => {
- const { fields: { slug }, meta: { id } } = node
+ const { language, fields: { slug }, meta: { id } } = node
createPage({
path: slug,
component,
context: {
id,
+ language,
},
})
}
)
// …
+ exports.onCreatePage = ({ page, actions, store }) => {
+ // for every page that is not yet localized, remove existing path, create
+ // new path and page for every language specified in config
+ const { exceptions } = PATH_LOCALIZATIONS
+ if (exceptions.includes(page.path)) return
+
+ const { config } = store.getState()
+ const l10n = getLanguageConfig(config)
+
+ console.log('l10n', l10n)
+ if (l10n && l10n.languages) {
+ const { createPage, deletePage } = actions
+ const { languages, default: defaultLanguage } = l10n
+
+ deletePage(page)
+
+ let updatedPage, localePath
+ languages.forEach(language => {
+ localePath = language === defaultLanguage && page.path === '/'
+ ? page.path
+ : `/${language}${page.path}`
+
+ updatedPage = merge({}, page, {
+ path: localePath,
+ context: { language },
+ })
+
+ createPage(updatedPage)
+ })
+ }
+ }
Afterwards language
is available through page context (for further usage in react-i18next, linguiJS and other i18n libraries). Query for alternates (to be added to html head) via graphql:
// src/templates/page.js
import React from 'react'
import { graphql } from 'gatsby'
- const Entry = ({ data: { entry } }) => (
+ const Entry = ({ data: { entry }, pageContext }) => (
<>
<h1>Entry "{entry.title}"</h1>
+ <p>language: <code>{pageContext.language}</code></p>
<pre>{JSON.stringify(entry, null, 2)}</pre>
</>
)
export default Entry
export const query = graphql`
query Entry($id: String!) {
entry: cockpitGenericCollectionEntries(
meta: { id: { eq: $id } }
) {
title
+ language
+
+ alternates {
+ title
+ language
+
+ fields {
+ slug
+ }
+ }
}
}
`
Serving remote files / documents (that are not images)
To serve assets that are not of an image mime type that Gatsby image recognizes, you need to configure a separate gatsby-source-filesystem
before the @dinghystudio/gatsby-source-cockpit-generic
and create a placeholder file (e.g. ${__dirname}/gatsby-filesystem-placeholder.txt
) which apparently is needed for the publicURL
attribute to be available:
// gatsby-config.js
module.exports = {
siteMetadata: {
title: '…',
},
plugins: [
{
// We need filesystem source plugin to add publicURL function to File nodes
resolve: 'gatsby-source-filesystem',
options: {
name: 'placeholder',
path: `${__dirname}/gatsby-filesystem-placeholder.txt`,
},
},
{
resolve: '@dinghystudio/gatsby-source-cockpit-generic',
options: {
'//': '…',
},
},
],
}
Then you can query the remote file with a publicURL
field.
See Issue #4993 for details.
Black- / Whitelisting
To only fetch certain Collections, Singletons or Assets it is possible to configure blacklists, whitelists and filetypes:
{
resolve: '@dinghystudio/gatsby-source-cockpit-generic',
options: {
'//': '…',
+ contents: [
+ {
+ type: 'collection',
+ blacklist: ['news'],
+ whitelist: ['entries', 'news', 'tags'],
+ },
+ { type: 'singleton', whitelist: ['contact'], blacklist: [] },
+ {
+ type: 'asset',
+ filetypes: {
+ image: true,
+ video: true,
+ audio: true,
+ archive: true,
+ document: true,
+ code: true,
+ },
+ tags: {
+ whitelist: [],
+ blacklist: [],
+ },
+ },
+ ],
},
},
If no white- or blacklists are specified, the plugin will fetch a complete list via Cockpit’s API. Blacklists precede whitelists. Entries should be the lowercase names as given per Cockpit’s API at /api/collections/listCollections
and /api/singletons/listSingletons
.
Note blacklisting a collection, that has relations to other collections will currently break the setup.
Development Status
This plugin is in production use on at least two public sites. However there are currently no automated tests. Also the current usage is highly specific and contents are heavily controlled.
Please see the source repositories at GitHub for issues and current status. Issues and pull requests welcome!
Todos
- Add tests
- Add full example
- Enhance console output using Gatsby’s
reporter
Reasoning
Why yet another Cockpit Source Plugin?
At the time of writing, plenty other plugins were available but heavily in development, not officially working with GatsbyJS v2, did not handle i18n, repeater fields or put emphasis on things we didn’t need in a source plugin (layout fields). The features needed in our project were straight forward and writing a custom plugin seemed easy enough. Also the featureset grew with our needs.
Previous work
- Ginetta Cockpit Source Plugin
- @fika/Gatsby-Source-Cockpit as featured in the official GatsbyJS docs
- Gatsby-Source-Cockpit by @mpartipilo
Thanks
Thanks to @mpartipilo for interesting exchange on Gatsby-Source-Plugin development and oddities of the Cockpit API via the Gatsby Discord channel.