Draft.jsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import React, { Fragment } from 'react';
  2. import PropTypes from 'prop-types';
  3. import { withTranslation } from 'react-i18next';
  4. import GrowiRenderer from '../../util/GrowiRenderer';
  5. import RevisionBody from '../Page/RevisionBody';
  6. class Draft extends React.Component {
  7. constructor(props) {
  8. super(props);
  9. this.state = {
  10. html: '',
  11. isOpen: false,
  12. };
  13. this.growiRenderer = new GrowiRenderer(this.props.crowi, this.props.crowiOriginRenderer, { mode: 'draft' });
  14. this.renderHtml = this.renderHtml.bind(this);
  15. this.toggleContent = this.toggleContent.bind(this);
  16. this.copyMarkdownToClipboard = this.copyMarkdownToClipboard.bind(this);
  17. this.renderAccordionTitle = this.renderAccordionTitle.bind(this);
  18. }
  19. copyMarkdownToClipboard() {
  20. navigator.clipboard.writeText(this.props.markdown);
  21. }
  22. async toggleContent(e) {
  23. const target = e.currentTarget.getAttribute('data-target');
  24. if (!this.state.html) {
  25. await this.renderHtml();
  26. }
  27. if (this.state.isOpen) {
  28. $(target).collapse('hide');
  29. this.setState({ isOpen: false });
  30. }
  31. else {
  32. $(target).collapse('show');
  33. this.setState({ isOpen: true });
  34. }
  35. }
  36. async renderHtml() {
  37. const context = {
  38. markdown: this.props.markdown,
  39. };
  40. const growiRenderer = this.growiRenderer;
  41. const interceptorManager = this.props.crowi.interceptorManager;
  42. await interceptorManager.process('prePreProcess', context)
  43. .then(() => {
  44. context.markdown = growiRenderer.preProcess(context.markdown);
  45. })
  46. .then(() => { return interceptorManager.process('postPreProcess', context) })
  47. .then(() => {
  48. const parsedHTML = growiRenderer.process(context.markdown);
  49. context.parsedHTML = parsedHTML;
  50. })
  51. .then(() => { return interceptorManager.process('prePostProcess', context) })
  52. .then(() => {
  53. context.parsedHTML = growiRenderer.postProcess(context.parsedHTML);
  54. })
  55. .then(() => { return interceptorManager.process('postPostProcess', context) })
  56. .then(() => {
  57. this.setState({ html: context.parsedHTML });
  58. });
  59. }
  60. renderAccordionTitle(isExist) {
  61. if (isExist) {
  62. return (
  63. <Fragment>
  64. <span>{this.props.path}</span>
  65. <span className="mx-2">({this.props.t('page exists')})</span>
  66. </Fragment>
  67. );
  68. }
  69. return (
  70. <Fragment>
  71. <a href={`${this.props.path}#edit`} target="_blank" rel="noopener noreferrer">{this.props.path}</a>
  72. <span className="mx-2">
  73. <span className="label-draft label label-default">draft</span>
  74. </span>
  75. </Fragment>
  76. );
  77. }
  78. render() {
  79. const { t } = this.props;
  80. const id = this.props.path.replace('/', '-');
  81. return (
  82. <div className="timeline-body">
  83. <div className="panel panel-timeline">
  84. <div className="panel-heading d-flex justify-content-between">
  85. <div className="panel-title" onClick={this.toggleContent} data-target={`#${id}`}>
  86. {this.renderAccordionTitle(this.props.isExist)}
  87. </div>
  88. <div>
  89. {this.props.isExist
  90. ? null
  91. : (
  92. <a
  93. href={`${this.props.path}#edit`}
  94. target="_blank"
  95. rel="noopener noreferrer"
  96. className="draft-edit"
  97. data-toggle="tooltip"
  98. data-placement="bottom"
  99. title={this.props.t('Edit')}
  100. >
  101. <i className="icon-note" />
  102. </a>
  103. )
  104. }
  105. <a
  106. className="draft-copy"
  107. data-toggle="tooltip"
  108. data-placement="bottom"
  109. title={this.props.t('Copy')}
  110. onClick={this.copyMarkdownToClipboard}
  111. >
  112. <i className="icon-doc" />
  113. </a>
  114. <a
  115. className="text-danger draft-delete"
  116. data-toggle="tooltip"
  117. data-placement="top"
  118. title={t('Delete')}
  119. onClick={() => { return this.props.clearDraft(this.props.path) }}
  120. >
  121. <i className="icon-trash" />
  122. </a>
  123. </div>
  124. </div>
  125. <div className="panel-body collapse" id={id} aria-labelledby={id} data-parent="#draft-list">
  126. <div className="revision-body wiki">
  127. <RevisionBody html={this.state.html} />
  128. </div>
  129. </div>
  130. </div>
  131. </div>
  132. );
  133. }
  134. }
  135. Draft.propTypes = {
  136. t: PropTypes.func.isRequired,
  137. crowi: PropTypes.object.isRequired,
  138. crowiOriginRenderer: PropTypes.object.isRequired,
  139. path: PropTypes.string.isRequired,
  140. markdown: PropTypes.string.isRequired,
  141. isExist: PropTypes.bool.isRequired,
  142. clearDraft: PropTypes.func.isRequired,
  143. };
  144. export default withTranslation()(Draft);