2026-05-04 16:46:14 +03:00

1363 lines
31 KiB
Vue

<template>
<webmailing-template>
<template #webmailing-sidebar>
<aside class="webmail-panel webmail-panel--sidebar">
<soft-button
color="primary"
variant="gradient"
size="sm"
class="webmail-compose"
@click="openCompose"
>
<i class="fas fa-pen me-2"></i>
<span>Compose</span>
</soft-button>
<nav class="webmail-sidebar-group">
<button
v-for="folder in folders"
:key="folder.id"
class="webmail-sidebar-item"
:class="{ 'is-active': selectedFolder === folder.id }"
type="button"
@click="selectedFolder = folder.id"
>
<span class="webmail-sidebar-item__content">
<span class="webmail-sidebar-item__icon">
<i :class="folder.icon"></i>
</span>
<span class="webmail-sidebar-item__label">{{
folder.label
}}</span>
</span>
<soft-badge
v-if="folder.count !== undefined"
:color="selectedFolder === folder.id ? 'primary' : 'secondary'"
:variant="selectedFolder === folder.id ? 'gradient' : 'fill'"
size="sm"
>
{{ folder.count }}
</soft-badge>
</button>
</nav>
<section class="webmail-sidebar-section">
<div class="webmail-sidebar-section__head">
<span>Labels</span>
</div>
<div class="webmail-sidebar-section__list">
<button
v-for="label in labels"
:key="label.id"
class="webmail-label-item"
type="button"
>
<span class="webmail-label-item__meta">
<span
class="webmail-label-item__dot"
:style="{ background: label.color }"
></span>
<span class="webmail-label-item__text">{{ label.label }}</span>
</span>
<soft-badge color="secondary" variant="fill" size="sm">
{{ label.count }}
</soft-badge>
</button>
</div>
</section>
<section class="webmail-storage-card">
<div class="webmail-storage-card__top">
<div>
<h4>Mailbox</h4>
<p>{{ stats.total }} messages synchronised</p>
</div>
<soft-badge color="info" variant="gradient" size="sm">
{{
webmailStore.isSyncing ? "sync..." : `${stats.unread} unread`
}}
</soft-badge>
</div>
<soft-progress
:percentage="storagePercentage"
color="info"
variant="gradient"
/>
<button
class="webmail-storage-card__button"
type="button"
:disabled="webmailStore.isSyncing"
@click="refreshMailbox"
>
{{
webmailStore.isSyncing ? "Syncing mailbox..." : "Refresh inbox"
}}
</button>
</section>
</aside>
</template>
<template #webmailing-list>
<section class="webmail-panel webmail-panel--list">
<div class="webmail-list-tabs">
<button
v-for="tab in tabs"
:key="tab.id"
class="webmail-tab-button"
:class="{ 'is-active': activeTab === tab.id }"
type="button"
@click="activeTab = tab.id"
>
<span class="webmail-tab-button__meta">
<i :class="tab.icon"></i>
<span>{{ tab.label }}</span>
</span>
<span class="webmail-tab-button__count">({{ tab.count }})</span>
</button>
</div>
<div v-if="webmailStore.isLoading" class="webmail-empty-state">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<div
v-else-if="storeError"
class="webmail-empty-state webmail-empty-state--error"
>
<p>{{ storeError }}</p>
<soft-button color="primary" size="sm" @click="refreshMailbox">
Retry
</soft-button>
</div>
<div v-else-if="!filteredMessages.length" class="webmail-empty-state">
<p>No messages available in this folder.</p>
</div>
<div v-else class="webmail-list-scroll">
<button
v-for="message in filteredMessages"
:key="message.id"
class="webmail-message-item"
:class="{ 'is-active': selectedMessageId === message.id }"
type="button"
@click="selectMessage(message)"
>
<span
class="webmail-message-item__dot"
:class="{ 'is-read': message.is_read }"
></span>
<span class="webmail-message-item__content">
<span class="webmail-message-item__head">
<span class="webmail-message-item__sender">{{
getSenderLabel(message)
}}</span>
<span class="webmail-message-item__time">{{
getMessageTime(message)
}}</span>
</span>
<span class="webmail-message-item__subject">{{
message.subject || "(No subject)"
}}</span>
<span class="webmail-message-item__preview">{{
message.snippet || "No preview available."
}}</span>
</span>
</button>
</div>
</section>
</template>
<template #webmailing-detail>
<section class="webmail-panel webmail-panel--detail">
<div class="webmail-toolbar">
<div class="webmail-toolbar__group">
<soft-button
color="light"
size="sm"
class="btn-icon-only webmail-icon-button"
@click="openCompose"
>
<i class="fas fa-pen"></i>
</soft-button>
</div>
<div class="webmail-toolbar__group">
<soft-button
v-for="action in toolbarActions"
:key="action.id"
color="light"
size="sm"
class="btn-icon-only webmail-icon-button"
@click="handleToolbarAction(action.id)"
>
<i :class="action.icon"></i>
</soft-button>
</div>
</div>
<div v-if="isComposeMode" class="webmail-detail-scroll">
<div class="webmail-compose-panel">
<div class="webmail-compose-panel__head">
<div>
<h2>Compose message</h2>
<p>Send a message using the Laravel Webmail API.</p>
</div>
<soft-button color="light" size="sm" @click="cancelCompose">
Cancel
</soft-button>
</div>
<div class="row g-3">
<div class="col-12">
<label class="webmail-field-label">To</label>
<soft-input
v-model="composeForm.to"
placeholder="client@example.com, second@example.com"
/>
</div>
<div class="col-md-6">
<label class="webmail-field-label">Cc</label>
<soft-input v-model="composeForm.cc" placeholder="Optional" />
</div>
<div class="col-md-6">
<label class="webmail-field-label">Bcc</label>
<soft-input v-model="composeForm.bcc" placeholder="Optional" />
</div>
<div class="col-12">
<label class="webmail-field-label">Subject</label>
<soft-input
v-model="composeForm.subject"
placeholder="Subject"
/>
</div>
<div class="col-12">
<label class="webmail-field-label">Message</label>
<textarea
v-model="composeForm.body"
class="form-control webmail-compose-textarea"
rows="12"
placeholder="Write your message here..."
></textarea>
</div>
</div>
<div class="webmail-message-actions mt-4">
<soft-button color="light" @click="cancelCompose">
Discard
</soft-button>
<soft-button
color="primary"
variant="gradient"
:disabled="webmailStore.isSending"
@click="submitCompose"
>
<i class="fas fa-paper-plane me-2"></i>
{{ webmailStore.isSending ? "Sending..." : "Send" }}
</soft-button>
</div>
</div>
</div>
<div v-else-if="currentMessage" class="webmail-detail-scroll">
<div class="webmail-detail-head">
<div class="webmail-detail-head__main">
<div class="webmail-detail-head__title-row">
<h2>{{ currentMessage.subject || "(No subject)" }}</h2>
<soft-button
color="light"
class="btn-icon-only webmail-bookmark"
@click="toggleStarred"
>
<i
:class="
currentMessage.is_starred
? 'fas fa-star text-warning'
: 'far fa-star'
"
></i>
</soft-button>
</div>
<div class="webmail-sender-card">
<soft-avatar
:img="fallbackAvatar"
:alt="getSenderLabel(currentMessage)"
size="sm"
shadow="sm"
border-radius="lg"
/>
<div class="webmail-sender-card__copy">
<p>{{ getPrimaryEmail(currentMessage) }}</p>
<button class="webmail-sender-card__meta" type="button">
{{
currentMessage.direction === "outgoing"
? "to recipients"
: "to me"
}}
<i class="fas fa-chevron-down"></i>
</button>
</div>
</div>
</div>
<div class="webmail-detail-head__aside">
<div class="webmail-detail-head__actions">
<soft-button
v-for="action in messageActions"
:key="action.id"
color="light"
size="sm"
class="btn-icon-only webmail-icon-button"
@click="handleMessageAction(action.id)"
>
<i :class="action.icon"></i>
</soft-button>
</div>
<p>{{ getFullDate(currentMessage) }}</p>
</div>
</div>
<article class="webmail-message-body">
<p
v-for="paragraph in getBodyParagraphs(currentMessage)"
:key="paragraph"
>
{{ paragraph }}
</p>
</article>
<div class="webmail-message-actions">
<soft-button
color="light"
class="webmail-message-actions__button"
@click="replyToMessage"
>
<i class="fas fa-reply me-2"></i>
<span>Reply</span>
</soft-button>
<soft-button
color="light"
class="webmail-message-actions__button"
@click="forwardMessage"
>
<i class="fas fa-share me-2"></i>
<span>Forward</span>
</soft-button>
</div>
</div>
<div v-else class="webmail-empty-state">
<p>Select a message or start composing.</p>
</div>
</section>
</template>
</webmailing-template>
</template>
<script setup>
import { computed, onMounted, ref, watch } from "vue";
import SoftAvatar from "@/components/SoftAvatar.vue";
import SoftBadge from "@/components/SoftBadge.vue";
import SoftButton from "@/components/SoftButton.vue";
import SoftInput from "@/components/SoftInput.vue";
import SoftProgress from "@/components/SoftProgress.vue";
import WebmailingTemplate from "@/components/templates/Webmailing/WebmailingTemplate.vue";
import { useNotificationStore } from "@/stores/notification";
import { useWebmailStore } from "@/stores/webmailStore";
const webmailStore = useWebmailStore();
const notificationStore = useNotificationStore();
const selectedFolder = ref("inbox");
const activeTab = ref("all");
const selectedMessageId = ref(null);
const isComposeMode = ref(false);
const fallbackAvatar = "https://via.placeholder.com/80x80.png?text=M";
const composeForm = ref({
to: "",
cc: "",
bcc: "",
subject: "",
body: "",
});
const toolbarActions = [
{ id: "refresh", icon: "fas fa-rotate-right" },
{ id: "toggle-read", icon: "fas fa-envelope-open" },
{ id: "toggle-star", icon: "fas fa-star" },
{ id: "delete", icon: "fas fa-trash" },
];
const messageActions = [
{ id: "reply", icon: "fas fa-reply" },
{ id: "forward", icon: "fas fa-share" },
{ id: "delete", icon: "fas fa-trash" },
{ id: "toggle-star", icon: "fas fa-star" },
];
const stats = computed(() => webmailStore.getStats);
const storeError = computed(() => webmailStore.error);
const folders = computed(() => [
{
id: "inbox",
label: "Inbox",
icon: "fas fa-inbox",
count: stats.value.inbox,
},
{
id: "important",
label: "Important",
icon: "fas fa-bookmark",
count: stats.value.starred,
},
{
id: "drafts",
label: "Drafts",
icon: "fas fa-file-alt",
count: stats.value.drafts,
},
{
id: "sent",
label: "Sent",
icon: "fas fa-paper-plane",
count: stats.value.sent,
},
{
id: "trash",
label: "Trash",
icon: "fas fa-trash",
count: stats.value.trash,
},
{
id: "all-mail",
label: "All Mail",
icon: "fas fa-box-archive",
count: stats.value.total,
},
]);
const labels = computed(() => [
{
id: "unread",
label: "Unread",
color: "#5e72e4",
count: stats.value.unread,
},
{
id: "starred",
label: "Starred",
color: "#f53939",
count: stats.value.starred,
},
]);
const tabs = computed(() => [
{
id: "all",
label: "All",
icon: "fas fa-list",
count: webmailStore.allMessages.length,
},
{
id: "unread",
label: "Unread",
icon: "fas fa-envelope",
count: webmailStore.allMessages.filter((message) => !message.is_read)
.length,
},
]);
const filteredMessages = computed(() => {
if (activeTab.value === "unread") {
return webmailStore.allMessages.filter((message) => !message.is_read);
}
return webmailStore.allMessages;
});
const currentMessage = computed(() => {
if (selectedMessageId.value) {
const fromList = filteredMessages.value.find(
(message) => message.id === selectedMessageId.value
);
if (fromList) {
return fromList;
}
}
return webmailStore.selectedMessage;
});
const storagePercentage = computed(() => {
const total = Math.max(stats.value.total, 1);
return Math.min(100, Math.round((stats.value.unread / total) * 100));
});
const parseEmails = (value) =>
value
.split(",")
.map((item) => item.trim())
.filter(Boolean);
const buildFolderParams = () => {
if (selectedFolder.value === "all-mail") {
return {};
}
if (selectedFolder.value === "important") {
return { starred: true };
}
return { folder: selectedFolder.value };
};
const getSenderLabel = (message) => {
if (message.direction === "outgoing") {
return `To: ${message.to?.[0] || "Unknown"}`;
}
return message.from_name || message.from_email || "Unknown sender";
};
const getPrimaryEmail = (message) => {
if (message.direction === "outgoing") {
return message.to?.join(", ") || "No recipients";
}
return message.from_email || "Unknown sender";
};
const getMessageDateValue = (message) =>
message.received_at || message.sent_at || message.created_at;
const getMessageTime = (message) => {
const dateValue = getMessageDateValue(message);
if (!dateValue) {
return "-";
}
const date = new Date(dateValue);
return date.toLocaleDateString("en-GB", {
day: "2-digit",
month: "short",
});
};
const getFullDate = (message) => {
const dateValue = getMessageDateValue(message);
if (!dateValue) {
return "Unknown date";
}
const date = new Date(dateValue);
return date.toLocaleString("en-GB", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
};
const getBodyParagraphs = (message) => {
const body = (message?.body || "").trim();
if (!body) {
return ["No content available."];
}
return body
.split(/\n{2,}/)
.map((paragraph) => paragraph.trim())
.filter(Boolean);
};
const resetComposeForm = () => {
composeForm.value = {
to: "",
cc: "",
bcc: "",
subject: "",
body: "",
};
};
const syncMailbox = async () => {
await Promise.all([
webmailStore.fetchMessages(buildFolderParams()),
webmailStore.fetchStats(),
webmailStore.fetchMailboxSettings(),
]);
selectedMessageId.value = webmailStore.selectedMessage?.id || null;
};
const refreshMailbox = async () => {
try {
const result = await webmailStore.syncMailbox(buildFolderParams());
if (result.imported > 0) {
notificationStore.success(
"Webmail",
`${result.imported} new message(s) imported from ${result.source}.`
);
return;
}
if (result.skipped > 0) {
notificationStore.info(
"Webmail",
"No new messages found in the configured mailbox."
);
return;
}
await syncMailbox();
} catch {
notificationStore.error(
"Webmail",
"Unable to sync messages from the configured mailbox."
);
}
};
const selectMessage = async (message) => {
isComposeMode.value = false;
selectedMessageId.value = message.id;
try {
await webmailStore.fetchMessage(message.id);
if (!message.is_read) {
await webmailStore.updateMessage(message.id, { is_read: true });
}
} catch {
notificationStore.error("Webmail", "Unable to load the selected message.");
}
};
const openCompose = () => {
isComposeMode.value = true;
selectedMessageId.value = null;
};
const cancelCompose = () => {
isComposeMode.value = false;
resetComposeForm();
selectedMessageId.value = webmailStore.selectedMessage?.id || null;
};
const submitCompose = async () => {
const to = parseEmails(composeForm.value.to);
const cc = parseEmails(composeForm.value.cc);
const bcc = parseEmails(composeForm.value.bcc);
if (!to.length) {
notificationStore.warning(
"Webmail",
"Please provide at least one recipient."
);
return;
}
if (!composeForm.value.body.trim()) {
notificationStore.warning("Webmail", "Message content is required.");
return;
}
try {
await webmailStore.sendMessage({
to,
cc,
bcc,
subject: composeForm.value.subject,
body: composeForm.value.body,
folder: "sent",
});
notificationStore.success("Webmail", "Message sent successfully.");
resetComposeForm();
isComposeMode.value = false;
selectedFolder.value = "sent";
await syncMailbox();
} catch {
notificationStore.error("Webmail", "Failed to send the message.");
}
};
const toggleStarred = async () => {
if (!currentMessage.value) {
return;
}
try {
await webmailStore.updateMessage(currentMessage.value.id, {
is_starred: !currentMessage.value.is_starred,
});
} catch {
notificationStore.error("Webmail", "Unable to update the starred state.");
}
};
const replyToMessage = () => {
if (!currentMessage.value) {
return;
}
openCompose();
composeForm.value.to = currentMessage.value.from_email || "";
composeForm.value.subject = currentMessage.value.subject
? `Re: ${currentMessage.value.subject}`
: "Re:";
};
const forwardMessage = () => {
if (!currentMessage.value) {
return;
}
openCompose();
composeForm.value.subject = currentMessage.value.subject
? `Fwd: ${currentMessage.value.subject}`
: "Fwd:";
composeForm.value.body = getBodyParagraphs(currentMessage.value).join("\n\n");
};
const handleToolbarAction = async (actionId) => {
if (actionId === "refresh") {
await refreshMailbox();
return;
}
if (!currentMessage.value) {
return;
}
if (actionId === "toggle-read") {
try {
await webmailStore.updateMessage(currentMessage.value.id, {
is_read: !currentMessage.value.is_read,
});
} catch {
notificationStore.error("Webmail", "Unable to update read status.");
}
return;
}
if (actionId === "toggle-star") {
await toggleStarred();
return;
}
if (actionId === "delete") {
try {
await webmailStore.deleteMessage(currentMessage.value.id);
notificationStore.success("Webmail", "Message deleted.");
selectedMessageId.value = webmailStore.selectedMessage?.id || null;
} catch {
notificationStore.error("Webmail", "Unable to delete this message.");
}
}
};
const handleMessageAction = async (actionId) => {
if (actionId === "reply") {
replyToMessage();
return;
}
if (actionId === "forward") {
forwardMessage();
return;
}
if (actionId === "toggle-star") {
await toggleStarred();
return;
}
if (actionId === "delete") {
await handleToolbarAction("delete");
}
};
watch(selectedFolder, async () => {
activeTab.value = "all";
isComposeMode.value = false;
try {
await syncMailbox();
} catch {
notificationStore.error("Webmail", "Unable to load messages.");
}
});
watch(filteredMessages, (messages) => {
if (!messages.length) {
selectedMessageId.value = null;
return;
}
const exists = messages.some(
(message) => message.id === selectedMessageId.value
);
if (!exists && !isComposeMode.value) {
selectedMessageId.value = messages[0].id;
}
});
onMounted(async () => {
try {
await syncMailbox();
} catch {
notificationStore.error("Webmail", "Unable to load messages.");
}
});
</script>
<style scoped>
.webmail-panel {
min-width: 0;
background: #ffffff;
}
.webmail-panel--sidebar,
.webmail-panel--list {
border-right: 1px solid #f0f2f5;
}
.webmail-panel--sidebar {
display: flex;
flex-direction: column;
gap: 1.1rem;
padding: 1rem;
background: #fbfcfd;
}
.webmail-compose {
border-radius: 0.75rem !important;
padding: 0.7rem 0.9rem !important;
display: inline-flex !important;
align-items: center;
justify-content: center;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.webmail-sidebar-item {
width: 100%;
border: 0;
background: transparent;
color: #67748e;
border-radius: 0.75rem;
padding: 0.7rem 0.75rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.55rem;
font-size: 0.75rem;
transition: background-color 0.2s ease, color 0.2s ease;
}
.webmail-sidebar-item:hover {
background: #ffffff;
color: #344767;
}
.webmail-sidebar-item.is-active {
background: rgba(94, 114, 228, 0.12);
color: #344767;
}
.webmail-sidebar-item__content {
display: inline-flex;
align-items: center;
gap: 0.8rem;
min-width: 0;
}
.webmail-sidebar-item__icon {
display: inline-flex;
width: 0.95rem;
justify-content: center;
}
.webmail-sidebar-item__label {
font-size: 0.8rem;
font-weight: 600;
white-space: nowrap;
}
.webmail-sidebar-group,
.webmail-sidebar-section__list {
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.webmail-sidebar-section {
display: flex;
flex-direction: column;
gap: 0.45rem;
}
.webmail-sidebar-section__head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 0.35rem;
color: #8392ab;
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 0.65rem;
font-weight: 700;
}
.webmail-label-item {
width: 100%;
border: 0;
background: transparent;
border-radius: 0.75rem;
padding: 0.6rem 0.7rem;
display: flex;
align-items: center;
justify-content: space-between;
color: #67748e;
transition: background-color 0.2s ease, color 0.2s ease;
}
.webmail-label-item:hover {
background: #ffffff;
color: #344767;
}
.webmail-label-item__meta {
display: inline-flex;
align-items: center;
gap: 0.7rem;
}
.webmail-label-item__dot {
width: 0.7rem;
height: 0.7rem;
border-radius: 0.25rem;
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.9);
}
.webmail-label-item__text {
font-size: 0.78rem;
font-weight: 600;
}
.webmail-storage-card {
margin-top: auto;
padding: 0.9rem;
border-radius: 0.9rem;
background: #ffffff;
border: 1px solid #f0f2f5;
}
.webmail-storage-card__top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 0.75rem;
}
.webmail-storage-card__top h4 {
margin: 0 0 0.25rem;
color: #344767;
font-size: 0.8rem;
font-weight: 700;
}
.webmail-storage-card__top p {
margin: 0;
color: #8392ab;
font-size: 0.7rem;
}
.webmail-storage-card :deep(.progress) {
height: 0.4rem;
margin: 1rem 0 0.85rem;
background: #eaecf4;
border-radius: 999px;
}
.webmail-storage-card__button {
border: 0;
padding: 0;
background: transparent;
color: #5e72e4;
font-size: 0.72rem;
font-weight: 700;
}
.webmail-panel--list {
display: flex;
flex-direction: column;
}
.webmail-list-tabs {
display: flex;
gap: 1rem;
padding: 0 1rem;
border-bottom: 1px solid #f0f2f5;
}
.webmail-tab-button {
position: relative;
border: 0;
background: transparent;
padding: 0.75rem 0;
color: #8392ab;
display: inline-flex;
align-items: center;
gap: 0.45rem;
font-size: 0.78rem;
font-weight: 600;
}
.webmail-tab-button::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: -1px;
height: 3px;
border-radius: 999px;
background: transparent;
transition: background-color 0.2s ease;
}
.webmail-tab-button.is-active {
color: #344767;
}
.webmail-tab-button.is-active::after {
background: #5e72e4;
}
.webmail-tab-button__meta {
display: inline-flex;
align-items: center;
gap: 0.45rem;
}
.webmail-tab-button__count {
color: inherit;
}
.webmail-list-scroll,
.webmail-detail-scroll {
overflow-y: auto;
min-height: 0;
}
.webmail-empty-state {
min-height: 16rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.75rem;
color: #67748e;
font-size: 0.82rem;
padding: 1rem;
text-align: center;
}
.webmail-empty-state--error {
color: #f53939;
}
.webmail-message-item {
width: 100%;
border: 0;
background: transparent;
display: grid;
grid-template-columns: auto 1fr;
gap: 0.75rem;
padding: 0.85rem 1rem;
border-bottom: 1px solid #f0f2f5;
text-align: left;
transition: background-color 0.2s ease, transform 0.2s ease;
}
.webmail-message-item:hover {
background: #f8f9fa;
}
.webmail-message-item.is-active {
background: rgba(94, 114, 228, 0.08);
}
.webmail-message-item__dot {
width: 0.65rem;
height: 0.65rem;
margin-top: 0.4rem;
border-radius: 999px;
background: #5e72e4;
box-shadow: 0 0 0 4px rgba(94, 114, 228, 0.12);
}
.webmail-message-item__dot.is-read {
background: #cbd5e1;
box-shadow: none;
}
.webmail-message-item__content {
min-width: 0;
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.webmail-message-item__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.webmail-message-item__sender {
color: #344767;
font-size: 0.76rem;
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.webmail-message-item__time {
color: #8392ab;
font-size: 0.7rem;
white-space: nowrap;
}
.webmail-message-item__subject {
color: #344767;
font-size: 0.84rem;
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.webmail-message-item__preview {
color: #67748e;
font-size: 0.74rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.webmail-panel--detail {
display: flex;
flex-direction: column;
min-width: 0;
}
.webmail-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
border-bottom: 1px solid #f0f2f5;
}
.webmail-toolbar__group,
.webmail-detail-head__actions,
.webmail-message-actions {
display: flex;
align-items: center;
gap: 0.65rem;
}
.webmail-icon-button,
.webmail-bookmark {
width: 2rem;
height: 2rem;
padding: 0 !important;
display: inline-flex !important;
align-items: center;
justify-content: center;
border-radius: 0.5rem !important;
box-shadow: none !important;
}
.webmail-detail-scroll {
padding: 1.25rem;
}
.webmail-detail-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.5rem;
}
.webmail-detail-head__main {
min-width: 0;
}
.webmail-detail-head__title-row {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1rem;
}
.webmail-detail-head__title-row h2 {
margin: 0;
color: #344767;
font-size: 1.1rem;
font-weight: 700;
}
.webmail-bookmark {
color: #94a3b8;
}
.webmail-sender-card {
display: flex;
align-items: center;
gap: 0.9rem;
}
.webmail-sender-card__copy p {
margin: 0 0 0.22rem;
color: #344767;
font-size: 0.82rem;
font-weight: 700;
}
.webmail-sender-card__meta {
border: 0;
padding: 0;
background: transparent;
color: #67748e;
display: inline-flex;
align-items: center;
gap: 0.25rem;
font-size: 0.72rem;
}
.webmail-detail-head__aside {
text-align: right;
}
.webmail-detail-head__aside p {
margin: 0.75rem 0 0;
color: #8392ab;
font-size: 0.72rem;
font-weight: 600;
}
.webmail-message-body {
max-width: 48rem;
margin-bottom: 1.75rem;
color: #67748e;
font-size: 0.84rem;
line-height: 1.7;
}
.webmail-message-body p {
margin-bottom: 1rem;
white-space: pre-wrap;
}
.webmail-compose-panel {
max-width: 48rem;
}
.webmail-compose-panel__head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.25rem;
}
.webmail-compose-panel__head h2 {
margin: 0 0 0.3rem;
color: #344767;
font-size: 1.05rem;
font-weight: 700;
}
.webmail-compose-panel__head p {
margin: 0;
color: #8392ab;
font-size: 0.75rem;
}
.webmail-field-label {
display: block;
margin-bottom: 0.35rem;
color: #344767;
font-size: 0.75rem;
font-weight: 600;
}
.webmail-compose-textarea {
min-height: 15rem;
font-size: 0.85rem;
color: #344767;
border-color: #d2d6da;
}
.webmail-message-actions {
padding-top: 1.1rem;
border-top: 1px solid #f0f2f5;
}
.webmail-message-actions__button {
display: inline-flex;
align-items: center;
gap: 0.55rem;
border-radius: 0.65rem !important;
padding: 0.65rem 0.9rem !important;
font-size: 0.75rem;
}
@media (max-width: 992px) {
.webmail-panel--sidebar,
.webmail-panel--list {
border-right: 0;
border-bottom: 1px solid #f0f2f5;
}
}
@media (max-width: 768px) {
.webmail-detail-head,
.webmail-compose-panel__head {
flex-direction: column;
}
.webmail-detail-head__aside {
width: 100%;
text-align: left;
}
.webmail-toolbar,
.webmail-detail-scroll {
padding-inline: 1rem;
}
}
</style>