UncontrolledCodeMirror.tsx 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import React, {
  2. useCallback, useRef, MutableRefObject,
  3. } from 'react';
  4. import { commands, Editor } from 'codemirror';
  5. import { ICodeMirror, UnControlled as CodeMirror } from 'react-codemirror2';
  6. // set save handler
  7. // CommandActions in @types/codemirror does not include 'save' but actualy exists
  8. // https://codemirror.net/5/doc/manual.html#commands
  9. (commands as any).save = (instance) => {
  10. if (instance.codeMirrorEditor != null) {
  11. instance.codeMirrorEditor.dispatchSave();
  12. }
  13. };
  14. window.CodeMirror = require('codemirror');
  15. require('codemirror/addon/display/placeholder');
  16. require('~/client/util/codemirror/gfm-growi.mode');
  17. export interface UncontrolledCodeMirrorProps extends ICodeMirror {
  18. value: string;
  19. isGfmMode?: boolean;
  20. lineNumbers?: boolean;
  21. onScrollCursorIntoView?: (line: number) => void;
  22. onSave?: () => Promise<void>;
  23. onPasteFiles?: (event: Event) => void;
  24. onCtrlEnter?: (event: Event) => void;
  25. }
  26. export const UncontrolledCodeMirror = React.forwardRef<CodeMirror|null, UncontrolledCodeMirrorProps>((props, forwardedRef): JSX.Element => {
  27. const wrapperRef = useRef<CodeMirror|null>();
  28. const editorRef = useRef<Editor>();
  29. const editorDidMountHandler = useCallback((editor: Editor): void => {
  30. editorRef.current = editor;
  31. }, []);
  32. const editorWillUnmountHandler = useCallback((): void => {
  33. // workaround to fix editor duplicating by https://github.com/scniro/react-codemirror2/issues/284#issuecomment-1155928554
  34. if (editorRef.current != null) {
  35. (editorRef.current as any).display.wrapper.remove();
  36. }
  37. if (wrapperRef.current != null) {
  38. (wrapperRef.current as any).hydrated = false;
  39. }
  40. }, []);
  41. const {
  42. value, lineNumbers, options,
  43. ...rest
  44. } = props;
  45. // default true
  46. const isGfmMode = rest.isGfmMode ?? true;
  47. return (
  48. <CodeMirror
  49. ref={(elem) => {
  50. // register to wrapperRef
  51. wrapperRef.current = elem;
  52. // register to forwardedRef
  53. if (forwardedRef != null) {
  54. if (typeof forwardedRef === 'function') {
  55. forwardedRef(elem);
  56. }
  57. else {
  58. (forwardedRef as MutableRefObject<CodeMirror|null>).current = elem;
  59. }
  60. }
  61. }}
  62. value={value}
  63. options={{
  64. lineNumbers: lineNumbers ?? true,
  65. mode: isGfmMode ? 'gfm-growi' : undefined,
  66. tabSize: 4,
  67. ...options,
  68. }}
  69. editorDidMount={editorDidMountHandler}
  70. editorWillUnmount={editorWillUnmountHandler}
  71. {...rest}
  72. />
  73. );
  74. });
  75. UncontrolledCodeMirror.displayName = 'UncontrolledCodeMirror';