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