Закрытие тегов <ul/"> в contenteditable без добавления лишних тегов

Работа с неупорядоченным (или упорядоченным) списком в contenteditable дает мне головную боль.

Всякий раз, когда я хочу закончить редактирование списка, нажав ENTER дважды, браузер закроет <ul />, но вставляет тег <p /> (Firefox) или <div /> (Chrome), который содержит <br />.
Пример здесь

Моя цель - избежать лишних <p /> или <div /> и вместо этого просто закрыть <ul />.
Я попытался изменить решение Tim Down, которое не позволит браузеру вставлять <p /> или <div /> при нажатии ENTER и вместо этого вставляет чистый тег <br />.
Пример здесь

К сожалению, при использовании этого решения <ul /> никогда не закрывается браузером, так как только теги <br /> вставлены внутри элемента <li />.

Итак, мой вопрос:

Как я могу активно закрыть <ul />, вставив node или вставляя html при нажатии enter на последнем пустом <li />?

Обновление: Если вопрос неясен: я ищу способ закрыть <ul /> без вставки тегов <p /> или <div />, но просто вставив старую добрую равнину <br /> вместо

Ответ 1

Подобно вашей попытке, мы изменяем контент, когда пользователь нажимает Enter: Демо

if (window.getSelection) { // w3c
    $('div').keypress(function (e) {
        var sel, node, children, br, range;
        if (e.which == 13) {
            sel = window.getSelection();
            node = $(sel.anchorNode);
            children = $(sel.anchorNode.childNodes);

            // if nothing is selected and the caret is in an empty <li>
            // (the browser seems to insert a <br> before we get called)
            if (sel.isCollapsed && node.is('li') && (!children.length ||
                    (children.length == 1 && children.first().is('br')))) {
                e.preventDefault();

                // if the empty <li> is in the middle of the list,
                // move the following <li> to a new list
                if (!node.is(':last-child')) {
                    node.parent().clone(false)
                        .empty()
                        .insertAfter(node.parent())
                        .append(node.nextAll());
                }

                // insert <br> after list
                br = $('<br>').insertAfter(node.parent());

                // move caret to after <br>
                range = document.createRange();
                range.setStartAfter(br.get(0));
                range.setEndAfter(br.get(0));
                sel.removeAllRanges();
                sel.addRange(range);

                // remove <li>
                node.remove();
            }
        }
    });

} else if (document.selection) { // internet explorer
    $('div').keypress(function (e) {
        var range, node, children;
        if (e.which == 13) {
            range = document.selection.createRange();
            node = $(range.parentElement());
            children = $(range.parentElement().childNodes);

            // if nothing is selected and the caret is in an empty <li>
            // (the browser seems to insert a <br> before we get called)
            if (!range.htmlText.length && node.is('li') && (!children.length ||
                    (children.length == 1 && children.first().is('br')))) {
                e.preventDefault();

                // if the empty <li> is in the middle of the list,
                // move the following <li> to a new list
                if (!node.is(':last-child')) {
                    node.parent().clone(false)
                        .empty()
                        .insertAfter(node.parent())
                        .append(node.nextAll());
                }

                // insert <br> after list
                br = $('<br>').insertAfter(node.parent());

                // move caret to after <br>
                range = document.body.createTextRange();
                range.moveToElementText(br.get(0));
                range.collapse(false);
                range.select();

                // remove <li>
                node.remove();
            }
        }
    });
}

Обратите внимание, что это не относится к случаю, когда пользователь выбрал что-то перед нажатием Enter. Если вы хотите обработать этот случай, вам нужно выяснить, выбрал ли пользователь все содержимое <li> (это не похоже на тривиальную задачу), и если это так, удалите содержимое и обработайте его так же, как если бы пользователь нажал Enter в пустом <li>.

Ответ 2

Я понимаю ваш вопрос, но неясно, почему такое требование существует. Это может помочь прояснить этот аспект.

Независимо, вот одна идея. Почему бы не заменить или удалить теги <div> и <p>.

