RevisionRenderer.jsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { withUnstatedContainers } from '../UnstatedUtils';
  4. import AppContainer from '../../services/AppContainer';
  5. import PageContainer from '../../services/PageContainer';
  6. import NavigationContainer from '../../services/NavigationContainer';
  7. import GrowiRenderer from '../../util/GrowiRenderer';
  8. import RevisionBody from './RevisionBody';
  9. class RevisionRenderer extends React.PureComponent {
  10. constructor(props) {
  11. super(props);
  12. this.state = {
  13. html: '',
  14. };
  15. this.renderHtml = this.renderHtml.bind(this);
  16. this.getHighlightedBody = this.getHighlightedBody.bind(this);
  17. }
  18. initCurrentRenderingContext() {
  19. this.currentRenderingContext = {
  20. markdown: this.props.markdown,
  21. currentPagePath: this.props.pageContainer.state.path,
  22. };
  23. }
  24. componentDidMount() {
  25. this.initCurrentRenderingContext();
  26. this.renderHtml();
  27. }
  28. componentDidUpdate(prevProps) {
  29. const { markdown: prevMarkdown, highlightKeywords: prevHighlightKeywords } = prevProps;
  30. const { markdown, highlightKeywords, navigationContainer } = this.props;
  31. // render only when props.markdown is updated
  32. if (markdown !== prevMarkdown || highlightKeywords !== prevHighlightKeywords) {
  33. this.initCurrentRenderingContext();
  34. this.renderHtml();
  35. return;
  36. }
  37. const HeaderLink = document.getElementsByClassName('revision-head-link');
  38. const HeaderLinkArray = Array.from(HeaderLink);
  39. navigationContainer.addSmoothScrollEvent(HeaderLinkArray);
  40. const { interceptorManager } = this.props.appContainer;
  41. interceptorManager.process('postRenderHtml', this.currentRenderingContext);
  42. }
  43. /**
  44. * transplanted from legacy code -- Yuki Takei
  45. * @param {string} body html strings
  46. * @param {string} keywords
  47. */
  48. getHighlightedBody(body, keywords) {
  49. let returnBody = body;
  50. keywords.replace(/"/g, '').split(' ').forEach((keyword) => {
  51. if (keyword === '') {
  52. return;
  53. }
  54. const k = keyword
  55. .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
  56. .replace(/(^"|"$)/g, ''); // for phrase (quoted) keyword
  57. const keywordExp = new RegExp(`(${k}(?!(.*?")))`, 'ig');
  58. returnBody = returnBody.replace(keywordExp, '<em class="highlighted">$&</em>');
  59. });
  60. return returnBody;
  61. }
  62. async renderHtml() {
  63. const {
  64. appContainer, growiRenderer,
  65. highlightKeywords,
  66. } = this.props;
  67. const { interceptorManager } = appContainer;
  68. const context = this.currentRenderingContext;
  69. await interceptorManager.process('preRender', context);
  70. await interceptorManager.process('prePreProcess', context);
  71. context.markdown = growiRenderer.preProcess(context.markdown);
  72. await interceptorManager.process('postPreProcess', context);
  73. context.parsedHTML = growiRenderer.process(context.markdown);
  74. await interceptorManager.process('prePostProcess', context);
  75. context.parsedHTML = growiRenderer.postProcess(context.parsedHTML);
  76. if (this.props.highlightKeywords != null) {
  77. context.parsedHTML = this.getHighlightedBody(context.parsedHTML, highlightKeywords);
  78. }
  79. await interceptorManager.process('postPostProcess', context);
  80. await interceptorManager.process('preRenderHtml', context);
  81. this.setState({ html: context.parsedHTML });
  82. }
  83. render() {
  84. const config = this.props.appContainer.getConfig();
  85. const isMathJaxEnabled = !!config.env.MATHJAX;
  86. return (
  87. <RevisionBody
  88. html={this.state.html}
  89. isMathJaxEnabled={isMathJaxEnabled}
  90. renderMathJaxOnInit
  91. />
  92. );
  93. }
  94. }
  95. /**
  96. * Wrapper component for using unstated
  97. */
  98. const RevisionRendererWrapper = withUnstatedContainers(RevisionRenderer, [AppContainer, PageContainer, NavigationContainer]);
  99. RevisionRenderer.propTypes = {
  100. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  101. pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
  102. navigationContainer: PropTypes.instanceOf(NavigationContainer).isRequired,
  103. growiRenderer: PropTypes.instanceOf(GrowiRenderer).isRequired,
  104. markdown: PropTypes.string.isRequired,
  105. highlightKeywords: PropTypes.string,
  106. };
  107. export default RevisionRendererWrapper;