From a63e85bf30e3b1cabbc9e0b52454f64d7308cb2b Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Mon, 18 Feb 2019 19:55:44 -0800 Subject: [PATCH] feat: report an account or toots (#1016) fixes #736 --- bin/svgs.js | 3 +- .../_actions/getRecentStatusesForAccount.js | 7 + src/routes/_actions/report.js | 6 + src/routes/_actions/reportStatuses.js | 13 ++ src/routes/_api/report.js | 12 + src/routes/_components/dialog/asyncDialogs.js | 4 + .../AccountProfileOptionsDialog.html | 18 +- .../components/GenericConfirmationDialog.html | 1 + .../dialog/components/GenericDialogList.html | 8 +- .../dialog/components/MuteDialog.html | 4 +- .../dialog/components/ReportDialog.html | 213 ++++++++++++++++++ .../components/StatusOptionsDialog.html | 19 +- .../dialog/creators/showReportDialog.js | 11 + tests/spec/028-report-ui.js | 34 +++ tests/utils.js | 1 + 15 files changed, 345 insertions(+), 9 deletions(-) create mode 100644 src/routes/_actions/getRecentStatusesForAccount.js create mode 100644 src/routes/_actions/report.js create mode 100644 src/routes/_actions/reportStatuses.js create mode 100644 src/routes/_api/report.js create mode 100644 src/routes/_components/dialog/components/ReportDialog.html create mode 100644 src/routes/_components/dialog/creators/showReportDialog.js create mode 100644 tests/spec/028-report-ui.js diff --git a/bin/svgs.js b/bin/svgs.js index f99d5eda..19ee38b7 100644 --- a/bin/svgs.js +++ b/bin/svgs.js @@ -44,5 +44,6 @@ module.exports = [ { id: 'fa-angle-right', src: 'src/thirdparty/font-awesome-svg-png/white/svg/angle-right.svg' }, { id: 'fa-search-minus', src: 'src/thirdparty/font-awesome-svg-png/white/svg/search-minus.svg' }, { id: 'fa-search-plus', src: 'src/thirdparty/font-awesome-svg-png/white/svg/search-plus.svg' }, - { id: 'fa-share-square-o', src: 'src/thirdparty/font-awesome-svg-png/white/svg/share-square-o.svg' } + { id: 'fa-share-square-o', src: 'src/thirdparty/font-awesome-svg-png/white/svg/share-square-o.svg' }, + { id: 'fa-flag', src: 'src/thirdparty/font-awesome-svg-png/white/svg/flag.svg' } ] diff --git a/src/routes/_actions/getRecentStatusesForAccount.js b/src/routes/_actions/getRecentStatusesForAccount.js new file mode 100644 index 00000000..98456049 --- /dev/null +++ b/src/routes/_actions/getRecentStatusesForAccount.js @@ -0,0 +1,7 @@ +import { store } from '../_store/store' +import { getTimeline } from '../_api/timelines' + +export async function getRecentStatusesForAccount (accountId) { + const { currentInstance, accessToken } = store.get() + return getTimeline(currentInstance, accessToken, `account/${accountId}`, null, null, 20) +} diff --git a/src/routes/_actions/report.js b/src/routes/_actions/report.js new file mode 100644 index 00000000..55aef1f9 --- /dev/null +++ b/src/routes/_actions/report.js @@ -0,0 +1,6 @@ +import { importShowReportDialog } from '../_components/dialog/asyncDialogs' + +export async function reportStatusOrAccount ({ status, account }) { + let showReportDialog = await importShowReportDialog() + showReportDialog({ status, account }) +} diff --git a/src/routes/_actions/reportStatuses.js b/src/routes/_actions/reportStatuses.js new file mode 100644 index 00000000..fc67bf7c --- /dev/null +++ b/src/routes/_actions/reportStatuses.js @@ -0,0 +1,13 @@ +import { store } from '../_store/store' +import { toast } from '../_components/toast/toast' +import { report } from '../_api/report' + +export async function reportStatuses (account, statusIds, comment, forward) { + let { currentInstance, accessToken } = store.get() + try { + await report(currentInstance, accessToken, account.id, statusIds, comment, forward) + toast.say('Submitted report') + } catch (e) { + toast.say('Failed to report: ' + (e.message || '')) + } +} diff --git a/src/routes/_api/report.js b/src/routes/_api/report.js new file mode 100644 index 00000000..97afeab0 --- /dev/null +++ b/src/routes/_api/report.js @@ -0,0 +1,12 @@ +import { auth, basename } from './utils' +import { post } from '../_utils/ajax' + +export async function report (instanceName, accessToken, accountId, statusIds, comment, forward) { + let url = `${basename(instanceName)}/api/v1/reports` + return post(url, { + account_id: accountId, + status_ids: statusIds, + comment, + forward + }, auth(accessToken)) +} diff --git a/src/routes/_components/dialog/asyncDialogs.js b/src/routes/_components/dialog/asyncDialogs.js index 06200106..feae8825 100644 --- a/src/routes/_components/dialog/asyncDialogs.js +++ b/src/routes/_components/dialog/asyncDialogs.js @@ -39,3 +39,7 @@ export const importShowMediaDialog = () => import( export const importShowMuteDialog = () => import( /* webpackChunkName: 'showMuteDialog' */ './creators/showMuteDialog' ).then(getDefault) + +export const importShowReportDialog = () => import( + /* webpackChunkName: 'showReportDialog' */ './creators/showReportDialog' + ).then(getDefault) diff --git a/src/routes/_components/dialog/components/AccountProfileOptionsDialog.html b/src/routes/_components/dialog/components/AccountProfileOptionsDialog.html index fa4aa575..77749366 100644 --- a/src/routes/_components/dialog/components/AccountProfileOptionsDialog.html +++ b/src/routes/_components/dialog/components/AccountProfileOptionsDialog.html @@ -21,6 +21,7 @@ import { setDomainBlocked } from '../../../_actions/setDomainBlocked' import { copyText } from '../../../_actions/copyText' import { composeNewStatusMentioning } from '../../../_actions/mention' import { toggleMute } from '../../../_actions/toggleMute' +import { reportStatusOrAccount } from '../../../_actions/report' export default { oncreate, @@ -75,11 +76,12 @@ export default { ? `Unhide ${domain}` : `Hide ${domain}` ), + reportLabel: ({ acct }) => `Report @${acct}`, items: ({ blockLabel, blocking, blockIcon, muteLabel, muteIcon, followLabel, followIcon, following, followRequested, accountId, verifyCredentialsId, acct, isUser, showReblogsLabel, - domain, blockDomainLabel + domain, blockDomainLabel, reportLabel }) => ([ !isUser && { key: 'mention', @@ -111,6 +113,11 @@ export default { label: blockDomainLabel, icon: '#fa-ban' }, + !isUser && { + key: 'report', + label: reportLabel, + icon: '#fa-flag' + }, { key: 'copy', label: 'Copy link to account', @@ -137,12 +144,14 @@ export default { return this.onBlockDomainClicked() case 'copy': return this.onCopyClicked() + case 'report': + return this.onReport() } }, async onMentionClicked () { let { account } = this.get() - await composeNewStatusMentioning(account) this.close() + await composeNewStatusMentioning(account) }, async onFollowClicked () { let { accountId, following } = this.get() @@ -174,6 +183,11 @@ export default { let { url } = account this.close() await copyText(url) + }, + async onReport () { + let { account } = this.get() + this.close() + await reportStatusOrAccount({ account }) } }, components: { diff --git a/src/routes/_components/dialog/components/GenericConfirmationDialog.html b/src/routes/_components/dialog/components/GenericConfirmationDialog.html index a090c71b..c6b8c570 100644 --- a/src/routes/_components/dialog/components/GenericConfirmationDialog.html +++ b/src/routes/_components/dialog/components/GenericConfirmationDialog.html @@ -2,6 +2,7 @@ {id} {label} {title} + {className} background="var(--main-bg)" >
diff --git a/src/routes/_components/dialog/components/GenericDialogList.html b/src/routes/_components/dialog/components/GenericDialogList.html index f1165b1f..8d50167c 100644 --- a/src/routes/_components/dialog/components/GenericDialogList.html +++ b/src/routes/_components/dialog/components/GenericDialogList.html @@ -81,4 +81,10 @@ margin-left: 10px; } } - \ No newline at end of file + + @media (max-width: 320px) { + .generic-dialog-list-button { + padding: 10px 10px; + } + } + diff --git a/src/routes/_components/dialog/components/MuteDialog.html b/src/routes/_components/dialog/components/MuteDialog.html index 5815935c..a53b6597 100644 --- a/src/routes/_components/dialog/components/MuteDialog.html +++ b/src/routes/_components/dialog/components/MuteDialog.html @@ -9,13 +9,13 @@

