From 65771daa0ffded8ede07b35f7aabecc3764203fc Mon Sep 17 00:00:00 2001 From: "Jan C. Borchardt" <925062+jancborchardt@users.noreply.github.com> Date: Mon, 11 May 2026 12:59:07 +0200 Subject: [PATCH] fix(composer): Fix paragraph breaks getting lost on paste and sending AI-assisted: GitHub Copilot (Claude Sonnet 4.6) Signed-off-by: Jan C. Borchardt <925062+jancborchardt@users.noreply.github.com> --- lib/Service/MimeMessage.php | 22 +----------- src/ckeditor/mail/MailPlugin.js | 36 -------------------- src/components/TextEditor.vue | 7 ++-- src/tests/unit/components/MailPlugin.spec.js | 23 ------------- src/tests/unit/components/TextEditor.spec.js | 7 ++-- src/tests/unit/util/text.spec.js | 10 +++--- src/util/text.js | 31 +++++++++++++++-- 7 files changed, 42 insertions(+), 94 deletions(-) delete mode 100644 src/ckeditor/mail/MailPlugin.js delete mode 100644 src/tests/unit/components/MailPlugin.spec.js diff --git a/lib/Service/MimeMessage.php b/lib/Service/MimeMessage.php index 1188d18500..7b1a9031c9 100644 --- a/lib/Service/MimeMessage.php +++ b/lib/Service/MimeMessage.php @@ -11,7 +11,6 @@ use DOMDocument; use DOMElement; -use DOMNode; use Horde_Mime_Part; use Horde_Text_Filter; use OCA\Mail\Exception\InvalidDataUriException; @@ -100,7 +99,7 @@ private function buildMessagePart(?string $contentPlain, ?string $contentHtml, a $plainPart->setType('text/plain'); $plainPart->setCharset('UTF-8'); $plainPart->setContents( - Horde_Text_Filter::filter($contentHtml, 'Html2text', ['callback' => [$this, 'htmlToTextCallback']]) + Horde_Text_Filter::filter($contentHtml, 'Html2text', []) ); } @@ -258,23 +257,4 @@ private function separateAttachments(array $attachments): array { return [$inline, $normal]; } - - /** - * A callback for Horde_Text_Filter. - * - * The purpose of this callback is to overwrite the default behavior - * of html2text filter to convert
Hello
=> Hello\n\n with - *Hello
=> Hello\n. - * - * @param DOMDocument $doc - * @param DOMNode $node - * @return string|null non-null, add this text to the output and skip further processing of the node. - */ - public function htmlToTextCallback(DOMDocument $doc, DOMNode $node) { - if ($node instanceof DOMElement && strtolower($node->tagName) === 'p') { - return $node->textContent . "\n"; - } - - return null; - } } diff --git a/src/ckeditor/mail/MailPlugin.js b/src/ckeditor/mail/MailPlugin.js deleted file mode 100644 index ca04e34a25..0000000000 --- a/src/ckeditor/mail/MailPlugin.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { Paragraph, Plugin } from 'ckeditor5' - -export default class Mail extends Plugin { - static get requires() { - return [Paragraph] - } - - init() { - this._overwriteParagraphConversion() - } - - /** - * Overwrite the elementToElement conversion - * from the default paragraph plugin to add - * margin:0 to every. - * - * @private - */ - _overwriteParagraphConversion() { - this.editor.conversion.elementToElement({ - model: 'paragraph', - view: { - name: 'p', - styles: { - margin: 0, - }, - }, - converterPriority: 'high', - }) - } -} diff --git a/src/components/TextEditor.vue b/src/components/TextEditor.vue index 9f970fdae1..8ae96ac6cd 100644 --- a/src/components/TextEditor.vue +++ b/src/components/TextEditor.vue @@ -54,7 +54,6 @@ import { } from 'ckeditor5' import { getLinkWithPicker, searchProvider } from '@nextcloud/vue/components/NcRichText' import TextDirectionPlugin from '../ckeditor/direction/TextDirectionPlugin.js' -import MailPlugin from '../ckeditor/mail/MailPlugin.js' import QuotePlugin from '../ckeditor/quote/QuotePlugin.js' import SignaturePlugin from '../ckeditor/signature/SignaturePlugin.js' import PickerPlugin from '../ckeditor/smartpicker/PickerPlugin.js' @@ -153,7 +152,6 @@ export default { Font, RemoveFormat, Base64UploadAdapter, - MailPlugin, SourceEditing, TextDirectionPlugin, ]) @@ -671,6 +669,11 @@ export default { cursor: text; margin: 0 !important; } + +// Show empty lines correctly in composer +:deep(p + p) { + margin-top: 1em !important; +}