From 82d1d2ad3e7d55e0c321806695a131f576f3086e Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Fri, 13 Feb 2026 11:46:49 -0600 Subject: [PATCH 1/4] Add custom layout support in ResultsView component - Introduced a new "custom" layout option in ResultsView. - Added a textarea for users to input custom HTML templates with handlebars syntax. - Integrated CustomView component to render results based on the custom template. - Enhanced state management to save template changes dynamically. This update allows for greater flexibility in displaying results according to user-defined templates. --- .../components/results-view/CustomView.tsx | 31 ++++++++ .../components/results-view/ResultsView.tsx | 49 +++++++++++++ apps/roam/src/utils/compileTemplate.ts | 71 +++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 apps/roam/src/components/results-view/CustomView.tsx create mode 100644 apps/roam/src/utils/compileTemplate.ts diff --git a/apps/roam/src/components/results-view/CustomView.tsx b/apps/roam/src/components/results-view/CustomView.tsx new file mode 100644 index 000000000..56d9abc02 --- /dev/null +++ b/apps/roam/src/components/results-view/CustomView.tsx @@ -0,0 +1,31 @@ +import React, { useMemo } from "react"; +import { compileTemplate, sanitizeHtml } from "~/utils/compileTemplate"; +import type { Result } from "~/utils/types"; + +export const DEFAULT_TEMPLATE = ``; + +type CustomViewProps = { + results: Result[]; + template?: string; +}; + +const CustomView = ({ results, template = DEFAULT_TEMPLATE }: CustomViewProps) => { + const html = useMemo(() => { + const compiled = compileTemplate({ template, results }); + return sanitizeHtml({ html: compiled }); + }, [results, template]); + + return ( +
+ ); +}; + +export default CustomView; + diff --git a/apps/roam/src/components/results-view/ResultsView.tsx b/apps/roam/src/components/results-view/ResultsView.tsx index dc2c9c091..847da4fd2 100644 --- a/apps/roam/src/components/results-view/ResultsView.tsx +++ b/apps/roam/src/components/results-view/ResultsView.tsx @@ -37,6 +37,7 @@ import getUids from "roamjs-components/dom/getUids"; import Charts from "./Charts"; import Timeline from "./Timeline"; import Kanban from "./Kanban"; +import CustomView, { DEFAULT_TEMPLATE } from "./CustomView"; import MenuItemSelect from "roamjs-components/components/MenuItemSelect"; import type { RoamBasicNode } from "roamjs-components/types/native"; import { render as renderToast } from "roamjs-components/components/Toast"; @@ -225,6 +226,11 @@ const SUPPORTED_LAYOUTS = [ { key: "legend", label: "Show Legend", options: ["No", "Yes"] }, ], }, + { + id: "custom", + icon: "code-block", + settings: [], + }, ] as const; const settingsById = Object.fromEntries( SUPPORTED_LAYOUTS.map((l) => [l.id, l.settings]), @@ -778,6 +784,40 @@ const ResultsView: ResultsViewComponent = ({ ); })} + {layoutMode === "custom" && ( +