feat: standalone buttons for all plugin pages + window system for modals
Standalone buttons: - Add ExternalLink button to Dms, Calendar, Mail, ContactsList toolbars - Create standalone pages for each (no sidebar, full screen) - Add standalone routes outside ProtectedRoute Window system for modals: - AppointmentModal -> AppointmentEditForm + openWindow() - ComposeModal -> MailComposeForm + openWindow() - FilePreviewModal -> FilePreviewContent + openWindow() - Calendar, Mail, Dms use openWindow() instead of modal state
This commit is contained in:
@@ -20,12 +20,13 @@ import { DayView } from '@/components/calendar/DayView';
|
||||
import { RangeView } from '@/components/calendar/RangeView';
|
||||
import { formatDateInput } from '@/utils/date';
|
||||
import { CalendarDetail } from '@/components/calendar/CalendarDetail';
|
||||
import { AppointmentModal } from '@/components/calendar/AppointmentModal';
|
||||
import { AppointmentEditForm } from '@/components/calendar/AppointmentEditForm';
|
||||
import { useWindowStore } from '@/store/windowStore';
|
||||
import { IcsControls } from '@/components/calendar/IcsControls';
|
||||
import { SharingSettings } from '@/components/calendar/SharingSettings';
|
||||
import { useCalendarStore, type CalendarViewMode } from '@/store/calendarStore';
|
||||
import { usePluginToolbarStore } from '@/store/pluginToolbarStore';
|
||||
import { ChevronLeft, ChevronRight, Info, Plus } from 'lucide-react';
|
||||
import { ChevronLeft, ChevronRight, ExternalLink, Info, Plus } from 'lucide-react';
|
||||
import {
|
||||
fetchCalendars,
|
||||
createCalendar,
|
||||
@@ -72,9 +73,7 @@ export function CalendarPage() {
|
||||
const [loadingCalendars, setLoadingCalendars] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [modalEntry, setModalEntry] = useState<CalendarEntry | null>(null);
|
||||
const [prefillDate, setPrefillDate] = useState<Date | null>(null);
|
||||
const openWindow = useWindowStore((s) => s.openWindow);
|
||||
|
||||
const [showSharing, setShowSharing] = useState(false);
|
||||
const [showIcs, setShowIcs] = useState(false);
|
||||
@@ -176,11 +175,44 @@ export function CalendarPage() {
|
||||
|
||||
// ??? Handlers ???????????????????????????????????????????????????????????
|
||||
|
||||
const handleSaved = useCallback(
|
||||
(saved: CalendarEntry) => {
|
||||
setEntries((cur) => {
|
||||
const exists = cur.some((e) => e.id === saved.id);
|
||||
return exists ? cur.map((e) => (e.id === saved.id ? saved : e)) : [...cur, saved];
|
||||
});
|
||||
if (selectedEntry?.id === saved.id) {
|
||||
setSelectedEntry(saved);
|
||||
}
|
||||
},
|
||||
[selectedEntry, setSelectedEntry],
|
||||
);
|
||||
|
||||
const handleDeleted = useCallback(
|
||||
(entryId: string) => {
|
||||
setEntries((cur) => cur.filter((e) => e.id !== entryId));
|
||||
if (selectedEntry?.id === entryId) {
|
||||
setSelectedEntry(null);
|
||||
}
|
||||
},
|
||||
[selectedEntry, setSelectedEntry],
|
||||
);
|
||||
|
||||
const handleCreateAt = useCallback((date: Date) => {
|
||||
setModalEntry(null);
|
||||
setPrefillDate(date);
|
||||
setModalOpen(true);
|
||||
}, []);
|
||||
openWindow({
|
||||
title: t('calendar.newAppointment'),
|
||||
type: 'appointment-create',
|
||||
component: AppointmentEditForm,
|
||||
componentProps: {
|
||||
entry: null,
|
||||
prefillDate: date,
|
||||
calendars,
|
||||
defaultCalendarId: activeCalendarId,
|
||||
onSaved: handleSaved,
|
||||
onDeleted: handleDeleted,
|
||||
},
|
||||
});
|
||||
}, [openWindow, t, calendars, activeCalendarId, handleSaved, handleDeleted]);
|
||||
|
||||
const handleEditEntry = useCallback(
|
||||
(entry: CalendarEntry) => {
|
||||
@@ -194,10 +226,20 @@ export function CalendarPage() {
|
||||
|
||||
const handleOpenEditModal = useCallback(() => {
|
||||
if (!selectedEntry) return;
|
||||
setModalEntry(selectedEntry);
|
||||
setPrefillDate(null);
|
||||
setModalOpen(true);
|
||||
}, [selectedEntry]);
|
||||
openWindow({
|
||||
title: t('calendar.editAppointment'),
|
||||
type: 'appointment-edit',
|
||||
component: AppointmentEditForm,
|
||||
componentProps: {
|
||||
entry: selectedEntry,
|
||||
prefillDate: null,
|
||||
calendars,
|
||||
defaultCalendarId: activeCalendarId,
|
||||
onSaved: handleSaved,
|
||||
onDeleted: handleDeleted,
|
||||
},
|
||||
});
|
||||
}, [selectedEntry, openWindow, t, calendars, activeCalendarId, handleSaved, handleDeleted]);
|
||||
|
||||
const handleMoveEntry = useCallback(
|
||||
async (entry: CalendarEntry, newStart: Date) => {
|
||||
@@ -223,29 +265,6 @@ export function CalendarPage() {
|
||||
[t, loadEntries, selectedEntry, setSelectedEntry],
|
||||
);
|
||||
|
||||
const handleSaved = useCallback(
|
||||
(saved: CalendarEntry) => {
|
||||
setEntries((cur) => {
|
||||
const exists = cur.some((e) => e.id === saved.id);
|
||||
return exists ? cur.map((e) => (e.id === saved.id ? saved : e)) : [...cur, saved];
|
||||
});
|
||||
if (selectedEntry?.id === saved.id) {
|
||||
setSelectedEntry(saved);
|
||||
}
|
||||
},
|
||||
[selectedEntry, setSelectedEntry],
|
||||
);
|
||||
|
||||
const handleDeleted = useCallback(
|
||||
(entryId: string) => {
|
||||
setEntries((cur) => cur.filter((e) => e.id !== entryId));
|
||||
if (selectedEntry?.id === entryId) {
|
||||
setSelectedEntry(null);
|
||||
}
|
||||
},
|
||||
[selectedEntry, setSelectedEntry],
|
||||
);
|
||||
|
||||
const handleConfirmDelete = useCallback(async () => {
|
||||
if (!deleteTarget) return;
|
||||
try {
|
||||
@@ -367,9 +386,19 @@ export function CalendarPage() {
|
||||
label: t('calendar.newAppointment'),
|
||||
group: 'actions',
|
||||
onClick: () => {
|
||||
setModalEntry(null);
|
||||
setPrefillDate(null);
|
||||
setModalOpen(true);
|
||||
openWindow({
|
||||
title: t('calendar.newAppointment'),
|
||||
type: 'appointment-create',
|
||||
component: AppointmentEditForm,
|
||||
componentProps: {
|
||||
entry: null,
|
||||
prefillDate: null,
|
||||
calendars,
|
||||
defaultCalendarId: activeCalendarId,
|
||||
onSaved: handleSaved,
|
||||
onDeleted: handleDeleted,
|
||||
},
|
||||
});
|
||||
},
|
||||
icon: (
|
||||
<Plus className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
@@ -401,6 +430,17 @@ export function CalendarPage() {
|
||||
<Info className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'open-standalone',
|
||||
plugin: 'calendar',
|
||||
label: 'In neuem Fenster',
|
||||
type: 'button' as const,
|
||||
group: 'actions',
|
||||
icon: (
|
||||
<ExternalLink className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
),
|
||||
onClick: () => window.open('/calendar-standalone', '_blank', 'width=1200,height=800'),
|
||||
},
|
||||
];
|
||||
|
||||
registerItems('calendar', items);
|
||||
@@ -685,17 +725,6 @@ export function CalendarPage() {
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
/>
|
||||
|
||||
{/* Appointment modal */}
|
||||
<AppointmentModal
|
||||
open={modalOpen}
|
||||
onClose={() => setModalOpen(false)}
|
||||
entry={modalEntry}
|
||||
prefillDate={prefillDate}
|
||||
calendars={calendars}
|
||||
defaultCalendarId={activeCalendarId}
|
||||
onSaved={handleSaved}
|
||||
onDeleted={handleDeleted}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { CalendarPage } from './Calendar';
|
||||
|
||||
export function CalendarStandalonePage() {
|
||||
return (
|
||||
<div className="h-screen w-screen overflow-hidden bg-white">
|
||||
<CalendarPage />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import { ContactDetail } from '@/components/contacts/ContactDetail';
|
||||
import { ContactEditForm } from '@/components/contacts/ContactEditForm';
|
||||
import { useWindowStore } from '@/store/windowStore';
|
||||
import { SavedFilters } from '@/components/SavedFilters';
|
||||
import { ArrowDownAZ, ArrowUpZA, ChevronLeft, LayoutGrid, List, Plus } from 'lucide-react';
|
||||
import { ArrowDownAZ, ArrowUpZA, ChevronLeft, ExternalLink, LayoutGrid, List, Plus } from 'lucide-react';
|
||||
import {
|
||||
useUnifiedContacts,
|
||||
useUnifiedContact,
|
||||
@@ -201,6 +201,17 @@ export function ContactsListPage() {
|
||||
),
|
||||
onClick: handleCreate,
|
||||
},
|
||||
{
|
||||
id: 'open-standalone',
|
||||
plugin: 'contacts',
|
||||
label: 'In neuem Fenster',
|
||||
type: 'button' as const,
|
||||
group: 'actions',
|
||||
icon: (
|
||||
<ExternalLink className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
),
|
||||
onClick: () => window.open('/contacts-standalone', '_blank', 'width=1200,height=800'),
|
||||
},
|
||||
];
|
||||
registerItems('contacts', items);
|
||||
return () => unregisterPlugin('contacts');
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { ContactsListPage } from './ContactsList';
|
||||
|
||||
export function ContactsStandalonePage() {
|
||||
return (
|
||||
<div className="h-screen w-screen overflow-hidden bg-white">
|
||||
<ContactsListPage />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+33
-14
@@ -20,11 +20,12 @@ import { SourceTree } from '@/components/dms/SourceTree';
|
||||
import { FileExplorer, type ViewMode, type SortBy, type SortOrder } from '@/components/dms/FileExplorer';
|
||||
import { FileDetails } from '@/components/dms/FileDetails';
|
||||
import { UploadDropzone } from '@/components/dms/UploadDropzone';
|
||||
import { FilePreviewModal } from '@/components/dms/FilePreviewModal';
|
||||
import { FilePreviewContent } from '@/components/dms/FilePreviewContent';
|
||||
import { useWindowStore } from '@/store/windowStore';
|
||||
import { ShareDialog } from '@/components/dms/ShareDialog';
|
||||
import { BulkActions } from '@/components/dms/BulkActions';
|
||||
import { usePluginToolbarStore } from '@/store/pluginToolbarStore';
|
||||
import { ArrowRight, ChevronLeft, ChevronRight, Info, Plus, Trash2, Upload } from 'lucide-react';
|
||||
import { ArrowRight, ChevronLeft, ChevronRight, ExternalLink, Info, Plus, Trash2, Upload } from 'lucide-react';
|
||||
import {
|
||||
fetchFolders,
|
||||
createFolder,
|
||||
@@ -72,7 +73,7 @@ export function DmsPage() {
|
||||
// Modal state
|
||||
const [showUpload, setShowUpload] = useState(false);
|
||||
const [showNewFolder, setShowNewFolder] = useState(false);
|
||||
const [previewFile, setPreviewFile] = useState<DmsFile | null>(null);
|
||||
const openWindow = useWindowStore((s) => s.openWindow);
|
||||
const [shareFile, setShareFile] = useState<DmsFile | null>(null);
|
||||
const [deleteTarget, setDeleteTarget] = useState<DmsFile | null>(null);
|
||||
const [submittingFolder, setSubmittingFolder] = useState(false);
|
||||
@@ -195,8 +196,15 @@ export function DmsPage() {
|
||||
|
||||
// Handle file double click (open preview)
|
||||
const handleFileDoubleClick = useCallback((file: DmsFile) => {
|
||||
setPreviewFile(file);
|
||||
}, []);
|
||||
openWindow({
|
||||
title: file.name,
|
||||
type: 'file-preview',
|
||||
component: FilePreviewContent,
|
||||
componentProps: {
|
||||
file,
|
||||
},
|
||||
});
|
||||
}, [openWindow]);
|
||||
|
||||
// Handle file share
|
||||
const handleFileShare = useCallback((file: DmsFile) => {
|
||||
@@ -210,8 +218,15 @@ export function DmsPage() {
|
||||
|
||||
// Handle file preview
|
||||
const handleFilePreview = useCallback((file: DmsFile) => {
|
||||
setPreviewFile(file);
|
||||
}, []);
|
||||
openWindow({
|
||||
title: file.name,
|
||||
type: 'file-preview',
|
||||
component: FilePreviewContent,
|
||||
componentProps: {
|
||||
file,
|
||||
},
|
||||
});
|
||||
}, [openWindow]);
|
||||
|
||||
// Handle toggle select
|
||||
const handleToggleSelect = useCallback((fileId: string) => {
|
||||
@@ -439,6 +454,17 @@ export function DmsPage() {
|
||||
),
|
||||
onClick: () => setShowDetails((v) => !v),
|
||||
},
|
||||
{
|
||||
id: 'open-standalone',
|
||||
plugin: 'dms',
|
||||
label: 'In neuem Fenster',
|
||||
type: 'button' as const,
|
||||
group: 'actions',
|
||||
icon: (
|
||||
<ExternalLink className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
),
|
||||
onClick: () => window.open('/dms-standalone', '_blank', 'width=1200,height=800'),
|
||||
},
|
||||
];
|
||||
|
||||
// Add bulk actions when files are selected
|
||||
@@ -706,13 +732,6 @@ export function DmsPage() {
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
/>
|
||||
|
||||
{/* File preview modal */}
|
||||
<FilePreviewModal
|
||||
open={!!previewFile}
|
||||
file={previewFile}
|
||||
onClose={() => setPreviewFile(null)}
|
||||
/>
|
||||
|
||||
{/* Share dialog */}
|
||||
<ShareDialog
|
||||
open={!!shareFile}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { DmsPage } from './Dms';
|
||||
|
||||
export function DmsStandalonePage() {
|
||||
return (
|
||||
<div className="h-screen w-screen overflow-hidden bg-white">
|
||||
<DmsPage />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+117
-59
@@ -15,9 +15,10 @@ import { ResizablePanel } from '@/components/ui/ResizablePanel';
|
||||
import { MailFolderTree } from '@/components/mail/MailFolderTree';
|
||||
import { MailList } from '@/components/mail/MailList';
|
||||
import { MailDetail } from '@/components/mail/MailDetail';
|
||||
import { ComposeModal, type ComposeMode } from '@/components/mail/ComposeModal';
|
||||
import { MailComposeForm, type ComposeMode } from '@/components/mail/MailComposeForm';
|
||||
import { useWindowStore } from '@/store/windowStore';
|
||||
import { usePluginToolbarStore } from '@/store/pluginToolbarStore';
|
||||
import { ArrowRight, Check, ChevronLeft, Loader2, Plus, Redo2, Trash2, TrendingUp, Undo2 } from 'lucide-react';
|
||||
import { ArrowRight, Check, ChevronLeft, ExternalLink, Loader2, Plus, Redo2, Trash2, TrendingUp, Undo2 } from 'lucide-react';
|
||||
import {
|
||||
fetchAccounts,
|
||||
fetchFolders,
|
||||
@@ -69,11 +70,11 @@ export function MailPage() {
|
||||
const [loadingMails, setLoadingMails] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [signatures, setSignatures] = useState<MailSignature[]>([]);
|
||||
const [composeOpen, setComposeOpen] = useState(false);
|
||||
const [composeMode, setComposeMode] = useState<ComposeMode>('new');
|
||||
const [replyToMail, setReplyToMail] = useState<Mail | null>(null);
|
||||
const [forwardMailState, setForwardMailState] = useState<Mail | null>(null);
|
||||
const [draftMailState, setDraftMailState] = useState<Mail | null>(null);
|
||||
const openWindow = useWindowStore((s) => s.openWindow);
|
||||
const [downloadingAttachmentId, setDownloadingAttachmentId] = useState<string | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [activeView, setActiveView] = useState<'folders' | 'list' | 'detail'>('folders');
|
||||
@@ -233,14 +234,66 @@ export function MailPage() {
|
||||
[toast],
|
||||
);
|
||||
|
||||
// Handle send (compose)
|
||||
const handleSend = useCallback(
|
||||
async (payload: SendMailPayload | ReplyPayload | ForwardPayload, mode: ComposeMode) => {
|
||||
try {
|
||||
if (mode === 'reply' && replyToMail) {
|
||||
await replyMail(replyToMail.id, payload as ReplyPayload);
|
||||
} else if (mode === 'forward' && forwardMailState) {
|
||||
await forwardMail(forwardMailState.id, payload as ForwardPayload);
|
||||
} else {
|
||||
await sendMail(payload as SendMailPayload);
|
||||
}
|
||||
toast.success(t('mail.sent'));
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[replyToMail, forwardMailState, toast, t],
|
||||
);
|
||||
|
||||
// Handle save draft (new or existing)
|
||||
const handleSaveDraft = useCallback(
|
||||
async (payload: MailDraftPayload) => {
|
||||
try {
|
||||
if (composeMode === 'draft' && draftMailState) {
|
||||
await updateDraft(draftMailState.id, payload);
|
||||
} else {
|
||||
await saveDraft(payload);
|
||||
}
|
||||
toast.success(t('mail.draftSaved'));
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[composeMode, draftMailState, toast, t],
|
||||
);
|
||||
|
||||
// Handle compose
|
||||
const handleCompose = useCallback(() => {
|
||||
setComposeMode('new');
|
||||
setReplyToMail(null);
|
||||
setForwardMailState(null);
|
||||
setDraftMailState(null);
|
||||
setComposeOpen(true);
|
||||
}, []);
|
||||
openWindow({
|
||||
title: t('mail.compose'),
|
||||
type: 'mail-compose',
|
||||
component: MailComposeForm,
|
||||
componentProps: {
|
||||
mode: 'new',
|
||||
accountId: selectedAccountId,
|
||||
replyToMail: null,
|
||||
forwardMail: null,
|
||||
draftMail: null,
|
||||
signatures,
|
||||
onSend: handleSend,
|
||||
onSaveDraft: handleSaveDraft,
|
||||
},
|
||||
});
|
||||
}, [openWindow, t, selectedAccountId, signatures, handleSend, handleSaveDraft]);
|
||||
|
||||
// Handle reply
|
||||
const handleReply = useCallback((mail: Mail) => {
|
||||
@@ -248,8 +301,22 @@ export function MailPage() {
|
||||
setReplyToMail(mail);
|
||||
setForwardMailState(null);
|
||||
setDraftMailState(null);
|
||||
setComposeOpen(true);
|
||||
}, []);
|
||||
openWindow({
|
||||
title: t('mail.reply'),
|
||||
type: 'mail-compose',
|
||||
component: MailComposeForm,
|
||||
componentProps: {
|
||||
mode: 'reply',
|
||||
accountId: selectedAccountId,
|
||||
replyToMail: mail,
|
||||
forwardMail: null,
|
||||
draftMail: null,
|
||||
signatures,
|
||||
onSend: handleSend,
|
||||
onSaveDraft: handleSaveDraft,
|
||||
},
|
||||
});
|
||||
}, [openWindow, t, selectedAccountId, signatures, handleSend, handleSaveDraft]);
|
||||
|
||||
// Handle forward
|
||||
const handleForward = useCallback((mail: Mail) => {
|
||||
@@ -257,8 +324,22 @@ export function MailPage() {
|
||||
setForwardMailState(mail);
|
||||
setReplyToMail(null);
|
||||
setDraftMailState(null);
|
||||
setComposeOpen(true);
|
||||
}, []);
|
||||
openWindow({
|
||||
title: t('mail.forward'),
|
||||
type: 'mail-compose',
|
||||
component: MailComposeForm,
|
||||
componentProps: {
|
||||
mode: 'forward',
|
||||
accountId: selectedAccountId,
|
||||
replyToMail: null,
|
||||
forwardMail: mail,
|
||||
draftMail: null,
|
||||
signatures,
|
||||
onSend: handleSend,
|
||||
onSaveDraft: handleSaveDraft,
|
||||
},
|
||||
});
|
||||
}, [openWindow, t, selectedAccountId, signatures, handleSend, handleSaveDraft]);
|
||||
|
||||
// Handle delete single mail
|
||||
const handleDeleteMail = useCallback(
|
||||
@@ -324,23 +405,6 @@ export function MailPage() {
|
||||
[selectedMailIds, toast, t],
|
||||
);
|
||||
|
||||
// Handle save draft (new or existing)
|
||||
const handleSaveDraft = useCallback(
|
||||
async (payload: MailDraftPayload) => {
|
||||
try {
|
||||
if (composeMode === 'draft' && draftMailState) {
|
||||
await updateDraft(draftMailState.id, payload);
|
||||
} else {
|
||||
await saveDraft(payload);
|
||||
}
|
||||
toast.success(t('mail.draftSaved'));
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[composeMode, draftMailState, toast, t],
|
||||
);
|
||||
|
||||
// Handle edit draft
|
||||
const handleEditDraft = useCallback((mail: Mail) => {
|
||||
@@ -348,8 +412,22 @@ export function MailPage() {
|
||||
setDraftMailState(mail);
|
||||
setReplyToMail(null);
|
||||
setForwardMailState(null);
|
||||
setComposeOpen(true);
|
||||
}, []);
|
||||
openWindow({
|
||||
title: t('mail.editDraft'),
|
||||
type: 'mail-compose',
|
||||
component: MailComposeForm,
|
||||
componentProps: {
|
||||
mode: 'draft',
|
||||
accountId: selectedAccountId,
|
||||
replyToMail: null,
|
||||
forwardMail: null,
|
||||
draftMail: mail,
|
||||
signatures,
|
||||
onSend: handleSend,
|
||||
onSaveDraft: handleSaveDraft,
|
||||
},
|
||||
});
|
||||
}, [openWindow, t, selectedAccountId, signatures, handleSend, handleSaveDraft]);
|
||||
|
||||
// Handle toggle flag
|
||||
const handleToggleFlag = useCallback(
|
||||
@@ -486,25 +564,6 @@ export function MailPage() {
|
||||
[toast],
|
||||
);
|
||||
|
||||
// Handle send (compose)
|
||||
const handleSend = useCallback(
|
||||
async (payload: SendMailPayload | ReplyPayload | ForwardPayload, mode: ComposeMode) => {
|
||||
try {
|
||||
if (mode === 'reply' && replyToMail) {
|
||||
await replyMail(replyToMail.id, payload as ReplyPayload);
|
||||
} else if (mode === 'forward' && forwardMailState) {
|
||||
await forwardMail(forwardMailState.id, payload as ForwardPayload);
|
||||
} else {
|
||||
await sendMail(payload as SendMailPayload);
|
||||
}
|
||||
toast.success(t('mail.sent'));
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[replyToMail, forwardMailState, toast, t],
|
||||
);
|
||||
|
||||
// Handle search
|
||||
const handleSearch = useCallback((query: string) => {
|
||||
@@ -632,6 +691,17 @@ export function MailPage() {
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'open-standalone',
|
||||
plugin: 'mail',
|
||||
label: 'In neuem Fenster',
|
||||
type: 'button' as const,
|
||||
group: 'tools',
|
||||
icon: (
|
||||
<ExternalLink className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
),
|
||||
onClick: () => window.open('/mail-standalone', '_blank', 'width=1200,height=800'),
|
||||
},
|
||||
];
|
||||
if (hasBulkSelection) {
|
||||
items.push(
|
||||
@@ -935,18 +1005,6 @@ export function MailPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ComposeModal
|
||||
open={composeOpen}
|
||||
mode={composeMode}
|
||||
accountId={selectedAccountId}
|
||||
replyToMail={replyToMail}
|
||||
forwardMail={forwardMailState}
|
||||
draftMail={draftMailState}
|
||||
signatures={signatures}
|
||||
onSend={handleSend}
|
||||
onSaveDraft={handleSaveDraft}
|
||||
onClose={() => setComposeOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { MailPage } from './Mail';
|
||||
|
||||
export function MailStandalonePage() {
|
||||
return (
|
||||
<div className="h-screen w-screen overflow-hidden bg-white">
|
||||
<MailPage />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user