Rich text / foldkit-richtext-dom
Configuring a placement
editorAt(hostId, placement) takes, each optional:
rendering: how marks and node kinds become elements; the default renders the core's own names. A mark renders inside its run element, so the run stays outermost and a selection still maps.vocabulary:{ marks, nodes }registries. The editor refuses an edit a declaration forbids, rather than only lettingvalidatereport it later.inputRules: rules applied to what is typed, so a marker turns into a block change as it is completed, such asfoldkit-richtext-markdown'smarkdownInputRules. A rule the vocabulary refuses does not cost the keystroke: the text is inserted on its own.decorate:document => DecorationSet, drawn over the document on the mount and every patch, code highlighting for one. It sees the document only. A highlight derived from application state, such as a search query or other people's carets, goes through theoverlay(hostId, decorations)Command instead. It draws them after the placement's own and leaves the caret where it is. Its positions name the document shown, so set it again when that changes; placing the host id again starts it with none.focus(hostId, selection)is the Command that sends the reader into the text from a control outside it (a table of contents, a find bar closing on its match): the editor takes the focus with that selection, scrolled to the middle of the window when it is out of view. The browser reports the selection back asSelected.slashEntries: what its slash menu offers, which Enter in a query resolves against: an application's own catalogue (an image, a table, a callout), beside or instead of the editor'sslashEntries. The application's menu view draws the same list.An empty line holds a
<br data-richtext-filler>after its run. A browser can neither draw a caret in an empty text node nor move one there with the arrow keys, so without it a new line made with Enter had no height and could not be reached. The caret on an empty line stands before it; it holds no text or position, server markup draws it too, and an import drops it.placeholder: what a blank document shows (RichText.isBlank). It is put on the block asdata-placeholder, and the root, arole="textbox", carries it asaria-placeholder. Drawing it is the stylesheet's, so the text never enters the content:css[data-placeholder]::before { content: attr(data-placeholder); float: left; height: 0; pointer-events: none; color: GrayText; }lineHint: what the empty paragraph a collapsed caret is in shows, such as "Type / for blocks", put on it asdata-hintand moved as the caret moves. A blank document's block shows the placeholder instead. Draw it like the placeholder, under:focusso it shows only while the editor is being written in:[contenteditable]:focus [data-hint]::before.serverRendered: whether a render is the server's; see "On a server-rendered page".
Each has a record at foldkit-richtext-dom/host (placeRendering/renderingFor,
placeVocabulary/vocabularyFor, and so on) that editorAt writes. It is kept for the host
id, not for one mount, so a host that unmounts and mounts again renders the same way. One host
id is one editor on the page: the patch Command finds its host by id.
The assembled parent's update handles every editor Message. Build them with edited and the
helpers beside it: typed, pressed, toggled, applied, cleared, retyped, wrapped,
converted, lifted, moved, selected, undone, redone, and patched.
When the document changes from outside
A document the parent puts in the Model any other way than an edit (an entry opened, a
revision restored, a form's fill or Reset) gets a fresh host. The host's Mount reads its
document once, so the view keys the host by the document it shows: each committed edit hands
its key on to the next document, and a document that did not come from an edit here has none
yet. The first document a host id shows is drawn without a key, as the server draws it.
So commit the document an edit returns as it is. A parent that puts its own copy in the Model after each edit (normalized, say) gets a fresh host, and a lost caret, on every keystroke.
When the new document continues the old one (another replica's change arriving, or the
document a collaborative edit projects to), return patchTo(hostId, previous, next) from the
same transition that puts it in the Model. It hands the host's key on from previous to
next.document, which have to be the very objects the view rendered and will render, and
patches the editor from the latest state it holds (what it has drawn, or a state waiting for an
IME to finish) to next, so the host and every run the change
did not touch keep their elements. next.selection becomes the browser selection, so pass
the caret carried across the change (Replicated.resolve of an anchored one), or the caret
is lost. A replacement that changes nothing drawn, as an exchange that only confirms edits
already shown does, leaves the DOM and its selection alone. replaceChangeSet(previous, next) is the patch, in a transaction's terms: the runs
whose text or marks differ and the blocks holding them, blocks whose runs moved in or out or
changed order, blocks whose own fields changed, the
nodes that came and went, and whether any block moved. An Edited OutMessage also carries
the transactions the edit applied, which is what RichText.Replicated.translate reads.