forked from cybrespace/pinafore
parent
631603b0b7
commit
1940260631
|
@ -1,14 +1,11 @@
|
|||
import throttle from 'lodash-es/throttle'
|
||||
import { mark, stop } from '../_utils/marks'
|
||||
import { store } from '../_store/store'
|
||||
import uniqBy from 'lodash-es/uniqBy'
|
||||
import uniq from 'lodash-es/uniq'
|
||||
import isEqual from 'lodash-es/isEqual'
|
||||
import { database } from '../_database/database'
|
||||
import { runMediumPriorityTask } from '../_utils/runMediumPriorityTask'
|
||||
import { concat } from '../_utils/arrays'
|
||||
|
||||
const STREAMING_THROTTLE_DELAY = 3000
|
||||
import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
|
||||
|
||||
function getExistingItemIdsSet (instanceName, timelineName) {
|
||||
let timelineItemIds = store.getForTimeline(instanceName, timelineName, 'timelineItemIds') || []
|
||||
|
@ -95,11 +92,11 @@ async function processFreshUpdates (instanceName, timelineName) {
|
|||
stop('processFreshUpdates')
|
||||
}
|
||||
|
||||
const lazilyProcessFreshUpdates = throttle((instanceName, timelineName) => {
|
||||
runMediumPriorityTask(() => {
|
||||
function lazilyProcessFreshUpdates (instanceName, timelineName) {
|
||||
scheduleIdleTask(() => {
|
||||
/* no await */ processFreshUpdates(instanceName, timelineName)
|
||||
})
|
||||
}, STREAMING_THROTTLE_DELAY)
|
||||
}
|
||||
|
||||
export function addStatusOrNotification (instanceName, timelineName, newStatusOrNotification) {
|
||||
addStatusesOrNotifications(instanceName, timelineName, [newStatusOrNotification])
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { store } from '../_store/store'
|
||||
import { deleteStatus } from '../_api/delete'
|
||||
import { toast } from '../_utils/toast'
|
||||
import { deleteStatus as deleteStatusLocally } from './deleteStatuses'
|
||||
|
||||
export async function doDeleteStatus (statusId) {
|
||||
let { currentInstance, accessToken } = store.get()
|
||||
try {
|
||||
await deleteStatus(currentInstance, accessToken, statusId)
|
||||
deleteStatusLocally(currentInstance, statusId)
|
||||
toast.say('Status deleted.')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { getIdsThatRebloggedThisStatus, getNotificationIdsForStatuses } from './statuses'
|
||||
import { store } from '../_store/store'
|
||||
import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
|
||||
import isEqual from 'lodash-es/isEqual'
|
||||
import { database } from '../_database/database'
|
||||
import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
|
||||
|
||||
function filterItemIdsFromTimelines (instanceName, timelineFilter, idFilter) {
|
||||
let keys = ['timelineItemIds', 'itemIdsToAdd']
|
||||
|
@ -16,6 +16,7 @@ function filterItemIdsFromTimelines (instanceName, timelineFilter, idFilter) {
|
|||
}
|
||||
let filteredIds = ids.filter(idFilter)
|
||||
if (!isEqual(ids, filteredIds)) {
|
||||
console.log('deleting an item from timelineName', timelineName, 'for key', key)
|
||||
store.setForTimeline(instanceName, timelineName, {
|
||||
[key]: filteredIds
|
||||
})
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
composeData: ({ $currentComposeData, realm }) => $currentComposeData[realm] || {},
|
||||
text: ({ composeData }) => composeData.text || '',
|
||||
media: ({ composeData }) => composeData.media || [],
|
||||
inReplyToId: ({ composeData }) => composeData.inReplyToId,
|
||||
postPrivacy: ({ postPrivacyKey }) => POST_PRIVACY_OPTIONS.find(_ => _.key === postPrivacyKey),
|
||||
defaultPostPrivacyKey: ({ $currentVerifyCredentials }) => $currentVerifyCredentials.source.privacy,
|
||||
postPrivacyKey: ({ composeData, defaultPostPrivacyKey }) => composeData.postPrivacy || defaultPostPrivacyKey,
|
||||
|
@ -167,12 +168,13 @@
|
|||
contentWarning,
|
||||
realm,
|
||||
overLimit,
|
||||
inReplyToUuid
|
||||
inReplyToUuid, // typical replies, using Pinafore-specific uuid
|
||||
inReplyToId // delete-and-redraft replies, using standard id
|
||||
} = this.get()
|
||||
let sensitive = media.length && !!contentWarning
|
||||
let mediaIds = media.map(_ => _.data.id)
|
||||
let mediaDescriptions = media.map(_ => _.description)
|
||||
let inReplyTo = (realm === 'home' || realm === 'dialog') ? null : realm
|
||||
let inReplyTo = inReplyToId || ((realm === 'home' || realm === 'dialog') ? null : realm)
|
||||
|
||||
if (overLimit || (!text && !media.length)) {
|
||||
return // do nothing if invalid
|
||||
|
|
|
@ -20,7 +20,7 @@ import { setAccountMuted } from '../../../_actions/mute'
|
|||
import { setStatusPinnedOrUnpinned } from '../../../_actions/pin'
|
||||
import { setConversationMuted } from '../../../_actions/muteConversation'
|
||||
import { copyText } from '../../../_actions/copyText'
|
||||
import { htmlToPlainText } from '../../../_utils/htmlToPlainText'
|
||||
import { statusHtmlToPlainText } from '../../../_utils/statusHtmlToPlainText'
|
||||
import { importShowComposeDialog } from '../asyncDialogs'
|
||||
|
||||
export default {
|
||||
|
@ -192,15 +192,17 @@ export default {
|
|||
let deleteStatusPromise = doDeleteStatus(status.id)
|
||||
let dialogPromise = importShowComposeDialog()
|
||||
await deleteStatusPromise
|
||||
|
||||
this.store.setComposeData('dialog', {
|
||||
text: (status.content && htmlToPlainText(status.content)) || '',
|
||||
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()
|
||||
let showComposeDialog = await dialogPromise
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
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"
|
||||
let anchors = doc.querySelectorAll('a.mention')
|
||||
for (let i = 0; i < anchors.length; i++) {
|
||||
let anchor = anchors[i]
|
||||
let href = anchor.getAttribute('href')
|
||||
let mention = mentions.find(mention => mention.url === href)
|
||||
if (mention) {
|
||||
anchor.innerText = `@${mention.acct}`
|
||||
}
|
||||
}
|
||||
let res = doc.documentElement.textContent
|
||||
stop('statusHtmlToPlainText')
|
||||
return res
|
||||
}
|
|
@ -9,7 +9,11 @@ import {
|
|||
getNthStatusMediaImg,
|
||||
composeModalPostPrivacyButton,
|
||||
getComposeModalNthMediaImg,
|
||||
getComposeModalNthMediaAltInput, getNthStatusSpoiler, composeModalContentWarningInput, dialogOptionsOption
|
||||
getComposeModalNthMediaAltInput,
|
||||
getNthStatusSpoiler,
|
||||
composeModalContentWarningInput,
|
||||
dialogOptionsOption,
|
||||
getNthReplyButton, getNthComposeReplyInput, getNthComposeReplyButton, getUrl
|
||||
} from '../utils'
|
||||
import { postAs, postEmptyStatusWithMediaAs, postWithSpoilerAndPrivacyAs } from '../serverActions'
|
||||
|
||||
|
@ -91,3 +95,53 @@ test('privacy and spoiler delete and redraft', async t => {
|
|||
.expect(modalDialog.exists).notOk()
|
||||
.expect(getNthStatusSpoiler(0).innerText).contains('no really, you should click this!')
|
||||
})
|
||||
|
||||
test('delete and redraft reply', async t => {
|
||||
await postAs('admin', 'hey hello')
|
||||
await loginAsFoobar(t)
|
||||
await t
|
||||
.hover(getNthStatus(0))
|
||||
.expect(getNthStatusContent(0).innerText).contains('hey hello')
|
||||
.click(getNthReplyButton(0))
|
||||
.typeText(getNthComposeReplyInput(0), 'hello there admin', { paste: true })
|
||||
.click(getNthComposeReplyButton(0))
|
||||
.expect(getNthStatus(0).innerText).contains('@admin hello there admin')
|
||||
.click(getNthStatusOptionsButton(0))
|
||||
.click(dialogOptionsOption.withText('Delete and redraft'))
|
||||
.expect(modalDialog.hasAttribute('aria-hidden')).notOk()
|
||||
.typeText(composeModalInput, ' oops forgot to say thank you')
|
||||
.click(composeModalComposeButton)
|
||||
.expect(modalDialog.exists).notOk()
|
||||
.expect(getNthStatusContent(0).innerText).match(/@admin hello there admin\s+oops forgot to say thank you/, {
|
||||
timeout: 30000
|
||||
})
|
||||
.click(getNthStatus(0))
|
||||
.expect(getUrl()).match(/statuses/)
|
||||
.expect(getNthStatusContent(0).innerText).contains('hey hello')
|
||||
.expect(getNthStatusContent(1).innerText).match(/@admin hello there admin\s+oops forgot to say thank you/)
|
||||
})
|
||||
|
||||
test('delete and redraft reply within thread', async t => {
|
||||
await postAs('admin', 'this is a thread')
|
||||
await loginAsFoobar(t)
|
||||
await t
|
||||
.hover(getNthStatus(0))
|
||||
.expect(getNthStatusContent(0).innerText).contains('this is a thread')
|
||||
.click(getNthStatus(0))
|
||||
.expect(getUrl()).match(/statuses/)
|
||||
.expect(getNthStatusContent(0).innerText).contains('this is a thread')
|
||||
.click(getNthReplyButton(0))
|
||||
.typeText(getNthComposeReplyInput(0), 'heyo', { paste: true })
|
||||
.click(getNthComposeReplyButton(0))
|
||||
.expect(getNthStatus(1).innerText).contains('@admin heyo')
|
||||
.click(getNthStatusOptionsButton(1))
|
||||
.click(dialogOptionsOption.withText('Delete and redraft'))
|
||||
.expect(modalDialog.hasAttribute('aria-hidden')).notOk()
|
||||
.typeText(composeModalInput, ' update!', { paste: true })
|
||||
.click(composeModalComposeButton)
|
||||
.expect(modalDialog.exists).notOk()
|
||||
.expect(getNthStatusContent(1).innerText).match(/@admin heyo\s+update!/, {
|
||||
timeout: 30000
|
||||
})
|
||||
.expect(getNthStatus(2).exists).notOk()
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue