Editor.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import AbstractEditor from './AbstractEditor';
  4. import CodeMirrorEditor from './CodeMirrorEditor';
  5. import FormControl from 'react-bootstrap/es/FormControl';
  6. import Dropzone from 'react-dropzone';
  7. import pasteHelper from './PasteHelper';
  8. export default class Editor extends AbstractEditor {
  9. constructor(props) {
  10. super(props);
  11. this.state = {
  12. dropzoneActive: false,
  13. isUploading: false,
  14. };
  15. this.dragEnterHandler = this.dragEnterHandler.bind(this);
  16. this.dragLeaveHandler = this.dragLeaveHandler.bind(this);
  17. this.dropHandler = this.dropHandler.bind(this);
  18. this.getDropzoneAccept = this.getDropzoneAccept.bind(this);
  19. this.getDropzoneClassName = this.getDropzoneClassName.bind(this);
  20. this.renderDropzoneOverlay = this.renderDropzoneOverlay.bind(this);
  21. }
  22. componentDidMount() {
  23. // initialize caret line
  24. this.setCaretLine(0);
  25. }
  26. forceToFocus() {
  27. if (this.props.isMobile) {
  28. }
  29. else {
  30. }
  31. }
  32. /**
  33. * set caret position of codemirror
  34. * @param {string} number
  35. */
  36. setCaretLine(line) {
  37. if (this.props.isMobile) {
  38. }
  39. else {
  40. }
  41. }
  42. /**
  43. * @inheritDoc
  44. */
  45. setScrollTopByLine(line) {
  46. if (this.props.isMobile) {
  47. }
  48. else {
  49. }
  50. }
  51. /**
  52. * @inheritDoc
  53. */
  54. insertText(text) {
  55. if (this.props.isMobile) {
  56. }
  57. else {
  58. }
  59. }
  60. /**
  61. * remove overlay and set isUploading to false
  62. */
  63. terminateUploadingState() {
  64. this.setState({
  65. dropzoneActive: false,
  66. isUploading: false,
  67. });
  68. }
  69. /**
  70. * dispatch onUpload event
  71. */
  72. dispatchUpload(files) {
  73. if (this.props.onUpload != null) {
  74. this.props.onUpload(files);
  75. }
  76. }
  77. pasteFilesHandler(event) {
  78. const dropzone = this.refs.dropzone;
  79. const items = event.clipboardData.items || event.clipboardData.files || [];
  80. // abort if length is not 1
  81. if (items.length != 1) {
  82. return;
  83. }
  84. const file = items[0].getAsFile();
  85. // check type and size
  86. if (pasteHelper.fileAccepted(file, dropzone.props.accept) &&
  87. pasteHelper.fileMatchSize(file, dropzone.props.maxSize, dropzone.props.minSize)) {
  88. this.dispatchUpload(file);
  89. this.setState({ isUploading: true });
  90. }
  91. }
  92. dragEnterHandler(event) {
  93. const dataTransfer = event.dataTransfer;
  94. // do nothing if contents is not files
  95. if (!dataTransfer.types.includes('Files')) {
  96. return;
  97. }
  98. this.setState({ dropzoneActive: true });
  99. }
  100. dragLeaveHandler() {
  101. this.setState({ dropzoneActive: false });
  102. }
  103. dropHandler(accepted, rejected) {
  104. // rejected
  105. if (accepted.length != 1) { // length should be 0 or 1 because `multiple={false}` is set
  106. this.setState({ dropzoneActive: false });
  107. return;
  108. }
  109. const file = accepted[0];
  110. this.dispatchUpload(file);
  111. this.setState({ isUploading: true });
  112. }
  113. getDropzoneAccept() {
  114. let accept = 'null'; // reject all
  115. if (this.props.isUploadable) {
  116. if (!this.props.isUploadableFile) {
  117. accept = 'image/*'; // image only
  118. }
  119. else {
  120. accept = ''; // allow all
  121. }
  122. }
  123. return accept;
  124. }
  125. getDropzoneClassName() {
  126. let className = 'dropzone';
  127. if (!this.props.isUploadable) {
  128. className += ' dropzone-unuploadable';
  129. }
  130. else {
  131. className += ' dropzone-uploadable';
  132. if (this.props.isUploadableFile) {
  133. className += ' dropzone-uploadablefile';
  134. }
  135. }
  136. // uploading
  137. if (this.state.isUploading) {
  138. className += ' dropzone-uploading';
  139. }
  140. return className;
  141. }
  142. getOverlayStyle() {
  143. return {
  144. position: 'absolute',
  145. zIndex: 4, // forward than .CodeMirror-gutters
  146. top: 0,
  147. right: 0,
  148. bottom: 0,
  149. left: 0,
  150. };
  151. }
  152. renderDropzoneOverlay() {
  153. const overlayStyle = this.getOverlayStyle();
  154. return (
  155. <div style={overlayStyle} className="overlay">
  156. {this.state.isUploading &&
  157. <span className="overlay-content">
  158. <div className="speeding-wheel d-inline-block"></div>
  159. <span className="sr-only">Uploading...</span>
  160. </span>
  161. }
  162. {!this.state.isUploading && <span className="overlay-content"></span>}
  163. </div>
  164. );
  165. }
  166. render() {
  167. const flexContainer = {
  168. height: '100%',
  169. display: 'flex',
  170. flexDirection: 'column',
  171. };
  172. const isMobile = this.props.isMobile;
  173. return <React.Fragment>
  174. <div style={flexContainer}>
  175. <Dropzone
  176. ref="dropzone"
  177. disableClick
  178. disablePreview={true}
  179. accept={this.getDropzoneAccept()}
  180. className={this.getDropzoneClassName()}
  181. acceptClassName="dropzone-accepted"
  182. rejectClassName="dropzone-rejected"
  183. multiple={false}
  184. onDragLeave={this.dragLeaveHandler}
  185. onDrop={this.dropHandler}
  186. >
  187. { this.state.dropzoneActive && this.renderDropzoneOverlay() }
  188. {/* for PC */}
  189. { !isMobile &&
  190. <CodeMirrorEditor
  191. onPasteFiles={this.pasteFilesHandler}
  192. onDragEnter={this.dragEnterHandler}
  193. {...this.props}
  194. />
  195. }
  196. {/* for mobile */}
  197. { isMobile &&
  198. <FormControl componentClass="textarea" className="textarea-for-mobile"
  199. inputRef={ref => { this.mobileeditor = ref }}
  200. defaultValue={this.state.value}
  201. onChange={(e) => {
  202. if (this.props.onChange != null) {
  203. this.props.onChange(e.target.value);
  204. }
  205. }} />
  206. }
  207. </Dropzone>
  208. <button type="button" className="btn btn-default btn-block btn-open-dropzone"
  209. onClick={() => {this.refs.dropzone.open()}}>
  210. <i className="icon-paper-clip" aria-hidden="true"></i>&nbsp;
  211. Attach files by dragging &amp; dropping,&nbsp;
  212. <span className="btn-link">selecting them</span>,&nbsp;
  213. or pasting from the clipboard.
  214. </button>
  215. </div>
  216. </React.Fragment>;
  217. }
  218. }
  219. Editor.propTypes = Object.assign({
  220. isMobile: PropTypes.bool,
  221. isUploadable: PropTypes.bool,
  222. isUploadableFile: PropTypes.bool,
  223. emojiStrategy: PropTypes.object,
  224. onChange: PropTypes.func,
  225. onUpload: PropTypes.func,
  226. }, AbstractEditor.propTypes);