/app/src/searchable-components/virtual-dom-parser.ts

https://github.com/Foundry376/Mailspring · TypeScript · 168 lines · 148 code · 20 blank · 0 comment · 56 complexity · ed0c7163b6461f906b5d176c6b9d5eb6 MD5 · raw file

  1. import _ from 'underscore';
  2. import React from 'react';
  3. import { VirtualDOMUtils } from 'mailspring-exports';
  4. import SearchMatch from './search-match';
  5. import UnifiedDOMParser from './unified-dom-parser';
  6. export default class VirtualDOMParser extends UnifiedDOMParser {
  7. getWalker(dom): Iterable<HTMLElement> {
  8. const pruneFn = node => {
  9. return node.type === 'style';
  10. };
  11. return VirtualDOMUtils.walk({
  12. element: dom,
  13. pruneFn,
  14. parentNode: undefined,
  15. childOffset: undefined,
  16. });
  17. }
  18. isTextNode({ element }): boolean {
  19. return typeof element === 'string';
  20. }
  21. textNodeLength({ element }) {
  22. return element.length;
  23. }
  24. textNodeContents(textNode) {
  25. return textNode.element;
  26. }
  27. looksLikeBlockElement({ element }) {
  28. if (!element) {
  29. return false;
  30. }
  31. const blockTypes = ['br', 'p', 'blockquote', 'div', 'table', 'iframe'];
  32. if (_.isFunction(element.type)) {
  33. return true;
  34. } else if (blockTypes.indexOf(element.type) >= 0) {
  35. return true;
  36. }
  37. return false;
  38. }
  39. getRawFullString(fullString) {
  40. return _.pluck(fullString, 'element').join('');
  41. }
  42. removeMatchesAndNormalize(element: any) {
  43. let newChildren = [];
  44. let strAccumulator = [];
  45. const resetAccumulator = () => {
  46. if (strAccumulator.length > 0) {
  47. newChildren.push(strAccumulator.join(''));
  48. strAccumulator = [];
  49. }
  50. };
  51. if (React.isValidElement(element) || _.isArray(element)) {
  52. let children;
  53. if (_.isArray(element)) {
  54. children = element;
  55. } else {
  56. children = (element.props as any).children;
  57. }
  58. if (!children) {
  59. newChildren = null;
  60. } else if (React.isValidElement(children)) {
  61. newChildren = children as any;
  62. } else if (typeof children === 'string') {
  63. strAccumulator.push(children);
  64. } else if (children.length > 0) {
  65. for (let i = 0; i < children.length; i++) {
  66. const child = children[i];
  67. if (typeof child === 'string') {
  68. strAccumulator.push(child);
  69. } else if (this._isSearchElement(child)) {
  70. resetAccumulator();
  71. newChildren.push(child.props.children);
  72. } else {
  73. resetAccumulator();
  74. newChildren.push(this.removeMatchesAndNormalize(child));
  75. }
  76. }
  77. } else {
  78. newChildren = children;
  79. }
  80. resetAccumulator();
  81. if (_.isArray(element)) {
  82. return newChildren;
  83. }
  84. return React.cloneElement(element, {}, newChildren);
  85. }
  86. return element;
  87. }
  88. _isSearchElement(element) {
  89. return element.type === SearchMatch;
  90. }
  91. createTextNode({ rawText }) {
  92. return rawText;
  93. }
  94. createMatchNode({ matchText, regionId, isCurrentMatch, renderIndex }) {
  95. const className = isCurrentMatch ? 'current-match' : '';
  96. return React.createElement(SearchMatch, { className, regionId, renderIndex }, matchText);
  97. }
  98. textNodeKey(textElement) {
  99. return textElement.parentNode;
  100. }
  101. highlightSearch(element, matchNodeMap) {
  102. if (React.isValidElement(element) || _.isArray(element)) {
  103. let newChildren = [];
  104. let children;
  105. if (_.isArray(element)) {
  106. children = element;
  107. } else {
  108. children = (element.props as any).children;
  109. }
  110. const matchNode = matchNodeMap.get(element);
  111. let originalTextNode = null;
  112. let newTextNodes = [];
  113. if (matchNode) {
  114. originalTextNode = matchNode.originalTextNode;
  115. newTextNodes = matchNode.newTextNodes;
  116. }
  117. if (!children) {
  118. newChildren = null;
  119. } else if (React.isValidElement(children)) {
  120. if (originalTextNode && originalTextNode.childOffset === 0) {
  121. newChildren = newTextNodes;
  122. } else {
  123. newChildren = this.highlightSearch(children, matchNodeMap);
  124. }
  125. } else if (children instanceof Array && children.length > 0) {
  126. for (let i = 0; i < children.length; i++) {
  127. const child = children[i];
  128. if (originalTextNode && originalTextNode.childOffset === i) {
  129. newChildren.push(newTextNodes);
  130. } else {
  131. newChildren.push(this.highlightSearch(child, matchNodeMap));
  132. }
  133. }
  134. } else {
  135. if (originalTextNode && originalTextNode.childOffset === 0) {
  136. newChildren = newTextNodes;
  137. } else {
  138. newChildren = children;
  139. }
  140. }
  141. if (_.isArray(element)) {
  142. return newChildren;
  143. }
  144. return React.cloneElement(element, {}, newChildren);
  145. }
  146. return element;
  147. }
  148. }