Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ you can place the files in a `src` subfolder and build them to the plugin folder
* [gatsby-remark-responsive-image](/docs/packages/gatsby-remark-responsive-image/)
* [gatsby-remark-smartypants](/docs/packages/gatsby-remark-smartypants/)
* [gatsby-sharp](/docs/packages/gatsby-sharp/)
* [gatsby-source-contentful](/docs/packages/gatsby-source-contentful/)
* [gatsby-source-drupal](/docs/packages/gatsby-source-drupal/)
* [gatsby-source-filesystem](/docs/packages/gatsby-source-filesystem/)
* [gatsby-source-hacker-news](/docs/packages/gatsby-source-hacker-news/)
Expand Down
2 changes: 2 additions & 0 deletions packages/gatsby-plugin-offline/src/app-shell.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from "react"

class AppShell extends React.Component {
componentDidMount() {
// TODO check if page exists and if not,
// force a hard reload.
window.___navigateTo(this.props.location.pathname)
}

Expand Down
1 change: 1 addition & 0 deletions packages/gatsby-source-contentful/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"axios": "^0.16.1",
"bluebird": "^3.4.6",
"contentful": "^4.3.0",
"json-stringify-safe": "^5.0.1",
"lodash": "^4.17.2"
}
}
98 changes: 74 additions & 24 deletions packages/gatsby-source-contentful/src/gatsby-node.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const contentful = require(`contentful`)
const crypto = require(`crypto`)
const stringify = require("json-stringify-safe")

const digest = str => crypto.createHash(`md5`).update(str).digest(`hex`)

const typePrefix = `contentful__`
const conflictFieldPrefix = `contentful`
Expand Down Expand Up @@ -58,6 +61,19 @@ exports.sourceNodes = async (
console.log(`assets fetched`, assets.items.length)
console.timeEnd(`fetch Contentful data`)

// Create map of not resolvable ids so we can filter them out while creating
// links.
const notResolvable = new Map()
entryList.forEach(ents => {
if (ents.errors) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blanking right now.. why would there be non-resolvable ids?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only place I found it documented is here: http://cocoadocs.org/docsets/ContentfulDeliveryAPI/1.9.2/Classes/CDAArray.html

Basically says if a referenced entry is deleted but the reference also isn't also deleted they send this error 🤷‍♂️

The client I'm working with has a ton of content types and has been doing a lot of experimenting so this came up.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, cool. Good to use it with real existing sites. :)

ents.errors.forEach(error => {
if (error.sys.id === `notResolvable`) {
notResolvable.set(error.details.id, error.details)
}
})
}
})

const contentTypeItems = contentTypes.items

// Build foreign reference map before starting to insert any nodes
Expand All @@ -75,6 +91,11 @@ exports.sourceNodes = async (
entryItemFieldValue[0].sys.id
) {
entryItemFieldValue.forEach(v => {
// Don't create link to an unresolvable field.
if (notResolvable.has(v.sys.id)) {
return
}

if (!foreignReferenceMap[v.sys.id]) {
foreignReferenceMap[v.sys.id] = []
}
Expand All @@ -87,7 +108,8 @@ exports.sourceNodes = async (
} else if (
entryItemFieldValue.sys &&
entryItemFieldValue.sys.type &&
entryItemFieldValue.sys.id
entryItemFieldValue.sys.id &&
!notResolvable.has(entryItemFieldValue.sys.id)
) {
if (!foreignReferenceMap[entryItemFieldValue.sys.id]) {
foreignReferenceMap[entryItemFieldValue.sys.id] = []
Expand All @@ -101,6 +123,26 @@ exports.sourceNodes = async (
})
})

function createTextNode(node, text, createNode) {
const textNode = {
id: `${node.id}TextNode`,
parent: node.id,
children: [],
text,
internal: {
type: `ComponentDescription`,
mediaType: `text/x-markdown`,
content: text,
contentDigest: digest(text),
},
}

node.children = node.children.concat([textNode.id])
createNode(textNode)

return textNode.id
}

contentTypeItems.forEach((contentTypeItem, i) => {
const contentTypeItemId = contentTypeItem.sys.id

Expand Down Expand Up @@ -138,13 +180,17 @@ exports.sourceNodes = async (
) {
entryItemFields[
`${entryItemFieldKey}___NODE`
] = entryItemFieldValue.map(v => v.sys.id)
] = entryItemFieldValue
.filter(v => !notResolvable.has(v.sys.id))
.map(v => v.sys.id)

delete entryItemFields[entryItemFieldKey]
}
} else if (
entryItemFieldValue.sys &&
entryItemFieldValue.sys.type &&
entryItemFieldValue.sys.id
entryItemFieldValue.sys.id &&
!notResolvable.has(entryItemFieldValue.sys.id)
) {
entryItemFields[`${entryItemFieldKey}___NODE`] =
entryItemFieldValue.sys.id
Expand All @@ -167,30 +213,42 @@ exports.sourceNodes = async (
})
}

const entryNode = {
let entryNode = {
id: entryItem.sys.id,
parent: contentTypeItemId,
children: [],
...entryItemFields,
internal: {
type: `${makeTypeName(contentTypeItemId)}`,
content: JSON.stringify(entryItem),
mediaType: `application/json`,
},
}

// Replace text fields with text nodes so we can process their markdown
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, in a separate project I ended up doing...

<div dangerouslySetInnerHTML={{ __html: md.render(content) }} />

... with a text field using MarkdownIt. Having built in support will be nice. I can update the using-contentful site later to exercise this functionality.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, doing markdown conversion at compile time is much nicer. Much less client code + somewhat faster render times.

// into HTML.
Object.keys(entryItemFields).forEach(entryItemFieldKey => {
if (entryItemFieldKey === `text`) {
entryItemFields[`${entryItemFieldKey}___NODE`] = createTextNode(
entryNode,
entryItemFields[entryItemFieldKey],
createNode
)

delete entryItemFields[entryItemFieldKey]
}
})

entryNode = { ...entryItemFields, ...entryNode }

// Get content digest of node.
const contentDigest = crypto
.createHash(`md5`)
.update(JSON.stringify(entryNode))
.digest(`hex`)
const contentDigest = digest(stringify(entryNode))

entryNode.internal.contentDigest = contentDigest

return entryNode
})

