RevisionRenderer.jsx 3.8 KB

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