gulf, 实时,共享和编辑文档

分享于 

19分钟阅读

GitHub

  繁體 雙語
transport-agnostic operational transformation control layer
  • 源代码名称:gulf
  • 源代码网址:http://www.github.com/gulf/gulf
  • gulf源代码文档
  • gulf源代码下载
  • Git URL:
    git://www.github.com/gulf/gulf.git
    Git Clone代码到本地:
    git clone http://www.github.com/gulf/gulf
    Subversion代码到本地:
    $ svn co --depth empty http://www.github.com/gulf/gulf
    Checked out revision 1.
    $ cd repo
    $ svn up trunk
    
    Build Status

    Join the chat at https://gitter.im/gulf/gulf

    操作转换是一组允许你同步文档的算法。 这就是 Google Docs 和Etherpad用于实时协作的原因。 湾是一个 unixy (",做一件事情并做好它,通过协作编辑,通过连接文档和其他的,自动解决冲突。

    Gulf stream (Public domain)

    背景

    这个项目从我的决定来取代Etherpad以更好的。 湾是我觉得很容易从Prototype中提取出来的代码的最小核心。 不幸的是,我在完成"更好的东西"之前花了大量的弯曲,最终发现了比没有本身更好的事情: 因此,CRDT是文物。

    尽管如此,它已经完成了,。 ( 这些测试由于熵而失败,因为我没有考虑。) 所以,如果你真的需要: 快乐同步 !

    用法

    /* * Server*/var textOT =require('ot-text').type// Create a new master documentvar doc =newgulf.Document({
     storageAdapter:newgulf.MemoryAdapter,
     ottype: textOT
    })doc.initializeFromStorage('abc') // Optionally supply default content// Set up a serverws.createServer(function(socket) {
     //.. . and create a slave link for each socket that connectsvar slave =doc.slaveLink()
     // now, add the new client as a slavesocket.pipe(slave).pipe(socket)
    })
    /* * Browser*/var textOT =require('ot-text').type// Create a new *editable* slave document (empty by default)var doc =newgulf.EditableDocument({
     storageAdapter:newgulf.MemoryAdapter,
     ottype: textOT
    })// Implement editor bindingsdoc._onBeforeChange=function() {/*...*/}doc._onChange=function() {/*...*/}doc._setContent=function() {/*...*/}// Connect to alice's serverws.connect(function(socket) {
     // create a link to the mastervar master =doc.masterLink()
     // connect to master documentsocket.pipe(master).pipe(socket)
    })

    你有一个合作的编辑 !

    用法

    文档

    文档可能包含任意数据。 文档的内容在 myDocument.content ( 这是只读的) 中可用。

    现在,如果 Document#content 无法访问,如何更改这里文档? 幸运的是还有 EditableDocuments。

    可以编辑文档

    可以编辑文档可以通过 submitChange(cs) 方法进行更新。 cs 是变更集的缩写。 变更集包含对文档的更改。

    EditableDocuments留下一些方法供你实现:

    vardocument=newgulf.EditableDocument(adapter, ottype)document._onChange=function(cs, cb) {
     /* apply changes return promise*/}document._setContent=function(newcontent, cb) {
     /* set new content return promise*/}document._onBeforeChange=functioncb() {
     /* collect changes and submitChange() them return promise*/}

    在任何事情发生之前,可以编辑文档被初始化为 wwith _setContent

    每次通过传入编辑 _onChange 更改文档时,都使用变更集调用。

    在处理传入编辑之前调用 _onBeforeChange,允许你在应用新编辑之前保存可能的更改。 就像你在抓取任何--之前一样,我不想让事情变得有毛病。

    有两种获取变更集的方法: 1 ) 计算最后一个已知状态与当前状态之间的差异。 2 ) 记录编辑事件并将它的转换为变更集。 有编辑器提供变更集 out-of-the-box,另外一些则必须通过( 使用差异计算) 来。

    现在我们更新我们的可以编辑文档,并注意到它保留了所有修改--文档的记录。 Nice。

    链接文档

    现在Alice和Bob都有一个可以编辑的文档并想同步它们。 为此,我们需要某种中介文档来处理同步过程,并表示绝对真实的truth。 在海湾中,这个中介被称为主文档。 它的最终说明是接受编辑,以及编辑是如何排序的。

    现在,Alice和小明需要将文档链接到主文档,以便发送它们所做的更改。

    对于这个海湾提供惊喜。 链接是简单的DuplexStream。 如果Alice想要连接主文档,她会创建一个主链接。 主文档将alice的链接附加为从属链接。

    net.createserver((socket) => {
     socket.pipe(masterDoc.slaveLink()).pipe(socket) // for each socket}).listen(1234)
    net.connect(1234,function(er, socket) {
     socket.pipe(slaveDoc.masterLink()).pipe(socket)
    })

    一个文档可以有许多奴隶,但只有一个主链接(。EditableDocuments没有从属链接)。

    现在我们已经连接了所有文档,每次Alice或者Bob进行更改时,编辑将只流向其他文档。

    相关软件包

    编辑器绑定

    你可以同步具有其他类型实现的任何文档类型和。

    因为向编辑器添加了for同步是一个重复的任务,很难得到正确的(。选择保留,生成差异,等等 ) 绑定。

    可以使用以下绑定:

    如果你想自己创建绑定,请遵循现有模块的API ( IE。 公开扩展EditableDocument并采取额外选项的单个类,称为 editorInstance。 别忘了实现 EditableDocument#close() )。 另外,请确保 NAME的软件包如下所示: gulf-editor-your-name-here

    存储适配器

    如果可以提供存储适配器,海湾允许你在任何地方存储你的数据。 它带有一个内存适配器,可以快速测试应用程序,但时间准备就绪。

    当前实现的适配器包括:

    如果你想编写自己的存储适配器,请前往API文档 below,并确保按如下方式对它的进行操作: gulf-backend-your-name-here

    wild示例/海湾

    在行动中观察海湾可能是最容易的。 所以,看看这些例子。

    如果你有最好的例子显示大小写,或者它的相关库留给我,你可以通过电子邮件或者的相关问题来给我一个

    API

    类:gulf.Link

    新 gulf.Link( [opts:Object] )

    创建一个新链接,可以选择使用一些选项:

    • opts.credentials 将发送到另一端的凭据以便进行身份验证。
    • 使用来自另一方的凭据调用的opts.authenticate,并具有以下签名: (credentials): Promise<Object>
    • opts.authorizeWrite 在另一端写入消息时调用的函数,并具有以下签名: (msg, user): Promise<Bool>user 是你的authenticate 钩子返回的值。
    • opts.authorizeRead 在链接写入消息时调用的函数,并具有以下签名: (msg, user): Promise<Bool>user 是你的authenticate 钩子返回的值。

    saving opts.authenticate的返回值也在保存快照时用作author字段。

    下面是如何设置链接身份验证和授权的示例:

    var link =newgulf.Link({
     authenticate:function(credentials) {
     returnauthenticate('token', credentials)
    . then((user) => {
     returnuser.id })
     }
    , authorizeWrite:function(msg, userId, cb) {
     switch(msg[0]) {
     case'edit':
     returnauthorize(userId, 'document:change')
    . then(auth=>auth.granted)
     case'requestInit':
     returnauthorize(userId, 'document:read')
    . then(auth=>auth.granted)
     }
     }
    , authorizeRead:function(msg, userId, cb) {
     switch(msg[0]) {
     case'init':
     case'edit':
     returnauthorize(userId, 'document:read')
    . then(auth=>auth.granted)
     case'ack':
     returnauthorize(userId, 'document:change')
    . then(auth=>auth.granted)
     }
     }
    })

    类:gulf.Document

    事件:init

    当文档收到包含快照的init 消息时激发,已经重置历史记录并设置新内容。

    事件:提交( 编辑:ownEdit:Boolean,)

    在提交编辑时激发( 已经确认,本地应用并存储)。 ownEdit 告诉你 edit 是由这里文档提交还是从其他文档接收。

    新的海湾文档( 选项:对象( {ottype, [storageAdapter] } ) )

    新建一个空的文档。 storageAdapter 是可选的,默认为 gulf.MemoryAdapter的新实例

    gulf document#initializefromstorage ( [initialContent] ): Promise

    从存储中加载文档。 opts 将被传递到文档构造函数。

    。document#slavelink ( 选项:对象): 链接

    opts 传递给链接构造函数并将它的作为从属链接连接时创建一个链接。

    。document#masterlink ( 选项:对象): 链接

    opts 传递给链接构造函数并将它的作为主链接连接时创建一个链接。

    gulf document#attachmasterlink ( 链接:链接)

    将现有链接附加为主节点。

    gulf document#attachslavelink ( 链接:链接)

    将现有链接作为从属连接。

    内部方法:

    。document#receiveinit ( 数据:对象,fromlink: 链接): promise

    当附加到这里文档的链接收到 init 消息时调用的侦听器函数。 data 可以如下所示: {contents: 'abc', edit: '<Edit>'}

    。document#receiveedit (。编辑:字符串,fromlink: 链接): Promise

    当链接接收到 edit 消息时调用的侦听器函数。 将编辑添加到队列( 在检查可能的主控形状之后),并在准备好时调用 Document#dispatchEdit ( )。

    。document#dispatchedit ( 编辑:编辑,fromlink: 链接): Promise

    检查文档的历史记录,是否已经知这里编辑,如果我们知道,是否知道它的父级。 在该文档中,将编辑到该文档的文档,并将它的添加到该文档的历史记录中,将它的添加到该文档的历史记录中,并将编辑发送到任何从属对象,并发出一个 edit 事件。

    。document#sanitizeedit ( 编辑:编辑,fromlink: 链接): Promise

    根据文档的历史,将经过的编辑转换为未编辑的编辑,并且编辑是父信息。

    。document#applyedit ( 编辑:编辑): Promise

    将编辑应用于文档的内容。

    gulf。( 编辑:编辑,[fromLink:Link] )

    将传递的编辑发送到所有附加的链接,但 fromLink 除外。

    类:gulf.EditableDocument(options )

    此类扩展 gulf.Document 并重写它的一些方法。

    以下是选项( 现在只是 mergeQueue )的默认值:

    
    {
    
    
     mergeQueue: true//If gulf should merge multiple outstanding edits into one, for faster collaboration.
    
    
    }
    
    
    
    
    事件:init

    当湾收到初始包并通过 EditableDocument#_setContent 设置内容时激发。

    事件:提交( 编辑:ownEdit:Boolean,)

    在提交编辑时激发( 已经确认,本地应用并存储)。 ownEdit 告诉你 edit 是由这里文档提交还是从其他文档接收。

    。editabledocument#submitchange ( 更改:混合)

    使用 changes 中提供的本地更改更新可以编辑文档。 这将修改编辑中的更改并将它们发送到主服务器。

    事件:提交( 编辑:修订)

    在调用 EditableDocument#update ( ) 时发生激发,但在主机已经批准更改之前。 edit 是新创建的编辑。

    注意:如果启用队列合并,提供的编辑可以在发送到服务器前与其他未完成的编辑合并。 如果你已经启用了队列合并,那么你将得到一个 commit 事件,每个编辑都得到了一个 update 事件,这是一个。

    gulf editabledocument#close ( )

    一个EditableDocument使用者可以调用这个来破坏EditableDocument和编辑器之间的连接。

    。editabledocument#_onchange ( cs: 混合): Promise

    需要由你或者包装器( 请参见编辑器绑定列表。) 实现。 在文档已经用 _setContents 初始化为从主机接收到的每个更改后调用。

    。editabledocument#_setcontent ( 内容:混合): Promise

    需要由你或者包装器( 请参见编辑器绑定列表。) 实现。 如果文档收到 init 消息,或者在发生错误时重新设置文档,则调用。

    。editabledocument#_onbeforechange ( ): Promise

    需要由你或者包装器( 请参见编辑器绑定列表。) 实现。 在调用 _onChange() 之前调用,以跟踪任何未完成的更改。

    类:gulf.Revision

    new Revision(ottype )

    在没有父级,更改或者标识的情况下实例化新的编辑。 所以它非常无用。

    gulf.Revision.fromJSON(json:String, ottype): 修订版

    反序列化使用 gulf.Revision#toJSON() 序列化的编辑。

    gulf.Revision.newInitial(ottype, initialContent): 修订版

    创建新的初始编辑。 初始修订带有内容,但没有更改。

    gulf.Revision.newFromChangeset(cs:mixed, ottype): 修订版

    创建一个新的编辑,更改设置为 cs

    gulf revision#apply ( documentContents )

    对文档快照应用这里编辑。

    gulf revision#follow ( 编辑:编辑)

    根据所传递的内容对该编辑进行转换,并设置另一个编辑为父级。 ( 这里操作改写历史。)

    gulf revision#transformagainst ( 编辑:编辑)

    将这里编辑转换为传递的,而不重置这里编辑的父级。

    。revision#merge ( 编辑:修订): 修订版

    将通过的编辑与这里合并。 返回新编辑。

    。revision#tojson ( ): 对象

    序列化这里编辑。

    。revision#clone ( ): 修订版

    返回具有与这里属性完全相同的属性的新编辑实例。

    命令行适配器

    bay存储适配器在npm包中提供,它们名为 gulf-backend-yournamehere。 他们处理修订对象。 修订是类似于下面这样的对象:

    {
     id:48, changeset: [0, "h"]
    , parent:47// ID of this revision's parent, content:'"Hello world"'// stringified representation of the new contents, author:12// The id of the author, as returned by `opts.authenticate` in the Link options (or the value you passed to gulf.Document#receiveEdit, if you passed in the edit directly)}

    在编写自己的适配器时,请检查内存适配器blob存储适配器。

    Adapter#getLastRevisionId ( ): 承诺 Adapter#storeRevision ( 修订:对象): Promise Adapter#getRevision(revId:Number) : Promise

    测试?

    Sauce Test Status

    Test-it-yourself

    要在 node 和浏览器中运行测试,请运行以下命令:

    
    npm run build && npm run test-local
    
    
    
    

    ( 请确保在你选择的浏览器中打开所提供的链接。)

    许可证

    ( c ) Marcel Klehr 2013 -2016

    gnu 宽通用公共许可证

    变更日志

    v5.0.0

    • 完整的重构和 API

    v4.1.0

    • EditableDoc: 添加事件 editableInitialized
    • EditableDoc: 添加事件 commit
    • 添加选项 mergeQueue

    v4.0.5

    • 修复内存泄漏:Doc#detachLink上的:'''

    v4.0.4

    • 修复重新连接的重新连接

    v4.0.3

    • 清除Link#reset上的sentEdit超时
    • 删除Link#send中无意义的保护

    v4.0.2

    • 修复 createDocument。历史记录未传递到 doc.id
    • 更改代码 header: LGPL

    v4.0.1

    • LGPL下的许可证

    DOC  EDI  时间  SHA  分享  实时  
    相关文章