PageEditorByHackmd.jsx 5.8 KB

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