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;
      };
相關文章
相關標籤/搜索