mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
feat: write Mountain View replies in place under the conversation
This commit is contained in:
@@ -2,7 +2,7 @@ import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import React from 'react';
|
||||
import { EmailComposer } from '../email-composer';
|
||||
import { MvComposeWindow } from '../mv-compose-window';
|
||||
import { MvComposeInline, MvComposeWindow } from '../mv-compose-window';
|
||||
|
||||
// ─── Heavy component mocks (mirrors reply-addressing.test.tsx) ────────────────
|
||||
|
||||
@@ -227,6 +227,16 @@ describe('composer in the Mountain View compose window', () => {
|
||||
expect(editor).toHaveAttribute('data-placement', 'bottom');
|
||||
});
|
||||
|
||||
it('writes a reply in place with a bar that pops it out, and no window controls', () => {
|
||||
const onPopOut = vi.fn();
|
||||
render(<MvComposeInline onPopOut={onPopOut}><EmailComposer initialData={{ ...DATA, mode: 'reply' as never }} mode="reply" /></MvComposeInline>);
|
||||
expect(document.querySelector('[data-mv-compose-inline-bar]')).not.toBeNull();
|
||||
expect(screen.queryByRole('button', { name: 'minimize' })).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'pop_out' }));
|
||||
expect(onPopOut).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('keeps its own header and a top toolbar outside the window', () => {
|
||||
render(<EmailComposer initialData={DATA} />);
|
||||
expect(document.querySelector('[data-mv-compose-titlebar]')).toBeNull();
|
||||
|
||||
@@ -29,6 +29,10 @@ import {
|
||||
Maximize2,
|
||||
Minimize2,
|
||||
Trash2,
|
||||
ExternalLink,
|
||||
ReplyAll,
|
||||
Reply,
|
||||
Forward,
|
||||
} from "@/components/icons";
|
||||
import { ComposerToolbarMenu, ComposerToolbarMenuItem, ComposerToolbarMenuCheckItem } from "@/components/email/composer-toolbar-menu";
|
||||
import { cn, formatFileSize, formatDateTime, generateUUID } from "@/lib/utils";
|
||||
@@ -3012,7 +3016,39 @@ export function EmailComposer({
|
||||
)}
|
||||
{/* Header - mobile: clean bar with close/send, desktop: title bar.
|
||||
In Mountain View's window it is the window's title bar instead. */}
|
||||
{mvWindow ? (
|
||||
{mvWindow?.variant === "inline" ? (
|
||||
<div className="flex items-center gap-2 h-10 ps-4 pe-1.5 shrink-0 border-b border-border/50" data-mv-compose-inline-bar="">
|
||||
{mode === 'replyAll'
|
||||
? <ReplyAll className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
||||
: mode === 'forward'
|
||||
? <Forward className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
||||
: <Reply className="w-4 h-4 text-muted-foreground flex-shrink-0" />}
|
||||
<h3 className="flex-1 min-w-0 truncate text-sm" data-testid="composer-save-status" data-status={saveStatus}>
|
||||
{mode === 'reply' ? t('reply') : mode === 'replyAll' ? t('reply_all') : mode === 'forward' ? t('forward') : t('new_message')}
|
||||
</h3>
|
||||
{saveStatus === 'saving' && <Save className="w-3.5 h-3.5 animate-pulse text-muted-foreground flex-shrink-0" aria-label={t('saving')} />}
|
||||
{mvWindow.popOut && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={mvWindow.popOut}
|
||||
className="grid h-8 w-8 place-items-center rounded-full text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||
aria-label={t('pop_out')}
|
||||
title={t('pop_out')}
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleClose()}
|
||||
className="grid h-8 w-8 place-items-center rounded-full text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||
aria-label={tCommon('close')}
|
||||
title={tCommon('close')}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
) : mvWindow ? (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-1 h-10 ps-4 pe-1.5 shrink-0 bg-[var(--mv-search-bg)]",
|
||||
|
||||
@@ -166,6 +166,8 @@ interface EmailViewerProps {
|
||||
*/
|
||||
mvConversation?: Email[];
|
||||
onOpenConversationEmail?: (email: Email) => void;
|
||||
/** Mountain View: a reply being written in place, shown under the conversation instead of the reply buttons. */
|
||||
mvInlineComposer?: React.ReactNode;
|
||||
onShowShortcuts?: () => void;
|
||||
onEditDraft?: () => void;
|
||||
onCancelScheduledForEdit?: () => void;
|
||||
@@ -809,6 +811,7 @@ export function EmailViewer({
|
||||
mvFolderName,
|
||||
mvConversation,
|
||||
onOpenConversationEmail,
|
||||
mvInlineComposer,
|
||||
onShowShortcuts,
|
||||
onEditDraft,
|
||||
onCancelScheduledForEdit,
|
||||
@@ -5551,8 +5554,15 @@ export function EmailViewer({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mountain View: a reply being written in place. */}
|
||||
{mvViewer && mvInlineComposer && (
|
||||
<div className="px-6 lg:ps-[80px] pt-6 pb-6" data-mv-inline-reply="" ref={(el) => el?.scrollIntoView({ block: 'nearest' })}>
|
||||
{mvInlineComposer}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mountain View: Reply / Reply all / Forward as buttons under the message. */}
|
||||
{mvReplyRow && !isDraft && !isScheduled && !isBodyLoading && (effectiveEmailContent.isHtml ? iframeReady : true) && (
|
||||
{mvReplyRow && !(mvViewer && mvInlineComposer) && !isDraft && !isScheduled && !isBodyLoading && (effectiveEmailContent.isHtml ? iframeReady : true) && (
|
||||
<div
|
||||
className={cn("pb-6 pt-2", isMobile ? "grid grid-cols-3 gap-2 px-4" : "flex flex-wrap items-center gap-3 px-6 lg:ps-[80px] pt-6")}
|
||||
data-mv-reply-row=""
|
||||
|
||||
@@ -11,8 +11,15 @@ export type MvComposeWindowState = "normal" | "minimized" | "maximized";
|
||||
* null and the composer keeps its own chrome.
|
||||
*/
|
||||
export interface MvComposeWindowControls {
|
||||
/**
|
||||
* `window` is the docked compose window; `inline` is a reply written in
|
||||
* place under the conversation, which can be popped out into a window.
|
||||
*/
|
||||
variant: "window" | "inline";
|
||||
state: MvComposeWindowState;
|
||||
setState: (state: MvComposeWindowState) => void;
|
||||
/** Inline only: move the reply into a docked window. */
|
||||
popOut?: () => void;
|
||||
}
|
||||
|
||||
export const MvComposeWindowContext = createContext<MvComposeWindowControls | null>(null);
|
||||
|
||||
@@ -15,7 +15,7 @@ export function MvComposeWindow({ children }: { children: ReactNode }) {
|
||||
const [state, setState] = useState<MvComposeWindowState>("normal");
|
||||
const minimized = state === "minimized";
|
||||
const maximized = state === "maximized";
|
||||
const controls = useMemo(() => ({ state, setState }), [state]);
|
||||
const controls = useMemo(() => ({ variant: "window" as const, state, setState }), [state]);
|
||||
|
||||
return (
|
||||
<MvComposeWindowContext.Provider value={controls}>
|
||||
@@ -43,3 +43,27 @@ export function MvComposeWindow({ children }: { children: ReactNode }) {
|
||||
</MvComposeWindowContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const NOOP = () => {};
|
||||
|
||||
/**
|
||||
* Mountain View's inline reply: the composer as a card under the open
|
||||
* conversation, as Gmail writes replies, with a button that pops it out into
|
||||
* the docked window.
|
||||
*/
|
||||
export function MvComposeInline({ children, onPopOut }: { children: ReactNode; onPopOut: () => void }) {
|
||||
const controls = useMemo(
|
||||
() => ({ variant: "inline" as const, state: "normal" as const, setState: NOOP, popOut: onPopOut }),
|
||||
[onPopOut],
|
||||
);
|
||||
return (
|
||||
<MvComposeWindowContext.Provider value={controls}>
|
||||
<div
|
||||
data-mv-compose-inline=""
|
||||
className="flex h-[min(480px,70dvh)] flex-col overflow-hidden rounded-lg border border-border bg-background shadow-[0_1px_2px_0_rgb(60_64_67/0.3),0_2px_6px_2px_rgb(60_64_67/0.15)]"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</MvComposeWindowContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { usePathname } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
import { EmailList } from "@/components/email/email-list";
|
||||
import { MvComposeWindow } from "@/components/email/mv-compose-window";
|
||||
import { MvComposeInline, MvComposeWindow } from "@/components/email/mv-compose-window";
|
||||
import { MessageListTabs } from "@/components/email/message-list-tabs";
|
||||
import dynamic from "next/dynamic";
|
||||
import type { ComposerDraftData } from "@/components/email/email-composer";
|
||||
@@ -201,6 +201,13 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
const dragStartWidth = useRef(0);
|
||||
// Mobile conversation view state
|
||||
const [conversationThread, setConversationThread] = useState<ThreadGroup | null>(null);
|
||||
// Mountain View writes replies in place under the conversation until the
|
||||
// user pops one out into the docked window.
|
||||
const [mvReplyPoppedOut, setMvReplyPoppedOut] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!showComposer) setMvReplyPoppedOut(false);
|
||||
}, [showComposer]);
|
||||
const handleMvPopOut = useCallback(() => setMvReplyPoppedOut(true), []);
|
||||
// Mountain View: the open message's whole conversation, light properties only.
|
||||
const [mvConversation, setMvConversation] = useState<Email[] | null>(null);
|
||||
const [conversationEmails, setConversationEmails] = useState<Email[]>([]);
|
||||
@@ -3618,7 +3625,9 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
const isHorizontalMailLayout = mailLayout === 'horizontal' && !isMobile && !isTablet;
|
||||
// Under Mountain View on desktop the composer floats over the list in a
|
||||
// docked window instead of taking the reading pane.
|
||||
const floatingComposer = mvShell && showComposer && !isEmbedded;
|
||||
const mvInlineReply = mvShell && showComposer && !isEmbedded && !mvReplyPoppedOut
|
||||
&& (pendingDraft?.mode ?? composerMode) !== 'compose' && Boolean(selectedEmail);
|
||||
const floatingComposer = mvShell && showComposer && !isEmbedded && !mvInlineReply;
|
||||
const hasViewerContent = (showComposer && !floatingComposer) || Boolean(conversationThread)
|
||||
|| Boolean(selectedEmail && !(mvShell && cursorOnly));
|
||||
const shouldCollapseListPane = (isTablet && !tabletListVisible && !readingPaneEmpty) || (!isMobile && isFocusedMailLayout && hasViewerContent);
|
||||
@@ -4651,7 +4660,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
{/* Inline Composer - shown in viewer pane.
|
||||
In Pro/embedded mode the composer is hoisted into its own
|
||||
Pro tab (see the effect below), so we never render it inline. */}
|
||||
{(showComposer && !isEmbedded && !floatingComposer) ? composerNode : (
|
||||
{(showComposer && !isEmbedded && !floatingComposer && !mvInlineReply) ? composerNode : (
|
||||
<>
|
||||
{/* Pending draft banner */}
|
||||
{pendingDraft && (
|
||||
@@ -4753,6 +4762,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
mvFolderName={mvShell && !isScheduledView && !isUnifiedView ? currentMailboxName : undefined}
|
||||
mvConversation={mvShell ? mvConversation ?? undefined : undefined}
|
||||
onOpenConversationEmail={mvShell ? (e) => void handleEmailSelect(e) : undefined}
|
||||
mvInlineComposer={mvInlineReply ? <MvComposeInline onPopOut={handleMvPopOut}>{composerNode}</MvComposeInline> : undefined}
|
||||
onToggleFullscreen={!isEmbedded && !isMobile ? () => setFullscreenReading((v) => !v) : undefined}
|
||||
isFullscreen={fullscreenReading}
|
||||
onShowShortcuts={() => setShowShortcutsModal(true)}
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "لون الخلفية",
|
||||
"remove_background_color": "إزالة لون الخلفية"
|
||||
},
|
||||
"send_filing_warning": "تم الإرسال - لكن التنظيف بعد الإرسال فشل، وقد تبقى مسودة قديمة."
|
||||
"send_filing_warning": "تم الإرسال - لكن التنظيف بعد الإرسال فشل، وقد تبقى مسودة قديمة.",
|
||||
"pop_out": "فتح الرد في نافذة منفصلة"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "تأكيد",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Color de fons",
|
||||
"remove_background_color": "Elimina el color de fons"
|
||||
},
|
||||
"send_filing_warning": "Enviat, però la neteja posterior a l'enviament ha fallat; és possible que quedi un esborrany obsolet."
|
||||
"send_filing_warning": "Enviat, però la neteja posterior a l'enviament ha fallat; és possible que quedi un esborrany obsolet.",
|
||||
"pop_out": "Obre la resposta en una finestra"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Confirma",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Barva pozadí",
|
||||
"remove_background_color": "Odebrat barvu pozadí"
|
||||
},
|
||||
"send_filing_warning": "Odesláno - ale následný úklid selhal, může zůstat zastaralý koncept."
|
||||
"send_filing_warning": "Odesláno - ale následný úklid selhal, může zůstat zastaralý koncept.",
|
||||
"pop_out": "Otevřít odpověď v okně"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Potvrdit",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Baggrundsfarve",
|
||||
"remove_background_color": "Fjern baggrundsfarve"
|
||||
},
|
||||
"send_filing_warning": "Sendt - men oprydningen bagefter mislykkedes, en forældet kladde kan blive stående."
|
||||
"send_filing_warning": "Sendt - men oprydningen bagefter mislykkedes, en forældet kladde kan blive stående.",
|
||||
"pop_out": "Åbn svaret i et vindue"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Bekræft",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Hintergrundfarbe",
|
||||
"remove_background_color": "Hintergrundfarbe entfernen"
|
||||
},
|
||||
"send_filing_warning": "Gesendet - aber das Aufräumen danach schlug fehl, evtl. bleibt ein alter Entwurf sichtbar."
|
||||
"send_filing_warning": "Gesendet - aber das Aufräumen danach schlug fehl, evtl. bleibt ein alter Entwurf sichtbar.",
|
||||
"pop_out": "Antwort in eigenem Fenster öffnen"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Bestätigen",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Background color",
|
||||
"remove_background_color": "Remove background color"
|
||||
},
|
||||
"send_filing_warning": "Sent - but the post-send cleanup failed, a stale draft may remain."
|
||||
"send_filing_warning": "Sent - but the post-send cleanup failed, a stale draft may remain.",
|
||||
"pop_out": "Pop out reply"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Confirm",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Color de fondo",
|
||||
"remove_background_color": "Quitar color de fondo"
|
||||
},
|
||||
"send_filing_warning": "Enviado - pero la limpieza posterior falló, puede quedar un borrador obsoleto."
|
||||
"send_filing_warning": "Enviado - pero la limpieza posterior falló, puede quedar un borrador obsoleto.",
|
||||
"pop_out": "Abrir la respuesta en una ventana"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Confirmar",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "رنگ پسزمینه",
|
||||
"remove_background_color": "حذف رنگ پسزمینه"
|
||||
},
|
||||
"send_filing_warning": "ارسال شد - اما پاکسازی پس از ارسال ناموفق بود، ممکن است پیشنویس قدیمی باقی بماند."
|
||||
"send_filing_warning": "ارسال شد - اما پاکسازی پس از ارسال ناموفق بود، ممکن است پیشنویس قدیمی باقی بماند.",
|
||||
"pop_out": "باز کردن پاسخ در پنجره جدا"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "تأیید",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Couleur d'arrière-plan",
|
||||
"remove_background_color": "Supprimer la couleur d'arrière-plan"
|
||||
},
|
||||
"send_filing_warning": "Envoyé - mais le nettoyage après envoi a échoué, un ancien brouillon peut subsister."
|
||||
"send_filing_warning": "Envoyé - mais le nettoyage après envoi a échoué, un ancien brouillon peut subsister.",
|
||||
"pop_out": "Ouvrir la réponse dans une fenêtre"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Confirmer",
|
||||
|
||||
@@ -768,7 +768,8 @@
|
||||
"background_color": "צבע רקע",
|
||||
"remove_background_color": "הסר צבע רקע"
|
||||
},
|
||||
"send_filing_warning": "נשלח - אך הניקוי שלאחר השליחה נכשל, ייתכן שתישאר טיוטה ישנה."
|
||||
"send_filing_warning": "נשלח - אך הניקוי שלאחר השליחה נכשל, ייתכן שתישאר טיוטה ישנה.",
|
||||
"pop_out": "פתיחת התשובה בחלון נפרד"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "אשר",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Háttérszín",
|
||||
"remove_background_color": "Háttérszín eltávolítása"
|
||||
},
|
||||
"send_filing_warning": "Elküldve - de az utólagos rendrakás nem sikerült, egy elavult piszkozat megmaradhat."
|
||||
"send_filing_warning": "Elküldve - de az utólagos rendrakás nem sikerült, egy elavult piszkozat megmaradhat.",
|
||||
"pop_out": "Válasz megnyitása külön ablakban"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Megerősítés",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Colore di sfondo",
|
||||
"remove_background_color": "Rimuovi colore di sfondo"
|
||||
},
|
||||
"send_filing_warning": "Inviato - ma la pulizia successiva non è riuscita, potrebbe restare una bozza obsoleta."
|
||||
"send_filing_warning": "Inviato - ma la pulizia successiva non è riuscita, potrebbe restare una bozza obsoleta.",
|
||||
"pop_out": "Apri la risposta in una finestra"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Conferma",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "背景色",
|
||||
"remove_background_color": "背景色を削除"
|
||||
},
|
||||
"send_filing_warning": "送信されましたが、送信後の整理に失敗しました。古い下書きが残る場合があります。"
|
||||
"send_filing_warning": "送信されましたが、送信後の整理に失敗しました。古い下書きが残る場合があります。",
|
||||
"pop_out": "返信を別ウィンドウで開く"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "確認",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "배경색",
|
||||
"remove_background_color": "배경색 제거"
|
||||
},
|
||||
"send_filing_warning": "보냈지만 전송 후 정리에 실패했습니다. 오래된 임시 보관 메일이 남아 있을 수 있습니다."
|
||||
"send_filing_warning": "보냈지만 전송 후 정리에 실패했습니다. 오래된 임시 보관 메일이 남아 있을 수 있습니다.",
|
||||
"pop_out": "답장을 별도 창에서 열기"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "확인",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Fona krāsa",
|
||||
"remove_background_color": "Noņemt fona krāsu"
|
||||
},
|
||||
"send_filing_warning": "Nosūtīts - bet pēcapstrāde neizdevās, var palikt novecojis melnraksts."
|
||||
"send_filing_warning": "Nosūtīts - bet pēcapstrāde neizdevās, var palikt novecojis melnraksts.",
|
||||
"pop_out": "Atvērt atbildi atsevišķā logā"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Apstiprināt",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Дэвсгэр өнгө",
|
||||
"remove_background_color": "Дэвсгэр өнгийг арилгах"
|
||||
},
|
||||
"send_filing_warning": "Илгээсэн - гэхдээ илгээсний дараах цэвэрлэгээ амжилтгүй болсон тул хуучирсан ноорог үлдэж болзошгүй."
|
||||
"send_filing_warning": "Илгээсэн - гэхдээ илгээсний дараах цэвэрлэгээ амжилтгүй болсон тул хуучирсан ноорог үлдэж болзошгүй.",
|
||||
"pop_out": "Хариуг тусдаа цонхонд нээх"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Баталгаажуулах",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Bakgrunnsfarge",
|
||||
"remove_background_color": "Fjern bakgrunnsfarge"
|
||||
},
|
||||
"send_filing_warning": "Meldingen ble sendt, men oppryddingen etterpå mislyktes. Et gammelt utkast kan bli liggende."
|
||||
"send_filing_warning": "Meldingen ble sendt, men oppryddingen etterpå mislyktes. Et gammelt utkast kan bli liggende.",
|
||||
"pop_out": "Åpne svaret i et eget vindu"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Bekreft",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Achtergrondkleur",
|
||||
"remove_background_color": "Achtergrondkleur verwijderen"
|
||||
},
|
||||
"send_filing_warning": "Verzonden - maar het opruimen daarna is mislukt, mogelijk blijft een oud concept staan."
|
||||
"send_filing_warning": "Verzonden - maar het opruimen daarna is mislukt, mogelijk blijft een oud concept staan.",
|
||||
"pop_out": "Antwoord in een apart venster openen"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Bevestigen",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Kolor tła",
|
||||
"remove_background_color": "Usuń kolor tła"
|
||||
},
|
||||
"send_filing_warning": "Wysłano - ale późniejsze porządkowanie nie powiodło się, może pozostać nieaktualna wersja robocza."
|
||||
"send_filing_warning": "Wysłano - ale późniejsze porządkowanie nie powiodło się, może pozostać nieaktualna wersja robocza.",
|
||||
"pop_out": "Otwórz odpowiedź w oknie"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Potwierdź",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Cor de fundo",
|
||||
"remove_background_color": "Remover cor de fundo"
|
||||
},
|
||||
"send_filing_warning": "Enviado - mas a limpeza posterior falhou, um rascunho antigo pode permanecer."
|
||||
"send_filing_warning": "Enviado - mas a limpeza posterior falhou, um rascunho antigo pode permanecer.",
|
||||
"pop_out": "Abrir a resposta numa janela"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Confirmar",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Culoarea fundalului",
|
||||
"remove_background_color": "Elimină culoarea fundalului"
|
||||
},
|
||||
"send_filing_warning": "Trimis - dar curățarea ulterioară a eșuat, poate rămâne o ciornă veche."
|
||||
"send_filing_warning": "Trimis - dar curățarea ulterioară a eșuat, poate rămâne o ciornă veche.",
|
||||
"pop_out": "Deschide răspunsul într-o fereastră"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Confirmare",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Цвет фона",
|
||||
"remove_background_color": "Убрать цвет фона"
|
||||
},
|
||||
"send_filing_warning": "Отправлено - но последующая очистка не удалась, может остаться устаревший черновик."
|
||||
"send_filing_warning": "Отправлено - но последующая очистка не удалась, может остаться устаревший черновик.",
|
||||
"pop_out": "Открыть ответ в отдельном окне"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Подтвердить",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Farba pozadia",
|
||||
"remove_background_color": "Odstrániť farbu pozadia"
|
||||
},
|
||||
"send_filing_warning": "Odoslané - ale následné upratovanie zlyhalo, môže zostať zastaraný koncept."
|
||||
"send_filing_warning": "Odoslané - ale následné upratovanie zlyhalo, môže zostať zastaraný koncept.",
|
||||
"pop_out": "Otvoriť odpoveď v okne"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Potvrdiť",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Arka plan rengi",
|
||||
"remove_background_color": "Arka plan rengini kaldır"
|
||||
},
|
||||
"send_filing_warning": "Gönderildi - ancak sonrasındaki temizleme başarısız oldu, eski bir taslak kalabilir."
|
||||
"send_filing_warning": "Gönderildi - ancak sonrasındaki temizleme başarısız oldu, eski bir taslak kalabilir.",
|
||||
"pop_out": "Yanıtı ayrı pencerede aç"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Onayla",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "Колір фону",
|
||||
"remove_background_color": "Прибрати колір фону"
|
||||
},
|
||||
"send_filing_warning": "Надіслано - але подальше очищення не вдалося, може залишитися застаріла чернетка."
|
||||
"send_filing_warning": "Надіслано - але подальше очищення не вдалося, може залишитися застаріла чернетка.",
|
||||
"pop_out": "Відкрити відповідь в окремому вікні"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "Підтвердити",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "背景顏色",
|
||||
"remove_background_color": "移除背景顏色"
|
||||
},
|
||||
"send_filing_warning": "郵件已傳送,但寄送後清理失敗,可能仍保留舊草稿。"
|
||||
"send_filing_warning": "郵件已傳送,但寄送後清理失敗,可能仍保留舊草稿。",
|
||||
"pop_out": "在獨立視窗中開啟回覆"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "確認",
|
||||
|
||||
@@ -803,7 +803,8 @@
|
||||
"background_color": "背景颜色",
|
||||
"remove_background_color": "移除背景颜色"
|
||||
},
|
||||
"send_filing_warning": "已发送,但发送后的清理失败,可能会残留旧草稿。"
|
||||
"send_filing_warning": "已发送,但发送后的清理失败,可能会残留旧草稿。",
|
||||
"pop_out": "在单独窗口中打开回复"
|
||||
},
|
||||
"confirm_dialog": {
|
||||
"confirm": "确认",
|
||||
|
||||
Reference in New Issue
Block a user