mirror of
https://github.com/zadam/trilium.git
synced 2026-01-04 21:54:23 +01:00
feat(print): report ignored notes
This commit is contained in:
parent
84c40eb233
commit
60866c959f
@ -9,10 +9,17 @@ import { CustomNoteList, useNoteViewType } from "./widgets/collections/NoteList"
|
||||
|
||||
interface RendererProps {
|
||||
note: FNote;
|
||||
onReady: () => void;
|
||||
onReady: (data: PrintReport) => void;
|
||||
onProgressChanged?: (progress: number) => void;
|
||||
}
|
||||
|
||||
export type PrintReport = {
|
||||
type: "single-note";
|
||||
} | {
|
||||
type: "collection";
|
||||
ignoredNoteIds: string[];
|
||||
};
|
||||
|
||||
async function main() {
|
||||
const notePath = window.location.hash.substring(1);
|
||||
const noteId = notePath.split("/").at(-1);
|
||||
@ -35,9 +42,11 @@ function App({ note, noteId }: { note: FNote | null | undefined, noteId: string
|
||||
window.dispatchEvent(new CustomEvent("note-load-progress", { detail: { progress } }));
|
||||
}
|
||||
}, []);
|
||||
const onReady = useCallback(() => {
|
||||
const onReady = useCallback((detail: PrintReport) => {
|
||||
if (sentReadyEvent.current) return;
|
||||
window.dispatchEvent(new Event("note-ready"));
|
||||
window.dispatchEvent(new CustomEvent("note-ready", {
|
||||
detail
|
||||
}));
|
||||
window._noteReady = true;
|
||||
sentReadyEvent.current = true;
|
||||
}, []);
|
||||
@ -92,7 +101,7 @@ function SingleNoteRenderer({ note, onReady }: RendererProps) {
|
||||
await loadCustomCss(note);
|
||||
}
|
||||
|
||||
load().then(() => requestAnimationFrame(onReady));
|
||||
load().then(() => requestAnimationFrame(() => onReady({})));
|
||||
}, [ note ]);
|
||||
|
||||
return <>
|
||||
@ -111,9 +120,9 @@ function CollectionRenderer({ note, onReady, onProgressChanged }: RendererProps)
|
||||
ntxId="print"
|
||||
highlightedTokens={null}
|
||||
media="print"
|
||||
onReady={async () => {
|
||||
onReady={async (data: PrintReport) => {
|
||||
await loadCustomCss(note);
|
||||
onReady();
|
||||
onReady(data);
|
||||
}}
|
||||
onProgressChanged={onProgressChanged}
|
||||
/>;
|
||||
|
||||
@ -1770,7 +1770,10 @@
|
||||
"note_detail": {
|
||||
"could_not_find_typewidget": "Could not find typeWidget for type '{{type}}'",
|
||||
"printing": "Printing in progress...",
|
||||
"printing_pdf": "Exporting to PDF in progress..."
|
||||
"printing_pdf": "Exporting to PDF in progress...",
|
||||
"print_report_title": "Print report",
|
||||
"print_report_collection_content_one": "{{count}} note in the collection could not be printed because they are not supported or they are protected.",
|
||||
"print_report_collection_content_other": "{{count}} notes in the collection could not be printed because they are not supported or they are protected."
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "type note's title here...",
|
||||
|
||||
@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import NoteContext from "../components/note_context";
|
||||
import FNote from "../entities/fnote";
|
||||
import type { PrintReport } from "../print";
|
||||
import attributes from "../services/attributes";
|
||||
import { t } from "../services/i18n";
|
||||
import protected_session_holder from "../services/protected_session_holder";
|
||||
@ -179,8 +180,21 @@ export default function NoteDetail() {
|
||||
showToast("printing", e.detail.progress);
|
||||
});
|
||||
|
||||
iframe.contentWindow.addEventListener("note-ready", () => {
|
||||
iframe.contentWindow.addEventListener("note-ready", (e) => {
|
||||
toast.closePersistent("printing");
|
||||
|
||||
if ("detail" in e) {
|
||||
const printReport = e.detail as PrintReport;
|
||||
if (printReport.type === "collection" && printReport.ignoredNoteIds.length > 0) {
|
||||
toast.showPersistent({
|
||||
id: "print-report",
|
||||
icon: "bx bx-collection",
|
||||
title: t("note_detail.print_report_title"),
|
||||
message: t("note_detail.print_report_collection_content", { count: printReport.ignoredNoteIds.length })
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
iframe.contentWindow?.print();
|
||||
document.body.removeChild(iframe);
|
||||
});
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
import { allViewTypes, ViewModeMedia, ViewModeProps, ViewTypeOptions } from "./interface";
|
||||
import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent } from "../react/hooks";
|
||||
import FNote from "../../entities/fnote";
|
||||
import "./NoteList.css";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import ViewModeStorage from "./view_mode_storage";
|
||||
import { subscribeToMessages, unsubscribeToMessage as unsubscribeFromMessage } from "../../services/ws";
|
||||
|
||||
import { WebSocketMessage } from "@triliumnext/commons";
|
||||
import froca from "../../services/froca";
|
||||
import { lazy, Suspense } from "preact/compat";
|
||||
import { VNode } from "preact";
|
||||
import { lazy, Suspense } from "preact/compat";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import FNote from "../../entities/fnote";
|
||||
import type { PrintReport } from "../../print";
|
||||
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";
|
||||
interface NoteListProps {
|
||||
note: FNote | null | undefined;
|
||||
notePath: string | null | undefined;
|
||||
@ -19,7 +22,7 @@ interface NoteListProps {
|
||||
ntxId: string | null | undefined;
|
||||
media: ViewModeMedia;
|
||||
viewType: ViewTypeOptions | undefined;
|
||||
onReady?: () => void;
|
||||
onReady?: (data: PrintReport) => void;
|
||||
onProgressChanged?(progress: number): void;
|
||||
}
|
||||
|
||||
@ -48,7 +51,7 @@ const ViewComponents: Record<ViewTypeOptions, { normal: LazyLoadedComponent, pri
|
||||
presentation: {
|
||||
normal: lazy(() => import("./presentation/index.js"))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default function NoteList(props: Pick<NoteListProps, "displayOnlyCollections" | "media" | "onReady" | "onProgressChanged">) {
|
||||
const { note, noteContext, notePath, ntxId, viewScope } = useNoteContext();
|
||||
@ -57,13 +60,13 @@ export default function NoteList(props: Pick<NoteListProps, "displayOnlyCollecti
|
||||
const [ enabled, setEnabled ] = useState(noteContext?.hasNoteList());
|
||||
useEffect(() => {
|
||||
setEnabled(noteContext?.hasNoteList());
|
||||
}, [ note, noteContext, viewType, viewScope?.viewMode, noteType ])
|
||||
return <CustomNoteList viewType={viewType} note={note} isEnabled={!!enabled} notePath={notePath} ntxId={ntxId} {...props} />
|
||||
}, [ note, noteContext, viewType, viewScope?.viewMode, noteType ]);
|
||||
return <CustomNoteList viewType={viewType} note={note} isEnabled={!!enabled} notePath={notePath} ntxId={ntxId} {...props} />;
|
||||
}
|
||||
|
||||
export function SearchNoteList(props: Omit<NoteListProps, "isEnabled" | "viewType">) {
|
||||
const viewType = useNoteViewType(props.note);
|
||||
return <CustomNoteList {...props} isEnabled={true} viewType={viewType} />
|
||||
return <CustomNoteList {...props} isEnabled={true} viewType={viewType} />;
|
||||
}
|
||||
|
||||
export function CustomNoteList({ note, viewType, isEnabled: shouldEnable, notePath, highlightedTokens, displayOnlyCollections, ntxId, onReady, onProgressChanged, ...restProps }: NoteListProps) {
|
||||
@ -112,7 +115,7 @@ export function CustomNoteList({ note, viewType, isEnabled: shouldEnable, notePa
|
||||
onProgressChanged: onProgressChanged ?? (() => {}),
|
||||
|
||||
...restProps
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const ComponentToRender = viewType && props && isEnabled && (
|
||||
@ -140,9 +143,9 @@ export function useNoteViewType(note?: FNote | null): ViewTypeOptions | undefine
|
||||
} else if (!(allViewTypes as readonly string[]).includes(viewType || "")) {
|
||||
// when not explicitly set, decide based on the note type
|
||||
return note.type === "search" ? "list" : "grid";
|
||||
} else {
|
||||
return viewType as ViewTypeOptions;
|
||||
}
|
||||
return viewType as ViewTypeOptions;
|
||||
|
||||
}
|
||||
|
||||
export function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOptions | undefined, ntxId: string | null | undefined) {
|
||||
@ -161,26 +164,26 @@ export function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOpt
|
||||
async function getNoteIds(note: FNote) {
|
||||
if (directChildrenOnly) {
|
||||
return await note.getChildNoteIdsWithArchiveFiltering(includeArchived);
|
||||
} else {
|
||||
return await note.getSubtreeNoteIds(includeArchived);
|
||||
}
|
||||
return await note.getSubtreeNoteIds(includeArchived);
|
||||
|
||||
}
|
||||
|
||||
// Refresh on note switch.
|
||||
useEffect(() => {
|
||||
refreshNoteIds()
|
||||
refreshNoteIds();
|
||||
}, [ note, includeArchived, directChildrenOnly ]);
|
||||
|
||||
// Refresh on alterations to the note subtree.
|
||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||
if (note && loadResults.getBranchRows().some(branch =>
|
||||
branch.parentNoteId === note.noteId
|
||||
branch.parentNoteId === note.noteId
|
||||
|| noteIds.includes(branch.parentNoteId ?? ""))
|
||||
|| loadResults.getAttributeRows().some(attr => attr.name === "archived" && attr.noteId && noteIds.includes(attr.noteId))
|
||||
) {
|
||||
refreshNoteIds();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Refresh on search.
|
||||
useTriliumEvent("searchRefreshed", ({ ntxId: eventNtxId }) => {
|
||||
@ -201,13 +204,13 @@ export function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOpt
|
||||
...noteIds,
|
||||
...await getNoteIds(importedNote),
|
||||
importedNoteId
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
subscribeToMessages(onImport);
|
||||
return () => unsubscribeFromMessage(onImport);
|
||||
}, [ note, noteIds, setNoteIds ])
|
||||
}, [ note, noteIds, setNoteIds ]);
|
||||
|
||||
return noteIds;
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import FNote from "../../entities/fnote";
|
||||
import type { PrintReport } from "../../print";
|
||||
|
||||
export const allViewTypes = ["list", "grid", "calendar", "table", "geoMap", "board", "presentation"] as const;
|
||||
export type ViewTypeOptions = typeof allViewTypes[number];
|
||||
@ -18,6 +19,6 @@ export interface ViewModeProps<T extends object> {
|
||||
viewConfig: T | undefined;
|
||||
saveConfig(newConfig: T): void;
|
||||
media: ViewModeMedia;
|
||||
onReady(): void;
|
||||
onReady(data: PrintReport): void;
|
||||
onProgressChanged?: ProgressChangedFn;
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useEffect, useLayoutEffect, useState } from "preact/hooks";
|
||||
|
||||
import type FNote from "../../../entities/fnote";
|
||||
import type { PrintReport } from "../../../print";
|
||||
import content_renderer from "../../../services/content_renderer";
|
||||
import froca from "../../../services/froca";
|
||||
import type { ViewModeProps } from "../interface";
|
||||
@ -13,13 +14,17 @@ interface NotesWithContent {
|
||||
|
||||
export function ListPrintView({ note, noteIds: unfilteredNoteIds, onReady, onProgressChanged }: ViewModeProps<{}>) {
|
||||
const noteIds = useFilteredNoteIds(note, unfilteredNoteIds);
|
||||
const [ notesWithContent, setNotesWithContent ] = useState<NotesWithContent[]>();
|
||||
const [ state, setState ] = useState<{
|
||||
notesWithContent?: NotesWithContent[],
|
||||
data?: PrintReport
|
||||
}>({});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const noteIdsSet = new Set<string>();
|
||||
|
||||
froca.getNotes(noteIds).then(async (notes) => {
|
||||
const noteIdsWithChildren = await note.getSubtreeNoteIds(true);
|
||||
const ignoredNoteIds: string[] = [];
|
||||
const notesWithContent: NotesWithContent[] = [];
|
||||
|
||||
async function processNote(note: FNote, depth: number) {
|
||||
@ -35,6 +40,8 @@ export function ListPrintView({ note, noteIds: unfilteredNoteIds, onReady, onPro
|
||||
rewriteHeadings(contentEl, depth);
|
||||
noteIdsSet.add(note.noteId);
|
||||
notesWithContent.push({ note, contentEl });
|
||||
} else {
|
||||
ignoredNoteIds.push(note.noteId);
|
||||
}
|
||||
|
||||
if (onProgressChanged) {
|
||||
@ -58,22 +65,28 @@ export function ListPrintView({ note, noteIds: unfilteredNoteIds, onReady, onPro
|
||||
rewriteLinks(contentEl, noteIdsSet);
|
||||
}
|
||||
|
||||
setNotesWithContent(notesWithContent);
|
||||
setState({
|
||||
notesWithContent,
|
||||
data: {
|
||||
type: "collection",
|
||||
ignoredNoteIds
|
||||
}
|
||||
});
|
||||
});
|
||||
}, [noteIds]);
|
||||
|
||||
useEffect(() => {
|
||||
if (notesWithContent && onReady) {
|
||||
onReady();
|
||||
if (onReady && state?.data) {
|
||||
onReady(state.data);
|
||||
}
|
||||
}, [ notesWithContent, onReady ]);
|
||||
}, [ state, onReady ]);
|
||||
|
||||
return (
|
||||
<div class="note-list list-print-view">
|
||||
<div class="note-list-container use-tn-links">
|
||||
<h1>{note.title}</h1>
|
||||
|
||||
{notesWithContent?.map(({ note: childNote, contentEl }) => (
|
||||
{state.notesWithContent?.map(({ note: childNote, contentEl }) => (
|
||||
<section id={`note-${childNote.noteId}`} class="note" dangerouslySetInnerHTML={{ __html: contentEl.innerHTML }} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
import type { App, BrowserWindow, BrowserWindowConstructorOptions, IpcMainEvent,WebContents } from "electron";
|
||||
import electron, { ipcMain } from "electron";
|
||||
import fs from "fs/promises";
|
||||
import { t } from "i18next";
|
||||
import path from "path";
|
||||
import url from "url";
|
||||
import port from "./port.js";
|
||||
import optionService from "./options.js";
|
||||
import log from "./log.js";
|
||||
import sqlInit from "./sql_init.js";
|
||||
|
||||
import cls from "./cls.js";
|
||||
import keyboardActionsService from "./keyboard_actions.js";
|
||||
import electron, { ipcMain } from "electron";
|
||||
import type { App, BrowserWindowConstructorOptions, BrowserWindow, WebContents, IpcMainEvent } from "electron";
|
||||
import { formatDownloadTitle, isDev, isMac, isWindows } from "./utils.js";
|
||||
import { t } from "i18next";
|
||||
import log from "./log.js";
|
||||
import optionService from "./options.js";
|
||||
import port from "./port.js";
|
||||
import { RESOURCE_DIR } from "./resource_dir.js";
|
||||
import sqlInit from "./sql_init.js";
|
||||
import { formatDownloadTitle, isDev, isMac, isWindows } from "./utils.js";
|
||||
|
||||
// Prevent the window being garbage collected
|
||||
let mainWindow: BrowserWindow | null;
|
||||
@ -160,8 +161,8 @@ async function getBrowserWindowForPrinting(e: IpcMainEvent, notePath: string, ac
|
||||
await browserWindow.loadURL(`http://127.0.0.1:${port}/?print#${notePath}`);
|
||||
await browserWindow.webContents.executeJavaScript(`
|
||||
new Promise(resolve => {
|
||||
if (window._noteReady) return resolve();
|
||||
window.addEventListener("note-ready", () => resolve());
|
||||
if (window._noteReady) return resolve(window._noteReady);
|
||||
window.addEventListener("note-ready", (data) => resolve(data));
|
||||
});
|
||||
`);
|
||||
ipcMain.off("print-progress", progressCallback);
|
||||
@ -289,9 +290,9 @@ async function configureWebContents(webContents: WebContents, spellcheckEnabled:
|
||||
function getIcon() {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
return path.join(__dirname, "../../../desktop/electron-forge/app-icon/png/256x256-dev.png");
|
||||
} else {
|
||||
return path.join(RESOURCE_DIR, "../public/assets/icon.png");
|
||||
}
|
||||
}
|
||||
return path.join(RESOURCE_DIR, "../public/assets/icon.png");
|
||||
|
||||
}
|
||||
|
||||
async function createSetupWindow() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user