javascript - 最小的 JavaScript 所见即所得 - 如果已存在,则从所选文本中删除标签

标签 javascript range selection wysiwyg documentfragment

我正在制作一个最小的 JavaScript 所见即所得控件。

我不想使用 document.execCommand,因为它不允许任意 HTML,跨浏览器不一致等等。

这是我迄今为止精简到最少的工作代码:

http://jsfiddle.net/2WxQn/1/

<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)会神奇地为您平衡标签。我似乎没有足够的来自选择、范围或碎片的信息来始终知道选择是否在给定标签内。有什么想法吗?

最佳答案

编辑:这不是一个好的解决方案。它完全分解为比单行简单文本更复杂的任何东西。我已经解决了,很快就会发布更好的解决方案。

想通了。

我构建了一个数组,其中包含所见即所得区域中的每个文本节点及其父标签列表。

然后我将所选内容包装在一个自定义元素中,以便以后轻松删除,并且不与任何现有的 HTML 元素冲突,建议使用 x- 前缀。

然后我从该列表中重建所见即所得元素的内容,从选择的所有节点中删除单击按钮的标签(如果它们都已经有了),这是大多数所见即所得编辑器处理它的方式。

http://jsfiddle.net/x7WRZ/

<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>

关于javascript - 最小的 JavaScript 所见即所得 - 如果已存在,则从所选文本中删除标签,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15965720/

相关文章:

arrays - 在限制元素移动范围的同时打乱列表

ios - 如何在 Objective-C 中检查一个字符串是否包含另一个字符串?

javascript - 如果 jQuery 选择不返回任何元素,则发生全局事件?

javascript - 创建一个 chrome 扩展,它在页面上突出显示文本并将其插入 popup.html 中的文本区域

javascript - javascript 中的 iframe 切换

javascript - 是否可以控制 typed.js 插件何时开始和停止输入文本?

javascript - jqplot 内部 donut 重叠和图例仅显示外部 donut chart

javascript - 在 d3js 中解析日期(以毫秒为单位)

跨浏览器范围的 Javascript 插件

ms-access - Access VBA : How to count items of a ComboBox?