diff --git a/packages/gatsby-transformer-remark/README.md b/packages/gatsby-transformer-remark/README.md index 423329b668d06..ffea63c965858 100644 --- a/packages/gatsby-transformer-remark/README.md +++ b/packages/gatsby-transformer-remark/README.md @@ -93,7 +93,28 @@ Using the following GraphQL query you'll be able to get the table of contents edges { node { html - tableOfContents(pathToSlugField: "frontmatter.path") + tableOfContents + } + } + } +} +``` + +### Configuring the tableOfContents + +By default the tableOfContents is using the field `slug` to generate URLs. You can however provide another field using the pathToSlugField parameter. **Note** that providing a non existing field will cause the result to be null. To alter the default values for tableOfContents generation, include values for `heading` (string) and/or `maxDepth` (number 1 to 6) in graphQL query. If a value for `heading` is given, the first heading that matches will be ommitted and the toc is generated from the next heading of the same depth onwards. Value for `maxDepth` sets the maximum depth of the toc (i.e. if a maxDepth of 3 is set, only h1 to h3 headings will appear in the toc). + +```graphql +{ + allMarkdownRemark { + edges { + node { + html + tableOfContents( + pathToSlugField: "frontmatter.path" + heading: "only show toc from this heading onwards" + maxDepth: 2 + ) frontmatter { # Assumes you're using path in your frontmatter. path @@ -104,7 +125,22 @@ Using the following GraphQL query you'll be able to get the table of contents } ``` -By default the tableOfContents is using the field `slug` to generate URLs. You can however provide another field using the pathToSlugField parameter. **Note** that providing a non existing field will cause the result to be null. +To pass default options to the plugin generating the tableOfContents, configure it in gatsby-config.js as shown below. The options shown below are the defaults used by the plugin. + +```javascript +// In your gatsby-config.js +plugins: [ + { + resolve: `gatsby-transformer-remark`, + options: { + tableOfContents: { + heading: null, + maxDepth: 6, + }, + }, + }, +] +``` ### Excerpts diff --git a/packages/gatsby-transformer-remark/src/__tests__/extend-node.js b/packages/gatsby-transformer-remark/src/__tests__/extend-node.js index 8fc3ead8756e0..d46f821579d44 100644 --- a/packages/gatsby-transformer-remark/src/__tests__/extend-node.js +++ b/packages/gatsby-transformer-remark/src/__tests__/extend-node.js @@ -455,6 +455,89 @@ final text expect(node).toMatchSnapshot() } ) + + bootstrapTest( + `table of contents is generated with correct depth (graphql option)`, + `--- +title: "my little pony" +date: "2017-09-18T23:19:51.246Z" +--- +# first title + +some text + +## second title + +some other text`, + `tableOfContents(pathToSlugField: "frontmatter.title", maxDepth: 1) + frontmatter { + title + }`, + node => { + expect(node.tableOfContents).toBe(``) + } + ) + + bootstrapTest( + `table of contents is generated with correct depth (plugin option)`, + `--- +title: "my little pony" +date: "2017-09-18T23:19:51.246Z" +--- +# first title + +some text + +## second title + +some other text`, + `tableOfContents(pathToSlugField: "frontmatter.title") + frontmatter { + title + }`, + node => { + expect(node.tableOfContents).toBe(``) + }, + { + pluginOptions: { + tableOfContents: { + maxDepth: 1, + }, + }, + } + ) + + bootstrapTest( + `table of contents is generated from given heading onwards`, + `--- +title: "my little pony" +date: "2017-09-18T23:19:51.246Z" +--- +# first title + +some text + +## second title + +some other text + +# third title + +final text`, + `tableOfContents(pathToSlugField: "frontmatter.title", heading: "first title") + frontmatter { + title + }`, + node => { + expect(node.tableOfContents).toBe(``) + } + ) }) describe(`Links are correctly prefixed`, () => { diff --git a/packages/gatsby-transformer-remark/src/extend-node-type.js b/packages/gatsby-transformer-remark/src/extend-node-type.js index c4054ba1722aa..1d478be20503e 100644 --- a/packages/gatsby-transformer-remark/src/extend-node-type.js +++ b/packages/gatsby-transformer-remark/src/extend-node-type.js @@ -49,10 +49,12 @@ const headingsCacheKey = node => `transformer-remark-markdown-headings-${ node.internal.contentDigest }-${pluginsCacheStr}-${pathPrefixCacheStr}` -const tableOfContentsCacheKey = node => +const tableOfContentsCacheKey = (node, appliedTocOptions) => `transformer-remark-markdown-toc-${ node.internal.contentDigest - }-${pluginsCacheStr}-${pathPrefixCacheStr}` + }-${pluginsCacheStr}-${JSON.stringify( + appliedTocOptions + )}-${pathPrefixCacheStr}` // ensure only one `/` in new url const withPathPrefix = (url, pathPrefix) => @@ -98,16 +100,21 @@ module.exports = ( return new Promise((resolve, reject) => { // Setup Remark. const { + blocks, commonmark = true, footnotes = true, - pedantic = true, gfm = true, - blocks, + pedantic = true, + tableOfContents = { + heading: null, + maxDepth: 6, + }, } = pluginOptions + const tocOptions = tableOfContents const remarkOptions = { - gfm, commonmark, footnotes, + gfm, pedantic, } if (_.isArray(blocks)) { @@ -271,27 +278,37 @@ module.exports = ( } } - async function getTableOfContents(markdownNode, pathToSlugField) { - const cachedToc = await cache.get(tableOfContentsCacheKey(markdownNode)) + async function getTableOfContents(markdownNode, gqlTocOptions) { + // fetch defaults + let appliedTocOptions = { ...tocOptions, ...gqlTocOptions } + // get cached toc + const cachedToc = await cache.get( + tableOfContentsCacheKey(markdownNode, appliedTocOptions) + ) if (cachedToc) { return cachedToc } else { const ast = await getAST(markdownNode) - const tocAst = mdastToToc(ast) + const tocAst = mdastToToc(ast, appliedTocOptions) let toc if (tocAst.map) { const addSlugToUrl = function(node) { if (node.url) { - if (_.get(markdownNode, pathToSlugField) === undefined) { + if ( + _.get(markdownNode, appliedTocOptions.pathToSlugField) === + undefined + ) { console.warn( - `Skipping TableOfContents. Field '${pathToSlugField}' missing from markdown node` + `Skipping TableOfContents. Field '${ + appliedTocOptions.pathToSlugField + }' missing from markdown node` ) return null } node.url = [ pathPrefix, - _.get(markdownNode, pathToSlugField), + _.get(markdownNode, appliedTocOptions.pathToSlugField), node.url, ] .join(`/`) @@ -309,7 +326,7 @@ module.exports = ( } else { toc = `` } - cache.set(tableOfContentsCacheKey(markdownNode), toc) + cache.set(tableOfContentsCacheKey(markdownNode, appliedTocOptions), toc) return toc } } @@ -528,9 +545,15 @@ module.exports = ( type: GraphQLString, defaultValue: `fields.slug`, }, + maxDepth: { + type: GraphQLInt, + }, + heading: { + type: GraphQLString, + }, }, - resolve(markdownNode, { pathToSlugField }) { - return getTableOfContents(markdownNode, pathToSlugField) + resolve(markdownNode, args) { + return getTableOfContents(markdownNode, args) }, }, // TODO add support for non-latin languages https://github.com/wooorm/remark/issues/251#issuecomment-296731071