Skip to content

Operational transformation format with support for concurrent rich text editing.

License

Notifications You must be signed in to change notification settings

Pietrorossellini/cljot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cljot

Build Status codecov Clojars Project

Operational transformation format with support for concurrent rich text editing.

Approach and compatibility

Cljot is designed for concurrent editing with centralized decision-making, i.e., it is primarily meant to be used with client-server topology where the canonical order of concurrent edits is decided by a central server. This poses some restrictions on true concurrency and entails an acknowledgement-based application of state changes.

Cljot is also heavily inspired by the Quill Delta format. Consequently, Cljot is mostly compatible with Deltas created with Quill Delta.

Usage

Cljot is based on deltas, which are vectors of operations applied successively. Three operations are provided:

  1. Insert: inserts new content, either text or non-opinionated map of attributes
  2. Retain: either keeps a given range intact or applies attributes to it
  3. Delete: removes a range

With these three operations, any number of different documents can flexibly be expressed. This covers representing both complete documents (as a lineage of operations that are applied to move from an empty document to the complete document) and changes between two document states.

API

The Cljot public API is contained within the cljot.delta, cljot.compose, and cljot.transform namespaces.

Constructing a delta

Cljot models documents using the delta abstraction. A delta is fundamentally a vector of operations.

delta

Parameterless delta constructor gives a new, empty delta:

(delta) ;=> []

Operations on delta

Operations can be conjed to a delta by using the insert, retain, and delete functions and threading.

insert

Used for appending content to a given delta:

(-> (delta) (insert "text"))
;=> [#cljot.delta.impl.ops.Insert{:value "text", :attributes nil}]

(-> (delta) (insert "text" {:bold true}))
;=> [#cljot.delta.impl.ops.Insert{:value "text", :attributes {:bold true}}]

retain

Used for either "moving on" within a given delta (to apply some other operation after a given range) or applying attributes to the range:

(-> (delta) (retain 3) (insert "text")) 
;=> [#cljot.delta.impl.ops.Retain{:value 3, :attributes nil}
;    #cljot.delta.impl.ops.Insert{:value "text", :attributes nil}]

(-> (delta) (retain 1 {:bold true}))
;=> [#cljot.delta.impl.ops.Retain{:value 1, :attributes {:bold true}}]

delete

Used for removing a given range:

(-> (delta) (delete 1))
;=> [#cljot.delta.impl.ops.Delete{:value 1}]

Operational transformation

Cljot includes two central functions for supporting operational transformation: delta composition and transformation.

compose

Composes two deltas:

(compose
  (-> (delta)
      (insert "a"))
  (-> (delta)
      (insert "b")))

;=> [#cljot.delta.impl.ops.Insert{:value "ba", :attributes nil}]

transform

Transforms a given delta by another.

The order of the two concurrent deltas can be decided:

transform-prioritising-a

The first delta takes place first.

(transform-prioritising-a
  (-> (delta)
      (insert "a"))
  (-> (delta)
      (insert "b")))

;=> [#cljot.delta.impl.ops.Retain{:value 1, :attributes nil}
;    #cljot.delta.impl.ops.Insert{:value "a", :attributes nil}]
transform-prioritising-b

The second delta takes place first.

(transform-prioritising-b
  (-> (delta)
      (insert "a"))
  (-> (delta)
      (insert "b")))

;=> [#cljot.delta.impl.ops.Insert{:value "a", :attributes nil}]

The transform function is an alias for transform-prioritising-b.

License

Copyright © 2018 Petri Myllys

Distributed under the Eclipse Public License 2.0.

About

Operational transformation format with support for concurrent rich text editing.

Topics

Resources

License

Stars

Watchers

Forks

Packages