diff --git a/apps/server/src/share/content_renderer.ts b/apps/server/src/share/content_renderer.ts
index c1c16e48d..e94eb4297 100644
--- a/apps/server/src/share/content_renderer.ts
+++ b/apps/server/src/share/content_renderer.ts
@@ -1,10 +1,18 @@
import { JSDOM } from "jsdom";
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 escapeHtml from "escape-html";
import type SNote from "./shaca/entities/snote.js";
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.
@@ -16,6 +24,87 @@ export interface Result {
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) {
if (note.isProtected) {
return {
diff --git a/apps/server/src/share/routes.ts b/apps/server/src/share/routes.ts
index 7e18ca505..4b0281ec5 100644
--- a/apps/server/src/share/routes.ts
+++ b/apps/server/src/share/routes.ts
@@ -4,40 +4,12 @@ import type { Request, Response, Router } from "express";
import shaca from "./shaca/shaca.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 SearchContext from "../services/search/search_context.js";
-import log from "../services/log.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 utils, { isDev, safeExtractMessageAndStackFromError } from "../services/utils.js";
-import options from "../services/options.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());
-}
+import utils from "../services/utils.js";
+import { renderNoteContent } from "./content_renderer.js";
function addNoIndexHeader(note: SNote, res: Response) {
if (note.isLabelTruthy("shareDisallowRobotIndexing")) {
@@ -108,8 +80,7 @@ function renderImageAttachment(image: SNote, res: Response, attachmentName: stri
let svgString = "";
const attachment = image.getAttachmentByTitle(attachmentName);
if (!attachment) {
- res.status(404);
- renderDefault(res, "404");
+
return;
}
const content = attachment.getContent();
@@ -137,12 +108,19 @@ function renderImageAttachment(image: SNote, res: Response, attachmentName: stri
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 renderNote(note: SNote, req: Request, res: Response) {
if (!note) {
console.log("Unable to find note ", note);
res.status(404);
- renderDefault(res, "404");
+ render404(res);
return;
}
@@ -159,63 +137,7 @@ function register(router: Router) {
return;
}
-
- 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);
- }
+ res.send(renderNoteContent(note));
}
router.get("/share/", (req, res) => {
@@ -399,12 +321,6 @@ function register(router: Router) {
});
}
-function renderDefault(res: Response>, 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 {
register
};