Skip to content

@calternal/editor

Public Markdown and Note editor surface, including the split Card layer (DESIGN §9, #632).

Property Type Description
active boolean True while any chrome interaction (handle focus/grab/drag, either pipeline) is in flight. NoteEditor.svelte folds this into its reading derivation: reading = !focused && !chromeInteraction.active.

hold(): () => void

Arm (or extend) the window. Returns an idempotent release — safe to call twice (mirrors holdSelectionAssassin’s own contract; Svelte effect teardowns and pointer cleanup paths can both fire in edge cases).

() => void


Title + body a Composer text becomes.

Property Type Description
body string Markdown body after the H1; may be empty.
title string Plain title (becomes the note H1, frontmatter title: and filename slug).

The event an “Add note” session is bound to.

Property Type Description
blockId string The event’s ^block-id, or ‘’ for an id-less event (addressed by the start/title/occurrence below; the attach write gives it a deterministic id).
date string YYYY-MM-DD day file the event lives in.
occurrence number Position among id-less events sharing start + title (0 for id’d events).
siblings number How many id-less events shared start + title when the card rendered.
start string Event start HH:MM, for the chip label.
tags string[] Event tags (normalized, no #), for the chip’s colour dot.
title string Event title, for the chip label.

Props for the standalone Svelte editor surface. Hosts own storage and choose when to call publishMarkdown() through the component instance.

Property Type Description
ariaLabel string Accessible name for the Markdown surface.
autofocus? boolean -
bareUrlPaste? boolean Keep standalone URL paste as bare Markdown and allow immediate Undo to make a link (#1151).
editable? boolean Mount the shared Markdown surface as a read-only document.
editorAttributes? Record<string, string> Host-owned DOM attributes, merged with the editor’s accessible defaults.
linkHTMLAttributes? Readonly<Record<string, string>> Optional link DOM attributes; no Markdown or schema attributes are added.
noteCards? boolean Render shared Card surfaces for --- section breaks in a Note.
onCardLayout? (segmented) => void Reports whether --- currently splits the Note into multiple Cards.
onEditorReady? EditorReady -
onImageResolve? ImageSourceResolver Display-only URL mapping; stored Markdown remains unchanged.
onLinkClick? EditorLinkClick -
onLoadError? (error) => void -
onMarkdownChange? MarkdownChange Called only when the host explicitly publishes at its save boundary.
onTransaction? EditorTransaction -
slashItems? SlashItem[] Append host-owned commands to the shared slash commands.
source EditorSource Initial source. Changing it requires remounting the surface.

Property Type Description
group FormatGroup -
id string Stable id — also the FormatIcon switch key AND the automation/test hook (buttons expose data-cmd={id}). Prefer this over a CSS class for selectors, per CLAUDE.md’s a11y/addressability rule.
label string Accessible name (the button’s aria-label).
shortcut? string Stable id shared with a host’s shortcut registry.
text? string Optional short text glyph for consumers that render a command label.

isActive(editor): boolean

True when this mark/block is active at the current selection — drives aria-pressed + the pressed visual.

Parameter Type
editor Editor

boolean

run(editor): void

Apply/toggle the format. Always begins .chain().focus() so applying from a toolbar (whose button took the pointer) restores the caret and toggles in place rather than against a blurred, selection-less editor.

Parameter Type
editor Editor

void


Sanitized text and HTML from an image paste. The host receives source strings here and image bytes in a separate File list. Temporary image URLs are removed from inserted HTML. Issue #1036; DESIGN §9.

Property Type Description
hadImages boolean True even when HTML has an image with an unusable source.
html string | null Sanitized clipboard HTML. Keep HTTP(S) images only when no image bytes exist.
pasteRange? object Range inserted by the synchronous rich paste, used to map image URLs to nodes.
pasteRange.from number -
pasteRange.to number -
sources string[] Image URLs from HTML when the clipboard did not provide file bytes.
text string Plain text is used when the clipboard has no remaining rich content.

The shared “a block moved” shape for a live-region announcement — produced by whichever caller commits a move (touchDrag.ts today; the keyboard path, Task 11, will produce the SAME shape from its own commit site) so NoteEditor.svelte’s announcement wording is governed by ONE contract regardless of which input modality drove the move. index/count are 1-based/total for a natural “position N of M” reading; dir is which way the block moved (not currently rendered into the wording, but kept for a future richer announcement — e.g. distinguishing “up” vs “down” — without a shape change).

Property Type
count number
dir -1 | 1
index number
summary string

Property Type
content PMNode[]
type "doc"

Property Type
attrs? Record<string, unknown>
content? PMNode[]
marks? PMMark[]
text? string
type string

Property Type Description
hint string -
icon string Inline SVG path markup for the leading icon (drawn at 18x18, currentColor).
keywords string[] -
run (editor, range) => void -
title string -

BlockMoveScope = "note" | "document"

‘note’: the leading H1 is the note title — never selectable and never crossed. ‘document’: every top-level block is ordinary (day entry and the editor harness). Same meaning as blockMoveBounds’ scope.


EditorLinkClick = (href, event) => void

Parameter Type
href string
event MouseEvent

void


EditorReady = (editor) => void

Parameter Type
editor FileIcon

void


EditorSource = { kind: "markdown"; value: string; } | { awareness?: unknown; doc: unknown; fragment?: unknown; kind: "yjs"; }

A document source is exclusive: Markdown is supported in Phase 1, while the Yjs branch reserves the stable shape for the collaboration phase.


EditorTransaction = (transaction, editor) => void

Parameter Type
transaction FileIcon
editor FileIcon

void


FormatGroup = "inline" | "block"


ImageSourceResolver = (storedSource) => string | null | undefined

Parameter Type
storedSource string

string | null | undefined


MarkdownChange = (markdown, editor) => void

Parameter Type
markdown string
editor FileIcon

void


NoteCardsLayer = ReturnType<typeof NoteCardsLayer>

Paint shared Card surfaces behind --- sections in a Note. The layer does not wrap ProseMirror blocks or receive pointer input. Use it with the Note cards decoration so selection and block positions stay fixed. Issue #632; DESIGN §9.


PMMark = { type: "bold"; } | { type: "italic"; } | { type: "code"; } | { type: "strike"; } | { type: "underline"; } | { type: "highlight"; } | { attrs: { href: string; }; type: "link"; } | { attrs: { raw: string; }; type: "escapedWikilink"; }


ShortcutBinder = <T>(id, command) => Record<string, T>

The editor owns its keyboard actions, while each host may own shortcut preferences. This small seam lets the calternal.js app keep using its shared registry without making the editor package depend on app settings or stores.

Type Parameter
T
Parameter Type
id string
command T

Record<string, T>


WikilinkLinkFormatter = (path, text, anchor) => string

Parameter Type
path string
text string
anchor string | null

string


WikilinkResolution = { kind: "ok"; path: string; title?: string; } | { kind: "missing" | "ambiguous"; }

Host-owned result for resolving one wiki reference. Resolution and URL construction stay outside the editor package because they depend on the host’s note index and navigation contract.


WikilinkResolver = (reference) => WikilinkResolution

Parameter Type
reference string

WikilinkResolution

const ALL_COMMANDS: FormatCommand[]

Complete command inventory retained for callers that need every transform.


const AttachmentImage: FileIcon<AttachmentImageOptions, any>


const BLOCK_COMMANDS: FormatCommand[]


const Callout: FileIcon<any, any>


const CollaborationHistoryKeys: FileIcon<any, any>


const INLINE_COMMANDS: FormatCommand[]


const NoteCardsLayer: Component

Paint shared Card surfaces behind --- sections in a Note. The layer does not wrap ProseMirror blocks or receive pointer input. Use it with the Note cards decoration so selection and block positions stay fixed. Issue #632; DESIGN §9.


const prefersReducedMotion: ReducedMotion


const SINGLE_LINE_TITLE_WORDS: 7 = 7

How many words a single-line text contributes to its title.


const SLASH_ITEMS: SlashItem[]

bindEditorShortcut<T>(id, command): Record<string, T>

Default keymap for a standalone editor consumer without a host registry.

Type Parameter
T
Parameter Type
id string
command T

Record<string, T>


calloutLabel(kind): string

Sentence-case label shared by the editable callout node and read view.

Parameter Type
kind string

string


calternalExtensions(opts?): AnyExtension[]

Build the shared Markdown schema and interactions (DESIGN §9). Mount and decoration updates keep the loaded tree exact; new blocks require an explicit editing action (#661). Both the live and fallback hosts use this preset.

Parameter Type
opts? ExtensionOpts

AnyExtension[]


composerNoteTargetKey(target): string

Stable identity for an event-bound Composer Note session.

Parameter Type
target ComposerNoteTarget | null

string


composerNoteTargetLabel(target): string

Chip text: 16:00 · Call with the design team. CSS truncates, never this.

Parameter Type
target ComposerNoteTarget

string


composerTextToNote(raw): ComposerNoteDraft | null

Map Composer text to a note (the user’s rule for “Add note”):

  • Multi-line: the FIRST non-blank line is the title; every later line is the body, verbatim (only blank edge lines are trimmed, so indentation and internal blank lines survive).
  • Single line: the title is the first 7 words; the body keeps the ENTIRE line so nothing the user typed is lost. When the line has 7 words or fewer the title already holds all of it, so the body stays empty instead of repeating the title.
  • A leading ATX heading marker (# , ## …) is dropped from the TITLE only: the H1 is written by the note builder, and # # Title would be noise. Other markdown characters stay; the core escapes them where they would break the attachment link text.
  • Whitespace-only text returns null: an empty composer never creates a note.
Parameter Type
raw string

ComposerNoteDraft | null


convertWikilinks(md, resolve, formatLink): string

Convert every [[ref]] / [[ref|alias]] / [[ref#anchor]] in md that the host resolver identifies uniquely into an angle-wrapped standard display link. A ref that is unresolved OR ambiguous is left completely VERBATIM — this function never guesses which note a ref means, mirroring resolveNoteRef’s own “never guess” contract (note-resolver.ts). Fenced code blocks (``` or ~~~) are scanned line-by-line and left untouched, so a [[…]] inside an example snippet is never treated as a real reference. Inline code SPANS (single- or double-backtick, e.g. `[[x]]`) are guarded the same way, per-line, via findCodeSpanRanges (I1, T8 review) — see that function’s own comment for the exact CommonMark backtick-run semantics it mirrors.

Line-based (not a single global regex over the whole body) so the fence toggle is trivial to reason about: a fence line flips inFence and is passed through verbatim; every other line gets the wikilink substitution only while inFence is false, and only for matches falling OUTSIDE any inline code span on that line.

Parameter Type
md string
resolve WikilinkResolver
formatLink WikilinkLinkFormatter

string


createChromeInteraction(): ChromeInteraction

Mint a fresh, independent instance — see the class’s own header for why this replaced a module-level singleton. Called once per EditorSurface mount (the real app path) or ad hoc by a bare unit test that doesn’t need cross-pipeline sharing at all.

ChromeInteraction


docToMarkdown(doc): string

Serialize a TipTap doc JSON to a markdown body string.

Parameter Type
doc PMDoc

string


hasWikilinkSyntax(md): boolean

True if md contains anything that could be a [[wikilink]] (either the clean on-disk form or the backslash-escaped form a legacy/live-typed doc may contain — see wikilinkSyntax.ts). A false positive just costs an extra (harmless) convertWikilinks pass; a false negative would mean a typed/pulled wikilink silently never converts, so this intentionally errs toward “maybe” rather than trying to be exact.

Parameter Type
md string

boolean


markdownFromSource(source): string

Return Markdown or reject a reserved source before mounting an editor.

Parameter Type
source EditorSource

string


markdownToDoc(md, work?): PMDoc

Parse Markdown. The schema stores line breaks, not source newline spelling. Source offsets stay off this ordinary editor parse path.

Parameter Type
md string
work? MarkdownSourceWork

PMDoc


markdownToDocWithSourcePositions(md, work?): MarkdownSourceDocument

The editor parser plus exact raw spans for reminder commit-time targeting.

Parameter Type
md string
work? MarkdownSourceWork

MarkdownSourceDocument


noteSlashItems(actions): SlashItem[]

Notes-editor-only slash items: /source and /draw need host actions, so they are injected instead of added to the shared SLASH_ITEMS defaults. Any other EditorSurface mount (the dev editor harness) keeps the plain default. (The day-entry BlockEditor that motivated the split is removed.)

The old /properties item is RETIRED. The block editor has no properties surface now; a future inspector sheet owns that interaction.

Actions are INJECTED (not imported store singletons) so this file stays store-free — slash.ts has zero svelte/store imports today, and reaching into $lib/stores/noteView.svelte directly here would be the first (this keeps the day-entry consumer’s import graph from ever seeing notes-editor view state it has no use for). toggleSource remains the NoteEditor’s async wrapper, while draw receives the editor after the slash range is removed so the host can insert a Drawing block at the current selection.

Parameter Type
actions { draw?: (editor) => void; toggleSource?: () => void; }
actions.draw? (editor) => void
actions.toggleSource? () => void

SlashItem[]

Renames and re-exports FileIcon


Renames and re-exports FileIcon


Renames and re-exports FileIcon


Renames and re-exports FileIcon


Renames and re-exports FileIcon