import { Button, html, icon } from "@mariozechner/mini-lit"; import "@mariozechner/mini-lit/dist/ModeToggle.js"; import { renderAsync } from "docx-preview"; import { LitElement } from "lit"; import { state } from "lit/decorators.js"; import { Download, X } from "lucide"; import * as pdfjsLib from "pdfjs-dist"; import * as XLSX from "xlsx"; import type { Attachment } from "../utils/attachment-utils.js"; import { i18n } from "../utils/i18n.js"; type FileType = "image" | "pdf" | "docx" | "pptx" | "excel" | "text"; export class AttachmentOverlay extends LitElement { @state() private attachment?: Attachment; @state() private showExtractedText = false; @state() private error: string | null = null; // Track current loading task to cancel if needed private currentLoadingTask: any = null; private onCloseCallback?: () => void; private boundHandleKeyDown?: (e: KeyboardEvent) => void; protected override createRenderRoot(): HTMLElement | DocumentFragment { return this; } static open(attachment: Attachment, onClose?: () => void) { const overlay = new AttachmentOverlay(); overlay.attachment = attachment; overlay.onCloseCallback = onClose; document.body.appendChild(overlay); overlay.setupEventListeners(); } private setupEventListeners() { this.boundHandleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { this.close(); } }; window.addEventListener("keydown", this.boundHandleKeyDown); } private close() { this.cleanup(); if (this.boundHandleKeyDown) { window.removeEventListener("keydown", this.boundHandleKeyDown); } this.onCloseCallback?.(); this.remove(); } private getFileType(): FileType { if (!this.attachment) return "text"; if (this.attachment.type === "image") return "image"; if (this.attachment.mimeType === "application/pdf") return "pdf"; if (this.attachment.mimeType?.includes("wordprocessingml")) return "docx"; if ( this.attachment.mimeType?.includes("presentationml") || this.attachment.fileName.toLowerCase().endsWith(".pptx") ) return "pptx"; if ( this.attachment.mimeType?.includes("spreadsheetml") || this.attachment.mimeType?.includes("ms-excel") || this.attachment.fileName.toLowerCase().endsWith(".xlsx") || this.attachment.fileName.toLowerCase().endsWith(".xls") ) return "excel"; return "text"; } private getFileTypeLabel(): string { const type = this.getFileType(); switch (type) { case "pdf": return i18n("PDF"); case "docx": return i18n("Document"); case "pptx": return i18n("Presentation"); case "excel": return i18n("Spreadsheet"); default: return ""; } } private handleBackdropClick = () => { this.close(); }; private handleDownload = () => { if (!this.attachment) return; // Create a blob from the base64 content const byteCharacters = atob(this.attachment.content); const byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); const blob = new Blob([byteArray], { type: this.attachment.mimeType }); // Create download link const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = this.attachment.fileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; private cleanup() { this.showExtractedText = false; this.error = null; // Cancel any loading PDF task when closing if (this.currentLoadingTask) { this.currentLoadingTask.destroy(); this.currentLoadingTask = null; } } override render() { if (!this.attachment) return html``; return html`
${
this.attachment.extractedText || i18n("No text content available")
}
${
this.attachment.extractedText || i18n("No content available")
}