parent
d665134d66
commit
a63e85bf30
|
@ -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' }
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { importShowReportDialog } from '../_components/dialog/asyncDialogs'
|
||||
|
||||
export async function reportStatusOrAccount ({ status, account }) {
|
||||
let showReportDialog = await importShowReportDialog()
|
||||
showReportDialog({ status, account })
|
||||
}
|
|
@ -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 || ''))
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
{id}
|
||||
{label}
|
||||
{title}
|
||||
{className}
|
||||
background="var(--main-bg)"
|
||||
>
|
||||
<form class="confirmation-dialog-form">
|
||||
|
|
|
@ -81,4 +81,10 @@
|
|||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@media (max-width: 320px) {
|
||||
.generic-dialog-list-button {
|
||||
padding: 10px 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,13 +9,13 @@
|
|||
<p>
|
||||
Mute @{account.acct} ?
|
||||
</p>
|
||||
<form class="mute-dialog-form">
|
||||
<div class="mute-dialog-form">
|
||||
<input type="checkbox"
|
||||
id="mute-notifications"
|
||||
name="mute-notifications"
|
||||
bind:checked="muteNotifications">
|
||||
<label for="mute-notifications">Mute notifications as well</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</GenericConfirmationDialog>
|
||||
<style>
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
<GenericConfirmationDialog
|
||||
{id}
|
||||
{label}
|
||||
{title}
|
||||
className="report-dialog-contents"
|
||||
{positiveText}
|
||||
on:positive="doReport()">
|
||||
<div class="report-dialog">
|
||||
<div class="report-flex">
|
||||
<div class="recent-statuses">
|
||||
{#if loading}
|
||||
<div class="loading-spinner-container">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
{:else}
|
||||
<ul>
|
||||
{#each displayStatuses as status (status.id)}
|
||||
<li>
|
||||
<input type="checkbox"
|
||||
id="status-report-{status.id}"
|
||||
name="status-report-{status.id}"
|
||||
checked={status.report}
|
||||
on:change="onChange(status.id, event)"
|
||||
>
|
||||
<label for="status-report-{status.id}">
|
||||
{status.text}
|
||||
</label>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="report-info">
|
||||
<p>You are reporting @{account.acct} to the moderators of {$currentInstance}.</p>
|
||||
<label class="sr-only" id="comments-label">Additional comments</label>
|
||||
<textarea bind:value="comments"
|
||||
placeholder="Additional comments"
|
||||
aria-labelledby="comments-label"
|
||||
maxlength="1000"></textarea>
|
||||
{#if remoteInstance}
|
||||
<p>Forward to the moderators of {remoteInstance} as well?</p>
|
||||
<input type="checkbox"
|
||||
id="report-forward"
|
||||
name="report-forward"
|
||||
bind:checked="forward">
|
||||
<label for="report-forward">
|
||||
Forward to {remoteInstance}
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</GenericConfirmationDialog>
|
||||
<style>
|
||||
:global(.report-dialog-contents .confirmation-dialog-form) {
|
||||
max-width: 80vw;
|
||||
}
|
||||
.report-dialog {
|
||||
padding: 20px 40px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.loading-spinner-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
max-height: 30vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
li {
|
||||
padding: 10px 0;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
max-height: 40vh;
|
||||
font-size: 1.3em;
|
||||
min-height: 100px;
|
||||
}
|
||||
p {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.recent-statuses li {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.recent-statuses label {
|
||||
width: 0;
|
||||
flex: 1;
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
white-space: pre-wrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.report-flex {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.report-flex > * {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.report-info {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.report-dialog {
|
||||
padding: 20px;
|
||||
overflow-x: hidden;
|
||||
max-height: 65vh;
|
||||
}
|
||||
.report-flex {
|
||||
flex-direction: column;
|
||||
}
|
||||
.report-info {
|
||||
margin-left: 0;
|
||||
}
|
||||
textarea {
|
||||
max-height: 20vh;
|
||||
}
|
||||
p, label {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
:global(.report-dialog-contents .confirmation-dialog-form) {
|
||||
max-width: calc(100vw - 20px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import GenericConfirmationDialog from './GenericConfirmationDialog.html'
|
||||
import LoadingSpinner from '../../LoadingSpinner.html'
|
||||
import { show } from '../helpers/showDialog'
|
||||
import { close } from '../helpers/closeDialog'
|
||||
import { oncreate as onCreateDialog } from '../helpers/onCreateDialog'
|
||||
import { getRecentStatusesForAccount } from '../../../_actions/getRecentStatusesForAccount'
|
||||
import { statusHtmlToPlainText } from '../../../_utils/statusHtmlToPlainText'
|
||||
import { toast } from '../../toast/toast'
|
||||
import { store } from '../../../_store/store'
|
||||
import { reportStatuses } from '../../../_actions/reportStatuses'
|
||||
|
||||
export default {
|
||||
async oncreate () {
|
||||
onCreateDialog.apply(this)
|
||||
let { account, status, reportMap } = this.get()
|
||||
if (status) {
|
||||
reportMap[status.id] = true
|
||||
}
|
||||
try {
|
||||
let recentStatuses = await getRecentStatusesForAccount(account.id)
|
||||
console.log('recentStatuses', recentStatuses)
|
||||
this.set({ recentStatuses })
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
toast.say('Unable to load recent statuses: ' + (err.message || ''))
|
||||
} finally {
|
||||
this.set({ loading: false })
|
||||
}
|
||||
},
|
||||
store: () => store,
|
||||
data: () => ({
|
||||
account: void 0,
|
||||
status: void 0,
|
||||
positiveText: 'Report',
|
||||
reportMap: {},
|
||||
recentStatuses: [],
|
||||
loading: true,
|
||||
forward: false,
|
||||
comments: ''
|
||||
}),
|
||||
computed: {
|
||||
displayStatuses: ({ statuses, reportMap }) => (
|
||||
statuses.map(status => ({
|
||||
id: status.id,
|
||||
text: statusHtmlToPlainText(status.content, status.mentions) || '(No content)',
|
||||
report: reportMap[status.id]
|
||||
}))
|
||||
),
|
||||
statuses: ({ status, recentStatuses }) => (
|
||||
[status].concat((recentStatuses || []).filter(({ id }) => (!status || id !== status.id))).filter(Boolean)
|
||||
),
|
||||
remoteInstance: ({ account }) => account.acct.split('@')[1]
|
||||
},
|
||||
methods: {
|
||||
show,
|
||||
close,
|
||||
onChange (statusId, event) {
|
||||
let report = event.target.checked
|
||||
let { reportMap } = this.get()
|
||||
reportMap[statusId] = report
|
||||
},
|
||||
async doReport () {
|
||||
let { displayStatuses, account, comment, forward, reportMap } = this.get()
|
||||
let statusIds = displayStatuses.map(({ id }) => id).filter(id => reportMap[id])
|
||||
if (!statusIds.length) {
|
||||
toast.say(`No toots to report.`)
|
||||
} else {
|
||||
await reportStatuses(account, statusIds, comment, forward)
|
||||
}
|
||||
}
|
||||
},
|
||||
components: {
|
||||
GenericConfirmationDialog,
|
||||
LoadingSpinner
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -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 }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -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()
|
||||
})
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue