كيف طورنا المجال لإدخال رسائل جديدة في رسولنا (Gem4me)

تحية للجميع!



اسمي ألكسندر بالتسيفيتش ، أعمل على المركز القيادي لفريق الويب لمشروع Gem4me. المشروع هو رسول مبتكر للجميع والجميع (حتى الآن في خيالي ، لكننا نسعى جاهدين لهذا ؛-))


باختصار حول حزمة إصدار الويب: ReactJS (الذي يشك في ذلك) + mobX (شخصيًا ، أنا لست متحمسًا على الإطلاق ، لكننا لا نخطط للهجرة إلى أي مكان ؛ إذا كنت مهتمًا بالتفاصيل ، فما الذي لا يناسبك بالضبط ، اكتب التعليقات - ربما سأفعل مقالًا منفصلاً عنها) + قصص + wdio (اختبار لقطة شاشة).


برنامج المراسلة هو في الأساس برنامج لتبادل الرسائل القصيرة (وعندها فقط كل هذه المكالمات والملصقات والمؤتمرات الأخرى). الرسائل وظائف أساسية يجب أن تعمل بشكل جيد. لا ، ليس كذلك: يجب أن يعمل بشكل مثالي. ولا يعرف سوى الشخص الذي واجه مرة واحدة على الأقل تطوير هذه الوظيفة كل الألم الذي يجب التغلب عليه حتى يبدو كل شيء جميلًا. لقد مررنا جميعًا بهذا مع الفريق. وقررنا المشاركة.




المتطلبات


ما الميزات التي يجب أن يوفرها حقل الإدخال؟


  • من السهل طباعة النص ، مع القدرة على الكتابة في عدة أسطر (بالضغط على shift + Enter). يتم استخدام المستخدمين المتقدمين أيضًا لاستخدام أحرف تخفيض السعر لتنسيق الرسائل بسرعة.



  • قم بلصق النص من المخزن المؤقت. في كثير من الأحيان عند نسخ النص ، يتم نسخ الخلفية تحت هذا النص أيضًا (انظر المثال أدناه) والتنسيق. لكن النص النظيف والأنيق ولا شيء آخر يجب أن يدخل مجال الإدخال.



  • إدراج صور من الحافظة. يبدو لي أنه لا يمكن لأحد العيش بدون هذه الميزة.
  • الابتسامات =) أصبحت لفترة طويلة نوعًا من ثقافة التواصل. لذلك ، لا يمكنك نسيان الابتسامات. في بداية النص ، في المنتصف ، عشر قطع متتالية أو واحدة في كل مرة من خلال ثلاث كلمات مدرجة من المكتبة الداخلية أو نسخها من مصدر آخر - في أي اختلافات واختلافات ، يجب رسمها دائمًا في خمس مع إضافة.
  • , , ( ). — , , . , , . 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