quill-delta

repository·main·Indexed 21 days ago

https://github.com/slab/delta

A JSON-based format for representing rich text documents and the changes (deltas) made to them. Designed for Operational Transformation (OT) systems, it uses insert, delete, and retain operations to describe document states and modifications. The library provides a Delta class for composing, transforming, inverting, and diffing documents, as well as an AttributeMap utility for managing text formatting.

Tokens
6.8K
Snippets
35
Records
35
Agent score
75%

What's inside quill-delta

  1. What is a Delta and how does it work?

    main

    A Delta is a JSON-based format used to describe both rich text documents and the changes made to them.

    Key Mental Models:

    • Representing a Document: A Delta represents a document by expressing the sequence of instructions required to build that document starting from an empty state.
    • Representing Changes: A Delta represents changes as an array of operations. Operations do not use absolute indices; instead, they describe changes at the current index. You use retain operations to skip or keep parts of the document to reach the desired position.
    • Operations: There are three types of operations:
      1. insert: Adds text or an embed.
      2. delete: Removes a specific number of characters/embeds.
      3. retain: Keeps a range of characters and optionally modifies their attributes.
    // Document with text "Gandalf the Grey"
    // with "Gandalf" bolded, and "Grey" in grey
    const delta = new Delta([
      { insert: 'Gandalf', attributes: { bold: true } },
      { insert: ' the ' },
      { insert: 'Grey', attributes: { color: '#ccc' } }
    ]);
    
    // Change intended to be applied to above:
    // Keep the first 12 characters, insert a white 'White'
    // and delete the next four characters ('Grey')
    const death = new Delta().retain(12)
                             .insert('White', { color: '#fff' })
                             .delete(4);
    
    // Applying the change:
    const restored = delta.compose(death);
  2. The structure of a Delta Operation (Op)

    main

    A Delta operation (Op) represents a single change to a document. An operation must contain exactly one of the following primary properties: insert, delete, or retain. Additionally, an operation can optionally include an attributes object of type AttributeMap to apply formatting to the affected range.

    • insert: Represents adding content. It can be a string (plain text) or a Record<string, unknown> (representing an embedded object or blot).
    • delete: Represents removing content. It must be a number specifying the number of characters/units to delete.
    • retain: Represents moving the cursor or applying attributes to existing content. It can be a number (to skip a specific number of characters) or a Record<string, unknown> (to apply attributes to the next single unit).

    Note: In an Op, only one of insert, delete, or retain will be present.

    interface Op {
      insert?: string | Record<string, unknown>;
      delete?: number;
      retain?: number | Record<string, unknown>;
      attributes?: AttributeMap;
    }
  3. Construct a new Delta

    main

    You can create a new Delta instance using the constructor in three ways:

    1. new Delta(): Creates an empty Delta.
    2. new Delta(ops): Creates a Delta from an array of operation objects.
    3. new Delta(delta): Creates a Delta from an existing Delta object (which must have an ops key).

    Note: No validity/sanity checks are performed during construction. The internal ops array is assigned by reference from the input without deep copying.

    const delta = new Delta([
      { insert: 'Hello World' },
      { insert: '!', attributes: { bold: true }}
    ]);
  4. Split Delta operations with partition()

    main

    The partition(predicate) method creates an array of two arrays: the first contains operations that passed the predicate, and the second contains those that failed.

    const delta = new Delta().insert('Hello', { bold: true })
                             .insert({ image: 'https://octodex.github.com/images/labtocat.png' })
                             .insert('World!');
    
    const results = delta.partition((op) => typeof op.insert === 'string');
    const passed = results[0];  // [{ insert: 'Hello', attributes: { bold: true }}, { insert: 'World'}]
    const failed = results[1];  // [{ insert: { image: 'https://octodex.github.com/images/labtocat.png' }}]
  5. Reduce Delta operations with reduce()

    main

    The reduce(predicate, initialValue) method applies a function against an accumulator and each operation to reduce the Delta to a single value.

    const delta = new Delta().insert('Hello', { bold: true })
                             .insert({ image: 'https://octodex.github.com/images/labtocat.png' })
                             .insert('World!');
    
    const length = delta.reduce((length, op) => (
      length + (op.insert.length || 1);
    ), 0);
  6. Transform Delta operations with map()

    main

    The map(predicate) method returns a new array containing the results of calling the provided function on each operation. This is useful for extracting or transforming data from the operations.

    const delta = new Delta().insert('Hello', { bold: true })
                             .insert({ image: 'https://octodex.github.com/images/labtocat.png' })
                             .insert('World!');
    
    const text = delta
      .map((op) => {
        if (typeof op.insert === 'string') {
          return op.insert;
        } else {
          return '';
        }
      })
      .join('');
  7. Get a subset of operations with slice()

    main

    The slice([start], [end]) method returns a copy of the Delta containing a subset of its operations.

    • start: The starting index (defaults to 0).
    • end: The ending index (defaults to the end of the operations).
    const delta = new Delta().insert('Hello', { bold: true }).insert(' World');
    
    // Returns a full copy
    const copy = delta.slice();
    
    // Returns subset starting at index 6
    const world = delta.slice(6);
    
    // Returns subset from index 5 to 6
    const space = delta.slice(5, 6);
  8. Filter Delta operations with filter()

    main

    The filter(predicate) method returns an array of operations that pass a given test function. This is useful for isolating specific types of operations, such as only text insertions.

    const delta = new Delta().insert('Hello', { bold: true })
                             .insert({ image: 'https://octodex.github.com/images/labtocat.png' })
                             .insert('World!');
    
    const text = delta
      .filter((op) => typeof op.insert === 'string')
      .map((op) => op.insert)
      .join('');
  9. Transform a Delta with transform()

    main

    The transform(other, [priority]) method transforms the current Delta against another Delta (other).

    • priority: A boolean used to break ties. If true, the current Delta (this) takes priority over other, meaning its actions are considered to happen first.
    const a = new Delta().insert('a');
    const b = new Delta().insert('b').retain(5).insert('c');
    
    a.transform(b, true);  // new Delta().retain(1).insert('b').retain(5).insert('c');
    a.transform(b, false); // new Delta().insert('b').retain(6).insert('c');
  10. Get the total length of a Delta with length()

    main

    The length() method returns the total length of the Delta, which is calculated as the sum of the lengths of its operations.

    new Delta().insert('Hello').length();  // Returns 5
    
    new Delta().insert('A').retain(2).delete(1).length(); // Returns 4
  11. Compose two Deltas with compose()

    main

    The compose(other) method returns a new Delta that is equivalent to applying the operations of the current Delta, followed by the operations of the other Delta.

    const a = new Delta().insert('abc');
    const b = new Delta().retain(1).delete(1);
    
    const composed = a.compose(b);  // composed == new Delta().insert('ac');
  12. Invert a Delta with invert()

    main

    The invert(base) method returns an inverted Delta that has the opposite effect of the current Delta when applied against a base document Delta.

    Mathematically: base.compose(delta).compose(inverted) === base.

    const base = new Delta().insert('Hello\n').insert('World');
    const delta = new Delta().retain(6, { bold: true }).insert('!').delete(5);
    
    const inverted = delta.invert(base);