Удостоверение орфографии

Название немного ошибочно; мой инструмент проверки орфографии больше фокусируется на формате, чем написание (шапки, знаки препинания и пробелы, апострофы, конвертирование интернет-сленга в полные слова, встроенные слова и т.д.). Однако применяются основные принципы.

В принципе, строчка JS/jQuery, которую я создаю, будет исправлять слова по мере их ввода (после пробела или знаков препинания после слова).

Однако, как и любая автокоррекция, она неизбежно сталкивается с ошибками. Я даже не рассматриваю возможность создания функциональности, которая бы определяла, является ли "ее" или "она" более подходящей в данном случае (хотя, если такой плагин или фрагмент кода существует, укажите мне один).

Поэтому я хочу сделать его "уступающим" автокорректом (из-за отсутствия знания лучшего имени). В основном,

  • Пользователь вводит слово в слово, которое будет вызывать проверку, и пространство.
  • Контроллер исправляет слово.
  • Пользователь считает это ошибкой и исправляет его назад (путем возврата всего слова или его частей, или выделяя его, или, тем не менее, им удобно редактировать его).
  • пользователь продолжает печатать, а контрольная панель не затрагивает этот экземпляр этого слова.

Теперь проще всего было бы полностью отключить проверку этого слова, но я хочу, чтобы контролер исправлял будущие экземпляры. То, что я ищу, обнаружит пользователя, редактирующего автокорректированное слово (независимо от того, правильно ли оно было после ввода или позже), до того, как оно было до автокоррекции, а затем научится оставлять этот конкретный экземпляр только этого слова.

Я даже не знаю, с чего начать. Я думаю, что контент с учетом каждого слова, заключенного в промежутке, автокорректированные, имеющие специальный класс, и атрибут data- *, содержащий исходный, прослушивают редактирование на автокорректированных словах, и если он отредактирован обратно, чтобы сравнять данные - значение, добавьте класс, который оставит его в будущих раундах автокоррекции.

Я думаю, что это может быть излишне сложным или, по крайней мере, не наименьшим сопротивлением. Каким будет самый умный способ сделать это?

Ответ 1

Ваш предложенный подход (разделение каждого слова в span и сохранение дополнительных данных в нем) на первый взгляд представляется наиболее разумным подходом. На уровне редактора вам просто нужно обеспечить, чтобы весь текст находился внутри некоторого span, и каждый из них содержит только одно слово (при необходимости разбивая его). На уровне слов просто слушайте изменения в span (привязка input и propertyChange) и действуйте согласно его классу/данным.

Однако реальная боль заключается в том, чтобы сохранить положение каретки согласованным. Когда вы меняете содержимое либо textarea, либо элемент с contentEditable, каретка движется довольно непредсказуемо, и нет простого (кросс-браузерного) способа отслеживания каретки. Я искал решения как здесь, так и в другом месте, и простейшее рабочее решение, которое я нашел, было в этом сообщении в блоге. К сожалению, он применяется только к textarea, поэтому решение "каждое слово в промежутке" не может быть использовано.

Итак, я предлагаю следующий подход:

  • Сохраняйте список слов в Array, где каждое слово хранит как текущее значение, так и оригинал;
  • Когда содержимое textarea изменяется, сохраните набор неизменных слов и повторите остальные;
  • Используйте только проверку орфографии, если каретка сразу после символа без слова (комната для улучшения), и вы не нажимаете backspace;
  • Если пользователь был недоволен коррекцией, нажатие backspace однажды отменит его, и он не будет снова проверен , если не будет изменен.
    • Если было сделано много исправлений сразу (например, если много текста было скопировано), каждый backspace отменит одну коррекцию до тех пор, пока никто не останется.
    • Нажатие любого другого ключа приведет к исправлению, поэтому, если пользователь все еще не удовлетворен, ему придется вернуться и снова изменить его.
    • Примечание: в отличие от требований OP измененная версия будет снова автокорректирована, если пользователь вводит символ без слова; ему нужно нажать backspace один раз, чтобы "защитить" его.

Я создал простую концептуальную концепцию в jsFiddle. Подробности ниже. Обратите внимание, что вы можете комбинировать его с другими подходами (например, обнаруживать клавишу "стрелка вниз" и отображать меню с некоторыми параметрами автоматической коррекции) и т.д.


