Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 1 addition & 20 deletions lib/Service/MimeMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,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', [])
);
}

Expand Down Expand Up @@ -258,23 +258,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 <p>Hello</p> => Hello\n\n with
* <p>Hello</p> => 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;
}
}
36 changes: 0 additions & 36 deletions src/ckeditor/mail/MailPlugin.js

This file was deleted.

7 changes: 5 additions & 2 deletions src/components/TextEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,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'
Expand Down Expand Up @@ -152,7 +151,6 @@ export default {
Font,
RemoveFormat,
Base64UploadAdapter,
MailPlugin,
TextDirectionPlugin,
])
toolbar.unshift(...[
Expand Down Expand Up @@ -600,6 +598,11 @@ export default {
cursor: text;
margin: 0 !important;
}

// Show empty lines correctly in composer
:deep(p + p) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will only fix it in the editor, not for the sent email, no?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this part specifically is to fix the appearance when pasted into the composer (both plaintext and HTML), as without it it looks like the "before" pics. Just verified and tested again, removing that line specifically.

I started with only that CSS part, but that wasn’t enough to fix the issue. So the rest is to make it actually work during the mail sending process.

margin-top: 1em !important;
}
</style>

<style>
Expand Down
23 changes: 0 additions & 23 deletions src/tests/unit/components/MailPlugin.spec.js

This file was deleted.

7 changes: 3 additions & 4 deletions src/tests/unit/components/TextEditor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { createLocalVue, shallowMount } from '@vue/test-utils'
import { Paragraph } from 'ckeditor5'
import mitt from 'mitt'
import TextEditor from '../../../components/TextEditor.vue'
import MailPlugin from '../../../ckeditor/mail/MailPlugin.js'
import Nextcloud from '../../../mixins/Nextcloud.js'
import VirtualTestEditor from '../../virtualtesteditor.js'

Expand Down Expand Up @@ -89,7 +88,7 @@ describe('TextEditor', () => {

expect(wrapper.emitted().ready[0]).toBeTruthy()
})
it('register conversion to add margin: 0px to every <p> element', async () => {
it('does not add inline margin style to paragraph elements', async () => {
const wrapper = shallowMount(TextEditor, {
localVue,
propsData: {
Expand All @@ -105,7 +104,7 @@ describe('TextEditor', () => {
const editor = await VirtualTestEditor.create({
licenseKey: 'GPL',
initialData: '<p>bonjour bonjour</p>',
plugins: [Paragraph, MailPlugin],
plugins: [Paragraph],
})

editor.ui = {
Expand All @@ -122,6 +121,6 @@ describe('TextEditor', () => {

expect(wrapper.emitted().ready[0]).toBeTruthy()
expect(wrapper.emitted().ready[0][0].getData())
.toEqual('<p style="margin:0;">bonjour bonjour</p>')
.toEqual('<p>bonjour bonjour</p>')
})
})
10 changes: 5 additions & 5 deletions src/tests/unit/util/text.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ describe('text', () => {
expect(actual).toEqual(expected)
})

it('produces a single line break between paragraphs', () => {
it('produces a blank line between paragraphs', () => {
const source = html('<p>hello</p><p>world</p>')
const expected = plain('hello\nworld')
const expected = plain('hello\n\nworld')

const actual = toPlain(source)

Expand All @@ -106,7 +106,7 @@ describe('text', () => {
})

it('produces a single line break after each block element', () => {
const selectors = ['p', 'div', 'header', 'footer', 'form', 'article', 'aside', 'main', 'nav', 'section']
const selectors = ['div', 'header', 'footer', 'form', 'article', 'aside', 'main', 'nav', 'section']
const source = html(selectors
.map((tag) => `<${tag}>foobar</${tag}>`)
.join(''))
Expand All @@ -118,7 +118,7 @@ describe('text', () => {
})

it('produces exactly one line break for each closing block element', () => {
const selectors = ['p', 'div', 'header', 'footer', 'form', 'article', 'aside', 'main', 'nav', 'section']
const selectors = ['div', 'header', 'footer', 'form', 'article', 'aside', 'main', 'nav', 'section']
const source = html(selectors
.map((tag) => `<${tag}><${tag}>foobar</${tag}></${tag}>`)
.join(''))
Expand All @@ -142,7 +142,7 @@ describe('text', () => {
const source = html('<html>'
+ '<body><p>Hello!</p><p>this <i>is</i> <b>some</b> random <strong>text</strong></p></body>'
+ '</html>')
const expected = plain('Hello!\nthis is some random text')
const expected = plain('Hello!\n\nthis is some random text')

const actual = toPlain(source)

Expand Down
31 changes: 28 additions & 3 deletions src/util/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,10 @@ export function toPlain(text) {
return text
}

// Build shared options for all block tags
const blockTags = ['p', 'div', 'header', 'footer', 'form', 'article', 'aside', 'main', 'nav', 'section']
const blockSelectors = blockTags.map((tag) => ({
// Build shared options for non-paragraph block tags.
// <p> is handled separately by customParagraph below.
const nonParagraphBlockTags = ['div', 'header', 'footer', 'form', 'article', 'aside', 'main', 'nav', 'section']
const blockSelectors = nonParagraphBlockTags.map((tag) => ({
selector: tag,
format: 'customBlock',
options: {
Expand Down Expand Up @@ -130,6 +131,23 @@ export function toPlain(text) {
// each closing tag.
builder.addLineBreak()
},
// <p> is a paragraph element: two forced line breaks produce the blank
// line that separates paragraphs in plain text, matching email convention
// where a paragraph break equals one blank line (i.e. \n\n).
customParagraph(elem, walk, builder, formatOptions) {
builder.openBlock({
isPre: formatOptions.preserveLeadingWhitespace,
leadingLineBreaks: 0,
})
walk(elem.children, builder)
builder.closeBlock({
trailingLineBreaks: 0,
blockTransform: (text) => text
.replace(/^ {2,}/gm, ' '),
})
builder.addLineBreak()
builder.addLineBreak()
},
customBlockQuote(elem, walk, builder, formatOptions) {
builder.openBlock({
leadingLineBreaks: formatOptions.leadingLineBreaks,
Expand Down Expand Up @@ -163,6 +181,13 @@ export function toPlain(text) {
trailingLineBreaks: 1,
},
},
{
selector: 'p',
format: 'customParagraph',
options: {
preserveLeadingWhitespace: true,
},
},
...blockSelectors,
],
})
Expand Down
Loading