mirror of
https://github.com/zadam/trilium.git
synced 2025-11-02 20:49:01 +01:00
refactor(share): extract note rendering logic
This commit is contained in:
parent
26afab03ce
commit
2c6ba9ba2c
@ -1,10 +1,18 @@
|
|||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
import shaca from "./shaca/shaca.js";
|
import shaca from "./shaca/shaca.js";
|
||||||
import assetPath from "../services/asset_path.js";
|
import assetPath, { assetUrlFragment } from "../services/asset_path.js";
|
||||||
import shareRoot from "./share_root.js";
|
import shareRoot from "./share_root.js";
|
||||||
import escapeHtml from "escape-html";
|
import escapeHtml from "escape-html";
|
||||||
import type SNote from "./shaca/entities/snote.js";
|
import type SNote from "./shaca/entities/snote.js";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
|
import SBranch from "./shaca/entities/sbranch.js";
|
||||||
|
import options from "../services/options.js";
|
||||||
|
import { getResourceDir, isDev, safeExtractMessageAndStackFromError } from "../services/utils.js";
|
||||||
|
import app_path from "../services/app_path.js";
|
||||||
|
import ejs from "ejs";
|
||||||
|
import log from "../services/log.js";
|
||||||
|
import { join } from "path";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the output of the content renderer.
|
* Represents the output of the content renderer.
|
||||||
@ -16,6 +24,87 @@ export interface Result {
|
|||||||
isEmpty?: boolean;
|
isEmpty?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSharedSubTreeRoot(note: SNote): { note?: SNote; branch?: SBranch } {
|
||||||
|
if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) {
|
||||||
|
// share root itself is not shared
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// every path leads to share root, but which one to choose?
|
||||||
|
// for the sake of simplicity, URLs are not note paths
|
||||||
|
const parentBranch = note.getParentBranches()[0];
|
||||||
|
|
||||||
|
if (parentBranch.parentNoteId === shareRoot.SHARE_ROOT_NOTE_ID) {
|
||||||
|
return {
|
||||||
|
note,
|
||||||
|
branch: parentBranch
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSharedSubTreeRoot(parentBranch.getParentNote());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderNoteContent(note: SNote) {
|
||||||
|
const { header, content, isEmpty } = getContent(note);
|
||||||
|
const subRoot = getSharedSubTreeRoot(note);
|
||||||
|
const showLoginInShareTheme = options.getOption("showLoginInShareTheme");
|
||||||
|
const opts = {
|
||||||
|
note,
|
||||||
|
header,
|
||||||
|
content,
|
||||||
|
isEmpty,
|
||||||
|
subRoot,
|
||||||
|
assetPath: isDev ? assetPath : `../${assetPath}`,
|
||||||
|
assetUrlFragment,
|
||||||
|
appPath: isDev ? app_path : `../${app_path}`,
|
||||||
|
showLoginInShareTheme,
|
||||||
|
t,
|
||||||
|
isDev
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the user has their own template.
|
||||||
|
if (note.hasRelation("shareTemplate")) {
|
||||||
|
// Get the template note and content
|
||||||
|
const templateId = note.getRelation("shareTemplate")?.value;
|
||||||
|
const templateNote = templateId && shaca.getNote(templateId);
|
||||||
|
|
||||||
|
// Make sure the note type is correct
|
||||||
|
if (templateNote && templateNote.type === "code" && templateNote.mime === "application/x-ejs") {
|
||||||
|
// EJS caches the result of this so we don't need to pre-cache
|
||||||
|
const includer = (path: string) => {
|
||||||
|
const childNote = templateNote.children.find((n) => path === n.title);
|
||||||
|
if (!childNote) throw new Error(`Unable to find child note: ${path}.`);
|
||||||
|
if (childNote.type !== "code" || childNote.mime !== "application/x-ejs") throw new Error("Incorrect child note type.");
|
||||||
|
|
||||||
|
const template = childNote.getContent();
|
||||||
|
if (typeof template !== "string") throw new Error("Invalid template content type.");
|
||||||
|
|
||||||
|
return { template };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to render user's template, w/ fallback to default view
|
||||||
|
try {
|
||||||
|
const content = templateNote.getContent();
|
||||||
|
if (typeof content === "string") {
|
||||||
|
return ejs.render(content, opts, { includer });
|
||||||
|
}
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
|
||||||
|
log.error(`Rendering user provided share template (${templateId}) threw exception ${errMessage} with stacktrace: ${errStack}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render with the default view otherwise.
|
||||||
|
const templatePath = join(getResourceDir(), "share-theme", "templates", "page.ejs");
|
||||||
|
return ejs.render(readFileSync(templatePath, "utf-8"), opts, {
|
||||||
|
includer: (path) => {
|
||||||
|
const templatePath = join(getResourceDir(), "share-theme", "templates", `${path}.ejs`);
|
||||||
|
return { filename: templatePath }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getContent(note: SNote) {
|
function getContent(note: SNote) {
|
||||||
if (note.isProtected) {
|
if (note.isProtected) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -4,40 +4,12 @@ import type { Request, Response, Router } from "express";
|
|||||||
|
|
||||||
import shaca from "./shaca/shaca.js";
|
import shaca from "./shaca/shaca.js";
|
||||||
import shacaLoader from "./shaca/shaca_loader.js";
|
import shacaLoader from "./shaca/shaca_loader.js";
|
||||||
import shareRoot from "./share_root.js";
|
|
||||||
import contentRenderer from "./content_renderer.js";
|
|
||||||
import assetPath, { assetUrlFragment } from "../services/asset_path.js";
|
|
||||||
import appPath from "../services/app_path.js";
|
|
||||||
import searchService from "../services/search/services/search.js";
|
import searchService from "../services/search/services/search.js";
|
||||||
import SearchContext from "../services/search/search_context.js";
|
import SearchContext from "../services/search/search_context.js";
|
||||||
import log from "../services/log.js";
|
|
||||||
import type SNote from "./shaca/entities/snote.js";
|
import type SNote from "./shaca/entities/snote.js";
|
||||||
import type SBranch from "./shaca/entities/sbranch.js";
|
|
||||||
import type SAttachment from "./shaca/entities/sattachment.js";
|
import type SAttachment from "./shaca/entities/sattachment.js";
|
||||||
import utils, { isDev, safeExtractMessageAndStackFromError } from "../services/utils.js";
|
import utils from "../services/utils.js";
|
||||||
import options from "../services/options.js";
|
import { renderNoteContent } from "./content_renderer.js";
|
||||||
import { t } from "i18next";
|
|
||||||
import ejs from "ejs";
|
|
||||||
|
|
||||||
function getSharedSubTreeRoot(note: SNote): { note?: SNote; branch?: SBranch } {
|
|
||||||
if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) {
|
|
||||||
// share root itself is not shared
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// every path leads to share root, but which one to choose?
|
|
||||||
// for the sake of simplicity, URLs are not note paths
|
|
||||||
const parentBranch = note.getParentBranches()[0];
|
|
||||||
|
|
||||||
if (parentBranch.parentNoteId === shareRoot.SHARE_ROOT_NOTE_ID) {
|
|
||||||
return {
|
|
||||||
note,
|
|
||||||
branch: parentBranch
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return getSharedSubTreeRoot(parentBranch.getParentNote());
|
|
||||||
}
|
|
||||||
|
|
||||||
function addNoIndexHeader(note: SNote, res: Response) {
|
function addNoIndexHeader(note: SNote, res: Response) {
|
||||||
if (note.isLabelTruthy("shareDisallowRobotIndexing")) {
|
if (note.isLabelTruthy("shareDisallowRobotIndexing")) {
|
||||||
@ -108,8 +80,7 @@ function renderImageAttachment(image: SNote, res: Response, attachmentName: stri
|
|||||||
let svgString = "<svg/>";
|
let svgString = "<svg/>";
|
||||||
const attachment = image.getAttachmentByTitle(attachmentName);
|
const attachment = image.getAttachmentByTitle(attachmentName);
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
res.status(404);
|
|
||||||
renderDefault(res, "404");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const content = attachment.getContent();
|
const content = attachment.getContent();
|
||||||
@ -137,12 +108,19 @@ function renderImageAttachment(image: SNote, res: Response, attachmentName: stri
|
|||||||
res.send(svg);
|
res.send(svg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function render404(res: Response) {
|
||||||
|
res.status(404);
|
||||||
|
const shareThemePath = `../../share-theme/templates/404.ejs`;
|
||||||
|
res.render(shareThemePath);
|
||||||
|
}
|
||||||
|
|
||||||
function register(router: Router) {
|
function register(router: Router) {
|
||||||
|
|
||||||
function renderNote(note: SNote, req: Request, res: Response) {
|
function renderNote(note: SNote, req: Request, res: Response) {
|
||||||
if (!note) {
|
if (!note) {
|
||||||
console.log("Unable to find note ", note);
|
console.log("Unable to find note ", note);
|
||||||
res.status(404);
|
res.status(404);
|
||||||
renderDefault(res, "404");
|
render404(res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,63 +137,7 @@ function register(router: Router) {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
res.send(renderNoteContent(note));
|
||||||
const { header, content, isEmpty } = contentRenderer.getContent(note);
|
|
||||||
const subRoot = getSharedSubTreeRoot(note);
|
|
||||||
const showLoginInShareTheme = options.getOption("showLoginInShareTheme");
|
|
||||||
const opts = {
|
|
||||||
note,
|
|
||||||
header,
|
|
||||||
content,
|
|
||||||
isEmpty,
|
|
||||||
subRoot,
|
|
||||||
assetPath: isDev ? assetPath : `../${assetPath}`,
|
|
||||||
assetUrlFragment,
|
|
||||||
appPath: isDev ? appPath : `../${appPath}`,
|
|
||||||
showLoginInShareTheme,
|
|
||||||
t,
|
|
||||||
isDev
|
|
||||||
};
|
|
||||||
let useDefaultView = true;
|
|
||||||
|
|
||||||
// Check if the user has their own template
|
|
||||||
if (note.hasRelation("shareTemplate")) {
|
|
||||||
// Get the template note and content
|
|
||||||
const templateId = note.getRelation("shareTemplate")?.value;
|
|
||||||
const templateNote = templateId && shaca.getNote(templateId);
|
|
||||||
|
|
||||||
// Make sure the note type is correct
|
|
||||||
if (templateNote && templateNote.type === "code" && templateNote.mime === "application/x-ejs") {
|
|
||||||
// EJS caches the result of this so we don't need to pre-cache
|
|
||||||
const includer = (path: string) => {
|
|
||||||
const childNote = templateNote.children.find((n) => path === n.title);
|
|
||||||
if (!childNote) throw new Error(`Unable to find child note: ${path}.`);
|
|
||||||
if (childNote.type !== "code" || childNote.mime !== "application/x-ejs") throw new Error("Incorrect child note type.");
|
|
||||||
|
|
||||||
const template = childNote.getContent();
|
|
||||||
if (typeof template !== "string") throw new Error("Invalid template content type.");
|
|
||||||
|
|
||||||
return { template };
|
|
||||||
};
|
|
||||||
|
|
||||||
// Try to render user's template, w/ fallback to default view
|
|
||||||
try {
|
|
||||||
const content = templateNote.getContent();
|
|
||||||
if (typeof content === "string") {
|
|
||||||
const ejsResult = ejs.render(content, opts, { includer });
|
|
||||||
res.send(ejsResult);
|
|
||||||
useDefaultView = false; // Rendering went okay, don't use default view
|
|
||||||
}
|
|
||||||
} catch (e: unknown) {
|
|
||||||
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
|
|
||||||
log.error(`Rendering user provided share template (${templateId}) threw exception ${errMessage} with stacktrace: ${errStack}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useDefaultView) {
|
|
||||||
renderDefault(res, "page", opts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get("/share/", (req, res) => {
|
router.get("/share/", (req, res) => {
|
||||||
@ -399,12 +321,6 @@ function register(router: Router) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDefault(res: Response<any, Record<string, any>>, template: "page" | "404", opts: any = {}) {
|
|
||||||
// Path is relative to apps/server/dist/assets/views
|
|
||||||
const shareThemePath = `../../share-theme/templates/${template}.ejs`;
|
|
||||||
res.render(shareThemePath, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
register
|
register
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user