mirror of
https://github.com/zadam/trilium.git
synced 2025-11-11 17:08:58 +01:00
chore(react/type_widget): basic SVG rendering
This commit is contained in:
parent
c49b90d33f
commit
8caaa99415
@ -1,6 +1,34 @@
|
||||
import { useCallback } from "preact/hooks";
|
||||
import SvgSplitEditor from "./helpers/SvgSplitEditor";
|
||||
import { TypeWidgetProps } from "./type_widget";
|
||||
import { getMermaidConfig, loadElkIfNeeded, postprocessMermaidSvg } from "../../services/mermaid";
|
||||
|
||||
let idCounter = 1;
|
||||
let registeredErrorReporter = false;
|
||||
|
||||
export default function Mermaid(props: TypeWidgetProps) {
|
||||
return <SvgSplitEditor {...props} />;
|
||||
const renderSvg = useCallback(async (content: string) => {
|
||||
const mermaid = (await import("mermaid")).default;
|
||||
await loadElkIfNeeded(mermaid, content);
|
||||
if (!registeredErrorReporter) {
|
||||
// (await import("./linters/mermaid.js")).default();
|
||||
registeredErrorReporter = true;
|
||||
}
|
||||
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
...(getMermaidConfig() as any),
|
||||
});
|
||||
|
||||
idCounter++;
|
||||
const { svg } = await mermaid.render(`mermaid-graph-${idCounter}`, content);
|
||||
return postprocessMermaidSvg(svg);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SvgSplitEditor
|
||||
renderSvg={renderSvg}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -12,6 +12,14 @@ import keyboard_actions from "../../../services/keyboard_actions";
|
||||
import { refToJQuerySelector } from "../../react/react_utils";
|
||||
import { CODE_THEME_DEFAULT_PREFIX as DEFAULT_PREFIX } from "../constants";
|
||||
|
||||
export interface EditableCodeProps extends TypeWidgetProps {
|
||||
// if true, the update will be debounced to prevent excessive updates. Especially useful if the editor is linked to a live preview.
|
||||
debounceUpdate?: boolean;
|
||||
lineWrapping?: boolean;
|
||||
updateInterval?: number;
|
||||
onContentChanged?: (content: string) => void;
|
||||
}
|
||||
|
||||
export function ReadOnlyCode({ note, viewScope, ntxId, parentComponent }: TypeWidgetProps) {
|
||||
const [ content, setContent ] = useState("");
|
||||
const blob = useNoteBlob(note);
|
||||
@ -35,12 +43,7 @@ export function ReadOnlyCode({ note, viewScope, ntxId, parentComponent }: TypeWi
|
||||
)
|
||||
}
|
||||
|
||||
export function EditableCode({ note, ntxId, debounceUpdate, parentComponent, updateInterval, ...editorProps }: TypeWidgetProps & {
|
||||
// if true, the update will be debounced to prevent excessive updates. Especially useful if the editor is linked to a live preview.
|
||||
debounceUpdate?: boolean;
|
||||
lineWrapping?: boolean;
|
||||
updateInterval?: number;
|
||||
}) {
|
||||
export function EditableCode({ note, ntxId, debounceUpdate, parentComponent, updateInterval, onContentChanged, ...editorProps }: EditableCodeProps) {
|
||||
const editorRef = useRef<VanillaCodeMirror>(null);
|
||||
const containerRef = useRef<HTMLPreElement>(null);
|
||||
const [ vimKeymapEnabled ] = useTriliumOptionBool("vimKeymapEnabled");
|
||||
@ -78,6 +81,9 @@ export function EditableCode({ note, ntxId, debounceUpdate, parentComponent, upd
|
||||
spacedUpdate.resetUpdateTimer();
|
||||
}
|
||||
spacedUpdate.scheduleUpdate();
|
||||
if (editorRef.current && onContentChanged) {
|
||||
onContentChanged(editorRef.current.getText());
|
||||
}
|
||||
}}
|
||||
{...editorProps}
|
||||
/>
|
||||
|
||||
@ -77,4 +77,8 @@
|
||||
|
||||
.note-detail-split.split-read-only .note-detail-split-preview-col {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* #region SVG */
|
||||
|
||||
/* #endregion */
|
||||
@ -2,17 +2,17 @@ import { useEffect, useRef } from "preact/hooks";
|
||||
import utils, { isMobile } from "../../../services/utils";
|
||||
import Admonition from "../../react/Admonition";
|
||||
import { useNoteLabelBoolean, useTriliumOption } from "../../react/hooks";
|
||||
import { TypeWidgetProps } from "../type_widget";
|
||||
import "./SplitEditor.css";
|
||||
import Split from "split.js";
|
||||
import { DEFAULT_GUTTER_SIZE } from "../../../services/resizer";
|
||||
import { EditableCode } from "../code/Code";
|
||||
import { EditableCode, EditableCodeProps } from "../code/Code";
|
||||
import { ComponentChildren } from "preact";
|
||||
import ActionButton, { ActionButtonProps } from "../../react/ActionButton";
|
||||
|
||||
export interface SplitEditorProps extends TypeWidgetProps {
|
||||
export interface SplitEditorProps extends EditableCodeProps {
|
||||
error?: string | null;
|
||||
splitOptions?: Split.Options;
|
||||
previewContent: ComponentChildren;
|
||||
previewButtons?: ComponentChildren;
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ export interface SplitEditorProps extends TypeWidgetProps {
|
||||
* - Can display errors to the user via {@link setError}.
|
||||
* - Horizontal or vertical orientation for the editor/preview split, adjustable via the switch split orientation button floating button.
|
||||
*/
|
||||
export default function SplitEditor({ note, error, splitOptions, previewButtons, ...editorProps }: SplitEditorProps) {
|
||||
export default function SplitEditor({ note, error, splitOptions, previewContent, previewButtons, ...editorProps }: SplitEditorProps) {
|
||||
const splitEditorOrientation = useSplitOrientation();
|
||||
const [ readOnly ] = useNoteLabelBoolean(note, "readOnly");
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@ -48,7 +48,9 @@ export default function SplitEditor({ note, error, splitOptions, previewButtons,
|
||||
|
||||
const preview = (
|
||||
<div className={`note-detail-split-preview-col ${error ? "on-error" : ""}`}>
|
||||
<div className="note-detail-split-preview">Preview goes here</div>
|
||||
<div className="note-detail-split-preview">
|
||||
{previewContent}
|
||||
</div>
|
||||
<div className="btn-group btn-group-sm map-type-switcher content-floating-buttons preview-buttons bottom-right" role="group">
|
||||
{previewButtons}
|
||||
</div>
|
||||
|
||||
@ -1,10 +1,43 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import { t } from "../../../services/i18n";
|
||||
import SplitEditor, { PreviewButton, SplitEditorProps } from "./SplitEditor";
|
||||
import { RawHtmlBlock } from "../../react/RawHtml";
|
||||
|
||||
interface SvgSplitEditorProps extends Omit<SplitEditorProps, "previewContent"> {
|
||||
/**
|
||||
* Called upon when the SVG preview needs refreshing, such as when the editor has switched to a new note or the content has switched.
|
||||
*
|
||||
* The method must return a valid SVG string that will be automatically displayed in the preview.
|
||||
*
|
||||
* @param content the content of the note, in plain text.
|
||||
*/
|
||||
renderSvg(content: string): string | Promise<string>;
|
||||
}
|
||||
|
||||
export default function SvgSplitEditor({ renderSvg, ...props }: SvgSplitEditorProps) {
|
||||
const [ svg, setSvg ] = useState<string>();
|
||||
const [ error, setError ] = useState<string | null | undefined>();
|
||||
|
||||
async function onContentChanged(content: string) {
|
||||
try {
|
||||
const svg = await renderSvg(content);
|
||||
|
||||
// Rendering was successful.
|
||||
setError(null);
|
||||
setSvg(svg);
|
||||
} catch (e) {
|
||||
// Rendering failed.
|
||||
setError((e as Error)?.message);
|
||||
}
|
||||
}
|
||||
|
||||
export default function SvgSplitEditor(props: SplitEditorProps) {
|
||||
return (
|
||||
<SplitEditor
|
||||
error="Hi there"
|
||||
onContentChanged={onContentChanged}
|
||||
previewContent={(
|
||||
<RawHtmlBlock className="render-container" html={svg} />
|
||||
)}
|
||||
previewButtons={
|
||||
<>
|
||||
<PreviewButton
|
||||
|
||||
@ -83,18 +83,12 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy
|
||||
try {
|
||||
svg = await this.renderSvg(content);
|
||||
|
||||
// Rendering was succesful.
|
||||
this.setError(null);
|
||||
|
||||
if (svg === this.svg) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.svg = svg;
|
||||
this.$renderContainer.html(svg);
|
||||
} catch (e: unknown) {
|
||||
// Rendering failed.
|
||||
this.setError((e as Error)?.message);
|
||||
}
|
||||
|
||||
await this.#setupPanZoom(!recenter);
|
||||
@ -118,15 +112,6 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy
|
||||
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.
|
||||
*
|
||||
* The method must return a valid SVG string that will be automatically displayed in the preview.
|
||||
*
|
||||
* @param content the content of the note, in plain text.
|
||||
*/
|
||||
abstract renderSvg(content: string): Promise<string>;
|
||||
|
||||
/**
|
||||
* Called to obtain the name of the note attachment (without .svg extension) that will be used for storing the preview.
|
||||
*/
|
||||
|
||||
@ -2,8 +2,6 @@ import type { EditorConfig } from "@triliumnext/codemirror";
|
||||
import { getMermaidConfig, loadElkIfNeeded, postprocessMermaidSvg } from "../../services/mermaid.js";
|
||||
import AbstractSvgSplitTypeWidget from "./abstract_svg_split_type_widget.js";
|
||||
|
||||
let idCounter = 1;
|
||||
let registeredErrorReporter = false;
|
||||
|
||||
export class MermaidTypeWidget extends AbstractSvgSplitTypeWidget {
|
||||
|
||||
@ -16,21 +14,7 @@ export class MermaidTypeWidget extends AbstractSvgSplitTypeWidget {
|
||||
}
|
||||
|
||||
async renderSvg(content: string) {
|
||||
const mermaid = (await import("mermaid")).default;
|
||||
await loadElkIfNeeded(mermaid, content);
|
||||
if (!registeredErrorReporter) {
|
||||
// (await import("./linters/mermaid.js")).default();
|
||||
registeredErrorReporter = true;
|
||||
}
|
||||
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
...(getMermaidConfig() as any),
|
||||
});
|
||||
|
||||
idCounter++;
|
||||
const { svg } = await mermaid.render(`mermaid-graph-${idCounter}`, content);
|
||||
return postprocessMermaidSvg(svg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user