diff --git a/e2e-tests/development-runtime/plugins/gatsby-remark-subcache/index.js b/e2e-tests/development-runtime/plugins/gatsby-remark-subcache/index.js index 57a1f51e598e9..691df912945c1 100644 --- a/e2e-tests/development-runtime/plugins/gatsby-remark-subcache/index.js +++ b/e2e-tests/development-runtime/plugins/gatsby-remark-subcache/index.js @@ -2,10 +2,18 @@ const visit = require(`unist-util-visit`) const { id } = require(`./constants`) module.exports = function remarkPlugin({ cache, markdownAST }) { - visit(markdownAST, `html`, async node => { - if (node.value.match(id)) { - const value = await cache.get(id) - node.value = node.value.replace(/%SUBCACHE_VALUE%/, value) - } + const promises = [] + + visit(markdownAST, `html`, node => { + promises.push( + (async () => { + if (node.value.match(id)) { + const value = await cache.get(id) + node.value = node.value.replace(/%SUBCACHE_VALUE%/, value) + } + })() + ) }) + + return Promise.all(promises) } diff --git a/packages/gatsby-transformer-remark/src/__tests__/__snapshots__/extend-node.js.snap b/packages/gatsby-transformer-remark/src/__tests__/__snapshots__/extend-node.js.snap index 4b6a05a63f702..3b35569067387 100644 --- a/packages/gatsby-transformer-remark/src/__tests__/__snapshots__/extend-node.js.snap +++ b/packages/gatsby-transformer-remark/src/__tests__/__snapshots__/extend-node.js.snap @@ -3,6 +3,13 @@ exports[`Excerpt is generated correctly from schema correctly loads a default excerpt 1`] = ` Object { "excerpt": "", + "excerptAst": Object { + "children": Array [], + "data": Object { + "quirksMode": false, + }, + "type": "root", + }, "frontmatter": Object { "title": "my little pony", }, @@ -12,6 +19,17 @@ Object { exports[`Excerpt is generated correctly from schema correctly loads an excerpt 1`] = ` Object { "excerpt": "Where oh where is my little pony?", + "excerptAst": Object { + "children": Array [ + Object { + "type": "text", + "value": "Where oh where is my little pony?", + }, + ], + "properties": Object {}, + "tagName": "p", + "type": "element", + }, "frontmatter": Object { "title": "my little pony", }, @@ -21,6 +39,17 @@ Object { exports[`Excerpt is generated correctly from schema correctly prunes length to default value 1`] = ` Object { "excerpt": "Where oh where is my little pony? Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi auctor sit amet velit id facilisis. Nulla…", + "excerptAst": Object { + "children": Array [ + Object { + "type": "text", + "value": "Where oh where is my little pony? Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi auctor sit amet velit id facilisis. Nulla…", + }, + ], + "properties": Object {}, + "tagName": "p", + "type": "element", + }, "frontmatter": Object { "title": "my little pony", }, @@ -30,6 +59,17 @@ Object { exports[`Excerpt is generated correctly from schema correctly prunes length to provided parameter 1`] = ` Object { "excerpt": "Where oh where is my little pony? Lorem ipsum…", + "excerptAst": Object { + "children": Array [ + Object { + "type": "text", + "value": "Where oh where is my little pony? Lorem ipsum…", + }, + ], + "properties": Object {}, + "tagName": "p", + "type": "element", + }, "frontmatter": Object { "title": "my little pony", }, @@ -39,6 +79,17 @@ Object { exports[`Excerpt is generated correctly from schema correctly prunes length to provided parameter with truncate 1`] = ` Object { "excerpt": "Where oh where is my little pony? Lorem ipsum dol…", + "excerptAst": Object { + "children": Array [ + Object { + "type": "text", + "value": "Where oh where is my little pony? Lorem ipsum dol…", + }, + ], + "properties": Object {}, + "tagName": "p", + "type": "element", + }, "frontmatter": Object { "title": "my little pony", }, @@ -49,6 +100,30 @@ exports[`Excerpt is generated correctly from schema correctly uses excerpt separ Object { "excerpt": "Where oh where is my little pony? ", + "excerptAst": Object { + "children": Array [ + Object { + "children": Array [ + Object { + "type": "text", + "value": "Where oh where is my little pony?", + }, + ], + "properties": Object {}, + "tagName": "p", + "type": "element", + }, + Object { + "type": "text", + "value": " +", + }, + ], + "data": Object { + "quirksMode": false, + }, + "type": "root", + }, "frontmatter": Object { "title": "my little pony", }, @@ -58,6 +133,76 @@ Object { exports[`Excerpt is generated correctly from schema given an html format, it correctly maps nested markdown to html 1`] = ` Object { "excerpt": "
Where oh where is ?
Where oh where is that pony? Is he in the stable…
", + "excerptAst": Object { + "children": Array [ + Object { + "type": "text", + "value": "Where oh where is that pony? Is he in the stable…", + }, + ], + "properties": Object {}, + "tagName": "p", + "type": "element", + }, "frontmatter": Object { "title": "my little pony", }, @@ -77,6 +233,45 @@ exports[`Excerpt is generated correctly from schema given an html format, it res Object { "excerpt": "Where oh where is that pony? Is he in the stable or by the stream?
", + "excerptAst": Object { + "children": Array [ + Object { + "children": Array [ + Object { + "type": "text", + "value": "Where oh where is that ", + }, + Object { + "children": Array [ + Object { + "type": "text", + "value": "pony", + }, + ], + "properties": Object {}, + "tagName": "em", + "type": "element", + }, + Object { + "type": "text", + "value": "? Is he in the stable or by the stream?", + }, + ], + "properties": Object {}, + "tagName": "p", + "type": "element", + }, + Object { + "type": "text", + "value": " +", + }, + ], + "data": Object { + "quirksMode": false, + }, + "type": "root", + }, "frontmatter": Object { "title": "my little pony", }, @@ -86,6 +281,32 @@ Object { exports[`Excerpt is generated correctly from schema given raw html in the text body, this html is not escaped 1`] = ` Object { "excerpt": "Where is my pony
named leo?
Where oh where is ?
pony
named leo?`,
`excerpt(format: HTML)
+ excerptAst
frontmatter {
title
}
@@ -278,6 +403,32 @@ Where is my pony
named leo?`,
expect(node.excerpt).toMatch(
`Where is my pony
named leo?
Where oh where is that pony? Is he in the stable…
` ) + expect(node.excerptAst).toMatchObject({ + children: [ + { + type: `text`, + value: `Where oh where is that pony? Is he in the stable…`, + }, + ], + properties: {}, + tagName: `p`, + type: `element`, + }) } ) @@ -316,6 +479,7 @@ Where oh where is that *pony*? Is he in the stable or by the stream? Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi auctor sit amet velit id facilisis. Nulla viverra, eros at efficitur pulvinar, lectus orci accumsan nisi, eu blandit elit nulla nec lectus. Integer porttitor imperdiet sapien. Quisque in orci sed nisi consequat aliquam. Aenean id mollis nisi. Sed auctor odio id erat facilisis venenatis. Quisque posuere faucibus libero vel fringilla. `, `excerpt(format: HTML, pruneLength: 50) + excerptAst(pruneLength: 50) frontmatter { title } @@ -325,6 +489,44 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi auctor sit amet v expect(node.excerpt).toMatch( `Where oh where is that pony? Is he in the stable or by the stream?
` ) + expect(node.excerptAst).toMatchObject({ + children: [ + { + children: [ + { + type: `text`, + value: `Where oh where is that `, + }, + { + children: [ + { + type: `text`, + value: `pony`, + }, + ], + properties: {}, + tagName: `em`, + type: `element`, + }, + { + type: `text`, + value: `? Is he in the stable or by the stream?`, + }, + ], + properties: {}, + tagName: `p`, + type: `element`, + }, + { + type: `text`, + value: `\n`, + }, + ], + data: { + quirksMode: false, + }, + type: `root`, + }) }, { pluginOptions: { excerpt_separator: `` } } ) diff --git a/packages/gatsby-transformer-remark/src/extend-node-type.js b/packages/gatsby-transformer-remark/src/extend-node-type.js index c357132c35148..3eabbcb45e93b 100644 --- a/packages/gatsby-transformer-remark/src/extend-node-type.js +++ b/packages/gatsby-transformer-remark/src/extend-node-type.js @@ -363,6 +363,91 @@ module.exports = ( } } + async function getExcerptAst( + markdownNode, + { pruneLength, truncate, excerptSeparator } + ) { + const fullAST = await getHTMLAst(markdownNode) + if (excerptSeparator) { + return cloneTreeUntil( + fullAST, + ({ nextNode }) => + nextNode.type === `raw` && nextNode.value === excerptSeparator + ) + } + if (!fullAST.children.length) { + return fullAST + } + + const excerptAST = cloneTreeUntil(fullAST, ({ root }) => { + const totalExcerptSoFar = getConcatenatedValue(root) + return totalExcerptSoFar && totalExcerptSoFar.length > pruneLength + }) + const unprunedExcerpt = getConcatenatedValue(excerptAST) + if ( + !unprunedExcerpt || + (pruneLength && unprunedExcerpt.length < pruneLength) + ) { + return excerptAST + } + + const lastTextNode = findLastTextNode(excerptAST) + const amountToPruneLastNode = + pruneLength - (unprunedExcerpt.length - lastTextNode.value.length) + if (!truncate) { + lastTextNode.value = prune( + lastTextNode.value, + amountToPruneLastNode, + `…` + ) + } else { + lastTextNode.value = _.truncate(lastTextNode.value, { + length: pruneLength, + omission: `…`, + }) + } + return excerptAST + } + + async function getExcerpt( + markdownNode, + { format, pruneLength, truncate, excerptSeparator } + ) { + if (format === `html`) { + const excerptAST = await getExcerptAst(markdownNode, { + pruneLength, + truncate, + excerptSeparator, + }) + const html = hastToHTML(excerptAST, { + allowDangerousHTML: true, + }) + return html + } + + if (markdownNode.excerpt) { + return markdownNode.excerpt + } + + const text = await getAST(markdownNode).then(ast => { + const excerptNodes = [] + visit(ast, node => { + if (node.type === `text` || node.type === `inlineCode`) { + excerptNodes.push(node.value) + } + return + }) + if (!truncate) { + return prune(excerptNodes.join(` `), pruneLength, `…`) + } + return _.truncate(excerptNodes.join(` `), { + length: pruneLength, + omission: `…`, + }) + }) + return text + } + const HeadingType = new GraphQLObjectType({ name: `MarkdownHeading`, fields: { @@ -433,77 +518,35 @@ module.exports = ( defaultValue: `plain`, }, }, - async resolve(markdownNode, { format, pruneLength, truncate }) { - if (format === `html`) { - if (pluginOptions.excerpt_separator) { - const fullAST = await getHTMLAst(markdownNode) - const excerptAST = cloneTreeUntil( - fullAST, - ({ nextNode }) => - nextNode.type === `raw` && - nextNode.value === pluginOptions.excerpt_separator - ) - return hastToHTML(excerptAST, { - allowDangerousHTML: true, - }) - } - const fullAST = await getHTMLAst(markdownNode) - if (!fullAST.children.length) { - return `` - } - - const excerptAST = cloneTreeUntil(fullAST, ({ root }) => { - const totalExcerptSoFar = getConcatenatedValue(root) - return totalExcerptSoFar && totalExcerptSoFar.length > pruneLength - }) - const unprunedExcerpt = getConcatenatedValue(excerptAST) - if (!unprunedExcerpt) { - return `` - } - - if (pruneLength && unprunedExcerpt.length < pruneLength) { - return hastToHTML(excerptAST, { - allowDangerousHTML: true, - }) - } - - const lastTextNode = findLastTextNode(excerptAST) - const amountToPruneLastNode = - pruneLength - (unprunedExcerpt.length - lastTextNode.value.length) - if (!truncate) { - lastTextNode.value = prune( - lastTextNode.value, - amountToPruneLastNode, - `…` - ) - } else { - lastTextNode.value = _.truncate(lastTextNode.value, { - length: pruneLength, - omission: `…`, - }) - } - return hastToHTML(excerptAST, { - allowDangerousHTML: true, - }) - } - if (markdownNode.excerpt) { - return Promise.resolve(markdownNode.excerpt) - } - return getAST(markdownNode).then(ast => { - const excerptNodes = [] - visit(ast, node => { - if (node.type === `text` || node.type === `inlineCode`) { - excerptNodes.push(node.value) - } - return - }) - if (!truncate) { - return prune(excerptNodes.join(` `), pruneLength, `…`) - } - return _.truncate(excerptNodes.join(` `), { - length: pruneLength, - omission: `…`, - }) + resolve(markdownNode, { format, pruneLength, truncate }) { + return getExcerpt(markdownNode, { + format, + pruneLength, + truncate, + excerptSeparator: pluginOptions.excerpt_separator, + }) + }, + }, + excerptAst: { + type: GraphQLJSON, + args: { + pruneLength: { + type: GraphQLInt, + defaultValue: 140, + }, + truncate: { + type: GraphQLBoolean, + defaultValue: false, + }, + }, + resolve(markdownNode, { pruneLength, truncate }) { + return getExcerptAst(markdownNode, { + pruneLength, + truncate, + excerptSeparator: pluginOptions.excerpt_separator, + }).then(ast => { + const strippedAst = stripPosition(_.clone(ast), true) + return hastReparseRaw(strippedAst) }) }, },