From dd6bf147b04aa91e162db61d1a26b5e3bb503f72 Mon Sep 17 00:00:00 2001 From: Jonathan Haines Date: Sat, 23 Jul 2016 12:32:19 +1000 Subject: [PATCH] Refactor to externalise toc generation to `mdast-util-toc` Closes GH-6. --- index.js | 288 +------------------- package.json | 2 +- test/fixtures/missing-content/output.md | 2 + test/fixtures/normal-content/input.md | 15 + test/fixtures/normal-content/output.md | 20 ++ test/fixtures/normal-more-content/input.md | 17 ++ test/fixtures/normal-more-content/output.md | 20 ++ 7 files changed, 85 insertions(+), 279 deletions(-) create mode 100644 test/fixtures/normal-content/input.md create mode 100644 test/fixtures/normal-content/output.md create mode 100644 test/fixtures/normal-more-content/input.md create mode 100644 test/fixtures/normal-more-content/output.md diff --git a/index.js b/index.js index 3024209..ecee9ae 100644 --- a/index.js +++ b/index.js @@ -15,286 +15,14 @@ */ var slug = require('remark-slug'); -var toString = require('mdast-util-to-string'); +var toc = require('mdast-util-toc'); /* * Constants. */ -var HEADING = 'heading'; -var LIST = 'list'; -var LIST_ITEM = 'listItem'; -var PARAGRAPH = 'paragraph'; -var LINK = 'link'; -var TEXT = 'text'; var DEFAULT_HEADING = 'toc|table[ -]of[ -]contents?'; -/** - * Transform a string into an applicable expression. - * - * @param {string} value - Content to expressionise. - * @return {RegExp} - Expression from `value`. - */ -function toExpression(value) { - return new RegExp('^(' + value + ')$', 'i'); -} - -/** - * Check if `node` is the main heading. - * - * @param {Node} node - Node to check. - * @param {number} depth - Depth to check. - * @param {RegExp} expression - Expression to check. - * @return {boolean} - Whether `node` is a main heading. - */ -function isOpeningHeading(node, depth, expression) { - return depth === null && node && node.type === HEADING && - expression.test(toString(node)); -} - -/** - * Check if `node` is the next heading. - * - * @param {Node} node - Node to check. - * @param {number} depth - Depth of opening heading. - * @return {boolean} - Whether znode is a closing heading. - */ -function isClosingHeading(node, depth) { - return depth && node && node.type === HEADING && node.depth <= depth; -} - -/** - * Search a node for a location. - * - * @param {Node} root - Parent to search in. - * @param {RegExp} expression - Heading-content to search - * for. - * @param {number} maxDepth - Maximum-depth to include. - * @return {Object} - Results. - */ -function search(root, expression, maxDepth) { - var index = -1; - var length = root.children.length; - var depth = null; - var lookingForToc = true; - var map = []; - var child; - var headingIndex; - var closingIndex; - var value; - - while (++index < length) { - child = root.children[index]; - - if (child.type !== HEADING) { - continue; - } - - value = toString(child); - - if (lookingForToc) { - if (isClosingHeading(child, depth)) { - closingIndex = index; - lookingForToc = false; - } - - if (isOpeningHeading(child, depth, expression)) { - headingIndex = index + 1; - depth = child.depth; - } - } - - if (!lookingForToc && value && child.depth <= maxDepth) { - map.push({ - 'depth': child.depth, - 'value': value, - 'id': child.data.htmlAttributes.id - }); - } - } - - if (headingIndex) { - if (!closingIndex) { - closingIndex = length + 1; - } - - /* - * Remove current TOC. - */ - - root.children.splice(headingIndex, closingIndex - headingIndex); - } - - return { - 'index': headingIndex || null, - 'map': map - }; -} - -/** - * Create a list. - * - * @return {Object} - List node. - */ -function list() { - return { - 'type': LIST, - 'ordered': false, - 'children': [] - }; -} - -/** - * Create a list item. - * - * @return {Object} - List-item node. - */ -function listItem() { - return { - 'type': LIST_ITEM, - 'loose': false, - 'children': [] - }; -} - -/** - * Insert a `node` into a `parent`. - * - * @param {Object} node - `node` to insert. - * @param {Object} parent - Parent of `node`. - * @param {boolean?} [tight] - Prefer tight list-items. - */ -function insert(node, parent, tight) { - var children = parent.children; - var length = children.length; - var last = children[length - 1]; - var isLoose = false; - var index; - var item; - - if (node.depth === 1) { - item = listItem(); - - item.children.push({ - 'type': PARAGRAPH, - 'children': [ - { - 'type': LINK, - 'title': null, - 'url': '#' + node.id, - 'children': [ - { - 'type': TEXT, - 'value': node.value - } - ] - } - ] - }); - - children.push(item); - } else if (last && last.type === LIST_ITEM) { - insert(node, last, tight); - } else if (last && last.type === LIST) { - node.depth--; - - insert(node, last); - } else if (parent.type === LIST) { - item = listItem(); - - insert(node, item); - - children.push(item); - } else { - item = list(); - node.depth--; - - insert(node, item); - - children.push(item); - } - - /* - * Properly style list-items with new lines. - */ - - if (parent.type === LIST_ITEM) { - parent.loose = tight ? false : children.length > 1; - } else { - if (tight) { - isLoose = false; - } else { - index = -1; - - while (++index < length) { - if (children[index].loose) { - isLoose = true; - - break; - } - } - } - - index = -1; - - while (++index < length) { - children[index].loose = isLoose; - } - } -} - -/** - * Transform a list of heading objects to a markdown list. - * - * @param {Array.} map - Heading-map to insert. - * @param {boolean?} [tight] - Prefer tight list-items. - * @return {Object} - List node. - */ -function contents(map, tight) { - var minDepth = Infinity; - var index = -1; - var length = map.length; - var table; - - /* - * Find minimum depth. - */ - - while (++index < length) { - if (map[index].depth < minDepth) { - minDepth = map[index].depth; - } - } - - /* - * Normalize depth. - */ - - index = -1; - - while (++index < length) { - map[index].depth -= minDepth - 1; - } - - /* - * Construct the main list. - */ - - table = list(); - - /* - * Add TOC to list. - */ - - index = -1; - - while (++index < length) { - insert(map[index], table, tight); - } - - return table; -} - /** * Attacher. * @@ -304,7 +32,7 @@ function contents(map, tight) { */ function attacher(processor, options) { var settings = options || {}; - var heading = toExpression(settings.heading || DEFAULT_HEADING); + var heading = settings.heading || DEFAULT_HEADING; var depth = settings.maxDepth || 6; var tight = settings.tight; @@ -317,9 +45,13 @@ function attacher(processor, options) { * @param {Node} node - Root to search in. */ function transformer(node) { - var result = search(node, heading, depth); + var result = toc(node, { + 'heading': heading, + 'maxDepth': depth, + 'tight': tight + }); - if (result.index === null || !result.map.length) { + if (result.index === null || result.index === -1 || !result.map) { return; } @@ -329,8 +61,8 @@ function attacher(processor, options) { node.children = [].concat( node.children.slice(0, result.index), - contents(result.map, tight), - node.children.slice(result.index) + result.map, + node.children.slice(result.endIndex) ); } diff --git a/package.json b/package.json index 5f4977c..851cb29 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ ], "dependencies": { "remark-slug": "^4.0.0", - "mdast-util-to-string": "^1.0.0" + "mdast-util-toc": "^2.0.0" }, "repository": { "type": "git", diff --git a/test/fixtures/missing-content/output.md b/test/fixtures/missing-content/output.md index d433c02..db96d5c 100644 --- a/test/fixtures/missing-content/output.md +++ b/test/fixtures/missing-content/output.md @@ -1,3 +1,5 @@ # Missing content ## Table of Contents + +* * * diff --git a/test/fixtures/normal-content/input.md b/test/fixtures/normal-content/input.md new file mode 100644 index 0000000..5f508a7 --- /dev/null +++ b/test/fixtures/normal-content/input.md @@ -0,0 +1,15 @@ +# Normal + +## Table of Contents + +Here’s some content. + +# Something if + +## Something else + +Text. + +## Something elsefi + +# Something iffi diff --git a/test/fixtures/normal-content/output.md b/test/fixtures/normal-content/output.md new file mode 100644 index 0000000..a86eacf --- /dev/null +++ b/test/fixtures/normal-content/output.md @@ -0,0 +1,20 @@ +# Normal + +## Table of Contents + +- [Something if](#something-if) + + - [Something else](#something-else) + - [Something elsefi](#something-elsefi) + +- [Something iffi](#something-iffi) + +# Something if + +## Something else + +Text. + +## Something elsefi + +# Something iffi diff --git a/test/fixtures/normal-more-content/input.md b/test/fixtures/normal-more-content/input.md new file mode 100644 index 0000000..7f23660 --- /dev/null +++ b/test/fixtures/normal-more-content/input.md @@ -0,0 +1,17 @@ +# Normal + +## Table of Contents + +Here’s some content. + +Here’s some more content. + +# Something if + +## Something else + +Text. + +## Something elsefi + +# Something iffi diff --git a/test/fixtures/normal-more-content/output.md b/test/fixtures/normal-more-content/output.md new file mode 100644 index 0000000..a86eacf --- /dev/null +++ b/test/fixtures/normal-more-content/output.md @@ -0,0 +1,20 @@ +# Normal + +## Table of Contents + +- [Something if](#something-if) + + - [Something else](#something-else) + - [Something elsefi](#something-elsefi) + +- [Something iffi](#something-iffi) + +# Something if + +## Something else + +Text. + +## Something elsefi + +# Something iffi