mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
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:
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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 />
|
||||
|
||||
|
||||
@@ -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": "تم الإرسال - لكن التنظيف بعد الإرسال فشل، وقد تبقى مسودة قديمة."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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": "ارسال شد - اما پاکسازی پس از ارسال ناموفق بود، ممکن است پیشنویس قدیمی باقی بماند."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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": "נשלח - אך הניקוי שלאחר השליחה נכשל, ייתכן שתישאר טיוטה ישנה."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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": "送信されましたが、送信後の整理に失敗しました。古い下書きが残る場合があります。"
|
||||
},
|
||||
|
||||
@@ -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": "보냈지만 전송 후 정리에 실패했습니다. 오래된 임시 보관 메일이 남아 있을 수 있습니다."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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": "Илгээсэн - гэхдээ илгээсний дараах цэвэрлэгээ амжилтгүй болсон тул хуучирсан ноорог үлдэж болзошгүй."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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": "Отправлено - но последующая очистка не удалась, может остаться устаревший черновик."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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": "Надіслано - але подальше очищення не вдалося, може залишитися застаріла чернетка."
|
||||
},
|
||||
|
||||
@@ -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": "郵件已傳送,但寄送後清理失敗,可能仍保留舊草稿。"
|
||||
},
|
||||
|
||||
@@ -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": "已发送,但发送后的清理失败,可能会残留旧草稿。"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user