Merge pull request #987 from ibayue/feature/composer-sticky-toolbar-font-size

feat(composer): sticky formatting toolbar with font-size and background color
This commit is contained in:
Linus Rath
2026-09-07 17:42:52 +02:00
committed by GitHub
31 changed files with 503 additions and 34 deletions
@@ -0,0 +1,132 @@
import { describe, expect, it } from 'vitest';
import { Editor } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
import { TextStyle, BackgroundColor } from '@tiptap/extension-text-style';
import Color from '@tiptap/extension-color';
import { FontSize, FONT_SIZES } from '../font-size';
// Mirrors the composer's registration order for TextStyle-based marks
// (rich-text-editor.tsx): TextStyle first, then the attribute extensions.
function makeEditor(content = '<p>Hello world</p>') {
return new Editor({
element: document.createElement('div'),
extensions: [StarterKit, TextStyle, Color, BackgroundColor, FontSize],
content,
});
}
describe('font-size extension', () => {
it('applies font-size as an inline style on the selection', () => {
const editor = makeEditor();
editor.commands.selectAll();
editor.commands.setFontSize('18px');
expect(editor.getHTML()).toContain('font-size: 18px');
editor.destroy();
});
it('reports the active size via the textStyle attributes', () => {
const editor = makeEditor();
editor.commands.selectAll();
editor.commands.setFontSize('24px');
expect(editor.getAttributes('textStyle').fontSize).toBe('24px');
editor.destroy();
});
it('unsetFontSize removes the attribute and the empty textStyle mark', () => {
const editor = makeEditor();
editor.commands.selectAll();
editor.commands.setFontSize('18px');
editor.commands.unsetFontSize();
expect(editor.getAttributes('textStyle').fontSize).toBeUndefined();
expect(editor.getHTML()).not.toContain('font-size');
editor.destroy();
});
it('parses font-size from pasted inline styles', () => {
const editor = makeEditor('<p><span style="font-size: 32px">Big</span> rest</p>');
editor.commands.setTextSelection({ from: 1, to: 5 });
expect(editor.getAttributes('textStyle').fontSize).toBe('32px');
editor.destroy();
});
it('keeps color and font-size on the same span', () => {
const editor = makeEditor();
editor.commands.selectAll();
editor.commands.setFontSize('18px');
editor.commands.setColor('#c5221f');
const html = editor.getHTML();
expect(html).toContain('font-size: 18px');
// jsdom's DOM serializer normalizes #c5221f to rgb() form; either way the
// colour declaration must be on the same span as the size.
expect(html).toContain('color: rgb(197, 34, 31)');
// Removing the size must keep the colour, not the whole mark.
editor.commands.unsetFontSize();
const after = editor.getHTML();
expect(after).not.toContain('font-size');
expect(after).toContain('color: rgb(197, 34, 31)');
editor.destroy();
});
it('exposes the toolbar size presets the UI renders', () => {
expect([...FONT_SIZES]).toEqual(['14px', '16px', '18px', '20px', '24px', '32px']);
});
});
describe('background-color extension', () => {
it('applies background-color as an inline style on the selection', () => {
const editor = makeEditor();
editor.commands.selectAll();
editor.commands.setBackgroundColor('#fef3c7');
const html = editor.getHTML();
// jsdom normalizes #fef3c7 to rgb() form.
expect(html).toContain('background-color: rgb(254, 243, 199)');
editor.destroy();
});
it('reports the active tint via the textStyle attributes', () => {
const editor = makeEditor();
editor.commands.selectAll();
editor.commands.setBackgroundColor('#dcfce7');
// getAttributes returns the stored attribute verbatim (no jsdom
// round-trip), unlike getHTML which normalizes to rgb() form.
expect(editor.getAttributes('textStyle').backgroundColor).toBe('#dcfce7');
editor.destroy();
});
it('unsetBackgroundColor removes the attribute and the empty textStyle mark', () => {
const editor = makeEditor();
editor.commands.selectAll();
editor.commands.setBackgroundColor('#dbeafe');
editor.commands.unsetBackgroundColor();
expect(editor.getHTML()).not.toContain('background-color');
editor.destroy();
});
it('parses background-color from pasted inline styles', () => {
const editor = makeEditor('<p><span style="background-color: #fde68a">Marked</span> rest</p>');
editor.commands.setTextSelection({ from: 1, to: 5 });
// parseHTML reads the raw style attribute, so the hex form survives.
expect(editor.getAttributes('textStyle').backgroundColor).toBe('#fde68a');
editor.destroy();
});
it('keeps color, background-color and font-size on the same span', () => {
const editor = makeEditor();
editor.commands.selectAll();
editor.commands.setFontSize('18px');
editor.commands.setColor('#c5221f');
editor.commands.setBackgroundColor('#fef3c7');
const html = editor.getHTML();
expect(html).toContain('font-size: 18px');
expect(html).toContain('color: rgb(197, 34, 31)');
expect(html).toContain('background-color: rgb(254, 243, 199)');
// Removing the tint must keep the other two attributes.
editor.commands.unsetBackgroundColor();
const after = editor.getHTML();
expect(after).not.toContain('background-color');
expect(after).toContain('font-size: 18px');
expect(after).toContain('color: rgb(197, 34, 31)');
editor.destroy();
});
});
+6 -4
View File
@@ -2616,9 +2616,10 @@ export function EmailComposer({
)}
</div>
<div className="flex-1 min-h-0 overflow-auto">
{/* Fields section */}
<div className="space-y-0 border-b">
{/* Fields section - outside the scroll container so From/To/Cc/Bcc/
Subject stay reachable while scrolling long bodies, matching the
pinned formatting toolbar (see rich-text-editor.tsx). */}
<div className="shrink-0 space-y-0 border-b">
{/* From field */}
<div className="flex items-center gap-2 px-4 py-2.5 border-b border-border/50">
<span className="text-sm text-muted-foreground w-12 md:w-16 shrink-0">{t('from')}:</span>
@@ -2897,8 +2898,9 @@ export function EmailComposer({
className="flex-1 border-0 focus-visible:ring-0 h-8 px-0 text-sm"
/>
</div>
</div>
</div>
<div className="flex-1 min-h-0 overflow-auto">
{/* Body */}
{plainTextMode ? (
<textarea
+79
View File
@@ -0,0 +1,79 @@
import { Extension } from "@tiptap/core";
// Font-size as an attribute on the TextStyle mark, mirroring how
// @tiptap/extension-color registers `color`: one global attribute on
// "textStyle", a setter and an unsetter. Emitted as an inline
// `style="font-size: …"`, which email clients preserve on round-trip -
// the same property the composer's StyledParagraph/StyledHeading keep
// for pasted content.
export const FontSize = Extension.create({
name: "fontSize",
addOptions() {
return {
types: ["textStyle"],
};
},
addGlobalAttributes() {
return [
{
types: this.options.types,
attributes: {
fontSize: {
default: null,
parseHTML: (element) => {
const styleAttr = element.getAttribute("style");
if (styleAttr) {
const decls = styleAttr.split(";").map((s) => s.trim()).filter(Boolean);
for (let i = decls.length - 1; i >= 0; i -= 1) {
const parts = decls[i].split(":");
if (parts.length >= 2 && parts[0].trim().toLowerCase() === "font-size") {
return parts.slice(1).join(":").trim().replace(/['"]+/g, "");
}
}
}
return element.style.fontSize?.replace(/['"]+/g, "") || null;
},
renderHTML: (attributes) => {
if (!attributes.fontSize) {
return {};
}
return {
style: `font-size: ${attributes.fontSize}`,
};
},
},
},
},
];
},
addCommands() {
return {
setFontSize:
(fontSize: string) =>
({ chain }) => {
return chain().setMark("textStyle", { fontSize }).run();
},
unsetFontSize:
() =>
({ chain }) => {
return chain().setMark("textStyle", { fontSize: null }).removeEmptyTextStyle().run();
},
};
},
});
// The sizes the toolbar offers. 14/16px read as body text, the rest as
// emphasis - the composer's base font is 14px (text-sm on .tiptap).
export const FONT_SIZES = ["14px", "16px", "18px", "20px", "24px", "32px"] as const;
declare module "@tiptap/core" {
interface Commands<ReturnType> {
fontSize: {
setFontSize: (fontSize: string) => ReturnType;
unsetFontSize: () => ReturnType;
};
}
}
+151 -3
View File
@@ -9,8 +9,9 @@ import Underline from "@tiptap/extension-underline";
import Link from "@tiptap/extension-link";
import TextAlign from "@tiptap/extension-text-align";
import { TextDirection } from "@/components/email/text-direction";
import { TextStyle } from "@tiptap/extension-text-style";
import { TextStyle, BackgroundColor } from "@tiptap/extension-text-style";
import Color from "@tiptap/extension-color";
import { FontSize, FONT_SIZES } from "@/components/email/font-size";
import { ResizableImage } from "@/components/email/resizable-image";
import Placeholder from "@tiptap/extension-placeholder";
import { Table } from "@tiptap/extension-table";
@@ -44,6 +45,7 @@ import {
Heading2,
Table as TableIcon,
Baseline,
Highlighter,
Trash2,
Rows3,
Columns3,
@@ -198,6 +200,8 @@ export function RichTextEditor({
}),
TextStyle,
Color,
BackgroundColor,
FontSize,
ResizableImage,
Placeholder.configure({
placeholder,
@@ -326,6 +330,21 @@ export function RichTextEditor({
const tableWrapperRef = useRef<HTMLDivElement>(null);
const [colorMenuOpen, setColorMenuOpen] = useState(false);
const colorWrapperRef = useRef<HTMLDivElement>(null);
const [bgColorMenuOpen, setBgColorMenuOpen] = useState(false);
const bgColorWrapperRef = useRef<HTMLDivElement>(null);
const [fontSizeMenuOpen, setFontSizeMenuOpen] = useState(false);
const fontSizeWrapperRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!fontSizeMenuOpen) return;
const handler = (e: MouseEvent) => {
if (fontSizeWrapperRef.current && !fontSizeWrapperRef.current.contains(e.target as Node)) {
setFontSizeMenuOpen(false);
}
};
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, [fontSizeMenuOpen]);
useEffect(() => {
if (!colorMenuOpen) return;
@@ -338,6 +357,17 @@ export function RichTextEditor({
return () => document.removeEventListener("mousedown", handler);
}, [colorMenuOpen]);
useEffect(() => {
if (!bgColorMenuOpen) return;
const handler = (e: MouseEvent) => {
if (bgColorWrapperRef.current && !bgColorWrapperRef.current.contains(e.target as Node)) {
setBgColorMenuOpen(false);
}
};
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, [bgColorMenuOpen]);
useEffect(() => {
if (!tableMenuOpen) return;
const handler = (e: MouseEvent) => {
@@ -355,10 +385,74 @@ export function RichTextEditor({
);
}
const currentFontSize: string | null = editor.getAttributes("textStyle").fontSize ?? null;
const currentBgColor: string | null = editor.getAttributes("textStyle").backgroundColor ?? null;
return (
<div className={cn("flex flex-col", hasError && "ring-2 ring-red-500 dark:ring-red-400 rounded", className)}>
{/* Toolbar */}
<div className="flex flex-wrap items-center gap-0.5 px-3 py-1.5 border-b border-border/50 bg-muted/30">
{/* Toolbar - sticky within the composer's scroll container so it stays
visible while editing long bodies. The background stays at main's
bg-muted/30 in every state - backdrop-blur has no visual effect while
the toolbar rests on the solid page background, and blurs body text
sliding underneath only once pinned, so pinned legibility needs no
extra opaque overlay (an always-on stronger overlay would read darker
than main even at rest). */}
<div className="sticky top-0 z-20 flex flex-wrap items-center gap-0.5 px-3 py-1.5 border-b border-border/50 bg-muted/30 backdrop-blur-sm">
{/* Font size - two baseline-aligned A's read as "size" at a glance;
once a size is chosen the button shows that number instead. */}
<div ref={fontSizeWrapperRef} className="relative">
<ToolbarButton
active={fontSizeMenuOpen || !!currentFontSize}
onClick={() => setFontSizeMenuOpen((v) => !v)}
title={tToolbar("font_size")}
>
{currentFontSize ? (
<span className="text-xs font-semibold min-w-6 text-center leading-4 tabular-nums">
{currentFontSize.replace("px", "")}
</span>
) : (
<span className="flex items-baseline justify-center leading-none" aria-hidden>
<span className="text-sm font-semibold">A</span>
<span className="text-[9px] font-semibold">A</span>
</span>
)}
</ToolbarButton>
{fontSizeMenuOpen && (
<div className="absolute z-50 top-full start-0 mt-1 bg-popover border border-border rounded-md shadow-md p-1 min-w-[64px]">
{FONT_SIZES.map((size) => (
<button
key={size}
type="button"
onClick={() => {
editor.chain().focus().setFontSize(size).run();
setFontSizeMenuOpen(false);
}}
className={cn(
"block w-full text-start px-2 py-1 rounded hover:bg-accent transition-colors",
currentFontSize === size && "bg-accent text-accent-foreground"
)}
style={{ fontSize: size, lineHeight: 1.4 }}
>
{size}
</button>
))}
<div className="h-px bg-border my-1" />
<button
type="button"
className="flex items-center gap-2 px-2 py-1 text-sm rounded hover:bg-accent text-start w-full"
onClick={() => {
editor.chain().focus().unsetFontSize().run();
setFontSizeMenuOpen(false);
}}
>
<RemoveFormatting className="w-4 h-4" /> {tToolbar("font_size_default")}
</button>
</div>
)}
</div>
<ToolbarSeparator />
<ToolbarButton
active={editor.isActive("bold")}
onClick={() => editor.chain().focus().toggleBold().run()}
@@ -430,6 +524,60 @@ export function RichTextEditor({
</div>
)}
</div>
{/* Background color - same palette, same grid and same swatch
styling as the text-colour picker above, so the two controls
read as siblings. The highlighter glyph previews the active
tint on its little bar. Inline style="background-color: …"
survives email round-trips like `color` does. */}
<div ref={bgColorWrapperRef} className="relative">
<ToolbarButton
active={!!currentBgColor}
onClick={() => setBgColorMenuOpen((v) => !v)}
title={tToolbar("background_color")}
>
<span className="relative inline-flex items-center justify-center">
<Highlighter className="w-4 h-4" />
<span
aria-hidden
className="absolute -bottom-[3px] left-1/2 h-[3px] w-3 -translate-x-1/2 rounded-[1px] border border-border/40"
style={{ backgroundColor: currentBgColor || undefined }}
/>
</span>
</ToolbarButton>
{bgColorMenuOpen && (
<div className="absolute z-50 top-full start-0 mt-1 bg-popover border border-border rounded-md shadow-md p-2">
<div className="grid gap-0.5" style={{ gridTemplateColumns: "repeat(8, 1fr)" }}>
{TEXT_COLORS.map((color) => (
<button
key={color}
type="button"
title={color}
onClick={() => {
editor.chain().focus().setBackgroundColor(color).run();
setBgColorMenuOpen(false);
}}
className={cn(
"w-4 h-4 border border-border/60 rounded-[2px] transition-transform hover:scale-110",
currentBgColor === color && "ring-1 ring-ring ring-offset-1"
)}
style={{ backgroundColor: color }}
/>
))}
</div>
<div className="h-px bg-border my-1.5" />
<button
type="button"
className="flex items-center gap-2 px-2 py-1 text-sm rounded hover:bg-accent text-start w-full"
onClick={() => {
editor.chain().focus().unsetBackgroundColor().run();
setBgColorMenuOpen(false);
}}
>
<RemoveFormatting className="w-4 h-4" /> {tToolbar("remove_background_color")}
</button>
</div>
)}
</div>
<ToolbarSeparator />
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "حذف العمود",
"toggle_header_row": "تبديل صف الرأس",
"delete_table": "حذف الجدول",
"pick_size": "اختيار الحجم"
"pick_size": "اختيار الحجم",
"font_size": "حجم الخط",
"font_size_default": "افتراضي",
"background_color": "لون الخلفية",
"remove_background_color": "إزالة لون الخلفية"
},
"send_filing_warning": "تم الإرسال - لكن التنظيف بعد الإرسال فشل، وقد تبقى مسودة قديمة."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Elimina la columna",
"toggle_header_row": "Commuta la fila de capçalera",
"delete_table": "Elimina la taula",
"pick_size": "Tria la mida"
"pick_size": "Tria la mida",
"font_size": "Mida de la lletra",
"font_size_default": "Per defecte",
"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."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Odstranit sloupec",
"toggle_header_row": "Přepnout řádek záhlaví",
"delete_table": "Odstranit tabulku",
"pick_size": "Vybrat velikost"
"pick_size": "Vybrat velikost",
"font_size": "Velikost písma",
"font_size_default": "Výchozí",
"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."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Slet kolonne",
"toggle_header_row": "Slå overskriftsrække til/fra",
"delete_table": "Slet tabel",
"pick_size": "Vælg størrelse"
"pick_size": "Vælg størrelse",
"font_size": "Skriftstørrelse",
"font_size_default": "Standard",
"background_color": "Baggrundsfarve",
"remove_background_color": "Fjern baggrundsfarve"
},
"send_filing_warning": "Sendt - men oprydningen bagefter mislykkedes, en forældet kladde kan blive stående."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Spalte löschen",
"toggle_header_row": "Kopfzeile umschalten",
"delete_table": "Tabelle löschen",
"pick_size": "Größe wählen"
"pick_size": "Größe wählen",
"font_size": "Schriftgröße",
"font_size_default": "Standard",
"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."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Delete column",
"toggle_header_row": "Toggle header row",
"delete_table": "Delete table",
"pick_size": "Pick size"
"pick_size": "Pick size",
"font_size": "Font size",
"font_size_default": "Default",
"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."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Eliminar columna",
"toggle_header_row": "Alternar fila de encabezado",
"delete_table": "Eliminar tabla",
"pick_size": "Elegir tamaño"
"pick_size": "Elegir tamaño",
"font_size": "Tamaño de fuente",
"font_size_default": "Predeterminado",
"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."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "حذف ستون",
"toggle_header_row": "تغییر وضعیت ردیف سرصفحه",
"delete_table": "حذف جدول",
"pick_size": "انتخاب اندازه"
"pick_size": "انتخاب اندازه",
"font_size": "اندازه فونت",
"font_size_default": "پیش‌فرض",
"background_color": "رنگ پس‌زمینه",
"remove_background_color": "حذف رنگ پس‌زمینه"
},
"send_filing_warning": "ارسال شد - اما پاک‌سازی پس از ارسال ناموفق بود، ممکن است پیش‌نویس قدیمی باقی بماند."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Supprimer la colonne",
"toggle_header_row": "Basculer la ligne d'en-tête",
"delete_table": "Supprimer le tableau",
"pick_size": "Choisir la taille"
"pick_size": "Choisir la taille",
"font_size": "Taille de police",
"font_size_default": "Par défaut",
"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."
},
+5 -1
View File
@@ -693,7 +693,11 @@
"delete_column": "מחיקת עמודה",
"toggle_header_row": "החלפת שורת כותרת",
"delete_table": "מחיקת טבלה",
"pick_size": "בחירת גודל"
"pick_size": "בחירת גודל",
"font_size": "גודל גופן",
"font_size_default": "ברירת מחדל",
"background_color": "צבע רקע",
"remove_background_color": "הסר צבע רקע"
},
"send_filing_warning": "נשלח - אך הניקוי שלאחר השליחה נכשל, ייתכן שתישאר טיוטה ישנה."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Oszlop törlése",
"toggle_header_row": "Fejlécsor váltása",
"delete_table": "Táblázat törlése",
"pick_size": "Méret kiválasztása"
"pick_size": "Méret kiválasztása",
"font_size": "Betűméret",
"font_size_default": "Alapértelmezett",
"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."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Elimina colonna",
"toggle_header_row": "Attiva/disattiva riga di intestazione",
"delete_table": "Elimina tabella",
"pick_size": "Scegli dimensione"
"pick_size": "Scegli dimensione",
"font_size": "Dimensione carattere",
"font_size_default": "Predefinita",
"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."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "列を削除",
"toggle_header_row": "ヘッダー行の切り替え",
"delete_table": "表を削除",
"pick_size": "サイズを選択"
"pick_size": "サイズを選択",
"font_size": "文字サイズ",
"font_size_default": "デフォルト",
"background_color": "背景色",
"remove_background_color": "背景色を削除"
},
"send_filing_warning": "送信されましたが、送信後の整理に失敗しました。古い下書きが残る場合があります。"
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "열 삭제",
"toggle_header_row": "머리글 행 전환",
"delete_table": "표 삭제",
"pick_size": "크기 선택"
"pick_size": "크기 선택",
"font_size": "글자 크기",
"font_size_default": "기본값",
"background_color": "배경색",
"remove_background_color": "배경색 제거"
},
"send_filing_warning": "보냈지만 전송 후 정리에 실패했습니다. 오래된 임시 보관 메일이 남아 있을 수 있습니다."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Dzēst kolonnu",
"toggle_header_row": "Pārslēgt galvenes rindu",
"delete_table": "Dzēst tabulu",
"pick_size": "Izvēlēties izmēru"
"pick_size": "Izvēlēties izmēru",
"font_size": "Fonta izmērs",
"font_size_default": "Noklusējums",
"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."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Баганыг устгах",
"toggle_header_row": "Толгойн мөрийг сэлгэх",
"delete_table": "Хүснэгт устгах",
"pick_size": "Хэмжээ сонгох"
"pick_size": "Хэмжээ сонгох",
"font_size": "Фонтын хэмжээ",
"font_size_default": "Үндсэн",
"background_color": "Дэвсгэр өнгө",
"remove_background_color": "Дэвсгэр өнгийг арилгах"
},
"send_filing_warning": "Илгээсэн - гэхдээ илгээсний дараах цэвэрлэгээ амжилтгүй болсон тул хуучирсан ноорог үлдэж болзошгүй."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Slett kolonne",
"toggle_header_row": "Slå overskriftsraden av eller på",
"delete_table": "Slett tabell",
"pick_size": "Velg størrelse"
"pick_size": "Velg størrelse",
"font_size": "Skriftstørrelse",
"font_size_default": "Standard",
"background_color": "Bakgrunnsfarge",
"remove_background_color": "Fjern bakgrunnsfarge"
},
"send_filing_warning": "Meldingen ble sendt, men oppryddingen etterpå mislyktes. Et gammelt utkast kan bli liggende."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Kolom verwijderen",
"toggle_header_row": "Koprij aan/uit",
"delete_table": "Tabel verwijderen",
"pick_size": "Grootte kiezen"
"pick_size": "Grootte kiezen",
"font_size": "Lettergrootte",
"font_size_default": "Standaard",
"background_color": "Achtergrondkleur",
"remove_background_color": "Achtergrondkleur verwijderen"
},
"send_filing_warning": "Verzonden - maar het opruimen daarna is mislukt, mogelijk blijft een oud concept staan."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Usuń kolumnę",
"toggle_header_row": "Przełącz wiersz nagłówka",
"delete_table": "Usuń tabelę",
"pick_size": "Wybierz rozmiar"
"pick_size": "Wybierz rozmiar",
"font_size": "Rozmiar czcionki",
"font_size_default": "Domyślny",
"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."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Excluir coluna",
"toggle_header_row": "Alternar linha de cabeçalho",
"delete_table": "Excluir tabela",
"pick_size": "Escolher tamanho"
"pick_size": "Escolher tamanho",
"font_size": "Tamanho da fonte",
"font_size_default": "Padrão",
"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."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Șterge coloana",
"toggle_header_row": "Comută rândul de antet",
"delete_table": "Șterge tabelul",
"pick_size": "Alege dimensiunea"
"pick_size": "Alege dimensiunea",
"font_size": "Dimensiune font",
"font_size_default": "Implicit",
"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."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Удалить столбец",
"toggle_header_row": "Переключить строку заголовка",
"delete_table": "Удалить таблицу",
"pick_size": "Выбрать размер"
"pick_size": "Выбрать размер",
"font_size": "Размер шрифта",
"font_size_default": "По умолчанию",
"background_color": "Цвет фона",
"remove_background_color": "Убрать цвет фона"
},
"send_filing_warning": "Отправлено - но последующая очистка не удалась, может остаться устаревший черновик."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Odstrániť stĺpec",
"toggle_header_row": "Prepnúť riadok záhlavia",
"delete_table": "Odstrániť tabuľku",
"pick_size": "Vybrať veľkosť"
"pick_size": "Vybrať veľkosť",
"font_size": "Veľkosť písma",
"font_size_default": "Predvolené",
"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."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Sütunu sil",
"toggle_header_row": "Başlık satırını aç/kapat",
"delete_table": "Tabloyu sil",
"pick_size": "Boyut seç"
"pick_size": "Boyut seç",
"font_size": "Yazı boyutu",
"font_size_default": "Varsayılan",
"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."
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "Видалити стовпець",
"toggle_header_row": "Переключити рядок заголовка",
"delete_table": "Видалити таблицю",
"pick_size": "Вибрати розмір"
"pick_size": "Вибрати розмір",
"font_size": "Розмір шрифту",
"font_size_default": "За замовчуванням",
"background_color": "Колір фону",
"remove_background_color": "Прибрати колір фону"
},
"send_filing_warning": "Надіслано - але подальше очищення не вдалося, може залишитися застаріла чернетка."
},
+5 -1
View File
@@ -725,7 +725,11 @@
"delete_column": "刪除欄",
"toggle_header_row": "切換標題列",
"delete_table": "刪除表格",
"pick_size": "選取大小"
"pick_size": "選取大小",
"font_size": "字型大小",
"font_size_default": "預設",
"background_color": "背景顏色",
"remove_background_color": "移除背景顏色"
},
"send_filing_warning": "郵件已傳送,但寄送後清理失敗,可能仍保留舊草稿。"
},
+5 -1
View File
@@ -728,7 +728,11 @@
"delete_column": "删除列",
"toggle_header_row": "切换标题行",
"delete_table": "删除表格",
"pick_size": "选择大小"
"pick_size": "选择大小",
"font_size": "字号",
"font_size_default": "默认",
"background_color": "背景颜色",
"remove_background_color": "移除背景颜色"
},
"send_filing_warning": "已发送,但发送后的清理失败,可能会残留旧草稿。"
},