From d9906a4a4751cff8bcdb2c847667b95503d7524a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 18:39:57 +0300 Subject: [PATCH 01/47] feat(collection/presentation): add new view type --- apps/client/src/translations/en/translation.json | 1 + apps/client/src/widgets/collections/interface.ts | 2 +- apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx | 3 ++- apps/client/src/widgets/ribbon/collection-properties-config.ts | 3 +++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 4a59d0a61..092f30221 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -767,6 +767,7 @@ "table": "Table", "geo-map": "Geo Map", "board": "Board", + "presentation": "Presentation", "include_archived_notes": "Show archived notes" }, "edited_notes": { diff --git a/apps/client/src/widgets/collections/interface.ts b/apps/client/src/widgets/collections/interface.ts index 0b2fdb22d..91b9f301b 100644 --- a/apps/client/src/widgets/collections/interface.ts +++ b/apps/client/src/widgets/collections/interface.ts @@ -1,6 +1,6 @@ import FNote from "../../entities/fnote"; -export const allViewTypes = ["list", "grid", "calendar", "table", "geoMap", "board"] as const; +export const allViewTypes = ["list", "grid", "calendar", "table", "geoMap", "board", "presentation"] as const; export type ViewTypeOptions = typeof allViewTypes[number]; export interface ViewModeProps { diff --git a/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx b/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx index 8960fe46d..3ff283589 100644 --- a/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx @@ -19,7 +19,8 @@ const VIEW_TYPE_MAPPINGS: Record = { calendar: t("book_properties.calendar"), table: t("book_properties.table"), geoMap: t("book_properties.geo-map"), - board: t("book_properties.board") + board: t("book_properties.board"), + presentation: t("book_properties.presentation") }; export default function CollectionPropertiesTab({ note }: TabContext) { diff --git a/apps/client/src/widgets/ribbon/collection-properties-config.ts b/apps/client/src/widgets/ribbon/collection-properties-config.ts index 93dbc1076..bb4dd405b 100644 --- a/apps/client/src/widgets/ribbon/collection-properties-config.ts +++ b/apps/client/src/widgets/ribbon/collection-properties-config.ts @@ -159,6 +159,9 @@ export const bookPropertiesConfig: Record = { }, board: { properties: [] + }, + presentation: { + properties: [] } }; From e0e791d9b4570ec00dd9a4d149482f437c6911dd Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 18:49:06 +0300 Subject: [PATCH 02/47] refactor(collections): delete unnecessary type parameter --- apps/client/src/widgets/collections/NoteList.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/collections/NoteList.tsx b/apps/client/src/widgets/collections/NoteList.tsx index e721d3eb9..1f35cfb40 100644 --- a/apps/client/src/widgets/collections/NoteList.tsx +++ b/apps/client/src/widgets/collections/NoteList.tsx @@ -13,7 +13,7 @@ import { subscribeToMessages, unsubscribeToMessage as unsubscribeFromMessage } f import { WebSocketMessage } from "@triliumnext/commons"; import froca from "../../services/froca"; -interface NoteListProps { +interface NoteListProps { note: FNote | null | undefined; notePath: string | null | undefined; highlightedTokens?: string[] | null; @@ -23,17 +23,17 @@ interface NoteListProps { ntxId: string | null | undefined; } -export default function NoteList(props: Pick, "displayOnlyCollections">) { +export default function NoteList(props: Pick) { const { note, noteContext, notePath, ntxId } = useNoteContext(); const isEnabled = noteContext?.hasNoteList(); return } -export function SearchNoteList(props: Omit, "isEnabled">) { +export function SearchNoteList(props: Omit) { return } -function CustomNoteList({ note, isEnabled: shouldEnable, notePath, highlightedTokens, displayOnlyCollections, ntxId }: NoteListProps) { +function CustomNoteList({ note, isEnabled: shouldEnable, notePath, highlightedTokens, displayOnlyCollections, ntxId }: NoteListProps) { const widgetRef = useRef(null); const viewType = useNoteViewType(note); const noteIds = useNoteIds(note, viewType, ntxId); From 025f22553f3c26360a4c9d04d0aa969c79b32b12 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 18:49:29 +0300 Subject: [PATCH 03/47] feat(collection/presentation): add empty view for presentation --- apps/client/src/widgets/collections/NoteList.tsx | 3 +++ apps/client/src/widgets/collections/presentation/index.tsx | 5 +++++ 2 files changed, 8 insertions(+) create mode 100644 apps/client/src/widgets/collections/presentation/index.tsx diff --git a/apps/client/src/widgets/collections/NoteList.tsx b/apps/client/src/widgets/collections/NoteList.tsx index 1f35cfb40..cb43ab1be 100644 --- a/apps/client/src/widgets/collections/NoteList.tsx +++ b/apps/client/src/widgets/collections/NoteList.tsx @@ -12,6 +12,7 @@ import BoardView from "./board"; import { subscribeToMessages, unsubscribeToMessage as unsubscribeFromMessage } from "../../services/ws"; import { WebSocketMessage } from "@triliumnext/commons"; import froca from "../../services/froca"; +import PresentationView from "./presentation"; interface NoteListProps { note: FNote | null | undefined; @@ -104,6 +105,8 @@ function getComponentByViewType(viewType: ViewTypeOptions, props: ViewModeProps< return case "board": return + case "presentation": + return } } diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx new file mode 100644 index 000000000..eada2b14e --- /dev/null +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -0,0 +1,5 @@ +import { ViewModeProps } from "../interface"; + +export default function PresentationView({ }: ViewModeProps<{}>) { + return

Presentation goes here.

; +} From 92e43f5210bb1e959c2f73fa1223371f1b0986c4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 18:54:10 +0300 Subject: [PATCH 04/47] chore(collection/presentation): separate slide builder --- .../src/widgets/collections/presentation/index.tsx | 14 ++++++++++++-- .../collections/presentation/slide_builder.ts | 5 +++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 apps/client/src/widgets/collections/presentation/slide_builder.ts diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index eada2b14e..24b59c047 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -1,5 +1,15 @@ +import { useEffect, useRef, useState } from "preact/hooks"; import { ViewModeProps } from "../interface"; +import { buildPresentation } from "./slide_builder"; -export default function PresentationView({ }: ViewModeProps<{}>) { - return

Presentation goes here.

; +export default function PresentationView({ note }: ViewModeProps<{}>) { + + const containerRef = useRef(null); + + useEffect(() => { + const presentationEl = buildPresentation(note.noteId); + containerRef.current?.replaceChildren(presentationEl); + }, [ note ]); + + return
; } diff --git a/apps/client/src/widgets/collections/presentation/slide_builder.ts b/apps/client/src/widgets/collections/presentation/slide_builder.ts new file mode 100644 index 000000000..1bcc63117 --- /dev/null +++ b/apps/client/src/widgets/collections/presentation/slide_builder.ts @@ -0,0 +1,5 @@ +export function buildPresentation(parentNoteId: string) { + const p = document.createElement("p"); + p.innerHTML = "Hello world"; + return p; +} From 79a31421a4de3c4c8b37d7d1b338a2d2473cb776 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 19:01:13 +0300 Subject: [PATCH 05/47] chore(collection/presentation): use note instead of note id --- .../widgets/collections/presentation/index.tsx | 5 +++-- .../collections/presentation/slide_builder.ts | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 24b59c047..f912e8d2e 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -7,8 +7,9 @@ export default function PresentationView({ note }: ViewModeProps<{}>) { const containerRef = useRef(null); useEffect(() => { - const presentationEl = buildPresentation(note.noteId); - containerRef.current?.replaceChildren(presentationEl); + buildPresentation(note).then(presentationEl => { + containerRef.current?.replaceChildren(presentationEl); + }); }, [ note ]); return
; diff --git a/apps/client/src/widgets/collections/presentation/slide_builder.ts b/apps/client/src/widgets/collections/presentation/slide_builder.ts index 1bcc63117..fcfdca8d0 100644 --- a/apps/client/src/widgets/collections/presentation/slide_builder.ts +++ b/apps/client/src/widgets/collections/presentation/slide_builder.ts @@ -1,5 +1,13 @@ -export function buildPresentation(parentNoteId: string) { - const p = document.createElement("p"); - p.innerHTML = "Hello world"; - return p; +import FNote from "../../../entities/fnote"; + +export async function buildPresentation(parentNote: FNote) { + const slides = await parentNote.getChildNotes(); + const rootElement = new DocumentFragment(); + + for (const slide of slides) { + const slideEl = document.createElement("div"); + + } + + return rootElement; } From 81b2b18eb70965fd8c8b364fb196c359ace1b9fa Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 19:05:34 +0300 Subject: [PATCH 06/47] refactor(collection/presentation): use React way --- .../collections/presentation/index.tsx | 25 +++++++++++++------ .../collections/presentation/slide_builder.ts | 13 ---------- 2 files changed, 17 insertions(+), 21 deletions(-) delete mode 100644 apps/client/src/widgets/collections/presentation/slide_builder.ts diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index f912e8d2e..8fd856acd 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -1,16 +1,25 @@ -import { useEffect, useRef, useState } from "preact/hooks"; import { ViewModeProps } from "../interface"; -import { buildPresentation } from "./slide_builder"; +import FNote from "../../../entities/fnote"; +import { useLayoutEffect, useState } from "preact/hooks"; export default function PresentationView({ note }: ViewModeProps<{}>) { + return note && ( + + ) +} - const containerRef = useRef(null); +function Presentation({ note }: { note: FNote }) { + const [ slides, setSlides ] = useState(); - useEffect(() => { - buildPresentation(note).then(presentationEl => { - containerRef.current?.replaceChildren(presentationEl); - }); + useLayoutEffect(() => { + note.getChildNotes().then(setSlides); }, [ note ]); - return
; + return (slides && slides?.map(slide => ( + + ))); +} + +function Slide({ note }: { note: FNote }) { + return

{note.title}

} diff --git a/apps/client/src/widgets/collections/presentation/slide_builder.ts b/apps/client/src/widgets/collections/presentation/slide_builder.ts deleted file mode 100644 index fcfdca8d0..000000000 --- a/apps/client/src/widgets/collections/presentation/slide_builder.ts +++ /dev/null @@ -1,13 +0,0 @@ -import FNote from "../../../entities/fnote"; - -export async function buildPresentation(parentNote: FNote) { - const slides = await parentNote.getChildNotes(); - const rootElement = new DocumentFragment(); - - for (const slide of slides) { - const slideEl = document.createElement("div"); - - } - - return rootElement; -} From 56b8381680bdfa0ac51242cf0ec50f97a5a16476 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 19:10:50 +0300 Subject: [PATCH 07/47] chore(collection/presentation): prepare structure for Reveal.js --- .../collections/presentation/index.tsx | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 8fd856acd..ae2c64586 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -15,11 +15,22 @@ function Presentation({ note }: { note: FNote }) { note.getChildNotes().then(setSlides); }, [ note ]); - return (slides && slides?.map(slide => ( - - ))); + return ( +
+
+ {slides && slides?.map(slide => ( + + ))} +
+
+ ) + } function Slide({ note }: { note: FNote }) { - return

{note.title}

+ return ( +
+

{note.title}

+
+ ); } From 343f103126df3cebd826bc07739e598ffb5b79a8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 19:16:55 +0300 Subject: [PATCH 08/47] chore(collection/presentation): install reveal.js --- apps/client/package.json | 1 + pnpm-lock.yaml | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index 6fe3949c4..36b123823 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -58,6 +58,7 @@ "panzoom": "9.4.3", "preact": "10.27.2", "react-i18next": "16.0.1", + "reveal.js": "5.2.1", "split.js": "1.6.5", "svg-pan-zoom": "3.6.2", "tabulator-tables": "6.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03317d892..9d5d794b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -253,6 +253,9 @@ importers: react-i18next: specifier: 16.0.1 version: 16.0.1(i18next@25.6.0(typescript@5.9.3))(react-dom@19.1.0(react@16.14.0))(react@16.14.0)(typescript@5.9.3) + reveal.js: + specifier: 5.2.1 + version: 5.2.1 split.js: specifier: 1.6.5 version: 1.6.5 @@ -11803,6 +11806,10 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + reveal.js@5.2.1: + resolution: {integrity: sha512-r7//6mIM5p34hFiDMvYfXgyjXqGRta+/psd9YtytsgRlrpRzFv4RbH76TXd2qD+7ZPZEbpBDhdRhJaFgfQ7zNQ==} + engines: {node: '>=18.0.0'} + rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} @@ -14616,8 +14623,6 @@ snapshots: '@ckeditor/ckeditor5-core': 47.0.0 '@ckeditor/ckeditor5-upload': 47.0.0 ckeditor5: 47.0.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-ai@47.0.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)': dependencies: @@ -14827,6 +14832,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.0.0 '@ckeditor/ckeditor5-watchdog': 47.0.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-dev-build-tools@43.1.0(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)': dependencies: @@ -15000,6 +15007,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.0.0 ckeditor5: 47.0.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-decoupled@47.0.0': dependencies: @@ -15516,8 +15525,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.0.0 '@ckeditor/ckeditor5-utils': 47.0.0 ckeditor5: 47.0.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-restricted-editing@47.0.0': dependencies: @@ -15604,8 +15611,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.0.0 '@ckeditor/ckeditor5-utils': 47.0.0 ckeditor5: 47.0.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-special-characters@47.0.0': dependencies: @@ -27816,6 +27821,8 @@ snapshots: reusify@1.1.0: {} + reveal.js@5.2.1: {} + rfdc@1.4.1: {} rgb2hex@0.2.5: {} From c736fba1b762e6be39892461df5e1f86cc8cccce Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 19:29:08 +0300 Subject: [PATCH 09/47] feat(collection/presentation): get slidejs to render --- apps/client/package.json | 1 + .../collections/presentation/index.tsx | 27 +++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index 36b123823..856f7db73 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -72,6 +72,7 @@ "@types/leaflet": "1.9.21", "@types/leaflet-gpx": "1.3.8", "@types/mark.js": "8.11.12", + "@types/reveal.js": "5.2.1", "@types/tabulator-tables": "6.2.11", "copy-webpack-plugin": "13.0.1", "happy-dom": "20.0.1", diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index ae2c64586..dbb8bbcd4 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -1,6 +1,9 @@ import { ViewModeProps } from "../interface"; import FNote from "../../../entities/fnote"; -import { useLayoutEffect, useState } from "preact/hooks"; +import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks"; +import Reveal from "reveal.js"; +import "reveal.js/dist/reveal.css"; +import "reveal.js/dist/theme/black.css"; export default function PresentationView({ note }: ViewModeProps<{}>) { return note && ( @@ -10,13 +13,33 @@ export default function PresentationView({ note }: ViewModeProps<{}>) { function Presentation({ note }: { note: FNote }) { const [ slides, setSlides ] = useState(); + const containerRef = useRef(null); + const apiRef = useRef(null); useLayoutEffect(() => { note.getChildNotes().then(setSlides); }, [ note ]); + useEffect(() => { + if (apiRef.current || !containerRef.current) return; + + apiRef.current = new Reveal(containerRef.current, { + transition: "slide" + }); + apiRef.current.initialize().then(() => { + console.log("Slide.js initialized."); + }); + + return () => { + if (apiRef.current) { + apiRef.current.destroy(); + apiRef.current = null; + } + } + }, []); + return ( -
+
{slides && slides?.map(slide => ( From 499c190632fba7b7b2db75050e4f9536bc763dc6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 19:29:19 +0300 Subject: [PATCH 10/47] chore(collection/presentation): add types for reveal.js --- pnpm-lock.yaml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d5d794b8..8a3bca65a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -290,6 +290,9 @@ importers: '@types/mark.js': specifier: 8.11.12 version: 8.11.12 + '@types/reveal.js': + specifier: 5.2.1 + version: 5.2.1 '@types/tabulator-tables': specifier: 6.2.11 version: 6.2.11 @@ -5009,6 +5012,9 @@ packages: '@types/retry@0.12.2': resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} + '@types/reveal.js@5.2.1': + resolution: {integrity: sha512-egr+amW5iilXo94kEGyJv24bJozsu/XAOHnhMHLnaJkHVxoui2gsWqzByaltA5zfXDTH2F4WyWnAkhHRcpytIQ==} + '@types/safe-compare@1.1.2': resolution: {integrity: sha512-kK/IM1+pvwCMom+Kezt/UlP8LMEwm8rP6UgGbRc6zUnhU/csoBQ5rWgmD2CJuHxiMiX+H1VqPGpo0kDluJGXYA==} @@ -15007,8 +15013,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.0.0 ckeditor5: 47.0.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-decoupled@47.0.0': dependencies: @@ -15027,6 +15031,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.0.0 ckeditor5: 47.0.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-multi-root@47.0.0': dependencies: @@ -15525,6 +15531,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.0.0 '@ckeditor/ckeditor5-utils': 47.0.0 ckeditor5: 47.0.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-restricted-editing@47.0.0': dependencies: @@ -19474,6 +19482,8 @@ snapshots: '@types/retry@0.12.2': {} + '@types/reveal.js@5.2.1': {} + '@types/safe-compare@1.1.2': {} '@types/sanitize-html@2.16.0': From ecf29fa0e87f4804bd1805ca6016df50cd45a798 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 19:40:46 +0300 Subject: [PATCH 11/47] chore(collection/presentation): use model based mechanism for note content --- .../collections/presentation/index.tsx | 30 +++++++++++-------- .../widgets/collections/presentation/model.ts | 25 ++++++++++++++++ 2 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 apps/client/src/widgets/collections/presentation/model.ts diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index dbb8bbcd4..7d57ef573 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -4,22 +4,24 @@ import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks"; import Reveal from "reveal.js"; import "reveal.js/dist/reveal.css"; import "reveal.js/dist/theme/black.css"; +import { buildPresentationModel, PresentationModel, PresentationSlideModel } from "./model"; export default function PresentationView({ note }: ViewModeProps<{}>) { - return note && ( - + const [ presentation, setPresentation ] = useState(); + + useLayoutEffect(() => { + buildPresentationModel(note).then(setPresentation); + }, [ note ]); + + return presentation && ( + ) } -function Presentation({ note }: { note: FNote }) { - const [ slides, setSlides ] = useState(); +function Presentation({ presentation } : { presentation: PresentationModel }) { const containerRef = useRef(null); const apiRef = useRef(null); - useLayoutEffect(() => { - note.getChildNotes().then(setSlides); - }, [ note ]); - useEffect(() => { if (apiRef.current || !containerRef.current) return; @@ -41,8 +43,8 @@ function Presentation({ note }: { note: FNote }) { return (
- {slides && slides?.map(slide => ( - + {presentation.slides?.map(slide => ( + ))}
@@ -50,10 +52,12 @@ function Presentation({ note }: { note: FNote }) { } -function Slide({ note }: { note: FNote }) { +function Slide({ slide }: { slide: PresentationSlideModel }) { + const containerRef = useRef(null); + return ( -
-

{note.title}

+
+ {slide.content}
); } diff --git a/apps/client/src/widgets/collections/presentation/model.ts b/apps/client/src/widgets/collections/presentation/model.ts new file mode 100644 index 000000000..a7108a5d6 --- /dev/null +++ b/apps/client/src/widgets/collections/presentation/model.ts @@ -0,0 +1,25 @@ +import FNote from "../../../entities/fnote"; + +export interface PresentationSlideModel { + content: string; +} + +export interface PresentationModel { + slides: PresentationSlideModel[]; +} + +export async function buildPresentationModel(note: FNote): Promise { + + const slideNotes = await note.getChildNotes(); + const slides: PresentationSlideModel[] = []; + + for (const slideNote of slideNotes) { + slides.push({ + content: await slideNote.getContent() ?? "" + }) + } + + return { + slides + }; +} From f9754cd82d61925396530482bfcc37f8984b5347 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 19:49:25 +0300 Subject: [PATCH 12/47] chore(collection/presentation): render note content --- apps/client/src/widgets/collections/presentation/index.tsx | 4 +--- apps/client/src/widgets/collections/presentation/model.ts | 6 ++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 7d57ef573..b3d96d69d 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -53,10 +53,8 @@ function Presentation({ presentation } : { presentation: PresentationModel }) { } function Slide({ slide }: { slide: PresentationSlideModel }) { - const containerRef = useRef(null); - return ( -
+
{slide.content}
); diff --git a/apps/client/src/widgets/collections/presentation/model.ts b/apps/client/src/widgets/collections/presentation/model.ts index a7108a5d6..c87a8c9d6 100644 --- a/apps/client/src/widgets/collections/presentation/model.ts +++ b/apps/client/src/widgets/collections/presentation/model.ts @@ -1,7 +1,7 @@ import FNote from "../../../entities/fnote"; export interface PresentationSlideModel { - content: string; + content: { __html: string; }; } export interface PresentationModel { @@ -15,7 +15,9 @@ export async function buildPresentationModel(note: FNote): Promise Date: Wed, 15 Oct 2025 20:05:39 +0300 Subject: [PATCH 13/47] chore(collection/presentation): render with shadow DOM --- .../collections/presentation/index.tsx | 12 ++++++--- apps/client/src/widgets/react/ShadowDom.tsx | 26 +++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 apps/client/src/widgets/react/ShadowDom.tsx diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index b3d96d69d..e3329970e 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -1,10 +1,10 @@ import { ViewModeProps } from "../interface"; -import FNote from "../../../entities/fnote"; import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks"; import Reveal from "reveal.js"; -import "reveal.js/dist/reveal.css"; -import "reveal.js/dist/theme/black.css"; +import slideBaseStylesheet from "reveal.js/dist/reveal.css?raw"; +import slideThemeStylesheet from "reveal.js/dist/theme/black.css?raw"; import { buildPresentationModel, PresentationModel, PresentationSlideModel } from "./model"; +import ShadowDom from "../../react/ShadowDom"; export default function PresentationView({ note }: ViewModeProps<{}>) { const [ presentation, setPresentation ] = useState(); @@ -14,7 +14,11 @@ export default function PresentationView({ note }: ViewModeProps<{}>) { }, [ note ]); return presentation && ( - + + + + + ) } diff --git a/apps/client/src/widgets/react/ShadowDom.tsx b/apps/client/src/widgets/react/ShadowDom.tsx new file mode 100644 index 000000000..6f7a889db --- /dev/null +++ b/apps/client/src/widgets/react/ShadowDom.tsx @@ -0,0 +1,26 @@ +import { ComponentChildren, HTMLAttributes, JSX, render } from "preact"; +import { useEffect, useRef, useState } from "preact/hooks"; + +interface ShadowDomProps extends HTMLAttributes { + children: ComponentChildren; +} + +export default function ShadowDom({ children, ...containerProps }: ShadowDomProps) { + const containerRef = useRef(null); + const [ shadowRoot, setShadowRoot ] = useState(null); + + // Create the shadow root. + useEffect(() => { + if (!containerRef.current || shadowRoot) return; + const shadow = containerRef.current.attachShadow({ mode: "open" }); + setShadowRoot(shadow); + }, [ shadowRoot ]); + + // Render the child elements. + useEffect(() => { + if (!shadowRoot) return; + render(<>{children}, shadowRoot); + }, [ shadowRoot, children ]); + + return
+} From 9281cc9290c73a502fd586cbfd2237fad6ae4406 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 20:18:53 +0300 Subject: [PATCH 14/47] fix(collection/presentation): DOM buttons are not visible after shadow DOM --- .../src/widgets/collections/presentation/index.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index e3329970e..9ac6e87e9 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -1,8 +1,8 @@ import { ViewModeProps } from "../interface"; import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks"; import Reveal from "reveal.js"; -import slideBaseStylesheet from "reveal.js/dist/reveal.css?raw"; -import slideThemeStylesheet from "reveal.js/dist/theme/black.css?raw"; +import slideBaseStylesheetUrl from "reveal.js/dist/reveal.css?url"; +import slideThemeStylesheetUrl from "reveal.js/dist/theme/black.css?url"; import { buildPresentationModel, PresentationModel, PresentationSlideModel } from "./model"; import ShadowDom from "../../react/ShadowDom"; @@ -15,8 +15,8 @@ export default function PresentationView({ note }: ViewModeProps<{}>) { return presentation && ( - - + + ) @@ -30,7 +30,8 @@ function Presentation({ presentation } : { presentation: PresentationModel }) { if (apiRef.current || !containerRef.current) return; apiRef.current = new Reveal(containerRef.current, { - transition: "slide" + transition: "slide", + embedded: true }); apiRef.current.initialize().then(() => { console.log("Slide.js initialized."); From 9f993363d76b48edc712c4cce6581bd6613ec210 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 20:25:07 +0300 Subject: [PATCH 15/47] fix(collection/presentation): CSS variables not working in shadow DOM --- .../src/widgets/collections/presentation/index.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 9ac6e87e9..dca20b2a0 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -1,11 +1,16 @@ import { ViewModeProps } from "../interface"; import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks"; import Reveal from "reveal.js"; -import slideBaseStylesheetUrl from "reveal.js/dist/reveal.css?url"; -import slideThemeStylesheetUrl from "reveal.js/dist/theme/black.css?url"; +import slideBaseStylesheet from "reveal.js/dist/reveal.css?raw"; +import slideThemeStylesheet from "reveal.js/dist/theme/black.css?raw"; import { buildPresentationModel, PresentationModel, PresentationSlideModel } from "./model"; import ShadowDom from "../../react/ShadowDom"; +const stylesheets = [ + slideBaseStylesheet, + slideThemeStylesheet +].map(stylesheet => stylesheet.replace(/:root/g, ":host")); + export default function PresentationView({ note }: ViewModeProps<{}>) { const [ presentation, setPresentation ] = useState(); @@ -15,8 +20,7 @@ export default function PresentationView({ note }: ViewModeProps<{}>) { return presentation && ( - - + {stylesheets.map(stylesheet => )} ) From 15fc98fca1a3186b1bfe97bc87285a13a162375f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 20:48:09 +0300 Subject: [PATCH 16/47] feat(collection/presentation): button to enter full screen --- .../collections/presentation/index.css | 5 +++ .../collections/presentation/index.tsx | 32 ++++++++++++++++--- apps/client/src/widgets/react/ShadowDom.tsx | 12 ++++--- 3 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 apps/client/src/widgets/collections/presentation/index.css diff --git a/apps/client/src/widgets/collections/presentation/index.css b/apps/client/src/widgets/collections/presentation/index.css new file mode 100644 index 000000000..5d96c177c --- /dev/null +++ b/apps/client/src/widgets/collections/presentation/index.css @@ -0,0 +1,5 @@ +.presentation-button-bar { + position: absolute; + top: 1em; + right: 1em; +} \ No newline at end of file diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index dca20b2a0..9f1b3715d 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -5,6 +5,9 @@ import slideBaseStylesheet from "reveal.js/dist/reveal.css?raw"; import slideThemeStylesheet from "reveal.js/dist/theme/black.css?raw"; import { buildPresentationModel, PresentationModel, PresentationSlideModel } from "./model"; import ShadowDom from "../../react/ShadowDom"; +import ActionButton from "../../react/ActionButton"; +import "./index.css"; +import { RefObject } from "preact"; const stylesheets = [ slideBaseStylesheet, @@ -13,16 +16,37 @@ const stylesheets = [ export default function PresentationView({ note }: ViewModeProps<{}>) { const [ presentation, setPresentation ] = useState(); + const containerRef = useRef(null); useLayoutEffect(() => { buildPresentationModel(note).then(setPresentation); }, [ note ]); return presentation && ( - - {stylesheets.map(stylesheet => )} - - + <> + + {stylesheets.map(stylesheet => )} + + + + + ) +} + +function ButtonOverlay({ containerRef }: { containerRef: RefObject }) { + return ( +
+ { + containerRef.current?.requestFullscreen(); + }} + /> +
) } diff --git a/apps/client/src/widgets/react/ShadowDom.tsx b/apps/client/src/widgets/react/ShadowDom.tsx index 6f7a889db..3bbd23427 100644 --- a/apps/client/src/widgets/react/ShadowDom.tsx +++ b/apps/client/src/widgets/react/ShadowDom.tsx @@ -1,12 +1,14 @@ -import { ComponentChildren, HTMLAttributes, JSX, render } from "preact"; -import { useEffect, useRef, useState } from "preact/hooks"; +import { ComponentChildren, HTMLAttributes, JSX, RefObject, render } from "preact"; +import { useEffect, useState } from "preact/hooks"; +import { useSyncedRef } from "./hooks"; -interface ShadowDomProps extends HTMLAttributes { +interface ShadowDomProps extends Omit, "ref"> { children: ComponentChildren; + containerRef?: RefObject; } -export default function ShadowDom({ children, ...containerProps }: ShadowDomProps) { - const containerRef = useRef(null); +export default function ShadowDom({ children, containerRef: externalContainerRef, ...containerProps }: ShadowDomProps) { + const containerRef = useSyncedRef(externalContainerRef, null); const [ shadowRoot, setShadowRoot ] = useState(null); // Create the shadow root. From 8f9ee3c1a9950f5cf4aaf5682d0592a7155c82c7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 20:48:43 +0300 Subject: [PATCH 17/47] refactor(collection/presentation): move style to CSS --- apps/client/src/widgets/collections/presentation/index.css | 5 +++++ apps/client/src/widgets/collections/presentation/index.tsx | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/collections/presentation/index.css b/apps/client/src/widgets/collections/presentation/index.css index 5d96c177c..5aafffd9f 100644 --- a/apps/client/src/widgets/collections/presentation/index.css +++ b/apps/client/src/widgets/collections/presentation/index.css @@ -2,4 +2,9 @@ position: absolute; top: 1em; right: 1em; +} + +.presentation-container { + width: 100%; + height: 100%; } \ No newline at end of file diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 9f1b3715d..e9d7eecec 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -26,7 +26,6 @@ export default function PresentationView({ note }: ViewModeProps<{}>) { <> {stylesheets.map(stylesheet => )} From cd7a1af7296c3acc3ce9bac359be5fc3900db667 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 21:24:42 +0300 Subject: [PATCH 18/47] feat(collection/presentation): add support for vertical slides --- .../collections/presentation/index.tsx | 19 +++++++--- .../widgets/collections/presentation/model.ts | 35 +++++++++++++++---- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index e9d7eecec..34c664247 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -85,9 +85,18 @@ function Presentation({ presentation } : { presentation: PresentationModel }) { } function Slide({ slide }: { slide: PresentationSlideModel }) { - return ( -
- {slide.content} -
- ); + if (!slide.verticalSlides) { + // Normal slide. + return
; + } else { + // Slide with sub notes (show as vertical slides). + return ( +
+
+ {slide.verticalSlides.map((slide) => ( +
+ ))} +
+ ) + } } diff --git a/apps/client/src/widgets/collections/presentation/model.ts b/apps/client/src/widgets/collections/presentation/model.ts index c87a8c9d6..838cf7a4c 100644 --- a/apps/client/src/widgets/collections/presentation/model.ts +++ b/apps/client/src/widgets/collections/presentation/model.ts @@ -1,7 +1,14 @@ import FNote from "../../../entities/fnote"; +type DangerouslySetInnerHTML = { __html: string; }; + export interface PresentationSlideModel { - content: { __html: string; }; + content: DangerouslySetInnerHTML; + verticalSlides: PresentationVerticalSlideModel[] | undefined; +} + +interface PresentationVerticalSlideModel { + content: DangerouslySetInnerHTML; } export interface PresentationModel { @@ -15,13 +22,27 @@ export async function buildPresentationModel(note: FNote): Promise { + const children = await parentSlideNote.getChildNotes(); + if (!children.length) return; + + const slides: PresentationVerticalSlideModel[] = []; + for (const child of children) { + slides.push({ + content: processContent(await child.getContent()) + }); + } + return slides; +} + +function processContent(content: string | undefined): DangerouslySetInnerHTML { + return { __html: content ?? "" }; } From 3495ed82fbeba6cceff898694235fd978d896a05 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 21:30:21 +0300 Subject: [PATCH 19/47] fix(collection/presentation): images appear stretched --- apps/client/src/widgets/collections/presentation/index.tsx | 4 +++- apps/client/src/widgets/collections/presentation/slidejs.css | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 apps/client/src/widgets/collections/presentation/slidejs.css diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 34c664247..c6429b8d7 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -3,6 +3,7 @@ import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks"; import Reveal from "reveal.js"; import slideBaseStylesheet from "reveal.js/dist/reveal.css?raw"; import slideThemeStylesheet from "reveal.js/dist/theme/black.css?raw"; +import slideCustomStylesheet from "./slidejs.css?raw"; import { buildPresentationModel, PresentationModel, PresentationSlideModel } from "./model"; import ShadowDom from "../../react/ShadowDom"; import ActionButton from "../../react/ActionButton"; @@ -11,7 +12,8 @@ import { RefObject } from "preact"; const stylesheets = [ slideBaseStylesheet, - slideThemeStylesheet + slideThemeStylesheet, + slideCustomStylesheet ].map(stylesheet => stylesheet.replace(/:root/g, ":host")); export default function PresentationView({ note }: ViewModeProps<{}>) { diff --git a/apps/client/src/widgets/collections/presentation/slidejs.css b/apps/client/src/widgets/collections/presentation/slidejs.css new file mode 100644 index 000000000..6fd846f0b --- /dev/null +++ b/apps/client/src/widgets/collections/presentation/slidejs.css @@ -0,0 +1,4 @@ +figure img { + aspect-ratio: unset !important; + height: auto !important; +} \ No newline at end of file From 8a85edf2db34083d761d0ed115c8eedce15ef1e5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 21:32:43 +0300 Subject: [PATCH 20/47] fix(collection/presentation): ocassional error when trying to enter fullscreen via key combination --- .../src/widgets/collections/presentation/index.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index c6429b8d7..47348ea65 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -60,7 +60,15 @@ function Presentation({ presentation } : { presentation: PresentationModel }) { apiRef.current = new Reveal(containerRef.current, { transition: "slide", - embedded: true + embedded: true, + keyboardCondition(event) { + // Full-screen requests sometimes fail, we rely on the UI button instead. + if (event.key === "f") { + return false; + } + + return true; + }, }); apiRef.current.initialize().then(() => { console.log("Slide.js initialized."); From 502e9b86bc3c38686ff0dd2f531816db3fb296ea Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 22:08:04 +0300 Subject: [PATCH 21/47] feat(collection/presentation): add button to edit slide --- apps/client/src/components/note_context.ts | 18 ++++++++ apps/client/src/services/link.ts | 17 +------ .../collections/presentation/index.tsx | 44 ++++++++++++++----- .../widgets/collections/presentation/model.ts | 16 ++++--- 4 files changed, 62 insertions(+), 33 deletions(-) diff --git a/apps/client/src/components/note_context.ts b/apps/client/src/components/note_context.ts index 1bc4e5498..4b77e4965 100644 --- a/apps/client/src/components/note_context.ts +++ b/apps/client/src/components/note_context.ts @@ -438,4 +438,22 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded"> } } +export function openInCurrentNoteContext(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent | null, notePath: string, viewScope?: ViewScope) { + const ntxId = $(evt?.target as any) + .closest("[data-ntx-id]") + .attr("data-ntx-id"); + + const noteContext = ntxId ? appContext.tabManager.getNoteContextById(ntxId) : appContext.tabManager.getActiveContext(); + + if (noteContext) { + noteContext.setNote(notePath, { viewScope }).then(() => { + if (noteContext !== appContext.tabManager.getActiveContext()) { + appContext.tabManager.activateNoteContext(noteContext.ntxId); + } + }); + } else { + appContext.tabManager.openContextWithNote(notePath, { viewScope, activate: true }); + } +} + export default NoteContext; diff --git a/apps/client/src/services/link.ts b/apps/client/src/services/link.ts index 16ca48bd7..b0ab04d07 100644 --- a/apps/client/src/services/link.ts +++ b/apps/client/src/services/link.ts @@ -4,6 +4,7 @@ import appContext, { type NoteCommandData } from "../components/app_context.js"; import froca from "./froca.js"; import utils from "./utils.js"; import { ALLOWED_PROTOCOLS } from "@triliumnext/commons"; +import { openInCurrentNoteContext } from "../components/note_context.js"; function getNotePathFromUrl(url: string) { const notePathMatch = /#(root[A-Za-z0-9_/]*)$/.exec(url); @@ -316,21 +317,7 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent viewScope }); } else if (isLeftClick) { - const ntxId = $(evt?.target as any) - .closest("[data-ntx-id]") - .attr("data-ntx-id"); - - const noteContext = ntxId ? appContext.tabManager.getNoteContextById(ntxId) : appContext.tabManager.getActiveContext(); - - if (noteContext) { - noteContext.setNote(notePath, { viewScope }).then(() => { - if (noteContext !== appContext.tabManager.getActiveContext()) { - appContext.tabManager.activateNoteContext(noteContext.ntxId); - } - }); - } else { - appContext.tabManager.openContextWithNote(notePath, { viewScope, activate: true }); - } + openInCurrentNoteContext(evt, notePath, viewScope); } } else if (hrefLink) { const withinEditLink = $link?.hasClass("ck-link-actions__preview"); diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 47348ea65..1d086fef7 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -9,6 +9,7 @@ import ShadowDom from "../../react/ShadowDom"; import ActionButton from "../../react/ActionButton"; import "./index.css"; import { RefObject } from "preact"; +import { openInCurrentNoteContext } from "../../../components/note_context"; const stylesheets = [ slideBaseStylesheet, @@ -19,6 +20,7 @@ const stylesheets = [ export default function PresentationView({ note }: ViewModeProps<{}>) { const [ presentation, setPresentation ] = useState(); const containerRef = useRef(null); + const apiRef = useRef(null); useLayoutEffect(() => { buildPresentationModel(note).then(setPresentation); @@ -31,29 +33,41 @@ export default function PresentationView({ note }: ViewModeProps<{}>) { containerRef={containerRef} > {stylesheets.map(stylesheet => )} - + - + ) } -function ButtonOverlay({ containerRef }: { containerRef: RefObject }) { +function ButtonOverlay({ containerRef, apiRef }: { containerRef: RefObject, apiRef: RefObject }) { return (
{ - containerRef.current?.requestFullscreen(); + icon="bx bx-edit" + text="Edit this slide" + onClick={e => { + const currentSlide = apiRef.current?.getCurrentSlide(); + const noteId = getNoteIdFromSlide(currentSlide); + + if (noteId) { + openInCurrentNoteContext(e, noteId); + } }} /> + + containerRef.current?.requestFullscreen()} + />
) } -function Presentation({ presentation } : { presentation: PresentationModel }) { +function Presentation({ presentation, apiRef: externalApiRef } : { presentation: PresentationModel, apiRef: RefObject }) { const containerRef = useRef(null); - const apiRef = useRef(null); + const apiRef = useRef(null); useEffect(() => { if (apiRef.current || !containerRef.current) return; @@ -70,8 +84,9 @@ function Presentation({ presentation } : { presentation: PresentationModel }) { return true; }, }); + externalApiRef.current = apiRef.current; apiRef.current.initialize().then(() => { - console.log("Slide.js initialized."); + // Initialization logic. }); return () => { @@ -97,16 +112,21 @@ function Presentation({ presentation } : { presentation: PresentationModel }) { function Slide({ slide }: { slide: PresentationSlideModel }) { if (!slide.verticalSlides) { // Normal slide. - return
; + return
; } else { // Slide with sub notes (show as vertical slides). return (
-
+
{slide.verticalSlides.map((slide) => ( -
+
))}
) } } + +function getNoteIdFromSlide(slide: HTMLElement | undefined) { + if (!slide) return; + return slide.dataset.noteId; +} diff --git a/apps/client/src/widgets/collections/presentation/model.ts b/apps/client/src/widgets/collections/presentation/model.ts index 838cf7a4c..ba95dbc9f 100644 --- a/apps/client/src/widgets/collections/presentation/model.ts +++ b/apps/client/src/widgets/collections/presentation/model.ts @@ -2,12 +2,14 @@ import FNote from "../../../entities/fnote"; type DangerouslySetInnerHTML = { __html: string; }; -export interface PresentationSlideModel { - content: DangerouslySetInnerHTML; - verticalSlides: PresentationVerticalSlideModel[] | undefined; +/** A top-level slide with optional vertical slides. */ +export interface PresentationSlideModel extends PresentationSlideBaseModel { + verticalSlides: PresentationSlideBaseModel[] | undefined; } -interface PresentationVerticalSlideModel { +/** Either a top-level slide or a vertical slide. */ +interface PresentationSlideBaseModel { + noteId: string; content: DangerouslySetInnerHTML; } @@ -22,6 +24,7 @@ export async function buildPresentationModel(note: FNote): Promise { +async function buildVerticalSlides(parentSlideNote: FNote): Promise { const children = await parentSlideNote.getChildNotes(); if (!children.length) return; - const slides: PresentationVerticalSlideModel[] = []; + const slides: PresentationSlideBaseModel[] = []; for (const child of children) { slides.push({ + noteId: child.noteId, content: processContent(await child.getContent()) }); } From b1babd62aab0ace7850e7915beb7dbc5f8066b85 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 22:25:40 +0300 Subject: [PATCH 22/47] feat(collection/presentation): use content renderer to support other note types --- apps/client/src/services/content_renderer.ts | 2 +- .../widgets/collections/presentation/model.ts | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/client/src/services/content_renderer.ts b/apps/client/src/services/content_renderer.ts index b0a10b868..e891b96d7 100644 --- a/apps/client/src/services/content_renderer.ts +++ b/apps/client/src/services/content_renderer.ts @@ -29,7 +29,7 @@ interface Options { const CODE_MIME_TYPES = new Set(["application/json"]); -async function getRenderedContent(this: {} | { ctx: string }, entity: FNote | FAttachment, options: Options = {}) { +export async function getRenderedContent(this: {} | { ctx: string }, entity: FNote | FAttachment, options: Options = {}) { options = Object.assign( { diff --git a/apps/client/src/widgets/collections/presentation/model.ts b/apps/client/src/widgets/collections/presentation/model.ts index ba95dbc9f..2d53f4ef7 100644 --- a/apps/client/src/widgets/collections/presentation/model.ts +++ b/apps/client/src/widgets/collections/presentation/model.ts @@ -1,4 +1,5 @@ import FNote from "../../../entities/fnote"; +import contentRenderer from "../../../services/content_renderer"; type DangerouslySetInnerHTML = { __html: string; }; @@ -25,7 +26,7 @@ export async function buildPresentationModel(note: FNote): Promise { + const { $renderedContent } = await contentRenderer.getRenderedContent(note, { + + }); + return { __html: $renderedContent.html() }; } From c8b7322f1e3f918fa63f964278eeac2fcea69f81 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 22:29:34 +0300 Subject: [PATCH 23/47] feat(collection/presentation): react to tree changes --- apps/client/src/widgets/collections/presentation/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 1d086fef7..5760fd2dd 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -17,14 +17,14 @@ const stylesheets = [ slideCustomStylesheet ].map(stylesheet => stylesheet.replace(/:root/g, ":host")); -export default function PresentationView({ note }: ViewModeProps<{}>) { +export default function PresentationView({ note, noteIds }: ViewModeProps<{}>) { const [ presentation, setPresentation ] = useState(); const containerRef = useRef(null); const apiRef = useRef(null); useLayoutEffect(() => { buildPresentationModel(note).then(setPresentation); - }, [ note ]); + }, [ note, noteIds ]); return presentation && ( <> From 66ba4a596c27373beb3a69b05f5cbf173f119299 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 22:31:29 +0300 Subject: [PATCH 24/47] fix(collection/presentation): sometimes doesn't refresh properly --- apps/client/src/widgets/collections/presentation/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 5760fd2dd..8ca32b17e 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -95,13 +95,13 @@ function Presentation({ presentation, apiRef: externalApiRef } : { presentation: apiRef.current = null; } } - }, []); + }, [ presentation ]); return (
{presentation.slides?.map(slide => ( - + ))}
From 4d772ab48dc856312c508e8df296482aa8e5d7f6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 22:34:01 +0300 Subject: [PATCH 25/47] feat(collection/presentation): use sync instead of full reload --- .../widgets/collections/presentation/index.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 8ca32b17e..856947e41 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -72,7 +72,7 @@ function Presentation({ presentation, apiRef: externalApiRef } : { presentation: useEffect(() => { if (apiRef.current || !containerRef.current) return; - apiRef.current = new Reveal(containerRef.current, { + const api = new Reveal(containerRef.current, { transition: "slide", embedded: true, keyboardCondition(event) { @@ -85,16 +85,18 @@ function Presentation({ presentation, apiRef: externalApiRef } : { presentation: }, }); externalApiRef.current = apiRef.current; - apiRef.current.initialize().then(() => { - // Initialization logic. + api.initialize().then(() => { + apiRef.current = api; }); return () => { - if (apiRef.current) { - apiRef.current.destroy(); - apiRef.current = null; - } + api.destroy(); + apiRef.current = null; } + }, [ ]); + + useEffect(() => { + apiRef.current?.sync(); }, [ presentation ]); return ( From f33fe4266afc7dd0669a0ce16149b400514501d3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 22:36:04 +0300 Subject: [PATCH 26/47] chore(collection/presentation): don't sync on first render --- apps/client/src/widgets/collections/presentation/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 856947e41..b384b0220 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -68,6 +68,7 @@ function ButtonOverlay({ containerRef, apiRef }: { containerRef: RefObject }) { const containerRef = useRef(null); const apiRef = useRef(null); + const isFirstRenderRef = useRef(true); useEffect(() => { if (apiRef.current || !containerRef.current) return; @@ -96,7 +97,10 @@ function Presentation({ presentation, apiRef: externalApiRef } : { presentation: }, [ ]); useEffect(() => { - apiRef.current?.sync(); + if (!isFirstRenderRef.current) { + apiRef.current?.sync(); + } + isFirstRenderRef.current = false; }, [ presentation ]); return ( From 3c5e0855d7cb44904fea643e7699cbf6e3c0e56b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 22:51:03 +0300 Subject: [PATCH 27/47] fix(collection/presentation): math equations rendering twice --- .../src/widgets/collections/presentation/index.tsx | 14 ++++++++++++-- .../widgets/collections/presentation/slidejs.css | 4 ++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index b384b0220..c25d0678f 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -10,6 +10,7 @@ import ActionButton from "../../react/ActionButton"; import "./index.css"; import { RefObject } from "preact"; import { openInCurrentNoteContext } from "../../../components/note_context"; +import { useTriliumEvent } from "../../react/hooks"; const stylesheets = [ slideBaseStylesheet, @@ -22,9 +23,18 @@ export default function PresentationView({ note, noteIds }: ViewModeProps<{}>) { const containerRef = useRef(null); const apiRef = useRef(null); - useLayoutEffect(() => { + function refresh() { buildPresentationModel(note).then(setPresentation); - }, [ note, noteIds ]); + } + + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { + if (loadResults.getNoteIds().find(noteId => noteIds.includes(noteId))) { + console.log("Needs reload!"); + refresh(); + } + }); + + useLayoutEffect(refresh, [ note, noteIds ]); return presentation && ( <> diff --git a/apps/client/src/widgets/collections/presentation/slidejs.css b/apps/client/src/widgets/collections/presentation/slidejs.css index 6fd846f0b..25363245c 100644 --- a/apps/client/src/widgets/collections/presentation/slidejs.css +++ b/apps/client/src/widgets/collections/presentation/slidejs.css @@ -1,4 +1,8 @@ figure img { aspect-ratio: unset !important; height: auto !important; +} + +[aria-hidden=true] { + display: none !important; } \ No newline at end of file From 7f074390af2eea1181dbf843879abc51d7de89bd Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 23:05:27 +0300 Subject: [PATCH 28/47] fix(collection/presentation): math breaking transitions --- apps/client/src/widgets/collections/presentation/slidejs.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/collections/presentation/slidejs.css b/apps/client/src/widgets/collections/presentation/slidejs.css index 25363245c..bf798c186 100644 --- a/apps/client/src/widgets/collections/presentation/slidejs.css +++ b/apps/client/src/widgets/collections/presentation/slidejs.css @@ -3,6 +3,6 @@ figure img { height: auto !important; } -[aria-hidden=true] { +span.katex-html { display: none !important; } \ No newline at end of file From 55f9a3712c7a47c45b2a34b6f6173f0e3813e3dd Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 23:05:51 +0300 Subject: [PATCH 29/47] refactor(collection/presentation): deduplicate slide rendering --- .../collections/presentation/index.tsx | 34 ++++++++----------- .../widgets/collections/presentation/model.ts | 4 +-- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index c25d0678f..ad321dba6 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -4,7 +4,7 @@ import Reveal from "reveal.js"; import slideBaseStylesheet from "reveal.js/dist/reveal.css?raw"; import slideThemeStylesheet from "reveal.js/dist/theme/black.css?raw"; import slideCustomStylesheet from "./slidejs.css?raw"; -import { buildPresentationModel, PresentationModel, PresentationSlideModel } from "./model"; +import { buildPresentationModel, PresentationModel, PresentationSlideBaseModel } from "./model"; import ShadowDom from "../../react/ShadowDom"; import ActionButton from "../../react/ActionButton"; import "./index.css"; @@ -116,30 +116,26 @@ function Presentation({ presentation, apiRef: externalApiRef } : { presentation: return (
- {presentation.slides?.map(slide => ( - - ))} + {presentation.slides?.map(slide => { + if (!slide.verticalSlides) { + return + } else { + return ( +
+ + {slide.verticalSlides.map(slide => )} +
+ ); + } + })}
) } -function Slide({ slide }: { slide: PresentationSlideModel }) { - if (!slide.verticalSlides) { - // Normal slide. - return
; - } else { - // Slide with sub notes (show as vertical slides). - return ( -
-
- {slide.verticalSlides.map((slide) => ( -
- ))} -
- ) - } +function Slide({ slide }: { slide: PresentationSlideBaseModel }) { + return
; } function getNoteIdFromSlide(slide: HTMLElement | undefined) { diff --git a/apps/client/src/widgets/collections/presentation/model.ts b/apps/client/src/widgets/collections/presentation/model.ts index 2d53f4ef7..a94ada59f 100644 --- a/apps/client/src/widgets/collections/presentation/model.ts +++ b/apps/client/src/widgets/collections/presentation/model.ts @@ -4,12 +4,12 @@ import contentRenderer from "../../../services/content_renderer"; type DangerouslySetInnerHTML = { __html: string; }; /** A top-level slide with optional vertical slides. */ -export interface PresentationSlideModel extends PresentationSlideBaseModel { +interface PresentationSlideModel extends PresentationSlideBaseModel { verticalSlides: PresentationSlideBaseModel[] | undefined; } /** Either a top-level slide or a vertical slide. */ -interface PresentationSlideBaseModel { +export interface PresentationSlideBaseModel { noteId: string; content: DangerouslySetInnerHTML; } From be076a66096df529cb692d3eb446dd8caee30ef0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 23:08:52 +0300 Subject: [PATCH 30/47] chore(collection/presentation): use translations --- apps/client/src/translations/en/translation.json | 4 ++++ apps/client/src/widgets/collections/presentation/index.tsx | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 092f30221..04e0eff76 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2029,6 +2029,10 @@ "edit-note-title": "Click to edit note title", "edit-column-title": "Click to edit column title" }, + "presentation_view": { + "edit-slide": "Edit this slide", + "start-presentation": "Start presentation" + }, "command_palette": { "tree-action-name": "Tree: {{name}}", "export_note_title": "Export Note", diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index ad321dba6..af05d6d4d 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -11,6 +11,7 @@ import "./index.css"; import { RefObject } from "preact"; import { openInCurrentNoteContext } from "../../../components/note_context"; import { useTriliumEvent } from "../../react/hooks"; +import { t } from "../../../services/i18n"; const stylesheets = [ slideBaseStylesheet, @@ -55,7 +56,7 @@ function ButtonOverlay({ containerRef, apiRef }: { containerRef: RefObject { const currentSlide = apiRef.current?.getCurrentSlide(); const noteId = getNoteIdFromSlide(currentSlide); @@ -68,7 +69,7 @@ function ButtonOverlay({ containerRef, apiRef }: { containerRef: RefObject containerRef.current?.requestFullscreen()} />
From f9c0b94ecb2a4cb585458a9e42090976283dd441 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 23:31:13 +0300 Subject: [PATCH 31/47] fix(collection/presentation): editing no longer works --- apps/client/src/widgets/collections/presentation/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index af05d6d4d..52339dc36 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -96,9 +96,9 @@ function Presentation({ presentation, apiRef: externalApiRef } : { presentation: return true; }, }); - externalApiRef.current = apiRef.current; api.initialize().then(() => { apiRef.current = api; + externalApiRef.current = api; }); return () => { From 319e753e7eb405c35aef795ab70ede63699c3336 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Oct 2025 23:37:08 +0300 Subject: [PATCH 32/47] feat(collection/presentation): add a button to toggle slide overview --- .../src/translations/en/translation.json | 3 ++- .../collections/presentation/index.tsx | 26 +++++++++++++++++++ .../client/src/widgets/react/ActionButton.tsx | 9 ++++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 04e0eff76..8dee04c63 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2031,7 +2031,8 @@ }, "presentation_view": { "edit-slide": "Edit this slide", - "start-presentation": "Start presentation" + "start-presentation": "Start presentation", + "slide-overview": "Toggle an overview of the slides" }, "command_palette": { "tree-action-name": "Tree: {{name}}", diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 52339dc36..a370a793a 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -52,6 +52,23 @@ export default function PresentationView({ note, noteIds }: ViewModeProps<{}>) { } function ButtonOverlay({ containerRef, apiRef }: { containerRef: RefObject, apiRef: RefObject }) { + const [ isOverviewActive, setIsOverviewActive ] = useState(false); + useEffect(() => { + const api = apiRef.current; + if (!api) return; + setIsOverviewActive(api.isOverview()); + const onEnabled = () => setIsOverviewActive(true); + const onDisabled = () => setIsOverviewActive(false); + api.on("overviewshown", onEnabled); + api.on("overviewhidden", onDisabled); + return () => { + api.off("overviewshown", onEnabled); + api.off("overviewhidden", onDisabled); + }; + }, [ apiRef ]); + + console.log("Active ", isOverviewActive); + return (
+ { + apiRef.current?.toggleOverview(); + }} + /> + void; triggerCommand?: CommandNames; noIconActionClass?: boolean; frame?: boolean; + active?: boolean; + disabled?: boolean; } -export default function ActionButton({ text, icon, className, onClick, triggerCommand, titlePosition, noIconActionClass, frame }: ActionButtonProps) { +export default function ActionButton({ text, icon, className, onClick, triggerCommand, titlePosition, noIconActionClass, frame, active, disabled }: ActionButtonProps) { const buttonRef = useRef(null); const [ keyboardShortcut, setKeyboardShortcut ] = useState(); @@ -32,8 +34,9 @@ export default function ActionButton({ text, icon, className, onClick, triggerCo return
) } From 96f5b55d9fc5cf55a99bfca6c9234d70509f81c8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Oct 2025 09:02:02 +0300 Subject: [PATCH 35/47] refactor(collection/presentation): apply review suggestions --- .../collections/presentation/index.tsx | 4 +-- .../widgets/collections/presentation/model.ts | 27 +++++++------------ apps/client/src/widgets/react/ShadowDom.tsx | 4 +-- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 656940c68..7f40e8923 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -30,7 +30,6 @@ export default function PresentationView({ note, noteIds }: ViewModeProps<{}>) { useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (loadResults.getNoteIds().find(noteId => noteIds.includes(noteId))) { - console.log("Needs reload!"); refresh(); } }); @@ -105,7 +104,7 @@ function ButtonOverlay({ containerRef, api }: { containerRef: RefObject void }) { +function Presentation({ presentation, setApi } : { presentation: PresentationModel, setApi: (api: Reveal.Api | undefined) => void }) { const containerRef = useRef(null); const apiRef = useRef(null); const isFirstRenderRef = useRef(true); @@ -133,6 +132,7 @@ function Presentation({ presentation, setApi } : { presentation: PresentationMod return () => { api.destroy(); apiRef.current = null; + setApi(undefined); } }, [ ]); diff --git a/apps/client/src/widgets/collections/presentation/model.ts b/apps/client/src/widgets/collections/presentation/model.ts index a94ada59f..031ff2f36 100644 --- a/apps/client/src/widgets/collections/presentation/model.ts +++ b/apps/client/src/widgets/collections/presentation/model.ts @@ -19,17 +19,12 @@ export interface PresentationModel { } export async function buildPresentationModel(note: FNote): Promise { - const slideNotes = await note.getChildNotes(); - const slides: PresentationSlideModel[] = []; - - for (const slideNote of slideNotes) { - slides.push({ - noteId: slideNote.noteId, - content: await processContent(slideNote), - verticalSlides: await buildVerticalSlides(slideNote) - }) - } + const slides: PresentationSlideModel[] = await Promise.all(slideNotes.map(async slideNote => ({ + noteId: slideNote.noteId, + content: await processContent(slideNote), + verticalSlides: await buildVerticalSlides(slideNote) + }))) return { slides }; } @@ -38,13 +33,11 @@ async function buildVerticalSlides(parentSlideNote: FNote): Promise ({ + noteId: childNote.noteId, + content: await processContent(childNote) + }))); + return slides; } diff --git a/apps/client/src/widgets/react/ShadowDom.tsx b/apps/client/src/widgets/react/ShadowDom.tsx index 3bbd23427..7b98aba75 100644 --- a/apps/client/src/widgets/react/ShadowDom.tsx +++ b/apps/client/src/widgets/react/ShadowDom.tsx @@ -13,10 +13,10 @@ export default function ShadowDom({ children, containerRef: externalContainerRef // Create the shadow root. useEffect(() => { - if (!containerRef.current || shadowRoot) return; + if (!containerRef.current) return; const shadow = containerRef.current.attachShadow({ mode: "open" }); setShadowRoot(shadow); - }, [ shadowRoot ]); + }, []); // Render the child elements. useEffect(() => { From 113061902e0ea35f2be9ea09545b926beda5eddd Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Oct 2025 09:23:56 +0300 Subject: [PATCH 36/47] feat(collection/presentation): add template for presentation & slide --- .../src/assets/translations/en/server.json | 6 +- .../src/services/hidden_subtree_templates.ts | 64 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/apps/server/src/assets/translations/en/server.json b/apps/server/src/assets/translations/en/server.json index 2fcbe5464..14220bcd8 100644 --- a/apps/server/src/assets/translations/en/server.json +++ b/apps/server/src/assets/translations/en/server.json @@ -423,7 +423,11 @@ "board_note_third": "Third note", "board_status_todo": "To Do", "board_status_progress": "In Progress", - "board_status_done": "Done" + "board_status_done": "Done", + "presentation": "Presentation", + "presentation_slide": "Presentation slide", + "presentation_slide_first": "First slide", + "presentation_slide_second": "Second slide" }, "sql_init": { "db_not_initialized_desktop": "DB not initialized, please follow on-screen instructions.", diff --git a/apps/server/src/services/hidden_subtree_templates.ts b/apps/server/src/services/hidden_subtree_templates.ts index 11ed6c66b..5b887978c 100644 --- a/apps/server/src/services/hidden_subtree_templates.ts +++ b/apps/server/src/services/hidden_subtree_templates.ts @@ -234,6 +234,70 @@ export default function buildHiddenSubtreeTemplates() { } ] }, + { + id: "_template_presentation", + type: "book", + title: t("hidden_subtree_templates.presentation"), + icon: "bx bx-slideshow", + attributes: [ + { + name: "template", + type: "label" + }, + { + name: "viewType", + type: "label", + value: "presentation" + }, + { + name: "collection", + type: "label" + }, + { + name: "child:template", + type: "relation", + value: "_template_presentation_slide" + } + ], + children: [ + { + id: "_template_presentation_first", + type: "text", + title: t("hidden_subtree_templates.presentation_slide_first"), + attributes: [ + { + name: "template", + type: "relation", + value: "_template_presentation_slide" + } + ] + }, + { + id: "_template_presentation_second", + type: "text", + title: t("hidden_subtree_templates.presentation_slide_second"), + attributes: [ + { + name: "template", + type: "relation", + value: "_template_presentation_slide" + } + ] + } + ] + }, + { + id: "_template_presentation_slide", + type: "text", + title: t("hidden_subtree_templates.presentation_slide"), + icon: "bx bx-rectangle", + attributes: [ + { + name: "slide", + type: "label" + } + ] + } ] }; From f9a24bf6013dfd7ac06c3fed2b21870ca13cdf16 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Oct 2025 09:29:28 +0300 Subject: [PATCH 37/47] fix(collection/presentation): hide slide from list of templates --- apps/client/src/services/note_types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/services/note_types.ts b/apps/client/src/services/note_types.ts index a42631f9f..74b6f5665 100644 --- a/apps/client/src/services/note_types.ts +++ b/apps/client/src/services/note_types.ts @@ -168,7 +168,8 @@ async function getBuiltInTemplates(title: string | null, command: TreeCommandNam } for (const templateNote of childNotes) { - if (templateNote.hasLabel("collection") !== filterCollections) { + if (templateNote.hasLabel("collection") !== filterCollections || + !templateNote.hasLabel("template")) { continue; } From d801d8a0532451cdd0f4192ff4e0036e3d8062e0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Oct 2025 11:06:47 +0300 Subject: [PATCH 38/47] feat(collection/presentation): add default content to the two slides --- apps/server/src/services/hidden_subtree.ts | 9 ++++++++- apps/server/src/services/hidden_subtree_templates.ts | 2 ++ packages/commons/src/lib/hidden_subtree.ts | 6 ++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/server/src/services/hidden_subtree.ts b/apps/server/src/services/hidden_subtree.ts index 5d8854aaa..547251818 100644 --- a/apps/server/src/services/hidden_subtree.ts +++ b/apps/server/src/services/hidden_subtree.ts @@ -345,17 +345,24 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree let branch; if (!note) { + // Missing item, add it. ({ note, branch } = noteService.createNewNote({ noteId: item.id, title: item.title, type: item.type, parentNoteId: parentNoteId, - content: "", + content: item.content ?? "", ignoreForbiddenParents: true })); } else { + // Existing item, check if it's in the right state. branch = note.getParentBranches().find((branch) => branch.parentNoteId === parentNoteId); + if (item.content && note.getContent() !== item.content) { + console.log(`Updating content of ${item.id}.`); + note.setContent(item.content); + } + // Clean up any branches that shouldn't exist according to the meta definition // For hidden subtree notes, we want to ensure they only exist in their designated locations if (item.enforceBranches || item.id.startsWith("_help")) { diff --git a/apps/server/src/services/hidden_subtree_templates.ts b/apps/server/src/services/hidden_subtree_templates.ts index 5b887978c..d85fb5546 100644 --- a/apps/server/src/services/hidden_subtree_templates.ts +++ b/apps/server/src/services/hidden_subtree_templates.ts @@ -264,6 +264,7 @@ export default function buildHiddenSubtreeTemplates() { id: "_template_presentation_first", type: "text", title: t("hidden_subtree_templates.presentation_slide_first"), + content: t("hidden_subtree_templates.presentation_slide_first"), attributes: [ { name: "template", @@ -276,6 +277,7 @@ export default function buildHiddenSubtreeTemplates() { id: "_template_presentation_second", type: "text", title: t("hidden_subtree_templates.presentation_slide_second"), + content: t("hidden_subtree_templates.presentation_slide_second"), attributes: [ { name: "template", diff --git a/packages/commons/src/lib/hidden_subtree.ts b/packages/commons/src/lib/hidden_subtree.ts index c86dd6b89..0d23f0805 100644 --- a/packages/commons/src/lib/hidden_subtree.ts +++ b/packages/commons/src/lib/hidden_subtree.ts @@ -54,4 +54,10 @@ export interface HiddenSubtreeItem { * definitions will be removed. */ enforceAttributes?: boolean; + /** + * Optionally, a content to be set in the hidden note. If undefined, an empty string will be set instead. + * + * The value is also checked at every startup to ensure that it's kept up to date according to the definition. + */ + content?: string; } From 8a86fdcd43cb95c1bbbd9cf2f0d08076de55f0b0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Oct 2025 11:26:48 +0300 Subject: [PATCH 39/47] feat(collection/presentation): add listing for themes --- .../collections/presentation/themes.ts | 61 +++++++++++++++++++ .../ribbon/collection-properties-config.ts | 13 +++- packages/commons/src/lib/attribute_names.ts | 1 + 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 apps/client/src/widgets/collections/presentation/themes.ts diff --git a/apps/client/src/widgets/collections/presentation/themes.ts b/apps/client/src/widgets/collections/presentation/themes.ts new file mode 100644 index 000000000..dca1b8760 --- /dev/null +++ b/apps/client/src/widgets/collections/presentation/themes.ts @@ -0,0 +1,61 @@ +interface ThemeDefinition { + name: string; + loadTheme: () => Promise; +} + +const themes: Record = { + black: { + name: "Black", + loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + }, + white: { + name: "White", + loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + }, + beige: { + name: "Beige", + loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + }, + serif: { + name: "Serif", + loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + }, + simple: { + name: "Simple", + loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + }, + solarized: { + name: "Solarized", + loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + }, + moon: { + name: "Moon", + loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + }, + dracula: { + name: "Dracula", + loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + }, + sky: { + name: "Sky", + loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + }, + blood: { + name: "Blood", + loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + } +} as const; + +export function getPresentationThemes() { + return Object.entries(themes).map(([ id, theme ]) => ({ + id: id, + name: theme.name + })); +} + +export async function loadPresentationTheme(name: keyof typeof themes) { + const theme = themes[name]; + if (!theme) return; + + return (await theme.loadTheme()).default; +} diff --git a/apps/client/src/widgets/ribbon/collection-properties-config.ts b/apps/client/src/widgets/ribbon/collection-properties-config.ts index bb4dd405b..0416c98fc 100644 --- a/apps/client/src/widgets/ribbon/collection-properties-config.ts +++ b/apps/client/src/widgets/ribbon/collection-properties-config.ts @@ -5,6 +5,7 @@ import NoteContextAwareWidget from "../note_context_aware_widget"; import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS, type MapLayer } from "../collections/geomap/map_layer"; import { ViewTypeOptions } from "../collections/interface"; import { FilterLabelsByType } from "@triliumnext/commons"; +import { getPresentationThemes } from "../collections/presentation/themes"; interface BookConfig { properties: BookProperty[]; @@ -161,7 +162,17 @@ export const bookPropertiesConfig: Record = { properties: [] }, presentation: { - properties: [] + properties: [ + { + label: "Theme", + type: "combobox", + bindToLabel: "presentation:theme", + options: getPresentationThemes().map(theme => ({ + value: theme.id, + label: theme.name + })) + } + ] } }; diff --git a/packages/commons/src/lib/attribute_names.ts b/packages/commons/src/lib/attribute_names.ts index 8b8de89c1..314cc8c9c 100644 --- a/packages/commons/src/lib/attribute_names.ts +++ b/packages/commons/src/lib/attribute_names.ts @@ -39,6 +39,7 @@ type Labels = { "board:groupBy": string; maxNestingDepth: number; includeArchived: boolean; + "presentation:theme": string; } /** From 7c2c89d4e26c3daa7d4abc5dc5d1d371568a9c12 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Oct 2025 11:35:15 +0300 Subject: [PATCH 40/47] feat(collection/presentation): load themes --- .../collections/presentation/index.tsx | 31 +++++++++++++------ .../collections/presentation/themes.spec.ts | 10 ++++++ .../collections/presentation/themes.ts | 24 +++++++------- 3 files changed, 45 insertions(+), 20 deletions(-) create mode 100644 apps/client/src/widgets/collections/presentation/themes.spec.ts diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 7f40e8923..6c8e530ff 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -2,7 +2,6 @@ import { ViewModeProps } from "../interface"; import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks"; import Reveal from "reveal.js"; import slideBaseStylesheet from "reveal.js/dist/reveal.css?raw"; -import slideThemeStylesheet from "reveal.js/dist/theme/black.css?raw"; import slideCustomStylesheet from "./slidejs.css?raw"; import { buildPresentationModel, PresentationModel, PresentationSlideBaseModel } from "./model"; import ShadowDom from "../../react/ShadowDom"; @@ -10,19 +9,16 @@ import ActionButton from "../../react/ActionButton"; import "./index.css"; import { RefObject } from "preact"; import { openInCurrentNoteContext } from "../../../components/note_context"; -import { useTriliumEvent } from "../../react/hooks"; +import { useNoteLabelWithDefault, useTriliumEvent } from "../../react/hooks"; import { t } from "../../../services/i18n"; - -const stylesheets = [ - slideBaseStylesheet, - slideThemeStylesheet, - slideCustomStylesheet -].map(stylesheet => stylesheet.replace(/:root/g, ":host")); +import { DEFAULT_THEME, loadPresentationTheme } from "./themes"; +import FNote from "../../../entities/fnote"; export default function PresentationView({ note, noteIds }: ViewModeProps<{}>) { const [ presentation, setPresentation ] = useState(); const containerRef = useRef(null); const [ api, setApi ] = useState(); + const stylesheets = usePresentationStylesheets(note); function refresh() { buildPresentationModel(note).then(setPresentation); @@ -36,7 +32,7 @@ export default function PresentationView({ note, noteIds }: ViewModeProps<{}>) { useLayoutEffect(refresh, [ note, noteIds ]); - return presentation && ( + return presentation && stylesheets && ( <> ) { ) } +function usePresentationStylesheets(note: FNote) { + const [ themeName ] = useNoteLabelWithDefault(note, "presentation:theme", DEFAULT_THEME); + const [ stylesheets, setStylesheets ] = useState(); + + useLayoutEffect(() => { + loadPresentationTheme(themeName).then((themeStylesheet) => { + setStylesheets([ + slideBaseStylesheet, + themeStylesheet, + slideCustomStylesheet + ].map(stylesheet => stylesheet.replace(/:root/g, ":host"))); + }); + }, [ themeName ]); + + return stylesheets; +} + function ButtonOverlay({ containerRef, api }: { containerRef: RefObject, api: Reveal.Api | undefined }) { const [ isOverviewActive, setIsOverviewActive ] = useState(false); useEffect(() => { diff --git a/apps/client/src/widgets/collections/presentation/themes.spec.ts b/apps/client/src/widgets/collections/presentation/themes.spec.ts new file mode 100644 index 000000000..854cad190 --- /dev/null +++ b/apps/client/src/widgets/collections/presentation/themes.spec.ts @@ -0,0 +1,10 @@ +import { it, describe } from "vitest"; +import { getPresentationThemes, loadPresentationTheme } from "./themes"; + +describe("Presentation themes", () => { + it("can load all themes", async () => { + const themes = getPresentationThemes(); + + await Promise.all(themes.map(theme => loadPresentationTheme(theme.id))); + }); +}); diff --git a/apps/client/src/widgets/collections/presentation/themes.ts b/apps/client/src/widgets/collections/presentation/themes.ts index dca1b8760..a5422cef5 100644 --- a/apps/client/src/widgets/collections/presentation/themes.ts +++ b/apps/client/src/widgets/collections/presentation/themes.ts @@ -1,3 +1,5 @@ +export const DEFAULT_THEME = "white"; + interface ThemeDefinition { name: string; loadTheme: () => Promise; @@ -10,39 +12,39 @@ const themes: Record = { }, white: { name: "White", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/white.css?raw") }, beige: { name: "Beige", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/beige.css?raw") }, serif: { name: "Serif", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/serif.css?raw") }, simple: { name: "Simple", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/simple.css?raw") }, solarized: { name: "Solarized", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/solarized.css?raw") }, moon: { name: "Moon", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/moon.css?raw") }, dracula: { name: "Dracula", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/dracula.css?raw") }, sky: { name: "Sky", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/sky.css?raw") }, blood: { name: "Blood", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/blood.css?raw") } } as const; @@ -54,8 +56,8 @@ export function getPresentationThemes() { } export async function loadPresentationTheme(name: keyof typeof themes) { - const theme = themes[name]; - if (!theme) return; + let theme = themes[name]; + if (!theme) theme = themes[DEFAULT_THEME]; return (await theme.loadTheme()).default; } From 60cee1f7dc664945515039bb29f5186541b3b1dd Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Oct 2025 12:15:31 +0300 Subject: [PATCH 41/47] fix(collection/presentation): default theme not shown correctly in ribbon --- apps/client/src/widgets/ribbon/collection-properties-config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/ribbon/collection-properties-config.ts b/apps/client/src/widgets/ribbon/collection-properties-config.ts index 0416c98fc..fd3d6251e 100644 --- a/apps/client/src/widgets/ribbon/collection-properties-config.ts +++ b/apps/client/src/widgets/ribbon/collection-properties-config.ts @@ -5,7 +5,7 @@ import NoteContextAwareWidget from "../note_context_aware_widget"; import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS, type MapLayer } from "../collections/geomap/map_layer"; import { ViewTypeOptions } from "../collections/interface"; import { FilterLabelsByType } from "@triliumnext/commons"; -import { getPresentationThemes } from "../collections/presentation/themes"; +import { DEFAULT_THEME, getPresentationThemes } from "../collections/presentation/themes"; interface BookConfig { properties: BookProperty[]; @@ -167,6 +167,7 @@ export const bookPropertiesConfig: Record = { label: "Theme", type: "combobox", bindToLabel: "presentation:theme", + defaultValue: DEFAULT_THEME, options: getPresentationThemes().map(theme => ({ value: theme.id, label: theme.name From 88689f2987c3f59418f2a4f7944f1e65f4e5eb18 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Oct 2025 12:22:26 +0300 Subject: [PATCH 42/47] fix(collection/presentation): template not well defined at first initialization --- .../src/services/hidden_subtree_templates.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/server/src/services/hidden_subtree_templates.ts b/apps/server/src/services/hidden_subtree_templates.ts index d85fb5546..3b2de5dbf 100644 --- a/apps/server/src/services/hidden_subtree_templates.ts +++ b/apps/server/src/services/hidden_subtree_templates.ts @@ -234,6 +234,18 @@ export default function buildHiddenSubtreeTemplates() { } ] }, + { + id: "_template_presentation_slide", + type: "text", + title: t("hidden_subtree_templates.presentation_slide"), + icon: "bx bx-rectangle", + attributes: [ + { + name: "slide", + type: "label" + } + ] + }, { id: "_template_presentation", type: "book", @@ -287,18 +299,6 @@ export default function buildHiddenSubtreeTemplates() { ] } ] - }, - { - id: "_template_presentation_slide", - type: "text", - title: t("hidden_subtree_templates.presentation_slide"), - icon: "bx bx-rectangle", - attributes: [ - { - name: "slide", - type: "label" - } - ] } ] }; From 04eeb28c0970552be7b75d106e77d734a4abccfc Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Oct 2025 14:47:23 +0300 Subject: [PATCH 43/47] feat(collection/presentation): support CK editor size --- .../widgets/collections/presentation/slidejs.css | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/collections/presentation/slidejs.css b/apps/client/src/widgets/collections/presentation/slidejs.css index bf798c186..c3e9735be 100644 --- a/apps/client/src/widgets/collections/presentation/slidejs.css +++ b/apps/client/src/widgets/collections/presentation/slidejs.css @@ -5,4 +5,16 @@ figure img { span.katex-html { display: none !important; -} \ No newline at end of file +} + +p:has(span.text-tiny), +p:has(span.text-small), +p:has(span.text-big), +p:has(span.text-huge) { + line-height: unset !important; +} + +span.text-tiny { font-size: 0.5em; } +span.text-small { font-size: 0.75em; } +span.text-big { font-size: 1.5em; } +span.text-huge { font-size: 2em; } \ No newline at end of file From 5d8ca1ecf7ba443b4c101dcf723e72520d45f943 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Oct 2025 15:32:16 +0300 Subject: [PATCH 44/47] chore(collection/presentation): address requested changes --- apps/client/src/components/note_context.ts | 2 +- .../widgets/collections/presentation/index.tsx | 18 +++++++----------- .../widgets/collections/presentation/themes.ts | 7 +------ apps/server/src/services/hidden_subtree.ts | 6 +++--- 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/apps/client/src/components/note_context.ts b/apps/client/src/components/note_context.ts index 4b77e4965..79d1e148b 100644 --- a/apps/client/src/components/note_context.ts +++ b/apps/client/src/components/note_context.ts @@ -439,7 +439,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded"> } export function openInCurrentNoteContext(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent | null, notePath: string, viewScope?: ViewScope) { - const ntxId = $(evt?.target as any) + const ntxId = $(evt?.target as Element) .closest("[data-ntx-id]") .attr("data-ntx-id"); diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 6c8e530ff..1d9896d35 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -119,11 +119,10 @@ function ButtonOverlay({ containerRef, api }: { containerRef: RefObject void }) { const containerRef = useRef(null); - const apiRef = useRef(null); - const isFirstRenderRef = useRef(true); + const [revealApi, setRevealApi] = useState(); useEffect(() => { - if (apiRef.current || !containerRef.current) return; + if (!containerRef.current) return; const api = new Reveal(containerRef.current, { transition: "slide", @@ -138,23 +137,20 @@ function Presentation({ presentation, setApi } : { presentation: PresentationMod }, }); api.initialize().then(() => { - apiRef.current = api; + setRevealApi(revealApi); setApi(api); }); return () => { api.destroy(); - apiRef.current = null; + setRevealApi(undefined); setApi(undefined); } - }, [ ]); + }, []); useEffect(() => { - if (!isFirstRenderRef.current) { - apiRef.current?.sync(); - } - isFirstRenderRef.current = false; - }, [ presentation ]); + revealApi?.sync(); + }, [ presentation, revealApi ]); return (
diff --git a/apps/client/src/widgets/collections/presentation/themes.ts b/apps/client/src/widgets/collections/presentation/themes.ts index a5422cef5..da706a658 100644 --- a/apps/client/src/widgets/collections/presentation/themes.ts +++ b/apps/client/src/widgets/collections/presentation/themes.ts @@ -1,11 +1,6 @@ export const DEFAULT_THEME = "white"; -interface ThemeDefinition { - name: string; - loadTheme: () => Promise; -} - -const themes: Record = { +const themes = { black: { name: "Black", loadTheme: () => import("reveal.js/dist/theme/black.css?raw") diff --git a/apps/server/src/services/hidden_subtree.ts b/apps/server/src/services/hidden_subtree.ts index 547251818..1013d16fc 100644 --- a/apps/server/src/services/hidden_subtree.ts +++ b/apps/server/src/services/hidden_subtree.ts @@ -359,7 +359,7 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree branch = note.getParentBranches().find((branch) => branch.parentNoteId === parentNoteId); if (item.content && note.getContent() !== item.content) { - console.log(`Updating content of ${item.id}.`); + log.info(`Updating content of ${item.id}.`); note.setContent(item.content); } @@ -369,7 +369,7 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree // If the note exists but doesn't have a branch in the expected parent, // create the missing branch to ensure it's in the correct location if (!branch) { - console.log("Creating missing branch for note", item.id, "under parent", parentNoteId); + log.info(`Creating missing branch for note ${item.id} under parent ${parentNoteId}.`); branch = new BBranch({ noteId: item.id, parentNoteId: parentNoteId, @@ -473,7 +473,7 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree }).save(); } else if (attr.name === "docName" || (existingAttribute.noteId.startsWith("_help") && attr.name === "iconClass")) { if (existingAttribute.value !== attr.value) { - console.log(`Updating attribute ${attrId} from "${existingAttribute.value}" to "${attr.value}"`); + log.info(`Updating attribute ${attrId} from "${existingAttribute.value}" to "${attr.value}"`); existingAttribute.value = attr.value ?? ""; existingAttribute.save(); } From 0a72133ca3acf4c5e3890de98208af5d0ed9d05b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Oct 2025 15:46:14 +0300 Subject: [PATCH 45/47] chore(collection/presentation): fix typecheck issue --- apps/client/src/services/in_app_help.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/services/in_app_help.ts b/apps/client/src/services/in_app_help.ts index 9e00f7e37..a0b118e5c 100644 --- a/apps/client/src/services/in_app_help.ts +++ b/apps/client/src/services/in_app_help.ts @@ -27,7 +27,8 @@ export const byBookType: Record = { calendar: "xWbu3jpNWapp", table: "2FvYrpmOXm29", geoMap: "81SGnPGMk7Xc", - board: "CtBQqbwXDx1w" + board: "CtBQqbwXDx1w", + presentation: null }; export function getHelpUrlForNote(note: FNote | null | undefined) { From d27d9bf7dcf9fa99d0648350a0f80d4c28f6330c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Oct 2025 18:25:10 +0300 Subject: [PATCH 46/47] chore(collection/presentation): fix typecheck --- apps/client/src/widgets/collections/presentation/themes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/collections/presentation/themes.ts b/apps/client/src/widgets/collections/presentation/themes.ts index da706a658..414472d56 100644 --- a/apps/client/src/widgets/collections/presentation/themes.ts +++ b/apps/client/src/widgets/collections/presentation/themes.ts @@ -50,7 +50,7 @@ export function getPresentationThemes() { })); } -export async function loadPresentationTheme(name: keyof typeof themes) { +export async function loadPresentationTheme(name: keyof typeof themes | string) { let theme = themes[name]; if (!theme) theme = themes[DEFAULT_THEME]; From 2d7f4290b7af323cefa947eed269fd1aebfa6f00 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Oct 2025 18:32:28 +0300 Subject: [PATCH 47/47] test(collection/presentation): assertion failure after change in hidden subtree --- apps/server/src/services/migration.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/services/migration.spec.ts b/apps/server/src/services/migration.spec.ts index 690956904..f47803ac4 100644 --- a/apps/server/src/services/migration.spec.ts +++ b/apps/server/src/services/migration.spec.ts @@ -12,7 +12,7 @@ describe("Migration", () => { const migration = (await import("./migration.js")).default; await migration.migrateIfNecessary(); - expect(sql.getValue("SELECT count(*) FROM blobs")).toBe(116); + expect(sql.getValue("SELECT count(*) FROM blobs")).toBe(118); resolve(); }); });