api.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. 'use strict'
  2. /**
  3. * # API
  4. * @author Ivan Voischev (@voischev),
  5. * Anton Winogradov (@awinogradov),
  6. * Alexej Yaroshevich (@zxqfox),
  7. * Vasiliy (@Yeti-or)
  8. * @module API
  9. * @namespace tree
  10. */
  11. module.exports = {
  12. /**
  13. * walk the tree and pass all nodes to callback
  14. *
  15. * @memberof tree
  16. * @param {Function} cb - Callback
  17. * @return {Function} - Node in callback
  18. *
  19. ***Usage**
  20. * ```js
  21. * export const walk = (tree) => {
  22. * tree.walk((node) => {
  23. * let classes = node.attrs && node.attrs.class.split(' ') || []
  24. *
  25. * if (classes.includes(className)) return cb(node)
  26. * return node
  27. * })
  28. * }
  29. * ```
  30. */
  31. walk: function (cb) {
  32. return traverse(this, cb)
  33. },
  34. /**
  35. * match expression to search nodes in the tree
  36. *
  37. * @memberof tree
  38. * @param {String|RegExp|Object|Array} expression - Matcher(s) to search
  39. * @param {Function} cb - Callback
  40. * @return {Function} - Node in callback
  41. *
  42. ***Usage**
  43. * ```js
  44. * export const match = (tree) => {
  45. * // Single matcher
  46. * tree.match({ tag: 'custom-tag' }, (node) => {
  47. * let tag = node.tag
  48. *
  49. * Object.assign(node, { tag: 'div', attrs: {class: tag} })
  50. *
  51. * return node
  52. * })
  53. * // Multiple matchers
  54. * tree.match([{ tag: 'b' }, { tag: 'strong' }], (node) => {
  55. * let style = 'font-weight: bold;'
  56. *
  57. * node.tag = 'span'
  58. *
  59. * node.attrs
  60. * ? ( node.attrs.style
  61. * ? ( node.attrs.style += style )
  62. * : node.attrs.style = style
  63. * )
  64. * : node.attrs = { style: style }
  65. *
  66. * return node
  67. * })
  68. * }
  69. * ```
  70. */
  71. match: function (expression, cb) {
  72. return Array.isArray(expression)
  73. ? traverse(this, function (node) {
  74. for (var i = 0; i < expression.length; i++) {
  75. if (compare(expression[i], node)) return cb(node)
  76. }
  77. return node
  78. })
  79. : traverse(this, function (node) {
  80. if (compare(expression, node)) return cb(node)
  81. return node
  82. })
  83. }
  84. }
  85. /** @private */
  86. function traverse (tree, cb) {
  87. if (Array.isArray(tree)) {
  88. for (var i = 0; i < tree.length; i++) {
  89. tree[i] = traverse(cb(tree[i]), cb)
  90. }
  91. } else if (
  92. tree &&
  93. typeof tree === 'object' &&
  94. tree.hasOwnProperty('content')
  95. ) traverse(tree.content, cb)
  96. return tree
  97. }
  98. /** @private */
  99. function compare (expected, actual) {
  100. if (expected instanceof RegExp) {
  101. if (typeof actual === 'object') return false
  102. if (typeof actual === 'string') return expected.test(actual)
  103. }
  104. if (typeof expected !== typeof actual) return false
  105. if (typeof expected !== 'object' || expected === null) {
  106. return expected === actual
  107. }
  108. if (Array.isArray(expected)) {
  109. return expected.every(function (exp) {
  110. return [].some.call(actual, function (act) {
  111. return compare(exp, act)
  112. })
  113. })
  114. }
  115. return Object.keys(expected).every(function (key) {
  116. var ao = actual[key]
  117. var eo = expected[key]
  118. if (typeof eo === 'object' && eo !== null && ao !== null) {
  119. return compare(eo, ao)
  120. }
  121. if (typeof eo === 'boolean') {
  122. return eo !== (ao == null)
  123. }
  124. return ao === eo
  125. })
  126. }