@fusebase/markdown-viewer
v0.1.8
Published
The portable half of the FuseBase v3 markdown note editor: everything that knows about our directive syntax without knowing about ProseMirror, the DOM or Next.
Readme
@fusebase/markdown-viewer
The portable half of the FuseBase v3 markdown note editor: everything that knows about our directive syntax without knowing about ProseMirror, the DOM or Next.
What lives here
directive-widget-attrs.ts— validation and normalisation of every directive attribute. Import-free on purpose, so it stays usable from Node.directive-mdast.ts— how each directive is written in markdown.directive-dom.ts— how each directive is drawn, as data. The editor's ProseMirror nodes hand these straight back to ProseMirror;<MarkdownViewer>turns them into hast. One description, two renderers.directive-hast.ts+markdown-viewer.tsx— the browser entry point.mermaid.ts— the diagram renderer and its theme, behindawait import().
Entry points
| Import | What it is |
| ------------------------------------------ | ------------------------------------------------------------- |
| @fusebase/markdown-viewer | the dependency-free layer: attributes, mdast specs, DOM specs |
| @fusebase/markdown-viewer (node condition) | the above plus parseMarkdown() / serializeMarkdown() |
| @fusebase/markdown-viewer (browser condition) | the above plus <MarkdownViewer> |
They are three separate source files (src/index.ts, src/node.ts, src/browser.ts) rather than one barrel, so a consumer does not load the remark chain unless it imports the entry that needs it.
<MarkdownViewer>
import { MarkdownViewer } from '@fusebase/markdown-viewer';
<MarkdownViewer markdown={note.markdown} className="note-body" />;Read-only, and read-only by construction rather than by a flag: there is no
ProseMirror here at all, so nothing it renders is editable. React is a peer
dependency (>=18) and never bundled.
<MarkdownEditor> — full edit mode — is a later iteration.
The three host-bound directives
::fusebase-dashboard, ::fusebase-iframe and :fusebase-mention need the
dashboard blot, the user-search API and next-intl, so they stay in
the host application. The viewer renders them as a placeholder carrying
data-directive and their attributes; a host that can draw them replaces that
element through the components prop.
Styling
import '@fusebase/markdown-viewer/viewer.css';That is the whole stylesheet a note needs: the --md-* brand map, the callouts,
the directive widgets, the tone table and a prose base for the viewer's md-note
root. It imports tokens.css itself, so a host never needs both.
Every widget rule is scoped to :is(.markdown-note-editor .milkdown .ProseMirror,
.md-note) — the editor and the viewer draw the same widgets from the same
DomSpec, so one file styles both. The prose base is scoped to
.md-note alone, since in the editor Crepe supplies it. Crepe frame overrides stay in the host editor, which imports
this one; the widget settings pencil and its popover are here, because the
popover mounts on <body> and reads the same token map.
A host that renders math also needs KaTeX's own stylesheet — the viewer calls
katex.renderToString but does not ship its CSS:
import 'katex/dist/katex.min.css';The public view
import { MarkdownViewer, PUBLIC_VIEW_COMPONENTS } from '@fusebase/markdown-viewer';
<MarkdownViewer markdown={note.markdown} components={PUBLIC_VIEW_COMPONENTS} />;What the three host-bound directives do when the reader is anonymous — a share
link, a portal, a published site. A dashboard becomes a static placeholder, an
iframe is framed only for an https URL on PUBLIC_EMBED_HOSTS and otherwise
shown as its link, and a mention is the display name as text with no lookup.
See public-components.tsx.
What deliberately does not
The editor node definitions and the three host-bound
directives (::fusebase-dashboard, ::fusebase-iframe, :fusebase-mention)
stay in the host application — they pull in the dashboard blot, the user-search API
and next-intl.
