diff --git a/app.go b/app.go
index f5c82a7..98c13b9 100644
--- a/app.go
+++ b/app.go
@@ -1,6 +1,7 @@
package main
import (
+ "archive/zip"
"context"
"encoding/base64"
"fmt"
@@ -24,14 +25,15 @@ import (
// App struct
type App struct {
- ctx context.Context
- StartupFilePath string
- openImagesMux sync.Mutex
- openImages map[string]bool
- openPDFsMux sync.Mutex
- openPDFs map[string]bool
- openEMLsMux sync.Mutex
- openEMLs map[string]bool
+ ctx context.Context
+ StartupFilePath string
+ CurrentMailFilePath string // Tracks the currently loaded mail file (from startup or file dialog)
+ openImagesMux sync.Mutex
+ openImages map[string]bool
+ openPDFsMux sync.Mutex
+ openPDFs map[string]bool
+ openEMLsMux sync.Mutex
+ openEMLs map[string]bool
}
// NewApp creates a new App application struct
@@ -48,6 +50,11 @@ func NewApp() *App {
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
+ // Set CurrentMailFilePath to StartupFilePath if a file was opened via command line
+ if a.StartupFilePath != "" {
+ a.CurrentMailFilePath = a.StartupFilePath
+ }
+
isViewer := false
for _, arg := range os.Args {
if strings.Contains(arg, "--view-image") || strings.Contains(arg, "--view-pdf") {
@@ -102,6 +109,16 @@ func (a *App) GetStartupFile() string {
return a.StartupFilePath
}
+// SetCurrentMailFilePath sets the path of the currently loaded mail file
+func (a *App) SetCurrentMailFilePath(filePath string) {
+ a.CurrentMailFilePath = filePath
+}
+
+// GetCurrentMailFilePath returns the path of the currently loaded mail file
+func (a *App) GetCurrentMailFilePath() string {
+ return a.CurrentMailFilePath
+}
+
// ReadEML reads a .eml file and returns the email data
func (a *App) ReadEML(filePath string) (*internal.EmailData, error) {
return internal.ReadEmlFile(filePath)
@@ -534,3 +551,338 @@ func (a *App) ConvertToUTF8(s string) string {
}
return decoded
}
+
+// ScreenshotResult contains the screenshot data and metadata
+type ScreenshotResult struct {
+ Data string `json:"data"` // Base64-encoded PNG data
+ Width int `json:"width"` // Image width in pixels
+ Height int `json:"height"` // Image height in pixels
+ Filename string `json:"filename"` // Suggested filename
+}
+
+// TakeScreenshot captures the current Wails application window and returns it as base64 PNG
+func (a *App) TakeScreenshot() (*ScreenshotResult, error) {
+ // Get the window title to find our window
+ windowTitle := "EMLy - EML Viewer for 3gIT"
+
+ // Check if we're in viewer mode
+ for _, arg := range os.Args {
+ if strings.Contains(arg, "--view-image") {
+ windowTitle = "EMLy Image Viewer"
+ break
+ }
+ if strings.Contains(arg, "--view-pdf") {
+ windowTitle = "EMLy PDF Viewer"
+ break
+ }
+ }
+
+ img, err := utils.CaptureWindowByTitle(windowTitle)
+ if err != nil {
+ return nil, fmt.Errorf("failed to capture window: %w", err)
+ }
+
+ base64Data, err := utils.ScreenshotToBase64PNG(img)
+ if err != nil {
+ return nil, fmt.Errorf("failed to encode screenshot: %w", err)
+ }
+
+ bounds := img.Bounds()
+ timestamp := time.Now().Format("20060102_150405")
+
+ return &ScreenshotResult{
+ Data: base64Data,
+ Width: bounds.Dx(),
+ Height: bounds.Dy(),
+ Filename: fmt.Sprintf("emly_screenshot_%s.png", timestamp),
+ }, nil
+}
+
+// SaveScreenshot captures and saves the screenshot to a file, returning the file path
+func (a *App) SaveScreenshot() (string, error) {
+ result, err := a.TakeScreenshot()
+ if err != nil {
+ return "", err
+ }
+
+ // Decode base64 data
+ data, err := base64.StdEncoding.DecodeString(result.Data)
+ if err != nil {
+ return "", fmt.Errorf("failed to decode screenshot data: %w", err)
+ }
+
+ // Save to temp directory
+ tempDir := os.TempDir()
+ filePath := filepath.Join(tempDir, result.Filename)
+
+ if err := os.WriteFile(filePath, data, 0644); err != nil {
+ return "", fmt.Errorf("failed to save screenshot: %w", err)
+ }
+
+ return filePath, nil
+}
+
+// SaveScreenshotAs opens a save dialog and saves the screenshot to the selected location
+func (a *App) SaveScreenshotAs() (string, error) {
+ result, err := a.TakeScreenshot()
+ if err != nil {
+ return "", err
+ }
+
+ // Open save dialog
+ savePath, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
+ DefaultFilename: result.Filename,
+ Title: "Save Screenshot",
+ Filters: []runtime.FileFilter{
+ {
+ DisplayName: "PNG Images (*.png)",
+ Pattern: "*.png",
+ },
+ },
+ })
+ if err != nil {
+ return "", fmt.Errorf("failed to open save dialog: %w", err)
+ }
+
+ if savePath == "" {
+ return "", nil // User cancelled
+ }
+
+ // Decode base64 data
+ data, err := base64.StdEncoding.DecodeString(result.Data)
+ if err != nil {
+ return "", fmt.Errorf("failed to decode screenshot data: %w", err)
+ }
+
+ if err := os.WriteFile(savePath, data, 0644); err != nil {
+ return "", fmt.Errorf("failed to save screenshot: %w", err)
+ }
+
+ return savePath, nil
+}
+
+// BugReportResult contains paths to the bug report files
+type BugReportResult struct {
+ FolderPath string `json:"folderPath"` // Path to the bug report folder
+ ScreenshotPath string `json:"screenshotPath"` // Path to the screenshot file
+ MailFilePath string `json:"mailFilePath"` // Path to the copied mail file (empty if no mail)
+}
+
+// CreateBugReportFolder creates a folder in temp with screenshot and optionally the current mail file
+func (a *App) CreateBugReportFolder() (*BugReportResult, error) {
+ // Create timestamp for unique folder name
+ timestamp := time.Now().Format("20060102_150405")
+ folderName := fmt.Sprintf("emly_bugreport_%s", timestamp)
+
+ // Create folder in temp directory
+ tempDir := os.TempDir()
+ bugReportFolder := filepath.Join(tempDir, folderName)
+
+ if err := os.MkdirAll(bugReportFolder, 0755); err != nil {
+ return nil, fmt.Errorf("failed to create bug report folder: %w", err)
+ }
+
+ result := &BugReportResult{
+ FolderPath: bugReportFolder,
+ }
+
+ // Take and save screenshot
+ screenshotResult, err := a.TakeScreenshot()
+ if err != nil {
+ return nil, fmt.Errorf("failed to take screenshot: %w", err)
+ }
+
+ screenshotData, err := base64.StdEncoding.DecodeString(screenshotResult.Data)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode screenshot: %w", err)
+ }
+
+ screenshotPath := filepath.Join(bugReportFolder, screenshotResult.Filename)
+ if err := os.WriteFile(screenshotPath, screenshotData, 0644); err != nil {
+ return nil, fmt.Errorf("failed to save screenshot: %w", err)
+ }
+ result.ScreenshotPath = screenshotPath
+
+ // Check if there's a mail file loaded and copy it
+ if a.CurrentMailFilePath != "" {
+ // Read the original mail file
+ mailData, err := os.ReadFile(a.CurrentMailFilePath)
+ if err != nil {
+ // Log but don't fail - screenshot is still valid
+ Log("Failed to read mail file for bug report:", err)
+ } else {
+ // Get the original filename
+ mailFilename := filepath.Base(a.CurrentMailFilePath)
+ mailFilePath := filepath.Join(bugReportFolder, mailFilename)
+
+ if err := os.WriteFile(mailFilePath, mailData, 0644); err != nil {
+ Log("Failed to copy mail file for bug report:", err)
+ } else {
+ result.MailFilePath = mailFilePath
+ }
+ }
+ }
+
+ return result, nil
+}
+
+// BugReportInput contains the user-provided bug report details
+type BugReportInput struct {
+ Name string `json:"name"`
+ Email string `json:"email"`
+ Description string `json:"description"`
+ ScreenshotData string `json:"screenshotData"` // Base64-encoded PNG screenshot
+}
+
+// SubmitBugReportResult contains the result of submitting a bug report
+type SubmitBugReportResult struct {
+ ZipPath string `json:"zipPath"` // Path to the created zip file
+ FolderPath string `json:"folderPath"` // Path to the bug report folder
+}
+
+// SubmitBugReport creates a complete bug report with user input, saves it, and zips the folder
+func (a *App) SubmitBugReport(input BugReportInput) (*SubmitBugReportResult, error) {
+ // Create timestamp for unique folder name
+ timestamp := time.Now().Format("20060102_150405")
+ folderName := fmt.Sprintf("emly_bugreport_%s", timestamp)
+
+ // Create folder in temp directory
+ tempDir := os.TempDir()
+ bugReportFolder := filepath.Join(tempDir, folderName)
+
+ if err := os.MkdirAll(bugReportFolder, 0755); err != nil {
+ return nil, fmt.Errorf("failed to create bug report folder: %w", err)
+ }
+
+ // Save the pre-captured screenshot
+ if input.ScreenshotData != "" {
+ screenshotData, err := base64.StdEncoding.DecodeString(input.ScreenshotData)
+ if err != nil {
+ Log("Failed to decode screenshot:", err)
+ } else {
+ screenshotPath := filepath.Join(bugReportFolder, fmt.Sprintf("emly_screenshot_%s.png", timestamp))
+ if err := os.WriteFile(screenshotPath, screenshotData, 0644); err != nil {
+ Log("Failed to save screenshot:", err)
+ }
+ }
+ }
+
+ // Copy the mail file if one is loaded
+ if a.CurrentMailFilePath != "" {
+ mailData, err := os.ReadFile(a.CurrentMailFilePath)
+ if err != nil {
+ Log("Failed to read mail file for bug report:", err)
+ } else {
+ mailFilename := filepath.Base(a.CurrentMailFilePath)
+ mailFilePath := filepath.Join(bugReportFolder, mailFilename)
+ if err := os.WriteFile(mailFilePath, mailData, 0644); err != nil {
+ Log("Failed to copy mail file for bug report:", err)
+ }
+ }
+ }
+
+ // Create the report.txt file with user's description
+ reportContent := fmt.Sprintf(`EMLy Bug Report
+================
+
+Name: %s
+Email: %s
+
+Description:
+%s
+
+Generated: %s
+`, input.Name, input.Email, input.Description, time.Now().Format("2006-01-02 15:04:05"))
+
+ reportPath := filepath.Join(bugReportFolder, "report.txt")
+ if err := os.WriteFile(reportPath, []byte(reportContent), 0644); err != nil {
+ return nil, fmt.Errorf("failed to save report file: %w", err)
+ }
+
+ // Get machine info and save it
+ machineInfo, err := utils.GetMachineInfo()
+ if err == nil && machineInfo != nil {
+ sysInfoContent := fmt.Sprintf(`System Information
+==================
+
+Hostname: %s
+OS: %s
+Version: %s
+Hardware ID: %s
+External IP: %s
+`, machineInfo.Hostname, machineInfo.OS, machineInfo.Version, machineInfo.HWID, machineInfo.ExternalIP)
+
+ sysInfoPath := filepath.Join(bugReportFolder, "system_info.txt")
+ if err := os.WriteFile(sysInfoPath, []byte(sysInfoContent), 0644); err != nil {
+ Log("Failed to save system info:", err)
+ }
+ }
+
+ // Zip the folder
+ zipPath := bugReportFolder + ".zip"
+ if err := zipFolder(bugReportFolder, zipPath); err != nil {
+ return nil, fmt.Errorf("failed to create zip file: %w", err)
+ }
+
+ return &SubmitBugReportResult{
+ ZipPath: zipPath,
+ FolderPath: bugReportFolder,
+ }, nil
+}
+
+// zipFolder creates a zip archive of the given folder
+func zipFolder(sourceFolder, destZip string) error {
+ zipFile, err := os.Create(destZip)
+ if err != nil {
+ return err
+ }
+ defer zipFile.Close()
+
+ zipWriter := zip.NewWriter(zipFile)
+ defer zipWriter.Close()
+
+ // Walk through the folder and add all files to the zip
+ return filepath.Walk(sourceFolder, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ // Skip the root folder itself
+ if path == sourceFolder {
+ return nil
+ }
+
+ // Get relative path for the zip entry
+ relPath, err := filepath.Rel(sourceFolder, path)
+ if err != nil {
+ return err
+ }
+
+ // Skip directories (they'll be created implicitly)
+ if info.IsDir() {
+ return nil
+ }
+
+ // Create the file in the zip
+ writer, err := zipWriter.Create(relPath)
+ if err != nil {
+ return err
+ }
+
+ // Read the file content
+ fileContent, err := os.ReadFile(path)
+ if err != nil {
+ return err
+ }
+
+ // Write to zip
+ _, err = writer.Write(fileContent)
+ return err
+ })
+}
+
+// OpenFolderInExplorer opens the specified folder in Windows Explorer
+func (a *App) OpenFolderInExplorer(folderPath string) error {
+ cmd := exec.Command("explorer", folderPath)
+ return cmd.Start()
+}
diff --git a/backend/utils/screenshot_windows.go b/backend/utils/screenshot_windows.go
new file mode 100644
index 0000000..6ac8206
--- /dev/null
+++ b/backend/utils/screenshot_windows.go
@@ -0,0 +1,259 @@
+package utils
+
+import (
+ "bytes"
+ "encoding/base64"
+ "fmt"
+ "image"
+ "image/png"
+ "syscall"
+ "unsafe"
+)
+
+var (
+ user32 = syscall.NewLazyDLL("user32.dll")
+ gdi32 = syscall.NewLazyDLL("gdi32.dll")
+ dwmapi = syscall.NewLazyDLL("dwmapi.dll")
+
+ // user32 functions
+ getForegroundWindow = user32.NewProc("GetForegroundWindow")
+ getWindowRect = user32.NewProc("GetWindowRect")
+ getClientRect = user32.NewProc("GetClientRect")
+ getDC = user32.NewProc("GetDC")
+ releaseDC = user32.NewProc("ReleaseDC")
+ findWindowW = user32.NewProc("FindWindowW")
+ getWindowDC = user32.NewProc("GetWindowDC")
+ printWindow = user32.NewProc("PrintWindow")
+ clientToScreen = user32.NewProc("ClientToScreen")
+
+ // gdi32 functions
+ createCompatibleDC = gdi32.NewProc("CreateCompatibleDC")
+ createCompatibleBitmap = gdi32.NewProc("CreateCompatibleBitmap")
+ selectObject = gdi32.NewProc("SelectObject")
+ bitBlt = gdi32.NewProc("BitBlt")
+ deleteDC = gdi32.NewProc("DeleteDC")
+ deleteObject = gdi32.NewProc("DeleteObject")
+ getDIBits = gdi32.NewProc("GetDIBits")
+
+ // dwmapi functions
+ dwmGetWindowAttribute = dwmapi.NewProc("DwmGetWindowAttribute")
+)
+
+// RECT structure for Windows API
+type RECT struct {
+ Left int32
+ Top int32
+ Right int32
+ Bottom int32
+}
+
+// POINT structure for Windows API
+type POINT struct {
+ X int32
+ Y int32
+}
+
+// BITMAPINFOHEADER structure
+type BITMAPINFOHEADER struct {
+ BiSize uint32
+ BiWidth int32
+ BiHeight int32
+ BiPlanes uint16
+ BiBitCount uint16
+ BiCompression uint32
+ BiSizeImage uint32
+ BiXPelsPerMeter int32
+ BiYPelsPerMeter int32
+ BiClrUsed uint32
+ BiClrImportant uint32
+}
+
+// BITMAPINFO structure
+type BITMAPINFO struct {
+ BmiHeader BITMAPINFOHEADER
+ BmiColors [1]uint32
+}
+
+const (
+ SRCCOPY = 0x00CC0020
+ DIB_RGB_COLORS = 0
+ BI_RGB = 0
+ PW_CLIENTONLY = 1
+ PW_RENDERFULLCONTENT = 2
+ DWMWA_EXTENDED_FRAME_BOUNDS = 9
+)
+
+// CaptureWindowByHandle captures a screenshot of a specific window by its handle
+func CaptureWindowByHandle(hwnd uintptr) (*image.RGBA, error) {
+ if hwnd == 0 {
+ return nil, fmt.Errorf("invalid window handle")
+ }
+
+ // Try to get the actual window bounds using DWM (handles DPI scaling better)
+ var rect RECT
+ ret, _, _ := dwmGetWindowAttribute.Call(
+ hwnd,
+ uintptr(DWMWA_EXTENDED_FRAME_BOUNDS),
+ uintptr(unsafe.Pointer(&rect)),
+ uintptr(unsafe.Sizeof(rect)),
+ )
+
+ // Fallback to GetWindowRect if DWM fails
+ if ret != 0 {
+ ret, _, err := getWindowRect.Call(hwnd, uintptr(unsafe.Pointer(&rect)))
+ if ret == 0 {
+ return nil, fmt.Errorf("GetWindowRect failed: %v", err)
+ }
+ }
+
+ width := int(rect.Right - rect.Left)
+ height := int(rect.Bottom - rect.Top)
+
+ if width <= 0 || height <= 0 {
+ return nil, fmt.Errorf("invalid window dimensions: %dx%d", width, height)
+ }
+
+ // Get window DC
+ hdcWindow, _, err := getWindowDC.Call(hwnd)
+ if hdcWindow == 0 {
+ return nil, fmt.Errorf("GetWindowDC failed: %v", err)
+ }
+ defer releaseDC.Call(hwnd, hdcWindow)
+
+ // Create compatible DC
+ hdcMem, _, err := createCompatibleDC.Call(hdcWindow)
+ if hdcMem == 0 {
+ return nil, fmt.Errorf("CreateCompatibleDC failed: %v", err)
+ }
+ defer deleteDC.Call(hdcMem)
+
+ // Create compatible bitmap
+ hBitmap, _, err := createCompatibleBitmap.Call(hdcWindow, uintptr(width), uintptr(height))
+ if hBitmap == 0 {
+ return nil, fmt.Errorf("CreateCompatibleBitmap failed: %v", err)
+ }
+ defer deleteObject.Call(hBitmap)
+
+ // Select bitmap into DC
+ oldBitmap, _, _ := selectObject.Call(hdcMem, hBitmap)
+ defer selectObject.Call(hdcMem, oldBitmap)
+
+ // Try PrintWindow first (works better with layered/composited windows)
+ ret, _, _ = printWindow.Call(hwnd, hdcMem, PW_RENDERFULLCONTENT)
+ if ret == 0 {
+ // Fallback to BitBlt
+ ret, _, err = bitBlt.Call(
+ hdcMem, 0, 0, uintptr(width), uintptr(height),
+ hdcWindow, 0, 0,
+ SRCCOPY,
+ )
+ if ret == 0 {
+ return nil, fmt.Errorf("BitBlt failed: %v", err)
+ }
+ }
+
+ // Prepare BITMAPINFO
+ bmi := BITMAPINFO{
+ BmiHeader: BITMAPINFOHEADER{
+ BiSize: uint32(unsafe.Sizeof(BITMAPINFOHEADER{})),
+ BiWidth: int32(width),
+ BiHeight: -int32(height), // Negative for top-down DIB
+ BiPlanes: 1,
+ BiBitCount: 32,
+ BiCompression: BI_RGB,
+ },
+ }
+
+ // Allocate buffer for pixel data
+ pixelDataSize := width * height * 4
+ pixelData := make([]byte, pixelDataSize)
+
+ // Get the bitmap bits
+ ret, _, err = getDIBits.Call(
+ hdcMem,
+ hBitmap,
+ 0,
+ uintptr(height),
+ uintptr(unsafe.Pointer(&pixelData[0])),
+ uintptr(unsafe.Pointer(&bmi)),
+ DIB_RGB_COLORS,
+ )
+ if ret == 0 {
+ return nil, fmt.Errorf("GetDIBits failed: %v", err)
+ }
+
+ // Convert BGRA to RGBA
+ img := image.NewRGBA(image.Rect(0, 0, width, height))
+ for i := 0; i < len(pixelData); i += 4 {
+ img.Pix[i+0] = pixelData[i+2] // R <- B
+ img.Pix[i+1] = pixelData[i+1] // G <- G
+ img.Pix[i+2] = pixelData[i+0] // B <- R
+ img.Pix[i+3] = pixelData[i+3] // A <- A
+ }
+
+ return img, nil
+}
+
+// CaptureForegroundWindow captures the currently focused window
+func CaptureForegroundWindow() (*image.RGBA, error) {
+ hwnd, _, _ := getForegroundWindow.Call()
+ if hwnd == 0 {
+ return nil, fmt.Errorf("no foreground window found")
+ }
+ return CaptureWindowByHandle(hwnd)
+}
+
+// CaptureWindowByTitle captures a window by its title
+func CaptureWindowByTitle(title string) (*image.RGBA, error) {
+ titlePtr, err := syscall.UTF16PtrFromString(title)
+ if err != nil {
+ return nil, fmt.Errorf("failed to convert title: %v", err)
+ }
+
+ hwnd, _, _ := findWindowW.Call(0, uintptr(unsafe.Pointer(titlePtr)))
+ if hwnd == 0 {
+ return nil, fmt.Errorf("window with title '%s' not found", title)
+ }
+ return CaptureWindowByHandle(hwnd)
+}
+
+// ScreenshotToBase64PNG captures a window and returns it as a base64-encoded PNG string
+func ScreenshotToBase64PNG(img *image.RGBA) (string, error) {
+ if img == nil {
+ return "", fmt.Errorf("nil image provided")
+ }
+
+ var buf bytes.Buffer
+ if err := png.Encode(&buf, img); err != nil {
+ return "", fmt.Errorf("failed to encode PNG: %v", err)
+ }
+
+ return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
+}
+
+// CaptureWindowToBase64 is a convenience function that captures a window and returns base64 PNG
+func CaptureWindowToBase64(hwnd uintptr) (string, error) {
+ img, err := CaptureWindowByHandle(hwnd)
+ if err != nil {
+ return "", err
+ }
+ return ScreenshotToBase64PNG(img)
+}
+
+// CaptureForegroundWindowToBase64 captures the foreground window and returns base64 PNG
+func CaptureForegroundWindowToBase64() (string, error) {
+ img, err := CaptureForegroundWindow()
+ if err != nil {
+ return "", err
+ }
+ return ScreenshotToBase64PNG(img)
+}
+
+// CaptureWindowByTitleToBase64 captures a window by title and returns base64 PNG
+func CaptureWindowByTitleToBase64(title string) (string, error) {
+ img, err := CaptureWindowByTitle(title)
+ if err != nil {
+ return "", err
+ }
+ return ScreenshotToBase64PNG(img)
+}
diff --git a/frontend/messages/en.json b/frontend/messages/en.json
index 32c8323..bde7195 100644
--- a/frontend/messages/en.json
+++ b/frontend/messages/en.json
@@ -89,5 +89,25 @@
"mail_pdf_already_open": "The PDF is already open in another window.",
"settings_danger_debugger_protection_label": "Enable attached debugger protection",
"settings_danger_debugger_protection_hint": "This will prevent the app from being debugged by an attached debugger.",
- "settings_danger_debugger_protection_info": "Info: This actions are currently not configurable and is always enabled for private builds."
+ "settings_danger_debugger_protection_info": "Info: This actions are currently not configurable and is always enabled for private builds.",
+ "bugreport_title": "Report a Bug",
+ "bugreport_description": "Describe what you were doing when the bug occurred and what you expected to happen instead.",
+ "bugreport_name_label": "Name",
+ "bugreport_name_placeholder": "Your name",
+ "bugreport_email_label": "Email",
+ "bugreport_email_placeholder": "your.email@example.com",
+ "bugreport_text_label": "Bug Description",
+ "bugreport_text_placeholder": "Describe the bug in detail...",
+ "bugreport_info": "Your message, email file (if loaded), screenshot, and system information will be included in the report.",
+ "bugreport_screenshot_label": "Attached Screenshot:",
+ "bugreport_cancel": "Cancel",
+ "bugreport_submit": "Submit Report",
+ "bugreport_submitting": "Creating report...",
+ "bugreport_success_title": "Bug Report Created",
+ "bugreport_success_message": "Your bug report has been saved to:",
+ "bugreport_copy_path": "Copy Path",
+ "bugreport_open_folder": "Open Folder",
+ "bugreport_close": "Close",
+ "bugreport_error": "Failed to create bug report.",
+ "bugreport_copied": "Path copied to clipboard!"
}
diff --git a/frontend/messages/it.json b/frontend/messages/it.json
index 7013dde..dfbf8fe 100644
--- a/frontend/messages/it.json
+++ b/frontend/messages/it.json
@@ -89,5 +89,25 @@
"mail_pdf_already_open": "Il file PDF è già aperto in una finestra separata.",
"settings_danger_debugger_protection_label": "Abilita protezione da debugger",
"settings_danger_debugger_protection_hint": "Questo impedirà che il debug dell'app venga eseguito da un debugger collegato.",
- "settings_danger_debugger_protection_info": "Info: Questa azione non è attualmente configurabile ed è sempre abilitata per le build private."
+ "settings_danger_debugger_protection_info": "Info: Questa azione non è attualmente configurabile ed è sempre abilitata per le build private.",
+ "bugreport_title": "Segnala un Bug",
+ "bugreport_description": "Descrivi cosa stavi facendo quando si è verificato il bug e cosa ti aspettavi che accadesse.",
+ "bugreport_name_label": "Nome",
+ "bugreport_name_placeholder": "Il tuo nome",
+ "bugreport_email_label": "Email",
+ "bugreport_email_placeholder": "tua.email@esempio.com",
+ "bugreport_text_label": "Descrizione del Bug",
+ "bugreport_text_placeholder": "Descrivi il bug in dettaglio...",
+ "bugreport_info": "Il tuo messaggio, il file email (se caricato), lo screenshot e le informazioni di sistema saranno inclusi nella segnalazione.",
+ "bugreport_screenshot_label": "Screenshot Allegato:",
+ "bugreport_cancel": "Annulla",
+ "bugreport_submit": "Invia Segnalazione",
+ "bugreport_submitting": "Creazione segnalazione...",
+ "bugreport_success_title": "Segnalazione Bug Creata",
+ "bugreport_success_message": "La tua segnalazione bug è stata salvata in:",
+ "bugreport_copy_path": "Copia Percorso",
+ "bugreport_open_folder": "Apri Cartella",
+ "bugreport_close": "Chiudi",
+ "bugreport_error": "Impossibile creare la segnalazione bug.",
+ "bugreport_copied": "Percorso copiato negli appunti!"
}
diff --git a/frontend/package.json b/frontend/package.json
index 66a27fb..a4702d7 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -36,7 +36,9 @@
"vite-plugin-devtools-json": "^1.0.0"
},
"dependencies": {
+ "@types/html2canvas": "^1.0.0",
"dompurify": "^3.3.1",
+ "html2canvas": "^1.4.1",
"pdfjs-dist": "^5.4.624",
"svelte-flags": "^3.0.1",
"svelte-sonner": "^1.0.7"
diff --git a/frontend/src/lib/components/MailViewer.svelte b/frontend/src/lib/components/MailViewer.svelte
index 2bc34f6..bd07687 100644
--- a/frontend/src/lib/components/MailViewer.svelte
+++ b/frontend/src/lib/components/MailViewer.svelte
@@ -1,6 +1,6 @@
+
+
{resultZipPath}
+