Socket
Socket
Sign inDemoInstall

richdoc

Package Overview
Dependencies
Maintainers
2
Versions
50
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

richdoc

Format for representing rich text documents and changes


Version published
Weekly downloads
9
decreased by-83.33%
Maintainers
2
Weekly downloads
 
Created
Source

Rich Doc

Format for representing rich text documents and changes.

Circle CI

说明

Operator 表示一个修改。每个修改可能是三种类型:insert, removeretain。其中 insert 表示插入,remove 表示删除,retain 表示保留(用来跳过或者修改属性)。

Delta 是一组修改的集合,用来表示对一篇文档的修改。当集合中所有的修改都为 insert 时,此 Delta 即可表示文档本身。如下所示的 Delta 表示一篇内容为“Hello World”的文档:

const doc = new Delta([
  new TextOperator({ action: 'insert', data: 'Hello ' }),
  new TextOperator({ action: 'insert', data: 'World', attributes: { bold: true } })
]);

其中 Delta 包含两个方法:composetransform。其中 compose 用来合并两个 Delta:

const doc = new Delta([
  new TextOperator({ action: 'insert', data: 'Hello ' }),
  new TextOperator({ action: 'insert', data: 'World', attributes: { bold: true } })
]);

const change = new Delta([
  new TextOperator({ action: 'retain', data: 6 }),
  new TextOperator({ action: 'insert', data: 'Tom' }),
  new TextOperator({ action: 'remove', data: 5 })
]);

const updatedDoc = doc.compose(change);

// updatedDoc 的结果为:
new Delta([
  new TextOperator({ action: 'insert', data: 'Hello Tom' })
]);

transform 则用于操作变基。如对一篇文档,A 先做了修改并提交到服务器,而 B 也在同一时刻对文档做了修改并提交到服务器。此时服务器先收到 A,后收到 B,且 A 和 B 都是对同一版本做的修改。为了合并这两个操作,需要变换 B 为 B' 使得 A.compose(B') === B.compose(A'),这个变换过程就是通过 transform 实现的,即 A.compose(A.transform(B)) === B.compose(B.transform(A))。举例而言,还是上面的文档:

const doc = new Delta([
  new TextOperator({ action: 'insert', data: 'Hello ' }),
  new TextOperator({ action: 'insert', data: 'World', attributes: { bold: true } })
]);

此时 A 进行了操作,在 Hello 后面加个逗号:

const A = new Delta([
  new TextOperator({ action: 'retain', data: 5 }),
  new TextOperator({ action: 'insert', data: ',' })
]);

同时 B 进行了操作,把 World 换成 Tom:

const B = new Delta([
  new TextOperator({ action: 'retain', data: 6 }),
  new TextOperator({ action: 'insert', data: 'Tom' }),
  new TextOperator({ action: 'remove', data: 5 })
]);

A.transform(B) 的结果为:

const AB = new Delta([
  new TextOperator({ action: 'retain', data: 7 }),
  new TextOperator({ action: 'insert', data: 'Tom' }),
  new TextOperator({ action: 'remove', data: 5 })
]);

B.transform(A) 的结果为:

const BA = new Delta([
  new TextOperator({ action: 'retain', data: 5 }),
  new TextOperator({ action: 'insert', data: ',' })
]);

而后,A.compose(AB)B.compose(BA) 都为:

new Delta([
  new TextOperator({ action: 'retain', data: 5 }),
  new TextOperator({ action: 'insert', data: ',' }),
  new TextOperator({ action: 'retain', data: 1 }),
  new TextOperator({ action: 'insert', data: 'Tom' }),
  new TextOperator({ action: 'remove', data: 5 })
]);

有种特殊情况是,A 和 B 同时在同一位置插入了内容,这时就要确定谁的内容放在前面,为此 transform 接受第二个参数,表示操作优先级。公式为 A.compose(A.transform(B, true)) === B.compose(B.transform(A, false))。为了统一起见,服务端先收到的 Delta 优先。

安装

npm install richdoc

用法

import { Delta, TableOperator, Operator, Operator, TextOperator } from 'richdoc';

举例来说,对于一篇只有一个 3x3 表格的文档,其中单元格 A1 有“Hello World”几个字,可以表示为:

const doc = new Delta([
  new TableOperator({
    action: 'insert',
    data: {
      rows: new Delta([
        new Operator({ action: 'insert', data: 3 })
      ]),
      cols: new Delta([
        new Operator({ action: 'insert', data: 1 }),
        new Operator({ action: 'insert', data: 1, attributes: { width: 50 } }),
        new Operator({ action: 'insert', data: 1 })
      ]),
      cells: {
        A1: new CellOperator({
          action: 'insert',
          data: new Delta([
            new TextOperator({ action: 'insert', data: 'Hello ' }),
            new TextOperator({ action: 'insert', data: 'World', attributes: { bold: true } })
          ])
        })
      }
    }
  })
]);

序列化

Delta 可以序列化成字符串方便传输:

import { pack, unpack } from 'richdoc';

const packed = pack(delta);
const unpacked = unpack(packed);

变化流程

为了方便叙述,这里定义两个伪函数:T(A, B) = A.transform(B), A + B = A.compose(B)。可以得出:A + T(A, B) = B + T(B, A)

客户端

客户端保存有三个 Delta,A, X 和 Y,其中:

  • A 表示当前客户端已知的服务端的最新版本的文档;
  • X 表示当前客户端已经提交给服务端,但是没有收到服务端确认的修改;
  • Y 表示当前客户端本地的修改,还没有提交到服务端。

当发生下列情况时,此三个 Delta 会发生变化:

  1. 用户在客户端进行了修改操作。

用户对文档进行了修改,产生 Delta E(根据定义,显然 E 是基于 Y 的修改)。客户端此时需要更新 Y,使得 Y <- Y + E。

  1. 客户端将修改提交给服务端。

当客户端要将本地修改 Y 发给服务端时,必须保证 X 为空(见下条情况)。此时客户端需要进行下列操作:

  1. 将 Y 发送给服务器

  2. 令 X <- Y

  3. 设 Y 为空 Delta

  4. 客户端收到服务端的确认。

当服务端收到客户端的修改时(即 Y),服务端会向客户端发送 ACK 响应来确认。此时客户端需要进行下列操作:

  1. A <- A + X
  2. 设 X 为空 Delta

之后每 500ms 客户端再次将本地修改 Y 提交给服务端,从而形成循环。

  1. 收到其他客户端的修改。

当客户端收到服务端发送来的其他客户端的修改 B 时(显然这些修改是基于 A 的),客户端执行如下操作:

  1. A' <- A + B
  2. X' <- T(B, X)
  3. Y' <- T(T(X, B), Y)
  4. D <- T(Y, T(X, B))
  5. A <- A'
  6. X <- X'
  7. Y <- Y'
  8. 将 D 应用于当前文档上(并对应修改用户界面)

演化测试

除了单元测试外,可以通过执行 npm run evolution 启动演化测试。程序会自动生成随机文档并不断演化文档,通过如下三个公式测试代码的正确性:

  1. a === a.compose(b).compose(a.invert(b))
  2. a.compose(a.transform(b)) === b.compose(b.transform(a))
  3. pack(a) === unpack(a)

Keywords

FAQs

Package last updated on 09 Jan 2017

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc