mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
Gate email mailbox behind build flags
Adds a shared email mailbox availability flag and hides the /mail route and mailbox UI when the feature is unavailable. Core/web builds enable the inbox, while desktop disables it to avoid unsupported routes. This also updates the demo inbox text and labels to use clearer English copy.
This commit is contained in:
@@ -3693,6 +3693,45 @@ subtitle = "Import bookmarks, build hierarchies, and apply the outline without c
|
||||
description = "Select the Edit Table of Contents tool to load its workspace."
|
||||
title = "Open the tool to start editing"
|
||||
|
||||
[email]
|
||||
accounts = "Accounts"
|
||||
attachments = "Attachments"
|
||||
back = "Back"
|
||||
cacheHint = "Message metadata is cached locally."
|
||||
cacheReady = "Local cache active"
|
||||
connectAccount = "Connect account"
|
||||
connectDescription = "Connect your email account to securely transfer attachments into your PDF workflow."
|
||||
connectTitle = "Connect mailbox"
|
||||
demoBody = "Attachments can be transferred directly into the Stirling PDF workspace after download."
|
||||
download = "Import"
|
||||
eyebrow = "File sources"
|
||||
firstSetup = "First step"
|
||||
folders = "Mailbox"
|
||||
inbox = "Inbox"
|
||||
message = "Message"
|
||||
messageDetails = "Message details"
|
||||
messageList = "Message list"
|
||||
messages = "messages"
|
||||
moreActions = "More actions"
|
||||
noAccount = "No account connected yet"
|
||||
noAccountHint = "Connect a mailbox to import attachments."
|
||||
noResults = "No messages found"
|
||||
noResultsHint = "Try a different search term."
|
||||
oauthNote = "Sign-in uses OAuth. Passwords are not stored by Stirling."
|
||||
queued = "Queued"
|
||||
refresh = "Refresh inbox"
|
||||
searchPlaceholder = "Search messages"
|
||||
selectMessage = "Select a message"
|
||||
selectMessageHint = "Choose an email from the list."
|
||||
settings = "Email settings"
|
||||
star = "Star"
|
||||
starred = "Starred"
|
||||
storageNote = "Attachments are stored in the file workflow; email data stays in the local cache."
|
||||
syncLabel = "Synchronization"
|
||||
syncTime = "4 minutes ago"
|
||||
title = "Email inbox"
|
||||
trash = "Trash"
|
||||
|
||||
[emptyFilesState]
|
||||
upload = "Upload"
|
||||
|
||||
@@ -3775,48 +3814,7 @@ closeFile = "Close file"
|
||||
[fileEditor]
|
||||
addFiles = "Add Files"
|
||||
|
||||
[email]
|
||||
back = "Back"
|
||||
eyebrow = "File sources"
|
||||
title = "Email inbox"
|
||||
cacheReady = "Local cache active"
|
||||
refresh = "Refresh inbox"
|
||||
settings = "Email settings"
|
||||
accounts = "Accounts"
|
||||
connectAccount = "Connect account"
|
||||
noAccount = "No account connected yet"
|
||||
noAccountHint = "Connect a mailbox to import attachments."
|
||||
folders = "Mailbox"
|
||||
inbox = "Inbox"
|
||||
starred = "Starred"
|
||||
trash = "Trash"
|
||||
syncLabel = "Synchronization"
|
||||
syncTime = "4 minutes ago"
|
||||
cacheHint = "Message metadata is cached locally."
|
||||
messageList = "Message list"
|
||||
messages = "messages"
|
||||
searchPlaceholder = "Search messages"
|
||||
noResults = "No messages found"
|
||||
noResultsHint = "Try a different search term."
|
||||
messageDetails = "Message details"
|
||||
message = "Message"
|
||||
moreActions = "More actions"
|
||||
star = "Star"
|
||||
demoBody = "Attachments can be transferred directly into the Stirling PDF workspace after download."
|
||||
attachments = "Attachments"
|
||||
queued = "Queued"
|
||||
download = "Import"
|
||||
storageNote = "Attachments are stored in the file workflow; email data stays in the local cache."
|
||||
selectMessage = "Select a message"
|
||||
selectMessageHint = "Choose an email from the list."
|
||||
firstSetup = "First step"
|
||||
connectTitle = "Connect mailbox"
|
||||
connectDescription = "Connect your email account to securely transfer attachments into your PDF workflow."
|
||||
oauthNote = "Sign-in uses OAuth. Passwords are not stored by Stirling."
|
||||
|
||||
[fileManager]
|
||||
email = "Email inbox"
|
||||
emailShort = "Email"
|
||||
active = "Active"
|
||||
addToUpload = "Add to Upload"
|
||||
changesNotUploaded = "Changes not uploaded"
|
||||
@@ -3835,6 +3833,8 @@ details = "File Details"
|
||||
download = "Download"
|
||||
downloadSelected = "Download Files"
|
||||
dropFilesHere = "Drop files here"
|
||||
email = "Email inbox"
|
||||
emailShort = "Email"
|
||||
fileFormat = "Format"
|
||||
fileHistory = "File History"
|
||||
fileName = "Name"
|
||||
@@ -3950,7 +3950,6 @@ upload = "Upload"
|
||||
workbench = "Workbench"
|
||||
|
||||
[fileSidebar]
|
||||
email = "Email inbox"
|
||||
addFiles = "Add files"
|
||||
addingFiles = "Adding files…"
|
||||
collapse = "Collapse sidebar"
|
||||
@@ -3961,6 +3960,7 @@ downloadFailed = "Download failed"
|
||||
dropHint = "Open files to get started"
|
||||
dropToAdd = "Drop files to add"
|
||||
duplicateFailed = "Could not duplicate file"
|
||||
email = "Email inbox"
|
||||
expand = "Expand sidebar"
|
||||
googleDrive = "Google Drive"
|
||||
googleDriveDisabled = "Google Drive is not configured"
|
||||
|
||||
@@ -8,6 +8,7 @@ import { PreferencesProvider } from "@app/contexts/PreferencesContext";
|
||||
import HomePage from "@app/pages/HomePage";
|
||||
import EmailInboxPage from "@app/pages/EmailInboxPage";
|
||||
import Onboarding from "@app/components/onboarding/Onboarding";
|
||||
import { EMAIL_MAILBOX_ENABLED } from "@app/constants/emailMailboxAvailability";
|
||||
|
||||
const MobileScannerPage = lazy(() => import("@app/pages/MobileScannerPage"));
|
||||
const MobileSignPage = lazy(() => import("@app/pages/MobileSignPage"));
|
||||
@@ -54,16 +55,18 @@ export default function App() {
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/mail"
|
||||
element={
|
||||
<AppProviders>
|
||||
<AppLayout>
|
||||
<EmailInboxPage />
|
||||
</AppLayout>
|
||||
</AppProviders>
|
||||
}
|
||||
/>
|
||||
{EMAIL_MAILBOX_ENABLED && (
|
||||
<Route
|
||||
path="/mail"
|
||||
element={
|
||||
<AppProviders>
|
||||
<AppLayout>
|
||||
<EmailInboxPage />
|
||||
</AppLayout>
|
||||
</AppProviders>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* All other routes need AppProviders for backend integration */}
|
||||
<Route
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useAppConfig } from "@app/contexts/AppConfigContext";
|
||||
import { useIsMobile } from "@app/hooks/useIsMobile";
|
||||
import MobileUploadModal from "@app/components/shared/MobileUploadModal";
|
||||
import { GoogleDriveIcon } from "@app/components/shared/CloudStorageIcons";
|
||||
import { EMAIL_MAILBOX_ENABLED } from "@app/constants/emailMailboxAvailability";
|
||||
|
||||
interface FileSourceButtonsProps {
|
||||
horizontal?: boolean;
|
||||
@@ -123,19 +124,21 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="tertiary"
|
||||
accent="neutral"
|
||||
leftSection={<EmailOutlinedIcon />}
|
||||
justify={buttonJustify}
|
||||
onClick={() => navigate("/mail")}
|
||||
fullWidth={!horizontal}
|
||||
size={buttonSize}
|
||||
>
|
||||
{horizontal
|
||||
? t("fileManager.emailShort", "Email")
|
||||
: t("fileManager.email", "Email inbox")}
|
||||
</Button>
|
||||
{EMAIL_MAILBOX_ENABLED && (
|
||||
<Button
|
||||
variant="tertiary"
|
||||
accent="neutral"
|
||||
leftSection={<EmailOutlinedIcon />}
|
||||
justify={buttonJustify}
|
||||
onClick={() => navigate("/mail")}
|
||||
fullWidth={!horizontal}
|
||||
size={buttonSize}
|
||||
>
|
||||
{horizontal
|
||||
? t("fileManager.emailShort", "Email")
|
||||
: t("fileManager.email", "Email inbox")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{!shouldHideMobileQR && (
|
||||
<Button
|
||||
|
||||
@@ -76,6 +76,7 @@ import {
|
||||
clearWatchedFolderDraggedFileIds,
|
||||
} from "@app/components/watchedFolders/watchedFolderDragState";
|
||||
import { WATCHED_FOLDERS_ENABLED } from "@app/constants/featureFlags";
|
||||
import { EMAIL_MAILBOX_ENABLED } from "@app/constants/emailMailboxAvailability";
|
||||
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
|
||||
import "@app/components/shared/FileSidebar.css";
|
||||
|
||||
@@ -1162,37 +1163,39 @@ const FileSidebar = forwardRef<HTMLDivElement, FileSidebarProps>(
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Tooltip
|
||||
label={t("fileSidebar.email", "E-Mail-Postfach")}
|
||||
position="right"
|
||||
withinPortal
|
||||
disabled={!collapsed}
|
||||
>
|
||||
<div
|
||||
className="file-sidebar-action-row"
|
||||
data-testid="email-button"
|
||||
onClick={() => {
|
||||
if (collapsed && onToggleCollapse) onToggleCollapse();
|
||||
navigate("/mail");
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={t("fileSidebar.email", "E-Mail-Postfach")}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
navigate("/mail");
|
||||
}
|
||||
}}
|
||||
{EMAIL_MAILBOX_ENABLED && (
|
||||
<Tooltip
|
||||
label={t("fileSidebar.email", "Email inbox")}
|
||||
position="right"
|
||||
withinPortal
|
||||
disabled={!collapsed}
|
||||
>
|
||||
<EmailOutlinedIcon className="file-sidebar-action-icon" />
|
||||
{!collapsed && (
|
||||
<span className="file-sidebar-action-label sidebar-content-fade">
|
||||
{t("fileSidebar.email", "E-Mail-Postfach")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div
|
||||
className="file-sidebar-action-row"
|
||||
data-testid="email-button"
|
||||
onClick={() => {
|
||||
if (collapsed && onToggleCollapse) onToggleCollapse();
|
||||
navigate("/mail");
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={t("fileSidebar.email", "Email inbox")}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
navigate("/mail");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<EmailOutlinedIcon className="file-sidebar-action-icon" />
|
||||
{!collapsed && (
|
||||
<span className="file-sidebar-action-label sidebar-content-fade">
|
||||
{t("fileSidebar.email", "Email inbox")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* Watched Folders entry */}
|
||||
{WATCHED_FOLDERS_ENABLED && (
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
// Email mailbox support is enabled for web-based builds.
|
||||
export const EMAIL_MAILBOX_ENABLED = true;
|
||||
@@ -78,8 +78,7 @@ const DEMO_MESSAGES: MailMessage[] = [
|
||||
sender: "Nordlicht GmbH",
|
||||
address: "buchhaltung@nordlicht.example",
|
||||
subject: "Rechnung 2026-0814",
|
||||
preview:
|
||||
"Anbei finden Sie die Rechnung für den aktuellen Abrechnungszeitraum.",
|
||||
preview: "Attached is the invoice for the current billing period.",
|
||||
date: "Heute, 09:42",
|
||||
unread: true,
|
||||
labels: [],
|
||||
@@ -100,7 +99,7 @@ const DEMO_MESSAGES: MailMessage[] = [
|
||||
address: "mara.hoffmann@example.com",
|
||||
subject: "Vertragsunterlagen zur Freigabe",
|
||||
preview:
|
||||
"Die aktualisierten Unterlagen liegen im Anhang. Bitte um kurze Rückmeldung.",
|
||||
"The updated documents are attached. Please provide a brief response.",
|
||||
date: "Gestern",
|
||||
hasAttachment: true,
|
||||
labels: [],
|
||||
@@ -116,7 +115,8 @@ const DEMO_MESSAGES: MailMessage[] = [
|
||||
id: "terms-docx",
|
||||
name: "Anlage_A.docx",
|
||||
type: "DOCX",
|
||||
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
mimeType:
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
size: "74 KB",
|
||||
},
|
||||
],
|
||||
@@ -125,9 +125,9 @@ const DEMO_MESSAGES: MailMessage[] = [
|
||||
id: "meeting-03",
|
||||
sender: "Projektteam",
|
||||
address: "projektteam@example.com",
|
||||
subject: "Nächste Schritte",
|
||||
subject: "Next steps",
|
||||
preview:
|
||||
"Danke für das Gespräch. Die nächsten Schritte sind im Überblick zusammengefasst.",
|
||||
"Thank you for the discussion. The next steps are summarized below.",
|
||||
date: "12. Aug.",
|
||||
labels: [],
|
||||
attachments: [],
|
||||
@@ -262,19 +262,20 @@ export default function EmailInboxPage() {
|
||||
id: message.id,
|
||||
sender: sender.name,
|
||||
address: sender.address,
|
||||
subject: message.subject || "(Ohne Betreff)",
|
||||
preview: message.preview,
|
||||
date: message.date,
|
||||
unread: message.unread,
|
||||
labels: message.labels ?? [],
|
||||
hasAttachment: true,
|
||||
attachments: message.attachments.map((attachment) => ({
|
||||
id: attachment.id,
|
||||
name: attachment.name,
|
||||
type: attachment.mimeType.split("/").pop()?.toUpperCase() ?? "FILE",
|
||||
mimeType: attachment.mimeType,
|
||||
size: formatFileSize(attachment.size),
|
||||
})),
|
||||
subject: message.subject || "(Ohne Betreff)",
|
||||
preview: message.preview,
|
||||
date: message.date,
|
||||
unread: message.unread,
|
||||
labels: message.labels ?? [],
|
||||
hasAttachment: true,
|
||||
attachments: message.attachments.map((attachment) => ({
|
||||
id: attachment.id,
|
||||
name: attachment.name,
|
||||
type:
|
||||
attachment.mimeType.split("/").pop()?.toUpperCase() ?? "FILE",
|
||||
mimeType: attachment.mimeType,
|
||||
size: formatFileSize(attachment.size),
|
||||
})),
|
||||
};
|
||||
});
|
||||
setMessages((current) =>
|
||||
@@ -370,7 +371,9 @@ export default function EmailInboxPage() {
|
||||
);
|
||||
const attachmentTypeOptions = useMemo(
|
||||
() =>
|
||||
Array.from(new Set([...attachmentTypes, ...customAttachmentTypes])).sort(),
|
||||
Array.from(
|
||||
new Set([...attachmentTypes, ...customAttachmentTypes]),
|
||||
).sort(),
|
||||
[attachmentTypes, customAttachmentTypes],
|
||||
);
|
||||
|
||||
@@ -382,27 +385,30 @@ export default function EmailInboxPage() {
|
||||
selectedLabels.length === 0
|
||||
)
|
||||
return messages;
|
||||
return messages.filter((message) =>
|
||||
(normalizedQuery
|
||||
? [message.sender, message.address, message.subject, message.preview]
|
||||
.join(" ")
|
||||
.toLocaleLowerCase()
|
||||
.includes(normalizedQuery)
|
||||
: true) &&
|
||||
(selectedAttachmentTypes.length > 0
|
||||
? message.attachments.some(
|
||||
(attachment) => selectedAttachmentTypes.includes(attachment.type),
|
||||
)
|
||||
: true) &&
|
||||
(selectedLabels.length > 0
|
||||
? selectedLabels.some((label) =>
|
||||
(message.labels ?? []).includes(label),
|
||||
)
|
||||
: true),
|
||||
return messages.filter(
|
||||
(message) =>
|
||||
(normalizedQuery
|
||||
? [message.sender, message.address, message.subject, message.preview]
|
||||
.join(" ")
|
||||
.toLocaleLowerCase()
|
||||
.includes(normalizedQuery)
|
||||
: true) &&
|
||||
(selectedAttachmentTypes.length > 0
|
||||
? message.attachments.some((attachment) =>
|
||||
selectedAttachmentTypes.includes(attachment.type),
|
||||
)
|
||||
: true) &&
|
||||
(selectedLabels.length > 0
|
||||
? selectedLabels.some((label) =>
|
||||
(message.labels ?? []).includes(label),
|
||||
)
|
||||
: true),
|
||||
);
|
||||
}, [messages, query, selectedAttachmentTypes, selectedLabels]);
|
||||
|
||||
const unreadMessageCount = messages.filter((message) => message.unread).length;
|
||||
const unreadMessageCount = messages.filter(
|
||||
(message) => message.unread,
|
||||
).length;
|
||||
|
||||
const selectedMessage =
|
||||
filteredMessages.find((message) => message.id === selectedMessageId) ??
|
||||
@@ -423,7 +429,7 @@ export default function EmailInboxPage() {
|
||||
const disconnectAccount = async () => {
|
||||
if (
|
||||
!window.confirm(
|
||||
"Gmail trennen? Die lokale Verbindung wird gelöscht und der Google-Zugriff widerrufen.",
|
||||
"Disconnect Gmail? The local connection will be deleted and Google access revoked.",
|
||||
)
|
||||
) {
|
||||
return;
|
||||
@@ -471,7 +477,7 @@ export default function EmailInboxPage() {
|
||||
if (
|
||||
alreadyExists &&
|
||||
!window.confirm(
|
||||
`Die Datei "${attachment.name}" ist bereits vorhanden. Soll sie erneut importiert werden?`,
|
||||
`The file "${attachment.name}" already exists. Import it again?`,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
@@ -496,16 +502,16 @@ export default function EmailInboxPage() {
|
||||
<div className="email-page-brand">
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
aria-label={t("email.back", "Zurück")}
|
||||
aria-label={t("email.back", "Back")}
|
||||
onClick={() => navigate("/editor")}
|
||||
>
|
||||
<ArrowBackIcon fontSize="small" />
|
||||
</ActionIcon>
|
||||
<div>
|
||||
<div className="email-page-eyebrow">
|
||||
{t("email.eyebrow", "Dateiquellen")}
|
||||
{t("email.eyebrow", "File sources")}
|
||||
</div>
|
||||
<h1>{t("email.title", "E-Mail-Postfach")}</h1>
|
||||
<h1>{t("email.title", "Email inbox")}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div className="email-page-header-actions">
|
||||
@@ -513,19 +519,19 @@ export default function EmailInboxPage() {
|
||||
<span className="email-status-dot" />
|
||||
{t("email.cacheReady", "Lokaler Cache aktiv")}
|
||||
</span>
|
||||
<Tooltip label={t("email.refresh", "Postfach aktualisieren")}>
|
||||
<Tooltip label={t("email.refresh", "Refresh inbox")}>
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
aria-label={t("email.refresh", "Postfach aktualisieren")}
|
||||
aria-label={t("email.refresh", "Refresh inbox")}
|
||||
onClick={refreshInbox}
|
||||
>
|
||||
<RefreshIcon fontSize="small" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label={t("email.settings", "E-Mail-Einstellungen")}>
|
||||
<Tooltip label={t("email.settings", "Email settings")}>
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
aria-label={t("email.settings", "E-Mail-Einstellungen")}
|
||||
aria-label={t("email.settings", "Email settings")}
|
||||
onClick={openSettings}
|
||||
>
|
||||
<SettingsOutlinedIcon fontSize="small" />
|
||||
@@ -537,7 +543,7 @@ export default function EmailInboxPage() {
|
||||
<div className="email-page-body">
|
||||
<aside className="email-sidebar">
|
||||
<div className="email-sidebar-heading">
|
||||
<span>{t("email.accounts", "Konten")}</span>
|
||||
<span>{t("email.accounts", "Accounts")}</span>
|
||||
<Tooltip label={t("email.connectAccount", "Konto verbinden")}>
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
@@ -570,7 +576,7 @@ export default function EmailInboxPage() {
|
||||
<span>
|
||||
{t(
|
||||
"email.noAccountHint",
|
||||
"Verbinde ein Postfach, um Anhänge zu importieren.",
|
||||
"Connect a mailbox to import attachments.",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -578,14 +584,14 @@ export default function EmailInboxPage() {
|
||||
|
||||
<Divider my="md" />
|
||||
<div className="email-sidebar-heading">
|
||||
<span>{t("email.folders", "Postfach")}</span>
|
||||
<span>{t("email.folders", "Mailbox")}</span>
|
||||
</div>
|
||||
<button
|
||||
className={`email-folder-row ${selectedFolder === "inbox" ? "is-active" : ""}`}
|
||||
onClick={() => setSelectedFolder("inbox")}
|
||||
>
|
||||
<InboxOutlinedIcon fontSize="small" />
|
||||
<span>{t("email.inbox", "Posteingang")}</span>
|
||||
<span>{t("email.inbox", "Inbox")}</span>
|
||||
{unreadMessageCount > 0 && (
|
||||
<Badge size="sm" variant="light">
|
||||
{unreadMessageCount}
|
||||
@@ -597,21 +603,21 @@ export default function EmailInboxPage() {
|
||||
onClick={() => setSelectedFolder("starred")}
|
||||
>
|
||||
<StarBorderIcon fontSize="small" />
|
||||
<span>{t("email.starred", "Markiert")}</span>
|
||||
<span>{t("email.starred", "Starred")}</span>
|
||||
</button>
|
||||
<button
|
||||
className={`email-folder-row ${selectedFolder === "trash" ? "is-active" : ""}`}
|
||||
onClick={() => setSelectedFolder("trash")}
|
||||
>
|
||||
<DeleteOutlineIcon fontSize="small" />
|
||||
<span>{t("email.trash", "Papierkorb")}</span>
|
||||
<span>{t("email.trash", "Trash")}</span>
|
||||
</button>
|
||||
|
||||
<div className="email-sidebar-footer">
|
||||
<span className="email-sync-label">
|
||||
{t("email.syncLabel", "Synchronisierung")}
|
||||
{t("email.syncLabel", "Synchronization")}
|
||||
</span>
|
||||
<strong>{t("email.syncTime", "Vor 4 Minuten")}</strong>
|
||||
<strong>{t("email.syncTime", "4 minutes ago")}</strong>
|
||||
<span>
|
||||
{t(
|
||||
"email.cacheHint",
|
||||
@@ -623,29 +629,23 @@ export default function EmailInboxPage() {
|
||||
|
||||
<section
|
||||
className="email-message-column"
|
||||
aria-label={t("email.messageList", "E-Mail-Liste")}
|
||||
aria-label={t("email.messageList", "Email list")}
|
||||
>
|
||||
<div className="email-column-toolbar">
|
||||
<div className="email-toolbar-top">
|
||||
<div>
|
||||
<h2>{t("email.inbox", "Posteingang")}</h2>
|
||||
<span>
|
||||
{filteredMessages.length} {t("email.messages", "Nachrichten")}
|
||||
{filteredMessages.length} {t("email.messages", "messages")}
|
||||
</span>
|
||||
</div>
|
||||
<TextInput
|
||||
className="email-search"
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.currentTarget.value)}
|
||||
placeholder={t(
|
||||
"email.searchPlaceholder",
|
||||
"Nachrichten durchsuchen",
|
||||
)}
|
||||
placeholder={t("email.searchPlaceholder", "Search messages")}
|
||||
leftSection={<SearchIcon fontSize="small" />}
|
||||
aria-label={t(
|
||||
"email.searchPlaceholder",
|
||||
"Nachrichten durchsuchen",
|
||||
)}
|
||||
aria-label={t("email.searchPlaceholder", "Search messages")}
|
||||
/>
|
||||
</div>
|
||||
<MultiSelect
|
||||
@@ -662,22 +662,24 @@ export default function EmailInboxPage() {
|
||||
addAttachmentType(attachmentTypeDraft);
|
||||
}
|
||||
}}
|
||||
placeholder={t("email.fileTypeFilter", "Dateityp")}
|
||||
aria-label={t("email.fileTypeFilter", "Dateityp filtern")}
|
||||
placeholder={t("email.fileTypeFilter", "File type")}
|
||||
aria-label={t("email.fileTypeFilter", "Filter by file type")}
|
||||
/>
|
||||
<TextInput
|
||||
className="email-type-custom-input"
|
||||
value={attachmentTypeDraft}
|
||||
onChange={(event) => setAttachmentTypeDraft(event.currentTarget.value)}
|
||||
onChange={(event) =>
|
||||
setAttachmentTypeDraft(event.currentTarget.value)
|
||||
}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== "Enter") return;
|
||||
addAttachmentType(attachmentTypeDraft);
|
||||
}}
|
||||
placeholder={t(
|
||||
"email.customFileType",
|
||||
"Eigenen Dateityp eingeben und Enter drücken",
|
||||
"Enter a custom file type and press Enter",
|
||||
)}
|
||||
aria-label={t("email.customFileType", "Eigenen Dateityp")}
|
||||
aria-label={t("email.customFileType", "Custom file type")}
|
||||
/>
|
||||
<MultiSelect
|
||||
className="email-label-filter"
|
||||
@@ -686,7 +688,7 @@ export default function EmailInboxPage() {
|
||||
value={selectedLabels}
|
||||
onChange={setSelectedLabels}
|
||||
placeholder={t("email.labelFilter", "Label")}
|
||||
aria-label={t("email.labelFilter", "Nach Labels filtern")}
|
||||
aria-label={t("email.labelFilter", "Filter by labels")}
|
||||
searchable
|
||||
/>
|
||||
</div>
|
||||
@@ -744,18 +746,22 @@ export default function EmailInboxPage() {
|
||||
) : (
|
||||
<div className="email-no-results">
|
||||
<SearchIcon />
|
||||
<strong>
|
||||
{t("email.noResults", "Keine Nachrichten gefunden")}
|
||||
</strong>
|
||||
<strong>{t("email.noResults", "No messages found")}</strong>
|
||||
<span>
|
||||
{t("email.noResultsHint", "Passe deinen Suchbegriff an.")}
|
||||
{t("email.noResultsHint", "Try a different search term.")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{loadingMore && (
|
||||
<div className="email-loading-more" role="status" aria-live="polite">
|
||||
<div
|
||||
className="email-loading-more"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
<span className="email-loading-spinner" aria-hidden="true" />
|
||||
<span>{t("email.loadingMore", "Weitere Nachrichten werden geladen ...")}</span>
|
||||
<span>
|
||||
{t("email.loadingMore", "Loading more messages ...")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
@@ -763,20 +769,20 @@ export default function EmailInboxPage() {
|
||||
|
||||
<section
|
||||
className="email-detail-column"
|
||||
aria-label={t("email.messageDetails", "Nachrichtendetails")}
|
||||
aria-label={t("email.messageDetails", "Message details")}
|
||||
>
|
||||
{selectedMessage ? (
|
||||
<>
|
||||
<div className="email-detail-toolbar">
|
||||
<span className="email-detail-label">
|
||||
{t("email.message", "Nachricht")}
|
||||
{t("email.message", "Message")}
|
||||
</span>
|
||||
<Menu shadow="md" width={220} position="bottom-end">
|
||||
<Menu.Target>
|
||||
<Tooltip label={t("email.moreActions", "Weitere Aktionen")}>
|
||||
<Tooltip label={t("email.moreActions", "More actions")}>
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
aria-label={t("email.moreActions", "Weitere Aktionen")}
|
||||
aria-label={t("email.moreActions", "More actions")}
|
||||
>
|
||||
<MoreHorizIcon fontSize="small" />
|
||||
</ActionIcon>
|
||||
@@ -785,7 +791,9 @@ export default function EmailInboxPage() {
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item
|
||||
onClick={() =>
|
||||
void navigator.clipboard.writeText(selectedMessage.subject)
|
||||
void navigator.clipboard.writeText(
|
||||
selectedMessage.subject,
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("email.copySubject", "Betreff kopieren")}
|
||||
@@ -801,7 +809,7 @@ export default function EmailInboxPage() {
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item onClick={refreshInbox}>
|
||||
{t("email.refresh", "Postfach aktualisieren")}
|
||||
{t("email.refresh", "Refresh inbox")}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
@@ -842,14 +850,14 @@ export default function EmailInboxPage() {
|
||||
<p className="email-message-body-copy">
|
||||
{t(
|
||||
"email.demoBody",
|
||||
"Die angehängten Dateien können nach dem Download direkt in den Stirling-PDF-Arbeitsbereich übernommen werden.",
|
||||
"Attached files can be transferred directly into the Stirling PDF workspace after download.",
|
||||
)}
|
||||
</p>
|
||||
|
||||
{selectedAttachments && selectedAttachments.length > 0 && (
|
||||
<div className="email-attachments">
|
||||
<div className="email-section-label">
|
||||
<span>{t("email.attachments", "Anhänge")}</span>
|
||||
<span>{t("email.attachments", "Attachments")}</span>
|
||||
<span>{selectedAttachments.length}</span>
|
||||
</div>
|
||||
{selectedAttachments.map((attachment) => (
|
||||
@@ -871,12 +879,15 @@ export default function EmailInboxPage() {
|
||||
<CloudDownloadOutlinedIcon fontSize="small" />
|
||||
}
|
||||
onClick={() =>
|
||||
void importAttachment(selectedMessage.id, attachment)
|
||||
void importAttachment(
|
||||
selectedMessage.id,
|
||||
attachment,
|
||||
)
|
||||
}
|
||||
>
|
||||
{downloadedAttachment === attachment.id
|
||||
? t("email.queued", "Vorgemerkt")
|
||||
: t("email.download", "Importieren")}
|
||||
: t("email.download", "Import")}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
@@ -884,7 +895,7 @@ export default function EmailInboxPage() {
|
||||
<EmailOutlinedIcon fontSize="inherit" />{" "}
|
||||
{t(
|
||||
"email.storageNote",
|
||||
"Anhänge werden im Datei-Workflow gespeichert, E-Mail-Daten bleiben im lokalen Cache.",
|
||||
"Attachments are stored in the file workflow; email data remains in the local cache.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -895,12 +906,9 @@ export default function EmailInboxPage() {
|
||||
) : (
|
||||
<div className="email-detail-empty">
|
||||
<EmailOutlinedIcon />
|
||||
<strong>{t("email.selectMessage", "Nachricht auswählen")}</strong>
|
||||
<strong>{t("email.selectMessage", "Select a message")}</strong>
|
||||
<span>
|
||||
{t(
|
||||
"email.selectMessageHint",
|
||||
"Wähle eine E-Mail aus der Liste aus.",
|
||||
)}
|
||||
{t("email.selectMessageHint", "Choose an email from the list.")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -916,11 +924,11 @@ export default function EmailInboxPage() {
|
||||
<span className="email-page-eyebrow">
|
||||
{t("email.firstSetup", "Erster Schritt")}
|
||||
</span>
|
||||
<h2>{t("email.connectTitle", "Postfach verbinden")}</h2>
|
||||
<h2>{t("email.connectTitle", "Connect mailbox")}</h2>
|
||||
<p>
|
||||
{t(
|
||||
"email.connectDescription",
|
||||
"Verbinde dein E-Mail-Konto, um Anhänge sicher in deinen PDF-Workflow zu übernehmen.",
|
||||
"Connect your email account to securely transfer attachments into your PDF workflow.",
|
||||
)}
|
||||
</p>
|
||||
<div className="email-provider-actions">
|
||||
@@ -936,7 +944,7 @@ export default function EmailInboxPage() {
|
||||
<small>
|
||||
{t(
|
||||
"email.oauthNote",
|
||||
"Die Anmeldung erfolgt über OAuth. Passwörter werden nicht in Stirling gespeichert.",
|
||||
"Sign-in uses OAuth. Passwords are not stored by Stirling.",
|
||||
)}
|
||||
</small>
|
||||
</div>
|
||||
@@ -946,14 +954,14 @@ export default function EmailInboxPage() {
|
||||
<Modal
|
||||
opened={settingsOpen}
|
||||
onClose={() => setSettingsOpen(false)}
|
||||
title={t("email.settings", "E-Mail-Einstellungen")}
|
||||
title={t("email.settings", "Email settings")}
|
||||
centered
|
||||
>
|
||||
<TextInput
|
||||
label={t("email.displayName", "Anzeigename")}
|
||||
description={t(
|
||||
"email.displayNameHint",
|
||||
"Dieser Name wird statt der E-Mail-Adresse im Postfach angezeigt.",
|
||||
"This name is displayed instead of the email address in the mailbox.",
|
||||
)}
|
||||
value={draftDisplayName}
|
||||
onChange={(event) => setDraftDisplayName(event.currentTarget.value)}
|
||||
@@ -968,16 +976,14 @@ export default function EmailInboxPage() {
|
||||
onClick={() => void disconnectAccount()}
|
||||
className="email-disconnect-button"
|
||||
>
|
||||
Gmail-Verbindung trennen
|
||||
Disconnect Gmail
|
||||
</Button>
|
||||
)}
|
||||
<div className="email-settings-actions">
|
||||
<Button variant="secondary" onClick={() => setSettingsOpen(false)}>
|
||||
{t("email.cancel", "Abbrechen")}
|
||||
</Button>
|
||||
<Button onClick={saveSettings}>
|
||||
{t("email.save", "Speichern")}
|
||||
</Button>
|
||||
<Button onClick={saveSettings}>{t("email.save", "Speichern")}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</main>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
// Email mailbox support is intentionally disabled for the Tauri desktop build.
|
||||
export const EMAIL_MAILBOX_ENABLED = false;
|
||||
@@ -21,6 +21,7 @@ import { WATCHED_FOLDERS_ENABLED } from "@app/constants/featureFlags";
|
||||
import { getAdminRouteExtensions } from "@app/routes/adminRouteExtensions";
|
||||
import { RootGate } from "@app/routes/RootGate";
|
||||
import { RequireAuth } from "@app/auth/guards/RequireAuth";
|
||||
import { EMAIL_MAILBOX_ENABLED } from "@app/constants/emailMailboxAvailability";
|
||||
|
||||
// Import global styles
|
||||
import "@app/styles/tailwind.css";
|
||||
@@ -106,17 +107,21 @@ export default function App() {
|
||||
<Route path="/auth/callback" element={<AuthCallback />} />
|
||||
<Route path="/invite/:token" element={<InviteAccept />} />
|
||||
<Route path="/share/:token" element={<ShareLinkPage />} />
|
||||
<Route
|
||||
path="/mail"
|
||||
element={
|
||||
<RequireAuth
|
||||
loading={<LoadingFallback />}
|
||||
fallback={<Navigate to="/login?from=%2Fmail" replace />}
|
||||
>
|
||||
<EmailInboxPage />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
{EMAIL_MAILBOX_ENABLED && (
|
||||
<Route
|
||||
path="/mail"
|
||||
element={
|
||||
<RequireAuth
|
||||
loading={<LoadingFallback />}
|
||||
fallback={
|
||||
<Navigate to="/login?from=%2Fmail" replace />
|
||||
}
|
||||
>
|
||||
<EmailInboxPage />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{/* The editor and its tool routes - Landing handles auth logic */}
|
||||
<Route path="/*" element={<Landing />} />
|
||||
</Routes>
|
||||
|
||||
+1
-8
@@ -1,13 +1,6 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Stack,
|
||||
Paper,
|
||||
Text,
|
||||
TextInput,
|
||||
Loader,
|
||||
Group,
|
||||
} from "@mantine/core";
|
||||
import { Stack, Paper, Text, TextInput, Loader, Group } from "@mantine/core";
|
||||
import { alert } from "@app/components/toast";
|
||||
import RestartConfirmationModal from "@app/components/shared/config/RestartConfirmationModal";
|
||||
import { useRestartServer } from "@app/components/shared/config/useRestartServer";
|
||||
|
||||
Reference in New Issue
Block a user