search.js 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. 'use strict'
  2. module.exports = search
  3. var toString = require('mdast-util-to-string')
  4. var visit = require('unist-util-visit')
  5. var is = require('unist-util-is')
  6. var slugs = require('github-slugger')()
  7. var HEADING = 'heading'
  8. // Search a node for a location.
  9. function search(root, expression, settings) {
  10. var length = root.children.length
  11. var depth = null
  12. var lookingForToc = expression !== null
  13. var maxDepth = settings.maxDepth || 6
  14. var parents = settings.parents || root
  15. var map = []
  16. var headingIndex
  17. var closingIndex
  18. if (!lookingForToc) {
  19. headingIndex = -1
  20. }
  21. slugs.reset()
  22. // Visit all headings in `root`. We `slug` all headings (to account for
  23. // duplicates), but only create a TOC from top-level headings.
  24. visit(root, HEADING, onheading)
  25. if (headingIndex && !closingIndex) {
  26. closingIndex = length + 1
  27. }
  28. if (headingIndex === undefined) {
  29. headingIndex = -1
  30. closingIndex = -1
  31. map = []
  32. }
  33. return {index: headingIndex, endIndex: closingIndex, map: map}
  34. function onheading(child, index, parent) {
  35. var value = toString(child)
  36. var id = child.data && child.data.hProperties && child.data.hProperties.id
  37. if (!is(parents, parent)) {
  38. return
  39. }
  40. if (lookingForToc) {
  41. if (isClosingHeading(child, depth)) {
  42. closingIndex = index
  43. lookingForToc = false
  44. }
  45. if (isOpeningHeading(child, depth, expression)) {
  46. headingIndex = index + 1
  47. depth = child.depth
  48. }
  49. }
  50. if (!lookingForToc && value && child.depth <= maxDepth) {
  51. map.push({depth: child.depth, value: value, id: slugs.slug(id || value)})
  52. }
  53. }
  54. }
  55. // Check if `node` is the main heading.
  56. function isOpeningHeading(node, depth, expression) {
  57. return (
  58. depth === null &&
  59. node &&
  60. node.type === HEADING &&
  61. expression.test(toString(node))
  62. )
  63. }
  64. // Check if `node` is the next heading.
  65. function isClosingHeading(node, depth) {
  66. return depth && node && node.type === HEADING && node.depth <= depth
  67. }