Éditeur : constructeur, méthodes, événements
Deux objets, et la frontière entre eux est tout le modèle :Editor est le document et la seule façon de le changer, sans une ligne de DOM ; EditorView est une projectionde cet objet dans une page. Supprimez la vue, le document est toujours là.
Editor — le cœur, sans DOM
Rien ici n'importe le navigateur, et c'est ce qui laisse le même éditeur tourner dans un onglet, dans un test Node, dans un plugin Obsidian et dans une fenêtre Tauri.
Propriétés
| Nom | Type | Défaut | Description |
|---|---|---|---|
| doc* | Doc | — | The live document. Read it freely; write only through Editor.dispatch. |
| plugins* | PluginRegistry | — | The block plugins this document is edited under. On the *model*, not only on the view, because a plugin's document invariants have to hold with no view mounted at all — a CLI import, a server-side migration, a collaborative peer applying a remote change. It is the same reason ProseMirror keeps plugins in the state rather than in the view. |
| schema* | Schema | — | Which block types exist, and what they may contain. |
| selection* | Selection | — | Where the caret or the block selection is, or `null` when nowhere. |
| validation* | ValidationMode | ```ts
'warn'
``` | How invalid blocks are surfaced. `warn` by default rather than `throw`: a validation bug that takes down a user's editor mid-sentence is worse than the state it was guarding against, since the document is still in memory and still savable. Tests and CI set `throw`. |
| redoDepth* | number | — | |
| undoDepth* | number | — | |
| dispatch* | void | — | |
| on* | function | — | |
| onSelection* | function | — | |
| redo* | boolean | — | |
| setSelection* | void | — | |
| undo* | boolean | — | |
| use* | this | — |
Constructeur et méthodes
dispatch
dispatch(build: function, opts: DispatchOptions): voidApply a transaction: the one way the document changes.
Paramètres
build: function— Fills the transaction.opts: DispatchOptions— `origin` labels the change for listeners; `selection` sets the selection atomically with the edit; `addToHistory: false` keeps it out of undo; `coalesce` merges consecutive dispatches sharing a key within 500 ms into one undo step, which is how typing undoes by word rather than by keystroke.
Retourne
void
editor.dispatch(
(tx) => tx.op({ type: 'delete_block', id }),
{ origin: 'ui' },
)on
on(listener: function): functionEvery change, after it has been applied.
Paramètres
listener: function
Retourne
function
const off = editor.on((change) => {
if (change.origin !== 'history') save(docToJSON(editor.doc))
})onSelection
onSelection(listener: function): functionCaret and block-selection moves, which are not edits and so never reach Editor.on.
Paramètres
listener: function
Retourne
function
redo
redo(): booleanRedo one step.
Retourne
boolean
setSelection
setSelection(sel: Selection, origin: string): voidMove the selection without a transaction, because a caret move is not an edit: it is not undoable and it does not mark the document dirty.
Paramètres
sel: Selectionorigin: string
Retourne
void
undo
undo(): booleanUndo one step, restoring the selection it was made from.
Retourne
boolean
use
use(plugin: BlockPlugin): thisRegister a block plugin: its schema becomes part of this editor's, and its normalization runs on every transaction.
Paramètres
plugin: BlockPlugin
Retourne
this
import { tableBlocks } from '@nbe/blocks-table'
const editor = new Editor({ plugins: tableBlocks })Écouter
Trois canaux, séparés parce qu'ils ne signifient pas la même chose. Chacun renvoie sa fonction de désabonnement : un écouteur qui survit à son hôte est une fuite.
// une transaction, une notification — un collage de quarante blocs
// n'appelle ce listener qu'une fois
const offChange = editor.on((change) => {
change.origin; // 'ui' | 'paste' | 'history' | ce que l'appelant a passé
change.ops; // les opérations appliquées
change.dirty; // les ids des blocs touchés
change.selectionSet;
});
// déplacer le caret n'est pas une édition : c'est un autre canal
const offSelection = editor.onSelection((selection, origin) => {
render(selection);
});
// la vue publie la fin d'un geste pointeur, une fois qu'il est retombé
const offGesture = view.onGestureEnd((name, committed) => {});
offChange(); offSelection(); offGesture();Écrire
dispatch est le seul chemin. Il applique la transaction, enregistre son inverse pour l'annulation, valide uniquement les blocs touchés, puis notifie une fois. Une mutation qui le contourne n'est ni annulable, ni validée, ni observée.
import { insertText, toggleMarkRange, moveBlocks } from '@nbe/core';
// une commande : du sucre au-dessus de dispatch
insertText(editor, 'bonjour');
// ou la transaction à la main
editor.dispatch(
(tx) => {
tx.op({ type: 'delete_block', id });
tx.op({ type: 'insert_block', block, index: 0 });
},
{ origin: 'ui', selection: textCaret(block.id, 0) },
);
editor.undo(); // → false s'il n'y avait rien à annuler
editor.redo();EditorView — la projection
La vue crée son propre élément dans le conteneur que vous lui donnez, y rend le document, attache les fonctionnalités demandées, et détache tout àdestroy(). Le contenu est non contrôlé : le document initial est lu une seule fois: le remonter à chaque rendu détruirait le caret, la sélection et l'historique.
Propriétés
| Nom | Type | Description |
|---|---|---|
| content* | HTMLElement | The `.nbe-editor` element this view created and owns. |
| editor* | Editor | The model this view projects. |
| gesture* | ActiveGesture | null | What pointer gesture is running, published by the gesture router. This is the state that replaced three wall-clock windows: modules ask what is happening instead of guessing from how long ago something happened. |
| gestureEndListeners* | Set<function> | Notified once a pointer gesture has fully settled. See `onGestureEnd`. |
| labels* | EditorLabels | Resolved on-screen strings: the defaults with `options.labels` merged in. |
| lastTextCaret* | Point | null | Where the caret last was in text. Opening a menu or selecting a block replaces the live selection, so anything that needs to act "where the user was typing" — table row/column actions, for one — reads this instead. |
| options* | EditorViewOptions | The options it was mounted with, verbatim. |
| plugins* | PluginRegistry | Per-editor, never module-global: two editors may have different sets. |
| readOnly* | boolean | True when the view was mounted read-only. |
| recognizers* | GestureRecognizer[] | Pointer gestures in precedence order. Features contribute to this list — `unshift` to outrank the defaults — and the router reads it at press time, so who wins a contested press is data rather than attach order. |
| topology* | EditableTopology | Where the editable boundary sits: one host per block, or one at the root. |
| composing* | boolean | |
| announce* | void | |
| blockEl* | HTMLElement | null | |
| destroy* | void | |
| focusBlock* | void | |
| leafEl* | HTMLElement | null | |
| onGestureEnd* | function | |
| onRender* | function | |
| renderAll* | void | |
| slot* | HTMLElement | |
| syncDomSelection* | void | |
| whenComplete* | Promise<void> |
Constructeur et méthodes
announce
announce(message: string): voidSay something in the live region, for screen readers.
Paramètres
message: string
Retourne
void
blockEl
blockEl(id: string): HTMLElement | nullThe element rendering a block, or `null` if it is not on screen.
Paramètres
id: string
Retourne
HTMLElement | null
destroy
destroy(): voidDetach every feature, stop listening to the editor, and remove the element.
Retourne
void
focusBlock
focusBlock(id: string, offset: number): voidPut the caret in a block, at a character offset, and focus it.
Paramètres
id: stringoffset: number
Retourne
void
leafEl
leafEl(id: string): HTMLElement | nullThe editable leaf inside a block, where its text lives.
Paramètres
id: string
Retourne
HTMLElement | null
onGestureEnd
onGestureEnd(cb: function): functionCalled once a pointer gesture has fully settled.
Paramètres
cb: function
Retourne
function
onRender
onRender(cb: function): functionCalled after the view has written to the DOM.
Paramètres
cb: function— Receives the blocks whose elements were replaced, or `null` when the whole surface was rebuilt.
Retourne
function
renderAll
renderAll(): voidRebuild the whole surface from the document.
Retourne
void
slot
slot(where: SlotName): HTMLElementA place for chrome that is not a block.
Paramètres
where: SlotName
Retourne
HTMLElement
const feature: EditorFeature = {
name: 'word-count',
attach(view) {
const el = view.slot('bottom')
…
},
}syncDomSelection
syncDomSelection(scrollIntoView: boolean): voidPush the model's selection into the browser, after changing it in code.
Paramètres
scrollIntoView: boolean
Retourne
void
whenComplete
whenComplete(): Promise<void>Resolves once a streamed opening render has finished.
Retourne
Promise<void>
Les options de montage ont leur propre page :Configuration.