PageEditorByHackmd.jsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import * as toastr from 'toastr';
  4. import HackmdEditor from './PageEditorByHackmd/HackmdEditor';
  5. export default class PageEditorByHackmd extends React.PureComponent {
  6. constructor(props) {
  7. super(props);
  8. this.state = {
  9. markdown: this.props.markdown,
  10. isInitialized: false,
  11. isInitializing: false,
  12. revisionId: this.props.revisionId,
  13. pageIdOnHackmd: this.props.pageIdOnHackmd,
  14. };
  15. this.getHackmdUri = this.getHackmdUri.bind(this);
  16. this.startToEdit = this.startToEdit.bind(this);
  17. this.resumeToEdit = this.resumeToEdit.bind(this);
  18. this.hackmdEditorChangeHandler = this.hackmdEditorChangeHandler.bind(this);
  19. this.apiErrorHandler = this.apiErrorHandler.bind(this);
  20. }
  21. componentWillMount() {
  22. }
  23. /**
  24. * return markdown document of HackMD
  25. * @return {Promise<string>}
  26. */
  27. getMarkdown() {
  28. if (!this.state.isInitialized) {
  29. return Promise.reject(new Error('HackmdEditor component has not initialized'));
  30. }
  31. return this.refs.hackmdEditor.getValue()
  32. .then(document => {
  33. this.setState({ markdown: document });
  34. return document;
  35. });
  36. }
  37. setMarkdown(markdown) {
  38. this.setState({ markdown });
  39. if (this.state.isInitialized) {
  40. this.refs.hackmdEditor.setValue(markdown);
  41. }
  42. }
  43. /**
  44. * update revisionId of state
  45. * @param {string} revisionId
  46. */
  47. setRevisionId(revisionId) {
  48. this.setState({revisionId});
  49. }
  50. getHackmdUri() {
  51. const envVars = this.props.crowi.config.env;
  52. return envVars.HACKMD_URI;
  53. }
  54. /**
  55. * Start integration with HackMD
  56. */
  57. startToEdit() {
  58. const hackmdUri = this.getHackmdUri();
  59. if (hackmdUri == null) {
  60. // do nothing
  61. return;
  62. }
  63. this.setState({
  64. isInitialized: false,
  65. isInitializing: true,
  66. });
  67. const params = {
  68. pageId: this.props.pageId,
  69. };
  70. this.props.crowi.apiPost('/hackmd.integrate', params)
  71. .then(res => {
  72. if (!res.ok) {
  73. throw new Error(res.error);
  74. }
  75. this.setState({
  76. isInitialized: true,
  77. pageIdOnHackmd: res.pageIdOnHackmd,
  78. revisionIdHackmdSynced: res.revisionIdHackmdSynced,
  79. });
  80. })
  81. .catch(this.apiErrorHandler)
  82. .then(() => {
  83. this.setState({isInitializing: false});
  84. });
  85. }
  86. /**
  87. * Start to edit w/o any api request
  88. */
  89. resumeToEdit() {
  90. this.setState({isInitialized: true});
  91. }
  92. /**
  93. * onChange event of HackmdEditor handler
  94. */
  95. hackmdEditorChangeHandler(body) {
  96. const hackmdUri = this.getHackmdUri();
  97. if (hackmdUri == null) {
  98. // do nothing
  99. return;
  100. }
  101. // do nothing if contents are same
  102. if (this.props.markdown === body) {
  103. return;
  104. }
  105. const params = {
  106. pageId: this.props.pageId,
  107. };
  108. this.props.crowi.apiPost('/hackmd.saveOnHackmd', params)
  109. .then(res => {
  110. // do nothing
  111. })
  112. .catch(err => {
  113. // do nothing
  114. });
  115. }
  116. apiErrorHandler(error) {
  117. toastr.error(error.message, 'Error occured', {
  118. closeButton: true,
  119. progressBar: true,
  120. newestOnTop: false,
  121. showDuration: '100',
  122. hideDuration: '100',
  123. timeOut: '3000',
  124. });
  125. }
  126. render() {
  127. const hackmdUri = this.getHackmdUri();
  128. const isPageExistsOnHackmd = (this.state.pageIdOnHackmd != null);
  129. const isRevisionMatch = (this.state.revisionId === this.props.revisionIdHackmdSynced);
  130. const isResume = isPageExistsOnHackmd && isRevisionMatch && this.props.hasDraftOnHackmd;
  131. if (this.state.isInitialized) {
  132. return (
  133. <HackmdEditor
  134. ref='hackmdEditor'
  135. hackmdUri={hackmdUri}
  136. pageIdOnHackmd={this.state.pageIdOnHackmd}
  137. initializationMarkdown={isResume ? null : this.state.markdown}
  138. onChange={this.hackmdEditorChangeHandler}
  139. onSaveWithShortcut={(document) => {
  140. this.props.onSaveWithShortcut(document);
  141. }}
  142. >
  143. </HackmdEditor>
  144. );
  145. }
  146. let content = undefined;
  147. // HackMD is not setup
  148. if (hackmdUri == null) {
  149. content = (
  150. <div>
  151. <p className="text-center hackmd-status-label"><i className="fa fa-file-text"></i> HackMD is not set up.</p>
  152. </div>
  153. );
  154. }
  155. else if (isResume) {
  156. content = (
  157. <div>
  158. <p className="text-center hackmd-status-label"><i className="fa fa-file-text"></i> HackMD is READY!</p>
  159. <p className="text-center">
  160. <button className="btn btn-success btn-lg waves-effect waves-light" type="button"
  161. onClick={() => this.resumeToEdit()}>
  162. <span className="btn-label"><i className="icon-control-end"></i></span>
  163. Resume to edit with HackMD
  164. </button>
  165. </p>
  166. <p className="text-center">Click to edit from the previous continuation.</p>
  167. </div>
  168. );
  169. }
  170. else {
  171. content = (
  172. <div>
  173. <p className="text-center hackmd-status-label"><i className="fa fa-file-text"></i> HackMD is READY!</p>
  174. <p className="text-center">
  175. <button className="btn btn-info btn-lg waves-effect waves-light" type="button"
  176. onClick={() => this.startToEdit()} disabled={this.state.isInitializing}>
  177. <span className="btn-label"><i className="icon-paper-plane"></i></span>
  178. Start to edit with HackMD
  179. </button>
  180. </p>
  181. <p className="text-center">Click to clone page content and start to edit.</p>
  182. </div>
  183. );
  184. }
  185. return (
  186. <div className="hackmd-preinit d-flex justify-content-center align-items-center">
  187. {content}
  188. </div>
  189. );
  190. }
  191. }
  192. PageEditorByHackmd.propTypes = {
  193. crowi: PropTypes.object.isRequired,
  194. markdown: PropTypes.string.isRequired,
  195. onSaveWithShortcut: PropTypes.func.isRequired,
  196. pageId: PropTypes.string,
  197. revisionId: PropTypes.string,
  198. pageIdOnHackmd: PropTypes.string,
  199. revisionIdHackmdSynced: PropTypes.string,
  200. hasDraftOnHackmd: PropTypes.bool,
  201. };