From b60d636ee2a2b9ce8c2047d2660b4c58af459826 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sun, 26 Aug 2018 18:54:59 -0700 Subject: [PATCH] media uploads no longer add URLs to status text (#500) fixes #8 --- routes/_actions/compose.js | 5 +- routes/_actions/media.js | 22 ++---- routes/_api/statuses.js | 3 +- routes/_components/compose/ComposeBox.html | 9 ++- routes/_components/compose/ComposeMedia.html | 2 +- .../_components/compose/ComposeMediaItem.html | 19 ++--- tests/spec/012-compose.js | 15 +++- tests/spec/013-compose-media.js | 71 ++++++++++++++++--- tests/spec/109-compose-media.js | 14 +++- 9 files changed, 112 insertions(+), 48 deletions(-) diff --git a/routes/_actions/compose.js b/routes/_actions/compose.js index 81d432a..82e5565 100644 --- a/routes/_actions/compose.js +++ b/routes/_actions/compose.js @@ -22,7 +22,7 @@ export async function insertHandleForReply (statusId) { export async function postStatus (realm, text, inReplyToId, mediaIds, sensitive, spoilerText, visibility, - mediaDescriptions = [], inReplyToUuid) { + mediaDescriptions, inReplyToUuid) { let { currentInstance, accessToken, online } = store.get() if (!online) { @@ -30,6 +30,9 @@ export async function postStatus (realm, text, inReplyToId, mediaIds, return } + text = text || '' + mediaDescriptions = mediaDescriptions || [] + store.set({ postingStatus: true }) diff --git a/routes/_actions/media.js b/routes/_actions/media.js index 1a3808f..b16f8b7 100644 --- a/routes/_actions/media.js +++ b/routes/_actions/media.js @@ -11,13 +11,11 @@ export async function doMediaUpload (realm, file) { let composeMedia = store.getComposeData(realm, 'media') || [] composeMedia.push({ data: response, - file: { name: file.name } + file: { name: file.name }, + description: '' }) - let composeText = store.getComposeData(realm, 'text') || '' - composeText += ' ' + response.text_url store.setComposeData(realm, { - media: composeMedia, - text: composeText + media: composeMedia }) scheduleIdleTask(() => store.save()) } catch (e) { @@ -30,20 +28,10 @@ export async function doMediaUpload (realm, file) { export function deleteMedia (realm, i) { let composeMedia = store.getComposeData(realm, 'media') - let deletedMedia = composeMedia.splice(i, 1)[0] - - 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 - } + composeMedia.splice(i, 1) store.setComposeData(realm, { - media: composeMedia, - text: composeText, - mediaDescriptions: mediaDescriptions + media: composeMedia }) scheduleIdleTask(() => store.save()) } diff --git a/routes/_api/statuses.js b/routes/_api/statuses.js index 12f6ea9..73d2605 100644 --- a/routes/_api/statuses.js +++ b/routes/_api/statuses.js @@ -16,7 +16,8 @@ export async function postStatus (instanceName, accessToken, text, inReplyToId, for (let key of Object.keys(body)) { 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] } } diff --git a/routes/_components/compose/ComposeBox.html b/routes/_components/compose/ComposeBox.html index 13bb87c..ff1c182 100644 --- a/routes/_components/compose/ComposeBox.html +++ b/routes/_components/compose/ComposeBox.html @@ -13,7 +13,7 @@ - +
@@ -186,8 +186,7 @@ overLimit: ({ length, $maxStatusChars }) => length > $maxStatusChars, contentWarningShown: ({ composeData }) => composeData.contentWarningShown, contentWarning: ({ composeData }) => composeData.contentWarning || '', - timelineInitialized: ({ $timelineInitialized }) => $timelineInitialized, - mediaDescriptions: ({ composeData }) => composeData.mediaDescriptions || [] + timelineInitialized: ({ $timelineInitialized }) => $timelineInitialized }, transitions: { slide @@ -214,14 +213,14 @@ contentWarning, realm, overLimit, - mediaDescriptions, inReplyToUuid } = 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 - if (!text || overLimit) { + if (overLimit || (!text && !media.length)) { return // do nothing if invalid } diff --git a/routes/_components/compose/ComposeMedia.html b/routes/_components/compose/ComposeMedia.html index c1d57ce..64ad35d 100644 --- a/routes/_components/compose/ComposeMedia.html +++ b/routes/_components/compose/ComposeMedia.html @@ -1,7 +1,7 @@ {#if media.length}
{#each media as mediaItem, index} - + {/each}
{/if} diff --git a/routes/_components/compose/ComposeMediaItem.html b/routes/_components/compose/ComposeMediaItem.html index 56f5ba8..4354f99 100644 --- a/routes/_components/compose/ComposeMediaItem.html +++ b/routes/_components/compose/ComposeMediaItem.html @@ -95,10 +95,10 @@ methods: { observe, setupSyncFromStore () { - this.observe('mediaDescriptions', mediaDescriptions => { - mediaDescriptions = mediaDescriptions || [] + this.observe('media', media => { + media = media || [] let { index, rawText } = this.get() - let text = mediaDescriptions[index] || '' + let text = (media[index] && media[index].description) || '' if (rawText !== text) { this.set({rawText: text}) } @@ -108,17 +108,12 @@ const saveStore = debounce(() => scheduleIdleTask(() => this.store.save()), 1000) this.observe('rawText', rawText => { - let { realm } = this.get() - let { index } = this.get() - let mediaDescriptions = store.getComposeData(realm, 'mediaDescriptions') || [] - if (mediaDescriptions[index] === rawText) { + let { realm, index, media } = this.get() + if (media[index].description === rawText) { return } - while (mediaDescriptions.length <= index) { - mediaDescriptions.push(null) - } - mediaDescriptions[index] = rawText - store.setComposeData(realm, {mediaDescriptions}) + media[index].description = rawText + store.setComposeData(realm, {media}) saveStore() }, {init: false}) }, diff --git a/tests/spec/012-compose.js b/tests/spec/012-compose.js index a06516c..8b5edd0 100644 --- a/tests/spec/012-compose.js +++ b/tests/spec/012-compose.js @@ -1,8 +1,9 @@ import { Selector as $ } from 'testcafe' import { - composeButton, composeInput, composeLengthIndicator, emojiButton, getComposeSelectionStart, getUrl, + composeButton, composeInput, composeLengthIndicator, emojiButton, getComposeSelectionStart, + getNthStatusContent, getUrl, homeNavButton, - notificationsNavButton, + notificationsNavButton, sleep, times } from '../utils' import { loginAsFoobar } from '../roles' @@ -97,3 +98,13 @@ test('inserts emoji without typing anything', async t => { .click($('button img[title=":blobpeek:"]')) .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') +}) diff --git a/tests/spec/013-compose-media.js b/tests/spec/013-compose-media.js index 9f53fde..fc9cd8d 100644 --- a/tests/spec/013-compose-media.js +++ b/tests/spec/013-compose-media.js @@ -1,5 +1,6 @@ import { - composeInput, getNthDeleteMediaButton, getNthMedia, mediaButton, + composeInput, getNthDeleteMediaButton, getNthMedia, getNthMediaAltInput, homeNavButton, mediaButton, + settingsNavButton, sleep, uploadKittenImage } from '../utils' import { loginAsFoobar } from '../roles' @@ -51,16 +52,70 @@ test('removes media', async t => { .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 t .expect(mediaButton.exists).ok() await (uploadKittenImage(1)()) - await t.expect(composeInput.value).match(/^ http:\/\/localhost:3000\/media\/\S+$/) - await (uploadKittenImage(1)()) - await t.expect(composeInput.value).match(/^ http:\/\/localhost:3000\/media\/\S+ http:\/\/localhost:3000\/media\/\S+$/) + await t + .typeText(getNthMediaAltInput(1), 'kitten numero uno') + 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)) - .expect(composeInput.value).match(/^ http:\/\/localhost:3000\/media\/\S+$/) - .click(getNthDeleteMediaButton(1)) - .expect(composeInput.value).eql('') + .expect(getNthMediaAltInput(1).value).eql('kitten numero dos') + .expect(getNthMedia(1).getAttribute('alt')).eql('kitten2.jpg') +}) + +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') }) diff --git a/tests/spec/109-compose-media.js b/tests/spec/109-compose-media.js index 86fe584..fd81681 100644 --- a/tests/spec/109-compose-media.js +++ b/tests/spec/109-compose-media.js @@ -1,5 +1,5 @@ import { - composeButton, getNthDeleteMediaButton, getNthMedia, getNthMediaAltInput, getNthStatusAndImage, getUrl, + composeButton, composeInput, getNthDeleteMediaButton, getNthMedia, getNthMediaAltInput, getNthStatusAndImage, getUrl, homeNavButton, mediaButton, notificationsNavButton, 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, 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!') +})