fix: preserve newlines correctly in delete-and-redraft (#845)
fixes #830
This commit is contained in:
parent
d5eac4e119
commit
27da387a01
24
src/routes/_actions/deleteAndRedraft.js
Normal file
24
src/routes/_actions/deleteAndRedraft.js
Normal file
|
@ -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()
|
||||||
|
}
|
|
@ -20,8 +20,7 @@ import { setAccountMuted } from '../../../_actions/mute'
|
||||||
import { setStatusPinnedOrUnpinned } from '../../../_actions/pin'
|
import { setStatusPinnedOrUnpinned } from '../../../_actions/pin'
|
||||||
import { setConversationMuted } from '../../../_actions/muteConversation'
|
import { setConversationMuted } from '../../../_actions/muteConversation'
|
||||||
import { copyText } from '../../../_actions/copyText'
|
import { copyText } from '../../../_actions/copyText'
|
||||||
import { statusHtmlToPlainText } from '../../../_utils/statusHtmlToPlainText'
|
import { deleteAndRedraft } from '../../../_actions/deleteAndRedraft'
|
||||||
import { importShowComposeDialog } from '../asyncDialogs'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate,
|
oncreate,
|
||||||
|
@ -189,24 +188,8 @@ export default {
|
||||||
},
|
},
|
||||||
async onRedraft () {
|
async onRedraft () {
|
||||||
let { status } = this.get()
|
let { status } = this.get()
|
||||||
let deleteStatusPromise = doDeleteStatus(status.id)
|
await deleteAndRedraft(status)
|
||||||
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
|
|
||||||
})
|
|
||||||
this.close()
|
this.close()
|
||||||
let showComposeDialog = await dialogPromise
|
|
||||||
showComposeDialog()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,8 @@ import { mark, stop } from './marks'
|
||||||
|
|
||||||
let domParser = process.browser && new DOMParser()
|
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')
|
let anchors = doc.querySelectorAll('a.mention')
|
||||||
for (let i = 0; i < anchors.length; i++) {
|
for (let i = 0; i < anchors.length; i++) {
|
||||||
let anchor = anchors[i]
|
let anchor = anchors[i]
|
||||||
|
@ -18,7 +13,29 @@ export function statusHtmlToPlainText (html, mentions) {
|
||||||
anchor.innerText = `@${mention.acct}`
|
anchor.innerText = `@${mention.acct}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let res = doc.documentElement.textContent
|
}
|
||||||
|
|
||||||
|
// paragraphs should be separated by double newlines
|
||||||
|
// single <br/>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')
|
stop('statusHtmlToPlainText')
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,3 +145,20 @@ test('delete and redraft reply within thread', async t => {
|
||||||
})
|
})
|
||||||
.expect(getNthStatus(2).exists).notOk()
|
.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')
|
||||||
|
})
|
||||||
|
|
Loading…
Reference in a new issue