import React from 'react'; // import PropTypes from 'prop-types'; import FormControl from 'react-bootstrap/es/FormControl'; import InterceptorManager from '@commons/util/interceptor-manager'; import AbstractEditor from './AbstractEditor'; import pasteHelper from './PasteHelper'; import mlu from './MarkdownListUtil'; import PreventMarkdownListInterceptor from './PreventMarkdownListInterceptor'; export default class TextAreaEditor extends AbstractEditor { constructor(props) { super(props); this.logger = require('@alias/logger')('growi:PageEditor:TextAreaEditor'); this.state = { value: this.props.value, isGfmMode: this.props.isGfmMode, }; this.init(); this.handleEnterKey = this.handleEnterKey.bind(this); this.keyPressHandler = this.keyPressHandler.bind(this); this.pasteHandler = this.pasteHandler.bind(this); this.dragEnterHandler = this.dragEnterHandler.bind(this); } init() { this.interceptorManager = new InterceptorManager(); this.interceptorManager.addInterceptors([ new PreventMarkdownListInterceptor(), ]); } componentDidMount() { // initialize caret line this.setCaretLine(0); // set event handlers this.textarea.addEventListener('keypress', this.keyPressHandler); this.textarea.addEventListener('paste', this.pasteHandler); this.textarea.addEventListener('dragenter', this.dragEnterHandler); } /** * @inheritDoc */ forceToFocus() { setTimeout(() => { this.textarea.focus(); }, 150); } /** * @inheritDoc */ setValue(newValue) { this.setState({ value: newValue }); this.textarea.value = newValue; } /** * @inheritDoc */ setGfmMode(bool) { this.setState({ isGfmMode: bool, }); } /** * @inheritDoc */ setCaretLine(line) { if (isNaN(line)) { return; } // scroll to bottom this.textarea.scrollTop = this.textarea.scrollHeight; const lines = this.textarea.value.split('\n').slice(0, line+1); const pos = lines .map(lineStr => lineStr.length + 1) // correct length+1 of each lines .reduce((a, x) => a += x, 0) // sum - 1; // -1 this.textarea.setSelectionRange(pos, pos); } /** * @inheritDoc */ setScrollTopByLine(line) { // do nothing } /** * @inheritDoc */ insertText(text) { const startPos = this.textarea.selectionStart; const endPos = this.textarea.selectionEnd; this.replaceValue(text, startPos, endPos); } /** * @inheritDoc */ getStrFromBol() { const currentPos = this.textarea.selectionStart; return this.textarea.value.substring(this.getBolPos(), currentPos); } /** * @inheritDoc */ getStrToEol() { const currentPos = this.textarea.selectionStart; return this.textarea.value.substring(currentPos, this.getEolPos()); } /** * @inheritDoc */ getStrFromBolToSelectedUpperPos() { const startPos = this.textarea.selectionStart; const endPos = this.textarea.selectionEnd; const upperPos = (startPos < endPos) ? startPos : endPos; return this.textarea.value.substring(this.getBolPos(), upperPos); } /** * @inheritDoc */ replaceBolToCurrentPos(text) { const startPos = this.textarea.selectionStart; const endPos = this.textarea.selectionEnd; const lowerPos = (startPos < endPos) ? endPos : startPos; this.replaceValue(text, this.getBolPos(), lowerPos); } getBolPos() { const currentPos = this.textarea.selectionStart; return this.textarea.value.lastIndexOf('\n', currentPos-1) + 1; } getEolPos() { const currentPos = this.textarea.selectionStart; const pos = this.textarea.value.indexOf('\n', currentPos); if (pos < 0) { // not found but EOF return this.textarea.value.length; } return pos; } replaceValue(text, startPos, endPos) { // create new value const value = this.textarea.value; const newValue = value.substring(0, startPos) + text + value.substring(endPos, value.length); // calculate new position const newPos = startPos + text.length; this.textarea.value = newValue; this.textarea.setSelectionRange(newPos, newPos); } /** * keypress event handler * @param {string} event */ keyPressHandler(event) { const key = event.key.toLowerCase(); if (key === 'enter') { if (event.ctrlKey || event.altKey || event.metaKey) { return; } this.handleEnterKey(event); } } /** * handle ENTER key * @param {string} event */ handleEnterKey(event) { if (!this.state.isGfmMode) { return; // do nothing } const context = { handlers: [], // list of handlers which process enter key editor: this, }; const interceptorManager = this.interceptorManager; interceptorManager.process('preHandleEnter', context) .then(() => { event.preventDefault(); if (context.handlers.length == 0) { mlu.newlineAndIndentContinueMarkdownList(this); } }); } /** * paste event handler * @param {any} event */ pasteHandler(event) { const types = event.clipboardData.types; // text if (types.includes('text/plain')) { pasteHelper.pasteText(this, event); } // files else if (types.includes('Files')) { this.dispatchPasteFiles(event); } } dragEnterHandler(event) { this.dispatchDragEnter(event); } dispatchDragEnter(event) { if (this.props.onDragEnter != null) { this.props.onDragEnter(event); } } render() { return { this.textarea = ref }} defaultValue={this.state.value} onChange={(e) => { if (this.props.onChange != null) { this.props.onChange(e.target.value); } }} /> ; } } TextAreaEditor.propTypes = Object.assign({ }, AbstractEditor.propTypes);