Шаги доказательств концепции подробно объясняются:

  • Сохраняйте список слов в Array, где каждое слово хранит как текущее значение, так и оригинал;

    var words = [];
    

    Это регулярное выражение разбивает текст на слова (каждое слово имеет свойство word и sp, последнее хранит немедленные символы без слов)

    delimiter:/^(\w+)(\W+)(.*)$/,
    ...
    regexSplit:function(regex,text) {
        var ret = [];
        for ( var match = regex.exec(text) ; match ; match = regex.exec(text) ) {
            ret.push({
                word:match[1],
                sp:match[2],
                length:match[1].length + match[2].length
            });
            text = match[3];
        }
        if ( text )
            ret.push({word:text, sp:'', length:text.length});
         return ret;
    }
    
  • Когда содержимое textarea изменяется, сохраняйте набор неизменных слов и повторите остальные;

        // Split all the text
        var split = $.autocorrect.regexSplit(options.delimiter, $this.val());
        // Find unchanged words in the beginning of the field
        var start = 0;
        while ( start < words.length && start < split.length ) {
            if ( !words[start].equals(split[start]) )
                break;
            start++;
        }
        // Find unchanged words in the end of the field
        var end = 0;
        while ( 0 < words.length - end && 0 < split.length - end ) {
            if ( !words[words.length-end-1].equals(split[split.length-end-1]) ||
                 words.length-end-1 < start )
                break;
            end++;
        }
        // Autocorrects words in-between
        var toSplice = [start, words.length-end - start];
        for ( var i = start ; i < split.length-end ; i++ )
            toSplice.push({
                word:check(split[i], i),
                sp:split[i].sp,
                original:split[i].word,
                equals:function(w) {
                    return this.word == w.word && this.sp == w.sp;
                }
            });
        words.splice.apply(words, toSplice);
        // Updates the text, preserving the caret position
        updateText();
    
  • Используйте только проверку орфографии, если каретка сразу после символа без слова (комната для улучшения), и вы не нажимаете backspace;

    var caret = doGetCaretPosition(this);
    var atFirstSpace = caret >= 2 &&
                       /\w\W/.test($this.val().substring(caret-2,caret));
    function check(word, index) {
        var w = (atFirstSpace && !backtracking ) ?
                options.checker(word.word) :
                word.word;
        if ( w != word.word )
            stack.push(index); // stack stores a list of auto-corrections
        return w;
    }
    
  • Если пользователь был неудовлетворен коррекцией, нажатие backspace однажды отменит его, и он не будет снова проверен , если не изменено.

    $(this).keydown(function(e) {
        if ( e.which == 8 ) {
            if ( stack.length > 0 ) {
                var last = stack.pop();
                words[last].word = words[last].original;
                updateText(last);
                return false;
            }
            else
                backtracking = true;
            stack = [];
        }
    });
    
  • Код для updateText просто снова соединяет все слова в строку и возвращает значение обратно в textarea. Каретка сохраняется, если ничего не было изменено или не было размещено сразу после последней выполненной/отмененной автокоррекции, для учета изменений длины текста:

    function updateText(undone) {
        var caret = doGetCaretPosition(element);
        var text = "";
        for ( var i = 0 ; i < words.length ; i++ )
            text += words[i].word + words[i].sp;
        $this.val(text);
        // If a word was autocorrected, put the caret right after it
        if ( stack.length > 0 || undone !== undefined ) {
            var last = undone !== undefined ? undone : stack[stack.length-1];
            caret = 0;
            for ( var i = 0 ; i < last ; i++ )
                caret += words[i].word.length + words[i].sp.length;
            caret += words[last].word.length + 1;
        }
        setCaretPosition(element,caret);
    }
    
  • Окончательная структура плагина:

    $.fn.autocorrect = function(options) {
        options = $.extend({
            delimiter:/^(\w+)(\W+)(.*)$/,
            checker:function(x) { return x; }
        }, options);
        return this.each(function() {
            var element = this, $this = $(this);
            var words = [];
            var stack = [];
            var backtracking = false;
            function updateText(undone) { ... }
            $this.bind("input propertyChange", function() {
                stack = [];
                // * Only apply the spell check if the caret...
                // * When the contents of the `textarea` changes...
                backtracking = false;
            });
            // * If the user was unsatisfied with the correction...
        });
    };
    $.autocorrect = {
        regexSplit:function(regex,text) { ... }
    };
    

Ответ 2

Предполагая, что вы только отправляете слово слева от каретки, не могли ли вы отключить проверку орфографии до тех пор, пока не будет напечатан символ пробела или перемещен курсор в текстовом поле?

Я не уверен, что этот ответ вам нужен.