defmodule Philtre.Block.ContentEditable.Selection do
@moduledoc """
Holds current selection in a block.
Passed from client to backend and vice-versa when executing block commands.
The ids are ids of cells in which a selection starts or ends.
The offests are indices within those cells where the selection starts or ends.
That means a simple caret (a cursor somewhere in the block text) will have
the same ids and same offsets.
Similarly, a selection of a text within a single cell will have the same ids,
but different offsets.
Lastly, a selection across cells within a block will have different ids and
different offsets.
Selection across blocks is not possible. Only whole blocks can be selected and
this is handled at a different level.
"""
alias Philtre.Block.ContentEditable
defstruct [:start_id, :end_id, :start_offset, :end_offset]
@type t :: %__MODULE__{
start_id: ContentEditable.Cell.id() | nil,
end_id: ContentEditable.Cell.id() | nil,
start_offset: non_neg_integer() | nil,
end_offset: non_neg_integer() | nil
}
def normalize!(nil), do: nil
def normalize!(%{
"start_id" => start_id,
"end_id" => end_id,
"start_offset" => start_offset,
"end_offset" => end_offset
})
when is_binary(start_id) and is_binary(end_id) and is_integer(start_offset) and
is_integer(end_offset) do
%__MODULE__{
start_id: start_id,
end_id: end_id,
start_offset: start_offset,
end_offset: end_offset
}
end
def normalize!(%{"start_id" => value}) when not is_binary(value) do
raise "selection start_id must be a valid id string, got: #{value}"
end
def normalize!(%{"end_id" => value}) when not is_binary(value) do
raise "selection end_id must be a valid id string, got: #{value}"
end
def normalize!(%{"start_offset" => value}) when not is_integer(value) do
raise "selection start_offset must be a valid number, got: #{value}"
end
def normalize!(%{"end_offset" => value}) when not is_integer(value) do
raise "selection end_offset must be a valid number, got: #{value}"
end
def new_empty, do: %__MODULE__{}
def new_start_of(%ContentEditable.Cell{id: id}) do
%__MODULE__{
start_id: id,
end_id: id,
start_offset: 0,
end_offset: 0
}
end
def new_end_of(%ContentEditable.Cell{id: id, text: text}) do
offset = String.length(text)
%__MODULE__{
start_id: id,
end_id: id,
start_offset: offset,
end_offset: offset
}
end
end