mirror of
https://github.com/zadam/trilium.git
synced 2025-11-28 19:44:24 +01:00
refactor(client): split text content rendering to separate module
This commit is contained in:
parent
abbb4e793f
commit
c32b6393af
@ -2,8 +2,9 @@ import FNote from "./entities/fnote";
|
|||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
import { CustomNoteList, useNoteViewType } from "./widgets/collections/NoteList";
|
import { CustomNoteList, useNoteViewType } from "./widgets/collections/NoteList";
|
||||||
import { useCallback, useLayoutEffect, useRef } from "preact/hooks";
|
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 { dynamicRequire, isElectron } from "./services/utils";
|
||||||
|
import { applyInlineMermaid } from "./services/content_renderer_text";
|
||||||
|
|
||||||
interface RendererProps {
|
interface RendererProps {
|
||||||
note: FNote;
|
note: FNote;
|
||||||
|
|||||||
@ -2,24 +2,21 @@ import renderService from "./render.js";
|
|||||||
import protectedSessionService from "./protected_session.js";
|
import protectedSessionService from "./protected_session.js";
|
||||||
import protectedSessionHolder from "./protected_session_holder.js";
|
import protectedSessionHolder from "./protected_session_holder.js";
|
||||||
import openService from "./open.js";
|
import openService from "./open.js";
|
||||||
import froca from "./froca.js";
|
|
||||||
import utils from "./utils.js";
|
import utils from "./utils.js";
|
||||||
import linkService from "./link.js";
|
|
||||||
import treeService from "./tree.js";
|
|
||||||
import FNote from "../entities/fnote.js";
|
import FNote from "../entities/fnote.js";
|
||||||
import FAttachment from "../entities/fattachment.js";
|
import FAttachment from "../entities/fattachment.js";
|
||||||
import imageContextMenuService from "../menus/image_context_menu.js";
|
import imageContextMenuService from "../menus/image_context_menu.js";
|
||||||
import { applySingleBlockSyntaxHighlight, formatCodeBlocks } from "./syntax_highlight.js";
|
import { applySingleBlockSyntaxHighlight } from "./syntax_highlight.js";
|
||||||
import { getMermaidConfig, loadElkIfNeeded, postprocessMermaidSvg } from "./mermaid.js";
|
import { loadElkIfNeeded, postprocessMermaidSvg } from "./mermaid.js";
|
||||||
import renderDoc from "./doc_renderer.js";
|
import renderDoc from "./doc_renderer.js";
|
||||||
import { t } from "../services/i18n.js";
|
import { t } from "../services/i18n.js";
|
||||||
import WheelZoom from 'vanilla-js-wheel-zoom';
|
import WheelZoom from 'vanilla-js-wheel-zoom';
|
||||||
import { renderMathInElement } from "./math.js";
|
|
||||||
import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons";
|
import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons";
|
||||||
|
import renderText from "./content_renderer_text.js";
|
||||||
|
|
||||||
let idCounter = 1;
|
let idCounter = 1;
|
||||||
|
|
||||||
interface Options {
|
export interface RenderOptions {
|
||||||
tooltip?: boolean;
|
tooltip?: boolean;
|
||||||
trim?: boolean;
|
trim?: boolean;
|
||||||
imageHasZoom?: boolean;
|
imageHasZoom?: boolean;
|
||||||
@ -29,7 +26,7 @@ interface Options {
|
|||||||
|
|
||||||
const CODE_MIME_TYPES = new Set(["application/json"]);
|
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(
|
options = Object.assign(
|
||||||
{
|
{
|
||||||
@ -116,33 +113,6 @@ export async function getRenderedContent(this: {} | { ctx: string }, entity: FNo
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderText(note: FNote | FAttachment, $renderedContent: JQuery<HTMLElement>, options: Options = {}) {
|
|
||||||
// entity must be FNote
|
|
||||||
const blob = await note.getBlob();
|
|
||||||
|
|
||||||
if (blob && !utils.isHtmlEmpty(blob.content)) {
|
|
||||||
$renderedContent.append($('<div class="ck-content">').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.
|
* 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<HT
|
|||||||
await applySingleBlockSyntaxHighlight($codeBlock, normalizeMimeTypeForCKEditor(note.mime));
|
await applySingleBlockSyntaxHighlight($codeBlock, normalizeMimeTypeForCKEditor(note.mime));
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderImage(entity: FNote | FAttachment, $renderedContent: JQuery<HTMLElement>, options: Options = {}) {
|
function renderImage(entity: FNote | FAttachment, $renderedContent: JQuery<HTMLElement>, options: RenderOptions = {}) {
|
||||||
const encodedTitle = encodeURIComponent(entity.title);
|
const encodedTitle = encodeURIComponent(entity.title);
|
||||||
|
|
||||||
let url;
|
let url;
|
||||||
@ -306,40 +276,6 @@ async function renderMermaid(note: FNote | FAttachment, $renderedContent: JQuery
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {jQuery} $renderedContent
|
|
||||||
* @param {FNote} note
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async function renderChildrenList($renderedContent: JQuery<HTMLElement>, 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("<br>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRenderingType(entity: FNote | FAttachment) {
|
function getRenderingType(entity: FNote | FAttachment) {
|
||||||
let type: string = "";
|
let type: string = "";
|
||||||
if ("type" in entity) {
|
if ("type" in entity) {
|
||||||
@ -371,34 +307,6 @@ function getRenderingType(entity: FNote | FAttachment) {
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Rewrite the code block from <pre><code> to <div> 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<HTMLElement>("div.mermaid-diagram"));
|
|
||||||
console.log("Got nodes", nodes);
|
|
||||||
try {
|
|
||||||
await mermaid.run({ nodes });
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getRenderedContent
|
getRenderedContent
|
||||||
};
|
};
|
||||||
|
|||||||
93
apps/client/src/services/content_renderer_text.ts
Normal file
93
apps/client/src/services/content_renderer_text.ts
Normal file
@ -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<HTMLElement>, options: RenderOptions = {}) {
|
||||||
|
// entity must be FNote
|
||||||
|
const blob = await note.getBlob();
|
||||||
|
|
||||||
|
if (blob && !isHtmlEmpty(blob.content)) {
|
||||||
|
$renderedContent.append($('<div class="ck-content">').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 <pre><code> to <div> 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<HTMLElement>("div.mermaid-diagram"));
|
||||||
|
try {
|
||||||
|
await mermaid.run({ nodes });
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderChildrenList($renderedContent: JQuery<HTMLElement>, 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("<br>");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -274,7 +274,7 @@ function getMimeTypeClass(mime: string) {
|
|||||||
return `mime-${mime.toLowerCase().replace(/[\W_]+/g, "-")}`;
|
return `mime-${mime.toLowerCase().replace(/[\W_]+/g, "-")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isHtmlEmpty(html: string) {
|
export function isHtmlEmpty(html: string) {
|
||||||
if (!html) {
|
if (!html) {
|
||||||
return true;
|
return true;
|
||||||
} else if (typeof html !== "string") {
|
} else if (typeof html !== "string") {
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import { formatCodeBlocks } from "../../../services/syntax_highlight";
|
|||||||
import TouchBar, { TouchBarButton, TouchBarSpacer } from "../../react/TouchBar";
|
import TouchBar, { TouchBarButton, TouchBarSpacer } from "../../react/TouchBar";
|
||||||
import appContext from "../../../components/app_context";
|
import appContext from "../../../components/app_context";
|
||||||
import { applyReferenceLinks } from "./read_only_helper";
|
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";
|
import clsx from "clsx";
|
||||||
|
|
||||||
export default function ReadOnlyText({ note, noteContext, ntxId }: TypeWidgetProps) {
|
export default function ReadOnlyText({ note, noteContext, ntxId }: TypeWidgetProps) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user