From c32b6393afc83220531c4c680010a4ca2eb29109 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 26 Nov 2025 15:17:34 +0200 Subject: [PATCH] refactor(client): split text content rendering to separate module --- apps/client/src/print.tsx | 3 +- apps/client/src/services/content_renderer.ts | 104 +----------------- .../src/services/content_renderer_text.ts | 93 ++++++++++++++++ apps/client/src/services/utils.ts | 2 +- .../type_widgets/text/ReadOnlyText.tsx | 2 +- 5 files changed, 103 insertions(+), 101 deletions(-) create mode 100644 apps/client/src/services/content_renderer_text.ts diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index ffef86044..9719a847b 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -2,8 +2,9 @@ import FNote from "./entities/fnote"; import { render } from "preact"; import { CustomNoteList, useNoteViewType } from "./widgets/collections/NoteList"; import { useCallback, useLayoutEffect, useRef } from "preact/hooks"; -import content_renderer, { applyInlineMermaid } from "./services/content_renderer"; +import content_renderer from "./services/content_renderer"; import { dynamicRequire, isElectron } from "./services/utils"; +import { applyInlineMermaid } from "./services/content_renderer_text"; interface RendererProps { note: FNote; diff --git a/apps/client/src/services/content_renderer.ts b/apps/client/src/services/content_renderer.ts index f7658d669..0e9db9302 100644 --- a/apps/client/src/services/content_renderer.ts +++ b/apps/client/src/services/content_renderer.ts @@ -2,24 +2,21 @@ import renderService from "./render.js"; import protectedSessionService from "./protected_session.js"; import protectedSessionHolder from "./protected_session_holder.js"; import openService from "./open.js"; -import froca from "./froca.js"; import utils from "./utils.js"; -import linkService from "./link.js"; -import treeService from "./tree.js"; import FNote from "../entities/fnote.js"; import FAttachment from "../entities/fattachment.js"; import imageContextMenuService from "../menus/image_context_menu.js"; -import { applySingleBlockSyntaxHighlight, formatCodeBlocks } from "./syntax_highlight.js"; -import { getMermaidConfig, loadElkIfNeeded, postprocessMermaidSvg } from "./mermaid.js"; +import { applySingleBlockSyntaxHighlight } from "./syntax_highlight.js"; +import { loadElkIfNeeded, postprocessMermaidSvg } from "./mermaid.js"; import renderDoc from "./doc_renderer.js"; import { t } from "../services/i18n.js"; import WheelZoom from 'vanilla-js-wheel-zoom'; -import { renderMathInElement } from "./math.js"; import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons"; +import renderText from "./content_renderer_text.js"; let idCounter = 1; -interface Options { +export interface RenderOptions { tooltip?: boolean; trim?: boolean; imageHasZoom?: boolean; @@ -29,7 +26,7 @@ interface Options { const CODE_MIME_TYPES = new Set(["application/json"]); -export async function getRenderedContent(this: {} | { ctx: string }, entity: FNote | FAttachment, options: Options = {}) { +export async function getRenderedContent(this: {} | { ctx: string }, entity: FNote | FAttachment, options: RenderOptions = {}) { options = Object.assign( { @@ -116,33 +113,6 @@ export async function getRenderedContent(this: {} | { ctx: string }, entity: FNo }; } -async function renderText(note: FNote | FAttachment, $renderedContent: JQuery, options: Options = {}) { - // entity must be FNote - const blob = await note.getBlob(); - - if (blob && !utils.isHtmlEmpty(blob.content)) { - $renderedContent.append($('
').html(blob.content)); - - if ($renderedContent.find("span.math-tex").length > 0) { - renderMathInElement($renderedContent[0], { trust: true }); - } - - const getNoteIdFromLink = (el: HTMLElement) => treeService.getNoteIdFromUrl($(el).attr("href") || ""); - const referenceLinks = $renderedContent.find("a.reference-link"); - const noteIdsToPrefetch = referenceLinks.map((i, el) => getNoteIdFromLink(el)); - await froca.getNotes(noteIdsToPrefetch); - - for (const el of referenceLinks) { - await linkService.loadReferenceLinkTitle($(el)); - } - - await rewriteMermaidDiagramsInContainer($renderedContent[0] as HTMLDivElement); - await formatCodeBlocks($renderedContent); - } else if (note instanceof FNote && !options.noChildrenList) { - await renderChildrenList($renderedContent, note); - } -} - /** * Renders a code note, by displaying its content and applying syntax highlighting based on the selected MIME type. */ @@ -164,7 +134,7 @@ async function renderCode(note: FNote | FAttachment, $renderedContent: JQuery, options: Options = {}) { +function renderImage(entity: FNote | FAttachment, $renderedContent: JQuery, options: RenderOptions = {}) { const encodedTitle = encodeURIComponent(entity.title); let url; @@ -306,40 +276,6 @@ async function renderMermaid(note: FNote | FAttachment, $renderedContent: JQuery } } -/** - * @param {jQuery} $renderedContent - * @param {FNote} note - * @returns {Promise} - */ -async function renderChildrenList($renderedContent: JQuery, note: FNote) { - let childNoteIds = note.getChildNoteIds(); - - if (!childNoteIds.length) { - return; - } - - $renderedContent.css("padding", "10px"); - $renderedContent.addClass("text-with-ellipsis"); - - if (childNoteIds.length > 10) { - childNoteIds = childNoteIds.slice(0, 10); - } - - // just load the first 10 child notes - const childNotes = await froca.getNotes(childNoteIds); - - for (const childNote of childNotes) { - $renderedContent.append( - await linkService.createLink(`${note.noteId}/${childNote.noteId}`, { - showTooltip: false, - showNoteIcon: true - }) - ); - - $renderedContent.append("
"); - } -} - function getRenderingType(entity: FNote | FAttachment) { let type: string = ""; if ("type" in entity) { @@ -371,34 +307,6 @@ function getRenderingType(entity: FNote | FAttachment) { return type; } -/** Rewrite the code block from
 to 
in order not to apply a codeblock style to it. */ -export async function rewriteMermaidDiagramsInContainer(container: HTMLDivElement) { - const mermaidBlocks = container.querySelectorAll('pre:has(code[class="language-mermaid"])'); - if (!mermaidBlocks.length) return; - const nodes: HTMLElement[] = []; - - for (const mermaidBlock of mermaidBlocks) { - const div = document.createElement("div"); - div.classList.add("mermaid-diagram"); - div.innerHTML = mermaidBlock.querySelector("code")?.innerHTML ?? ""; - mermaidBlock.replaceWith(div); - nodes.push(div); - } -} - -export async function applyInlineMermaid(container: HTMLDivElement) { - // Initialize mermaid - const mermaid = (await import("mermaid")).default; - mermaid.initialize(getMermaidConfig()); - const nodes = Array.from(container.querySelectorAll("div.mermaid-diagram")); - console.log("Got nodes", nodes); - try { - await mermaid.run({ nodes }); - } catch (e) { - console.log(e); - } -} - export default { getRenderedContent }; diff --git a/apps/client/src/services/content_renderer_text.ts b/apps/client/src/services/content_renderer_text.ts new file mode 100644 index 000000000..e074f439a --- /dev/null +++ b/apps/client/src/services/content_renderer_text.ts @@ -0,0 +1,93 @@ +import { formatCodeBlocks } from "./syntax_highlight.js"; +import { getMermaidConfig } from "./mermaid.js"; +import { renderMathInElement } from "./math.js"; +import FNote from "../entities/fnote.js"; +import FAttachment from "../entities/fattachment.js"; +import tree from "./tree.js"; +import froca from "./froca.js"; +import link from "./link.js"; +import { isHtmlEmpty } from "./utils.js"; +import type { RenderOptions } from "./content_renderer.js"; + +export default async function renderText(note: FNote | FAttachment, $renderedContent: JQuery, options: RenderOptions = {}) { + // entity must be FNote + const blob = await note.getBlob(); + + if (blob && !isHtmlEmpty(blob.content)) { + $renderedContent.append($('
').html(blob.content)); + + if ($renderedContent.find("span.math-tex").length > 0) { + renderMathInElement($renderedContent[0], { trust: true }); + } + + const getNoteIdFromLink = (el: HTMLElement) => tree.getNoteIdFromUrl($(el).attr("href") || ""); + const referenceLinks = $renderedContent.find("a.reference-link"); + const noteIdsToPrefetch = referenceLinks.map((i, el) => getNoteIdFromLink(el)); + await froca.getNotes(noteIdsToPrefetch); + + for (const el of referenceLinks) { + await link.loadReferenceLinkTitle($(el)); + } + + await rewriteMermaidDiagramsInContainer($renderedContent[0] as HTMLDivElement); + await formatCodeBlocks($renderedContent); + } else if (note instanceof FNote && !options.noChildrenList) { + await renderChildrenList($renderedContent, note); + } +} + +/** Rewrite the code block from
 to 
in order not to apply a codeblock style to it. */ +export async function rewriteMermaidDiagramsInContainer(container: HTMLDivElement) { + const mermaidBlocks = container.querySelectorAll('pre:has(code[class="language-mermaid"])'); + if (!mermaidBlocks.length) return; + const nodes: HTMLElement[] = []; + + for (const mermaidBlock of mermaidBlocks) { + const div = document.createElement("div"); + div.classList.add("mermaid-diagram"); + div.innerHTML = mermaidBlock.querySelector("code")?.innerHTML ?? ""; + mermaidBlock.replaceWith(div); + nodes.push(div); + } +} + +export async function applyInlineMermaid(container: HTMLDivElement) { + // Initialize mermaid + const mermaid = (await import("mermaid")).default; + mermaid.initialize(getMermaidConfig()); + const nodes = Array.from(container.querySelectorAll("div.mermaid-diagram")); + try { + await mermaid.run({ nodes }); + } catch (e) { + console.log(e); + } +} + +async function renderChildrenList($renderedContent: JQuery, note: FNote) { + let childNoteIds = note.getChildNoteIds(); + + if (!childNoteIds.length) { + return; + } + + $renderedContent.css("padding", "10px"); + $renderedContent.addClass("text-with-ellipsis"); + + if (childNoteIds.length > 10) { + childNoteIds = childNoteIds.slice(0, 10); + } + + // just load the first 10 child notes + const childNotes = await froca.getNotes(childNoteIds); + + for (const childNote of childNotes) { + $renderedContent.append( + await link.createLink(`${note.noteId}/${childNote.noteId}`, { + showTooltip: false, + showNoteIcon: true + }) + ); + + $renderedContent.append("
"); + } +} diff --git a/apps/client/src/services/utils.ts b/apps/client/src/services/utils.ts index bb6d6d07f..b11578bb6 100644 --- a/apps/client/src/services/utils.ts +++ b/apps/client/src/services/utils.ts @@ -274,7 +274,7 @@ function getMimeTypeClass(mime: string) { return `mime-${mime.toLowerCase().replace(/[\W_]+/g, "-")}`; } -function isHtmlEmpty(html: string) { +export function isHtmlEmpty(html: string) { if (!html) { return true; } else if (typeof html !== "string") { diff --git a/apps/client/src/widgets/type_widgets/text/ReadOnlyText.tsx b/apps/client/src/widgets/type_widgets/text/ReadOnlyText.tsx index 4e85029ff..5e2883201 100644 --- a/apps/client/src/widgets/type_widgets/text/ReadOnlyText.tsx +++ b/apps/client/src/widgets/type_widgets/text/ReadOnlyText.tsx @@ -16,7 +16,7 @@ import { formatCodeBlocks } from "../../../services/syntax_highlight"; import TouchBar, { TouchBarButton, TouchBarSpacer } from "../../react/TouchBar"; import appContext from "../../../components/app_context"; import { applyReferenceLinks } from "./read_only_helper"; -import { applyInlineMermaid, rewriteMermaidDiagramsInContainer } from "../../../services/content_renderer"; +import { applyInlineMermaid, rewriteMermaidDiagramsInContainer } from "../../../services/content_renderer_text"; import clsx from "clsx"; export default function ReadOnlyText({ note, noteContext, ntxId }: TypeWidgetProps) {