diff --git a/src/public/app/widgets/mermaid.ts b/src/public/app/widgets/mermaid.ts index be608ef2c..dcf2f72ac 100644 --- a/src/public/app/widgets/mermaid.ts +++ b/src/public/app/widgets/mermaid.ts @@ -1,239 +1,161 @@ -import { t } from "../services/i18n.js"; -import libraryLoader from "../services/library_loader.js"; -import NoteContextAwareWidget from "./note_context_aware_widget.js"; -import server from "../services/server.js"; -import utils from "../services/utils.js"; -import { loadElkIfNeeded, postprocessMermaidSvg } from "../services/mermaid.js"; -import type FNote from "../entities/fnote.js"; -import type { EventData } from "../components/app_context.js"; +// import { t } from "../services/i18n.js"; +// import libraryLoader from "../services/library_loader.js"; +// import NoteContextAwareWidget from "./note_context_aware_widget.js"; +// import server from "../services/server.js"; +// import utils from "../services/utils.js"; +// import { loadElkIfNeeded, postprocessMermaidSvg } from "../services/mermaid.js"; +// import type FNote from "../entities/fnote.js"; +// import type { EventData } from "../components/app_context.js"; -const TPL = `
- +// .mermaid-render svg { +// max-width: 100% !important; +// width: 100%; +// } +// -
-

${t("mermaid.diagram_error")}

-

-
+//
+//

${t("mermaid.diagram_error")}

+//

