Squire编辑器源码解读

《Squire编辑器源码解读》

编辑器简单介绍

Squire is an HTML5 rich text editor, which provides powerful cross-browser normalisation, whilst being supremely lightweight and flexible. It is built for the present and the future, and as such does not support truly ancient browsers. It should work fine back to around Opera 12, Firefox 3.5, Safari 5, Chrome 9 and IE9.

各文件的功能

  • Editor.js:用构造函数定义了编辑器,并且在原型上增加属性,以实现更多的编辑器操作

    • function Squire ( root, config ) {}
    • var proto = Squire.prototype;
  • clean.js:定义了编辑支持的各种元素,包括tag、属性、style。

    • 对标签,属性,样式多统一化处理
    • 去除白名单外的标签,属性和样式
  • clipboard.js:定义了复制、剪切、粘贴、拖拽操作的drop。所有的编辑器定义的粘贴操作都类似,基本步骤如下:

    • 先从系统剪贴板获取数据;
    • 根据不同浏览器以及数据类型,将数据插入到对应的位置
    • 阻止默认事件
  • Constants.js:定义了编辑器的全局配置,以及一些缩写变量。通常包含以下内容:

    • 给生涩变量取别名

      var ELEMENT_NODE = 1;  
      var TEXT_NODE = 3;
      var ZWS = '\u200B';
    • 缩写,比如当前文档对象所在的window对象:var win = doc.defaultView, 也就是var win = node.ownerDocument.defaultView
    • 浏览器的判定以及判断是否支持一些属性:var canObserveMutations = typeof MutationObserver !== 'undefined';
  • exports.js:定义了将要暴露的接口

    Squire.onPaste = onPaste;
    // Node.js exports
    Squire.isInline = isInline;
    Squire.isBlock = isBlock;
    Squire.isContainer = isContainer;
    Squire.getBlockWalker = getBlockWalker;
  • intro.js:代码拼凑的头部`

    ( function ( doc, undefined ) {
    "use strict";`
  • outro.js:代码拼凑的尾部。用Shell命令依次合并intro.js–>其他js文件–>outro.js

    if ( typeof exports === 'object' ) {
        module.exports = Squire;
    } else if ( typeof define === 'function' && define.amd ) {
        define( function () {
            return Squire;
        });
    } else {
        win.Squire = Squire;
        if ( top !== win &&
                doc.documentElement.getAttribute( 'data-squireinit' ) === 'true' ) {
            win.editor = new Squire( doc );
            if ( win.onEditorLoad ) {
                win.onEditorLoad( win.editor );
                win.onEditorLoad = null;
            }
        }
    }
    }( document ) );
  • KeyHandlers.js:重新定义了上下左右导航、删除、backspace、tab、回车,以及组合按键等操作。以获取在编辑器一致的变现,并且在某些操作中增加钩子函数,比如beforeDelete;afterDelete

    • 对于会对产生变化的操作,先需压入undolist->根据range.collapsed进行操作->更新选区、dom结构树路径
    • 去除白名单外的标签,属性和样式
    • space按键操作的代码解读:

      space: function ( self, _, range ) {
          var node, parent;
          self._recordUndoState( range );
          addLinks( range.startContainer, self._root, self );
          self._getRangeAndRemoveBookmark( range );
      
          // If the cursor is at the end of a link (<a>foo|</a>) then move it
          // outside of the link (<a>foo</a>|) so that the space is not part of
          // the link text.
          node = range.endContainer;
          parent = node.parentNode;
          <p><s>asdf</s>sdfas<a href='xx' ><i><b>asdf</b><i></a><p>
          if ( range.collapsed && range.endOffset === getLength( node ) ) {
              if ( node.nodeName === 'A' ) {
                  range.setStartAfter( node );
              } else if ( parent.nodeName === 'A' && !node.nextSibling ) {
                  range.setStartAfter( parent );
              }
          }
          // Delete the selection if not collapsed
          if ( !range.collapsed ) {
              deleteContentsOfRange( range, self._root );
              self._ensureBottomLine();
              self.setSelection( range );
              self._updatePath( range, true );
          }
      
          self.setSelection( range );
      },
  • Range.js:关于光标、选区以及范围的操作。

    • 定义一些辅助函数,以实现节点的定位

      var getNodeBefore = function ( node, offset ) {
      var children = node.childNodes;
      // 
      while ( offset && node.nodeType === ELEMENT_NODE ) {
          node = children[ offset - 1 ];
          children = node.childNodes;
          offset = children.length;
      }
      return node;
      };
    • Range的增删改查操作

      // Returns the first block at least partially contained by the range,
      // or null if no block is contained by the range.
      var getStartBlockOfRange = function ( range, root ) {
          var container = range.startContainer,
              block;
      
          // If inline, get the containing block.
          if ( isInline( container ) ) {
              block = getPreviousBlock( container, root );
          } else if ( container !== root && isBlock( container ) ) {
              block = container;
          } else {
              block = getNodeBefore( container, range.startOffset );
              block = getNextBlock( block, root );
          }
          // Check the block actually intersects the range
          return block && isNodeContainedInRange( range, block, true ) ? block : null;
      };
  • Node.js:关于节点的基础定义,以及基本操作。

    • 从各种角度,定义节点类型,如:inline&&block、叶子节点&&非叶子节点
    • 定义查找指定节点的方法以及节点间的关系:

      function getNearest ( node, root, tag, attributes ) {
          while ( node && node !== root ) {
              if ( hasTagAttributes( node, tag, attributes ) ) {
                  return node;
              }
              node = node.parentNode;
          }
          return null;
      }
    • 定义节点以及之间的操作方法,如:split、merge等操作:

      function _mergeInlines ( node, fakeRange ) {
          var children = node.childNodes,
              l = children.length,
              frags = [],
              child, prev, len;
          while ( l-- ) {
              child = children[l];
              prev = l && children[ l - 1 ];
              if ( l && isInline( child ) && areAlike( child, prev ) &&
                      !leafNodeNames[ child.nodeName ] ) {
                  if ( fakeRange.startContainer === child ) {
                      fakeRange.startContainer = prev;
                      fakeRange.startOffset += getLength( prev );
                  }
                  if ( fakeRange.endContainer === child ) {
                      fakeRange.endContainer = prev;
                      fakeRange.endOffset += getLength( prev );
                  }
                  if ( fakeRange.startContainer === node ) {
                      if ( fakeRange.startOffset > l ) {
                          fakeRange.startOffset -= 1;
                      }
                      else if ( fakeRange.startOffset === l ) {
                          fakeRange.startContainer = prev;
                          fakeRange.startOffset = getLength( prev );
                      }
                  }
                  if ( fakeRange.endContainer === node ) {
                      if ( fakeRange.endOffset > l ) {
                          fakeRange.endOffset -= 1;
                      }
                      else if ( fakeRange.endOffset === l ) {
                          fakeRange.endContainer = prev;
                          fakeRange.endOffset = getLength( prev );
                      }
                  }
                  detach( child );
                  if ( child.nodeType === TEXT_NODE ) {
                      prev.appendData( child.data );
                  }
                  else {
                      frags.push( empty( child ) );
                  }
              }
              else if ( child.nodeType === ELEMENT_NODE ) {
                  len = frags.length;
                  while ( len-- ) {
                      child.appendChild( frags.pop() );
                  }
                  _mergeInlines( child, fakeRange );
              }
          }
      }
  • TreeWakler.js:定义节点的遍历模型,定义最基本的节点查找方法。

    TreeWalker.prototype.previousNode = function () {
        var current = this.currentNode,
            root = this.root,
            nodeType = this.nodeType,
            filter = this.filter,
            node;
        while ( true ) {
            if ( current === root ) {
                return null;
            }
            node = current.previousSibling;
            if ( node ) {
                while ( current = node.lastChild ) {
                    node = current;
                }
            } else {
                node = current.parentNode;
            }
            if ( !node ) {
                return null;
            }
            if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
                    filter( node ) ) {
                this.currentNode = node;
                return node;
            }
            current = node;
        }
    };
  • Editor.js:编辑器构造函数的模型function Squire ( root, config ) { }

    • 编辑器构造函数
    • 编辑器的参数操作函数
    • 编辑器的原型方法(操作函数、自定义事件机制、光标以及选区的方法)

      proto.getSelection = function () {
          var sel = getWindowSelection( this );
          var root = this._root;
          var selection, startContainer, endContainer, node;
          // If not focused, always rely on cached selection; another function may
          // have set it but the DOM is not modified until focus again
          if ( this._isFocused && sel && sel.rangeCount ) {
              selection  = sel.getRangeAt( 0 ).cloneRange();
              startContainer = selection.startContainer;
              endContainer = selection.endContainer;
              // FF can return the selection as being inside an <img>. WTF?
              if ( startContainer && isLeaf( startContainer ) ) {
                  selection.setStartBefore( startContainer );
              }
              if ( endContainer && isLeaf( endContainer ) ) {
                  selection.setEndBefore( endContainer );
              }
          }
          if ( selection &&
                  isOrContains( root, selection.commonAncestorContainer ) ) {
              this._lastSelection = selection;
          } else {
              selection = this._lastSelection;
              node = selection.commonAncestorContainer;
              // Check the editor is in the live document; if not, the range has
              // probably been rewritten by the browser and is bogus
              if ( !isOrContains( node.ownerDocument, node ) ) {
                  selection = null;
              }
          }
          if ( !selection ) {
              selection = this._createRange( root.firstChild, 0 );
          }
          return selection;
      };
    原文作者:yyh1945
    原文地址: https://segmentfault.com/a/1190000015880059
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