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 \\"that?

", + "excerptAst": Object { + "children": Array [ + Object { + "type": "text", + "value": "Where oh ", + }, + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "type": "text", + "value": "where", + }, + ], + "properties": Object {}, + "tagName": "em", + "type": "element", + }, + ], + "properties": Object { + "href": "nick.com", + }, + "tagName": "a", + "type": "element", + }, + Object { + "type": "text", + "value": " ", + }, + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "type": "text", + "value": "is", + }, + ], + "properties": Object {}, + "tagName": "em", + "type": "element", + }, + ], + "properties": Object {}, + "tagName": "strong", + "type": "element", + }, + Object { + "type": "text", + "value": " ", + }, + Object { + "children": Array [], + "properties": Object { + "alt": "that pony", + "src": "pony.png", + }, + "tagName": "img", + "type": "element", + }, + Object { + "type": "text", + "value": "?", + }, + ], + "properties": Object {}, + "tagName": "p", + "type": "element", + }, "frontmatter": Object { "title": "my little pony", }, @@ -67,6 +212,17 @@ Object { exports[`Excerpt is generated correctly from schema given an html format, it prunes large excerpts 1`] = ` Object { "excerpt": "

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?

", + "excerptAst": Object { + "children": Array [ + Object { + "type": "text", + "value": "Where is my ", + }, + Object { + "children": Array [ + Object { + "type": "text", + "value": "pony", + }, + ], + "properties": Object {}, + "tagName": "code", + "type": "element", + }, + Object { + "type": "text", + "value": " named leo?", + }, + ], + "properties": Object {}, + "tagName": "p", + "type": "element", + }, "frontmatter": Object { "title": "my little pony", }, diff --git a/packages/gatsby-transformer-remark/src/__tests__/extend-node.js b/packages/gatsby-transformer-remark/src/__tests__/extend-node.js index 5a1237e33c188..435fc8b8d1862 100644 --- a/packages/gatsby-transformer-remark/src/__tests__/extend-node.js +++ b/packages/gatsby-transformer-remark/src/__tests__/extend-node.js @@ -124,7 +124,7 @@ const bootstrapTest = ( actions, createNodeId, }, - { ...additionalParameters } + { ...additionalParameters, ...pluginOptions } ) }) } @@ -138,6 +138,7 @@ date: "2017-09-18T23:19:51.246Z" --- Where oh where is my little pony?`, `excerpt + excerptAst frontmatter { title } @@ -145,6 +146,17 @@ Where oh where is my little pony?`, node => { expect(node).toMatchSnapshot() expect(node.excerpt).toMatch(`Where oh where is my little pony?`) + expect(node.excerptAst).toMatchObject({ + children: [ + { + type: `text`, + value: `Where oh where is my little pony?`, + }, + ], + properties: {}, + tagName: `p`, + type: `element`, + }) } ) @@ -155,6 +167,7 @@ title: "my little pony" date: "2017-09-18T23:19:51.246Z" ---`, `excerpt + excerptAst frontmatter { title } @@ -162,6 +175,13 @@ date: "2017-09-18T23:19:51.246Z" node => { expect(node).toMatchSnapshot() expect(node.excerpt).toMatch(``) + expect(node.excerptAst).toMatchObject({ + children: [], + data: { + quirksMode: false, + }, + type: `root`, + }) } ) @@ -178,6 +198,7 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi auctor sit amet v In quis lectus sed eros efficitur luctus. Morbi tempor, nisl eget feugiat tincidunt, sem velit vulputate enim, nec interdum augue enim nec mauris. Nulla iaculis ante sed enim placerat pretium. Nulla metus odio, facilisis vestibulum lobortis vitae, bibendum at nunc. Donec sit amet efficitur metus, in bibendum nisi. Vivamus tempus vel turpis sit amet auctor. Maecenas luctus vestibulum velit, at sagittis leo volutpat quis. Praesent posuere nec augue eget sodales. Pellentesque vitae arcu ut est varius venenatis id maximus sem. Curabitur non consectetur turpis. `, `excerpt + excerptAst frontmatter { title } @@ -185,8 +206,31 @@ In quis lectus sed eros efficitur luctus. Morbi tempor, nisl eget feugiat tincid node => { expect(node).toMatchSnapshot() expect(node.excerpt).toMatch(`Where oh where is my little pony?`) + expect(node.excerptAst).toMatchObject({ + children: [ + { + children: [ + { + type: `text`, + value: `Where oh where is my little pony?`, + }, + ], + properties: {}, + tagName: `p`, + type: `element`, + }, + { + type: `text`, + value: `\n`, + }, + ], + data: { + quirksMode: false, + }, + type: `root`, + }) }, - { additionalParameters: { excerpt_separator: `` } } + { pluginOptions: { excerpt_separator: `` } } ) const content = `--- @@ -201,6 +245,7 @@ In quis lectus sed eros efficitur luctus. Morbi tempor, nisl eget feugiat tincid `correctly prunes length to default value`, content, `excerpt + excerptAst frontmatter { title } @@ -208,6 +253,8 @@ In quis lectus sed eros efficitur luctus. Morbi tempor, nisl eget feugiat tincid node => { expect(node).toMatchSnapshot() expect(node.excerpt.length).toBe(139) + expect(node.excerptAst.children.length).toBe(1) + expect(node.excerptAst.children[0].value.length).toBe(139) } ) @@ -215,6 +262,7 @@ In quis lectus sed eros efficitur luctus. Morbi tempor, nisl eget feugiat tincid `correctly prunes length to provided parameter`, content, `excerpt(pruneLength: 50) + excerptAst(pruneLength: 50) frontmatter { title } @@ -222,6 +270,8 @@ In quis lectus sed eros efficitur luctus. Morbi tempor, nisl eget feugiat tincid node => { expect(node).toMatchSnapshot() expect(node.excerpt.length).toBe(46) + expect(node.excerptAst.children.length).toBe(1) + expect(node.excerptAst.children[0].value.length).toBe(46) } ) @@ -229,6 +279,7 @@ In quis lectus sed eros efficitur luctus. Morbi tempor, nisl eget feugiat tincid `correctly prunes length to provided parameter with truncate`, content, `excerpt(pruneLength: 50, truncate: true) + excerptAst(pruneLength: 50, truncate: true) frontmatter { title } @@ -236,6 +287,8 @@ In quis lectus sed eros efficitur luctus. Morbi tempor, nisl eget feugiat tincid node => { expect(node).toMatchSnapshot() expect(node.excerpt.length).toBe(50) + expect(node.excerptAst.children.length).toBe(1) + expect(node.excerptAst.children[0].value.length).toBe(50) } ) @@ -248,6 +301,7 @@ date: "2017-09-18T23:19:51.246Z" Where oh [*where*](nick.com) **_is_** ![that pony](pony.png)?`, `excerpt(format: HTML) + excerptAst frontmatter { title } @@ -257,6 +311,76 @@ Where oh [*where*](nick.com) **_is_** ![that pony](pony.png)?`, expect(node.excerpt).toMatch( `

