HackmdEditor.jsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import React from 'react';
  2. import connectToChild from 'penpal/lib/connectToChild';
  3. import PropTypes from 'prop-types';
  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. const shouldInit = document.getElementById('iframe-hackmd') != null;
  21. if (shouldInit) {
  22. return;
  23. }
  24. // eslint-disable-next-line @typescript-eslint/no-this-alias
  25. const _this = this; // for in methods scope
  26. const iframe = document.createElement('iframe');
  27. iframe.src = `${this.props.hackmdUri}/${this.props.pageIdOnHackmd}?both`;
  28. iframe.id = 'iframe-hackmd';
  29. this.iframeContainer.appendChild(iframe);
  30. const connection = connectToChild({
  31. iframe,
  32. methods: { // expose methods to HackMD
  33. notifyBodyChanges(document) {
  34. _this.notifyBodyChangesHandler(document);
  35. },
  36. saveWithShortcut(document) {
  37. _this.saveWithShortcutHandler(document);
  38. },
  39. },
  40. timeout: 15000,
  41. debug: DEBUG_PENPAL,
  42. });
  43. try {
  44. const child = await connection.promise;
  45. this.hackmd = child;
  46. if (this.props.initializationMarkdown != null) {
  47. child.setValueOnInit(this.props.initializationMarkdown);
  48. }
  49. }
  50. catch (err) {
  51. logger.error(err);
  52. if (this.props.onPenpalErrorOccured != null) {
  53. this.props.onPenpalErrorOccured(err);
  54. }
  55. }
  56. }
  57. /**
  58. * return markdown document of HackMD
  59. * @return {Promise<string>}
  60. */
  61. getValue() {
  62. return this.hackmd.getValue();
  63. }
  64. setValue(newValue) {
  65. this.hackmd.setValue(newValue);
  66. }
  67. notifyBodyChangesHandler(body) {
  68. // dispatch onChange() when there is difference from 'initializationMarkdown' props
  69. if (this.props.onChange != null && body !== this.props.initializationMarkdown) {
  70. this.props.onChange(body);
  71. }
  72. }
  73. saveWithShortcutHandler(document) {
  74. if (this.props.onSaveWithShortcut != null) {
  75. this.props.onSaveWithShortcut(document);
  76. }
  77. }
  78. render() {
  79. return (
  80. // will be rendered in componentDidMount
  81. <div id="iframe-hackmd-container" ref={(c) => { this.iframeContainer = c }}></div>
  82. );
  83. }
  84. }
  85. HackmdEditor.propTypes = {
  86. hackmdUri: PropTypes.string.isRequired,
  87. pageIdOnHackmd: PropTypes.string.isRequired,
  88. initializationMarkdown: PropTypes.string,
  89. onChange: PropTypes.func,
  90. onSaveWithShortcut: PropTypes.func,
  91. onPenpalErrorOccured: PropTypes.func,
  92. };