Compare commits
3 Commits
ea43cd715a
...
f1d603cc45
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1d603cc45 | ||
|
|
e9500209a8 | ||
|
|
44ee69051d |
@@ -55,6 +55,9 @@ Email parsing lives in `backend/utils/mail/`:
|
|||||||
- `msg_reader.go` - Microsoft MSG (CFB format) parsing
|
- `msg_reader.go` - Microsoft MSG (CFB format) parsing
|
||||||
- `mailparser.go` - MIME multipart handling
|
- `mailparser.go` - MIME multipart handling
|
||||||
|
|
||||||
|
For any major change to backend functionality, add a new method to `App` and implement it in a new `app_*.go` file for organization.
|
||||||
|
And update the DOCUMENTATION.md file in the root of the repository with a brief description of the new method and its purpose.
|
||||||
|
|
||||||
### Frontend Structure (SvelteKit + Svelte 5)
|
### Frontend Structure (SvelteKit + Svelte 5)
|
||||||
|
|
||||||
**Routes** (file-based routing):
|
**Routes** (file-based routing):
|
||||||
|
|||||||
@@ -144,3 +144,16 @@ func (a *App) OpenFolderInExplorer(folderPath string) error {
|
|||||||
cmd := exec.Command("explorer", folderPath)
|
cmd := exec.Command("explorer", folderPath)
|
||||||
return cmd.Start()
|
return cmd.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenURLInBrowser opens the specified URL in the system's default web browser.
|
||||||
|
// Uses the Windows "start" command to launch the default browser.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - url: The URL to open (must be a valid http/https URL)
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - error: Error if launching the browser fails
|
||||||
|
func (a *App) OpenURLInBrowser(url string) error {
|
||||||
|
cmd := exec.Command("cmd", "/c", "start", "", url)
|
||||||
|
return cmd.Start()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[EMLy]
|
[EMLy]
|
||||||
SDK_DECODER_SEMVER="1.3.1"
|
SDK_DECODER_SEMVER="1.3.2"
|
||||||
SDK_DECODER_RELEASE_CHANNEL="beta"
|
SDK_DECODER_RELEASE_CHANNEL="beta"
|
||||||
GUI_SEMVER="1.3.1"
|
GUI_SEMVER="1.4.0"
|
||||||
GUI_RELEASE_CHANNEL="beta"
|
GUI_RELEASE_CHANNEL="stable"
|
||||||
LANGUAGE="it"
|
LANGUAGE="it"
|
||||||
@@ -120,5 +120,45 @@
|
|||||||
"settings_export_error": "Failed to export settings.",
|
"settings_export_error": "Failed to export settings.",
|
||||||
"settings_import_success": "Settings imported successfully!",
|
"settings_import_success": "Settings imported successfully!",
|
||||||
"settings_import_error": "Failed to import settings.",
|
"settings_import_error": "Failed to import settings.",
|
||||||
"settings_import_invalid": "Invalid settings file."
|
"settings_import_invalid": "Invalid settings file.",
|
||||||
|
"settings_email_dark_viewer_label": "Dark theme for email content",
|
||||||
|
"settings_email_dark_viewer_hint": "Display email body with a dark background matching the app theme.",
|
||||||
|
"settings_email_dark_viewer_info": "Info: When disabled, emails will display with their original light background. Some emails may be designed for light backgrounds and look better with this disabled.",
|
||||||
|
"sidebar_credits": "Credits",
|
||||||
|
"credits_title": "Credits",
|
||||||
|
"credits_description": "Acknowledgments and attributions for EMLy.",
|
||||||
|
"credits_about_title": "About EMLy",
|
||||||
|
"credits_about_description": "\"A slick app that somehow still works, with a badass UI that makes reading emails almost enjoyable.\"",
|
||||||
|
"credits_about_description_2": " -Someone who clearly hasn't seen the codebase",
|
||||||
|
"credits_app_tagline": "EML & MSG Viewer for Windows",
|
||||||
|
"credits_app_description": "EMLy is a lightweight, modern desktop application designed to view .eml and .msg email files. Built with performance and usability in mind, it provides a clean interface for reading emails, viewing attachments, and handling Italian PEC certified emails.",
|
||||||
|
"credits_team_title": "Development Team",
|
||||||
|
"credits_team_description": "The people behind EMLy.",
|
||||||
|
"credits_role_lead_developer": "Lead Developer",
|
||||||
|
"credits_role_senior_developer": "Senior Developer",
|
||||||
|
"credits_foisx_desc": "Creator and maintainer of EMLy. Responsible for architecture, development, and design.",
|
||||||
|
"credits_laky64_desc": "Implemented the custom MSG file parser in Go for native Outlook message support.",
|
||||||
|
"credits_special_thanks_title": "Special Thanks",
|
||||||
|
"credits_special_thanks_description": "Contributors who helped make EMLy better in a significant way.",
|
||||||
|
"credits_made_with": "Made with",
|
||||||
|
"credits_at_3git": "at 3gIT",
|
||||||
|
"credits_tech_title": "Built With",
|
||||||
|
"credits_tech_description": "Core technologies powering EMLy.",
|
||||||
|
"credits_tech_wails": "Desktop application framework for Go",
|
||||||
|
"credits_tech_go": "Backend programming language",
|
||||||
|
"credits_tech_sveltekit": "Frontend application framework",
|
||||||
|
"credits_tech_svelte": "Reactive UI framework",
|
||||||
|
"credits_tech_typescript": "Type-safe JavaScript",
|
||||||
|
"credits_tech_tailwind": "Utility-first CSS framework",
|
||||||
|
"credits_libraries_title": "Libraries & Packages",
|
||||||
|
"credits_libraries_description": "Open source packages that make EMLy possible.",
|
||||||
|
"credits_lib_shadcn": "Beautiful UI components for Svelte",
|
||||||
|
"credits_lib_lucide": "Beautiful & consistent icon set",
|
||||||
|
"credits_lib_paraglide": "Type-safe internationalization",
|
||||||
|
"credits_lib_sonner": "Toast notifications for Svelte",
|
||||||
|
"credits_lib_pdfjs": "PDF rendering library by Mozilla",
|
||||||
|
"credits_lib_dompurify": "XSS sanitizer for HTML content",
|
||||||
|
"credits_license_title": "License & Source",
|
||||||
|
"credits_license_text": "EMLy is proprietary software developed by 3gIT. All rights reserved. The application uses various open source libraries, each governed by their respective licenses.",
|
||||||
|
"credits_copyright": "All rights reserved."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,5 +120,45 @@
|
|||||||
"settings_export_error": "Impossibile esportare le impostazioni.",
|
"settings_export_error": "Impossibile esportare le impostazioni.",
|
||||||
"settings_import_success": "Impostazioni importate con successo!",
|
"settings_import_success": "Impostazioni importate con successo!",
|
||||||
"settings_import_error": "Impossibile importare le impostazioni.",
|
"settings_import_error": "Impossibile importare le impostazioni.",
|
||||||
"settings_import_invalid": "File impostazioni non valido."
|
"settings_import_invalid": "File impostazioni non valido.",
|
||||||
|
"settings_email_dark_viewer_label": "Tema scuro per contenuto email",
|
||||||
|
"settings_email_dark_viewer_hint": "Visualizza il corpo dell'email con uno sfondo scuro che corrisponde al tema dell'app.",
|
||||||
|
"settings_email_dark_viewer_info": "Info: Quando disabilitato, le email verranno visualizzate con lo sfondo chiaro originale. Alcune email potrebbero essere progettate per sfondi chiari e apparire meglio con questa opzione disabilitata.",
|
||||||
|
"sidebar_credits": "Crediti",
|
||||||
|
"credits_title": "Crediti",
|
||||||
|
"credits_description": "Riconoscimenti e attribuzioni per EMLy.",
|
||||||
|
"credits_about_title": "Informazioni su EMLy",
|
||||||
|
"credits_about_description": "\"Un'app che in qualche modo funziona ancora, con un'interfaccia da paura che rende quasi piacevole leggere le email.\"",
|
||||||
|
"credits_about_description_2": " -Qualcuno che chiaramente non ha visto il codice sorgente",
|
||||||
|
"credits_app_tagline": "Visualizzatore EML e MSG per Windows",
|
||||||
|
"credits_app_description": "EMLy è un'applicazione desktop leggera e moderna progettata per visualizzare file email .eml e .msg. Costruita con prestazioni e usabilità in mente, fornisce un'interfaccia pulita per leggere email, visualizzare allegati e gestire email PEC certificate italiane.",
|
||||||
|
"credits_team_title": "Team di Sviluppo",
|
||||||
|
"credits_team_description": "Le persone dietro EMLy.",
|
||||||
|
"credits_role_lead_developer": "Sviluppatore Principale",
|
||||||
|
"credits_role_senior_developer": "Sviluppatore Senior",
|
||||||
|
"credits_foisx_desc": "Creatore e manutentore di EMLy. Responsabile dell'architettura, sviluppo e design.",
|
||||||
|
"credits_laky64_desc": "Ha implementato il parser MSG personalizzato in Go per il supporto nativo dei messaggi Outlook.",
|
||||||
|
"credits_special_thanks_title": "Ringraziamenti Speciali",
|
||||||
|
"credits_special_thanks_description": "Contributori che hanno aiutato a migliorare EMLy in modo significativo.",
|
||||||
|
"credits_made_with": "Fatto con",
|
||||||
|
"credits_at_3git": "presso 3gIT",
|
||||||
|
"credits_tech_title": "Costruito Con",
|
||||||
|
"credits_tech_description": "Tecnologie principali che alimentano EMLy.",
|
||||||
|
"credits_tech_wails": "Framework per applicazioni desktop in Go",
|
||||||
|
"credits_tech_go": "Linguaggio di programmazione backend",
|
||||||
|
"credits_tech_sveltekit": "Framework per applicazioni frontend",
|
||||||
|
"credits_tech_svelte": "Framework UI reattivo",
|
||||||
|
"credits_tech_typescript": "JavaScript type-safe",
|
||||||
|
"credits_tech_tailwind": "Framework CSS utility-first",
|
||||||
|
"credits_libraries_title": "Librerie e Pacchetti",
|
||||||
|
"credits_libraries_description": "Pacchetti open source che rendono possibile EMLy.",
|
||||||
|
"credits_lib_shadcn": "Componenti UI per Svelte",
|
||||||
|
"credits_lib_lucide": "Set di icone belle e coerenti",
|
||||||
|
"credits_lib_paraglide": "Internazionalizzazione type-safe",
|
||||||
|
"credits_lib_sonner": "Notifiche toast per Svelte",
|
||||||
|
"credits_lib_pdfjs": "Libreria di rendering PDF di Mozilla",
|
||||||
|
"credits_lib_dompurify": "Sanitizzatore XSS per contenuti HTML",
|
||||||
|
"credits_license_title": "Licenza e Sorgente",
|
||||||
|
"credits_license_text": "EMLy è un software proprietario sviluppato da 3gIT. Tutti i diritti riservati. L'applicazione utilizza varie librerie open source, ciascuna governata dalle rispettive licenze.",
|
||||||
|
"credits_copyright": "Tutti i diritti riservati."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,8 @@
|
|||||||
|
|
||||||
// Import refactored utilities
|
// Import refactored utilities
|
||||||
import {
|
import {
|
||||||
IFRAME_UTIL_HTML,
|
IFRAME_UTIL_HTML_DARK,
|
||||||
|
IFRAME_UTIL_HTML_LIGHT,
|
||||||
CONTENT_TYPES,
|
CONTENT_TYPES,
|
||||||
PEC_FILES,
|
PEC_FILES,
|
||||||
arrayBufferToBase64,
|
arrayBufferToBase64,
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
processEmailBody,
|
processEmailBody,
|
||||||
isEmailFile,
|
isEmailFile,
|
||||||
} from '$lib/utils/mail';
|
} from '$lib/utils/mail';
|
||||||
|
import { settingsStore } from '$lib/stores/settings.svelte';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// State
|
// State
|
||||||
@@ -42,6 +44,13 @@
|
|||||||
let isLoading = $state(false);
|
let isLoading = $state(false);
|
||||||
let loadingText = $state('');
|
let loadingText = $state('');
|
||||||
|
|
||||||
|
// Derived iframe HTML based on dark/light setting
|
||||||
|
let iframeUtilHtml = $derived(
|
||||||
|
settingsStore.settings.useDarkEmailViewer !== false
|
||||||
|
? IFRAME_UTIL_HTML_DARK
|
||||||
|
: IFRAME_UTIL_HTML_LIGHT
|
||||||
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Event Handlers
|
// Event Handlers
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -347,9 +356,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Email Body -->
|
<!-- Email Body -->
|
||||||
<div class="email-body-wrapper">
|
<div class="email-body-wrapper" class:light-theme={settingsStore.settings.useDarkEmailViewer === false}>
|
||||||
<iframe
|
<iframe
|
||||||
srcdoc={mailState.currentEmail.body + IFRAME_UTIL_HTML}
|
srcdoc={mailState.currentEmail.body + iframeUtilHtml}
|
||||||
title="Email Body"
|
title="Email Body"
|
||||||
class="email-iframe"
|
class="email-iframe"
|
||||||
sandbox="allow-same-origin allow-scripts"
|
sandbox="allow-same-origin allow-scripts"
|
||||||
@@ -579,9 +588,15 @@
|
|||||||
|
|
||||||
.email-body-wrapper {
|
.email-body-wrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background: white;
|
background: #0d0d0d;
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
|
border-radius: 0 0 14px 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-body-wrapper.light-theme {
|
||||||
|
background: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-iframe {
|
.email-iframe {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
||||||
import { dangerZoneEnabled } from "$lib/stores/app";
|
import { dangerZoneEnabled } from "$lib/stores/app";
|
||||||
import * as m from "$lib/paraglide/messages.js";
|
import * as m from "$lib/paraglide/messages.js";
|
||||||
import { Mail } from "@lucide/svelte/icons";
|
import { Mail, Heart } from "@lucide/svelte/icons";
|
||||||
|
|
||||||
const CLICK_WINDOW_MS = 4000;
|
const CLICK_WINDOW_MS = 4000;
|
||||||
const REQUIRED_CLICKS = 10;
|
const REQUIRED_CLICKS = 10;
|
||||||
@@ -43,6 +43,13 @@
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
id: 2,
|
id: 2,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: m.sidebar_credits(),
|
||||||
|
url: "/credits",
|
||||||
|
icon: Heart,
|
||||||
|
disabled: false,
|
||||||
|
id: 3,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const defaults: EMLy_GUI_Settings = {
|
|||||||
useBuiltinPDFViewer: true,
|
useBuiltinPDFViewer: true,
|
||||||
previewFileSupportedTypes: ["jpg", "jpeg", "png"],
|
previewFileSupportedTypes: ["jpg", "jpeg", "png"],
|
||||||
enableAttachedDebuggerProtection: true,
|
enableAttachedDebuggerProtection: true,
|
||||||
|
useDarkEmailViewer: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
class SettingsStore {
|
class SettingsStore {
|
||||||
|
|||||||
1
frontend/src/lib/types.d.ts
vendored
1
frontend/src/lib/types.d.ts
vendored
@@ -8,6 +8,7 @@ interface EMLy_GUI_Settings {
|
|||||||
useBuiltinPDFViewer?: boolean;
|
useBuiltinPDFViewer?: boolean;
|
||||||
previewFileSupportedTypes?: SupportedFileTypePreview[];
|
previewFileSupportedTypes?: SupportedFileTypePreview[];
|
||||||
enableAttachedDebuggerProtection?: boolean;
|
enableAttachedDebuggerProtection?: boolean;
|
||||||
|
useDarkEmailViewer?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SupportedLanguages = "en" | "it";
|
type SupportedLanguages = "en" | "it";
|
||||||
|
|||||||
@@ -1,10 +1,87 @@
|
|||||||
/**
|
/**
|
||||||
* HTML/CSS injected into the email body iframe for styling and security
|
* Dark theme HTML/CSS injected into the email body iframe
|
||||||
|
* - Applies dark theme matching the main app
|
||||||
|
* - Removes default body margins
|
||||||
|
* - Disables link clicking for security
|
||||||
|
* - Prevents Ctrl+Wheel zoom in iframe
|
||||||
|
* - Styles links, tables, and common email elements for dark mode
|
||||||
|
*/
|
||||||
|
export const IFRAME_UTIL_HTML_DARK = `<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background-color: #0d0d0d;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
pointer-events: none !important;
|
||||||
|
cursor: default !important;
|
||||||
|
color: #60a5fa !important;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-color: rgba(255, 255, 255, 0.15) !important;
|
||||||
|
}
|
||||||
|
td, th {
|
||||||
|
border-color: rgba(255, 255, 255, 0.15) !important;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
border-color: rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
blockquote {
|
||||||
|
border-left: 3px solid rgba(255, 255, 255, 0.2);
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 16px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
pre, code {
|
||||||
|
background-color: rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
padding: 12px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
</style><script>function handleWheel(event){if(event.ctrlKey){event.preventDefault();}}document.addEventListener('wheel',handleWheel,{passive:false});<\/script>`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Light theme HTML/CSS injected into the email body iframe (original styling)
|
||||||
|
* - Standard white background
|
||||||
* - Removes default body margins
|
* - Removes default body margins
|
||||||
* - Disables link clicking for security
|
* - Disables link clicking for security
|
||||||
* - Prevents Ctrl+Wheel zoom in iframe
|
* - Prevents Ctrl+Wheel zoom in iframe
|
||||||
*/
|
*/
|
||||||
export const IFRAME_UTIL_HTML = `<style>body{margin:0;padding:20px;font-family:sans-serif;} a{pointer-events:none!important;cursor:default!important;}</style><script>function handleWheel(event){if(event.ctrlKey){event.preventDefault();}}document.addEventListener('wheel',handleWheel,{passive:false});<\/script>`;
|
export const IFRAME_UTIL_HTML_LIGHT = `<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #1a1a1a;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
pointer-events: none !important;
|
||||||
|
cursor: default !important;
|
||||||
|
color: #2563eb !important;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
</style><script>function handleWheel(event){if(event.ctrlKey){event.preventDefault();}}document.addEventListener('wheel',handleWheel,{passive:false});<\/script>`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default iframe HTML (dark theme for backwards compatibility)
|
||||||
|
* @deprecated Use IFRAME_UTIL_HTML_DARK or IFRAME_UTIL_HTML_LIGHT instead
|
||||||
|
*/
|
||||||
|
export const IFRAME_UTIL_HTML = IFRAME_UTIL_HTML_DARK;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supported email file extensions
|
* Supported email file extensions
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
// Constants
|
// Constants
|
||||||
export {
|
export {
|
||||||
IFRAME_UTIL_HTML,
|
IFRAME_UTIL_HTML,
|
||||||
|
IFRAME_UTIL_HTML_DARK,
|
||||||
|
IFRAME_UTIL_HTML_LIGHT,
|
||||||
EMAIL_EXTENSIONS,
|
EMAIL_EXTENSIONS,
|
||||||
CONTENT_TYPES,
|
CONTENT_TYPES,
|
||||||
PEC_FILES,
|
PEC_FILES,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
FolderOpen,
|
FolderOpen,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
Camera,
|
Camera,
|
||||||
|
Heart,
|
||||||
} from "@lucide/svelte";
|
} from "@lucide/svelte";
|
||||||
import { Separator } from "$lib/components/ui/separator/index.js";
|
import { Separator } from "$lib/components/ui/separator/index.js";
|
||||||
import { toast } from "svelte-sonner";
|
import { toast } from "svelte-sonner";
|
||||||
@@ -253,8 +254,10 @@
|
|||||||
{#if dev}
|
{#if dev}
|
||||||
v{versionInfo?.EMLy.GUISemver}_{versionInfo?.EMLy.GUIReleaseChannel}
|
v{versionInfo?.EMLy.GUISemver}_{versionInfo?.EMLy.GUIReleaseChannel}
|
||||||
<debug>(DEBUG BUILD)</debug>
|
<debug>(DEBUG BUILD)</debug>
|
||||||
{:else}
|
{:else if versionInfo?.EMLy.GUIReleaseChannel !== "stable"}
|
||||||
v{versionInfo?.EMLy.GUISemver}_{versionInfo?.EMLy.GUIReleaseChannel}
|
v{versionInfo?.EMLy.GUISemver}_{versionInfo?.EMLy.GUIReleaseChannel}
|
||||||
|
{:else}
|
||||||
|
v{versionInfo?.EMLy.GUISemver}
|
||||||
{/if}
|
{/if}
|
||||||
</version>
|
</version>
|
||||||
{#if versionInfo}
|
{#if versionInfo}
|
||||||
@@ -355,6 +358,15 @@
|
|||||||
style="cursor: pointer; opacity: 0.7;"
|
style="cursor: pointer; opacity: 0.7;"
|
||||||
class="hover:opacity-100 transition-opacity"
|
class="hover:opacity-100 transition-opacity"
|
||||||
/>
|
/>
|
||||||
|
<Heart
|
||||||
|
size="16"
|
||||||
|
onclick={() => {
|
||||||
|
if (page.url.pathname !== "/credits" && page.url.pathname !== "/credits/")
|
||||||
|
goto("/credits");
|
||||||
|
}}
|
||||||
|
style="cursor: pointer; opacity: 0.7;"
|
||||||
|
class="hover:opacity-100 transition-opacity"
|
||||||
|
/>
|
||||||
|
|
||||||
<Separator orientation="vertical" />
|
<Separator orientation="vertical" />
|
||||||
<Bug
|
<Bug
|
||||||
|
|||||||
265
frontend/src/routes/(app)/credits/+page.svelte
Normal file
265
frontend/src/routes/(app)/credits/+page.svelte
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { goto } from "$app/navigation";
|
||||||
|
import { Button } from "$lib/components/ui/button";
|
||||||
|
import * as Card from "$lib/components/ui/card";
|
||||||
|
import { Separator } from "$lib/components/ui/separator";
|
||||||
|
import { ChevronLeft, Heart, Code, Package, Globe, Github, Mail } from "@lucide/svelte";
|
||||||
|
import * as m from "$lib/paraglide/messages";
|
||||||
|
import { OpenURLInBrowser } from "$lib/wailsjs/go/main/App";
|
||||||
|
|
||||||
|
let { data } = $props();
|
||||||
|
let config = $derived(data.config);
|
||||||
|
|
||||||
|
// Open external URL in default browser
|
||||||
|
async function openUrl(url: string) {
|
||||||
|
try {
|
||||||
|
await OpenURLInBrowser(url);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to open URL:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gravatar URL helper - uses MD5 hash of email
|
||||||
|
// Pre-computed hashes for known emails
|
||||||
|
const gravatarUrls: Record<string, string> = {
|
||||||
|
"f.fois@3git.eu": "https://gravatar.com/avatar/6a2b6cfd8ab2c36ac3eace1faa871f79084b64ad08fb6e490f050e71ee1b599c",
|
||||||
|
"iraci.matteo@gmail.com": "https://gravatar.com/avatar/0c17334ae886eb44b670d226e7de32ac082b9c85925ce4ed4c12239d9d8351f2",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Technology stack
|
||||||
|
const technologies = [
|
||||||
|
{ name: "Wails v2", description: m.credits_tech_wails(), url: "https://wails.io" },
|
||||||
|
{ name: "Go", description: m.credits_tech_go(), url: "https://go.dev" },
|
||||||
|
{ name: "SvelteKit", description: m.credits_tech_sveltekit(), url: "https://kit.svelte.dev" },
|
||||||
|
{ name: "Svelte 5", description: m.credits_tech_svelte(), url: "https://svelte.dev" },
|
||||||
|
{ name: "TypeScript", description: m.credits_tech_typescript(), url: "https://www.typescriptlang.org" },
|
||||||
|
{ name: "Tailwind CSS", description: m.credits_tech_tailwind(), url: "https://tailwindcss.com" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Libraries and packages
|
||||||
|
const libraries = [
|
||||||
|
{ name: "shadcn-svelte", description: m.credits_lib_shadcn(), url: "https://www.shadcn-svelte.com" },
|
||||||
|
{ name: "Lucide Icons", description: m.credits_lib_lucide(), url: "https://lucide.dev" },
|
||||||
|
{ name: "ParaglideJS", description: m.credits_lib_paraglide(), url: "https://inlang.com/m/gerre34r/library-inlang-paraglideJs" },
|
||||||
|
{ name: "svelte-sonner", description: m.credits_lib_sonner(), url: "https://svelte-sonner.vercel.app" },
|
||||||
|
{ name: "PDF.js", description: m.credits_lib_pdfjs(), url: "https://mozilla.github.io/pdf.js" },
|
||||||
|
{ name: "DOMPurify", description: m.credits_lib_dompurify(), url: "https://github.com/cure53/DOMPurify" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Team / Contributors
|
||||||
|
const team = [
|
||||||
|
{
|
||||||
|
username: "FOISX",
|
||||||
|
name: "Flavio Fois",
|
||||||
|
role: m.credits_role_lead_developer(),
|
||||||
|
description: m.credits_foisx_desc(),
|
||||||
|
email: "f.fois@3git.eu",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Special thanks
|
||||||
|
const specialThanks = [
|
||||||
|
{
|
||||||
|
name: "Laky64",
|
||||||
|
contribution: m.credits_laky64_desc(),
|
||||||
|
email: "iraci.matteo@gmail.com",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="min-h-[calc(100vh-1rem)] from-background to-muted/30">
|
||||||
|
<div
|
||||||
|
class="mx-auto flex max-w-3xl flex-col gap-4 px-4 py-6 sm:px-6 sm:py-10 opacity-80"
|
||||||
|
>
|
||||||
|
<header class="flex items-start justify-between gap-3">
|
||||||
|
<div class="min-w-0">
|
||||||
|
<h1
|
||||||
|
class="text-balance text-2xl font-semibold tracking-tight sm:text-3xl"
|
||||||
|
>
|
||||||
|
{m.credits_title()}
|
||||||
|
</h1>
|
||||||
|
<p class="mt-2 text-sm text-muted-foreground">
|
||||||
|
{m.credits_description()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
class="cursor-pointer hover:cursor-pointer"
|
||||||
|
variant="ghost"
|
||||||
|
onclick={() => goto("/")}
|
||||||
|
><ChevronLeft class="size-4" /> {m.settings_back()}</Button
|
||||||
|
>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- About Card -->
|
||||||
|
<Card.Root>
|
||||||
|
<Card.Header class="space-y-1">
|
||||||
|
<Card.Title class="flex items-center gap-2">
|
||||||
|
<Heart class="size-5 text-red-500" />
|
||||||
|
{m.credits_about_title()}
|
||||||
|
</Card.Title>
|
||||||
|
<Card.Description>
|
||||||
|
<span style="font-style: italic">{m.credits_about_description()}</span>
|
||||||
|
<span>{m.credits_about_description_2()}</span>
|
||||||
|
</Card.Description>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content>
|
||||||
|
<div class="flex items-center gap-4 mb-4">
|
||||||
|
<img src="/appicon.png" alt="EMLy Logo" width="64" height="64" class="rounded-lg" />
|
||||||
|
<div>
|
||||||
|
<h3 class="font-semibold text-lg">EMLy</h3>
|
||||||
|
<p class="text-sm text-muted-foreground">{m.credits_app_tagline()}</p>
|
||||||
|
{#if config}
|
||||||
|
<p class="text-xs text-muted-foreground mt-1">
|
||||||
|
v{config.GUISemver} ({config.GUIReleaseChannel})
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-muted-foreground">
|
||||||
|
{m.credits_app_description()}
|
||||||
|
</p>
|
||||||
|
</Card.Content>
|
||||||
|
</Card.Root>
|
||||||
|
|
||||||
|
<!-- Team Card -->
|
||||||
|
<Card.Root>
|
||||||
|
<Card.Header class="space-y-1">
|
||||||
|
<Card.Title class="flex items-center gap-2">
|
||||||
|
<Code class="size-5" />
|
||||||
|
{m.credits_team_title()}
|
||||||
|
</Card.Title>
|
||||||
|
<Card.Description>{m.credits_team_description()}</Card.Description>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content class="space-y-4">
|
||||||
|
{#each team as member}
|
||||||
|
<div class="flex items-start gap-4 rounded-lg border bg-card p-4">
|
||||||
|
<img
|
||||||
|
src={gravatarUrls[member.email]}
|
||||||
|
alt={member.name}
|
||||||
|
class="h-14 w-14 rounded-full border-2 border-primary/20"
|
||||||
|
/>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="font-medium">{member.username} ({member.name})</div>
|
||||||
|
<div class="text-sm text-primary/80">{member.role}</div>
|
||||||
|
<div class="text-sm text-muted-foreground mt-1">{member.description}</div>
|
||||||
|
<a
|
||||||
|
href="mailto:{member.email}"
|
||||||
|
class="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-primary mt-2 transition-colors"
|
||||||
|
>
|
||||||
|
<Mail class="size-3" />
|
||||||
|
{member.email}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<div class="text-center text-sm text-muted-foreground pt-2">
|
||||||
|
<span class="flex items-center justify-center gap-1">
|
||||||
|
{m.credits_made_with()} <Heart class="size-3 text-red-500 inline" /> {m.credits_at_3git()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Card.Content>
|
||||||
|
</Card.Root>
|
||||||
|
|
||||||
|
<!-- Special Thanks Card -->
|
||||||
|
<Card.Root>
|
||||||
|
<Card.Header class="space-y-1">
|
||||||
|
<Card.Title class="flex items-center gap-2">
|
||||||
|
<Heart class="size-5 text-pink-500" />
|
||||||
|
{m.credits_special_thanks_title()}
|
||||||
|
</Card.Title>
|
||||||
|
<Card.Description>{m.credits_special_thanks_description()}</Card.Description>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content>
|
||||||
|
<div class="space-y-3">
|
||||||
|
{#each specialThanks as contributor}
|
||||||
|
<div class="flex items-center gap-3 rounded-lg border bg-card p-3">
|
||||||
|
<img
|
||||||
|
src={gravatarUrls[contributor.email]}
|
||||||
|
alt={contributor.name}
|
||||||
|
class="h-10 w-10 rounded-full border-2 border-primary/20"
|
||||||
|
/>
|
||||||
|
<div class="flex-1">
|
||||||
|
<span class="font-medium text-sm">{contributor.name}</span>
|
||||||
|
-
|
||||||
|
<span class="text-muted-foreground text-sm">{contributor.contribution}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Card.Content>
|
||||||
|
</Card.Root>
|
||||||
|
|
||||||
|
<!-- Technologies Card -->
|
||||||
|
<Card.Root>
|
||||||
|
<Card.Header class="space-y-1">
|
||||||
|
<Card.Title class="flex items-center gap-2">
|
||||||
|
<Globe class="size-5" />
|
||||||
|
{m.credits_tech_title()}
|
||||||
|
</Card.Title>
|
||||||
|
<Card.Description>{m.credits_tech_description()}</Card.Description>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content>
|
||||||
|
<div class="grid gap-3 sm:grid-cols-2">
|
||||||
|
{#each technologies as tech}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => openUrl(tech.url)}
|
||||||
|
class="flex items-start gap-3 rounded-lg border bg-card p-3 transition-colors hover:bg-accent/50 cursor-pointer text-left"
|
||||||
|
>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="font-medium text-sm">{tech.name}</div>
|
||||||
|
<div class="text-xs text-muted-foreground">{tech.description}</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Card.Content>
|
||||||
|
</Card.Root>
|
||||||
|
|
||||||
|
<!-- Libraries Card -->
|
||||||
|
<Card.Root>
|
||||||
|
<Card.Header class="space-y-1">
|
||||||
|
<Card.Title class="flex items-center gap-2">
|
||||||
|
<Package class="size-5" />
|
||||||
|
{m.credits_libraries_title()}
|
||||||
|
</Card.Title>
|
||||||
|
<Card.Description>{m.credits_libraries_description()}</Card.Description>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content>
|
||||||
|
<div class="grid gap-3 sm:grid-cols-2">
|
||||||
|
{#each libraries as lib}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => openUrl(lib.url)}
|
||||||
|
class="flex items-start gap-3 rounded-lg border bg-card p-3 transition-colors hover:bg-accent/50 cursor-pointer text-left"
|
||||||
|
>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="font-medium text-sm">{lib.name}</div>
|
||||||
|
<div class="text-xs text-muted-foreground">{lib.description}</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Card.Content>
|
||||||
|
</Card.Root>
|
||||||
|
|
||||||
|
<!-- License Card -->
|
||||||
|
<Card.Root>
|
||||||
|
<Card.Header class="space-y-1">
|
||||||
|
<Card.Title class="flex items-center gap-2">
|
||||||
|
<Github class="size-5" />
|
||||||
|
{m.credits_license_title()}
|
||||||
|
</Card.Title>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content>
|
||||||
|
<p class="text-sm text-muted-foreground">
|
||||||
|
{m.credits_license_text()}
|
||||||
|
</p>
|
||||||
|
<Separator class="my-4" />
|
||||||
|
<p class="text-xs text-muted-foreground text-center">
|
||||||
|
© 2025-{new Date().getFullYear()} 3gIT. {m.credits_copyright()}
|
||||||
|
</p>
|
||||||
|
</Card.Content>
|
||||||
|
</Card.Root>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
19
frontend/src/routes/(app)/credits/+page.ts
Normal file
19
frontend/src/routes/(app)/credits/+page.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { PageLoad } from './$types';
|
||||||
|
import { GetConfig } from "$lib/wailsjs/go/main/App";
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
|
export const load = (async () => {
|
||||||
|
if (!browser) return { config: null };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const configRoot = await GetConfig();
|
||||||
|
return {
|
||||||
|
config: configRoot.EMLy
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to load config for credits", e);
|
||||||
|
return {
|
||||||
|
config: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}) satisfies PageLoad;
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
useBuiltinPDFViewer: true,
|
useBuiltinPDFViewer: true,
|
||||||
previewFileSupportedTypes: ["jpg", "jpeg", "png"],
|
previewFileSupportedTypes: ["jpg", "jpeg", "png"],
|
||||||
enableAttachedDebuggerProtection: true,
|
enableAttachedDebuggerProtection: true,
|
||||||
|
useDarkEmailViewer: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function setLanguage(
|
async function setLanguage(
|
||||||
@@ -68,6 +69,8 @@
|
|||||||
s.previewFileSupportedTypes || defaults.previewFileSupportedTypes || [],
|
s.previewFileSupportedTypes || defaults.previewFileSupportedTypes || [],
|
||||||
enableAttachedDebuggerProtection:
|
enableAttachedDebuggerProtection:
|
||||||
s.enableAttachedDebuggerProtection ?? defaults.enableAttachedDebuggerProtection ?? true,
|
s.enableAttachedDebuggerProtection ?? defaults.enableAttachedDebuggerProtection ?? true,
|
||||||
|
useDarkEmailViewer:
|
||||||
|
s.useDarkEmailViewer ?? defaults.useDarkEmailViewer ?? true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +80,7 @@
|
|||||||
!!a.useBuiltinPreview === !!b.useBuiltinPreview &&
|
!!a.useBuiltinPreview === !!b.useBuiltinPreview &&
|
||||||
!!a.useBuiltinPDFViewer === !!b.useBuiltinPDFViewer &&
|
!!a.useBuiltinPDFViewer === !!b.useBuiltinPDFViewer &&
|
||||||
!!a.enableAttachedDebuggerProtection === !!b.enableAttachedDebuggerProtection &&
|
!!a.enableAttachedDebuggerProtection === !!b.enableAttachedDebuggerProtection &&
|
||||||
|
!!a.useDarkEmailViewer === !!b.useDarkEmailViewer &&
|
||||||
JSON.stringify(a.previewFileSupportedTypes?.sort()) ===
|
JSON.stringify(a.previewFileSupportedTypes?.sort()) ===
|
||||||
JSON.stringify(b.previewFileSupportedTypes?.sort())
|
JSON.stringify(b.previewFileSupportedTypes?.sort())
|
||||||
);
|
);
|
||||||
@@ -450,6 +454,29 @@
|
|||||||
{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_email_dark_viewer_label()}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-muted-foreground">
|
||||||
|
{m.settings_email_dark_viewer_hint()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
bind:checked={form.useDarkEmailViewer}
|
||||||
|
class="cursor-pointer hover:cursor-pointer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-muted-foreground mt-2">
|
||||||
|
{m.settings_email_dark_viewer_info()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#define ApplicationName 'EMLy'
|
#define ApplicationName 'EMLy'
|
||||||
#define ApplicationVersion GetVersionNumbersString('EMLy.exe')
|
#define ApplicationVersion GetVersionNumbersString('EMLy.exe')
|
||||||
#define ApplicationVersion '1.3.1_beta'
|
#define ApplicationVersion '1.4.0'
|
||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
AppName={#ApplicationName}
|
AppName={#ApplicationName}
|
||||||
|
|||||||
Reference in New Issue
Block a user