// Create a node for each content type
const contentTypeItemStr = JSON.stringify(contentTypeItem)
const contentTypeItemStr = stringify(contentTypeItem)

const contentTypeNode = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really in scope for this review, but I wonder if it was a good idea to expose ContentType itself as a gatsby node. I haven't thought of a use case for it yet. I suppose there's no particular harm, but could consider removing it to lower the plugin complexity / surface area.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I wondered about this too...

One use case perhaps is someone building an internal documentation site from their content model.

I'd lean towards keeping it I guess. I assume Contentful exposes it because there's sufficient demand?

@Khaledgarbaya could you provide background for what people generally use this data for?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @KyleAMathews I've seen user generally get ContentTypes if they want to map content types to components, for example, to have some sort of dynamic fields etc. Bu the normal usage I see user mostly care about Entries and assets

id: contentTypeItemId,
Expand All @@ -201,16 +259,12 @@ exports.sourceNodes = async (
description: contentTypeItem.description,
internal: {
type: `${makeTypeName(`ContentType`)}`,
content: contentTypeItemStr,
mediaType: `application/json`,
mediaType: `text/x-contentful`,
},
}

// Get content digest of node.
const contentDigest = crypto
.createHash(`md5`)
.update(JSON.stringify(contentTypeNode))
.digest(`hex`)
const contentDigest = digest(stringify(contentTypeNode))

contentTypeNode.internal.contentDigest = contentDigest

Expand All @@ -222,7 +276,7 @@ exports.sourceNodes = async (

assets.items.forEach(assetItem => {
// Create a node for each asset. They may be referenced by Entries
const assetItemStr = JSON.stringify(assetItem)
const assetItemStr = stringify(assetItem)

const assetNode = {
id: assetItem.sys.id,
Expand All @@ -231,16 +285,12 @@ exports.sourceNodes = async (
...assetItem.fields,
internal: {
type: `${makeTypeName(`Asset`)}`,
content: assetItemStr,
mediaType: `application/json`,
mediaType: `text/x-contentful`,
},
}

// Get content digest of node.
const contentDigest = crypto
.createHash(`md5`)
.update(JSON.stringify(assetNode))
.digest(`hex`)
const contentDigest = digest(stringify(assetNode))

assetNode.internal.contentDigest = contentDigest

Expand Down
1 change: 1 addition & 0 deletions packages/gatsby/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"is-relative-url": "^2.0.0",
"joi": "^9.1.1",
"json-loader": "^0.5.2",
"json-stringify-safe": "^5.0.1",
"json5": "^0.5.0",
"loader-utils": "^0.2.16",
"lodash": "^4.17.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class Dev404Page extends React.Component {
<h2>Pages ({this.props.data.allSitePage.totalCount})</h2>
<ul>
{this.props.data.allSitePage.edges.map(({ node }) =>
<li><Link to={node.path}>{node.path}</Link></li>
<li key={node.path}><Link to={node.path}>{node.path}</Link></li>
)}
</ul>
</div>}
Expand Down
3 changes: 2 additions & 1 deletion packages/gatsby/src/redux/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const _ = require(`lodash`)
const { composeWithDevTools } = require(`remote-redux-devtools`)
const fs = require(`fs`)
const EventEmitter = require(`eventemitter2`)
const stringify = require(`json-stringify-safe`)

// Create event emitter for actions
const emitter = new EventEmitter()
Expand Down Expand Up @@ -47,7 +48,7 @@ const saveState = _.debounce(state => {
const pickedState = _.pick(state, [`nodes`, `status`, `pageDataDependencies`])
fs.writeFile(
`${process.cwd()}/.cache/redux-state.json`,
JSON.stringify(pickedState, null, 2),
stringify(pickedState, null, 2),
() => {}
)
}, 1000)
Expand Down
6 changes: 4 additions & 2 deletions packages/gatsby/src/schema/infer-graphql-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ function inferFromFieldName(value, selector, types): GraphQLFieldConfig<*, *> {
const [, , linkedField] = key.split(`___`)

const linkedNode = findLinkedNode(value, linkedField)

invariant(
linkedNode,
oneLine`
Expand Down Expand Up @@ -464,9 +465,10 @@ export function inferObjectStructureFromNodes({
shouldInferFile(nodes, nextSelector, value)
) {
inferredField = inferFromUri(key, types)
}

// Finally our automatic inference of field value type.
} else {
// Finally our automatic inference of field value type.
if (!inferredField) {
inferredField = inferGraphQLType({
nodes,
types,
Expand Down