diff --git a/components/email/__tests__/signature-block.test.ts b/components/email/__tests__/signature-block.test.ts
index b4949fb60..bee9c3747 100644
--- a/components/email/__tests__/signature-block.test.ts
+++ b/components/email/__tests__/signature-block.test.ts
@@ -1,15 +1,52 @@
import { describe, expect, it } from 'vitest';
import { Editor } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
+import Paragraph from '@tiptap/extension-paragraph';
import {
SignatureBlock,
buildSignatureBlock,
containsEmbeddedSignature,
+ unlockSignatureBlock,
SIGNATURE_BLOCK_MARKER,
SIGNATURE_RANGE_MARKER,
} from '../signature-block';
import { serializeEditorContent } from '../quoted-html';
+import { sanitizeSignatureHtml } from '@/lib/email-sanitization';
+import { styledBlockAttributes } from '../styled-block-attributes';
+
+// The exact paragraph the composer runs (rich-text-editor.tsx builds
+// StyledParagraph from the same styledBlockAttributes), so marker-survival
+// and line-spacing assertions test the schema users actually get.
+const ComposerParagraph = Paragraph.extend({
+ addAttributes() {
+ return {
+ ...this.parent?.(),
+ ...styledBlockAttributes,
+ };
+ },
+});
+
+const bracketed = (inner: string) =>
+ `
per line. Divs render margin-less
+ // in mail clients but have no schema node, so a naive parse re-wraps every
+ // line into a default-margin paragraph and the signature falls apart into
+ // spaced paragraphs (second #822 report, live test).
+ it('keeps tight line spacing for div-per-line signatures', () => {
+ const editor = makeEditor(bracketed('
Jane Doe
IT-Management
'));
+ try {
+ unlockSignatureBlock(editor, findSignaturePos(editor));
+ const out = serializeEditorContent(editor);
+ // Both lines carry the vertical margin reset, none is a bare default paragraph.
+ expect(out.match(/margin-top: 0px; margin-bottom: 0px/g)?.length).toBe(2);
+ } finally {
+ editor.destroy();
+ }
+ });
+
+ // Longhand margins must not defeat the reset: only the missing verticals
+ // are zeroed, explicit spacing and indents survive.
+ it('keeps indents while resetting missing vertical margins', () => {
+ const editor = makeEditor(bracketed('
Indented line
'));
+ try {
+ unlockSignatureBlock(editor, findSignaturePos(editor));
+ const out = serializeEditorContent(editor);
+ expect(out).toContain('margin-left: 12px');
+ expect(out).toContain('margin-top: 0px');
+ expect(out).toContain('margin-bottom: 0px');
+ } finally {
+ editor.destroy();
+ }
+ });
+
+ // Wrapper divs have no schema node and are dropped by the parse; their
+ // inherited styles (the font/color the whole signature lives in) must move
+ // onto the generated line paragraphs, with the line's own styles winning.
+ it('hoists inherited wrapper styles onto the line paragraphs', () => {
+ const editor = makeEditor(bracketed(
+ '
'
+ ));
+ try {
+ unlockSignatureBlock(editor, findSignaturePos(editor));
+ const out = serializeEditorContent(editor);
+ expect(out.match(/font-family: Arial/g)?.length).toBe(2);
+ expect(out).toContain('color: rgb(51, 51, 51)');
+ expect(out).toContain('color: rgb(255, 0, 0)');
+ } finally {
+ editor.destroy();
+ }
+ });
+
+ it('keeps
line runs together and respects explicit margins', () => {
+ const editor = makeEditor(bracketed(
+ '
Tel: 1 • Fax: 2
Mobil: 3
Legal
'
+ ));
+ try {
+ unlockSignatureBlock(editor, findSignaturePos(editor));
+ const out = serializeEditorContent(editor);
+ // The br stays a line break inside one paragraph, not a paragraph split.
+ expect(out).toMatch(/Fax: 2
Mobil: 3/);
+ // An explicit margin is not overridden by the margin-0 default.
+ expect(out).toContain('margin: 4px 0');
+ } finally {
+ editor.destroy();
+ }
+ });
+});
+
+describe('unlockSignatureBlock keeps the classic Outlook-style signature intact', () => {
+ // One
with
line runs, font-size on the block, colored bold spans -
+ // the shape that shipped broken twice: schema parse must keep it a single
+ // paragraph with hard breaks, through the real pipeline (sanitize -> embed
+ // -> initial parse -> unlock).
+ const SIG = `
Mit freundlichem Gruß
+Jane Doe
+IT-Management
+Tel: 111 • Fax: 222
+Der Inhalt dieser E-Mail ist vertraulich.
+
`;
+
+ it('stays one paragraph with all hard breaks after the unlock', () => {
+ const sanitized = sanitizeSignatureHtml(SIG);
+ const body = `
--
`
+ + `${buildSignatureBlock(sanitized)}
`;
+ const editor = makeEditor(body);
+ try {
+ const pos = findSignaturePos(editor);
+ expect(unlockSignatureBlock(editor, pos)).toBe(true);
+ const out = serializeEditorContent(editor);
+ // Leading paragraph + separator + ONE signature paragraph + end marker.
+ expect((out.match(/
el.getAttribute("style"),
- renderHTML: (attrs: Record) =>
- attrs.style ? { style: attrs.style } : {},
- },
- class: {
- default: null as string | null,
- parseHTML: (el: HTMLElement) => el.getAttribute("class"),
- renderHTML: (attrs: Record) =>
- attrs.class ? { class: attrs.class } : {},
- },
- "data-signature-block": {
- default: null as string | null,
- parseHTML: (el: HTMLElement) => el.getAttribute("data-signature-block"),
- renderHTML: (attrs: Record) =>
- attrs["data-signature-block"]
- ? { "data-signature-block": attrs["data-signature-block"] }
- : {},
- },
-};
-
const StyledParagraph = Paragraph.extend({
addAttributes() {
return {
@@ -198,6 +172,7 @@ export function RichTextEditor({
onEditorReady,
}: RichTextEditorProps) {
const rtlEditingSupport = useSettingsStore((st) => st.rtlEditingSupport);
+ const tComposer = useTranslations("email_composer");
const onImageUploadRef = React.useRef(onImageUpload);
onImageUploadRef.current = onImageUpload;
const onEditorReadyRef = React.useRef(onEditorReady);
@@ -253,8 +228,9 @@ export function RichTextEditor({
QuotedHtml,
// Identity signature - held verbatim as a non-editable atomic node so
// rich/branded signatures keep their inline styling in the editor and
- // in the sent mail (see signature-block.ts).
- SignatureBlock,
+ // in the sent mail (see signature-block.ts). Double-click unlocks it
+ // into editable content (#822).
+ SignatureBlock.configure({ editHint: tComposer('signature_edit_hint') }),
TextDirection,
],
content,
diff --git a/components/email/signature-block.ts b/components/email/signature-block.ts
index 507775d2f..0918ae1fe 100644
--- a/components/email/signature-block.ts
+++ b/components/email/signature-block.ts
@@ -1,6 +1,9 @@
"use client";
-import { Node as TiptapNode, mergeAttributes } from "@tiptap/core";
+import { Node as TiptapNode, mergeAttributes, type Editor } from "@tiptap/core";
+import { TextSelection } from "@tiptap/pm/state";
+import { DOMParser as ProseMirrorDOMParser } from "@tiptap/pm/model";
+import { parseHtmlSafely } from "@/lib/email-sanitization";
// Marker attribute that identifies the signature wrapper in serialized HTML,
// so parseHTML can recognise it on the way back in (initial content, drafts).
@@ -43,6 +46,14 @@ function forceLinksToNewTab(root: HTMLElement): void {
* Mirrors QuotedHtml, but the inner region is read-only: a signature is meant
* to be inserted/removed as a unit, not edited inline. Select the node and
* press Backspace/Delete to drop the whole signature.
+ *
+ * Read-only is the default, not a wall (#822): double-click the block to
+ * dissolve it into regular editable content via
+ * {@link unlockSignatureBlock}. That trades the verbatim-styling guarantee
+ * for editability - the explicit user gesture is what makes the trade
+ * acceptable, unlike the silent flattening #476 fixed. (Enter on the selected
+ * block deliberately keeps ProseMirror's default create-paragraph-after
+ * behaviour - it is a low-intent everyday key.)
*/
export const SignatureBlock = TiptapNode.create({
name: "signatureBlock",
@@ -53,6 +64,15 @@ export const SignatureBlock = TiptapNode.create({
// Isolating keeps selection/gapcursor behaviour sane at the boundary.
isolating: true,
+ addOptions() {
+ return {
+ // Translated tooltip shown on the block ("Double-click to edit the
+ // signature"). Set via .configure() where translations are available;
+ // empty means no tooltip.
+ editHint: "",
+ };
+ },
+
addAttributes() {
return {
html: {
@@ -79,10 +99,25 @@ export const SignatureBlock = TiptapNode.create({
},
addNodeView() {
- return ({ node }) => {
+ return ({ node, editor, getPos }) => {
const dom = document.createElement("div");
dom.setAttribute(SIGNATURE_BLOCK_MARKER, "");
dom.className = "signature-block-island";
+ if (this.options.editHint) {
+ dom.title = this.options.editHint;
+ }
+ // Double-click dissolves the atom into regular editable content (#822).
+ // The listener sits on the wrapper; shadow-root events are composed, so
+ // it fires for the whole island.
+ dom.addEventListener("dblclick", (event) => {
+ // A double-click landing on a link is the user working the link (the
+ // first click already opened it) - not a request to start editing.
+ if (event.composedPath().some((t) => (t as Element).tagName === "A")) return;
+ const pos = typeof getPos === "function" ? getPos() : undefined;
+ if (pos == null) return;
+ event.preventDefault();
+ unlockSignatureBlock(editor, pos);
+ });
// CRITICAL: render the signature inside a Shadow Root. The app's global
@@ -125,6 +160,100 @@ export const SignatureBlock = TiptapNode.create({
},
});
+// Block elements that must not end up inside a : a div containing any of
+// these is a container, not a text line, and is left for the schema parse.
+const NON_LINE_BLOCKS =
+ "div, p, table, ul, ol, blockquote, h1, h2, h3, h4, h5, h6, hr, pre, address, figure, dl";
+
+// Inherited style properties hoisted from wrapper divs onto the generated
+// line paragraphs. Wrappers themselves have no schema node and are dropped by
+// the parse, so without hoisting the font/color the whole signature inherits
+// from its container would silently vanish on unlock. Non-inherited
+// properties (background, border, padding) are deliberately NOT hoisted -
+// per-line copies would render differently than the container did.
+const INHERITED_STYLE_PROPS = [
+ "font", "font-family", "font-size", "font-style", "font-weight",
+ "color", "line-height", "letter-spacing", "text-transform", "direction",
+];
+
+/**
+ * Line-level
s render margin-less in mail clients, but the editor schema
+ * has no div node: parsing would re-wrap their content into default-margin
+ * paragraphs and the signature's tight line structure blows apart into spaced
+ * paragraphs. Rewrite leaf divs into explicit margin-0 paragraphs (keeping
+ * their attributes, inheriting wrapper styles) so the unlocked signature
+ * keeps the locked rendering's line spacing - the composer's paragraph
+ * preserves inline styles. Vertical margins are reset only where the line
+ * does not set them itself; horizontal margins (indented lines) are left
+ * alone.
runs and
blocks are untouched (the schema already handles
+ * those faithfully). Returns the parsed body element, ready for the
+ * ProseMirror DOM parser.
+ */
+function normalizeSignatureHtmlForEditing(html: string): HTMLElement {
+ const doc = parseHtmlSafely(html);
+ const body = doc.body;
+ body.querySelectorAll("div").forEach((div) => {
+ if (div.querySelector(NON_LINE_BLOCKS)) return;
+ const p = doc.createElement("p");
+ for (const attr of Array.from(div.attributes)) p.setAttribute(attr.name, attr.value);
+ // Hoist inherited styles from wrapper divs, outermost first so closer
+ // wrappers win; the line's own declarations always take precedence.
+ const wrappers: HTMLElement[] = [];
+ for (let ancestor = div.parentElement; ancestor && ancestor !== body; ancestor = ancestor.parentElement) {
+ if (ancestor.tagName === "DIV") wrappers.unshift(ancestor);
+ }
+ for (const wrapper of wrappers) {
+ for (const prop of INHERITED_STYLE_PROPS) {
+ const value = wrapper.style.getPropertyValue(prop);
+ if (value && !div.style.getPropertyValue(prop)) p.style.setProperty(prop, value);
+ }
+ }
+ // A div renders with zero margins; reset only the verticals the line
+ // does not declare itself, keeping explicit spacing and indents intact.
+ if (!p.style.margin) {
+ if (!p.style.marginTop) p.style.marginTop = "0";
+ if (!p.style.marginBottom) p.style.marginBottom = "0";
+ }
+ while (div.firstChild) p.appendChild(div.firstChild);
+ div.replaceWith(p);
+ });
+ // An empty signature still needs a block to put the caret into.
+ if (!body.firstElementChild) body.appendChild(doc.createElement("p"));
+ return body;
+}
+
+/**
+ * Dissolve the signature atom at `pos` into regular, editable editor content
+ * (#822). The verbatim HTML is parsed into the schema at the atom's position -
+ * the pre-#476 representation, with the known consequence that heavily styled
+ * markup may be normalized by the schema. Leaf
lines are rewritten to
+ * margin-0 paragraphs first so the line spacing survives the parse (see
+ * normalizeSignatureHtmlForEditing). The bracketing `data-signature-block`
+ * marker paragraphs live OUTSIDE the atom and are left untouched, so the
+ * identity-switch splice and the send path's already-embedded detection keep
+ * working on the unlocked content, and switching identities re-embeds a fresh
+ * locked block. Undo restores the atom.
+ */
+export function unlockSignatureBlock(editor: Editor, pos: number): boolean {
+ if (!editor.isEditable) return false;
+ const node = editor.state.doc.nodeAt(pos);
+ if (!node || node.type.name !== "signatureBlock") return false;
+ const body = normalizeSignatureHtmlForEditing(String(node.attrs.html || ""));
+ // Parse as a CLOSED document (complete blocks) and splice it in with one
+ // plain transaction. insertContentAt parses an OPEN slice instead, and
+ // fitting an open slice may split text runs at hard breaks - which turned a
+ //
-lined signature into one spaced paragraph per line in the browser.
+ // A closed fragment of complete blocks cannot be refitted that way.
+ const parsed = ProseMirrorDOMParser.fromSchema(editor.state.schema).parse(body, {
+ preserveWhitespace: false,
+ });
+ const tr = editor.state.tr.replaceWith(pos, pos + node.nodeSize, parsed.content);
+ tr.setSelection(TextSelection.near(tr.doc.resolve(pos))).scrollIntoView();
+ editor.view.dispatch(tr);
+ editor.view.focus();
+ return true;
+}
+
/**
* Build the editor-content wrapper that embeds the signature as a single
* SignatureBlock node. The inner HTML must be pre-sanitized
diff --git a/components/email/styled-block-attributes.ts b/components/email/styled-block-attributes.ts
new file mode 100644
index 000000000..9cee9056f
--- /dev/null
+++ b/components/email/styled-block-attributes.ts
@@ -0,0 +1,28 @@
+// Pasted email content (signatures, replies, quoted text) commonly carries
+// inline styles on block elements. StarterKit's default Paragraph/Heading
+// drop unknown attributes; the composer extends them with these so `style`,
+// `class` and the signature range marker round-trip the editor. Kept in its
+// own module so tests can build the exact composer paragraph instead of
+// hand-copying (and silently drifting from) this config.
+export const styledBlockAttributes = {
+ style: {
+ default: null as string | null,
+ parseHTML: (el: HTMLElement) => el.getAttribute("style"),
+ renderHTML: (attrs: Record) =>
+ attrs.style ? { style: attrs.style } : {},
+ },
+ class: {
+ default: null as string | null,
+ parseHTML: (el: HTMLElement) => el.getAttribute("class"),
+ renderHTML: (attrs: Record) =>
+ attrs.class ? { class: attrs.class } : {},
+ },
+ "data-signature-block": {
+ default: null as string | null,
+ parseHTML: (el: HTMLElement) => el.getAttribute("data-signature-block"),
+ renderHTML: (attrs: Record) =>
+ attrs["data-signature-block"]
+ ? { "data-signature-block": attrs["data-signature-block"] }
+ : {},
+ },
+};
diff --git a/locales/ar/common.json b/locales/ar/common.json
index b39180bb8..fded78cd5 100644
--- a/locales/ar/common.json
+++ b/locales/ar/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "إيصال القراءة مطلوب (انقر للتعطيل)",
"read_receipt_off": "طلب إيصال قراءة",
"new_message": "رسالة جديدة",
+ "signature_edit_hint": "انقر نقرًا مزدوجًا لتحرير التوقيع",
"reply": "رد",
"reply_all": "الرد على الجميع",
"forward": "إعادة توجيه",
diff --git a/locales/ca/common.json b/locales/ca/common.json
index 22f503834..825892a61 100644
--- a/locales/ca/common.json
+++ b/locales/ca/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Confirmació de lectura sol·licitada (feu clic per desactivar-la)",
"read_receipt_off": "Sol·licita una confirmació de lectura",
"new_message": "Missatge nou",
+ "signature_edit_hint": "Feu doble clic per editar la signatura",
"reply": "Respon",
"reply_all": "Respon a tots",
"forward": "Reenvia",
diff --git a/locales/cs/common.json b/locales/cs/common.json
index 2570b45f2..0557708b1 100644
--- a/locales/cs/common.json
+++ b/locales/cs/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Vyžádáno potvrzení o přečtení (kliknutím vypnete)",
"read_receipt_off": "Vyžádat potvrzení o přečtení",
"new_message": "Nová zpráva",
+ "signature_edit_hint": "Dvojklikem upravíte podpis",
"reply": "Odpovědět",
"reply_all": "Odpovědět všem",
"forward": "Přeposlat",
diff --git a/locales/da/common.json b/locales/da/common.json
index 3cb27e398..f86bf048a 100644
--- a/locales/da/common.json
+++ b/locales/da/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Læsekvittering anmodet (klik for at deaktivere)",
"read_receipt_off": "Anmod om læsekvittering",
"new_message": "Ny besked",
+ "signature_edit_hint": "Dobbeltklik for at redigere signaturen",
"reply": "Svar",
"reply_all": "Svar alle",
"forward": "Videresend",
diff --git a/locales/de/common.json b/locales/de/common.json
index d4d7045a6..bdbdcb58a 100644
--- a/locales/de/common.json
+++ b/locales/de/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Lesebestätigung angefordert (klicken zum Deaktivieren)",
"read_receipt_off": "Lesebestätigung anfordern",
"new_message": "Neue Nachricht",
+ "signature_edit_hint": "Doppelklick, um die Signatur zu bearbeiten",
"reply": "Antworten",
"reply_all": "Allen antworten",
"forward": "Weiterleiten",
diff --git a/locales/en/common.json b/locales/en/common.json
index 9280dc65f..df4fee084 100644
--- a/locales/en/common.json
+++ b/locales/en/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Read receipt requested (click to disable)",
"read_receipt_off": "Request a read receipt",
"new_message": "New Message",
+ "signature_edit_hint": "Double-click to edit the signature",
"reply": "Reply",
"reply_all": "Reply All",
"forward": "Forward",
diff --git a/locales/es/common.json b/locales/es/common.json
index 25e20438e..5f55898ae 100644
--- a/locales/es/common.json
+++ b/locales/es/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Confirmación de lectura solicitada (haz clic para desactivar)",
"read_receipt_off": "Solicitar confirmación de lectura",
"new_message": "Nuevo Mensaje",
+ "signature_edit_hint": "Haz doble clic para editar la firma",
"reply": "Responder",
"reply_all": "Responder a Todos",
"forward": "Reenviar",
diff --git a/locales/fa/common.json b/locales/fa/common.json
index 629d514fc..f4d3811ad 100644
--- a/locales/fa/common.json
+++ b/locales/fa/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "درخواست تأیید خواندن فعال (کلیک برای غیرفعال کردن)",
"read_receipt_off": "درخواست تأیید خواندن",
"new_message": "پیام جدید",
+ "signature_edit_hint": "برای ویرایش امضا دوبار کلیک کنید",
"reply": "پاسخ",
"reply_all": "پاسخ به همه",
"forward": "ارسال مجدد",
diff --git a/locales/fr/common.json b/locales/fr/common.json
index 366374a3e..31f1c9a70 100644
--- a/locales/fr/common.json
+++ b/locales/fr/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Accusé de lecture demandé (cliquez pour désactiver)",
"read_receipt_off": "Demander un accusé de lecture",
"new_message": "Nouveau message",
+ "signature_edit_hint": "Double-cliquez pour modifier la signature",
"reply": "Répondre",
"reply_all": "Répondre à tous",
"forward": "Transférer",
diff --git a/locales/he/common.json b/locales/he/common.json
index bc6c7f5d1..c10b8f43d 100644
--- a/locales/he/common.json
+++ b/locales/he/common.json
@@ -537,6 +537,7 @@
},
"email_composer": {
"new_message": "הודעה חדשה",
+ "signature_edit_hint": "לחץ לחיצה כפולה לעריכת החתימה",
"reply": "השב",
"reply_all": "השב לכולם",
"forward": "קדימה",
diff --git a/locales/hu/common.json b/locales/hu/common.json
index c1d7170f8..8f61011ca 100644
--- a/locales/hu/common.json
+++ b/locales/hu/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Olvasási visszaigazolás kérve (kattints a letiltáshoz)",
"read_receipt_off": "Olvasási visszaigazolás kérése",
"new_message": "Új üzenet",
+ "signature_edit_hint": "Kattintson duplán az aláírás szerkesztéséhez",
"reply": "Válasz",
"reply_all": "Válasz mindenkinek",
"forward": "Továbbítás",
diff --git a/locales/it/common.json b/locales/it/common.json
index 45a2bc615..8c03fb27c 100644
--- a/locales/it/common.json
+++ b/locales/it/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Conferma di lettura richiesta (clicca per disattivare)",
"read_receipt_off": "Richiedi una conferma di lettura",
"new_message": "Nuovo messaggio",
+ "signature_edit_hint": "Doppio clic per modificare la firma",
"reply": "Rispondi",
"reply_all": "Rispondi a tutti",
"forward": "Inoltra",
diff --git a/locales/ja/common.json b/locales/ja/common.json
index d4b7d4df4..3876f6505 100644
--- a/locales/ja/common.json
+++ b/locales/ja/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "開封確認を要求中(クリックで無効化)",
"read_receipt_off": "開封確認を要求する",
"new_message": "新規メッセージ",
+ "signature_edit_hint": "ダブルクリックで署名を編集",
"reply": "返信",
"reply_all": "全員に返信",
"forward": "転送",
diff --git a/locales/ko/common.json b/locales/ko/common.json
index 56ed12e9d..c3723ee4c 100644
--- a/locales/ko/common.json
+++ b/locales/ko/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "읽음 확인 요청됨 (클릭하여 해제)",
"read_receipt_off": "읽음 확인 요청",
"new_message": "새 메시지",
+ "signature_edit_hint": "서명을 편집하려면 두 번 클릭하세요",
"reply": "답장",
"reply_all": "전체 답장",
"forward": "전달",
diff --git a/locales/lv/common.json b/locales/lv/common.json
index 0c4f15193..1d2113128 100644
--- a/locales/lv/common.json
+++ b/locales/lv/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Pieprasīts lasīšanas apstiprinājums (noklikšķiniet, lai atspējotu)",
"read_receipt_off": "Pieprasīt lasīšanas apstiprinājumu",
"new_message": "Jauns ziņojums",
+ "signature_edit_hint": "Veiciet dubultklikšķi, lai rediģētu parakstu",
"reply": "Atbildēt",
"reply_all": "Atbildēt visiem",
"forward": "Pārsūtīt",
diff --git a/locales/mn/common.json b/locales/mn/common.json
index 59424b3d0..4fae3ccc6 100644
--- a/locales/mn/common.json
+++ b/locales/mn/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Уншсан баримт хүссэн (идэвхгүй болгохын тулд дарна уу)",
"read_receipt_off": "Уншсан баримт хүсэх",
"new_message": "Шинэ мессеж",
+ "signature_edit_hint": "Гарын үсгийг засахын тулд давхар товшино уу",
"reply": "Хариулах",
"reply_all": "Бүгдэд нь хариулах",
"forward": "Урагшаа",
diff --git a/locales/nl/common.json b/locales/nl/common.json
index c8e460521..83a666c7e 100644
--- a/locales/nl/common.json
+++ b/locales/nl/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Leesbevestiging aangevraagd (klik om uit te schakelen)",
"read_receipt_off": "Leesbevestiging aanvragen",
"new_message": "Nieuw bericht",
+ "signature_edit_hint": "Dubbelklik om de handtekening te bewerken",
"reply": "Beantwoorden",
"reply_all": "Allen beantwoorden",
"forward": "Doorsturen",
diff --git a/locales/pl/common.json b/locales/pl/common.json
index f4d1fa706..4f449c468 100644
--- a/locales/pl/common.json
+++ b/locales/pl/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Zażądano potwierdzenia przeczytania (kliknij, aby wyłączyć)",
"read_receipt_off": "Zażądaj potwierdzenia przeczytania",
"new_message": "Nowa wiadomość",
+ "signature_edit_hint": "Kliknij dwukrotnie, aby edytować podpis",
"reply": "Odpowiedz",
"reply_all": "Odpowiedz wszystkim",
"forward": "Przekaż dalej",
diff --git a/locales/pt/common.json b/locales/pt/common.json
index d234128a5..dc06101f6 100644
--- a/locales/pt/common.json
+++ b/locales/pt/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Confirmação de leitura solicitada (clique para desativar)",
"read_receipt_off": "Solicitar confirmação de leitura",
"new_message": "Nova Mensagem",
+ "signature_edit_hint": "Clique duas vezes para editar a assinatura",
"reply": "Responder",
"reply_all": "Responder a Todos",
"forward": "Encaminhar",
diff --git a/locales/ro/common.json b/locales/ro/common.json
index 1a0d06a7d..82938f547 100644
--- a/locales/ro/common.json
+++ b/locales/ro/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Solicitare confirmare de citire (faceți clic pentru a dezactiva)",
"read_receipt_off": "Solicitați o confirmare de citire",
"new_message": "Mesaj nou",
+ "signature_edit_hint": "Dublu clic pentru a edita semnătura",
"reply": "Răspunde",
"reply_all": "Răspunde tuturor",
"forward": "Redirecționează",
diff --git a/locales/ru/common.json b/locales/ru/common.json
index 4725a400c..051ded7a8 100644
--- a/locales/ru/common.json
+++ b/locales/ru/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Запрошено уведомление о прочтении (нажмите, чтобы отключить)",
"read_receipt_off": "Запросить уведомление о прочтении",
"new_message": "Новое письмо",
+ "signature_edit_hint": "Дважды щёлкните, чтобы изменить подпись",
"reply": "Ответить",
"reply_all": "Ответить всем",
"forward": "Переслать",
diff --git a/locales/sk/common.json b/locales/sk/common.json
index 6cded9d01..60e88ad20 100644
--- a/locales/sk/common.json
+++ b/locales/sk/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Potvrdenie o prečítaní požiadané (kliknutím vypnete)",
"read_receipt_off": "Požiadať o potvrdenie o prečítaní",
"new_message": "Nová správa",
+ "signature_edit_hint": "Dvojklikom upravíte podpis",
"reply": "Odpovedať",
"reply_all": "Odpovedať všetkým",
"forward": "Preposlať",
diff --git a/locales/tr/common.json b/locales/tr/common.json
index 65d2da05a..c4b241e82 100644
--- a/locales/tr/common.json
+++ b/locales/tr/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Okundu bilgisi istendi (devre dışı bırakmak için tıklayın)",
"read_receipt_off": "Okundu bilgisi iste",
"new_message": "Yeni İleti",
+ "signature_edit_hint": "İmzayı düzenlemek için çift tıklayın",
"reply": "Yanıtla",
"reply_all": "Tümünü Yanıtla",
"forward": "İlet",
diff --git a/locales/uk/common.json b/locales/uk/common.json
index f21e1ebb3..2a5e08a73 100644
--- a/locales/uk/common.json
+++ b/locales/uk/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "Запитано сповіщення про прочитання (натисніть, щоб вимкнути)",
"read_receipt_off": "Запитати сповіщення про прочитання",
"new_message": "Нове повідомлення",
+ "signature_edit_hint": "Двічі клацніть, щоб редагувати підпис",
"reply": "Відповісти",
"reply_all": "Відповісти всім",
"forward": "вперед",
diff --git a/locales/zh/common.json b/locales/zh/common.json
index 7bd5deab1..a339e7ee7 100644
--- a/locales/zh/common.json
+++ b/locales/zh/common.json
@@ -574,6 +574,7 @@
"read_receipt_on": "已请求已读回执(点击以关闭)",
"read_receipt_off": "请求已读回执",
"new_message": "新邮件",
+ "signature_edit_hint": "双击以编辑签名",
"reply": "回复",
"reply_all": "全部回复",
"forward": "转发",