mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Implement mermaid diagrams (#2187)
* Start implementing mermaid diagrams * Implement theming for mermaid diagrams * Add edit tab to mermaid diagrams * Remove comment * Render mermaid diagrams in included notes * Prevent mermaid notes from being removed to consistency checks
This commit is contained in:
parent
14f24c646a
commit
b37bcd294c
58
libraries/mermaid.min.js
vendored
Normal file
58
libraries/mermaid.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -15,7 +15,8 @@ const NOTE_TYPE_ICONS = {
|
||||
"search": "bx bx-file-find",
|
||||
"relation-map": "bx bx-map-alt",
|
||||
"book": "bx bx-book",
|
||||
"note-map": "bx bx-map-alt"
|
||||
"note-map": "bx bx-map-alt",
|
||||
"mermaid": "bx bx-network-chart"
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -50,6 +50,10 @@ const FORCE_GRAPH = {
|
||||
js: [ "libraries/force-graph.min.js"]
|
||||
};
|
||||
|
||||
const MERMAID = {
|
||||
js: [ "libraries/mermaid.min.js" ]
|
||||
}
|
||||
|
||||
async function requireLibrary(library) {
|
||||
if (library.css) {
|
||||
library.css.map(cssUrl => requireCss(cssUrl));
|
||||
@ -99,5 +103,6 @@ export default {
|
||||
CALENDAR_WIDGET,
|
||||
KATEX,
|
||||
WHEEL_ZOOM,
|
||||
FORCE_GRAPH
|
||||
FORCE_GRAPH,
|
||||
MERMAID
|
||||
}
|
||||
|
@ -82,6 +82,24 @@ async function getRenderedContent(note, options = {}) {
|
||||
|
||||
$renderedContent.append($content);
|
||||
}
|
||||
else if (type === 'mermaid') {
|
||||
await libraryLoader.requireLibrary(libraryLoader.MERMAID);
|
||||
|
||||
const noteComplement = await froca.getNoteComplement(note.noteId);
|
||||
const graph = noteComplement.content || "";
|
||||
|
||||
const updateWithContent = (content) => {
|
||||
$renderedContent.append($(content))
|
||||
}
|
||||
|
||||
try {
|
||||
mermaid.mermaidAPI.render('graphDiv', graph, updateWithContent);
|
||||
} catch (e) {
|
||||
const $error = $("<p>The diagram could not displayed.</p>");
|
||||
|
||||
$renderedContent.append($error);
|
||||
}
|
||||
}
|
||||
else if (type === 'render') {
|
||||
const $content = $('<div>');
|
||||
|
||||
|
@ -21,6 +21,7 @@ import ReadOnlyCodeTypeWidget from "./type_widgets/read_only_code.js";
|
||||
import NoneTypeWidget from "./type_widgets/none.js";
|
||||
import attributeService from "../services/attributes.js";
|
||||
import NoteMapTypeWidget from "./type_widgets/note_map.js";
|
||||
import MermaidTypeWidget from "./type_widgets/mermaid.js"
|
||||
|
||||
const TPL = `
|
||||
<div class="note-detail">
|
||||
@ -47,7 +48,8 @@ const typeWidgetClasses = {
|
||||
'relation-map': RelationMapTypeWidget,
|
||||
'protected-session': ProtectedSessionTypeWidget,
|
||||
'book': BookTypeWidget,
|
||||
'note-map': NoteMapTypeWidget
|
||||
'note-map': NoteMapTypeWidget,
|
||||
'mermaid': MermaidTypeWidget
|
||||
};
|
||||
|
||||
export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
|
@ -11,6 +11,7 @@ const NOTE_TYPES = [
|
||||
{ type: "relation-map", mime: "application/json", title: "Relation Map", selectable: true },
|
||||
{ type: "render", mime: '', title: "Render Note", selectable: true },
|
||||
{ type: "book", mime: '', title: "Book", selectable: true },
|
||||
{ type: "mermaid", mime: 'text/mermaid', title: "Mermaid Diagram", selectable: true },
|
||||
{ type: "code", mime: 'text/plain', title: "Code", selectable: true }
|
||||
];
|
||||
|
||||
|
@ -55,7 +55,7 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return this.note && (this.note.type === 'text' || this.note.type === 'code');
|
||||
return this.note && (this.note.type === 'text' || this.note.type === 'code' || this.note.type == 'mermaid');
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
|
135
src/public/app/widgets/type_widgets/mermaid.js
Normal file
135
src/public/app/widgets/type_widgets/mermaid.js
Normal file
@ -0,0 +1,135 @@
|
||||
import libraryLoader from "../../services/library_loader.js";
|
||||
import TypeWidget from "./type_widget.js";
|
||||
import froca from "../../services/froca.js";
|
||||
|
||||
const TPL = `<div>
|
||||
<style>
|
||||
.note-detail-code-editor {
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
height: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="mermaid-error alert alert-warning">
|
||||
<p><strong>The diagram could not displayed.</strong></p>
|
||||
<p class="error-content">Rendering diagram...</p>
|
||||
</div>
|
||||
|
||||
<div class="mermaid-render"></div>
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
<div class="note-detail-code note-detail-printable">
|
||||
<div class="note-detail-code-editor"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export default class MermaidTypeWidget extends TypeWidget {
|
||||
static getType() { return "mermaid"; }
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$display = this.$widget.find('.mermaid-render');
|
||||
|
||||
this.$editor = this.$widget.find('.note-detail-code-editor');
|
||||
|
||||
this.$errorContainer = this.$widget.find(".mermaid-error");
|
||||
this.$errorMessage = this.$errorContainer.find(".error-content");
|
||||
|
||||
this.initialized = Promise.all( [this.initRenderer(), this.initEditor()]);
|
||||
|
||||
super.doRender();
|
||||
}
|
||||
|
||||
async initRenderer() {
|
||||
await libraryLoader.requireLibrary(libraryLoader.MERMAID);
|
||||
|
||||
const documentStyle = window.getComputedStyle(document.documentElement);
|
||||
const mermaidTheme = documentStyle.getPropertyValue('--mermaid-theme');
|
||||
|
||||
mermaid.mermaidAPI.initialize({ startOnLoad: false, theme: mermaidTheme.trim() });
|
||||
}
|
||||
|
||||
async initEditor() {
|
||||
await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR);
|
||||
|
||||
CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
|
||||
CodeMirror.keyMap.default["Tab"] = "indentMore";
|
||||
|
||||
// these conflict with backward/forward navigation shortcuts
|
||||
delete CodeMirror.keyMap.default["Alt-Left"];
|
||||
delete CodeMirror.keyMap.default["Alt-Right"];
|
||||
|
||||
CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js';
|
||||
|
||||
this.codeEditor = CodeMirror(this.$editor[0], {
|
||||
value: "",
|
||||
viewportMargin: Infinity,
|
||||
indentUnit: 4,
|
||||
matchBrackets: true,
|
||||
matchTags: {bothTags: true},
|
||||
highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false},
|
||||
lint: true,
|
||||
gutters: ["CodeMirror-lint-markers"],
|
||||
lineNumbers: true,
|
||||
tabindex: 300,
|
||||
// we linewrap partly also because without it horizontal scrollbar displays only when you scroll
|
||||
// all the way to the bottom of the note. With line wrap there's no horizontal scrollbar so no problem
|
||||
lineWrapping: true,
|
||||
dragDrop: false // with true the editor inlines dropped files which is not what we expect
|
||||
});
|
||||
|
||||
this.codeEditor.on('change', () => {
|
||||
this.spacedUpdate.scheduleUpdate();
|
||||
|
||||
this.updateRender(this.codeEditor.getValue() || "");
|
||||
});
|
||||
}
|
||||
|
||||
async doRefresh(note) {
|
||||
const noteComplement = await froca.getNoteComplement(note.noteId);
|
||||
|
||||
await this.spacedUpdate.allowUpdateWithoutChange(() => {
|
||||
this.updateRender(noteComplement.content || "");
|
||||
|
||||
this.codeEditor.setValue(noteComplement.content || "");
|
||||
this.codeEditor.clearHistory();
|
||||
});
|
||||
|
||||
this.show();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.$widget.show();
|
||||
|
||||
if (this.codeEditor) { // show can be called before render
|
||||
this.codeEditor.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
getContent() {
|
||||
return this.codeEditor.getValue();
|
||||
}
|
||||
|
||||
async updateRender(graph) {
|
||||
const updateWithContent = (content) => {
|
||||
this.$display.html(content);
|
||||
}
|
||||
|
||||
this.$display.empty();
|
||||
|
||||
this.$errorMessage.text('Rendering diagram...');
|
||||
|
||||
try {
|
||||
mermaid.mermaidAPI.render('graphDiv', graph, updateWithContent);
|
||||
|
||||
this.$errorContainer.hide();
|
||||
} catch (e) {
|
||||
this.$errorMessage.text(e.message);
|
||||
this.$errorContainer.show();
|
||||
}
|
||||
}
|
||||
}
|
@ -60,6 +60,8 @@
|
||||
--scrollbar-border-color: #888;
|
||||
--tooltip-background-color: #333;
|
||||
--link-color: lightskyblue;
|
||||
|
||||
--mermaid-theme: dark;
|
||||
}
|
||||
|
||||
body .global-menu-button {
|
||||
|
@ -63,4 +63,6 @@ html {
|
||||
--scrollbar-border-color: #ddd;
|
||||
--tooltip-background-color: #f8f8f8;
|
||||
--link-color: blue;
|
||||
|
||||
--mermaid-theme: default;
|
||||
}
|
||||
|
@ -267,7 +267,7 @@ class ConsistencyChecks {
|
||||
SELECT noteId, type
|
||||
FROM notes
|
||||
WHERE isDeleted = 0
|
||||
AND type NOT IN ('text', 'code', 'render', 'file', 'image', 'search', 'relation-map', 'book', 'note-map')`,
|
||||
AND type NOT IN ('text', 'code', 'render', 'file', 'image', 'search', 'relation-map', 'book', 'note-map', 'mermaid')`,
|
||||
({noteId, type}) => {
|
||||
if (this.autoFix) {
|
||||
const note = becca.getNote(noteId);
|
||||
|
Loading…
x
Reference in New Issue
Block a user