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:用构造函数定义了编辑器,而且在原型上增长属性,以实现更多的编辑器操做javascript

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

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

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

    • 给生涩变量取别名app

      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:定义了将要暴露的接口dom

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

    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按键操做的代码解读:flex

      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;
      };
相关文章
相关标签/搜索