media uploads no longer add URLs to status text (#500)

fixes #8
This commit is contained in:
Nolan Lawson 2018-08-26 18:54:59 -07:00 committed by GitHub
parent d49af06fbd
commit b60d636ee2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 112 additions and 48 deletions

View file

@ -22,7 +22,7 @@ export async function insertHandleForReply (statusId) {
export async function postStatus (realm, text, inReplyToId, mediaIds, export async function postStatus (realm, text, inReplyToId, mediaIds,
sensitive, spoilerText, visibility, sensitive, spoilerText, visibility,
mediaDescriptions = [], inReplyToUuid) { mediaDescriptions, inReplyToUuid) {
let { currentInstance, accessToken, online } = store.get() let { currentInstance, accessToken, online } = store.get()
if (!online) { if (!online) {
@ -30,6 +30,9 @@ export async function postStatus (realm, text, inReplyToId, mediaIds,
return return
} }
text = text || ''
mediaDescriptions = mediaDescriptions || []
store.set({ store.set({
postingStatus: true postingStatus: true
}) })

View file

@ -11,13 +11,11 @@ export async function doMediaUpload (realm, file) {
let composeMedia = store.getComposeData(realm, 'media') || [] let composeMedia = store.getComposeData(realm, 'media') || []
composeMedia.push({ composeMedia.push({
data: response, data: response,
file: { name: file.name } file: { name: file.name },
description: ''
}) })
let composeText = store.getComposeData(realm, 'text') || ''
composeText += ' ' + response.text_url
store.setComposeData(realm, { store.setComposeData(realm, {
media: composeMedia, media: composeMedia
text: composeText
}) })
scheduleIdleTask(() => store.save()) scheduleIdleTask(() => store.save())
} catch (e) { } catch (e) {
@ -30,20 +28,10 @@ export async function doMediaUpload (realm, file) {
export function deleteMedia (realm, i) { export function deleteMedia (realm, i) {
let composeMedia = store.getComposeData(realm, 'media') let composeMedia = store.getComposeData(realm, 'media')
let deletedMedia = composeMedia.splice(i, 1)[0] composeMedia.splice(i, 1)
let composeText = store.getComposeData(realm, 'text') || ''
composeText = composeText.replace(' ' + deletedMedia.data.text_url, '')
let mediaDescriptions = store.getComposeData(realm, 'mediaDescriptions') || []
if (mediaDescriptions[i]) {
mediaDescriptions[i] = null
}
store.setComposeData(realm, { store.setComposeData(realm, {
media: composeMedia, media: composeMedia
text: composeText,
mediaDescriptions: mediaDescriptions
}) })
scheduleIdleTask(() => store.save()) scheduleIdleTask(() => store.save())
} }

View file

@ -16,7 +16,8 @@ export async function postStatus (instanceName, accessToken, text, inReplyToId,
for (let key of Object.keys(body)) { for (let key of Object.keys(body)) {
let value = body[key] let value = body[key]
if (!value || (Array.isArray(value) && !value.length)) { // remove any unnecessary fields, except 'status' which must at least be an empty string
if (key !== 'status' && (!value || (Array.isArray(value) && !value.length))) {
delete body[key] delete body[key]
} }
} }

View file

@ -13,7 +13,7 @@
<ComposeLengthGauge {length} {overLimit} /> <ComposeLengthGauge {length} {overLimit} />
<ComposeToolbar {realm} {postPrivacy} {media} {contentWarningShown} {text} /> <ComposeToolbar {realm} {postPrivacy} {media} {contentWarningShown} {text} />
<ComposeLengthIndicator {length} {overLimit} /> <ComposeLengthIndicator {length} {overLimit} />
<ComposeMedia {realm} {media} {mediaDescriptions} /> <ComposeMedia {realm} {media} />
</div> </div>
<div class="compose-box-button-sentinel {hideAndFadeIn}" ref:sentinel></div> <div class="compose-box-button-sentinel {hideAndFadeIn}" ref:sentinel></div>
<div class="compose-box-button-wrapper {realm === 'home' ? 'compose-button-sticky' : ''} {hideAndFadeIn}" > <div class="compose-box-button-wrapper {realm === 'home' ? 'compose-button-sticky' : ''} {hideAndFadeIn}" >
@ -186,8 +186,7 @@
overLimit: ({ length, $maxStatusChars }) => length > $maxStatusChars, overLimit: ({ length, $maxStatusChars }) => length > $maxStatusChars,
contentWarningShown: ({ composeData }) => composeData.contentWarningShown, contentWarningShown: ({ composeData }) => composeData.contentWarningShown,
contentWarning: ({ composeData }) => composeData.contentWarning || '', contentWarning: ({ composeData }) => composeData.contentWarning || '',
timelineInitialized: ({ $timelineInitialized }) => $timelineInitialized, timelineInitialized: ({ $timelineInitialized }) => $timelineInitialized
mediaDescriptions: ({ composeData }) => composeData.mediaDescriptions || []
}, },
transitions: { transitions: {
slide slide
@ -214,14 +213,14 @@
contentWarning, contentWarning,
realm, realm,
overLimit, overLimit,
mediaDescriptions,
inReplyToUuid inReplyToUuid
} = this.get() } = this.get()
let sensitive = media.length && !!contentWarning let sensitive = media.length && !!contentWarning
let mediaIds = media.map(_ => _.data.id) let mediaIds = media.map(_ => _.data.id)
let mediaDescriptions = media.map(_ => _.description)
let inReplyTo = (realm === 'home' || realm === 'dialog') ? null : realm let inReplyTo = (realm === 'home' || realm === 'dialog') ? null : realm
if (!text || overLimit) { if (overLimit || (!text && !media.length)) {
return // do nothing if invalid return // do nothing if invalid
} }

View file

@ -1,7 +1,7 @@
{#if media.length} {#if media.length}
<div class="compose-media-container" style="grid-template-columns: repeat({media.length}, 1fr);"> <div class="compose-media-container" style="grid-template-columns: repeat({media.length}, 1fr);">
{#each media as mediaItem, index} {#each media as mediaItem, index}
<ComposeMediaItem {realm} {mediaItem} {index} {mediaDescriptions} /> <ComposeMediaItem {realm} {mediaItem} {index} {media} />
{/each} {/each}
</div> </div>
{/if} {/if}

View file

@ -95,10 +95,10 @@
methods: { methods: {
observe, observe,
setupSyncFromStore () { setupSyncFromStore () {
this.observe('mediaDescriptions', mediaDescriptions => { this.observe('media', media => {
mediaDescriptions = mediaDescriptions || [] media = media || []
let { index, rawText } = this.get() let { index, rawText } = this.get()
let text = mediaDescriptions[index] || '' let text = (media[index] && media[index].description) || ''
if (rawText !== text) { if (rawText !== text) {
this.set({rawText: text}) this.set({rawText: text})
} }
@ -108,17 +108,12 @@
const saveStore = debounce(() => scheduleIdleTask(() => this.store.save()), 1000) const saveStore = debounce(() => scheduleIdleTask(() => this.store.save()), 1000)
this.observe('rawText', rawText => { this.observe('rawText', rawText => {
let { realm } = this.get() let { realm, index, media } = this.get()
let { index } = this.get() if (media[index].description === rawText) {
let mediaDescriptions = store.getComposeData(realm, 'mediaDescriptions') || []
if (mediaDescriptions[index] === rawText) {
return return
} }
while (mediaDescriptions.length <= index) { media[index].description = rawText
mediaDescriptions.push(null) store.setComposeData(realm, {media})
}
mediaDescriptions[index] = rawText
store.setComposeData(realm, {mediaDescriptions})
saveStore() saveStore()
}, {init: false}) }, {init: false})
}, },

View file

@ -1,8 +1,9 @@
import { Selector as $ } from 'testcafe' import { Selector as $ } from 'testcafe'
import { import {
composeButton, composeInput, composeLengthIndicator, emojiButton, getComposeSelectionStart, getUrl, composeButton, composeInput, composeLengthIndicator, emojiButton, getComposeSelectionStart,
getNthStatusContent, getUrl,
homeNavButton, homeNavButton,
notificationsNavButton, notificationsNavButton, sleep,
times times
} from '../utils' } from '../utils'
import { loginAsFoobar } from '../roles' import { loginAsFoobar } from '../roles'
@ -97,3 +98,13 @@ test('inserts emoji without typing anything', async t => {
.click($('button img[title=":blobpeek:"]')) .click($('button img[title=":blobpeek:"]'))
.expect(composeInput.value).eql(':blobpeek: :blobpats: ') .expect(composeInput.value).eql(':blobpeek: :blobpats: ')
}) })
test('cannot post an empty status', async t => {
await loginAsFoobar(t)
await t
.expect(getNthStatusContent(0).innerText).contains('pinned toot 1')
.click(composeButton)
await sleep(2)
await t
.expect(getNthStatusContent(0).innerText).contains('pinned toot 1')
})

View file

@ -1,5 +1,6 @@
import { import {
composeInput, getNthDeleteMediaButton, getNthMedia, mediaButton, composeInput, getNthDeleteMediaButton, getNthMedia, getNthMediaAltInput, homeNavButton, mediaButton,
settingsNavButton, sleep,
uploadKittenImage uploadKittenImage
} from '../utils' } from '../utils'
import { loginAsFoobar } from '../roles' import { loginAsFoobar } from '../roles'
@ -51,16 +52,70 @@ test('removes media', async t => {
.expect(getNthMedia(2).exists).notOk() .expect(getNthMedia(2).exists).notOk()
}) })
test('changes URLs as media is added/removed', async t => { test('does not add URLs as media is added/removed', async t => {
await loginAsFoobar(t)
await t
.typeText(composeInput, 'this is a toot')
.expect(mediaButton.exists).ok()
await (uploadKittenImage(1)())
await t.expect(composeInput.value).eql('this is a toot')
await (uploadKittenImage(1)())
await t.expect(composeInput.value).eql('this is a toot')
.click(getNthDeleteMediaButton(1))
.expect(composeInput.value).eql('this is a toot')
.click(getNthDeleteMediaButton(1))
.expect(composeInput.value).eql('this is a toot')
})
test('keeps media descriptions as media is removed', async t => {
await loginAsFoobar(t) await loginAsFoobar(t)
await t await t
.expect(mediaButton.exists).ok() .expect(mediaButton.exists).ok()
await (uploadKittenImage(1)()) await (uploadKittenImage(1)())
await t.expect(composeInput.value).match(/^ http:\/\/localhost:3000\/media\/\S+$/) await t
await (uploadKittenImage(1)()) .typeText(getNthMediaAltInput(1), 'kitten numero uno')
await t.expect(composeInput.value).match(/^ http:\/\/localhost:3000\/media\/\S+ http:\/\/localhost:3000\/media\/\S+$/) await (uploadKittenImage(2)())
await t
.typeText(getNthMediaAltInput(2), 'kitten numero dos')
.expect(getNthMediaAltInput(1).value).eql('kitten numero uno')
.expect(getNthMediaAltInput(2).value).eql('kitten numero dos')
.expect(getNthMedia(1).getAttribute('alt')).eql('kitten1.jpg')
.expect(getNthMedia(2).getAttribute('alt')).eql('kitten2.jpg')
.click(getNthDeleteMediaButton(1)) .click(getNthDeleteMediaButton(1))
.expect(composeInput.value).match(/^ http:\/\/localhost:3000\/media\/\S+$/) .expect(getNthMediaAltInput(1).value).eql('kitten numero dos')
.click(getNthDeleteMediaButton(1)) .expect(getNthMedia(1).getAttribute('alt')).eql('kitten2.jpg')
.expect(composeInput.value).eql('') })
test('keeps media in local storage', async t => {
await loginAsFoobar(t)
await t
.expect(mediaButton.exists).ok()
await (uploadKittenImage(1)())
await t
.typeText(getNthMediaAltInput(1), 'kitten numero uno')
await (uploadKittenImage(2)())
await t
.typeText(getNthMediaAltInput(2), 'kitten numero dos')
await t
.typeText(composeInput, 'hello hello')
.expect(composeInput.value).eql('hello hello')
.expect(getNthMediaAltInput(1).value).eql('kitten numero uno')
.expect(getNthMediaAltInput(2).value).eql('kitten numero dos')
.expect(getNthMedia(1).getAttribute('alt')).eql('kitten1.jpg')
.expect(getNthMedia(2).getAttribute('alt')).eql('kitten2.jpg')
await sleep(1)
await t
.click(settingsNavButton)
.click(homeNavButton)
.expect(composeInput.value).eql('hello hello')
.expect(getNthMediaAltInput(1).value).eql('kitten numero uno')
.expect(getNthMediaAltInput(2).value).eql('kitten numero dos')
.expect(getNthMedia(1).getAttribute('alt')).eql('kitten1.jpg')
.expect(getNthMedia(2).getAttribute('alt')).eql('kitten2.jpg')
.navigateTo('/')
.expect(composeInput.value).eql('hello hello')
.expect(getNthMediaAltInput(1).value).eql('kitten numero uno')
.expect(getNthMediaAltInput(2).value).eql('kitten numero dos')
.expect(getNthMedia(1).getAttribute('alt')).eql('kitten1.jpg')
.expect(getNthMedia(2).getAttribute('alt')).eql('kitten2.jpg')
}) })

View file

@ -1,5 +1,5 @@
import { import {
composeButton, getNthDeleteMediaButton, getNthMedia, getNthMediaAltInput, getNthStatusAndImage, getUrl, composeButton, composeInput, getNthDeleteMediaButton, getNthMedia, getNthMediaAltInput, getNthStatusAndImage, getUrl,
homeNavButton, homeNavButton,
mediaButton, notificationsNavButton, mediaButton, notificationsNavButton,
uploadKittenImage uploadKittenImage
@ -77,3 +77,15 @@ test('saves alts to local storage', async t => {
.expect(getNthStatusAndImage(0, 0).getAttribute('alt')).eql('kitten numero uno') .expect(getNthStatusAndImage(0, 0).getAttribute('alt')).eql('kitten numero uno')
.expect(getNthStatusAndImage(0, 1).getAttribute('alt')).eql('kitten numero dos') .expect(getNthStatusAndImage(0, 1).getAttribute('alt')).eql('kitten numero dos')
}) })
test('can post a status with empty content if there is media', async t => {
await loginAsFoobar(t)
await t
.expect(mediaButton.hasAttribute('disabled')).notOk()
.typeText(composeInput, 'this is a toot')
await (uploadKittenImage(1)())
await t
.typeText(getNthMediaAltInput(1), 'just an image!')
await t.click(composeButton)
.expect(getNthStatusAndImage(0, 0).getAttribute('alt')).eql('just an image!')
})