Редактируемый HTML-контент с нередактируемыми островками

У меня есть браузерный WYSIWYG-редактор, в котором пользователи могут редактировать документы-шаблоны.

Шаблон документа представляет собой обычный html с некоторыми специальными «заполнителями кода слияния». Такой шаблон «создается» путем замены этих заполнителей данными, поступающими из БД. Это дает окончательный документ - экземпляр шаблона.

Мой текущий подход выглядит следующим образом:

<div contenteditable>
  Sample template with <input type=button class="mergecode" value="MergeCode1">.
</div>

(Онлайн-пример для игры: http://jsfiddle.net/tFBKN/)

В таком случае ввод не редактируется и ведет себя как сплошной блок - именно то, что мне нужно. Пользователи могут удалять такие коды слияния, нажимая DEL или BACKSPACE в качестве любых других символов и т. д. Имея надлежащий CSS для такого input.mergecode, я могу добиться желаемого внешнего вида.

Но с таким подходом у меня есть три разные проблемы в трех разных UA:

  • IE - CSS { font:inherit } там просто не работает, поэтому если ввод находится внутри <b> как здесь <b><input value="test"></b> он не наследует никакие стили шрифтов.
  • FF — копия фрагмента, содержащего элемент <input>, удаляет этот ввод из содержимого буфера обмена, поэтому дальнейшая операция вставки вставляет все, кроме вводов.
  • GC — Нажмите {BACKSPACE} сразу после того, как <input> выдаст странные результаты (ошибка)

Поэтому я ищу другие идеи о том, как представить нередактируемый встроенный блок, похожий на «острова» в HTML.

Другой подход, который я пробовал до сих пор:

  • <span contenteditable="false">MergeCode1</span> - не работает, так как большинство ПА убирают такой узел из выбора. Таким образом, невозможно, скажем, применить <b> или <i> поверх выделения, содержащего такой диапазон.

Любые другие идеи?


person c-smile    schedule 30.01.2013    source источник
comment
Невозможно. contenteditable влияет на все дочерние элементы. Вы можете использовать JS для замены в случае удаления.   -  person Mooseman    schedule 31.01.2013
comment
@Mooseman Извините, что именно невозможно?   -  person c-smile    schedule 31.01.2013
comment
Комментарий @Mooseman неверен; contenteditable можно переопределить для дочерних элементов, как в примере <span contenteditable="false">MergeCode1</span>. Как объясняется в вопросе, этот естественный подход, однако, плохо работает с выборками (например, тройной щелчок не выделяет весь блок).   -  person Jukka K. Korpela    schedule 31.01.2013
comment
Попробуйте этот код: <div style="background:#000; color:#FFF" contenteditable=true> Hello, world! <span contenteditable="false" style="font-weight:bold">Unedtiable</span> Hello, world! </div> Как видите, span все еще можно удалить. Текст «Нередактируемый» нельзя редактировать, но пользователь может удалить <span> целиком целиком. Инструменты разработчика Chrome показывают, что весь блок был удален из DOM.   -  person Mooseman    schedule 31.01.2013
comment
@Mooseman, как я уже сказал, я уже пробовал contenteditable=false. Это не работает в случае, когда вам нужно применить пролет типа <b> поверх такого острова. Попробуйте следующее: jsfiddle.net/RYsvZ/1, выделите текст с MergeCode1 внутри и нажмите CTRL- Я например. Вы увидите текст, выделенный курсивом, но не MergeCode1. Используйте инспектор DOM, чтобы увидеть проблему.   -  person c-smile    schedule 31.01.2013
comment
Интересно, как применяются <b>. Я не нашел никакой спецификации, относящейся к поведению дочерних элементов, за исключением того, что значение по умолчанию — inherit. Описанное вами поведение, которое я вижу в Chrome, где <span> не выделено курсивом или жирным шрифтом, кажется мне правильным. contenteditable относится к изменению текста, а также к форматированию. JS мог бы помочь здесь, но я предполагаю, что вы не хотите идти туда сейчас.   -  person Mooseman    schedule 31.01.2013
comment
@Mooseman: я не хочу менять содержимое элемента contenteditable=false, я просто хочу обернуть выделение, содержащее его, в диапазон <b>. Chrome предотвращает это по неизвестным причинам.   -  person c-smile    schedule 31.01.2013
comment
Как я это вижу, но не выделяя диапазон жирным шрифтом, Chrome уважает редактируемое содержимое.   -  person Mooseman    schedule 31.01.2013
comment
@c-smile В итоге вы остановились на методе ‹input type=button›? Или вы пошли по пути обработки всех событий вручную? Компромисс здесь кажется неудачным :(   -  person sthomps    schedule 20.10.2013


Ответы (6)


Я разработчик CKEditor. У нас есть некоторый опыт работы с вложенными элементами только для чтения (например, плагин placeholder и функция, над которой мы сейчас работаем #9764), и у меня нет хороших новостей. Вы должны обрабатывать каждое поведение вручную, если хотите иметь согласованный внешний вид. Нет никаких трюков, которые исправят браузеры. И многие вещи (например, странные вещи, происходящие вокруг ввода на GC) кажутся неразрешимыми.

person Reinmar    schedule 04.02.2013
comment
Спасибо за ответ, похоже это не только моя проблема. Этот факт делает кожуру чуть менее горькой, но все же. Тем не менее спасибо за ответ. - person c-smile; 05.02.2013
comment
@Reinmar Поздно на вечеринку, но ... вы нашли какой-нибудь обходной путь для этой проблемы? ваше здоровье! - person pelican_george; 20.06.2016

Еще одна идея, которая выглядит многообещающе:

Чтобы использовать пустой диапазон с ::before { content:"caption"; }, это должно создать нередактируемый блок, представленный в DOM как узел, внутри которого нет позиций каретки.

Вы можете попробовать это здесь http://jsfiddle.net/TwVzt/1/

Но этот подход не свободен от проблем (в моем случае): невозможно объявить ::before встроенным с помощью DOM-атрибута style, поэтому весь набор должен быть объявлен в CSS заранее. Но набор кодов слияния, который у меня есть, довольно велик, и в некоторых случаях он даже неизвестен заранее. Вздох.

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

person c-smile    schedule 02.02.2013
comment
Работал как шарм! Используется data-attributes для динамического содержимого внутри ::before. - person Artyom Neustroev; 30.04.2014
comment
Да, это хорошее решение. Я также нашел эту технику здесь: атрибуты данных HTML5: hacks.mozilla.org/2012/10/ и видео на этой странице: youtube.com/watch?v=On_WyUB1gOk (6 минут) - person Roelof Berkepeis; 02.08.2014
comment
ОГРОМНОЕ СПАСИБО! Этот подход в сочетании с динамическим текстом через атрибут данных устранил для меня серьезное препятствие. - person Floscher; 01.08.2016
comment
Чтобы добавить больше света к этому сообщению и комментариям: <span data-something="awesome"></span> и span:before{content:attr(data-something);} И достаточно просто создать блок <style>...</style> с псевдосодержимым и стилями, сгенерированными динамически с помощью JS, а затем добавить его в заголовок. - person bit-less; 01.11.2017

Я знаю, что это старая тема, с которой я столкнулся с похожей проблемой. Просто нужно несколько не редактируемых диапазонов в редактируемом div. Закончилось внедрением HACK-AROUND, например...

$('#testDiv').on('keydown', function(e) { 
    var targetNode = getSelectionStart();
    if(targetNode != undefined && targetNode.nodeType === 1 && targetNode.nodeName == 'SPAN')    
    {
      var nodeHtmlString = targetNode.outerHTML;

      if(~nodeHtmlString.indexOf("nonEditable"))
      {
        e.preventDefault();
        e.stopPropagation();
      }
    }
});

function getSelectionStart() {
 var node = document.getSelection().anchorNode;
 return (node.nodeType == 3 ? node.parentNode : node);
}

Полная скрипта на https://jsfiddle.net/fxmb3nL6/20/

person Pravin    schedule 29.12.2015

Решение ниже -- с небольшой оговоркой. Но сначала -- вы, ребята, потрясающие!
Я не специалист по JS или HTML, но я также не знал о jsfiddle.net. Итак, читая ваши комментарии, а также другие поисковые запросы в Интернете и некоторые тесты на jsfiddle, я создал следующий код, который работал.

<pre><code>
<div contenteditable="false" class="editor" readonly="true" UNSELECTABLE="ON">
    Sample template with <span class="mergecode test1" />.
    <span contenteditable> editable </span>
    <font color=red><span UNSELECTABLE="ON" contenteditable="false" readonly="true"
     UNSELECTABLE="ON">  uneditable1 </span></font>
    <span contenteditable> editable2 </span>
    <font color=red><span contenteditable=false readonly="true" UNSELECTABLE="ON"> uneditable3</span></font>  
    <span contenteditable> editable4 </span>
    <span contenteditable="false" readonly="true" UNSELECTABLE="ON"> uneditable5 </span>
< /div>
</code></pre>

Если вы поместите это в jsfiddle в chrome, вы увидите, что он работает в chrome (и IE и FF, но немного по-другому), но, похоже, есть небольшое предостережение.

Предостережение заключается в том, что ваша привязка клавиш делает с Backspace! В моем случае, когда я выбираю нередактируемую фразу и нажимаю клавишу Backspace в Chrome, она как будто обновляется или делает «переход на последнюю страницу»! Я сделал первые два нередактируемых элемента красными (последний оставил черным), чтобы вы могли видеть, что происходит. Я тестирую его на jsfiddle в Chrome, в FF и в IE. Все, кажется, действовало правильно, плюс-минус проблема возврата.

И я думаю, что вот логика того, как это работает. Первый Div является родительским элементом верхнего уровня, и он имеет значение contenteditable="false". Так что все под ним должно быть нередактируемым. ЗА ИСКЛЮЧЕНИЕМ дочерних элементов внутри него, у которых есть span contenteditble="true", они становятся редактируемыми. Таким образом, вы наследуете от своего родителя, но потом можете перезаписать как дочерний.

Я также поставил теги readonly="true" UNSELECTABLE="ON", потому что где-то читал о них. И потребовалось больше испытаний!!! ОБРАТИТЕ ВНИМАНИЕ, ПОЖАЛУЙСТА, мне пришлось взять Font... за пределы диапазона, иначе это не сработало бы. Но в IE все работало отлично. Нередактируемые элементы были того цвета, который я им указал, и никакие одиночные, двойные или тройные щелчки не могли выбрать нередактируемые элементы в jsfiddle!!! :-) Для Chrome один щелчок ничего не делал, двойной щелчок выделял нередактируемое, а с возвратом на задний план он немного сошел с ума! Казалось, что пойдет на предыдущую страницу. С ФФ дела обстояли чуть похуже! Двойным щелчком выбрано нередактируемое и редактируемое перед ним! Не знаю, почему.

Если кто-то может выяснить и написать о проблемах с Chrome и FF, это было бы здорово. Было бы неплохо иметь двойной щелчок или любое количество быстрых кликов, чтобы они не работали в Chrome и FF. Опять же, я хочу подчеркнуть диапазон... кажется, он должен быть рядом с нередактируемыми элементами и шрифтом... за пределами диапазона.

Надеюсь, все это имеет смысл.

person user96265    schedule 25.09.2014
comment
Использовал это, чтобы иметь встроенные переменные в текстовом редакторе, так что это полностью работает для меня, спасибо! - person electrikmilk; 27.06.2019

Вот решение, которое я использую в проекте:

<div>
  <span contenteditable>Editable text</span> <span> - Non editable text</span>
</div>

С соответствующим CSS, чтобы увидеть результат:

div {
  border: 1px solid black;
  display: inline-block;
  max-width: 200px;
}

Вот результат: http://www.cssdesk.com/rULJM

person tomfl    schedule 27.10.2019
comment
Да, но вы решаете совсем другую задачу: редактируемые островки в нередактируемом тексте. - person c-smile; 28.10.2019

Для IE попробуйте следующее:

<span contenteditable="true">Uneditable</span>

Обратите внимание на обратное значение атрибута contenteditable! Это противоречит здравому смыслу, но работает — по крайней мере, в IE8 и IE9. Этот нередактируемый элемент можно перемещать и копировать/вставлять. Однако он далек от совершенства. Например. если выделить жирным шрифтом область, охватывающую нередактируемый элемент, это также заразит содержимое нередактируемого элемента. Поэтому вам, вероятно, придется использовать некоторые сценарии, чтобы сохранить работоспособность элемента. Также установка contenteditable="true" украсит нередактируемый элемент маркерами изменения размера. Вы можете снова отключить их, установив unselectable="on", но это предотвратит перетаскивание объекта. Опять же, вам, вероятно, придется полагаться на скрипт, восстанавливающий ширину и высоту.

person meringue    schedule 31.01.2013
comment
К сожалению, этот подход не работает в GC и FF — если вы выберете текст, содержащий такие <span>, и вызовете document.execCommand('bold'), чтобы применить <b> к выделению, диапазон будет исключен из выделения. Вы увидите что-то вроде этого: <b>text before</b><span contenteditable=false>Test</span><b>text after</b>. - person c-smile; 31.01.2013