forked from cybrespace/pinafore
parent
92edb3d835
commit
60751b3339
|
@ -1,8 +1,8 @@
|
|||
<div class="compose-media">
|
||||
<img src={mediaItem.data.preview_url} alt={mediaItem.file.name}/>
|
||||
<img src={mediaItem.data.preview_url} {alt} />
|
||||
<div class="compose-media-delete">
|
||||
<button class="compose-media-delete-button"
|
||||
aria-label="Delete {mediaItem.file.name}"
|
||||
aria-label="Delete {shortName}"
|
||||
on:click="onDeleteMedia()" >
|
||||
<svg class="compose-media-delete-button-svg">
|
||||
<use xlink:href="#fa-times" />
|
||||
|
@ -10,12 +10,15 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="compose-media-alt">
|
||||
<input type="text"
|
||||
<input id="compose-media-input-{uuid}"
|
||||
type="text"
|
||||
class="compose-media-alt-input"
|
||||
placeholder="Description"
|
||||
aria-label="Describe {mediaItem.file.name} for the visually impaired"
|
||||
bind:value=rawText
|
||||
>
|
||||
<label for="compose-media-input-{uuid}" class="sr-only">
|
||||
Describe {shortName} for the visually impaired
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
|
@ -91,6 +94,16 @@
|
|||
data: () => ({
|
||||
rawText: ''
|
||||
}),
|
||||
computed: {
|
||||
filename: ({ mediaItem }) => mediaItem.file && mediaItem.file.name,
|
||||
alt: ({ filename, mediaItem }) => (
|
||||
// sometimes we no longer have the file, e.g. in a delete and redraft situation,
|
||||
// so fall back to the description if it was provided
|
||||
filename || mediaItem.description || ''
|
||||
),
|
||||
shortName: ({ filename }) => filename || 'media',
|
||||
uuid: ({ realm, mediaItem }) => `${realm}-${mediaItem.data.id}`
|
||||
},
|
||||
store: () => store,
|
||||
methods: {
|
||||
observe,
|
||||
|
|
|
@ -20,6 +20,8 @@ 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 { importShowComposeDialog } from '../asyncDialogs'
|
||||
|
||||
export default {
|
||||
oncreate,
|
||||
|
@ -109,6 +111,11 @@ export default {
|
|||
label: muteConversationLabel,
|
||||
icon: muteConversationIcon
|
||||
},
|
||||
isUser && {
|
||||
key: 'redraft',
|
||||
label: 'Delete and redraft',
|
||||
icon: '#fa-pencil'
|
||||
},
|
||||
{
|
||||
key: 'copy',
|
||||
label: 'Copy link to toot',
|
||||
|
@ -140,6 +147,8 @@ export default {
|
|||
return this.onCopyClicked()
|
||||
case 'muteConversation':
|
||||
return this.onMuteConversationClicked()
|
||||
case 'redraft':
|
||||
return this.onRedraft()
|
||||
}
|
||||
},
|
||||
async onDeleteClicked () {
|
||||
|
@ -177,6 +186,25 @@ export default {
|
|||
let { url } = status
|
||||
await copyText(url)
|
||||
this.close()
|
||||
},
|
||||
async onRedraft () {
|
||||
let { status } = this.get()
|
||||
let deleteStatusPromise = doDeleteStatus(status.id)
|
||||
let dialogPromise = importShowComposeDialog()
|
||||
await deleteStatusPromise
|
||||
this.store.setComposeData('dialog', {
|
||||
text: (status.content && htmlToPlainText(status.content)) || '',
|
||||
contentWarningShown: !!status.spoiler_text,
|
||||
contentWarning: status.spoiler_text || '',
|
||||
postPrivacy: status.visibility,
|
||||
media: status.media_attachments && status.media_attachments.map(_ => ({
|
||||
description: _.description || '',
|
||||
data: _
|
||||
}))
|
||||
})
|
||||
this.close()
|
||||
let showComposeDialog = await dialogPromise
|
||||
showComposeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,11 @@ export async function postAs (username, text) {
|
|||
null, null, false, null, 'public')
|
||||
}
|
||||
|
||||
export async function postWithSpoilerAndPrivacyAs (username, text, spoiler, privacy) {
|
||||
return postStatus(instanceName, users[username].accessToken, text,
|
||||
null, null, true, spoiler, privacy)
|
||||
}
|
||||
|
||||
export async function postEmptyStatusWithMediaAs (username, filename, alt) {
|
||||
let mediaResponse = await submitMedia(users[username].accessToken, filename, alt)
|
||||
return postStatus(instanceName, users[username].accessToken, '',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {
|
||||
accountProfileMoreOptionsButton, closeDialogButton,
|
||||
accountProfileMoreOptionsButton, closeDialogButton, composeModalInput,
|
||||
getNthDialogOptionsOption, modalDialog
|
||||
} from '../utils'
|
||||
import { loginAsFoobar } from '../roles'
|
||||
|
@ -14,7 +14,7 @@ test('can mention from account profile', async t => {
|
|||
.click(accountProfileMoreOptionsButton)
|
||||
.expect(getNthDialogOptionsOption(1).innerText).contains('Mention @baz')
|
||||
.click(getNthDialogOptionsOption(1))
|
||||
.expect(modalDialog.find('.compose-box-input').value).eql('@baz ')
|
||||
.expect(composeModalInput.value).eql('@baz ')
|
||||
.click(closeDialogButton)
|
||||
.expect(modalDialog.exists).notOk()
|
||||
})
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
import {
|
||||
composeButton, getNthStatus, scrollToStatus, modalDialog, sleep,
|
||||
notificationsNavButton, getUrl, getNthStatusSelector
|
||||
composeButton,
|
||||
getNthStatus,
|
||||
scrollToStatus,
|
||||
modalDialog,
|
||||
sleep,
|
||||
notificationsNavButton,
|
||||
getUrl,
|
||||
getNthStatusSelector,
|
||||
composeModalEmojiButton,
|
||||
composeModalInput,
|
||||
composeModalComposeButton
|
||||
} from '../utils'
|
||||
import { loginAsFoobar } from '../roles'
|
||||
import { Selector as $ } from 'testcafe'
|
||||
|
@ -16,8 +25,8 @@ test('can compose using a dialog', async t => {
|
|||
await sleep(2000)
|
||||
await t.click(composeButton)
|
||||
.expect(modalDialog.hasAttribute('aria-hidden')).notOk()
|
||||
.typeText(modalDialog.find('.compose-box-input'), 'hello from the modal')
|
||||
.click(modalDialog.find('.compose-box-button-compose'))
|
||||
.typeText(composeModalInput, 'hello from the modal')
|
||||
.click(composeModalComposeButton)
|
||||
.expect(modalDialog.exists).notOk()
|
||||
.click(notificationsNavButton)
|
||||
.expect(getUrl()).contains('/notifications')
|
||||
|
@ -32,10 +41,10 @@ test('can use emoji dialog within compose dialog', async t => {
|
|||
await t.expect(composeButton.getAttribute('aria-label')).eql('Compose')
|
||||
await sleep(2000)
|
||||
await t.click(composeButton)
|
||||
.click(modalDialog.find('.compose-box-toolbar button:nth-child(1)'))
|
||||
.click(composeModalEmojiButton)
|
||||
.click($('button img[title=":blobpats:"]'))
|
||||
.expect(modalDialog.find('.compose-box-input').value).eql(':blobpats: ')
|
||||
.click(modalDialog.find('.compose-box-button-compose'))
|
||||
.expect(composeModalInput.value).eql(':blobpats: ')
|
||||
.click(composeModalComposeButton)
|
||||
.expect(modalDialog.exists).notOk()
|
||||
.click(notificationsNavButton)
|
||||
.expect(getUrl()).contains('/notifications')
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
import { loginAsFoobar } from '../roles'
|
||||
import {
|
||||
composeModalComposeButton,
|
||||
getNthStatus,
|
||||
getNthStatusContent,
|
||||
getNthStatusOptionsButton,
|
||||
modalDialog,
|
||||
composeModalInput,
|
||||
getNthStatusMediaImg,
|
||||
composeModalPostPrivacyButton,
|
||||
getComposeModalNthMediaImg,
|
||||
getComposeModalNthMediaAltInput, getNthStatusSpoiler, composeModalContentWarningInput, dialogOptionsOption
|
||||
} from '../utils'
|
||||
import { postAs, postEmptyStatusWithMediaAs, postWithSpoilerAndPrivacyAs } from '../serverActions'
|
||||
|
||||
fixture`121-delete-and-redraft.js`
|
||||
.page`http://localhost:4002`
|
||||
|
||||
test('basic delete and redraft', async t => {
|
||||
await postAs('foobar', 'hey ho this is grate')
|
||||
await loginAsFoobar(t)
|
||||
await t
|
||||
.hover(getNthStatus(0))
|
||||
.expect(getNthStatusContent(0).innerText).contains('hey ho this is grate')
|
||||
.click(getNthStatusOptionsButton(0))
|
||||
.click(dialogOptionsOption.withText('Delete and redraft'))
|
||||
.expect(modalDialog.hasAttribute('aria-hidden')).notOk()
|
||||
.expect(composeModalInput.value).contains('hey ho this is grate')
|
||||
.expect(composeModalPostPrivacyButton.getAttribute('aria-label')).eql('Adjust privacy (currently Public)')
|
||||
.typeText(composeModalInput, 'hey ho this is great', { replace: true, paste: true })
|
||||
.click(composeModalComposeButton)
|
||||
.expect(modalDialog.exists).notOk()
|
||||
.expect(getNthStatusContent(0).innerText).contains('hey ho this is great')
|
||||
})
|
||||
|
||||
test('image with empty text delete and redraft', async t => {
|
||||
await postEmptyStatusWithMediaAs('foobar', 'kitten2.jpg', 'what a kitteh')
|
||||
await loginAsFoobar(t)
|
||||
await t
|
||||
.hover(getNthStatus(0))
|
||||
.expect(getNthStatusMediaImg(0).getAttribute('alt')).eql('what a kitteh')
|
||||
.click(getNthStatusOptionsButton(0))
|
||||
.click(dialogOptionsOption.withText('Delete and redraft'))
|
||||
.expect(modalDialog.hasAttribute('aria-hidden')).notOk()
|
||||
.expect(composeModalInput.value).eql('')
|
||||
.expect(composeModalPostPrivacyButton.getAttribute('aria-label')).eql('Adjust privacy (currently Public)')
|
||||
.expect(getComposeModalNthMediaImg(1).getAttribute('alt')).eql('what a kitteh')
|
||||
.expect(getComposeModalNthMediaAltInput(1).value).eql('what a kitteh')
|
||||
.typeText(composeModalInput, 'I love this kitteh', { replace: true, paste: true })
|
||||
.click(composeModalComposeButton)
|
||||
.expect(modalDialog.exists).notOk()
|
||||
.expect(getNthStatusContent(0).innerText).contains('I love this kitteh')
|
||||
.expect(getNthStatusMediaImg(0).getAttribute('alt')).eql('what a kitteh')
|
||||
})
|
||||
|
||||
test('image with no alt delete and redraft', async t => {
|
||||
await postEmptyStatusWithMediaAs('foobar', 'kitten3.jpg', '')
|
||||
await loginAsFoobar(t)
|
||||
await t
|
||||
.hover(getNthStatus(0))
|
||||
.expect(getNthStatusMediaImg(0).getAttribute('alt')).eql('')
|
||||
.click(getNthStatusOptionsButton(0))
|
||||
.click(dialogOptionsOption.withText('Delete and redraft'))
|
||||
.expect(modalDialog.hasAttribute('aria-hidden')).notOk()
|
||||
.expect(composeModalInput.value).eql('')
|
||||
.expect(composeModalPostPrivacyButton.getAttribute('aria-label')).eql('Adjust privacy (currently Public)')
|
||||
.expect(getComposeModalNthMediaImg(1).getAttribute('alt')).eql('')
|
||||
.expect(getComposeModalNthMediaAltInput(1).value).eql('')
|
||||
.typeText(composeModalInput, 'oops forgot an alt', { replace: true, paste: true })
|
||||
.typeText(getComposeModalNthMediaAltInput(1), 'lovely kitteh', { replace: true, paste: true })
|
||||
.click(composeModalComposeButton)
|
||||
.expect(modalDialog.exists).notOk()
|
||||
.expect(getNthStatusContent(0).innerText).contains('oops forgot an alt')
|
||||
.expect(getNthStatusMediaImg(0).getAttribute('alt')).eql('lovely kitteh')
|
||||
})
|
||||
|
||||
test('privacy and spoiler delete and redraft', async t => {
|
||||
await postWithSpoilerAndPrivacyAs('foobar', 'this is hidden', 'click to see!', 'private')
|
||||
await loginAsFoobar(t)
|
||||
await t
|
||||
.hover(getNthStatus(0))
|
||||
.expect(getNthStatusSpoiler(0).innerText).contains('click to see!')
|
||||
.click(getNthStatusOptionsButton(0))
|
||||
.click(dialogOptionsOption.withText('Delete and redraft'))
|
||||
.expect(modalDialog.hasAttribute('aria-hidden')).notOk()
|
||||
.expect(composeModalInput.value).eql('this is hidden')
|
||||
.expect(composeModalPostPrivacyButton.getAttribute('aria-label')).eql('Adjust privacy (currently Followers-only)')
|
||||
.expect(composeModalContentWarningInput.value).eql('click to see!')
|
||||
.typeText(composeModalContentWarningInput, 'no really, you should click this!', { replace: true, paste: true })
|
||||
.click(composeModalComposeButton)
|
||||
.expect(modalDialog.exists).notOk()
|
||||
.expect(getNthStatusSpoiler(0).innerText).contains('no really, you should click this!')
|
||||
})
|
|
@ -45,6 +45,21 @@ export const generalSettingsButton = $('a[href="/settings/general"]')
|
|||
export const markMediaSensitiveInput = $('#choice-mark-media-sensitive')
|
||||
export const neverMarkMediaSensitiveInput = $('#choice-never-mark-media-sensitive')
|
||||
export const removeEmojiFromDisplayNamesInput = $('#choice-omit-emoji-in-display-names')
|
||||
export const dialogOptionsOption = $(`.modal-dialog button`)
|
||||
|
||||
export const composeModalInput = $('.modal-dialog .compose-box-input')
|
||||
export const composeModalComposeButton = $('.modal-dialog .compose-box-button')
|
||||
export const composeModalContentWarningInput = $('.modal-dialog .content-warning-input')
|
||||
export const composeModalEmojiButton = $('.modal-dialog .compose-box-toolbar button:nth-child(1)')
|
||||
export const composeModalPostPrivacyButton = $('.modal-dialog .compose-box-toolbar button:nth-child(3)')
|
||||
|
||||
export function getComposeModalNthMediaAltInput (n) {
|
||||
return $(`.modal-dialog .compose-media:nth-child(${n}) .compose-media-alt input`)
|
||||
}
|
||||
|
||||
export function getComposeModalNthMediaImg (n) {
|
||||
return $(`.modal-dialog .compose-media:nth-child(${n}) img`)
|
||||
}
|
||||
|
||||
export const favoritesCountElement = $('.status-favs').addCustomDOMProperties({
|
||||
innerCount: el => parseInt(el.innerText, 10)
|
||||
|
@ -190,6 +205,10 @@ export function getNthStatusMedia (n) {
|
|||
return $(`${getNthStatusSelector(n)} .status-media`)
|
||||
}
|
||||
|
||||
export function getNthStatusMediaImg (n) {
|
||||
return $(`${getNthStatusSelector(n)} .status-media img`)
|
||||
}
|
||||
|
||||
export function getNthStatusHeader (n) {
|
||||
return $(`${getNthStatusSelector(n)} .status-header`)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue