Получите слово, начинающееся со специальных символов в карете в contenteditable div

У меня есть contenteditable div, и мне нужно знать слово в текущей позиции каретки. Я попробовал это решение, но проблема в том, что он не распознает специальные символы, такие как @ и ~. Поэтому, если слово начинается с ~, например ~fool, я получаю fool, тогда как я ожидал ~fool. Поэтому я попытался изменить решение, приняв во внимание, что если после перемещения выделения назад встреченный символ не является пробелом, я продолжу движение назад до тех пор, пока не будет обнаружено место. Это сделало бы начало выбора. Точно так же я продолжал бы двигаться вперед, пока не найду пространство, и это означало бы конец выбора. Тогда выбор дал бы мне слово. Чтобы получить позицию каретки, я использовал это решение. Комбинированный код теперь выглядит следующим образом:

function getCaretPosition(editableDiv) {
  var caretPos = 0,
    sel, range;
  if (window.getSelection) {
    sel = window.getSelection();
    if (sel.rangeCount) {
      range = sel.getRangeAt(0);
      if (range.commonAncestorContainer.parentNode == editableDiv) {
        caretPos = range.endOffset;
      }
    }
  } else if (document.selection && document.selection.createRange) {
    range = document.selection.createRange();
    if (range.parentElement() == editableDiv) {
      var tempEl = document.createElement("span");
      editableDiv.insertBefore(tempEl, editableDiv.firstChild);
      var tempRange = range.duplicate();
      tempRange.moveToElementText(tempEl);
      tempRange.setEndPoint("EndToEnd", range);
      caretPos = tempRange.text.length;
    }
  }
  return caretPos;
}

function getCurrentWord() {
    var sel, word = "";
    if (window.getSelection && (sel = window.getSelection()).modify) {
        var selectedRange = sel.getRangeAt(0);
        sel.collapseToStart();
        sel.modify("move", "backward", "word");

        while (sel.toString() != " " && getCaretPosition($("#editor").get(0)) != 0) {
            sel.modify("move", "backward", "character");
            (sel = window.getSelection()).modify;
        }
        sel.modify("move", "forward", "character");      
        sel.modify("extend", "forward", "word");
        word = sel.toString();

        // Restore selection
        sel.removeAllRanges();
        sel.addRange(selectedRange);
    } else if ((sel = document.selection) && sel.type != "Control") {
        var range = sel.createRange();
        range.collapse(true);
        range.expand("word");
        word = range.text;
    }
    return word;
}

$(function () {

    $(document).on('keyup keydown paste cut mouseup',"#editor", function () {
        var word = getCurrentWord();
        console.log(word);
    });
});

Однако это совсем не работает. Это проблема нет. 1. Проблема 2 есть, даже если есть изображение в pic и пользователь нажимает на pic, обработчик продолжает возвращать последнее слово перед изображением, тогда как я ожидаю пустую строку. Может ли кто-нибудь помочь мне исправить эти два вопроса?

Ответ 1

Я изменил функцию getCurrentWord(), чтобы использовать базовые методы String, чтобы получить слово из позиции каретки. Функция принимает элемент и положение и возвращает слово в этой позиции.

Ниже приведена обновленная функция.

function getCurrentWord(el, position) {
    // Get content of div
    var content = el.textContent;

    // Check if clicked at the end of word
    position = content[position] === ' ' ? position - 1 : position;

    // Get the start and end index
    var startPosition = content.lastIndexOf(' ', position);
    var endPosition = content.indexOf(' ', position);

    // Special cases
    startPosition = startPosition === content.length ? 0 : startPosition;
    endPosition = endPosition === -1 ? content.length : endPosition;

    return content.substring(startPosition + 1, endPosition);
}

Функция сначала получает содержимое элемента. Затем он проверяет, нажал ли пользователь в конце слова, если да, а затем вычтите его из позиции, чтобы indexOf и lastIndexOf корректно работали в пространстве.

В начальных и конечных положениях есть два особых случая, которые необходимо обрабатывать. Сначала нажмите на последний элемент. Для этого startPosition будет -1, так как после последнего слова не может быть пробела.

Во-вторых, при нажатии на первое слово endPosition будет -1, потому что перед первым словом не может быть пробела.

Эти два условия будут работать, даже если перед первым символом и пробелом есть пробел после последнего символа.

indexOf и lastIndexOf используются для поиска пробела до и после слова, и использование этих индексов substring даст слово в этой позиции.

Здесь живая демонстрация для тестирования.

$(function() {
  function getCaretPosition(editableDiv) {
    var caretPos = 0,
      sel, range;
    if (window.getSelection) {
      sel = window.getSelection();
      if (sel.rangeCount) {
        range = sel.getRangeAt(0);
        if (range.commonAncestorContainer.parentNode == editableDiv) {
          caretPos = range.endOffset;
        }
      }
    } else if (document.selection && document.selection.createRange) {
      range = document.selection.createRange();
      if (range.parentElement() == editableDiv) {
        var tempEl = document.createElement("span");
        editableDiv.insertBefore(tempEl, editableDiv.firstChild);
        var tempRange = range.duplicate();
        tempRange.moveToElementText(tempEl);
        tempRange.setEndPoint("EndToEnd", range);
        caretPos = tempRange.text.length;
      }
    }
    return caretPos;
  }

  function getCurrentWord(el, position) {
    var word = '';

    // Get content of div
    var content = el.textContent;

    // Check if clicked at the end of word
    position = content[position] === ' ' ? position - 1 : position;

    // Get the start and end index
    var startPosition = content.lastIndexOf(' ', position);
    startPosition = startPosition === content.length ? 0 : startPosition;
    var endPosition = content.indexOf(' ', position);
    endPosition = endPosition === -1 ? content.length : endPosition;

    return content.substring(startPosition + 1, endPosition);
  }


  $('#editor').on('keyup keydown paste cut mouseup', function() {
    var caretPosition = getCaretPosition(this);
    var word = getCurrentWord(this, caretPosition);
    console.log(word);
  });
});
div {
  font-size: 18px;
  line-height: 1.5em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="editor" contenteditable>



  Lorem !p$um dolor $!t @met, con$ectetur @d!p!$!c!ng el!t, $ed do e!u$mod tempor !nc!d!dunt ut [email protected] et dolore [email protected]@ @[email protected] Ut en!m @d m!n!m [email protected], qu!$ no$trud [email protected]!on [email protected] [email protected]!$ n!$! ut @l!qu!p ex [email protected] commodo [email protected] Du!$ @ute !rure dolor
  !n reprehender!t !n [email protected] vel!t e$$e c!llum dolore eu [email protected] [email protected] [email protected][email protected]
</div>

Ответ 2

Вот пример симпатичных баребон. Там div с примером текста:

<div contenteditable onclick="getCaretCharacterOffsetWithin(this)">some ~test content</div>

Здесь script. Когда мы нажимаем на div, где бы ни находился курсор, мы получаем текущую позицию и выплющаем слово, в котором мы находимся. Это слово может включать специальные символы и обозначается только пробелом.

function getCaretCharacterOffsetWithin(element) {
    var caretOffset = 0;
    var doc = element.ownerDocument || element.document;
    var win = doc.defaultView || doc.parentWindow;
    var sel;
    if (typeof win.getSelection != "undefined") {
        sel = win.getSelection();
        if (sel.rangeCount > 0) {
            var range = win.getSelection().getRangeAt(0);
            var preCaretRange = range.cloneRange();
            preCaretRange.selectNodeContents(element);
            preCaretRange.setEnd(range.endContainer, range.endOffset);
            caretOffset = preCaretRange.toString().length;
        }
    } else if ( (sel = doc.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        var preCaretTextRange = doc.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        caretOffset = preCaretTextRange.text.length;
    }
  console.log('caretOffset', caretOffset);
  word = getWordAtPosition(caretOffset, element);
  console.log('word', word);
    return caretOffset;
}

function getWordAtPosition(position, element) {
  var total_text = element.innerHTML;
  var current_word = "";
  var i = 0;
  var word_found = false;
  while(i < total_text.length) {
    if(total_text[i] != ' ')
      current_word += total_text[i];
    else if(word_found)
      return current_word;
    else
      current_word = "";
    if(i == position)
      word_found = true;
    i++;
  }
  return current_word;
}

Вот пример его работы:

https://codepen.io/anon/pen/dWdyLV