CMS / foldkit-cms
The editor
Cms.editor is the authoring screen's state: a form, the entry it belongs to,
and the draft that keeps what the author has entered. It is a Bundle, placed
like any other; the view is the form's own.
ts
const Editor = Cms.editor('PostEditor', { content: Posts })
const Base = Bundle.compose({ remote: Remote.Model }).pipe(
Bundle.withMessages(Remote.messages),
Bundle.withChild('editor', Editor.bundle), // its Model and its Messages, in yours
)
// App = Surface.application(Base), and Data = Remote.make({ model: App.model.remote, ... })
const PostEditor = Editor.at({ data: Data, model: App.model.editor })
const Page = Base.pipe(
Bundle.withServices<RemoteClient>(),
Bundle.configure('editor', { onOut: PostEditor.onOut }), // made from Data, so given now
)
const Placed = Page.children.editor
const update = PostEditor.after(Page.placements.update(yourUpdate))
Data.subscriptions({ ...yourSurfaces, ...PostEditor.actives })
Placed.helpers.open(entryId) // resume the draft, else show what is published
Placed.helpers.create(Cms.newEntryId()) // something new; its first save makes the entry
Editor.Message.PublishAsked() // also: ScheduleAsked({ at }), UnscheduleAsked, DiscardAsked,
// RestoreAsked({ revision }), UnpublishAsked, ArchiveAsked, UnarchiveAsked, ReloadAsked, OverwriteAsked
PostEditor.status(model) // Opened | Editing | Saving | Saved | Conflict | Publishing | Published | ...
PostEditor.state(model) // Option: the entry's lifecycle state, as the server last derived it
PostEditor.may(model, 'publish') // whether the server's `allow` lets this principal ask it; false until read
PostEditor.pageId(model) // Option: the row's id, or the entry's until there is a row: what a preview shows under
PostEditor.storedEntry(model) // Option: the entry the server knows, none until something new is first saved- Saving is automatic and is not publishing. Each edit starts a rest (
rest, one second by default), and the edit that is still the last one when its rest ends saves the form as it stands, valid or not. There is no Save button to forget, and a validation error never costs an author their work. - A publish the form's own checks stop is
Incomplete, not whatever the last save said: nothing was sent, the failing fields say why, and the next edit clears it. One waiting for a check still running (an address being looked up) isPublishinguntil the check answers. - What counts as an edit is the form's answer, not a Message tag. The editor
asks
form.authoredChanged(before, after), so a blur, a refusal, or a repeated value starts no rest, while a control the editor has never heard of — a stateful or Bundle-backed one — autosaves exactly like a text field. - A form control backed by a Bundle keeps running inside the editor. Its
Subscriptions and Resources (see
Input.bundle) run while an entry is open and stop when the editor closes. A runtime that runs the editor provides their Resources, as for any Bundle that has some. Editingmeans there are edits that are not saved yet, andOpenedmeans the form is as it was found. A draft the editor filled the form from is on the server already, so it readsSaved.- A draft is listed by what its author called it, valid or not, and
untitleduntil they have. Publish pressed twice asks once. - Publishing submits the form, so its rules and checks decide, and an invalid form publishes nothing and says why in place. What is published is the saved draft, so a publish saves first when the last edit has not.
- Scheduling is a publish promised for later:
ScheduleAsked({ at })submits and saves the form now, and the server keeps the promise. The entry'sstatesays for when, and whether it happened. The form's own submit is always a publish now. - Leaving within the rest.
open,createandclosedrop what is in the form. RunPostEditor.flush(model)first, as the example does: it saves what has not been saved, now, and does nothing when there is nothing to save. - A draft never fails to open. The saved Model is tried first, guarded by the
form's name and
version; then the saved values, key by key, keeping what the form still accepts; then what is published.PostEditor.resumed(model)says which:Model(shown with nothing in flight: a check that was running when it was saved will never answer),Values,Published,Blank, orLost, which is worth telling the author. Bumpversionwhen you change the form incompatibly. - A second author's save is a
Conflict, with the text still in the form.ReloadAskedshows the server's copy;OverwriteAskedsaves over it, based on it. Merging is not attempted. - A taken address lands on the address. A publish refused with
Cms.refusal.field(slugKey, reason)marks the slug's own field invalid, keeping what was typed, and clears when the author edits it. Nothing to wire: the editor reads it withData.refusal. - Restoring a revision replaces what is in the form with that value, as a draft. It publishes nothing.
- Preview is the application's own views. Give the content type
preview, the operations an optimistic publish of a value would show, andPreviewShownlays what is in the form over Remote's store untilPreviewHidden: every Selection and view draws it, edit by edit, and nothing is sent. Only what decodes is shown. It is lifted when the editor closes, reloads or discards. Nopreview, no preview:PostEditor.canPreviewsays which. - Discarding shows what is published again; something never published has nothing left, and the editor closes.
- What the server holds is read from Remote and never copied: a save is based on
the draft's
updatedAtin the store, a publish on the entry'srevision. The server answers each operation with patches, so nothing is refetched to know them. createtakes the id so thatupdatestays pure: make it in a Command or an event handler.