| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- 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 <React.Fragment>
- <FormControl
- componentClass="textarea" className="textarea-editor"
- inputRef={ref => { this.textarea = ref }}
- defaultValue={this.state.value}
- onChange={(e) => {
- if (this.props.onChange != null) {
- this.props.onChange(e.target.value);
- }
- }} />
- </React.Fragment>;
- }
- }
- TextAreaEditor.propTypes = Object.assign({
- }, AbstractEditor.propTypes);
|