Composition / foldkit-builder

The selection in the URL

A link that opens the editor on a Block is the Builder's selection mirrored into the URL. The Builder owns the selection, and a selection does more than set a field (the layers' focus moves, the rows above it open), so the URL never writes it directly: a navigation becomes a Selected Message, and the selection is read back out into the URL.

In, from the parent's own update, for a form key named document placed as page:

ts
const blockIn = (url: Url): Option.Option<NodeId> =>
  Option.map(
    Option.filter(
      Option.fromNullOr(new URLSearchParams(Option.getOrElse(url.search, () => '')).get('block')),
      id => id !== '',
    ),
    NodeId.make,
  )

const selectFrom = (url: Url) =>
  Message.GotPageMessage({
    message: PageForm.control('document').send(
      Option.match(blockIn(url), {
        onNone: () => BuilderMessage.Deselected(),
        onSome: id => BuilderMessage.Selected({ id }),
      }),
    ),
  })

const update = placements.update((model, message) =>
  message._tag === 'UrlChanged'
    ? {
        model,
        commands: [{ name: 'SelectFromUrl', effect: Effect.succeed(selectFrom(message.url)) }],
      }
    : { model },
)

Out, as one of the parent's own Subscriptions, given to placements.subscriptions(selectionUrl):

ts
const selectionUrl = Subscription.make<Model, Message>()(entry => ({
  selectionUrl: entry(
    { block: Schema.Option(Schema.String) },
    {
      modelToDependencies: model => ({
        block: PageForm.control('document').field(model.page).value.selected,
      }),
      dependenciesToStream: ({ block }) =>
        Option.match(block, {
          onNone: () => Stream.empty,
          onSome: id => Stream.fromEffect(Navigation.replaceUrl(`?block=${id}`)).pipe(Stream.drain),
        }),
    },
  ),
}))

The Builder refuses an id its page lacks, so a link opened before the page is loaded selects nothing: send selectFrom again once the form is filled. Nothing selected leaves the URL as it is, so that link is not lost while the page loads. An application with other query parameters builds the URL with them. test/url.test.ts runs this recipe, and the CMS example's page editor (examples/cms, at /pages) routes this way, with the page and the Block both in the address.

Previewing a context

A page whose Catalog declares a context shows some nodes only under conditions (Conditions). The Builder keeps what the author previews the page as in preview, by context key, starting from the preview it was made with:

ts
Builder.make('PageBuilder', { catalog: Site, renderer, starters, preview: { audience: 'guest' } })

The page is drawn in edit mode with preview as its context, so a node hidden for it is still drawn, marked data-composition-hidden. Previewing changes nothing in the page and is not an edit.