$(document).ready(function(){
    $("div").keyup(function(evt) { 
        $("div, p").filter( function() {
                $this = $(this);
            return !($.trim($(this).text()).length);
        }).replaceWith('<br />');
    });
});

Рабочий пример: http://jsfiddle.net/4xyR2/9/

Одна проблема, которую я замечаю, касается того, как contenteditable влияет на курсор, используя указанное выше решение. Я думаю, что Chrome и Firefox требуют <div> и <p>, чтобы они могли отслеживать, где находится курсор в <div>. Это происходит из моих наблюдений во время тестирования, а не из глубокого понимания того, как браузер интерпретирует contenteditable.

Chrome (24.0.1312.52 m), похоже, не нравится замене. Я вижу странное расположение курсора, когда я его тестирую, но он работает. Firefox (17.0.1) отлично справляется с заменой.

Ответ 3

Лучшее, что я смог тренировать без Javascript, было

HTML:

<div contenteditable="true">
  <div>asdfasdf</div>
</div>

LESS:

div[contenteditable=true] {
  outline: none;

  &>p,
  &>div {
      margin: 0 0 0 30px;
      display: list-item;
  }
}

bin http://jsbin.com/idekix/6/edit

Но здесь есть некоторые недостатки,

  • первый и очень большой FireFox 18.0.1 на Win8 вместо вставки тегов div или p в качестве новой строки использует нечто вроде double br,

  • вторая произошла, когда вы удаляете все и запускаете тип и нажимаете enter, поведение разных браузеров по-разному, Chrome вставляет первую строку в виде простого текстового контейнера node из contenteditable, а затем помещает каждую новую строку в div Opera 12.12 справится с этим лучше, и он никогда не позволяет удалить последний div node, IE9-10, начинающий прыгать, и после того, как вы снова начали использовать теги p вместо div (что даже не такое катастрофа, просто несогласованность), но тем не менее FireFox еще не пытается изменить поведение br. Также пытаясь проверить IE7-8 с режимом отладки IE10, и это то же самое, что и Opera, хотя это не настоящие браузеры.

  • третий, который позволяет вставлять пустые узлы, говоря об этом, хорошая работа, выполняемая Opera, см. в этом бункере в Opera http://jsbin.com/uxesez/1/edit, разметка здесь ближе к оригиналу, и она не позволяет вам вставлять пустой li.

Тем не менее, все тесты проводились на 64-битной платформе Windows 8 с Chrome 24.0.1312.56 m, FireFox 18.0.1, Opera 12.12 и IE10 с эмуляцией старых IE с помощью панели отладки IE. Признайте, что на других платформах/версиях все может быть разным.

Подведите итог выше, contenteditable имеет очень хороший диапазон поддержки браузера http://caniuse.com/contenteditable, но все же у каждого браузера есть собственная реализация, а другая отдельный вопрос почему. Таким образом, без перекрестного вызова JavaScrpt вы не сможете использовать его. Кроме того, как моя личная идея contenteditable Основная идея более подходит, как легкая замена iframe, когда она используется как элемент почти каждого богатого текстового редактора в браузере.

UPD из источника Mozilla https://developer.mozilla.org/en-US/docs/HTML/Content_Editable

В HTML5 любой элемент может быть доступен для редактирования. Эта функция была введена давно, но теперь стандартизована WHATWG (html current spec). С помощью некоторых обработчиков событий JavaScript вы можете преобразовать свою веб-страницу в полнофункциональный богатый текстовый редактор.

Следующий шаг - это просто мое скромное предложение. В вашем случае очень удобно, по моему мнению, использовать реальные редактируемые поля. Очень большая версия прототипа, которую я сделал в этом бункере http://jsbin.com/ojiyok/7/edit, все еще есть вещи для улучшения, но похоже, что он имеет более предсказуемое поведение, чем реальный conteneditables, и здесь вы можете изменить пользовательский интерфейс в соответствии со своими потребностями во всех браузерах одновременно.