text_delta v1.4.0 TextDelta View Source

Delta is a format used to describe text states and changes.

Delta can describe any rich text changes or a rich text itself, preserving all the formatting.

At the baseline level, delta is an array of operations (constructed via TextDelta.Operation). Operations can be either TextDelta.Operation.insert/0, TextDelta.Operation.retain/0 or TextDelta.Operation.delete/0. None of the operations contain index, meaning that delta aways describes text or a change staring from the very beginning.

Delta can describe both changes to and text states themselves. We can think of a document as an artefact of all the changes applied to it. This way, newly imported documents can be thinked of as a sequence of inserts applied to an empty text.

Deltas are composable. This means that a text delta can be composed with another delta for that text, resulting in a shorter, optimized version.

Deltas are also transformable. This attribute of deltas is what enables Operational Transformation - a way to transform one operation against the context of another one. Operational Transformation allows us to build optimistic, non-locking collaborative editors.

The format for deltas was deliberately copied from Quill - a rich text editor for web. This library aims to be an Elixir counter-part for Quill, enabling us to build matching backends for the editor.

Example

iex> delta = TextDelta.insert(TextDelta.new(), "Gandalf", %{bold: true})
%TextDelta{ops: [
    %{insert: "Gandalf", attributes: %{bold: true}}]}
iex> delta = TextDelta.insert(delta, " the ")
%TextDelta{ops: [
    %{insert: "Gandalf", attributes: %{bold: true}},
    %{insert: " the "}]}
iex> TextDelta.insert(delta, "Grey", %{color: "#ccc"})
%TextDelta{ops: [
    %{insert: "Gandalf", attributes: %{bold: true}},
    %{insert: " the "},
    %{insert: "Grey", attributes: %{color: "#ccc"}}]}

Link to this section Summary

Types

A text state represented as delta. Any text state can be represented as a set of TextDelta.Operation.insert/0 operations

Functions

Appends given operation to the delta

Creates and appends new delete operation to the delta

Creates and appends new insert operation to the delta

Calculates the length of a given delta

Creates new delta

Returns set of operations for a given delta

Creates and appends new retain operation to the delta

Trims trailing retains from the end of a given delta

Link to this section Types

Link to this type document() View Source
document() :: state()

Alias to TextDelta.state/0.

Link to this type state() View Source
state() :: %TextDelta{ops: [TextDelta.Operation.insert()]}

A text state represented as delta. Any text state can be represented as a set of TextDelta.Operation.insert/0 operations.

Link to this section Functions

Appends given operation to the delta.

Before adding operation to the delta, this function attempts to compact it by applying 2 simple rules:

  1. Delete followed by insert is swapped to ensure that insert goes first.
  2. Same operations with the same attributes are merged.

These two rules ensure that our deltas are always as short as possible and canonical, making it easier to compare, compose and transform them.

Example

iex> operation = TextDelta.Operation.insert("hello")
iex> TextDelta.append(TextDelta.new(), operation)
%TextDelta{ops: [%{insert: "hello"}]}
Link to this function delete(delta, len) View Source
delete(t(), non_neg_integer()) :: t()

Creates and appends new delete operation to the delta.

TextDelta.append/2 is used undert the hood to add operation to the delta after construction. So all append rules apply.

Example

iex> TextDelta.delete(TextDelta.new(), 3)
%TextDelta{ops: [%{delete: 3}]}

Creates and appends new insert operation to the delta.

Same as with underlying TextDelta.Operation.insert/2 function, attributes are optional.

TextDelta.append/2 is used undert the hood to add operation to the delta after construction. So all append rules apply.

Example

iex> TextDelta.insert(TextDelta.new(), "hello", %{bold: true})
%TextDelta{ops: [%{insert: "hello", attributes: %{bold: true}}]}
Link to this function length(delta, op_types \\ [:insert, :retain, :delete]) View Source

Calculates the length of a given delta.

Length of delta is a sum of its operations length.

Example

iex> TextDelta.length(TextDelta.new([%{insert: "hello"}, %{retain: 5}]))
10

The function also allows to select which types of operations we include in the summary with optional second argument:

iex> TextDelta.length(TextDelta.new([%{insert: "hi"}]), [:retain])
0

Creates new delta.

Examples

iex> TextDelta.new()
%TextDelta{ops: []}

You can also pass set of operations using optional argument:

iex> TextDelta.new([TextDelta.Operation.insert("hello")])
%TextDelta{ops: [%{insert: "hello"}]}
Link to this function operations(delta) View Source
operations(t()) :: [TextDelta.Operation.t()]

Returns set of operations for a given delta.

Example

iex> TextDelta.operations(TextDelta.new([%{delete: 5}, %{retain: 3}]))
[%{delete: 5}, %{retain: 3}]
Link to this function retain(delta, len, attrs \\ %{}) View Source
retain(t(), non_neg_integer(), TextDelta.Attributes.t()) :: t()

Creates and appends new retain operation to the delta.

Same as with underlying TextDelta.Operation.retain/2 function, attributes are optional.

TextDelta.append/2 is used undert the hood to add operation to the delta after construction. So all append rules apply.

Example

iex> TextDelta.retain(TextDelta.new(), 5, %{italic: true})
%TextDelta{ops: [%{retain: 5, attributes: %{italic: true}}]}
Link to this function transform(left, right, priority) View Source
Link to this function trim(delta) View Source
trim(t()) :: t()

Trims trailing retains from the end of a given delta.

Example

iex> TextDelta.trim(TextDelta.new([%{insert: "hello"}, %{retain: 5}]))
%TextDelta{ops: [%{insert: "hello"}]}