Merge remote-tracking branch 'origin/main' into feature/pdfjs_sidebar_experiments
Some checks are pending
Checks / main (push) Waiting to run

This commit is contained in:
Elian Doran 2025-12-30 11:45:46 +02:00
commit 7182d32d9c
No known key found for this signature in database
11 changed files with 66 additions and 38 deletions

View File

@ -1259,6 +1259,12 @@ 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: border-color 250ms ease-in;
border: 1px solid transparent;
&.active {
border-color: var(--link-selection-outline-color);
}
}
body:not(.background-effects) #center-pane .note-split {

3
apps/client/src/types-pdfjs.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
interface Window {
TRILIUM_VIEW_HISTORY_STORE?: object;
}

View File

@ -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<T extends object>(note: FNote | null | undefined, viewType: ViewTypeOptions | undefined) {
export function useViewModeConfig<T extends object>(note: FNote | null | undefined, viewType: ViewModeStorageType | undefined) {
const [ viewConfig, setViewConfig ] = useState<{
config: T | undefined;
storeFn: (data: T) => void;

View File

@ -4,14 +4,16 @@ import { ViewTypeOptions } from "../collections/interface";
const ATTACHMENT_ROLE = "viewConfig";
export type ViewModeStorageType = ViewTypeOptions | "pdfHistory";
export default class ViewModeStorage<T extends object> {
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) {

View File

@ -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<SplitNoteWidget> {
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<SplitNoteWidget> {
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<SplitNoteWidget> {
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">) {

View File

@ -16,7 +16,7 @@ export default function FileTypeWidget({ note, parentComponent, noteContext }: T
if (blob?.content) {
return <TextPreview content={blob.content} />;
} else if (note.mime === "application/pdf") {
return <PdfPreview blob={blob} note={note} componentId={parentComponent?.componentId} noteContext={noteContext} />;
return noteContext && <PdfPreview blob={blob} note={note} componentId={parentComponent?.componentId} noteContext={noteContext} />;
} else if (note.mime.startsWith("video/")) {
return <VideoPreview note={note} />;
} else if (note.mime.startsWith("audio/")) {

View File

@ -1,6 +1,7 @@
import { RefObject } from "preact";
import { useCallback, useEffect, useRef } from "preact/hooks";
import appContext from "../../../components/app_context";
import type NoteContext from "../../../components/note_context";
import FBlob from "../../../entities/fblob";
import FNote from "../../../entities/fnote";
@ -16,9 +17,9 @@ const VARIABLE_WHITELIST = new Set([
]);
export default function PdfPreview({ note, blob, componentId, noteContext }: {
note: FNote,
noteContext: NoteContext
blob: FBlob | null | undefined,
note: FNote;
noteContext: NoteContext;
blob: FBlob | null | undefined;
componentId: string | undefined;
}) {
const iframeRef = useRef<HTMLIFrameElement>(null);
@ -150,9 +151,28 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: {
}
}, [ blob ]);
// Trigger focus when iframe content is clicked (iframe focus doesn't bubble)
useEffect(() => {
const iframe = iframeRef.current;
if (!iframe) return;
const handleIframeClick = () => {
if (noteContext.ntxId) {
appContext.tabManager.activateNoteContext(noteContext.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, noteContext ]);
return (historyConfig &&
<iframe
tabIndex={300}
ref={iframeRef}
class="pdf-preview"
src={`pdfjs/web/viewer.html?file=../../api/notes/${note.noteId}/open&lang=${locale}&sidebar=${newLayout ? "0" : "1"}`}
@ -201,7 +221,7 @@ function useStyleInjection(iframeRef: RefObject<HTMLIFrameElement>) {
function getRootCssVariables() {
const styles = getComputedStyle(document.documentElement);
const vars = {};
const vars: Record<string, string> = {};
for (let i = 0; i < styles.length; i++) {
const prop = styles[i];
@ -213,7 +233,7 @@ function getRootCssVariables() {
return vars;
}
function cssVarsToString(vars) {
function cssVarsToString(vars: Record<string, string>) {
return `:root {\n${Object.entries(vars)
.map(([k, v]) => ` ${k}: ${v};`)
.join('\n')}\n}`;

View File

@ -4,8 +4,6 @@ import { setupPdfPages } from "./pages";
import { setupPdfAttachments } from "./attachments";
import { setupPdfLayers } from "./layers";
const LOG_EVENT_BUS = false;
async function main() {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get("sidebar") === "0") {
@ -20,9 +18,6 @@ async function main() {
}
const app = window.PDFViewerApplication;
if (LOG_EVENT_BUS) {
patchEventBus();
}
app.eventBus.on("documentloaded", () => {
manageSave();
extractAndSendToc();
@ -74,7 +69,7 @@ function manageSave() {
window.parent.postMessage({
type: "pdfjs-viewer-document-modified",
data: data
}, "*");
}, window.location.origin);
storage.resetModified();
timeout = null;
}, 2_000);
@ -93,15 +88,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");

View File

@ -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") {
@ -40,7 +42,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);
}

View File

@ -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"
}
]
}

View File

@ -63,6 +63,9 @@
{
"path": "./packages/share-theme"
},
{
"path": "./packages/pdfjs-viewer"
},
{
"path": "./scripts"
}