fix: preserve newlines correctly in delete-and-redraft (#845)
fixes #830
This commit is contained in:
		
							parent
							
								
									d5eac4e119
								
							
						
					
					
						commit
						27da387a01
					
				
					 4 changed files with 68 additions and 27 deletions
				
			
		
							
								
								
									
										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) {
 | 
					// mentions like "@foo" have to be expanded to "@foo@example.com"
 | 
				
			||||||
  if (!html) {
 | 
					function massageMentions (doc, mentions) {
 | 
				
			||||||
    return ''
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  mark('statusHtmlToPlainText')
 | 
					 | 
				
			||||||
  let doc = domParser.parseFromString(html, 'text/html')
 | 
					 | 
				
			||||||
  // mentions like "@foo" have to be expanded to "@foo@example.com"
 | 
					 | 
				
			||||||
  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…
	
	Add table
		
		Reference in a new issue