forked from cybrespace/pinafore
		
	
							parent
							
								
									92edb3d835
								
							
						
					
					
						commit
						60751b3339
					
				
					 7 changed files with 182 additions and 15 deletions
				
			
		| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			@ -126,4 +139,4 @@
 | 
			
		|||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,7 +186,26 @@ 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()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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 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…
	
	Add table
		
		Reference in a new issue