You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 387cdbf
Browse filesBrowse the repository at this point in the historyBrowse files
- `BlockView.placement: 'after'` keeps the block as the editor draws it,
editable, and places the view's element after it, like a widget, with
the same lifetime and reveal rule as a replacing view: for UI that adds
to the text instead of standing in for it, such as a preview card for
a link that sits on its own line of a paragraph.
- `inlineLines(content)` gives the spans between a block's line breaks,
so a host can match one line. The line stays a span and the parser
never splits the paragraph; the architecture doc records why.
`blockView` draws whole blocks the app wants to show its own way, such as a paragraph that is one bare link shown as a preview card with data from the app's server. `match(block)` picks the blocks, and `render(block, element, { editor })` draws one into `element`, a div the editor owns and keeps while the block is unchanged: typing elsewhere, a new selection, `redecorate()` and a switch to read-only never call `render` again, so app UI mounted into the element, such as a React portal, stays. `render` runs again for a block whose content changed and for a new block, and may return a function, which the editor calls when it lets the element go: the block was removed or changed, `refreshViews()` ran, or the editor was destroyed. Returning `false` leaves the block as the editor draws it. While editing, the block shows as the editor draws it, editable, when the selection is in it, and a click on the view puts the caret there, as for a formula; read-only always shows the view. `refreshViews(name)` with the extension's name draws its blocks again.
253
253
254
+
`placement: 'after'` keeps the block as the editor draws it, editable, and places the element after it instead, like a widget: for UI that adds to the text rather than standing in for it, such as a card for a link that sits on its own line of a paragraph. `inlineLines(content)` gives a block's lines, the spans between its line breaks, so `match` can look at one line. The element is still withheld while the selection is in the block, so a link being typed is not previewed on every keystroke, and clicks on it are the app's, not the editor's.
Copy file name to clipboardExpand all lines: docs/architecture.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -257,7 +257,7 @@ That is 64 places and 45 distinct icons. `chevronDown`, `chevronRight`, `selecte
257
257
-**Key bindings** (`keymap`): see "Input pipeline".
258
258
-**Decorations** (`decorations`): a function from one text container (a paragraph, heading, list item, quote or table cell) to CSS classes on ranges of its text. The context lists the ranges of inline code, links, images and tokens, which `textDecorator` skips. Decorations are computed while rendering a block, so they only run for blocks that changed, and they are wrapped in `span.mbe-deco` elements that the DOM-to-model mapping walks through like mark elements. `codeDecorations` does the same for the code of a code block (syntax highlighting); other decorators never see code blocks. `editor.redecorate()` re-renders every block with fresh decorations, after the composition ends if one is running, for decorations that read state outside the document.
259
259
- **Views** (`views`): a function that renders the source of a token or raw block of one syntax into an element the renderer provides (a span or a div). A token with a view renders as one `contenteditable="false"` atom that counts as many characters as its source (`data-source` holds it), so DOM-to-model mapping and the IME read-back still see the source. Inline raw HTML has no token, so the `html` view takes runs instead (`htmlRuns` in `view/dom.ts`): an opening tag span through its matching closing tag span in the same container, or one tag span alone; a run is one atom as long as its text, and it reveals like a token. The view gets only the run's tags, with a slot marker (`slotMarker`, private-use characters) where text between them was; the renderer renders that text itself, with its marks, decorations and tokens, and puts it where the view left the marker (`fillSlots`). An HTML section renders the same way (`splitHtmlSection`): its source is parsed again, the HTML blocks are joined with one marker per run of Markdown blocks, and each run renders as the editor's blocks in a `.mbe-flow` container. Those blocks are content of the section, not blocks of the document: the renderer strips their block ids, content markers and `contenteditable`, and makes their inputs inert. A marker that a Markdown-rendering view wraps in a paragraph replaces the paragraph; a slot whose marker the view dropped follows the rest. The stylesheet's block rules select `:is(.mbe-root, .mbe-flow) >`, so section blocks look like top-level ones, and a re-rendered section keeps which `<details>` the reader opened. Raw Markdown blocks (`syntax: 'markdown'`) have a built-in view, `MARKDOWN_VIEW` in `view/dom.ts`, that the renderer draws itself with the editor's blocks (`renderMarkdown`): it parses the source again, renders each run of nodes the model can hold as blocks, and draws a quote or list item the model cannot hold as a container (`blockquote.mbe-quote`, or `.mbe-list-item` with its marker and a `.mbe-flow` of its blocks), recursively. Like an HTML section's Markdown, these blocks are content, not blocks of the document. An extension's `markdown` view replaces it. A raw block with a view renders as a `contenteditable="false"` block with no content element, like a divider, and so does a code block whose language has a `code:<language>` view; both reveal their source the same way. A view may return a promise: the renderer caches the element it filled in at once, records which blocks show it, and when the promise settles asks the editor to render those blocks again (on the next frame, or when a composition settles), from the finished element or, after a rejection, as source. Which views show their source is a pure function of the state (`view/reveal.ts`): a token that either end of the selection touches (edges included) and a raw block that holds either end, only while editable and focused. The renderer keeps each block's revealed views next to its element and rebuilds a block when they change, the same way it rebuilds a block whose object changed. Every render goes through `Editor.paint`, which recomputes them; selection, focus and editability changes call `refreshRevealed`, which paints only when they differ. While a mouse button is down nothing changes until mouseup, so a drag is not disturbed. A click on a view puts the caret in its source, and an arrow key that would skip a rendered block steps into its source instead. Rendered elements are cached by source (least recently used, 500 entries) and cloned, so typing next to a formula does not re-run the renderer; listeners a view attaches are lost in the clone, so a view's interaction is CSS or a listener on `editor.root` from `onCreate`. A view gets the editor as its last argument: its element is not in the page when it runs, so a view that lays out text (a diagram) reads the font from `editor.root` and measures on a canvas. The renderer notes which blocks showed each cached, failed or declined view (`viewBlocks`); `dropViews(syntax?)` forgets those views and returns the blocks, and `Editor.refreshViews(syntax?)` renders only them again (after a composition, through `unsettled`, as settled promises are). A promise still pending when its view is dropped settles into nothing: a later render of the same source owns the cache key. The editor refreshes every view itself when a theme change touches a font token (`FONT_PROPERTIES`), since the colors are CSS variables but a measured layout is not.
260
-
- **Block views** (`blockView`): an extension draws whole blocks of the document that its `match` picks, for UI that needs data from outside the document, such as a bare link shown as a preview card. Unlike a syntax view, the result is not cached by source and cloned: the renderer keeps one element per block ID (`hosted` in `BlockRenderer`) while the block object is the same, and hands the same element back on every render of that block, so app UI mounted into it (a React portal) survives renders the block itself did not cause. The element is the child of a fresh `div.mbe-block.mbe-view-block.mbe-block-view` root, which carries the editor's own classes and attributes, so the app's element is its own. A block object that changed (its content) gets a new element, and the old one's dispose function (what `render` returned) runs; so does a block that is gone after a render, every block on `dropViews` (so `refreshViews(name)`, with the extension's name, draws them again) and on destroy. A view that declined (`false`) is remembered for that block object, so it is not asked again until the block changes. A hosted block reveals like a raw block with a view: `revealedViews` marks it `REVEAL_BLOCK` when the selection is in it, and it then renders as the editor draws it, editable, while the element waits detached; `viewEntry` puts the caret at its start or end. Blocks inside an HTML section or raw Markdown view are content, not blocks of the document, and never go through a block view, nor does source mode's text block.
260
+
- **Block views** (`blockView`): an extension draws whole blocks of the document that its `match` picks, for UI that needs data from outside the document, such as a bare link shown as a preview card. Unlike a syntax view, the result is not cached by source and cloned: the renderer keeps one element per block ID (`hosted` in `BlockRenderer`) while the block object is the same, and hands the same element back on every render of that block, so app UI mounted into it (a React portal) survives renders the block itself did not cause. The element is the child of a fresh `div.mbe-block.mbe-view-block.mbe-block-view` root, which carries the editor's own classes and attributes, so the app's element is its own. A block object that changed (its content) gets a new element, and the old one's dispose function (what `render` returned) runs; so does a block that is gone after a render, every block on `dropViews` (so `refreshViews(name)`, with the extension's name, draws them again) and on destroy. A view that declined (`false`) is remembered for that block object, so it is not asked again until the block changes. A hosted block reveals like a raw block with a view: `revealedViews` marks it `REVEAL_BLOCK` when the selection is in it, and it then renders as the editor draws it, editable, while the element waits detached; `viewEntry` puts the caret at its start or end. Blocks inside an HTML section or raw Markdown view are content, not blocks of the document, and never go through a block view, nor does source mode's text block. With `placement: 'after'` the block renders as the editor draws it and the element follows it in a `div.mbe-block-view.mbe-block-after` wrapper, `contenteditable="false"`, placed by the renderer between the block's element and its widgets (`afterView`), with the same lifetime and the same reveal rule as a replacing view: while the selection is in the block the wrapper waits detached, so a link being typed is not previewed on every keystroke, and when the selection leaves the same element is placed again. The block's own element never carries `mbe-view-block`, so the caret moves through it as through any paragraph, and the wrapper is app UI like a widget: events from it are not editor input (`APP_UI` in `editor.ts`), and a click on it reveals nothing. This is the editor's answer to a link on its own line of a paragraph: the line stays a span (a card is block-level, and a span has no identity to keep an element by), the parser never splits the paragraph (the model would then disagree with every other renderer, and a save after an edit would turn the line break into a blank line), and the app finds the line with `inlineLines`, which splits inline content at `"\n"`.
261
261
-**Widgets** (`addWidget`): elements placed after a block element by the renderer, `contenteditable="false"`, and ignored by input handling. A widget follows its block's ID; when a change removes that block, the widget moves to the nearest earlier remaining block (after `setMarkdown`, the block at the same index). Widgets are not in the document, so they are never saved and undo does not touch them.
262
262
-**Paste and drop** (`handlePaste`, `handleDrop`): run before the editor's own handling. `fileHandler` is built on them. Dropped text with no handler is parsed as Markdown and inserted at the drop position.
263
263
-**Commands** are pure functions `(state) => state` in `commands.ts`; `editor.run(fn)` applies any of them, and `editor.commands` binds the common ones. `run` treats a command that rebuilt the block list without changing any block as a no-op, so `commands.*` return false when nothing changed. Two of them carry editing rules an app would otherwise reimplement from model details: `insertFragment` with `block: true` replaces an empty paragraph at the target instead of leaving an empty line above the inserted blocks, and `insertToken` inserts `#tag`-like text at the selection's focus spaced as a word, reusing a space that follows; `commands.insertToken` also refuses during IME composition, when an insert would break the composed text.
0 commit comments