Mute @{account.acct} ?

- +
- +
+ diff --git a/src/routes/_components/dialog/components/StatusOptionsDialog.html b/src/routes/_components/dialog/components/StatusOptionsDialog.html index e58901a8..cf3b5355 100644 --- a/src/routes/_components/dialog/components/StatusOptionsDialog.html +++ b/src/routes/_components/dialog/components/StatusOptionsDialog.html @@ -22,6 +22,7 @@ import { copyText } from '../../../_actions/copyText' import { deleteAndRedraft } from '../../../_actions/deleteAndRedraft' import { shareStatus } from '../../../_actions/share' import { toggleMute } from '../../../_actions/toggleMute' +import { reportStatusOrAccount } from '../../../_actions/report' export default { oncreate, @@ -120,6 +121,11 @@ export default { label: 'Delete and redraft', icon: '#fa-pencil' }, + !isUser && { + key: 'report', + label: 'Report toot', + icon: '#fa-flag' + }, isPublicOrUnlisted && supportsWebShare && { key: 'share', label: 'Share toot', @@ -160,6 +166,8 @@ export default { return this.onRedraft() case 'share': return this.onShare() + case 'report': + return this.onReport() } }, async onDeleteClicked () { @@ -195,18 +203,23 @@ export default { async onCopyClicked () { let { status } = this.get() let { url } = status - await copyText(url) this.close() + await copyText(url) }, async onRedraft () { let { status } = this.get() - await deleteAndRedraft(status) this.close() + await deleteAndRedraft(status) }, async onShare () { let { status } = this.get() - await shareStatus(status) this.close() + await shareStatus(status) + }, + async onReport () { + let { status, account } = this.get() + this.close() + await reportStatusOrAccount(({ status, account })) } } } diff --git a/src/routes/_components/dialog/creators/showReportDialog.js b/src/routes/_components/dialog/creators/showReportDialog.js new file mode 100644 index 00000000..facf4aa3 --- /dev/null +++ b/src/routes/_components/dialog/creators/showReportDialog.js @@ -0,0 +1,11 @@ +import ReportDialog from '../components/ReportDialog.html' +import { showDialog } from './showDialog' + +export default function showReportDialog ({ account, status }) { + return showDialog(ReportDialog, { + label: 'Report dialog', + title: `Report @${account.acct}`, + account, + status + }) +} diff --git a/tests/spec/028-report-ui.js b/tests/spec/028-report-ui.js new file mode 100644 index 00000000..515ade60 --- /dev/null +++ b/tests/spec/028-report-ui.js @@ -0,0 +1,34 @@ +import { + accountProfileMoreOptionsButton, + confirmationDialogCancelButton, + getNthStatusOptionsButton, + modalDialog +} from '../utils' +import { loginAsFoobar } from '../roles' +import { Selector as $ } from 'testcafe' + +fixture`028-report-ui.js` + .page`http://localhost:4002` + +test('Can open a report UI from a status', async t => { + await loginAsFoobar(t) + await t + .click(getNthStatusOptionsButton(0)) + .click($('.modal-dialog button').withText('Report')) + .expect(modalDialog.innerText).contains('You are reporting @quux') + .expect(modalDialog.find('.recent-statuses').innerText).contains('pinned toot 2') + .click(confirmationDialogCancelButton) + .expect(modalDialog.exists).notOk() +}) + +test('Can open a report UI from an account', async t => { + await loginAsFoobar(t) + await t + .navigateTo('/accounts/3') + .click(accountProfileMoreOptionsButton) + .click($('.modal-dialog button').withText('Report')) + .expect(modalDialog.innerText).contains('You are reporting @quux') + .expect(modalDialog.find('.recent-statuses').innerText).contains('pinned toot 2') + .click(confirmationDialogCancelButton) + .expect(modalDialog.exists).notOk() +}) diff --git a/tests/utils.js b/tests/utils.js index 57c15a97..50327201 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -48,6 +48,7 @@ export const removeEmojiFromDisplayNamesInput = $('#choice-omit-emoji-in-display export const dialogOptionsOption = $(`.modal-dialog button`) export const emojiSearchInput = $('.emoji-mart-search input') export const confirmationDialogOKButton = $('.confirmation-dialog-form-flex button:nth-child(1)') +export const confirmationDialogCancelButton = $('.confirmation-dialog-form-flex button:nth-child(2)') export const composeModalInput = $('.modal-dialog .compose-box-input') export const composeModalComposeButton = $('.modal-dialog .compose-box-button')