Authors and comments
Both features belong to the advanced editor (not the simple one) and need a collaborative surface for the user's identity: :collaboration="{ ydoc, provider, user: { id, name, color } }" (AuthorIdentity). Entries: @skaile/forge-editor/authors/vue or /authors/react (the framework-free /authors and /authors/schema hold the model), @skaile/forge-editor/comments/vue or /comments/react (framework-free /comments, /comments/schema); CSS styles/authors.css and styles/comments.css (after core.css; the Nuxt module adds them).
import { authorsFeature } from '@skaile/forge-editor/authors/vue'
import { collabFeature } from '@skaile/forge-editor/collab/vue'
import { commentsFeature } from '@skaile/forge-editor/comments/vue'
const preset = defineEditor({ features: [coreContentFeature(), coreUiFeature(), collabFeature(), authorsFeature(), commentsFeature()] })
On React the same list comes from /authors/react, /collab/react and /comments/react, and collaboration is a prop of the same name.
authorsFeature() (no dependency, inert without a user)
- Every inserted text run carries an
authormark{ id, name, color }(persisted, invisible, not in Markdown). Text typed inside someone else's run takes the typist's mark; remote edits and Yjs undo/redo keep the original author.documentAuthors(doc)lists the authors with their character counts;currentAuthor(editor)is the user the editor writes as. - The colouring mode is a per-user preference:
editing.authorColors(off by default) andediting.authorColorStyle('background'tint or'text'colour). It is set in the „Autorenfarben“ settings tab and in the toolbar's palette button, whose popover also holds a legend of the document's authors (AuthorColorsPanel).
commentsFeature({ model? }) (requires collab unless model is given)
model: (getEditor) => CommentsModelbacks the comments with the host's own data (REST threads, a database) instead of the Y.Doc: the feature then requires nocollab, theCommentsplugin reads the user, the thread ids and the ranges frommodel.state(a snapshot withavailable,canComment,user,threads,ranges, …), anduseComments(editor)resolves the model of the editor's surface (commentsModelOf(ctx); the factory runs once for the plugin and once per hook consumer, so share state outside it).CommentsSnapshot.canCommentgates the selection bubble: the default model saysavailableand editable, a host model may allow readers.CommentsModel,CommentsSnapshot,CommentsActions,DocRangeare exported from/comments.- Select text and choose „Kommentar“ in the selection bubble; others reply, anyone resolves or reopens a thread („Erledigt“ threads lose their highlight), authors edit or delete their own comments. A thread whose text was deleted stays as orphan („Textstelle gelöscht“). Threads live in the Y.Doc map
comments(createCommentsModel(getEditor)is the framework-free model;useComments(editor)is the thin Vue hook, returning theCommentsStore, and the React binding has the same hook), the range is thecommentmark (the schema mark is always registered, so stored marks round-trip even without the feature's UI). editing.commentsLayout('margin'default,'inline') is a per-user setting in the „Kommentare“ settings tab:margin(„Randspalte“): cards beside the commented text. The host placesCommentsMargin(propseditor,container,open) inside the editor's scroll container (relative flex, editor wrappermin-w-0 flex-1) and bindsv-model:comments-openon the surface (React:commentsOpen/onCommentsOpenChange). The toolbar's „Kommentare“ button andMod-Shift-mtoggle it; opening it closes the AI sidebar and vice versa.- Layout „Aktiver Faden“: the active thread (or the draft composer) is pinned exactly to the top of its text's line and shows in full; every other open thread is a one-line card (author, excerpt, reply count) that moves up or down out of its way, as close to its own line as its neighbours allow, never overlapping. Without an active thread the cards stack top-down at their lines. A click on a card activates it and reveals its text. Resolved threads are hidden behind the „Erledigt (n)“ toggle at the bottom of the column. A thread whose text was deleted stays as a greyed-out card at the deletion point, labelled „Text gelöscht“ (its position comes from the thread's anchor, a Yjs relative position kept in the Y.Map; a thread without one goes to the end); undoing the deletion attaches it to its text again. The rule is framework-free and testable on its own:
layoutMargin(entries, activeId, { gap, minTop })maps measured cards (MarginEntry: id, line top ornull, height) to tops,marginCards(...)builds the cards in document order, andcreateMarginController(source)does the measuring (coordsAtPos, card heights, one layout per animation frame); bothCommentsMargincomponents only render it (all from@skaile/forge-editor/comments). inline:CommentsPopover(mounted by the feature as an overlay) opens at the commented text; the toolbar'sCommentsListMenulists all threads with the open count. Esc closes the popover.
- Without collaboration the toolbar button is disabled with an explanation. Switching the layout closes any open thread or draft.
<div ref="scrollEl" class="relative flex overflow-auto">
<div class="min-w-0 flex-1">
<AdvancedEditor v-model:comments-open="commentsOpen" :collaboration="{ ydoc, provider, user: { id, name, color } }" />
</div>
<CommentsMargin :editor="editor" :container="scrollEl" :open="commentsOpen" />
</div>
React, same layout:
<main ref={setScrollEl} className="relative flex overflow-auto">
<div className="min-w-0 flex-1">
<AdvancedEditor commentsOpen={commentsOpen} onCommentsOpenChange={setCommentsOpen} collaboration={{ ydoc, provider, user: { id, name, color } }} />
</div>
<CommentsMargin editor={editor} container={scrollEl} open={commentsOpen} />
</main>
bun run check:editor-entries proves the isolation with the authors and comments fixtures; the marks are part of the server schema snapshot.
Media, mentions and transcript features
Add image, audio, video and file blocks, @ mentions and # tags, and speaker transcripts as independent features, with uploads, metadata lists and server schemas.
Themes (Obsidian)
Render an Obsidian theme on the whole app, choose, upload and delete themes, and know the limits.