Where oh where is that pony?

` ) + expect(node.excerptAst).toMatchObject({ + children: [ + { + type: `text`, + value: `Where oh `, + }, + { + children: [ + { + children: [ + { + type: `text`, + value: `where`, + }, + ], + properties: {}, + tagName: `em`, + type: `element`, + }, + ], + properties: { + href: `nick.com`, + }, + tagName: `a`, + type: `element`, + }, + { + type: `text`, + value: ` `, + }, + { + children: [ + { + children: [ + { + type: `text`, + value: `is`, + }, + ], + properties: {}, + tagName: `em`, + type: `element`, + }, + ], + properties: {}, + tagName: `strong`, + type: `element`, + }, + { + type: `text`, + value: ` `, + }, + { + children: [], + properties: { + alt: `that pony`, + src: `pony.png`, + }, + tagName: `img`, + type: `element`, + }, + { + type: `text`, + value: `?`, + }, + ], + properties: {}, + tagName: `p`, + type: `element`, + }) } ) @@ -269,6 +393,7 @@ date: "2017-09-18T23:19:51.246Z" Where is my 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?

` ) + expect(node.excerptAst).toMatchObject({ + children: [ + { + type: `text`, + value: `Where is my `, + }, + { + children: [ + { + type: `text`, + value: `pony`, + }, + ], + properties: {}, + tagName: `code`, + type: `element`, + }, + { + type: `text`, + value: ` named leo?`, + }, + ], + properties: {}, + tagName: `p`, + type: `element`, + }) }, { pluginOptions: { excerpt_separator: `` } } ) @@ -291,6 +442,7 @@ date: "2017-09-18T23:19:51.246Z" Where oh where is that pony? Is he in the stable or down by the stream?`, `excerpt(format: HTML, pruneLength: 50) + excerptAst(pruneLength: 50) frontmatter { title } @@ -300,6 +452,17 @@ Where oh where is that pony? Is he in the stable or down by the stream?`, expect(node.excerpt).toMatch( `

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) }) }, },