Compare commits
1 Commits
main
...
webview-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a343769e5 |
49
app_mail.go
49
app_mail.go
@@ -3,7 +3,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emly/backend/utils/mail"
|
internal "emly/backend/utils/mail"
|
||||||
)
|
)
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -86,3 +86,50 @@ func (a *App) ReadMSGOSS(filePath string) (*internal.EmailData, error) {
|
|||||||
func (a *App) ShowOpenFileDialog() (string, error) {
|
func (a *App) ShowOpenFileDialog() (string, error) {
|
||||||
return internal.ShowFileDialog(a.ctx)
|
return internal.ShowFileDialog(a.ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) ShowOpenFolderDialog() (string, error) {
|
||||||
|
return internal.ShowFolderDialog(a.ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveAttachment saves an attachment to the configured download folder.
|
||||||
|
// Uses EXPORT_ATTACHMENT_FOLDER from config.ini if set,
|
||||||
|
// otherwise falls back to WEBVIEW2_DOWNLOAD_PATH, then to default Downloads folder.
|
||||||
|
// After saving, opens Windows Explorer to show the saved file.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - filename: The name to save the file as
|
||||||
|
// - base64Data: The base64-encoded attachment data
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - string: The full path where the file was saved
|
||||||
|
// - error: Any file system errors
|
||||||
|
func (a *App) SaveAttachment(filename string, base64Data string) (string, error) {
|
||||||
|
// Try to get configured export folder first
|
||||||
|
folderPath := a.GetExportAttachmentFolder()
|
||||||
|
|
||||||
|
// If not set, try to get WEBVIEW2_DOWNLOAD_PATH from config
|
||||||
|
if folderPath == "" {
|
||||||
|
config := a.GetConfig()
|
||||||
|
if config != nil && config.EMLy.WebView2DownloadPath != "" {
|
||||||
|
folderPath = config.EMLy.WebView2DownloadPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
savedPath, err := internal.SaveAttachmentToFolder(filename, base64Data, folderPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return savedPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenExplorerForPath opens Windows Explorer to show the specified file or folder.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - path: The full path to open in Explorer
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - error: Any execution errors
|
||||||
|
func (a *App) OpenExplorerForPath(path string) error {
|
||||||
|
return internal.OpenFileExplorer(path)
|
||||||
|
}
|
||||||
|
|||||||
@@ -128,3 +128,41 @@ func (a *App) SetUpdateCheckerEnabled(enabled bool) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetExportAttachmentFolder updates the EXPORT_ATTACHMENT_FOLDER setting in config.ini
|
||||||
|
// based on the user's preference from the GUI settings.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - folderPath: The path to the folder where attachments should be exported
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - error: Error if loading or saving config fails
|
||||||
|
func (a *App) SetExportAttachmentFolder(folderPath string) error {
|
||||||
|
// Load current config
|
||||||
|
config := a.GetConfig()
|
||||||
|
if config == nil {
|
||||||
|
return fmt.Errorf("failed to load config")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the setting
|
||||||
|
config.EMLy.ExportAttachmentFolder = folderPath
|
||||||
|
|
||||||
|
// Save config back to disk
|
||||||
|
if err := a.SaveConfig(config); err != nil {
|
||||||
|
return fmt.Errorf("failed to save config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExportAttachmentFolder returns the EXPORT_ATTACHMENT_FOLDER setting from config.ini
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - string: The path to the export folder, or empty string if not set
|
||||||
|
func (a *App) GetExportAttachmentFolder() string {
|
||||||
|
config := a.GetConfig()
|
||||||
|
if config == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return config.EMLy.ExportAttachmentFolder
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ type EMLyConfig struct {
|
|||||||
UpdateCheckEnabled string `ini:"UPDATE_CHECK_ENABLED"`
|
UpdateCheckEnabled string `ini:"UPDATE_CHECK_ENABLED"`
|
||||||
UpdatePath string `ini:"UPDATE_PATH"`
|
UpdatePath string `ini:"UPDATE_PATH"`
|
||||||
UpdateAutoCheck string `ini:"UPDATE_AUTO_CHECK"`
|
UpdateAutoCheck string `ini:"UPDATE_AUTO_CHECK"`
|
||||||
|
WebView2UserDataPath string `ini:"WEBVIEW2_USERDATA_PATH"`
|
||||||
|
WebView2DownloadPath string `ini:"WEBVIEW2_DOWNLOAD_PATH"`
|
||||||
|
ExportAttachmentFolder string `ini:"EXPORT_ATTACHMENT_FOLDER"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig reads the config.ini file at the given path and returns a Config struct
|
// LoadConfig reads the config.ini file at the given path and returns a Config struct
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
)
|
)
|
||||||
@@ -16,6 +23,14 @@ var EmailDialogOptions = runtime.OpenDialogOptions{
|
|||||||
ShowHiddenFiles: false,
|
ShowHiddenFiles: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var FolderDialogOptions = runtime.OpenDialogOptions{
|
||||||
|
Title: "Select Folder",
|
||||||
|
Filters: []runtime.FileFilter{
|
||||||
|
{DisplayName: "Folders", Pattern: "*"},
|
||||||
|
},
|
||||||
|
ShowHiddenFiles: false,
|
||||||
|
}
|
||||||
|
|
||||||
func ShowFileDialog(ctx context.Context) (string, error) {
|
func ShowFileDialog(ctx context.Context) (string, error) {
|
||||||
filePath, err := runtime.OpenFileDialog(ctx, EmailDialogOptions)
|
filePath, err := runtime.OpenFileDialog(ctx, EmailDialogOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -23,3 +38,86 @@ func ShowFileDialog(ctx context.Context) (string, error) {
|
|||||||
}
|
}
|
||||||
return filePath, nil
|
return filePath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ShowFolderDialog(ctx context.Context) (string, error) {
|
||||||
|
folderPath, err := runtime.OpenDirectoryDialog(ctx, FolderDialogOptions)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return folderPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveAttachmentToFolder saves a base64-encoded attachment to the specified folder.
|
||||||
|
// If folderPath is empty, uses the user's Downloads folder as default.
|
||||||
|
// Expands environment variables in the format %%VAR%% or %VAR%.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - filename: The name to save the file as
|
||||||
|
// - base64Data: The base64-encoded file content
|
||||||
|
// - folderPath: Optional custom folder path (uses Downloads if empty)
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - string: The full path where the file was saved
|
||||||
|
// - error: Any file system or decoding errors
|
||||||
|
func SaveAttachmentToFolder(filename string, base64Data string, folderPath string) (string, error) {
|
||||||
|
// Decode base64 data
|
||||||
|
data, err := base64.StdEncoding.DecodeString(base64Data)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to decode attachment data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use configured folder or default to Downloads
|
||||||
|
targetFolder := folderPath
|
||||||
|
if targetFolder == "" {
|
||||||
|
targetFolder = filepath.Join(os.Getenv("USERPROFILE"), "Downloads")
|
||||||
|
} else {
|
||||||
|
// Expand environment variables (%%VAR%% or %VAR% format)
|
||||||
|
re := regexp.MustCompile(`%%([^%]+)%%|%([^%]+)%`)
|
||||||
|
targetFolder = re.ReplaceAllStringFunc(targetFolder, func(match string) string {
|
||||||
|
varName := strings.Trim(match, "%")
|
||||||
|
return os.Getenv(varName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the target folder exists
|
||||||
|
if err := os.MkdirAll(targetFolder, 0755); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create target folder: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create full path
|
||||||
|
fullPath := filepath.Join(targetFolder, filename)
|
||||||
|
|
||||||
|
// Save the file
|
||||||
|
if err := os.WriteFile(fullPath, data, 0644); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to save attachment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenFileExplorer opens Windows Explorer and selects the specified file.
|
||||||
|
// Uses the /select parameter to highlight the file in Explorer.
|
||||||
|
// If the path is a directory, opens the directory without selecting anything.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - filePath: The full path to the file or directory to open in Explorer
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - error: Any execution errors
|
||||||
|
func OpenFileExplorer(filePath string) error {
|
||||||
|
// Check if path is a directory or file
|
||||||
|
info, err := os.Stat(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to stat path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
// Open directory
|
||||||
|
cmd := exec.Command("explorer.exe", filePath)
|
||||||
|
return cmd.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open and select file
|
||||||
|
cmd := exec.Command("explorer.exe", "/select,", filePath)
|
||||||
|
return cmd.Start()
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,3 +7,6 @@ LANGUAGE = it
|
|||||||
UPDATE_CHECK_ENABLED = false
|
UPDATE_CHECK_ENABLED = false
|
||||||
UPDATE_PATH =
|
UPDATE_PATH =
|
||||||
UPDATE_AUTO_CHECK = true
|
UPDATE_AUTO_CHECK = true
|
||||||
|
WEBVIEW2_USERDATA_PATH =
|
||||||
|
WEBVIEW2_DOWNLOAD_PATH = %%USERPROFILE%%\Documents\EMLy_Attachments
|
||||||
|
EXPORT_ATTACHMENT_FOLDER =
|
||||||
|
|||||||
@@ -218,5 +218,11 @@
|
|||||||
"pdf_error_no_data_desc": "No PDF data provided. Please open this window from the main EMLy application.",
|
"pdf_error_no_data_desc": "No PDF data provided. Please open this window from the main EMLy application.",
|
||||||
"pdf_error_timeout": "Timeout loading PDF. The worker might have failed to initialize.",
|
"pdf_error_timeout": "Timeout loading PDF. The worker might have failed to initialize.",
|
||||||
"pdf_error_parsing": "Error parsing PDF: ",
|
"pdf_error_parsing": "Error parsing PDF: ",
|
||||||
"pdf_error_rendering": "Error rendering page: "
|
"pdf_error_rendering": "Error rendering page: ",
|
||||||
|
"settings_custom_download_label": "Custom Attachment Download",
|
||||||
|
"settings_custom_download_hint": "Save attachments to a custom folder and open Explorer automatically",
|
||||||
|
"settings_custom_download_info": "Info: When enabled, attachments will be saved to the folder configured below (or WEBVIEW2_DOWNLOAD_PATH if not set) and Windows Explorer will open to show the file. When disabled, uses browser's default download behavior.",
|
||||||
|
"settings_export_folder_label": "Select a folder to save exported attachments",
|
||||||
|
"settings_export_folder_hint": "Choose a default location for saving attachments that you export from emails (instead of the Downloads folder)",
|
||||||
|
"settings_select_folder_button": "Select folder"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,6 +218,12 @@
|
|||||||
"pdf_error_rendering": "Errore nel rendering della pagina: ",
|
"pdf_error_rendering": "Errore nel rendering della pagina: ",
|
||||||
"mail_download_btn_label": "Scarica",
|
"mail_download_btn_label": "Scarica",
|
||||||
"mail_download_btn_title": "Scarica",
|
"mail_download_btn_title": "Scarica",
|
||||||
"mail_download_btn_text": "Scarica"
|
"mail_download_btn_text": "Scarica",
|
||||||
|
"settings_custom_download_label": "Download Personalizzato Allegati",
|
||||||
|
"settings_custom_download_hint": "Salva gli allegati in una cartella personalizzata e apri automaticamente Esplora Risorse",
|
||||||
|
"settings_custom_download_info": "Info: Quando abilitato, gli allegati verranno salvati nella cartella configurata di seguito (o WEBVIEW2_DOWNLOAD_PATH se non impostata) e Windows Explorer si aprirà per mostrare il file. Quando disabilitato, usa il comportamento di download predefinito del browser.",
|
||||||
|
"settings_export_folder_label": "Seleziona una cartella per salvare gli allegati esportati",
|
||||||
|
"settings_export_folder_hint": "Scegli una posizione predefinita per salvare gli allegati che esporti dalle email (invece della cartella Download)",
|
||||||
|
"settings_select_folder_button": "Seleziona cartella"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { EventsOn, WindowShow, WindowUnminimise } from '$lib/wailsjs/runtime/runtime';
|
import { EventsOn, WindowShow, WindowUnminimise } from '$lib/wailsjs/runtime/runtime';
|
||||||
|
import { SaveAttachment, OpenExplorerForPath } from '$lib/wailsjs/go/main/App';
|
||||||
import { mailState } from '$lib/stores/mail-state.svelte';
|
import { mailState } from '$lib/stores/mail-state.svelte';
|
||||||
import * as m from '$lib/paraglide/messages';
|
import * as m from '$lib/paraglide/messages';
|
||||||
import { dev } from '$app/environment';
|
import { dev } from '$app/environment';
|
||||||
@@ -61,19 +62,41 @@
|
|||||||
mailState.clear();
|
mailState.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDownloadAttachments() {
|
async function onDownloadAttachments() {
|
||||||
if (!mailState.currentEmail || !mailState.currentEmail.attachments) return;
|
if (!mailState.currentEmail || !mailState.currentEmail.attachments) return;
|
||||||
|
|
||||||
mailState.currentEmail.attachments.forEach((att) => {
|
// Check if custom download behavior is enabled
|
||||||
const base64 = arrayBufferToBase64(att.data);
|
const useCustomDownload = settingsStore.settings.useCustomAttachmentDownload ?? false;
|
||||||
const dataUrl = createDataUrl(att.contentType, base64);
|
|
||||||
const link = document.createElement('a');
|
if (useCustomDownload) {
|
||||||
link.href = dataUrl;
|
// Use backend SaveAttachment (saves to configured folder and opens Explorer)
|
||||||
link.download = att.filename;
|
try {
|
||||||
document.body.appendChild(link);
|
let lastSavedPath = '';
|
||||||
link.click();
|
for (const att of mailState.currentEmail.attachments) {
|
||||||
document.body.removeChild(link);
|
const base64 = arrayBufferToBase64(att.data);
|
||||||
});
|
lastSavedPath = await SaveAttachment(att.filename, base64);
|
||||||
|
toast.success(`Saved: ${att.filename}`);
|
||||||
|
}
|
||||||
|
// Open Explorer to show the folder where files were saved
|
||||||
|
if (lastSavedPath) {
|
||||||
|
await OpenExplorerForPath(lastSavedPath);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(`Failed to save attachments: ${err}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use browser default download (downloads to browser's default folder)
|
||||||
|
mailState.currentEmail.attachments.forEach((att) => {
|
||||||
|
const base64 = arrayBufferToBase64(att.data);
|
||||||
|
const dataUrl = createDataUrl(att.contentType, base64);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = dataUrl;
|
||||||
|
link.download = att.filename;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onOpenMail() {
|
async function onOpenMail() {
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ const defaults: EMLy_GUI_Settings = {
|
|||||||
reduceMotion: false,
|
reduceMotion: false,
|
||||||
theme: "dark",
|
theme: "dark",
|
||||||
increaseWindowButtonsContrast: false,
|
increaseWindowButtonsContrast: false,
|
||||||
|
exportAttachmentFolder: "",
|
||||||
|
useCustomAttachmentDownload: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
class SettingsStore {
|
class SettingsStore {
|
||||||
|
|||||||
12
frontend/src/lib/types.d.ts
vendored
12
frontend/src/lib/types.d.ts
vendored
@@ -5,15 +5,17 @@ type SupportedFileTypePreview = "jpg" | "jpeg" | "png";
|
|||||||
interface EMLy_GUI_Settings {
|
interface EMLy_GUI_Settings {
|
||||||
selectedLanguage: SupportedLanguages = "en" | "it";
|
selectedLanguage: SupportedLanguages = "en" | "it";
|
||||||
useBuiltinPreview: boolean;
|
useBuiltinPreview: boolean;
|
||||||
useBuiltinPDFViewer?: boolean;
|
useBuiltinPDFViewer: boolean;
|
||||||
previewFileSupportedTypes?: SupportedFileTypePreview[];
|
previewFileSupportedTypes: SupportedFileTypePreview[];
|
||||||
enableAttachedDebuggerProtection?: boolean;
|
enableAttachedDebuggerProtection: boolean;
|
||||||
useDarkEmailViewer?: boolean;
|
useDarkEmailViewer?: boolean;
|
||||||
enableUpdateChecker?: boolean;
|
enableUpdateChecker?: boolean;
|
||||||
musicInspirationEnabled?: boolean;
|
musicInspirationEnabled?: boolean;
|
||||||
reduceMotion?: boolean;
|
reduceMotion?: boolean;
|
||||||
theme?: "light" | "dark";
|
theme: "light" | "dark";
|
||||||
increaseWindowButtonsContrast?: boolean;
|
increaseWindowButtonsContrast: boolean;
|
||||||
|
exportAttachmentFolder?: string;
|
||||||
|
useCustomAttachmentDownload?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SupportedLanguages = "en" | "it";
|
type SupportedLanguages = "en" | "it";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { Label } from "$lib/components/ui/label";
|
import { Label } from "$lib/components/ui/label";
|
||||||
import { Separator } from "$lib/components/ui/separator";
|
import { Separator } from "$lib/components/ui/separator";
|
||||||
import { Switch } from "$lib/components/ui/switch";
|
import { Switch } from "$lib/components/ui/switch";
|
||||||
import { ChevronLeft, Flame, Download, Upload, RefreshCw, CheckCircle2, AlertCircle, Sun, Moon } from "@lucide/svelte";
|
import { ChevronLeft, Flame, Download, Upload, RefreshCw, CheckCircle2, AlertCircle, Sun, Moon, FolderArchive } from "@lucide/svelte";
|
||||||
import type { EMLy_GUI_Settings } from "$lib/types";
|
import type { EMLy_GUI_Settings } from "$lib/types";
|
||||||
import { toast } from "svelte-sonner";
|
import { toast } from "svelte-sonner";
|
||||||
import { It, Us } from "svelte-flags";
|
import { It, Us } from "svelte-flags";
|
||||||
@@ -25,8 +25,9 @@
|
|||||||
import { setLocale } from "$lib/paraglide/runtime";
|
import { setLocale } from "$lib/paraglide/runtime";
|
||||||
import { mailState } from "$lib/stores/mail-state.svelte.js";
|
import { mailState } from "$lib/stores/mail-state.svelte.js";
|
||||||
import { dev } from '$app/environment';
|
import { dev } from '$app/environment';
|
||||||
import { ExportSettings, ImportSettings, CheckForUpdates, DownloadUpdate, InstallUpdate, GetUpdateStatus, SetUpdateCheckerEnabled } from "$lib/wailsjs/go/main/App";
|
import { ExportSettings, ImportSettings, CheckForUpdates, DownloadUpdate, InstallUpdate, SetUpdateCheckerEnabled, ShowOpenFolderDialog, GetExportAttachmentFolder, SetExportAttachmentFolder } from "$lib/wailsjs/go/main/App";
|
||||||
import { EventsOn, EventsOff } from "$lib/wailsjs/runtime/runtime";
|
import { EventsOn, EventsOff } from "$lib/wailsjs/runtime/runtime";
|
||||||
|
import Input from "$lib/components/ui/input/input.svelte";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
let config = $derived(data.config);
|
let config = $derived(data.config);
|
||||||
@@ -44,6 +45,8 @@
|
|||||||
reduceMotion: false,
|
reduceMotion: false,
|
||||||
theme: "dark",
|
theme: "dark",
|
||||||
increaseWindowButtonsContrast: false,
|
increaseWindowButtonsContrast: false,
|
||||||
|
exportAttachmentFolder: "",
|
||||||
|
useCustomAttachmentDownload: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function setLanguage(
|
async function setLanguage(
|
||||||
@@ -82,6 +85,8 @@
|
|||||||
reduceMotion: s.reduceMotion ?? defaults.reduceMotion ?? false,
|
reduceMotion: s.reduceMotion ?? defaults.reduceMotion ?? false,
|
||||||
theme: s.theme || defaults.theme || "light",
|
theme: s.theme || defaults.theme || "light",
|
||||||
increaseWindowButtonsContrast: s.increaseWindowButtonsContrast ?? defaults.increaseWindowButtonsContrast ?? false,
|
increaseWindowButtonsContrast: s.increaseWindowButtonsContrast ?? defaults.increaseWindowButtonsContrast ?? false,
|
||||||
|
exportAttachmentFolder: s.exportAttachmentFolder || defaults.exportAttachmentFolder || "",
|
||||||
|
useCustomAttachmentDownload: s.useCustomAttachmentDownload ?? defaults.useCustomAttachmentDownload ?? false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +99,8 @@
|
|||||||
!!a.useDarkEmailViewer === !!b.useDarkEmailViewer &&
|
!!a.useDarkEmailViewer === !!b.useDarkEmailViewer &&
|
||||||
!!a.enableUpdateChecker === !!b.enableUpdateChecker &&
|
!!a.enableUpdateChecker === !!b.enableUpdateChecker &&
|
||||||
!!a.reduceMotion === !!b.reduceMotion &&
|
!!a.reduceMotion === !!b.reduceMotion &&
|
||||||
|
!!a.exportAttachmentFolder === !!b.exportAttachmentFolder &&
|
||||||
|
!!a.useCustomAttachmentDownload === !!b.useCustomAttachmentDownload &&
|
||||||
(a.theme ?? "light") === (b.theme ?? "light") &&
|
(a.theme ?? "light") === (b.theme ?? "light") &&
|
||||||
!!a.increaseWindowButtonsContrast === !!b.increaseWindowButtonsContrast &&
|
!!a.increaseWindowButtonsContrast === !!b.increaseWindowButtonsContrast &&
|
||||||
JSON.stringify(a.previewFileSupportedTypes?.sort()) ===
|
JSON.stringify(a.previewFileSupportedTypes?.sort()) ===
|
||||||
@@ -142,6 +149,7 @@
|
|||||||
sessionStorage.removeItem("debugWindowInSettings");
|
sessionStorage.removeItem("debugWindowInSettings");
|
||||||
dangerZoneEnabled.set(false);
|
dangerZoneEnabled.set(false);
|
||||||
LogDebug("Reset danger zone setting to false.");
|
LogDebug("Reset danger zone setting to false.");
|
||||||
|
await SetExportAttachmentFolder("");
|
||||||
} catch {
|
} catch {
|
||||||
toast.error(m.settings_toast_reset_failed());
|
toast.error(m.settings_toast_reset_failed());
|
||||||
return;
|
return;
|
||||||
@@ -195,10 +203,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Sync update checker setting to backend config.ini
|
// Sync update checker setting to backend config.ini
|
||||||
let previousUpdateCheckerEnabled = form.enableUpdateChecker;
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (!browser) return;
|
if (!browser) return;
|
||||||
|
let previousUpdateCheckerEnabled = form.enableUpdateChecker;
|
||||||
if (form.enableUpdateChecker !== previousUpdateCheckerEnabled) {
|
if (form.enableUpdateChecker !== previousUpdateCheckerEnabled) {
|
||||||
try {
|
try {
|
||||||
await SetUpdateCheckerEnabled(form.enableUpdateChecker ?? true);
|
await SetUpdateCheckerEnabled(form.enableUpdateChecker ?? true);
|
||||||
@@ -221,6 +229,52 @@
|
|||||||
previousTheme = form.theme;
|
previousTheme = form.theme;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load export attachment folder from config.ini on startup
|
||||||
|
$effect(() => {
|
||||||
|
if (!browser) return;
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const configFolder = await GetExportAttachmentFolder();
|
||||||
|
if (configFolder && configFolder.trim() !== "") {
|
||||||
|
form.exportAttachmentFolder = configFolder;
|
||||||
|
// Also update lastSaved to avoid triggering unsaved changes
|
||||||
|
lastSaved = { ...lastSaved, exportAttachmentFolder: configFolder };
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load export folder from config:", err);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function openFolderDialog(): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const result = await ShowOpenFolderDialog();
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to open folder dialog:", err);
|
||||||
|
toast.error("Failed to open folder dialog.");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectExportFolder() {
|
||||||
|
const folder = await openFolderDialog();
|
||||||
|
if (folder) {
|
||||||
|
// Save to form state
|
||||||
|
form.exportAttachmentFolder = folder;
|
||||||
|
// Save to config.ini
|
||||||
|
try {
|
||||||
|
await SetExportAttachmentFolder(folder);
|
||||||
|
toast.success("Export folder updated!");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to save export folder:", err);
|
||||||
|
toast.error("Failed to save export folder to config.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function exportSettings() {
|
async function exportSettings() {
|
||||||
try {
|
try {
|
||||||
const settingsJSON = JSON.stringify(form, null, 2);
|
const settingsJSON = JSON.stringify(form, null, 2);
|
||||||
@@ -344,7 +398,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="min-h-[calc(100vh-1rem)] bg-gradient-to-b from-background to-muted/30">
|
<div class="min-h-[calc(100vh-1rem)] bg-linear-to-b from-background to-muted/30">
|
||||||
<div
|
<div
|
||||||
class="mx-auto flex max-w-3xl flex-col gap-4 px-4 py-6 sm:px-6 sm:py-10 opacity-80"
|
class="mx-auto flex max-w-3xl flex-col gap-4 px-4 py-6 sm:px-6 sm:py-10 opacity-80"
|
||||||
>
|
>
|
||||||
@@ -692,6 +746,61 @@
|
|||||||
{m.settings_preview_pdf_builtin_info()}
|
{m.settings_preview_pdf_builtin_info()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="flex items-center justify-between gap-4 rounded-lg border bg-card p-4">
|
||||||
|
<div>
|
||||||
|
<div class="font-medium">{m.settings_custom_download_label()}</div>
|
||||||
|
<div class="text-sm text-muted-foreground">
|
||||||
|
{m.settings_custom_download_hint()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
id="use-custom-attachment-download"
|
||||||
|
bind:checked={form.useCustomAttachmentDownload}
|
||||||
|
class="cursor-pointer hover:cursor-pointer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-muted-foreground mt-2">
|
||||||
|
{m.settings_custom_download_info()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if form.useCustomAttachmentDownload}
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="rounded-lg border bg-card p-4 space-y-3">
|
||||||
|
<div>
|
||||||
|
<div class="font-medium">
|
||||||
|
{m.settings_export_folder_label()}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-muted-foreground">
|
||||||
|
{m.settings_export_folder_hint()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="%USERPROFILE%\Documents\EMLy_Attachments"
|
||||||
|
class="flex-1"
|
||||||
|
readonly
|
||||||
|
bind:value={form.exportAttachmentFolder}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
class="cursor-pointer hover:cursor-pointer"
|
||||||
|
onclick={selectExportFolder}
|
||||||
|
>
|
||||||
|
<FolderArchive class="size-4 mr-2" />
|
||||||
|
{m.settings_select_folder_button()}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
|
|
||||||
|
|||||||
56
main.go
56
main.go
@@ -4,11 +4,14 @@ import (
|
|||||||
"embed"
|
"embed"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2"
|
"github.com/wailsapp/wails/v2"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
||||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +36,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer CloseLogger()
|
defer CloseLogger()
|
||||||
|
|
||||||
|
// Load config.ini to get WebView2 paths
|
||||||
|
configPath := filepath.Join(filepath.Dir(os.Args[0]), "config.ini")
|
||||||
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||||
|
configPath = "config.ini" // fallback to current directory
|
||||||
|
}
|
||||||
|
|
||||||
// Check for custom args
|
// Check for custom args
|
||||||
args := os.Args
|
args := os.Args
|
||||||
uniqueId := "emly-app-lock"
|
uniqueId := "emly-app-lock"
|
||||||
@@ -74,6 +83,49 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create application with options
|
// Create application with options
|
||||||
|
// Configure WebView2 DataPath (user data folder)
|
||||||
|
userDataPath := filepath.Join(os.Getenv("APPDATA"), "EMLy") // default
|
||||||
|
downloadPath := filepath.Join(os.Getenv("USERPROFILE"), "Downloads") // default
|
||||||
|
|
||||||
|
// Helper function to expand Windows-style environment variables
|
||||||
|
expandEnvVars := func(path string) string {
|
||||||
|
// Match %%VAR%% or %VAR% patterns and replace with actual values
|
||||||
|
re := regexp.MustCompile(`%%([^%]+)%%|%([^%]+)%`)
|
||||||
|
return re.ReplaceAllStringFunc(path, func(match string) string {
|
||||||
|
varName := strings.Trim(match, "%")
|
||||||
|
return os.Getenv(varName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load paths from config.ini if available
|
||||||
|
if cfg, err := os.ReadFile(configPath); err == nil {
|
||||||
|
// Simple INI parsing for these specific values
|
||||||
|
lines := strings.Split(string(cfg), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if strings.HasPrefix(line, "WEBVIEW2_USERDATA_PATH") {
|
||||||
|
parts := strings.SplitN(line, "=", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
path := strings.TrimSpace(parts[1])
|
||||||
|
if path != "" {
|
||||||
|
userDataPath = expandEnvVars(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(line, "WEBVIEW2_DOWNLOAD_PATH") {
|
||||||
|
parts := strings.SplitN(line, "=", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
path := strings.TrimSpace(parts[1])
|
||||||
|
if path != "" {
|
||||||
|
downloadPath = expandEnvVars(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("WebView2 UserDataPath: %s", userDataPath)
|
||||||
|
log.Printf("WebView2 DownloadPath: %s", downloadPath)
|
||||||
|
|
||||||
err := wails.Run(&options.App{
|
err := wails.Run(&options.App{
|
||||||
Title: windowTitle,
|
Title: windowTitle,
|
||||||
Width: windowWidth,
|
Width: windowWidth,
|
||||||
@@ -94,6 +146,10 @@ func main() {
|
|||||||
MinWidth: 964,
|
MinWidth: 964,
|
||||||
MinHeight: 690,
|
MinHeight: 690,
|
||||||
Frameless: frameless,
|
Frameless: frameless,
|
||||||
|
Windows: &windows.Options{
|
||||||
|
WebviewUserDataPath: userDataPath,
|
||||||
|
WebviewBrowserPath: "", // Empty = use system Edge WebView2
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user