hackmd-agent.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. /**
  2. * GROWI agent for HackMD
  3. *
  4. * This file will be transpiled as a single JS
  5. * and should be load from HackMD head via 'routes/hackmd.js' route
  6. *
  7. * USAGE:
  8. * <script src="${hostname of GROWI}/_hackmd/load-agent"></script>
  9. *
  10. * @author Yuki Takei <yuki@weseek.co.jp>
  11. */
  12. import connectToParent from 'penpal/lib/connectToParent';
  13. import { debounce } from 'throttle-debounce';
  14. const DEBUG_PENPAL = false;
  15. /* eslint-disable no-console */
  16. const allowedOrigin = '<%= origin %>'; // will be replaced by ejs
  17. /**
  18. * return the value of CodeMirror
  19. */
  20. function getValueOfCodemirror() {
  21. // get CodeMirror instance
  22. const editor = window.editor;
  23. return editor.doc.getValue();
  24. }
  25. /**
  26. * set the specified document to CodeMirror
  27. * @param {string} value
  28. */
  29. function setValueToCodemirror(value) {
  30. // get CodeMirror instance
  31. const editor = window.editor;
  32. editor.doc.setValue(value);
  33. }
  34. /**
  35. * set the specified document to CodeMirror on window loaded
  36. * @param {string} value
  37. */
  38. function setValueToCodemirrorOnInit(newValue) {
  39. if (window.cmClient != null) {
  40. setValueToCodemirror(newValue);
  41. return;
  42. }
  43. const intervalId = setInterval(() => {
  44. if (window.cmClient != null) {
  45. clearInterval(intervalId);
  46. setValueToCodemirror(newValue);
  47. }
  48. }, 250);
  49. }
  50. /**
  51. * postMessage to GROWI to notify body changes
  52. * @param {string} body
  53. */
  54. function postParentToNotifyBodyChanges(body) {
  55. window.growi.notifyBodyChanges(body);
  56. }
  57. // generate debounced function
  58. const debouncedPostParentToNotifyBodyChanges = debounce(800, postParentToNotifyBodyChanges);
  59. /**
  60. * postMessage to GROWI to save with shortcut
  61. * @param {string} document
  62. */
  63. function postParentToSaveWithShortcut(document) {
  64. window.growi.saveWithShortcut(document);
  65. }
  66. function addEventListenersToCodemirror() {
  67. // get CodeMirror instance
  68. const codemirror = window.CodeMirror;
  69. // get CodeMirror editor instance
  70. const editor = window.editor;
  71. // e.g. 404 not found
  72. if (codemirror == null || editor == null) {
  73. return;
  74. }
  75. // == change event
  76. editor.on('change', (cm, change) => {
  77. if (change.origin === 'ignoreHistory') {
  78. // do nothing because this operation triggered by other user
  79. return;
  80. }
  81. debouncedPostParentToNotifyBodyChanges(cm.doc.getValue());
  82. });
  83. // == save event
  84. // Reset save commands and Cmd-S/Ctrl-S shortcuts that initialized by HackMD
  85. codemirror.commands.save = function(cm) {
  86. postParentToSaveWithShortcut(cm.doc.getValue());
  87. };
  88. delete editor.options.extraKeys['Cmd-S'];
  89. delete editor.options.extraKeys['Ctrl-S'];
  90. }
  91. function connectToParentWithPenpal() {
  92. const connection = connectToParent({
  93. parentOrigin: allowedOrigin,
  94. // Methods child is exposing to parent
  95. methods: {
  96. getValue() {
  97. return getValueOfCodemirror();
  98. },
  99. setValue(newValue) {
  100. setValueToCodemirror(newValue);
  101. },
  102. setValueOnInit(newValue) {
  103. setValueToCodemirrorOnInit(newValue);
  104. },
  105. },
  106. debug: DEBUG_PENPAL,
  107. });
  108. connection.promise
  109. .then((parent) => {
  110. window.growi = parent;
  111. })
  112. .catch((err) => {
  113. console.log(err);
  114. });
  115. }
  116. /**
  117. * main
  118. */
  119. (function() {
  120. // check HackMD is in iframe
  121. if (window === window.parent) {
  122. console.log('[GROWI] Loading agent for HackMD is not processed because currently not in iframe');
  123. return;
  124. }
  125. console.log('[HackMD] Loading GROWI agent for HackMD...');
  126. window.addEventListener('load', () => {
  127. addEventListenersToCodemirror();
  128. });
  129. connectToParentWithPenpal();
  130. console.log('[HackMD] GROWI agent for HackMD has successfully loaded.');
  131. }());