Draft.jsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import React, { Fragment } from 'react';
  2. import PropTypes from 'prop-types';
  3. import { withTranslation } from 'react-i18next';
  4. import { CopyToClipboard } from 'react-copy-to-clipboard';
  5. import Panel from 'react-bootstrap/es/Panel';
  6. import Tooltip from 'react-bootstrap/es/Tooltip';
  7. import OverlayTrigger from 'react-bootstrap/es/OverlayTrigger';
  8. import { createSubscribedElement } from '../UnstatedUtils';
  9. import AppContainer from '../../services/AppContainer';
  10. import RevisionBody from '../Page/RevisionBody';
  11. class Draft extends React.Component {
  12. constructor(props) {
  13. super(props);
  14. this.state = {
  15. html: '',
  16. isRendered: false,
  17. isPanelExpanded: false,
  18. showCopiedMessage: false,
  19. };
  20. this.growiRenderer = this.props.appContainer.getRenderer('draft');
  21. this.changeToolTipLabel = this.changeToolTipLabel.bind(this);
  22. this.expandPanelHandler = this.expandPanelHandler.bind(this);
  23. this.collapsePanelHandler = this.collapsePanelHandler.bind(this);
  24. this.renderHtml = this.renderHtml.bind(this);
  25. this.renderAccordionTitle = this.renderAccordionTitle.bind(this);
  26. }
  27. changeToolTipLabel() {
  28. this.setState({ showCopiedMessage: true });
  29. setTimeout(() => {
  30. this.setState({ showCopiedMessage: false });
  31. }, 1000);
  32. }
  33. expandPanelHandler() {
  34. this.setState({ isPanelExpanded: true });
  35. if (!this.state.isRendered) {
  36. this.renderHtml();
  37. }
  38. }
  39. collapsePanelHandler() {
  40. this.setState({ isPanelExpanded: false });
  41. }
  42. async renderHtml() {
  43. const context = {
  44. markdown: this.props.markdown,
  45. };
  46. const growiRenderer = this.growiRenderer;
  47. const interceptorManager = this.props.appContainer.interceptorManager;
  48. await interceptorManager.process('prePreProcess', context)
  49. .then(() => {
  50. context.markdown = growiRenderer.preProcess(context.markdown);
  51. })
  52. .then(() => { return interceptorManager.process('postPreProcess', context) })
  53. .then(() => {
  54. const parsedHTML = growiRenderer.process(context.markdown);
  55. context.parsedHTML = parsedHTML;
  56. })
  57. .then(() => { return interceptorManager.process('prePostProcess', context) })
  58. .then(() => {
  59. context.parsedHTML = growiRenderer.postProcess(context.parsedHTML);
  60. })
  61. .then(() => { return interceptorManager.process('postPostProcess', context) })
  62. .then(() => {
  63. this.setState({ html: context.parsedHTML, isRendered: true });
  64. });
  65. }
  66. renderAccordionTitle(isExist) {
  67. const iconClass = this.state.isPanelExpanded ? 'caret-opened' : '';
  68. return (
  69. <Fragment>
  70. <i className={`caret ${iconClass}`}></i>
  71. <span className="mx-2">{this.props.path}</span>
  72. { isExist && (
  73. <span>({this.props.t('page exists')})</span>
  74. ) }
  75. { !isExist && (
  76. <span className="label-draft label label-default">draft</span>
  77. ) }
  78. </Fragment>
  79. );
  80. }
  81. render() {
  82. const { t } = this.props;
  83. const copyButtonTooltip = (
  84. <Tooltip id="draft-copied-tooltip">
  85. { this.state.showCopiedMessage && (
  86. <strong>copied!</strong>
  87. ) }
  88. { !this.state.showCopiedMessage && (
  89. <span>{this.props.t('Copy')}</span>
  90. ) }
  91. </Tooltip>
  92. );
  93. return (
  94. <div className="draft-list-item">
  95. <Panel>
  96. <Panel.Heading className="d-flex">
  97. <Panel.Toggle>
  98. {this.renderAccordionTitle(this.props.isExist)}
  99. </Panel.Toggle>
  100. <a href={this.props.path}><i className="icon icon-login"></i></a>
  101. <div className="flex-grow-1"></div>
  102. <div className="icon-container">
  103. {this.props.isExist
  104. ? null
  105. : (
  106. <a
  107. href={`${this.props.path}#edit`}
  108. target="_blank"
  109. rel="noopener noreferrer"
  110. data-toggle="tooltip"
  111. title={this.props.t('Edit')}
  112. >
  113. <i className="mx-2 icon-note" />
  114. </a>
  115. )
  116. }
  117. <OverlayTrigger overlay={copyButtonTooltip} placement="top">
  118. <CopyToClipboard text={this.props.markdown} onCopy={this.changeToolTipLabel}>
  119. <a
  120. className="text-center draft-copy"
  121. >
  122. <i className="mx-2 ti-clipboard" />
  123. </a>
  124. </CopyToClipboard>
  125. </OverlayTrigger>
  126. <a
  127. className="text-danger text-center"
  128. data-toggle="tooltip"
  129. data-placement="top"
  130. title={t('Delete')}
  131. onClick={() => { return this.props.clearDraft(this.props.path) }}
  132. >
  133. <i className="mx-2 icon-trash" />
  134. </a>
  135. </div>
  136. </Panel.Heading>
  137. <Panel.Collapse onEnter={this.expandPanelHandler} onExit={this.collapsePanelHandler}>
  138. <Panel.Body>
  139. {/* loading spinner */}
  140. { this.state.isPanelExpanded && !this.state.isRendered && (
  141. <div className="text-center">
  142. <i className="fa fa-lg fa-spinner fa-pulse mx-auto text-muted"></i>
  143. </div>
  144. ) }
  145. {/* contents */}
  146. { this.state.isPanelExpanded && this.state.isRendered && (
  147. <RevisionBody html={this.state.html} />
  148. ) }
  149. </Panel.Body>
  150. </Panel.Collapse>
  151. </Panel>
  152. </div>
  153. );
  154. }
  155. }
  156. /**
  157. * Wrapper component for using unstated
  158. */
  159. const DraftWrapper = (props) => {
  160. return createSubscribedElement(Draft, props, [AppContainer]);
  161. };
  162. Draft.propTypes = {
  163. t: PropTypes.func.isRequired,
  164. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  165. path: PropTypes.string.isRequired,
  166. markdown: PropTypes.string.isRequired,
  167. isExist: PropTypes.bool.isRequired,
  168. clearDraft: PropTypes.func.isRequired,
  169. };
  170. export default withTranslation()(DraftWrapper);