Delta
简单而富有表现力的格式来描述文档的内容和变更 🗃
Delta是一种简单而富有表现力的格式,可用于描述内容和变更。 该格式是JSON的严格子集,易于人类阅读,并且机器可以轻松解析。 Delta可以描述任何富文本文档,包括所有文本和格式信息,而不会出现HTML中的模糊性和复杂性。
Delta格式适用于操作转换,并可用于实时协作文档编辑器(例如Slab、Google文档)。关于Delta设计动机和思路的详细介绍,请参阅Designing the Delta Format。
请参阅文档。
安装
在mix.exs
中将delta
添加到项目依赖项中:
def deps do
[{:delta, "~> 0.4.0"}]
end
用法
Delta由一系列操作组成,这些操作描述了对文档的更改。这些操作可以是insert
、delete
或retain
。这些操作不需要索引,而是描述当前索引处的更改。保留操作用于"保留"文档的一部分。
快速示例
alias Delta.Op
# 文档内容为"Gandalf the Grey",其中"Gandalf"加粗,"Grey"为灰色
delta = [
Op.insert("Gandalf", %{"bold" => true}),
Op.insert(" the "),
Op.insert("Grey", %{"color" => "#ccc"}),
]
# 定义要应用于上述内容的更改:
# 保留前12个字符,删除接下来的4个字符,
# 并插入一个白色的"White"
death = [
Op.retain(12),
Op.delete(4),
Op.insert("White", %{"color" => "#fff"}),
]
# 应用更改:
Delta.compose(delta, death)
# => [
# %{"insert" => "Gandalf", "attributes" => %{"bold" => true}},
# %{"insert" => " the "},
# %{"insert" => "White", "attributes" => %{"color" => "#fff"}},
# ]
操作
插入
插入操作具有已定义的insert
键。字符串值表示插入文本。任何其他类型都表示插入嵌入(但仅执行一级对象比较以进行相等性判断)。
对于文本和嵌入,都可以定义可选的attributes
键,该键是一个map
,用于描述附加的格式信息。保留操作可以更改格式。
# 插入文本
Op.insert("一些文本")
# 插入加粗文本
Op.insert("加粗文本", %{"bold" => true})
# 插入链接
Op.insert("谷歌", %{"link" => "https://www.google.com"})
# 插入嵌入
Op.insert(%{"image" => "https://app.com/logo.png"}, %{"alt" => "应用程序logo"})
# 插入另一个嵌入
Op.insert(%{"video" => "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}, %{"width" => 420, "height" => 315})
删除
删除操作具有定义的正整数delete
键,表示要删除的字符数。所有嵌入的长度都为1。
# 删除接下来的10个字符
Op.delete(10)
保留
保留操作具有定义的正整数retain
键,表示要保留的字符数(其他库可能使用保留或跳过)。可以定义可选的attributes
键,该键是一个map
,用于描述字符范围内的格式更改。属性映射中的值为nil
表示删除该键。
注意:没有必要保留文档的最后几个字符,因为这是隐含的。
# 保留接下来的5个字符
Op.retain(5)
# 保留并加粗接下来的5个字符
Op.retain(5, %{"bold" => true})
# 保留并取消加粗接下来的5个字符
Op.retain(5, %{"bold" => nil})
操作转换
操作转换(OT)是一种构建协作体验的技术,在应用程序共享和构建支持多用户协作的实时文档编辑器(例如Google文档、Slab)中非常有用。
Delta开箱即用地支持OT,在Elixir中使用操作转换技术非常有用。它支持以下属性:
组合
返回一个新的Delta,它等价于先应用一个Delta的操作,然后再应用另一个Delta的操作:
a = [Op.insert("abc")]
b = [Op.retain(1), Op.delete(1)]
Delta.compose(a, b)
# => [%{"insert" => "ac"}]
转换
将给定的Delta转换为针对另一个Delta的操作。这接受一个可选的priority
参数(默认值:false
),用于打破平局。如果true
,第一个Delta优先于其他Delta,即它的操作被认为首先发生。
a = [Op.insert("a")]
b = [Op.insert("b"), Op.retain(5), Op.insert("c")]
Delta.transform(a, b, true)
# => [
# %{"retain" => 1},
# %{"insert" => "b"},
# %{"retain" => 5},
# %{"insert" => "c"},
# ]
Delta.transform(a, b)
# => [
# %{"insert" => "b"},
# %{"retain" => 6},
# %{"insert" => "c"},
# ]
请注意,即使delete
操作支持属性,使用transform
时也只能安全地使用不带属性的删除操作。
反转
返回一个反转的Delta,其效果与针对基础文档Delta的效果相反。
也就是说base |> Delta.compose(change) |> Delta.compose(inverted) == base
。
base = [Op.insert("Hello\nWorld")]
change = [
Op.retain(6, %{"bold" => true}),
Op.delete(5),
Op.insert("!"),
]
inverted = Delta.invert(change, base)
# => [
# %{"retain" => 6, "attributes" => %{"bold" => nil}},
# %{"insert" => "World"},
# %{"delete" => 1},
# ]
base |> Delta.compose(change) |> Delta.compose(inverted) == base
# => true
贡献
- Fork、增强并发送PR
- 提交任何bug或功能请求的issue
- 实现路线图中的内容
- 传播这个消息 :heart: