HackmdEditor.jsx 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import connectToChild from 'penpal/lib/connectToChild';
  4. import loggerFactory from '~/utils/logger';
  5. const DEBUG_PENPAL = false;
  6. const logger = loggerFactory('growi:HackmdEditor');
  7. export default class HackmdEditor extends React.PureComponent {
  8. constructor(props) {
  9. super(props);
  10. this.hackmd = null;
  11. this.initHackmdWithPenpal = this.initHackmdWithPenpal.bind(this);
  12. this.notifyBodyChangesHandler = this.notifyBodyChangesHandler.bind(this);
  13. this.saveWithShortcutHandler = this.saveWithShortcutHandler.bind(this);
  14. }
  15. componentDidMount() {
  16. // append iframe with penpal
  17. this.initHackmdWithPenpal();
  18. }
  19. async initHackmdWithPenpal() {
  20. // eslint-disable-next-line @typescript-eslint/no-this-alias
  21. const _this = this; // for in methods scope
  22. const iframe = document.createElement('iframe');
  23. iframe.src = `${this.props.hackmdUri}/${this.props.pageIdOnHackmd}?both`;
  24. this.iframeContainer.appendChild(iframe);
  25. const connection = connectToChild({
  26. iframe,
  27. methods: { // expose methods to HackMD
  28. notifyBodyChanges(document) {
  29. _this.notifyBodyChangesHandler(document);
  30. },
  31. saveWithShortcut(document) {
  32. _this.saveWithShortcutHandler(document);
  33. },
  34. },
  35. timeout: 15000,
  36. debug: DEBUG_PENPAL,
  37. });
  38. try {
  39. const child = await connection.promise;
  40. this.hackmd = child;
  41. if (this.props.initializationMarkdown != null) {
  42. child.setValueOnInit(this.props.initializationMarkdown);
  43. }
  44. }
  45. catch (err) {
  46. logger.error(err);
  47. if (this.props.onPenpalErrorOccured != null) {
  48. this.props.onPenpalErrorOccured(err);
  49. }
  50. }
  51. }
  52. /**
  53. * return markdown document of HackMD
  54. * @return {Promise<string>}
  55. */
  56. getValue() {
  57. return this.hackmd.getValue();
  58. }
  59. setValue(newValue) {
  60. this.hackmd.setValue(newValue);
  61. }
  62. notifyBodyChangesHandler(body) {
  63. // dispatch onChange() when there is difference from 'initializationMarkdown' props
  64. if (this.props.onChange != null && body !== this.props.initializationMarkdown) {
  65. this.props.onChange(body);
  66. }
  67. }
  68. saveWithShortcutHandler(document) {
  69. if (this.props.onSaveWithShortcut != null) {
  70. this.props.onSaveWithShortcut(document);
  71. }
  72. }
  73. render() {
  74. return (
  75. // will be rendered in componentDidMount
  76. <div id="iframe-hackmd-container" ref={(c) => { this.iframeContainer = c }}></div>
  77. );
  78. }
  79. }
  80. HackmdEditor.propTypes = {
  81. hackmdUri: PropTypes.string.isRequired,
  82. pageIdOnHackmd: PropTypes.string.isRequired,
  83. initializationMarkdown: PropTypes.string,
  84. onChange: PropTypes.func,
  85. onSaveWithShortcut: PropTypes.func,
  86. onPenpalErrorOccured: PropTypes.func,
  87. };