Quillon.Edit (Quillon v0.4.0)

Copy Markdown View Source

Editing a document through a selection.

Quillon.Commands works on one block and two integers - the caller has to know which block and where. This module takes a whole document and a Quillon.Selection instead, resolves the selection, and applies the existing command to the right block.

iex> doc = Quillon.document([Quillon.paragraph("Hello")])
iex> selection = Quillon.Selection.text(
...>   Quillon.Selection.point([0], 0),
...>   Quillon.Selection.point([0], 5)
...> )
iex> {:ok, doc, _selection} = Quillon.Edit.apply_mark(doc, selection, :bold)
iex> Quillon.to_html(doc)
~s(<div class="quillon"><p><strong>Hello</strong></p></div>)

Every function returns the new selection

An edit moves the text under the caret, so each function answers with {:ok, document, selection} and the caret follows what it just did - after split_block/2 it sits at the start of the new block, after insert_block/3 inside the inserted one.

This is not general position mapping. A selection held by another user is not rebased by these functions; that arrives with the collaboration layer, which is what decides how positions are tracked across concurrent edits.

Errors

  • :invalid_path - the selection points at a node that is not there
  • :out_of_range - an offset falls outside its block
  • :cross_block - a text selection spanning two blocks. Representable, but not yet acted on
  • :unsupported_block - the block is not one the transform layer edits, which today means anything but a paragraph or heading

Summary

Types

Why an edit could not be applied

The result of an edit

Functions

Apply a mark across the selection.

Delete what the selection covers, leaving the caret where it started.

Whether every text node in the selection carries a mark.

Insert a block after the one holding the caret, and put the caret in it.

Remove a mark across the selection.

Split the block at a collapsed caret into two, and put the caret at the start of the second.

Toggle a mark across the selection.

Types

error()

@type error() :: :invalid_path | :out_of_range | :cross_block | :unsupported_block

Why an edit could not be applied

result()

@type result() :: {:ok, tuple(), Quillon.Selection.t()} | {:error, error()}

The result of an edit

Functions

apply_mark(doc, selection, mark)

@spec apply_mark(tuple(), Quillon.Selection.t(), atom() | {atom(), map()}) :: result()

Apply a mark across the selection.

The selection is returned unchanged - marking text does not move it.

Examples

iex> doc = Quillon.document([Quillon.paragraph("Hello")])
iex> selection = Quillon.Selection.text(
...>   Quillon.Selection.point([0], 0),
...>   Quillon.Selection.point([0], 2)
...> )
iex> {:ok, doc, _selection} = Quillon.Edit.apply_mark(doc, selection, :bold)
iex> Quillon.to_html(doc)
~s(<div class="quillon"><p><strong>He</strong>llo</p></div>)

delete_selection(doc, selection)

@spec delete_selection(tuple(), Quillon.Selection.t()) :: result()

Delete what the selection covers, leaving the caret where it started.

A node selection removes the node outright.

Examples

iex> doc = Quillon.document([Quillon.paragraph("Hello world")])
iex> selection = Quillon.Selection.text(
...>   Quillon.Selection.point([0], 5),
...>   Quillon.Selection.point([0], 11)
...> )
iex> {:ok, doc, selection} = Quillon.Edit.delete_selection(doc, selection)
iex> Quillon.to_html(doc)
~s(<div class="quillon"><p>Hello</p></div>)
iex> Quillon.Selection.Point.offset(selection.head)
5

iex> doc = Quillon.document([Quillon.paragraph("One"), Quillon.divider()])
iex> {:ok, doc, _selection} = Quillon.Edit.delete_selection(doc, Quillon.Selection.node([1]))
iex> Quillon.to_html(doc)
~s(<div class="quillon"><p>One</p></div>)

has_mark?(doc, selection, mark_type)

@spec has_mark?(tuple(), Quillon.Selection.t(), atom()) :: boolean()

Whether every text node in the selection carries a mark.

Examples

iex> doc = Quillon.document([{:paragraph, %{}, [Quillon.text("Hello", [:bold])]}])
iex> selection = Quillon.Selection.text(
...>   Quillon.Selection.point([0], 0),
...>   Quillon.Selection.point([0], 5)
...> )
iex> Quillon.Edit.has_mark?(doc, selection, :bold)
true

insert_block(doc, selection, new_node)

@spec insert_block(tuple(), Quillon.Selection.t(), tuple()) :: result()

Insert a block after the one holding the caret, and put the caret in it.

The containing block is not split, so inserting mid-paragraph places the new block after that whole paragraph. Use split_block/2 first to break the paragraph at the caret.

Examples

iex> doc = Quillon.document([Quillon.paragraph("One")])
iex> {:ok, doc, selection} =
...>   Quillon.Edit.insert_block(doc, Quillon.Selection.cursor([0], 3), Quillon.paragraph("Two"))
iex> Quillon.to_html(doc)
~s(<div class="quillon"><p>One</p><p>Two</p></div>)
iex> Quillon.Selection.Point.path(selection.head)
[1]

remove_mark(doc, selection, mark_type)

@spec remove_mark(tuple(), Quillon.Selection.t(), atom()) :: result()

Remove a mark across the selection.

Examples

iex> doc = Quillon.document([{:paragraph, %{}, [Quillon.text("Hello", [:bold])]}])
iex> selection = Quillon.Selection.text(
...>   Quillon.Selection.point([0], 0),
...>   Quillon.Selection.point([0], 5)
...> )
iex> {:ok, doc, _selection} = Quillon.Edit.remove_mark(doc, selection, :bold)
iex> Quillon.to_html(doc)
~s(<div class="quillon"><p>Hello</p></div>)

split_block(doc, selection)

@spec split_block(tuple(), Quillon.Selection.t()) :: result()

Split the block at a collapsed caret into two, and put the caret at the start of the second.

Both halves keep the original block's type and attributes.

Examples

iex> doc = Quillon.document([Quillon.paragraph("HelloWorld")])
iex> {:ok, doc, selection} = Quillon.Edit.split_block(doc, Quillon.Selection.cursor([0], 5))
iex> Quillon.to_html(doc)
~s(<div class="quillon"><p>Hello</p><p>World</p></div>)
iex> Quillon.Selection.Point.path(selection.head)
[1]

toggle_mark(doc, selection, mark)

@spec toggle_mark(tuple(), Quillon.Selection.t(), atom() | {atom(), map()}) ::
  result()

Toggle a mark across the selection.

Examples

iex> doc = Quillon.document([Quillon.paragraph("Hello")])
iex> selection = Quillon.Selection.text(
...>   Quillon.Selection.point([0], 0),
...>   Quillon.Selection.point([0], 5)
...> )
iex> {:ok, bolded, selection} = Quillon.Edit.toggle_mark(doc, selection, :bold)
iex> {:ok, plain, _selection} = Quillon.Edit.toggle_mark(bolded, selection, :bold)
iex> Quillon.to_html(plain)
~s(<div class="quillon"><p>Hello</p></div>)