рд╣рдордиреЗ рдЕрдкрдиреЗ рд╕рдВрджреЗрд╢рд╡рд╛рд╣рдХ (Gem4me) рдореЗрдВ рдирдП рд╕рдВрджреЗрд╢ рджрд░реНрдЬ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХреНрд╖реЗрддреНрд░ рдХрд╛ рд╡рд┐рдХрд╛рд╕ рдХреИрд╕реЗ рдХрд┐рдпрд╛

рд╕рднреА рдХреЛ рдирдорд╕реНрдХрд╛рд░!тАи


рдореЗрд░рд╛ рдирд╛рдо рдЕрд▓реЗрдХреНрдЬреЗрдВрдбрд░ рдмрд╛рд▓реНрдЯрд┐рд╡рд┐рдЪ рд╣реИ, рдореИрдВ Gem4me рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рд╡реЗрдм-рдЯреАрдо рдХреЗ рдиреЗрддреГрддреНрд╡ рдХреА рд╕реНрдерд┐рддрд┐ рдкрд░ рдХрд╛рдо рдХрд░рддрд╛ рд╣реВрдВред рдкрд░рд┐рдпреЛрдЬрдирд╛ рд╣рд░ рдХрд┐рд╕реА рдФрд░ рд╕рднреА рдХреЗ рд▓рд┐рдП рдПрдХ рдЕрднрд┐рдирд╡ рд╕рдВрджреЗрд╢рд╡рд╛рд╣рдХ рд╣реИ (рдЕрдм рддрдХ рдореЗрд░реА рдХрд▓реНрдкрдирд╛рдУрдВ рдореЗрдВ, рд▓реЗрдХрд┐рди рд╣рдо рдЗрд╕рдХреЗ рд▓рд┐рдП рдкреНрд░рдпрд╛рд╕ рдХрд░рддреЗ рд╣реИрдВ; ;-))


рд╡реЗрдм рд╕рдВрд╕реНрдХрд░рдг рд╕реНрдЯреИрдХ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╕рдВрдХреНрд╖реЗрдк рдореЗрдВ: ReactJS (рдЬреЛ рдЗрд╕ рдкрд░ рд╕рдВрджреЗрд╣ рдХрд░реЗрдЧрд╛) + mobX (рд╡реНрдпрдХреНрддрд┐рдЧрдд рд░реВрдк рд╕реЗ, рдореИрдВ рдмрд┐рд▓реНрдХреБрд▓ рдЙрддреНрд╕рд╛рд╣реА рдирд╣реАрдВ рд╣реВрдВ, рд▓реЗрдХрд┐рди рд╣рдо рдХрд╣реАрдВ рднреА рдорд╛рдЗрдЧреНрд░реЗрдЯ рдХрд░рдиреЗ рдХреА рдпреЛрдЬрдирд╛ рдирд╣реАрдВ рдмрдирд╛рддреЗ рд╣реИрдВ; рдпрджрд┐ рдЖрдк рд╡рд┐рд╡рд░рдг рдореЗрдВ рд░реБрдЪрд┐ рд░рдЦрддреЗ рд╣реИрдВ, рддреЛ рдЖрдк рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдХреНрдпрд╛ рд╕реЛрдЪрддреЗ рд╣реИрдВ, рдЖрдк рдЯрд┐рдкреНрдкрдгреА рдирд╣реАрдВ рд▓рд┐рдЦрддреЗ рд╣реИрдВ - рд╢рд╛рдпрдж рдореИрдВ рдЗрд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдПрдХ рдЕрд▓рдЧ рд▓реЗрдЦ рд▓рд┐рдЦреВрдВрдЧрд╛) + рд╕реНрдЯреЛрд░реАрдмреБрдХ + wdio (рд╕реНрдХреНрд░реАрдирд╢реЙрдЯ рдкрд░реАрдХреНрд╖рдг)ред


рдореИрд╕реЗрдВрдЬрд░ рдореБрдЦреНрдп рд░реВрдк рд╕реЗ рдЫреЛрдЯреЗ рд╕рдВрджреЗрд╢реЛрдВ (рдФрд░ рдлрд┐рд░ рдЗрди рд╕рднреА рдХреЙрд▓, рд╕реНрдЯрд┐рдХрд░ рдФрд░ рдЕрдиреНрдп рд╕рдореНрдореЗрд▓рдиреЛрдВ) рдХреЗ рдЖрджрд╛рди-рдкреНрд░рджрд╛рди рдХреЗ рд▓рд┐рдП рдПрдХ рдХрд╛рд░реНрдпрдХреНрд░рдо рд╣реИред рд╕рдВрджреЗрд╢ рдмреБрдирд┐рдпрд╛рджреА рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рд╣реИрдВ рдЬрд┐рдиреНрд╣реЗрдВ рдЕрдЪреНрдЫреА рддрд░рд╣ рд╕реЗ рдХрд╛рдо рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдПред рдирд╣реАрдВ, рдРрд╕рд╛ рдирд╣реАрдВ рд╣реИ: рдЗрд╕реЗ рдкреВрд░реА рддрд░рд╣ рд╕реЗ рдХрд╛рдо рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдПред рдФрд░ рдХреЗрд╡рд▓ рдПрдХ рдЬрд┐рд╕рдиреЗ рдХрдо рд╕реЗ рдХрдо рдПрдХ рдмрд╛рд░ рдЗрд╕ рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдХреЗ рд╡рд┐рдХрд╛рд╕ рдХрд╛ рд╕рд╛рдордирд╛ рдХрд┐рдпрд╛, рд╡рд╣ рд╕рднреА рджрд░реНрдж рдЬрд╛рдирддрд╛ рд╣реИ рдЬрд┐рд╕реЗ рджреВрд░ рдХрд░рдирд╛ рд╣реИ рддрд╛рдХрд┐ рд╕рдм рдХреБрдЫ рд╕реБрдВрджрд░ рджрд┐рдЦреЗред рд╣рдо рд╕рднреА рдЗрд╕ рдЯреАрдо рдХреЗ рд╕рд╛рде рдЧрдПред рдФрд░ рд╣рдордиреЗ рд╕рд╛рдЭрд╛ рдХрд░рдиреЗ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛ред




рдЖрд╡рд╢реНрдпрдХрддрд╛рдПрдБ


рдЗрдирдкреБрдЯ рдлрд╝реАрд▓реНрдб рдореЗрдВ рдХреНрдпрд╛ рд╕реБрд╡рд┐рдзрд╛рдПрдБ рдкреНрд░рджрд╛рди рдХрд░рдиреА рдЪрд╛рд╣рд┐рдП?


  • рдпрд╣ рдЯреЗрдХреНрд╕реНрдЯ рдХреЛ рдкреНрд░рд┐рдВрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЯреНрд░рд╛рдЗрдЯ рд╣реИ, рдЬрд┐рд╕рдореЗрдВ рдХрдИ рд▓рд╛рдЗрдиреЛрдВ рдореЗрдВ рд▓рд┐рдЦрдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рд╣реИ (рд╢рд┐рдлреНрдЯ + рдПрдВрдЯрд░ рджрдмрд╛рдХрд░)ред рд╕рдВрджреЗрд╢ рдХреЛ рдЬрд▓реНрджреА рд╕реЗ рдкреНрд░рд╛рд░реВрдкрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрдиреНрдирдд рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рдорд╛рд░реНрдХрдбрд╛рдЙрди рдЕрдХреНрд╖рд░реЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рднреА рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред



  • рдкрд╛рда рдмрдлрд░ рд╕реЗ рдЪрд┐рдкрдХрд╛рдПрдБред рдЕрдХреНрд╕рд░ рдкрд╛рда рдХреА рдкреНрд░рддрд┐рд▓рд┐рдкрд┐ рдмрдирд╛рддреЗ рд╕рдордп, рдЗрд╕ рдкрд╛рда рдХреА рдкреГрд╖реНрдарднреВрдорд┐ рдХреЛ рднреА рдХреЙрдкреА рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ (рдиреАрдЪреЗ рдЙрджрд╛рд╣рд░рдг рджреЗрдЦреЗрдВ) рдФрд░ рд╕реНрд╡рд░реВрдкрдгред рд▓реЗрдХрд┐рди рд╕реНрд╡рдЪреНрдЫ, рд╕рд╛рдл-рд╕реБрдерд░рд╛ рдкрд╛рда рдФрд░ рдЕрдзрд┐рдХ рдХреБрдЫ рднреА рдЗрдирдкреБрдЯ рдХреНрд╖реЗрддреНрд░ рдореЗрдВ рдирд╣реАрдВ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдПред



  • рдХреНрд▓рд┐рдкрдмреЛрд░реНрдб рд╕реЗ рдЪрд┐рддреНрд░ рдбрд╛рд▓реЗрдВред рдпрд╣ рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдХреЛрдИ рднреА рдЗрд╕ рд╕реБрд╡рд┐рдзрд╛ рдХреЗ рдмрд┐рдирд╛ рдирд╣реАрдВ рд░рд╣ рд╕рдХрддрд╛ рд╣реИред
  • рд╕реНрдорд╛рдЗрд▓реА =) рд▓рдВрдмреЗ рд╕рдордп рд╕реЗ рд╕рдВрдЪрд╛рд░ рдХреА рд╕рдВрд╕реНрдХреГрддрд┐ рдХрд╛ рдПрдХ рд╣рд┐рд╕реНрд╕рд╛ рдмрди рдЧрдпрд╛ рд╣реИред рдЗрд╕рд▓рд┐рдП, рдЖрдк рдореБрд╕реНрдХреБрд░рд╛рд╣рдЯ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдирд╣реАрдВ рднреВрд▓ рд╕рдХрддреЗред рдкрд╛рда рдХреА рд╢реБрд░реБрдЖрдд рдореЗрдВ, рдмреАрдЪ рдореЗрдВ, рдПрдХ рдкрдВрдХреНрддрд┐ рдореЗрдВ рджрд╕ рдЯреБрдХрдбрд╝реЗ рдпрд╛ рдПрдХ рд╕рдордп рдореЗрдВ рдЖрдВрддрд░рд┐рдХ рдкреБрд╕реНрддрдХрд╛рд▓рдп рд╕реЗ рдбрд╛рд▓реЗ рдЧрдП рддреАрди рд╢рдмреНрджреЛрдВ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдпрд╛ рдХрд┐рд╕реА рдЕрдиреНрдп рд╕рдВрд╕рд╛рдзрди рд╕реЗ рдХреЙрдкреА рдХрд┐рдП рдЧрдП - рдХрд┐рд╕реА рднреА рд░реВрдкрд╛рдВрддрд░ рдФрд░ рднрд┐рдиреНрдирддрд╛ рдореЗрдВ, рд╣рдореЗрд╢рд╛ рдкреНрд▓рд╕ рдХреЗ рд╕рд╛рде рдкрд╛рдВрдЪ рдореЗрдВ рдЦреАрдВрдЪрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдПред
  • , , ( ). тАФ , , . , , . Esc ( ).



" " ("mention") , . :


  • тАЬ@тАЬ тАЬ тАЭ. , :



  • "@" ("@T" => "T");
  • тАЬ тАЭ , , "@Tes", ("@Te|s"), . ;
  • , " @ ?", "@" ("| @ ?") "@" (" @ | ?"), тАЬ тАЭ ;
  • , (" @ ?|") тАФ , (" @| ?") тАФ , "@" ;
  • , ("@| "), " " . тАФ , . , , (тАЬ@ | тАЭ).

, .



, , , тАФ :


  • , , . , , .
  • . , - тАФ ( ), тАФ - . , . , .

, , тАФ , .., , , .


!


HTML


, HTML . , , , , , , input, textarea , .


input , , .


textarea тАФ " ", . , (. ). input, shift + Enter . 5 , . textarea , , .






, textarea, , , div. , , , ! contenteditable.


The contenteditable global attribute is an enumerated attribute indicating if the element should be editable by the user. If so, the browser modifies its widget to allow editing. (MDN)

, , .


:


<div
  className={styles.input}
  placeholder="Type a message"
  contentEditable
/>


, тАФ , . input onChange. div onInput, , . addEventListener , . :


class Input extends Component {
    setRef = (ref) => {
        this.ref = ref;
    };
    saveInputValue = () => {
        const text = this.ref.innerText;
        this.props.onChange(text);
    };
    componentDidMount() {
        this.ref.addEventListener('input', this.saveInputValue);
    }
    componentWillUnmount() {
        this.ref.removeEventListener('input', this.saveInputValue);
    }
    render() {
        return (
            <div
                className={styles.input}
                ref={this.setRef}
                placeholder="Type a message"
                contentEditable
            />
        );
    }
}

input ref innerText . div uncontrolled, .. , .



тАФ . , Enter. Enter . . input keydown. :


onKeyDown = (event) => {
  if (event.keyCode === ENTER_KEY_CODE && event.shiftKey === false) {
    event.stopPropagation();
    event.preventDefault();

    this.props.submit();
  }
};
componentDidMount() {
  this.ref.addEventListener('keydown', this.onKeyDown);
  ...
}

event.preventDefault() Enter, event.stopPropogation() . , Shift+Enter .


-


тАФ - . - . paste.


handlePaste = (event) => {
  event.preventDefault();

  const text = event.clipboardData.getData('text/plain');
  document.execCommand('insertText', false, text);
};
componentDidMount() {
  ...
  this.ref.addEventListener('paste', this.handlePaste);
}

event.preventDefault() , ( event.clipboardData === DataTransfer). , тАФ . document.execCommand('insertText', false, text). , .. , .


. . fileList, тАФ , , :


handlePaste = (event) => {
  event.preventDefault();

  if (event.clipboardData.files.length > 0) {
    // ...
  }

  const text = event.clipboardData.getData('text/plain');
  document.execCommand('insertText', false, text);
};

, .. , . .


Emoji


emoji . unicode emoji emoji . Emoji тАФ , тАФ execComand(тАШinsertTextтАЩ). , , . ! , Emoji, , .тАитАи


, , this.ref.focus() , :


insertEmoji = (emoji) => {
  if (this.ref) {
    this.ref.focus();
  }
  document.execCommand('insertText', false, emoji);
}

, : , Emoji, тАФ . , - . , Emoji , , . .


, , .



API тАФ Selection (MDN).


A Selection object represents the range of text selected by the user or the current position of the caret. To obtain a Selection object for examination or manipulation, call window.getSelection().

window.getSelection() , , , . , , тАФ . API . , . :




gem4me, "m" "e". selection selection.anchorNode ( ) selection.anchorOffset (5). , selection.anchorNode , , , . , , тАФ . :


updateCaretPosition = () => {
  const { node, cursorPosition } = this.state;

  const selection = window.getSelection();
  const newNode = selection.anchorNode;
  const newCursorPosition = selection.anchorOffset;

  if ( node === newNode && cursorPosition === newCursorPosition) {
    return;
  }

  this.setState({ node, cursorPosition });
}

, . , :


тАФ onInput, , . paste , .


onInput. , тАФ . тАФ , keyup ( keydown тАФ , , ). , 2 ( , input keyup). state, . , .. , =).


. , ? click .


. -!


Emoji


, , Emoji. API Range, Selection.


The Range interface represents a fragment of a document that can contain nodes and parts of text nodes

, . :


const { node, cursorPosition } = this.state;

const range = document.createRange();
range.setStart(node, cursorPosition);
range.setEnd(node, cursorPosition); 

тАи , . тАФ . :


const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);

selection, , . this.ref.focus() ! , , :


document.execCommand('insertText', false, emoji);

:


customFocus = () => {
  const { node, cursorPosition } = this.state;

  const range = document.createRange();
  range.setStart(node, cursorPosition);
  range.setEnd(node, cursorPosition);

  const selection = window.getSelection();
  selection.removeAllRanges();
  selection.addRange(range);
}

insertEmoji = (emoji) => {
  this.customFocus();
  document.execCommand('insertText', false, emoji);
};


, , . :


, , ( ). тАФ , , . , , . Esc ( ).

, . keydown. , . :


if (
  event.keyCode === UP_ARROW_KEY_CODE &&
  this.props.isTextEmpty &&
  this.props.mode === INPUT_CONTEXT_TYPES.STANDARD
) {
  тАж      
}

, , . :


if (
  event.keyCode === UP_ARROW_KEY_CODE &&
  this.props.isTextEmpty &&
  this.props.mode === INPUT_CONTEXT_TYPES.STANDARD
) {
  event.preventDefault();
  event.stopPropagation();

  this.props.setMode('edit');
  document.execCommand('insertText', false, this.props.lastMessage); 
}

document.execCommand('insertText') , .


Esc . keydown . this.ref.textContent = "".



, , . тАФ , .. , . , "@". updateCaretPosition:


updateAndProcessCaretPosition = () => {
  const { node, cursorPosition } = this.state;

  const selection = window.getSelection();
  const newNode = selection.anchorNode;
  const newCursorPosition = selection.anchorOffset;

  if (node === newNode && cursorPosition === newCursorPosition) {
    return;
  }

  if (this.props.isAvailableMention) {
    this.props.parseMention(node.textContent, cursorPosition);
  };

  this.setState({ node, cursorPosition });
}

isAvailableMention , . , true. :


parseMention = (text, cursorPosition) => {
  if (text && cursorPosition && text.includes('@')) {
    const lastWord = text
      .substring(0, cursorPosition)
      .split(' ')
      .pop();

    if (lastWord[0] === '@') {
      this.setFilter(lastWord.substring(1, cursorPosition));
      return;
    }
  }
  this.clearFilter();
};

, "@" . тАФ , , "@", . , .


insertMention = (insertRestPieceOfMention) => {
  this.customFocus();
  document.execCommand('insertText', false, insertRestPieceOfMention + ' ');
};

, document.execCommand('insertText') : , , .



, , , , , 20+ , , . , - . тАФ , , (, , , , , - ), , , , . тАФ , .


, Gem4me


All Articles