mirror of
https://github.com/zadam/trilium.git
synced 2026-01-04 05:34:30 +01:00
feat(right_pane): display attachments
This commit is contained in:
parent
c1d6b3121a
commit
43a749b6a7
@ -31,6 +31,10 @@ export interface NoteContextDataMap {
|
||||
scrollToPage(page: number): void;
|
||||
requestThumbnail(page: number): void;
|
||||
};
|
||||
pdfAttachments: {
|
||||
attachments: Array<{ filename: string; size: number }>;
|
||||
downloadAttachment(filename: string): void;
|
||||
};
|
||||
}
|
||||
|
||||
type ContextDataKey = keyof NoteContextDataMap;
|
||||
|
||||
57
apps/client/src/widgets/sidebar/PdfAttachments.css
Normal file
57
apps/client/src/widgets/sidebar/PdfAttachments.css
Normal file
@ -0,0 +1,57 @@
|
||||
.pdf-attachments-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pdf-attachment-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.pdf-attachment-item:hover {
|
||||
background-color: var(--hover-item-background-color);
|
||||
}
|
||||
|
||||
.pdf-attachment-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.pdf-attachment-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pdf-attachment-filename {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--main-text-color);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pdf-attachment-size {
|
||||
font-size: 11px;
|
||||
color: var(--muted-text-color);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.no-attachments {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.pdf-attachment-item .bx {
|
||||
flex-shrink: 0;
|
||||
font-size: 18px;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.pdf-attachment-item:hover .bx {
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
75
apps/client/src/widgets/sidebar/PdfAttachments.tsx
Normal file
75
apps/client/src/widgets/sidebar/PdfAttachments.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import "./PdfAttachments.css";
|
||||
|
||||
import { useActiveNoteContext, useGetContextData, useNoteProperty } from "../react/hooks";
|
||||
import Icon from "../react/Icon";
|
||||
import RightPanelWidget from "./RightPanelWidget";
|
||||
|
||||
interface AttachmentInfo {
|
||||
filename: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export default function PdfAttachments() {
|
||||
const { note } = useActiveNoteContext();
|
||||
const noteType = useNoteProperty(note, "type");
|
||||
const noteMime = useNoteProperty(note, "mime");
|
||||
|
||||
if (noteType !== "file" || noteMime !== "application/pdf") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RightPanelWidget id="pdf-attachments" title="Attachments">
|
||||
<PdfAttachmentsList key={note?.noteId} />
|
||||
</RightPanelWidget>
|
||||
);
|
||||
}
|
||||
|
||||
function PdfAttachmentsList() {
|
||||
const attachmentsData = useGetContextData("pdfAttachments");
|
||||
|
||||
if (!attachmentsData || attachmentsData.attachments.length === 0) {
|
||||
return <div className="no-attachments">No attachments</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pdf-attachments-list">
|
||||
{attachmentsData.attachments.map((attachment) => (
|
||||
<PdfAttachmentItem
|
||||
key={attachment.filename}
|
||||
attachment={attachment}
|
||||
onDownload={attachmentsData.downloadAttachment}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PdfAttachmentItem({
|
||||
attachment,
|
||||
onDownload
|
||||
}: {
|
||||
attachment: AttachmentInfo;
|
||||
onDownload: (filename: string) => void;
|
||||
}) {
|
||||
const sizeText = formatFileSize(attachment.size);
|
||||
|
||||
return (
|
||||
<div className="pdf-attachment-item" onClick={() => onDownload(attachment.filename)}>
|
||||
<Icon icon="bx bx-paperclip" />
|
||||
<div className="pdf-attachment-info">
|
||||
<div className="pdf-attachment-filename">{attachment.filename}</div>
|
||||
<div className="pdf-attachment-size">{sizeText}</div>
|
||||
</div>
|
||||
<Icon icon="bx bx-download" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function formatFileSize(bytes: number): string {
|
||||
if (bytes === 0) return "0 B";
|
||||
const k = 1024;
|
||||
const sizes = ["B", "KB", "MB", "GB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return `${Math.round((bytes / Math.pow(k, i)) * 100) / 100 } ${ sizes[i]}`;
|
||||
}
|
||||
@ -15,6 +15,7 @@ import { useActiveNoteContext, useLegacyWidget, useNoteProperty, useTriliumEvent
|
||||
import Icon from "../react/Icon";
|
||||
import LegacyRightPanelWidget from "../right_panel_widget";
|
||||
import HighlightsList from "./HighlightsList";
|
||||
import PdfAttachments from "./PdfAttachments";
|
||||
import PdfPages from "./PdfPages";
|
||||
import RightPanelWidget from "./RightPanelWidget";
|
||||
import TableOfContents from "./TableOfContents";
|
||||
@ -72,6 +73,10 @@ function useItems(rightPaneVisible: boolean, widgetsByParent: WidgetsByParent) {
|
||||
el: <PdfPages />,
|
||||
enabled: isPdf,
|
||||
},
|
||||
{
|
||||
el: <PdfAttachments />,
|
||||
enabled: isPdf,
|
||||
},
|
||||
{
|
||||
el: <HighlightsList />,
|
||||
enabled: noteType === "text" && highlightsList.length > 0,
|
||||
|
||||
@ -109,6 +109,18 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: {
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if (event.data.type === "pdfjs-viewer-attachments") {
|
||||
noteContext.setContextData("pdfAttachments", {
|
||||
attachments: event.data.attachments,
|
||||
downloadAttachment: (filename: string) => {
|
||||
iframeRef.current?.contentWindow?.postMessage({
|
||||
type: "trilium-download-attachment",
|
||||
filename
|
||||
}, "*");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleMessage);
|
||||
|
||||
80
packages/pdfjs-viewer/src/attachments.ts
Normal file
80
packages/pdfjs-viewer/src/attachments.ts
Normal file
@ -0,0 +1,80 @@
|
||||
export async function setupPdfAttachments() {
|
||||
const app = window.PDFViewerApplication;
|
||||
|
||||
// Extract immediately since we're called after documentloaded
|
||||
await extractAndSendAttachments();
|
||||
|
||||
// Listen for download requests
|
||||
window.addEventListener("message", async (event) => {
|
||||
if (event.data?.type === "trilium-download-attachment") {
|
||||
const filename = event.data.filename;
|
||||
await downloadAttachment(filename);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function extractAndSendAttachments() {
|
||||
const app = window.PDFViewerApplication;
|
||||
|
||||
try {
|
||||
const attachments = await app.pdfDocument.getAttachments();
|
||||
console.log("Got attachments:", attachments);
|
||||
|
||||
if (!attachments) {
|
||||
window.parent.postMessage({
|
||||
type: "pdfjs-viewer-attachments",
|
||||
attachments: []
|
||||
}, "*");
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert attachments object to array
|
||||
const attachmentList = Object.entries(attachments).map(([filename, data]: [string, any]) => ({
|
||||
filename,
|
||||
content: data.content, // Uint8Array
|
||||
size: data.content?.length || 0
|
||||
}));
|
||||
|
||||
// Send metadata only (not the full content)
|
||||
window.parent.postMessage({
|
||||
type: "pdfjs-viewer-attachments",
|
||||
attachments: attachmentList.map(att => ({
|
||||
filename: att.filename,
|
||||
size: att.size
|
||||
}))
|
||||
}, "*");
|
||||
} catch (error) {
|
||||
console.error("Error extracting attachments:", error);
|
||||
window.parent.postMessage({
|
||||
type: "pdfjs-viewer-attachments",
|
||||
attachments: []
|
||||
}, "*");
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadAttachment(filename: string) {
|
||||
const app = window.PDFViewerApplication;
|
||||
|
||||
try {
|
||||
const attachments = await app.pdfDocument.getAttachments();
|
||||
const attachment = attachments?.[filename];
|
||||
|
||||
if (!attachment) {
|
||||
console.error("Attachment not found:", filename);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create blob and download
|
||||
const blob = new Blob([attachment.content], { type: "application/octet-stream" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.click();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error("Error downloading attachment:", error);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
import interceptPersistence from "./persistence";
|
||||
import { extractAndSendToc, setupScrollToHeading, setupActiveHeadingTracking } from "./toc";
|
||||
import { setupPdfPages } from "./pages";
|
||||
import { setupPdfAttachments } from "./attachments";
|
||||
|
||||
const LOG_EVENT_BUS = false;
|
||||
|
||||
@ -23,6 +24,7 @@ async function main() {
|
||||
setupScrollToHeading();
|
||||
setupActiveHeadingTracking();
|
||||
setupPdfPages();
|
||||
setupPdfAttachments();
|
||||
});
|
||||
await app.initializedPromise;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user