parent
92edb3d835
commit
60751b3339
|
@ -1,8 +1,8 @@
|
||||||
<div class="compose-media">
|
<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">
|
<div class="compose-media-delete">
|
||||||
<button class="compose-media-delete-button"
|
<button class="compose-media-delete-button"
|
||||||
aria-label="Delete {mediaItem.file.name}"
|
aria-label="Delete {shortName}"
|
||||||
on:click="onDeleteMedia()" >
|
on:click="onDeleteMedia()" >
|
||||||
<svg class="compose-media-delete-button-svg">
|
<svg class="compose-media-delete-button-svg">
|
||||||
<use xlink:href="#fa-times" />
|
<use xlink:href="#fa-times" />
|
||||||
|
@ -10,12 +10,15 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="compose-media-alt">
|
<div class="compose-media-alt">
|
||||||
<input type="text"
|
<input id="compose-media-input-{uuid}"
|
||||||
|
type="text"
|
||||||
class="compose-media-alt-input"
|
class="compose-media-alt-input"
|
||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
aria-label="Describe {mediaItem.file.name} for the visually impaired"
|
|
||||||
bind:value=rawText
|
bind:value=rawText
|
||||||
>
|
>
|
||||||
|
<label for="compose-media-input-{uuid}" class="sr-only">
|
||||||
|
Describe {shortName} for the visually impaired
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<style>
|
<style>
|
||||||
|
@ -91,6 +94,16 @@
|
||||||
data: () => ({
|
data: () => ({
|
||||||
rawText: ''
|
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,
|
store: () => store,
|
||||||
methods: {
|
methods: {
|
||||||
observe,
|
observe,
|
||||||
|
|
|
@ -20,6 +20,8 @@ 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 { htmlToPlainText } from '../../../_utils/htmlToPlainText'
|
||||||
|
import { importShowComposeDialog } from '../asyncDialogs'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate,
|
oncreate,
|
||||||
|
@ -109,6 +111,11 @@ export default {
|
||||||
label: muteConversationLabel,
|
label: muteConversationLabel,
|
||||||
icon: muteConversationIcon
|
icon: muteConversationIcon
|
||||||
},
|
},
|
||||||
|
isUser && {
|
||||||
|
key: 'redraft',
|
||||||
|
label: 'Delete and redraft',
|
||||||
|
icon: '#fa-pencil'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'copy',
|
key: 'copy',
|
||||||
label: 'Copy link to toot',
|
label: 'Copy link to toot',
|
||||||
|
@ -140,6 +147,8 @@ export default {
|
||||||
return this.onCopyClicked()
|
return this.onCopyClicked()
|
||||||
case 'muteConversation':
|
case 'muteConversation':
|
||||||
return this.onMuteConversationClicked()
|
return this.onMuteConversationClicked()
|
||||||
|
case 'redraft':
|
||||||
|
return this.onRedraft()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onDeleteClicked () {
|
async onDeleteClicked () {
|
||||||
|
@ -177,6 +186,25 @@ export default {
|
||||||
let { url } = status
|
let { url } = status
|
||||||
await copyText(url)
|
await copyText(url)
|
||||||
this.close()
|
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')
|
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) {
|
export async function postEmptyStatusWithMediaAs (username, filename, alt) {
|
||||||
let mediaResponse = await submitMedia(users[username].accessToken, filename, alt)
|
let mediaResponse = await submitMedia(users[username].accessToken, filename, alt)
|
||||||
return postStatus(instanceName, users[username].accessToken, '',
|
return postStatus(instanceName, users[username].accessToken, '',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {
|
import {
|
||||||
accountProfileMoreOptionsButton, closeDialogButton,
|
accountProfileMoreOptionsButton, closeDialogButton, composeModalInput,
|
||||||
getNthDialogOptionsOption, modalDialog
|
getNthDialogOptionsOption, modalDialog
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { loginAsFoobar } from '../roles'
|
import { loginAsFoobar } from '../roles'
|
||||||
|
@ -14,7 +14,7 @@ test('can mention from account profile', async t => {
|
||||||
.click(accountProfileMoreOptionsButton)
|
.click(accountProfileMoreOptionsButton)
|
||||||
.expect(getNthDialogOptionsOption(1).innerText).contains('Mention @baz')
|
.expect(getNthDialogOptionsOption(1).innerText).contains('Mention @baz')
|
||||||
.click(getNthDialogOptionsOption(1))
|
.click(getNthDialogOptionsOption(1))
|
||||||
.expect(modalDialog.find('.compose-box-input').value).eql('@baz ')
|
.expect(composeModalInput.value).eql('@baz ')
|
||||||
.click(closeDialogButton)
|
.click(closeDialogButton)
|
||||||
.expect(modalDialog.exists).notOk()
|
.expect(modalDialog.exists).notOk()
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
import {
|
import {
|
||||||
composeButton, getNthStatus, scrollToStatus, modalDialog, sleep,
|
composeButton,
|
||||||
notificationsNavButton, getUrl, getNthStatusSelector
|
getNthStatus,
|
||||||
|
scrollToStatus,
|
||||||
|
modalDialog,
|
||||||
|
sleep,
|
||||||
|
notificationsNavButton,
|
||||||
|
getUrl,
|
||||||
|
getNthStatusSelector,
|
||||||
|
composeModalEmojiButton,
|
||||||
|
composeModalInput,
|
||||||
|
composeModalComposeButton
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { loginAsFoobar } from '../roles'
|
import { loginAsFoobar } from '../roles'
|
||||||
import { Selector as $ } from 'testcafe'
|
import { Selector as $ } from 'testcafe'
|
||||||
|
@ -16,8 +25,8 @@ test('can compose using a dialog', async t => {
|
||||||
await sleep(2000)
|
await sleep(2000)
|
||||||
await t.click(composeButton)
|
await t.click(composeButton)
|
||||||
.expect(modalDialog.hasAttribute('aria-hidden')).notOk()
|
.expect(modalDialog.hasAttribute('aria-hidden')).notOk()
|
||||||
.typeText(modalDialog.find('.compose-box-input'), 'hello from the modal')
|
.typeText(composeModalInput, 'hello from the modal')
|
||||||
.click(modalDialog.find('.compose-box-button-compose'))
|
.click(composeModalComposeButton)
|
||||||
.expect(modalDialog.exists).notOk()
|
.expect(modalDialog.exists).notOk()
|
||||||
.click(notificationsNavButton)
|
.click(notificationsNavButton)
|
||||||
.expect(getUrl()).contains('/notifications')
|
.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 t.expect(composeButton.getAttribute('aria-label')).eql('Compose')
|
||||||
await sleep(2000)
|
await sleep(2000)
|
||||||
await t.click(composeButton)
|
await t.click(composeButton)
|
||||||
.click(modalDialog.find('.compose-box-toolbar button:nth-child(1)'))
|
.click(composeModalEmojiButton)
|
||||||
.click($('button img[title=":blobpats:"]'))
|
.click($('button img[title=":blobpats:"]'))
|
||||||
.expect(modalDialog.find('.compose-box-input').value).eql(':blobpats: ')
|
.expect(composeModalInput.value).eql(':blobpats: ')
|
||||||
.click(modalDialog.find('.compose-box-button-compose'))
|
.click(composeModalComposeButton)
|
||||||
.expect(modalDialog.exists).notOk()
|
.expect(modalDialog.exists).notOk()
|
||||||
.click(notificationsNavButton)
|
.click(notificationsNavButton)
|
||||||
.expect(getUrl()).contains('/notifications')
|
.expect(getUrl()).contains('/notifications')
|
||||||
|
|
93
tests/spec/121-delete-and-redraft.js
Normal file
93
tests/spec/121-delete-and-redraft.js
Normal file
|
@ -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 markMediaSensitiveInput = $('#choice-mark-media-sensitive')
|
||||||
export const neverMarkMediaSensitiveInput = $('#choice-never-mark-media-sensitive')
|
export const neverMarkMediaSensitiveInput = $('#choice-never-mark-media-sensitive')
|
||||||
export const removeEmojiFromDisplayNamesInput = $('#choice-omit-emoji-in-display-names')
|
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({
|
export const favoritesCountElement = $('.status-favs').addCustomDOMProperties({
|
||||||
innerCount: el => parseInt(el.innerText, 10)
|
innerCount: el => parseInt(el.innerText, 10)
|
||||||
|
@ -190,6 +205,10 @@ export function getNthStatusMedia (n) {
|
||||||
return $(`${getNthStatusSelector(n)} .status-media`)
|
return $(`${getNthStatusSelector(n)} .status-media`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNthStatusMediaImg (n) {
|
||||||
|
return $(`${getNthStatusSelector(n)} .status-media img`)
|
||||||
|
}
|
||||||
|
|
||||||
export function getNthStatusHeader (n) {
|
export function getNthStatusHeader (n) {
|
||||||
return $(`${getNthStatusSelector(n)} .status-header`)
|
return $(`${getNthStatusSelector(n)} .status-header`)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue