资讯专栏INFORMATION COLUMN

Squire编辑器源码解读

raise_yang / 3285人阅读

摘要:编辑器构造函数的模型。编辑器构造函数编辑器的参数操作函数编辑器的原型方法操作函数自定义事件机制光标以及选区的方法

编辑器简单介绍
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 (foo|) then move it
    // outside of the link (foo|) so that the space is not part of
    // the link text.
    node = range.endContainer;
    parent = node.parentNode;
    

asdfsdfasasdf

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/96582.html

相关文章

  • Laravel php artisan optimize 源码解读

    摘要:确定的代码位于的中这样一看,其实就是将文件删除,而这个是会自动生成的一个数组文件,这里指定了每个和的位置和命名空间的全路径等,在启动项目的时候,可以直接读取使用。所以这个命令可以拆为两步层面优化加载速度删除很清晰。 原文:https://www.codecasts.com/blo... 在部署 Laravel 项目的时候,我们经常会使用到一个提升性能的命令: php artisan o...

    brianway 评论0 收藏0
  • 源码解读:php artisan serve

    摘要:原文来自在学习的时候,可能很多人接触的第一个的命令就是,这样我们就可以跑起第一个的应用。本文来尝试解读一下这个命令行的源码。 原文来自:https://www.codecasts.com/blo... 在学习 Laravel 的时候,可能很多人接触的第一个 artisan 的命令就是:php artisan serve,这样我们就可以跑起第一个 Laravel 的应用。本文来尝试解读一...

    Loong_T 评论0 收藏0
  • 源码解读:Laravel php artisan route:cache

    摘要:然而,本文的讨论重点,还是背后的源码,是怎么做到这一步的。从哪开始看源码位于你还是可以使用编辑器搜,就可以看到源码了。第三步序列化所有路由注册映射关系,还是在的方法中上面的方法位于中的中。所以到这里,的源码解读就完成了。 学 Laravel 和 Vuejs 你真应该来 codecasts.com ! Laravel ​route:cache 可以直接缓存路由文件,这样其实可以在一定程度...

    wangzy2019 评论0 收藏0
  • vue源码解读-目录结构

    摘要:目录结构构建相关的文件,一般情况下我们不需要动钩子别名配置 目录结构 ├── scripts ------------------------------- 构建相关的文件,一般情况下我们不需要动│ ├── git-hooks ------------------------- git钩子│ ├── alias.js -------------------------- 别名配...

    philadelphia 评论0 收藏0
  • Laravel 源码解读:php artisan make:auth

    摘要:添加路由在方法中,通过下面几行代码添加路由注意这个参数,就是将这个文件的内容附加在原来路由文件的后面,并不会将原来的路由清零。 学 Laravel 和 Vuejs,你真应该来 codecasts.com ,有免费高质量视频! 在 Laravel 5.2 的时候,官方给我们提供了 make:auth 命令,这个命令使得我们在执行一条命令的情况下实现用户注册和登录,忘记密码,找回密码的过程...

    xorpay 评论0 收藏0

发表评论

0条评论

raise_yang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<