From fef30f4bea847c59635635fba3c91799478454b1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 30 Dec 2025 01:20:29 +0200 Subject: [PATCH 1/8] chore(client): fix typecheck --- apps/client/src/types-pdfjs.d.ts | 3 +++ apps/client/src/widgets/collections/NoteList.tsx | 5 +++-- apps/client/src/widgets/collections/view_mode_storage.ts | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 apps/client/src/types-pdfjs.d.ts diff --git a/apps/client/src/types-pdfjs.d.ts b/apps/client/src/types-pdfjs.d.ts new file mode 100644 index 000000000..54a440f16 --- /dev/null +++ b/apps/client/src/types-pdfjs.d.ts @@ -0,0 +1,3 @@ +interface Window { + TRILIUM_VIEW_HISTORY_STORE?: object; +} diff --git a/apps/client/src/widgets/collections/NoteList.tsx b/apps/client/src/widgets/collections/NoteList.tsx index 5626cc33c..ec80e6b19 100644 --- a/apps/client/src/widgets/collections/NoteList.tsx +++ b/apps/client/src/widgets/collections/NoteList.tsx @@ -11,7 +11,8 @@ import froca from "../../services/froca"; import { subscribeToMessages, unsubscribeToMessage as unsubscribeFromMessage } from "../../services/ws"; import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent } from "../react/hooks"; import { allViewTypes, ViewModeMedia, ViewModeProps, ViewTypeOptions } from "./interface"; -import ViewModeStorage from "./view_mode_storage"; +import ViewModeStorage, { type ViewModeStorageType } from "./view_mode_storage"; + interface NoteListProps { note: FNote | null | undefined; notePath: string | null | undefined; @@ -215,7 +216,7 @@ export function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOpt return noteIds; } -export function useViewModeConfig(note: FNote | null | undefined, viewType: ViewTypeOptions | undefined) { +export function useViewModeConfig(note: FNote | null | undefined, viewType: ViewModeStorageType | undefined) { const [ viewConfig, setViewConfig ] = useState<{ config: T | undefined; storeFn: (data: T) => void; diff --git a/apps/client/src/widgets/collections/view_mode_storage.ts b/apps/client/src/widgets/collections/view_mode_storage.ts index 95c3ff800..b8c3b94f2 100644 --- a/apps/client/src/widgets/collections/view_mode_storage.ts +++ b/apps/client/src/widgets/collections/view_mode_storage.ts @@ -4,14 +4,16 @@ import { ViewTypeOptions } from "../collections/interface"; const ATTACHMENT_ROLE = "viewConfig"; +export type ViewModeStorageType = ViewTypeOptions | "pdfHistory"; + export default class ViewModeStorage { private note: FNote; private attachmentName: string; - constructor(note: FNote, viewType: ViewTypeOptions) { + constructor(note: FNote, viewType: ViewModeStorageType) { this.note = note; - this.attachmentName = viewType + ".json"; + this.attachmentName = `${viewType}.json`; } async store(data: T) { From a1ebdc3004d2db119644c817c32d0dadf541f607 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 30 Dec 2025 01:23:19 +0200 Subject: [PATCH 2/8] chore(pdfjs): integrate into typecheck --- packages/pdfjs-viewer/tsconfig.app.json | 5 +++-- tsconfig.json | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/pdfjs-viewer/tsconfig.app.json b/packages/pdfjs-viewer/tsconfig.app.json index f6d82c11b..efdd8ceea 100644 --- a/packages/pdfjs-viewer/tsconfig.app.json +++ b/packages/pdfjs-viewer/tsconfig.app.json @@ -14,7 +14,8 @@ "tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo" }, "include": [ - "src/**/*.ts" + "src/**/*.ts", + "../../apps/client/src/types-pdfjs.d.ts" ], "exclude": [ "eslint.config.js", @@ -23,7 +24,7 @@ ], "references": [ { - "path": "../commons/tsconfig.app.json" + "path": "../commons/tsconfig.lib.json" } ] } diff --git a/tsconfig.json b/tsconfig.json index d937096fb..fb9d2774a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -63,6 +63,9 @@ { "path": "./packages/share-theme" }, + { + "path": "./packages/pdfjs-viewer" + }, { "path": "./scripts" } From 23044079864d4ffe98363b68507aa2f7c3bc55ca Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 30 Dec 2025 01:27:05 +0200 Subject: [PATCH 3/8] chore(pdfjs): address origin concerns --- packages/pdfjs-viewer/src/custom.ts | 2 +- packages/pdfjs-viewer/src/persistence.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pdfjs-viewer/src/custom.ts b/packages/pdfjs-viewer/src/custom.ts index 84ae4caeb..99f66c26b 100644 --- a/packages/pdfjs-viewer/src/custom.ts +++ b/packages/pdfjs-viewer/src/custom.ts @@ -47,7 +47,7 @@ function manageSave() { window.parent.postMessage({ type: "pdfjs-viewer-document-modified", data: data - }, "*"); + }, window.location.origin); storage.resetModified(); timeout = null; }, 2_000); diff --git a/packages/pdfjs-viewer/src/persistence.ts b/packages/pdfjs-viewer/src/persistence.ts index 965b5a54b..a70671262 100644 --- a/packages/pdfjs-viewer/src/persistence.ts +++ b/packages/pdfjs-viewer/src/persistence.ts @@ -40,7 +40,7 @@ function saveHistory(value: string) { window.parent.postMessage({ type: "pdfjs-viewer-save-view-history", data: JSON.stringify(history) - }, "*"); + }, window.location.origin); saveTimeout = null; }, 2_000); } From 51b0eb74a5b0255261c4f59b4583178dd87bb643 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 30 Dec 2025 01:37:44 +0200 Subject: [PATCH 4/8] chore(pdfjs): address requested changes --- apps/client/src/widgets/type_widgets/file/Pdf.tsx | 4 ++-- packages/pdfjs-viewer/src/custom.ts | 14 -------------- packages/pdfjs-viewer/src/persistence.ts | 2 ++ 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/file/Pdf.tsx b/apps/client/src/widgets/type_widgets/file/Pdf.tsx index fcce704cf..f254e13c4 100644 --- a/apps/client/src/widgets/type_widgets/file/Pdf.tsx +++ b/apps/client/src/widgets/type_widgets/file/Pdf.tsx @@ -99,7 +99,7 @@ function useStyleInjection(iframeRef: RefObject) { function getRootCssVariables() { const styles = getComputedStyle(document.documentElement); - const vars = {}; + const vars: Record = {}; for (let i = 0; i < styles.length; i++) { const prop = styles[i]; @@ -111,7 +111,7 @@ function getRootCssVariables() { return vars; } -function cssVarsToString(vars) { +function cssVarsToString(vars: Record) { return `:root {\n${Object.entries(vars) .map(([k, v]) => ` ${k}: ${v};`) .join('\n')}\n}`; diff --git a/packages/pdfjs-viewer/src/custom.ts b/packages/pdfjs-viewer/src/custom.ts index 99f66c26b..39f588119 100644 --- a/packages/pdfjs-viewer/src/custom.ts +++ b/packages/pdfjs-viewer/src/custom.ts @@ -1,9 +1,6 @@ import interceptPersistence from "./persistence"; -const LOG_EVENT_BUS = false; - async function main() { - console.log("Hi"); interceptPersistence(getCustomAppOptions()); // Wait for the PDF viewer application to be available. @@ -66,15 +63,4 @@ function manageSave() { }); } -function patchEventBus() { - const eventBus = window.PDFViewerApplication.eventBus; - const originalDispatch = eventBus.dispatch.bind(eventBus); - - eventBus.dispatch = (type: string, data?: any) => { - console.log("PDF.js event:", type, data); - return originalDispatch(type, data); - }; -} - main(); -console.log("Custom script loaded"); diff --git a/packages/pdfjs-viewer/src/persistence.ts b/packages/pdfjs-viewer/src/persistence.ts index a70671262..7f0110799 100644 --- a/packages/pdfjs-viewer/src/persistence.ts +++ b/packages/pdfjs-viewer/src/persistence.ts @@ -1,4 +1,6 @@ export default function interceptViewHistory(customOptions?: object) { + // We need to monkey-patch the localStorage used by PDF.js to store view history. + // Other attempts to intercept the history saving/loading (like overriding methods on PDFViewerApplication) have failed. const originalSetItem = Storage.prototype.setItem; Storage.prototype.setItem = function (key: string, value: string) { if (key === "pdfjs.history") { From 76cfced60f1762f78064887496c483dfd64f5a21 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 30 Dec 2025 01:43:07 +0200 Subject: [PATCH 5/8] chore(pdfjs): fix partially removed method --- packages/pdfjs-viewer/src/custom.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/pdfjs-viewer/src/custom.ts b/packages/pdfjs-viewer/src/custom.ts index 39f588119..da1fb6850 100644 --- a/packages/pdfjs-viewer/src/custom.ts +++ b/packages/pdfjs-viewer/src/custom.ts @@ -9,9 +9,6 @@ async function main() { } const app = window.PDFViewerApplication; - if (LOG_EVENT_BUS) { - patchEventBus(); - } app.eventBus.on("documentloaded", () => { manageSave(); }); From 52292cb5a5fccfa4ddeb94f02ac1913b59acf41b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 30 Dec 2025 09:31:03 +0200 Subject: [PATCH 6/8] style(next): indicate active note context --- .../src/stylesheets/theme-next/shell.css | 5 +++++ .../containers/split_note_container.ts | 22 ++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/apps/client/src/stylesheets/theme-next/shell.css b/apps/client/src/stylesheets/theme-next/shell.css index 5920ef0e9..cc3de0cd3 100644 --- a/apps/client/src/stylesheets/theme-next/shell.css +++ b/apps/client/src/stylesheets/theme-next/shell.css @@ -1259,6 +1259,11 @@ body.layout-horizontal #rest-pane > .classic-toolbar-widget { #center-pane .note-split { padding-top: 2px; background-color: var(--note-split-background-color, var(--main-background-color)); + transition: box-shadow 250ms ease-in; + + &.active { + box-shadow: 0 0 0 2px var(--link-selection-outline-color) inset; + } } body:not(.background-effects) #center-pane .note-split { diff --git a/apps/client/src/widgets/containers/split_note_container.ts b/apps/client/src/widgets/containers/split_note_container.ts index 1cee46b73..c006775a4 100644 --- a/apps/client/src/widgets/containers/split_note_container.ts +++ b/apps/client/src/widgets/containers/split_note_container.ts @@ -1,10 +1,11 @@ -import FlexContainer from "./flex_container.js"; import appContext, { type CommandData, type CommandListenerData, type EventData, type EventNames, type NoteSwitchedContext } from "../../components/app_context.js"; -import type BasicWidget from "../basic_widget.js"; import Component from "../../components/component.js"; +import NoteContext from "../../components/note_context.js"; import splitService from "../../services/resizer.js"; import { isMobile } from "../../services/utils.js"; -import NoteContext from "../../components/note_context.js"; +import type BasicWidget from "../basic_widget.js"; +import NoteContextAwareWidget from "../note_context_aware_widget.js"; +import FlexContainer from "./flex_container.js"; interface SplitNoteWidget extends BasicWidget { hasBeenAlreadyShown?: boolean; @@ -74,7 +75,7 @@ export default class SplitNoteContainer extends FlexContainer { const subContexts = activeContext.getSubContexts(); - let noteContext: NoteContext | undefined = undefined; + let noteContext: NoteContext | undefined; if (isMobile() && subContexts.length > 1) { noteContext = subContexts.find(s => s.ntxId !== ntxId); } @@ -201,6 +202,11 @@ export default class SplitNoteContainer extends FlexContainer { async refresh() { this.toggleExt(true); + + // Mark the active note context. + for (const child of this.children as NoteContextAwareWidget[]) { + child.$widget.toggleClass("active", !!child.noteContext?.isActive()); + } } toggleInt(show: boolean) {} // not needed @@ -239,16 +245,16 @@ export default class SplitNoteContainer extends FlexContainer { widget.hasBeenAlreadyShown = true; return [widget.handleEvent("noteSwitched", noteSwitchedContext), this.refreshNotShown(noteSwitchedContext)]; - } else { - return Promise.resolve(); } + return Promise.resolve(); + } if (name === "activeContextChanged") { return this.refreshNotShown(data as EventData<"activeContextChanged">); - } else { - return super.handleEventInChildren(name, data); } + return super.handleEventInChildren(name, data); + } refreshNotShown(data: NoteSwitchedContext | EventData<"activeContextChanged">) { From 01f05ac6fd1f23d79a6aced588198e706d31e8c7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 30 Dec 2025 09:47:02 +0200 Subject: [PATCH 7/8] fix(pdf): active context not changed when clicking preview --- apps/client/src/widgets/type_widgets/File.tsx | 4 ++-- .../src/widgets/type_widgets/file/Pdf.tsx | 24 ++++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/File.tsx b/apps/client/src/widgets/type_widgets/File.tsx index b720d6a0d..a26bb0987 100644 --- a/apps/client/src/widgets/type_widgets/File.tsx +++ b/apps/client/src/widgets/type_widgets/File.tsx @@ -10,13 +10,13 @@ import { TypeWidgetProps } from "./type_widget"; const TEXT_MAX_NUM_CHARS = 5000; -export default function FileTypeWidget({ note, parentComponent }: TypeWidgetProps) { +export default function FileTypeWidget({ note, parentComponent, noteContext }: TypeWidgetProps) { const blob = useNoteBlob(note, parentComponent?.componentId); if (blob?.content) { return ; } else if (note.mime === "application/pdf") { - return ; + return ; } else if (note.mime.startsWith("video/")) { return ; } else if (note.mime.startsWith("audio/")) { diff --git a/apps/client/src/widgets/type_widgets/file/Pdf.tsx b/apps/client/src/widgets/type_widgets/file/Pdf.tsx index f254e13c4..1c4e24573 100644 --- a/apps/client/src/widgets/type_widgets/file/Pdf.tsx +++ b/apps/client/src/widgets/type_widgets/file/Pdf.tsx @@ -1,6 +1,7 @@ import { RefObject } from "preact"; import { useCallback, useEffect, useRef } from "preact/hooks"; +import appContext from "../../../components/app_context"; import FBlob from "../../../entities/fblob"; import FNote from "../../../entities/fnote"; import server from "../../../services/server"; @@ -14,10 +15,11 @@ const VARIABLE_WHITELIST = new Set([ "main-text-color" ]); -export default function PdfPreview({ note, blob, componentId }: { +export default function PdfPreview({ note, blob, componentId, ntxId }: { note: FNote, blob: FBlob | null | undefined, componentId: string | undefined; + ntxId: string | null | undefined; }) { const iframeRef = useRef(null); const { onLoad } = useStyleInjection(iframeRef); @@ -49,8 +51,28 @@ export default function PdfPreview({ note, blob, componentId }: { } }, [ blob ]); + // Trigger focus when iframe content is clicked (iframe focus doesn't bubble) + useEffect(() => { + const iframe = iframeRef.current; + if (!iframe) return; + + const handleIframeClick = () => { + if (ntxId) { + appContext.tabManager.activateNoteContext(ntxId); + } + }; + + // Listen for clicks on the iframe's content window + const iframeDoc = iframe.contentWindow?.document; + if (iframeDoc) { + iframeDoc.addEventListener('click', handleIframeClick); + return () => iframeDoc.removeEventListener('click', handleIframeClick); + } + }, [ iframeRef.current?.contentWindow, ntxId ]); + return (historyConfig &&