PageEditor.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import * as toastr from 'toastr';
  4. import Editor from './PageEditor/Editor';
  5. import Preview from './PageEditor/Preview';
  6. export default class PageEditor extends React.Component {
  7. constructor(props) {
  8. super(props);
  9. this.state = {
  10. revisionId: this.props.revisionId,
  11. markdown: this.props.markdown,
  12. };
  13. // initial preview
  14. this.renderPreview();
  15. this.setCaretLine = this.setCaretLine.bind(this);
  16. this.focusToEditor = this.focusToEditor.bind(this);
  17. this.onMarkdownChanged = this.onMarkdownChanged.bind(this);
  18. this.onSave = this.onSave.bind(this);
  19. }
  20. focusToEditor() {
  21. this.refs.editor.forceToFocus();
  22. }
  23. /**
  24. * set caret position of editor
  25. * @param {number} line
  26. */
  27. setCaretLine(line) {
  28. this.refs.editor.setCaretLine(line);
  29. }
  30. /**
  31. * the change event handler for `markdown` state
  32. * @param {string} value
  33. */
  34. onMarkdownChanged(value) {
  35. this.setState({
  36. markdown: value,
  37. html: '',
  38. });
  39. this.renderPreview();
  40. }
  41. /**
  42. * the save event handler
  43. */
  44. onSave() {
  45. let endpoint;
  46. let data;
  47. // update
  48. if (this.props.pageId != null) {
  49. endpoint = '/pages.update';
  50. data = {
  51. page_id: this.props.pageId,
  52. revision_id: this.state.revisionId,
  53. body: this.state.markdown,
  54. };
  55. }
  56. // create
  57. else {
  58. endpoint = '/pages.create';
  59. data = {
  60. path: this.props.pagePath,
  61. body: this.state.markdown,
  62. };
  63. }
  64. this.props.crowi.apiPost(endpoint, data)
  65. .then((res) => {
  66. const page = res.page;
  67. toastr.success(undefined, 'Saved successfully', {
  68. closeButton: true,
  69. progressBar: true,
  70. newestOnTop: false,
  71. showDuration: "100",
  72. hideDuration: "100",
  73. timeOut: "1200",
  74. extendedTimeOut: "150",
  75. });
  76. // update states
  77. this.setState({
  78. revisionId: page.revision._id,
  79. markdown: page.revision.body
  80. })
  81. })
  82. .catch((error) => {
  83. console.error(error);
  84. toastr.error(error.message, 'Error occured on saveing', {
  85. closeButton: true,
  86. progressBar: true,
  87. newestOnTop: false,
  88. showDuration: "100",
  89. hideDuration: "100",
  90. timeOut: "3000",
  91. });
  92. });
  93. }
  94. renderPreview() {
  95. const config = this.props.crowi.config;
  96. // generate options obj
  97. const rendererOptions = {
  98. // see: https://www.npmjs.com/package/marked
  99. marked: {
  100. breaks: config.isEnabledLineBreaks,
  101. }
  102. };
  103. // render html
  104. var context = {
  105. markdown: this.state.markdown,
  106. dom: this.previewElement,
  107. currentPagePath: decodeURIComponent(location.pathname)
  108. };
  109. this.props.crowi.interceptorManager.process('preRenderPreview', context)
  110. .then(() => crowi.interceptorManager.process('prePreProcess', context))
  111. .then(() => {
  112. context.markdown = crowiRenderer.preProcess(context.markdown, context.dom);
  113. })
  114. .then(() => crowi.interceptorManager.process('postPreProcess', context))
  115. .then(() => {
  116. var parsedHTML = crowiRenderer.render(context.markdown, context.dom, rendererOptions);
  117. context['parsedHTML'] = parsedHTML;
  118. })
  119. .then(() => crowi.interceptorManager.process('postRenderPreview', context))
  120. .then(() => crowi.interceptorManager.process('preRenderPreviewHtml', context))
  121. .then(() => {
  122. this.setState({html: context.parsedHTML});
  123. // set html to the hidden input (for submitting to save)
  124. $('#form-body').val(this.state.markdown);
  125. })
  126. // process interceptors for post rendering
  127. .then(() => crowi.interceptorManager.process('postRenderPreviewHtml', context));
  128. }
  129. render() {
  130. return (
  131. <div className="row">
  132. <div className="col-md-6 col-sm-12 page-editor-editor-container">
  133. <Editor ref="editor" value={this.state.markdown}
  134. onChange={this.onMarkdownChanged}
  135. onSave={this.onSave}
  136. />
  137. </div>
  138. <div className="col-md-6 hidden-sm hidden-xs page-editor-preview-container">
  139. <Preview html={this.state.html} inputRef={el => this.previewElement = el} />
  140. </div>
  141. </div>
  142. )
  143. }
  144. }
  145. PageEditor.propTypes = {
  146. crowi: PropTypes.object.isRequired,
  147. pageId: PropTypes.string,
  148. revisionId: PropTypes.string,
  149. pagePath: PropTypes.string,
  150. markdown: PropTypes.string,
  151. };