feat: Add mermaidjs integration (#3679)

* feat: Add mermaidjs integration (#3523)

* Add mermaidjs to dependencies and CodeFenceNode

* Fix diagram id for mermaidjs diagrams

* Fix typescript compiler errors on mermaid integration

* Fix id generation for mermaid diagrams

* Refactor mermaidjs integration into prosemirror plugin

* Remove unnecessary class attribute in mermaidjs integration

* Change mermaidjs label to singular

* Change decorator.inline to decorator.node for mermaid diagram id

* Fix diagram toggle state

* Add border and background to mermaid diagrams

* Stop mermaidjs from overwriting fontFamily inside diagrams

* Add stable diagramId to mermaid diagrams

* Separate text for hide/show diagram
Use uuid as diagramId, avoid storing in state
Fix cursor on diagrams

* fix: Base diagram visibility off presence of source

* fix: More cases where our font-family is ignored

* Disable HTML labels

* fix: Button styling – not technically required but now we have a third button this felt all the more needed

closes #3116

* named chunks

* Upgrade mermaid 9.1.3

Co-authored-by: Jan Niklas Richter <5812215+ArcticXWolf@users.noreply.github.com>
This commit is contained in:
Tom Moor
2022-06-29 08:44:36 +03:00
committed by GitHub
parent e24a5adbd5
commit 9a6e09bafa
9 changed files with 972 additions and 280 deletions

View File

@@ -38,6 +38,7 @@ import { Dictionary } from "~/hooks/useDictionary";
import toggleBlockType from "../commands/toggleBlockType";
import { MarkdownSerializerState } from "../lib/markdown/serializer";
import Mermaid from "../plugins/Mermaid";
import Prism, { LANGUAGES } from "../plugins/Prism";
import isInCode from "../queries/isInCode";
import { Dispatch } from "../types";
@@ -114,7 +115,7 @@ export default class CodeFence extends Node {
],
toDOM: (node) => {
const button = document.createElement("button");
button.innerText = "Copy";
button.innerText = this.options.dictionary.copy;
button.type = "button";
button.addEventListener("click", this.handleCopyToClipboard);
@@ -135,9 +136,30 @@ export default class CodeFence extends Node {
select.appendChild(option);
});
// For the Mermaid language we add an extra button to toggle between
// source code and a rendered diagram view.
if (node.attrs.language === "mermaidjs") {
const showSourceButton = document.createElement("button");
showSourceButton.innerText = this.options.dictionary.showSource;
showSourceButton.type = "button";
showSourceButton.classList.add("show-source-button");
showSourceButton.addEventListener("click", this.handleToggleDiagram);
actions.prepend(showSourceButton);
const showDiagramButton = document.createElement("button");
showDiagramButton.innerText = this.options.dictionary.showDiagram;
showDiagramButton.type = "button";
showDiagramButton.classList.add("show-digram-button");
showDiagramButton.addEventListener("click", this.handleToggleDiagram);
actions.prepend(showDiagramButton);
}
return [
"div",
{ class: "code-block", "data-language": node.attrs.language },
{
class: "code-block",
"data-language": node.attrs.language,
},
["div", { contentEditable: "false" }, actions],
["pre", ["code", { spellCheck: "false" }, 0]],
];
@@ -222,20 +244,46 @@ export default class CodeFence extends Node {
if (result) {
const language = element.value;
const transaction = tr
.setSelection(Selection.near(view.state.doc.resolve(result.inside)))
.setNodeMarkup(result.inside, undefined, {
language,
});
view.dispatch(transaction);
localStorage?.setItem(PERSISTENCE_KEY, language);
}
};
handleToggleDiagram = (event: InputEvent) => {
const { view } = this.editor;
const { tr } = view.state;
const element = event.currentTarget;
if (!(element instanceof HTMLButtonElement)) {
return;
}
const { top, left } = element.getBoundingClientRect();
const result = view.posAtCoords({ top, left });
if (!result) {
return;
}
const diagramId = element
.closest(".code-block")
?.getAttribute("data-diagram-id");
if (!diagramId) {
return;
}
const transaction = tr.setMeta("mermaid", { toggleDiagram: diagramId });
view.dispatch(transaction);
};
get plugins() {
return [Prism({ name: this.name })];
return [Prism({ name: this.name }), Mermaid({ name: this.name })];
}
inputRules({ type }: { type: NodeType }) {