Минимальный JavaScript wysiwyg - удалить тег из выделенного текста, если он уже существует
Я делаю минимальный JavaScript WYSIWYG элемент управления.
Я не хочу использовать document.execCommand, потому что он не допускает произвольный HTML, он несовместим во всех браузерах и т. Д.
Вот то, что я до сих пор сократил до минимума рабочего кода:
<button data-action="strong"><strong>b</strong></button>
<button data-action="em"><em>i</em></button>
<button data-action="u"><u>u</u></button>
<p contenteditable>The quick brown fox jumps over the lazy dog.</p>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script>
$( function(){
$( 'button' ).on( 'click', function(){
var selection = window.getSelection();
var range = selection.getRangeAt( 0 );
var action = $( this ).attr( 'data-action' );
var node = document.createElement( action );
var frag = range.extractContents();
node.appendChild( frag );
range.insertNode( node );
return false;
});
});
</script>
Если часть выбора уже содержит сильные теги (или что-то еще), как мне сделать так, чтобы повторное нажатие на кнопку удаляло эти теги вместо того, чтобы обернуть выделение новым сильным тегом?
Написание этого вопроса дало мне идею. Я попробую это сейчас и отвечу на свой вопрос, если он работает - таким образом, этот вопрос здесь, на случай, если кто-то еще столкнется с этим. В противном случае я буду ждать вашей помощи с приманкой:)
РЕДАКТИРОВАТЬ: очевидно, если кто-то еще публикует рабочее решение, я приму их ответ, а не мой, если это будет лучше.
РЕДАКТИРОВАТЬ (2): так что моя идея не оправдалась. Оказывается, что-то (возможно, range.insertNode) волшебным образом уравновесит теги для вас. Кажется, у меня недостаточно информации из выделения, диапазона или фрагмента, чтобы всегда знать, находится ли выделение внутри данного тега. Есть идеи?
1 ответ
РЕДАКТИРОВАТЬ: Это не хорошее решение. Он полностью разбивается на что-то более сложное, чем простая строка простого текста. Я разработал и скоро опубликую лучшее решение.
Догадаться.
Я строю массив, содержащий каждый текстовый узел в области wysiwyg, и список его родительских тегов.
Затем я обернул выделение в пользовательский элемент, чтобы потом его было легко удалить, и чтобы он не конфликтовал с какими-либо существующими элементами HTML, используя префикс x, как рекомендовано.
Затем я перестроил содержимое элемента wysiwyg из этого списка, удалив тег для нажатой кнопки со всех узлов в выделении, если он у всех уже был, именно так его обрабатывают большинство редакторов wywiwyg.
<button data-action="B"><b>b</b></button>
<button data-action="I"><i>i</i></button>
<button data-action="U"><u>u</u></button>
<p contenteditable>The quick brown fox jumps over the lazy dog.</p>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
<script>
$( function(){
var selectionWrapper = 'X-SELECTION';
function getTextData( element ) {
function getTextNodesIn( root ) {
var textNodes = [];
var parents = [];
function getTextNodes( node ) {
if( node.nodeType === 3 ){
var text = node.textContent;
textNodes.push({
text: text,
parents: parents.slice( 0 )
});
} else {
if( node !== root ){
parents.push( node.tagName );
}
for( var i = 0, len = node.childNodes.length; i < len; ++i ){
getTextNodes( node.childNodes[ i ] );
}
parents.pop();
}
}
getTextNodes( element );
return textNodes;
}
return getTextNodesIn( element );
}
function handleSelection( container, action ) {
var textData = getTextData( container );
container.innerHTML = '';
//if every textNode in the selection has action as a parent, we want
//to remove it from all of them.
var selection = _( textData ).filter( function( data ){
return _( data.parents ).contains( selectionWrapper );
});
var remove = _( selection ).every( function( data ) {
return _( data.parents ).contains( action ) || data.text.trim() === '';
});
_( selection ).each( function( data ){
if( remove ) {
data.parents = _( data.parents ).without( action );
} else {
data.parents.push( action );
}
});
//rebuild each text node
_( textData ).each( function( data ){
//no need to add empty text nodes
if( data.text === '' ) {
return;
}
//remove duplicates of the same parent tag and remove the selection wrapper
var parents = _( data.parents ).chain().uniq().without( selectionWrapper ).value();
var target = container;
_( parents ).each( function( parent ){
var node = document.createElement( parent );
target.appendChild( node );
target = node;
});
var text = document.createTextNode( data.text );
target.appendChild( text );
});
}
$( 'button' ).on( 'click', function(){
var action = $( this ).attr( 'data-action' );
var selection = window.getSelection();
for( var i = 0; i < selection.rangeCount; i++ ){
var range = selection.getRangeAt( i );
var node = document.createElement( selectionWrapper );
node.appendChild( range.extractContents() );
range.insertNode( node );
handleSelection( $( 'p' )[ 0 ], action );
}
return false;
});
});
</script>