From 27da387a0127f0cf798dc04922be066df5fda30e Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Wed, 19 Dec 2018 00:57:56 -0800 Subject: [PATCH] fix: preserve newlines correctly in delete-and-redraft (#845) fixes #830 --- src/routes/_actions/deleteAndRedraft.js | 24 ++++++++++++++ .../components/StatusOptionsDialog.html | 21 ++---------- src/routes/_utils/statusHtmlToPlainText.js | 33 ++++++++++++++----- tests/spec/121-delete-and-redraft.js | 17 ++++++++++ 4 files changed, 68 insertions(+), 27 deletions(-) create mode 100644 src/routes/_actions/deleteAndRedraft.js diff --git a/src/routes/_actions/deleteAndRedraft.js b/src/routes/_actions/deleteAndRedraft.js new file mode 100644 index 0000000..aeb087d --- /dev/null +++ b/src/routes/_actions/deleteAndRedraft.js @@ -0,0 +1,24 @@ +import { statusHtmlToPlainText } from '../_utils/statusHtmlToPlainText' +import { importShowComposeDialog } from '../_components/dialog/asyncDialogs' +import { doDeleteStatus } from './delete' +import { store } from '../_store/store' + +export async function deleteAndRedraft (status) { + let deleteStatusPromise = doDeleteStatus(status.id) + let dialogPromise = importShowComposeDialog() + await deleteStatusPromise + + store.setComposeData('dialog', { + text: statusHtmlToPlainText(status.content, status.mentions), + contentWarningShown: !!status.spoiler_text, + contentWarning: status.spoiler_text || '', + postPrivacy: status.visibility, + media: status.media_attachments && status.media_attachments.map(_ => ({ + description: _.description || '', + data: _ + })), + inReplyToId: status.in_reply_to_id + }) + let showComposeDialog = await dialogPromise + showComposeDialog() +} diff --git a/src/routes/_components/dialog/components/StatusOptionsDialog.html b/src/routes/_components/dialog/components/StatusOptionsDialog.html index fd21c0e..4fcf60a 100644 --- a/src/routes/_components/dialog/components/StatusOptionsDialog.html +++ b/src/routes/_components/dialog/components/StatusOptionsDialog.html @@ -20,8 +20,7 @@ import { setAccountMuted } from '../../../_actions/mute' import { setStatusPinnedOrUnpinned } from '../../../_actions/pin' import { setConversationMuted } from '../../../_actions/muteConversation' import { copyText } from '../../../_actions/copyText' -import { statusHtmlToPlainText } from '../../../_utils/statusHtmlToPlainText' -import { importShowComposeDialog } from '../asyncDialogs' +import { deleteAndRedraft } from '../../../_actions/deleteAndRedraft' export default { oncreate, @@ -189,24 +188,8 @@ export default { }, async onRedraft () { let { status } = this.get() - let deleteStatusPromise = doDeleteStatus(status.id) - let dialogPromise = importShowComposeDialog() - await deleteStatusPromise - - this.store.setComposeData('dialog', { - text: statusHtmlToPlainText(status.content, status.mentions), - contentWarningShown: !!status.spoiler_text, - contentWarning: status.spoiler_text || '', - postPrivacy: status.visibility, - media: status.media_attachments && status.media_attachments.map(_ => ({ - description: _.description || '', - data: _ - })), - inReplyToId: status.in_reply_to_id - }) + await deleteAndRedraft(status) this.close() - let showComposeDialog = await dialogPromise - showComposeDialog() } } } diff --git a/src/routes/_utils/statusHtmlToPlainText.js b/src/routes/_utils/statusHtmlToPlainText.js index 40f912a..9911768 100644 --- a/src/routes/_utils/statusHtmlToPlainText.js +++ b/src/routes/_utils/statusHtmlToPlainText.js @@ -2,13 +2,8 @@ import { mark, stop } from './marks' let domParser = process.browser && new DOMParser() -export function statusHtmlToPlainText (html, mentions) { - if (!html) { - return '' - } - mark('statusHtmlToPlainText') - let doc = domParser.parseFromString(html, 'text/html') - // mentions like "@foo" have to be expanded to "@foo@example.com" +// mentions like "@foo" have to be expanded to "@foo@example.com" +function massageMentions (doc, mentions) { let anchors = doc.querySelectorAll('a.mention') for (let i = 0; i < anchors.length; i++) { let anchor = anchors[i] @@ -18,7 +13,29 @@ export function statusHtmlToPlainText (html, mentions) { anchor.innerText = `@${mention.acct}` } } - let res = doc.documentElement.textContent +} + +// paragraphs should be separated by double newlines +// single
s should become single newlines +function innerTextRetainingNewlines (doc) { + let paragraphs = doc.querySelectorAll('p') + return Array.from(paragraphs).map(paragraph => { + let brs = paragraph.querySelectorAll('br') + Array.from(brs).forEach(br => { + br.parentNode.replaceChild(doc.createTextNode('\n'), br) + }) + return paragraph.textContent + }).join('\n\n') +} + +export function statusHtmlToPlainText (html, mentions) { + if (!html) { + return '' + } + mark('statusHtmlToPlainText') + let doc = domParser.parseFromString(html, 'text/html') + massageMentions(doc, mentions) + let res = innerTextRetainingNewlines(doc) stop('statusHtmlToPlainText') return res } diff --git a/tests/spec/121-delete-and-redraft.js b/tests/spec/121-delete-and-redraft.js index bbd7a37..a8e6ef7 100644 --- a/tests/spec/121-delete-and-redraft.js +++ b/tests/spec/121-delete-and-redraft.js @@ -145,3 +145,20 @@ test('delete and redraft reply within thread', async t => { }) .expect(getNthStatus(2).exists).notOk() }) + +test('multiple paragraphs', async t => { + let text = 'hey ho\n\ndouble newline!\njust one newline\njust another newline\n\nanother double newline!' + await postAs('foobar', text) + await loginAsFoobar(t) + await t + .hover(getNthStatus(0)) + .expect(getNthStatusContent(0).innerText).contains(text) + .click(getNthStatusOptionsButton(0)) + .click(dialogOptionsOption.withText('Delete and redraft')) + .expect(modalDialog.hasAttribute('aria-hidden')).notOk() + .expect(composeModalInput.value).eql(text) + .typeText(composeModalInput, '\n\nwoot', { paste: true }) + .click(composeModalComposeButton) + .expect(modalDialog.exists).notOk() + .expect(getNthStatusContent(0).innerText).contains(text + '\n\nwoot') +})