+//
-
-
`; +//
+// `; -let idCounter = 1; +// export default class MermaidWidget extends NoteContextAwareWidget { -export default class MermaidWidget extends NoteContextAwareWidget { +// private $display!: JQuery; +// private $errorContainer!: JQuery; +// private $errorMessage!: JQuery; +// private dirtyAttachment?: boolean; +// private lastNote?: FNote; - private $display!: JQuery; - private $errorContainer!: JQuery; - private $errorMessage!: JQuery; - private dirtyAttachment?: boolean; - private zoomHandler?: () => void; - private zoomInstance?: SvgPanZoom.Instance; - private lastNote?: FNote; +// isEnabled() { +// return super.isEnabled() && this.note?.type === "mermaid" && this.note.isContentAvailable() && this.noteContext?.viewScope?.viewMode === "default"; +// } - isEnabled() { - return super.isEnabled() && this.note?.type === "mermaid" && this.note.isContentAvailable() && this.noteContext?.viewScope?.viewMode === "default"; - } +// doRender() { +// this.$widget = $(TPL); +// this.contentSized(); +// this.$display = this.$widget.find(".mermaid-render"); +// this.$errorContainer = this.$widget.find(".mermaid-error"); +// this.$errorMessage = this.$errorContainer.find(".error-content"); +// } - doRender() { - this.$widget = $(TPL); - this.contentSized(); - this.$display = this.$widget.find(".mermaid-render"); - this.$errorContainer = this.$widget.find(".mermaid-error"); - this.$errorMessage = this.$errorContainer.find(".error-content"); - } +// async refreshWithNote(note: FNote) { +// const isSameNote = (this.lastNote === note); - async refreshWithNote(note: FNote) { - const isSameNote = (this.lastNote === note); +// this.cleanup(); +// this.$errorContainer.hide(); - this.cleanup(); - this.$errorContainer.hide(); +// if (!isSameNote) { +// this.$display.empty(); +// } - await libraryLoader.requireLibrary(libraryLoader.MERMAID); +// this.$errorContainer.hide(); - mermaid.mermaidAPI.initialize({ - startOnLoad: false, - ...(getMermaidConfig() as any) - }); +// try { +// const svg = await this.renderSvg(); - if (!isSameNote) { - this.$display.empty(); - } +// if (this.dirtyAttachment) { +// const payload = { +// role: "image", +// title: "mermaid-export.svg", +// mime: "image/svg+xml", +// content: svg, +// position: 0 +// }; - this.$errorContainer.hide(); +// server.post(`notes/${this.noteId}/attachments?matchBy=title`, payload).then(() => { +// this.dirtyAttachment = false; +// }); +// } - try { - const svg = await this.renderSvg(); +// this.$display.html(svg); +// this.$display.attr("id", `mermaid-render-${idCounter}`); - if (this.dirtyAttachment) { - const payload = { - role: "image", - title: "mermaid-export.svg", - mime: "image/svg+xml", - content: svg, - position: 0 - }; +// // Enable pan to zoom. +// this.#setupPanZoom($svg[0], isSameNote); +// } catch (e: any) { +// console.warn(e); +// this.#cleanUpZoom(); +// this.$display.empty(); +// this.$errorMessage.text(e.message); +// this.$errorContainer.show(); +// } - server.post(`notes/${this.noteId}/attachments?matchBy=title`, payload).then(() => { - this.dirtyAttachment = false; - }); - } +// this.lastNote = note; +// } - this.$display.html(svg); - this.$display.attr("id", `mermaid-render-${idCounter}`); +// cleanup() { +// super.cleanup(); +// if (this.zoomHandler) { +// $(window).off("resize", this.zoomHandler); +// this.zoomHandler = undefined; +// } +// } - // Fit the image to bounds. - const $svg = this.$display.find("svg"); - $svg.attr("width", "100%").attr("height", "100%"); - // Enable pan to zoom. - this.#setupPanZoom($svg[0], isSameNote); - } catch (e: any) { - console.warn(e); - this.#cleanUpZoom(); - this.$display.empty(); - this.$errorMessage.text(e.message); - this.$errorContainer.show(); - } - this.lastNote = note; - } +// toggleInt(show: boolean | null | undefined): void { +// super.toggleInt(show); - cleanup() { - super.cleanup(); - if (this.zoomHandler) { - $(window).off("resize", this.zoomHandler); - this.zoomHandler = undefined; - } - } +// if (!show) { +// this.cleanup(); +// } +// } - #cleanUpZoom() { - if (this.zoomInstance) { - this.zoomInstance.destroy(); - this.zoomInstance = undefined; - } - } +// async renderSvg() { - toggleInt(show: boolean | null | undefined): void { - super.toggleInt(show); - if (!show) { - this.cleanup(); - } - } +// if (!this.note) { +// return ""; +// } - async renderSvg() { - idCounter++; +// await loadElkIfNeeded(content); - if (!this.note) { - return ""; - } +// } - const blob = await this.note.getBlob(); - const content = blob?.content || ""; - await loadElkIfNeeded(content); - const { svg } = await mermaid.mermaidAPI.render(`mermaid-graph-${idCounter}`, content); - return postprocessMermaidSvg(svg); - } - async #setupPanZoom(svgEl: SVGElement, isSameNote: boolean) { - // Clean up - let pan = null; - let zoom = null; - if (this.zoomInstance) { - // Store pan and zoom for same note, when the user is editing the note. - if (isSameNote && this.zoomInstance) { - pan = this.zoomInstance.getPan(); - zoom = this.zoomInstance.getZoom(); - } - this.#cleanUpZoom(); - } +// async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { +// if (this.noteId && loadResults.isNoteContentReloaded(this.noteId)) { +// this.dirtyAttachment = true; - const svgPanZoom = (await import("svg-pan-zoom")).default; - const zoomInstance = svgPanZoom(svgEl, { - zoomEnabled: true, - controlIconsEnabled: true - }); +// await this.refresh(); +// } +// } - if (pan && zoom) { - // Restore the pan and zoom. - zoomInstance.zoom(zoom); - zoomInstance.pan(pan); - } else { - // New instance, reposition properly. - zoomInstance.center(); - zoomInstance.fit(); - } +// async exportSvgEvent({ ntxId }: EventData<"exportSvg">) { +// if (!this.isNoteContext(ntxId) || this.note?.type !== "mermaid") { +// return; +// } - this.zoomHandler = () => { - zoomInstance.resize(); - zoomInstance.fit(); - zoomInstance.center(); - }; - this.zoomInstance = zoomInstance; - $(window).on("resize", this.zoomHandler); - } +// const svg = await this.renderSvg(); +// utils.downloadSvg(this.note.title, svg); +// } +// } - async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (this.noteId && loadResults.isNoteContentReloaded(this.noteId)) { - this.dirtyAttachment = true; - - await this.refresh(); - } - } - - async exportSvgEvent({ ntxId }: EventData<"exportSvg">) { - if (!this.isNoteContext(ntxId) || this.note?.type !== "mermaid") { - return; - } - - const svg = await this.renderSvg(); - utils.downloadSvg(this.note.title, svg); - } -} - -export function getMermaidConfig(): MermaidConfig { - const documentStyle = window.getComputedStyle(document.documentElement); - const mermaidTheme = documentStyle.getPropertyValue("--mermaid-theme"); - - return { - theme: mermaidTheme.trim(), - securityLevel: "antiscript", - // TODO: Are all these options correct? - flow: { useMaxWidth: false }, - sequence: { useMaxWidth: false }, - gantt: { useMaxWidth: false }, - class: { useMaxWidth: false }, - state: { useMaxWidth: false }, - pie: { useMaxWidth: true }, - journey: { useMaxWidth: false }, - git: { useMaxWidth: false } - }; -} diff --git a/src/public/app/widgets/type_widgets/abstract_svg_split_type_widget.ts b/src/public/app/widgets/type_widgets/abstract_svg_split_type_widget.ts index 2802cab85..e82183119 100644 --- a/src/public/app/widgets/type_widgets/abstract_svg_split_type_widget.ts +++ b/src/public/app/widgets/type_widgets/abstract_svg_split_type_widget.ts @@ -12,10 +12,14 @@ import AbstractSplitTypeWidget from "./abstract_split_type_widget.js"; export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTypeWidget { private $renderContainer!: JQuery; + private zoomHandler?: () => void; + private zoomInstance?: SvgPanZoom.Instance; doRender(): void { super.doRender(); - this.$renderContainer = $(`
`); + this.$renderContainer = $(`
`) + .addClass("render-container") + .css("height", "100%"); this.$preview.append(this.$renderContainer); } @@ -42,9 +46,15 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy if (this.note) { const svg = await this.renderSvg(content); this.$renderContainer.html(svg); + await this.#setupPanZoom(); } } + cleanup(): void { + this.#cleanUpZoom(); + super.cleanup(); + } + /** * Called upon when the SVG preview needs refreshing, such as when the editor has switched to a new note or the content has switched. * @@ -54,4 +64,58 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy */ abstract renderSvg(content: string): Promise; + async #setupPanZoom() { + // Clean up + let pan = null; + let zoom = null; + if (this.zoomInstance) { + // Store pan and zoom for same note, when the user is editing the note. + pan = this.zoomInstance.getPan(); + zoom = this.zoomInstance.getZoom(); + this.#cleanUpZoom(); + } + + const $svgEl = this.$renderContainer.find("svg"); + + // Fit the image to bounds + $svgEl.attr("width", "100%") + .attr("height", "100%") + .css("max-width", "100%"); + + if (!$svgEl.length) { + return; + } + + const svgPanZoom = (await import("svg-pan-zoom")).default; + const zoomInstance = svgPanZoom($svgEl[0], { + zoomEnabled: true, + controlIconsEnabled: true + }); + + if (pan && zoom) { + // Restore the pan and zoom. + zoomInstance.zoom(zoom); + zoomInstance.pan(pan); + } else { + // New instance, reposition properly. + zoomInstance.center(); + zoomInstance.fit(); + } + + this.zoomHandler = () => { + zoomInstance.resize(); + zoomInstance.fit(); + zoomInstance.center(); + }; + this.zoomInstance = zoomInstance; + $(window).on("resize", this.zoomHandler); + } + + #cleanUpZoom() { + if (this.zoomInstance) { + this.zoomInstance.destroy(); + this.zoomInstance = undefined; + } + } + }