mirror of
https://github.com/zadam/trilium.git
synced 2025-10-26 15:56:29 +01:00
feat(client/print): print presentations with waiting for slides to load
This commit is contained in:
@@ -2,6 +2,7 @@ import FNote from "./entities/fnote";
|
|||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
import { CustomNoteList } from "./widgets/collections/NoteList";
|
import { CustomNoteList } from "./widgets/collections/NoteList";
|
||||||
import "./print.css";
|
import "./print.css";
|
||||||
|
import { useCallback, useRef } from "preact/hooks";
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const notePath = window.location.hash.substring(1);
|
const notePath = window.location.hash.substring(1);
|
||||||
@@ -12,10 +13,25 @@ async function main() {
|
|||||||
const note = await froca.getNote(noteId);
|
const note = await froca.getNote(noteId);
|
||||||
|
|
||||||
if (!note) return;
|
if (!note) return;
|
||||||
render(getElementForNote(note), document.body);
|
render(<App note={note} />, document.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getElementForNote(note: FNote) {
|
function App({ note }: { note: FNote }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ContentRenderer note={note} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ContentRenderer({ note }: { note: FNote }) {
|
||||||
|
const sentReadyEvent = useRef(false);
|
||||||
|
const onReady = useCallback(() => {
|
||||||
|
if (sentReadyEvent.current) return;
|
||||||
|
window.dispatchEvent(new Event("note-ready"));
|
||||||
|
sentReadyEvent.current = true;
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Collections.
|
// Collections.
|
||||||
if (note.type === "book") {
|
if (note.type === "book") {
|
||||||
return <CustomNoteList
|
return <CustomNoteList
|
||||||
@@ -25,6 +41,7 @@ function getElementForNote(note: FNote) {
|
|||||||
ntxId="print"
|
ntxId="print"
|
||||||
highlightedTokens={null}
|
highlightedTokens={null}
|
||||||
media="print"
|
media="print"
|
||||||
|
onReady={onReady}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2422,4 +2422,14 @@ footer.webview-footer button {
|
|||||||
.revision-diff-removed {
|
.revision-diff-removed {
|
||||||
background: rgba(255, 100, 100, 0.5);
|
background: rgba(255, 100, 100, 0.5);
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe.print-iframe {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -600px;
|
||||||
|
right: -600px;
|
||||||
|
bottom: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
}
|
}
|
||||||
@@ -23,9 +23,10 @@ interface NoteListProps {
|
|||||||
isEnabled: boolean;
|
isEnabled: boolean;
|
||||||
ntxId: string | null | undefined;
|
ntxId: string | null | undefined;
|
||||||
media: ViewModeMedia;
|
media: ViewModeMedia;
|
||||||
|
onReady: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NoteList<T extends object>(props: Pick<NoteListProps, "displayOnlyCollections" | "media">) {
|
export default function NoteList<T extends object>(props: Pick<NoteListProps, "displayOnlyCollections" | "media" | "onReady">) {
|
||||||
const { note, noteContext, notePath, ntxId } = useNoteContext();
|
const { note, noteContext, notePath, ntxId } = useNoteContext();
|
||||||
const isEnabled = noteContext?.hasNoteList();
|
const isEnabled = noteContext?.hasNoteList();
|
||||||
return <CustomNoteList note={note} isEnabled={!!isEnabled} notePath={notePath} ntxId={ntxId} {...props} />
|
return <CustomNoteList note={note} isEnabled={!!isEnabled} notePath={notePath} ntxId={ntxId} {...props} />
|
||||||
|
|||||||
@@ -16,4 +16,5 @@ export interface ViewModeProps<T extends object> {
|
|||||||
viewConfig: T | undefined;
|
viewConfig: T | undefined;
|
||||||
saveConfig(newConfig: T): void;
|
saveConfig(newConfig: T): void;
|
||||||
media: ViewModeMedia;
|
media: ViewModeMedia;
|
||||||
|
onReady(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { t } from "../../../services/i18n";
|
|||||||
import { DEFAULT_THEME, loadPresentationTheme } from "./themes";
|
import { DEFAULT_THEME, loadPresentationTheme } from "./themes";
|
||||||
import FNote from "../../../entities/fnote";
|
import FNote from "../../../entities/fnote";
|
||||||
|
|
||||||
export default function PresentationView({ note, noteIds, media }: ViewModeProps<{}>) {
|
export default function PresentationView({ note, noteIds, media, onReady }: ViewModeProps<{}>) {
|
||||||
const [ presentation, setPresentation ] = useState<PresentationModel>();
|
const [ presentation, setPresentation ] = useState<PresentationModel>();
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [ api, setApi ] = useState<Reveal.Api>();
|
const [ api, setApi ] = useState<Reveal.Api>();
|
||||||
@@ -33,6 +33,14 @@ export default function PresentationView({ note, noteIds, media }: ViewModeProps
|
|||||||
|
|
||||||
useLayoutEffect(refresh, [ note, noteIds ]);
|
useLayoutEffect(refresh, [ note, noteIds ]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// We need to wait for Reveal.js to initialize (by setting api) and for the presentation to become available.
|
||||||
|
if (api && presentation) {
|
||||||
|
// Timeout is necessary because it otherwise can cause flakiness by rendering only the first slide.
|
||||||
|
setTimeout(onReady, 200);
|
||||||
|
}
|
||||||
|
}, [ api, presentation ]);
|
||||||
|
|
||||||
if (!presentation || !stylesheets) return;
|
if (!presentation || !stylesheets) return;
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -297,8 +297,19 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger in timeout to dismiss the menu while printing.
|
const iframe = document.createElement('iframe');
|
||||||
setTimeout(window.print, 0);
|
iframe.src = `?print#${this.notePath}`;
|
||||||
|
iframe.className = "print-iframe";
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
iframe.onload = () => {
|
||||||
|
console.log("Got ", iframe, iframe.contentWindow);
|
||||||
|
if (iframe.contentWindow) {
|
||||||
|
iframe.contentWindow.addEventListener("note-ready", () => {
|
||||||
|
iframe.contentWindow?.print();
|
||||||
|
document.body.removeChild(iframe);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async exportAsPdfEvent() {
|
async exportAsPdfEvent() {
|
||||||
|
|||||||
@@ -47,11 +47,11 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
|||||||
const canBeConvertedToAttachment = note?.isEligibleForConversionToAttachment();
|
const canBeConvertedToAttachment = note?.isEligibleForConversionToAttachment();
|
||||||
const isSearchable = ["text", "code", "book", "mindMap", "doc"].includes(note.type);
|
const isSearchable = ["text", "code", "book", "mindMap", "doc"].includes(note.type);
|
||||||
const isInOptions = note.noteId.startsWith("_options");
|
const isInOptions = note.noteId.startsWith("_options");
|
||||||
const isPrintable = ["text", "code"].includes(note.type);
|
const isPrintable = ["text", "code", "book"].includes(note.type);
|
||||||
const isElectron = getIsElectron();
|
const isElectron = getIsElectron();
|
||||||
const isMac = getIsMac();
|
const isMac = getIsMac();
|
||||||
const hasSource = ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type);
|
const hasSource = ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type);
|
||||||
const isSearchOrBook = ["search", "book"].includes(note.type);
|
const isSearchOrBook = ["search", "book"].includes(note.type);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -74,7 +74,7 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
|||||||
<CommandItem icon="bx bx-export" text={t("note_actions.export_note")}
|
<CommandItem icon="bx bx-export" text={t("note_actions.export_note")}
|
||||||
disabled={isInOptions || note.noteId === "_backendLog"}
|
disabled={isInOptions || note.noteId === "_backendLog"}
|
||||||
command={() => noteContext?.notePath && parentComponent?.triggerCommand("showExportDialog", {
|
command={() => noteContext?.notePath && parentComponent?.triggerCommand("showExportDialog", {
|
||||||
notePath: noteContext.notePath,
|
notePath: noteContext.notePath,
|
||||||
defaultType: "single"
|
defaultType: "single"
|
||||||
})} />
|
})} />
|
||||||
<FormDropdownDivider />
|
<FormDropdownDivider />
|
||||||
@@ -133,4 +133,4 @@ function ConvertToAttachment({ note }: { note: FNote }) {
|
|||||||
}}
|
}}
|
||||||
>{t("note_actions.convert_into_attachment")}</FormListItem>
|
>{t("note_actions.convert_into_attachment")}</FormListItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user