Skip to content

Commit

Permalink
Allow subobjects in markdown frontmatter arrays to link to files (#2255)
Browse files Browse the repository at this point in the history
* Allow subobjects in markdown frontmatter arrays to link to files

* Format and cleanup comments
  • Loading branch information
KyleAMathews committed Sep 28, 2017
1 parent 91058a4 commit ef48d66
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Array [
Object {
"children": Array [],
"frontmatter": Object {
"___PARENT": "whatever",
"date": "2017-09-18T23:19:51.246Z",
"parent": "whatever",
"title": "my little pony",
Expand Down Expand Up @@ -35,6 +36,7 @@ Array [
"child": Object {
"children": Array [],
"frontmatter": Object {
"___PARENT": "whatever",
"date": "2017-09-18T23:19:51.246Z",
"parent": "whatever",
"title": "my little pony",
Expand Down
17 changes: 17 additions & 0 deletions packages/gatsby-transformer-remark/src/on-node-create.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,26 @@ module.exports = async function onCreateNode({
type: `MarkdownRemark`,
},
}

// Add ___PARENT to sub-object in the frontmatter so we can
// use this to find the root markdown node when running GraphQL
// queries. Yes this is lame. But it's because in GraphQL child nodes
// can't access their parent nodes so we use this ___PARENT convention
// to get around this.
_.each(data.data, (v, k) => {
if (_.isArray(v) && _.isObject(v[0])) {
data.data[k] = v.map(o => {
return { ...o, ___PARENT: node.id }
})
}
})

markdownNode.frontmatter = {
title: ``, // always include a title
...data.data,
___PARENT: node.id,
// TODO Depreciate this at v2 as much larger chance of conflicting with a
// user supplied field.
parent: node.id,
}

Expand Down
79 changes: 69 additions & 10 deletions packages/gatsby/src/schema/infer-graphql-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -345,11 +345,15 @@ function findRootNode(node) {
let rootNode = node
let whileCount = 0
while (
rootNode.parent &&
getNode(rootNode.parent) !== undefined &&
(rootNode.___PARENT || rootNode.parent) &&
(getNode(rootNode.parent) !== undefined || getNode(rootNode.___PARENT)) &&
whileCount < 101
) {
rootNode = getNode(rootNode.parent)
if (rootNode.___PARENT) {
rootNode = getNode(rootNode.___PARENT)
} else {
rootNode = getNode(rootNode.parent)
}
whileCount += 1
if (whileCount > 100) {
console.log(
Expand All @@ -363,13 +367,6 @@ function findRootNode(node) {
}

function shouldInferFile(nodes, key, value) {
// Find the node used for this example.
const node = nodes.find(n => _.get(n, key) === value)

if (!node) {
return false
}

const looksLikeFile =
_.isString(value) &&
mime.lookup(value) !== `application/octet-stream` &&
Expand All @@ -382,6 +379,67 @@ function shouldInferFile(nodes, key, value) {
return false
}

// Find the node used for this example.
let node = nodes.find(n => _.get(n, key) === value)

if (!node) {
// Try another search as our "key" isn't always correct e.g.
// it doesn't support arrays so the right key could be "a.b[0].c" but
// this function will get "a.b.c".
//
// We loop through every value of nodes until we find
// a match.
const visit = (current, selector = [], fn) => {
for (let i = 0, keys = Object.keys(current); i < keys.length; i++) {
const key = keys[i]
const value = current[key]

if (value === undefined || value === null) continue

if (typeof value === `object` || typeof value === `function`) {
visit(current[key], selector.concat([key]), fn)
continue
}

let proceed = fn(current[key], key, selector, current)

if (proceed === false) {
break
}
}
}

const isNormalInteger = str => /^\+?(0|[1-9]\d*)$/.test(str)

node = nodes.find(n => {
let isMatch = false
visit(n, [], (v, k, selector, parent) => {
if (v === value) {
// Remove integers as they're for arrays, which our passed
// in object path doesn't have.
const normalizedSelector = selector
.map(s => (isNormalInteger(s) ? `` : s))
.filter(s => s !== ``)
const fullSelector = `${normalizedSelector.join(`.`)}.${k}`
if (fullSelector === key) {
isMatch = true
return false
}
}

// Not a match so we continue
return true
})

return isMatch
})

// Still no node.
if (!node) {
return false
}
}

const rootNode = findRootNode(node)

// Only nodes transformed (ultimately) from a File
Expand Down Expand Up @@ -498,6 +556,7 @@ export function inferObjectStructureFromNodes({
// Third if the field is pointing to a file (from another file).
} else if (
nodes[0].internal.type !== `File` &&
_.isString(value) &&
shouldInferFile(nodes, nextSelector, value)
) {
inferredField = inferFromUri(key, types)
Expand Down

0 comments on commit ef48d66

Please sign in to comment.