Mermaid diagrams (flowcharts) in notes

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
sarahandClaude Opus 5.5 committed 2026-10-13 10:51:27 +02:00
1 parent b8609363b7
commit 292614c696
5 files changed
+244 -2

No files matched your search

+78
View File
@@ -3,10 +3,14 @@
// > and list markers) hidden. Wherever the cursor is, the syntax comes back so
// it can be edited: a heading's #s on its line, a bold's ** inside it.
// The colours are the same roles as .MarkdownBody (Styles/Theme.css).
// ```mermaid blocks show as their diagram until the cursor goes into them.
import { syntaxTree } from "@codemirror/language";
import { StateField } from "@codemirror/state";
import { Decoration, EditorView, ViewPlugin, WidgetType } from "@codemirror/view";
import { RenderDiagram } from "./Mermaid.js";
class BulletWidget extends WidgetType {
eq() {
return true;
@@ -63,6 +67,42 @@ class CheckboxWidget extends WidgetType {
}
}
// A ```mermaid block drawn as its diagram; clicking it puts the cursor in
// the block, which brings the code back.
class DiagramWidget extends WidgetType {
constructor(Source) {
super();
this.Source = Source;
}
eq(Other) {
return this.Source === Other.Source;
}
toDOM(View) {
const Element = document.createElement("div");
Element.className = "MermaidDiagram cm-LiveDiagram";
Element.dataset.source = this.Source;
Element.addEventListener("mousedown", (Event) => {
Event.preventDefault();
const Line = View.state.doc.lineAt(View.posAtDOM(Element));
const Inside = Math.min(Line.to + 1, View.state.doc.length);
View.dispatch({ selection: { anchor: Inside } });
View.focus();
});
RenderDiagram(Element);
return Element;
}
get estimatedHeight() {
return 200;
}
ignoreEvent() {
return true;
}
}
const Hidden = Decoration.replace({});
const Bullet = Decoration.replace({ widget: new BulletWidget() });
const Rule = Decoration.replace({ widget: new RuleWidget() });
@@ -216,7 +256,45 @@ function BuildDecorations(View) {
return Decoration.set(Ranges, true);
}
// Diagrams replace whole lines, which CodeMirror only allows from a state
// field, not from the plugin above. Without focus info here, a block shows
// as code while the cursor (or selection) is in it, even when unfocused.
function BuildDiagrams(State) {
const Doc = State.doc;
const Ranges = [];
syntaxTree(State).iterate({
enter(Node) {
if (Node.name !== "FencedCode") return;
const Info = Node.node.getChild("CodeInfo");
if (!Info || Doc.sliceString(Info.from, Info.to).trim().toLowerCase() !== "mermaid") return false;
const From = Doc.lineAt(Node.from).from;
const To = Doc.lineAt(Node.to).to;
if (State.selection.ranges.some((Range) => Range.from <= To && Range.to >= From)) return false;
const Code = Node.node.getChild("CodeText");
const Source = Code ? Doc.sliceString(Code.from, Code.to) : "";
Ranges.push(Decoration.replace({ widget: new DiagramWidget(Source), block: true }).range(From, To));
return false;
},
});
return Decoration.set(Ranges, true);
}
const Diagrams = StateField.define({
create: BuildDiagrams,
update(Current, Transaction) {
const TreeChanged = syntaxTree(Transaction.state) !== syntaxTree(Transaction.startState);
if (Transaction.docChanged || Transaction.selection || TreeChanged) return BuildDiagrams(Transaction.state);
return Current;
},
provide: (Field) => EditorView.decorations.from(Field),
});
export const LivePreview = [
Diagrams,
ViewPlugin.fromClass(
class {
constructor(View) {