npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

richdoc

v2.0.0

Published

Format for representing rich text documents and changes

Downloads

43

Readme

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)