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';
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.js
ide
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
函數
undo
list->根據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
:關於節點的基礎定義,以及基本操做。
定義查找指定節點的方法以及節點間的關係:
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; };