Position keys: a text per item that sorts between its neighbours, so that moving an item writes that one row, and two people moving at once both land.
iex> Narabe.Position.between(nil, nil)
"a0"
iex> Narabe.Position.between("a0", nil)
"a1"
iex> Narabe.Position.between("a0", "a1")
"a0V"
iex> Narabe.Position.between(nil, "a0")
"Zz"It is fractional indexing, as rocicorp's fractional-indexing (CC0):
base 62 (0-9A-Za-z), compared byte by byte. A key is an integer part
(a head letter that says its length, then digits) and a fraction that
never ends in 0. Keys after the last count up ("a1", "a2", …,
"b00"), keys before the first count down, and a key between two close
ones grows a fraction. The browser's twin (assets/src/position.ts) gives
the same keys, held to the same answers by one fixture.
In the database
Keys compare byte by byte, which is not every database's default:
- Postgres: the column needs
COLLATE "C"(Narabe.Migration.position/2adds it); a text column in a locale's collation sorts"a"and"B"the other way. - MySQL: a binary collation (
utf8mb4_bin). - SQLite: its default (
BINARY) is right.
Two moves into the same gap at the same moment get the same key: order
by the key, then by id (Narabe.Ecto.ordered/2), and the order is the
same on every screen. Keys grow only when items keep landing in the same
gap; Narabe.Ecto.rebalance/3 spreads a list's keys out again.
Summary
Functions
A key between a and b; nil is the start or the end.
n keys between a and b, in order, spread so that none grows long:
for several items moved at once, or a list's keys written anew.
Whether a text is a position key.
Types
@type t() :: String.t()
A position key.
Functions
A key between a and b; nil is the start or the end.
Raises ArgumentError when a is not before b, or either is not a key.
@spec between_many(t() | nil, t() | nil, non_neg_integer()) :: [t()]
n keys between a and b, in order, spread so that none grows long:
for several items moved at once, or a list's keys written anew.
iex> Narabe.Position.between_many(nil, nil, 3)
["a0", "a1", "a2"]
iex> Narabe.Position.between_many("a0", "a1", 3)
["a0G", "a0V", "a0l"]
Whether a text is a position key.
iex> Narabe.Position.valid?("a0V")
true
iex> Narabe.Position.valid?("a0V0")
false