Rich text / foldkit-mixins-richtext

The link editor

An address field with two buttons: link the selection (or change the link around the caret), and remove that link. The address being typed is the application's; what it sends is the editor's AppliedMark or ClearedMark through wrap, so the editor applies either as one transition and one undo step.

ts
import * as RichText from 'foldkit-richtext'
import { linkEditor } from 'foldkit-mixins-richtext'

// Opening the editor: start the draft from the link the selection is in, if any.
const linkDraft = RichText.linkAt(model.document, model.editor.selection)?.href ?? ''

// In a view:
linkEditor<Message>()(
  {
    document: model.document,
    selection: model.editor.selection,
    draft: model.linkDraft,
    drafted: href => DraftedLink({ href }),
    wrap: edited,
  },
  h,
)

What it sends is RichText.safeUrl(draft), so the address is cleaned and a javascript: or data: one is never sent. Applying needs such an address and something to link: a range of text, or a caret inside a link. Otherwise the apply button is disabled and Enter in the field falls through. The remove button is drawn only inside a link. Where the editor appears, and when it opens or closes, is the application's; give it closed and Escape in the field sends it. Focusing the field as the editor opens, so a link can be pasted at once, is a Command the application runs (Dom.focus('[data-link="address"]')).

The view decides what its button sends from the draft it was drawn with, which is a frame behind keys typed fast: Enter straight after the last character would apply the draft before it. Give it submitted, and Enter sends that instead, for update to apply linkEdit(document, selection, draft) to the draft the Model holds; linkEdit is the same decision the button makes, as an Option of the editor's Message.

SlotCapabilityRenders
rootContainerthe editor's wrapper, role="group"
inputTextInputthe address field, data-link="address", "Paste or type a link"
applyInteractivethe apply button, data-link="apply", labelled Apply or Update
removeInteractivethe remove button, data-link="remove", inside a link only