feat: Report posts (#2184)
This commit is contained in:
parent
5ea09d323f
commit
34aca66fef
8 changed files with 462 additions and 70 deletions
|
@ -1,5 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { mastodon } from 'masto'
|
||||
import { toggleFollowAccount, useRelationship } from '~~/composables/masto/relationship'
|
||||
|
||||
const { account, command, context, ...props } = defineProps<{
|
||||
account: mastodon.v1.Account
|
||||
|
@ -14,26 +15,6 @@ const enable = $computed(() => !isSelf && currentUser.value)
|
|||
const relationship = $computed(() => props.relationship || useRelationship(account).value)
|
||||
|
||||
const { client } = $(useMasto())
|
||||
async function toggleFollow() {
|
||||
if (relationship!.following) {
|
||||
if (await openConfirmDialog({
|
||||
title: t('confirm.unfollow.title'),
|
||||
confirm: t('confirm.unfollow.confirm'),
|
||||
cancel: t('confirm.unfollow.cancel'),
|
||||
}) !== 'confirm')
|
||||
return
|
||||
}
|
||||
relationship!.following = !relationship!.following
|
||||
try {
|
||||
const newRel = await client.v1.accounts[relationship!.following ? 'follow' : 'unfollow'](account.id)
|
||||
Object.assign(relationship!, newRel)
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
// TODO error handling
|
||||
relationship!.following = !relationship!.following
|
||||
}
|
||||
}
|
||||
|
||||
async function unblock() {
|
||||
relationship!.blocking = false
|
||||
|
@ -67,7 +48,7 @@ useCommand({
|
|||
visible: () => command && enable,
|
||||
name: () => `${relationship?.following ? t('account.unfollow') : t('account.follow')} ${getShortHandle(account)}`,
|
||||
icon: 'i-ri:star-line',
|
||||
onActivate: () => toggleFollow(),
|
||||
onActivate: () => toggleFollowAccount(relationship!, account),
|
||||
})
|
||||
|
||||
const buttonStyle = $computed(() => {
|
||||
|
@ -95,7 +76,7 @@ const buttonStyle = $computed(() => {
|
|||
rounded-full flex="~ gap2 center" font-500 min-w-30 h-fit px3 py1
|
||||
:class="buttonStyle"
|
||||
:hover="!relationship?.blocking && !relationship?.muting && relationship?.following ? 'border-red text-red' : 'bg-base border-primary text-primary'"
|
||||
@click="relationship?.blocking ? unblock() : relationship?.muting ? unmute() : toggleFollow()"
|
||||
@click="relationship?.blocking ? unblock() : relationship?.muting ? unmute() : toggleFollowAccount(relationship!, account)"
|
||||
>
|
||||
<template v-if="relationship?.blocking">
|
||||
<span elk-group-hover="hidden">{{ $t('account.blocking') }}</span>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { mastodon } from 'masto'
|
||||
import { toggleBlockAccount, toggleBlockDomain, toggleMuteAccount } from '~~/composables/masto/relationship'
|
||||
|
||||
const { account } = defineProps<{
|
||||
account: mastodon.v1.Account
|
||||
|
@ -18,46 +19,6 @@ const { t } = useI18n()
|
|||
const { client } = $(useMasto())
|
||||
const useStarFavoriteIcon = usePreferences('useStarFavoriteIcon')
|
||||
|
||||
async function toggleMute() {
|
||||
if (!relationship!.muting && await openConfirmDialog({
|
||||
title: t('confirm.mute_account.title', [account.acct]),
|
||||
confirm: t('confirm.mute_account.confirm'),
|
||||
cancel: t('confirm.mute_account.cancel'),
|
||||
}) !== 'confirm')
|
||||
return
|
||||
|
||||
relationship!.muting = !relationship!.muting
|
||||
relationship = relationship!.muting
|
||||
? await client.v1.accounts.mute(account.id, {
|
||||
// TODO support more options
|
||||
})
|
||||
: await client.v1.accounts.unmute(account.id)
|
||||
}
|
||||
|
||||
async function toggleBlockUser() {
|
||||
if (!relationship!.blocking && await openConfirmDialog({
|
||||
title: t('confirm.block_account.title', [account.acct]),
|
||||
confirm: t('confirm.block_account.confirm'),
|
||||
cancel: t('confirm.block_account.cancel'),
|
||||
}) !== 'confirm')
|
||||
return
|
||||
|
||||
relationship!.blocking = !relationship!.blocking
|
||||
relationship = await client.v1.accounts[relationship!.blocking ? 'block' : 'unblock'](account.id)
|
||||
}
|
||||
|
||||
async function toggleBlockDomain() {
|
||||
if (!relationship!.domainBlocking && await openConfirmDialog({
|
||||
title: t('confirm.block_domain.title', [getServerName(account)]),
|
||||
confirm: t('confirm.block_domain.confirm'),
|
||||
cancel: t('confirm.block_domain.cancel'),
|
||||
}) !== 'confirm')
|
||||
return
|
||||
|
||||
relationship!.domainBlocking = !relationship!.domainBlocking
|
||||
await client.v1.domainBlocks[relationship!.domainBlocking ? 'block' : 'unblock'](getServerName(account))
|
||||
}
|
||||
|
||||
async function toggleReblogs() {
|
||||
if (!relationship!.showingReblogs && await openConfirmDialog({
|
||||
title: t('confirm.show_reblogs.title', [account.acct]),
|
||||
|
@ -149,16 +110,16 @@ async function removeUserNote() {
|
|||
<CommonDropdownItem
|
||||
v-if="!relationship?.muting"
|
||||
:text="$t('menu.mute_account', [`@${account.acct}`])"
|
||||
icon="i-ri:volume-up-fill"
|
||||
icon="i-ri:volume-mute-line"
|
||||
:command="command"
|
||||
@click="toggleMute()"
|
||||
@click="toggleMuteAccount (relationship!, account)"
|
||||
/>
|
||||
<CommonDropdownItem
|
||||
v-else
|
||||
:text="$t('menu.unmute_account', [`@${account.acct}`])"
|
||||
icon="i-ri:volume-mute-line"
|
||||
icon="i-ri:volume-up-fill"
|
||||
:command="command"
|
||||
@click="toggleMute()"
|
||||
@click="toggleMuteAccount (relationship!, account)"
|
||||
/>
|
||||
|
||||
<CommonDropdownItem
|
||||
|
@ -166,14 +127,14 @@ async function removeUserNote() {
|
|||
:text="$t('menu.block_account', [`@${account.acct}`])"
|
||||
icon="i-ri:forbid-2-line"
|
||||
:command="command"
|
||||
@click="toggleBlockUser()"
|
||||
@click="toggleBlockAccount (relationship!, account)"
|
||||
/>
|
||||
<CommonDropdownItem
|
||||
v-else
|
||||
:text="$t('menu.unblock_account', [`@${account.acct}`])"
|
||||
icon="i-ri:checkbox-circle-line"
|
||||
:command="command"
|
||||
@click="toggleBlockUser()"
|
||||
@click="toggleBlockAccount (relationship!, account)"
|
||||
/>
|
||||
|
||||
<template v-if="getServerName(account) !== currentServer">
|
||||
|
@ -182,16 +143,23 @@ async function removeUserNote() {
|
|||
:text="$t('menu.block_domain', [getServerName(account)])"
|
||||
icon="i-ri:shut-down-line"
|
||||
:command="command"
|
||||
@click="toggleBlockDomain()"
|
||||
@click="toggleBlockDomain(relationship!, account)"
|
||||
/>
|
||||
<CommonDropdownItem
|
||||
v-else
|
||||
:text="$t('menu.unblock_domain', [getServerName(account)])"
|
||||
icon="i-ri:restart-line"
|
||||
:command="command"
|
||||
@click="toggleBlockDomain()"
|
||||
@click="toggleBlockDomain(relationship!, account)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<CommonDropdownItem
|
||||
:text="$t('menu.report_account', [`@${account.acct}`])"
|
||||
icon="i-ri:flag-2-line"
|
||||
:command="command"
|
||||
@click="openReportDialog(account)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
isMediaPreviewOpen,
|
||||
isPreviewHelpOpen,
|
||||
isPublishDialogOpen,
|
||||
isReportDialogOpen,
|
||||
isSigninDialogOpen,
|
||||
} from '~/composables/dialog'
|
||||
|
||||
|
@ -102,5 +103,8 @@ function handleFavouritedBoostedByClose() {
|
|||
<ModalDialog v-model="isKeyboardShortcutsDialogOpen" max-w-full sm:max-w-140 md:max-w-170 lg:max-w-220 md:min-w-160>
|
||||
<MagickeysKeyboardShortcuts @close="closeKeyboardShortcuts()" />
|
||||
</ModalDialog>
|
||||
<ModalDialog v-model="isReportDialogOpen" keep-alive max-w-175>
|
||||
<ReportModal v-if="reportAccount" :account="reportAccount" :status="reportStatus" @close="closeReportDialog()" />
|
||||
</ModalDialog>
|
||||
</template>
|
||||
</template>
|
||||
|
|
266
components/report/ReportModal.vue
Normal file
266
components/report/ReportModal.vue
Normal file
|
@ -0,0 +1,266 @@
|
|||
<script setup lang="ts">
|
||||
import type { mastodon } from 'masto'
|
||||
import { toggleBlockAccount, toggleFollowAccount, toggleMuteAccount, useRelationship } from '~~/composables/masto/relationship'
|
||||
|
||||
const { account, status } = defineProps<{
|
||||
account: mastodon.v1.Account
|
||||
status?: mastodon.v1.Status
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'close'): void
|
||||
}>()
|
||||
|
||||
const { client } = useMasto()
|
||||
|
||||
const step = ref('selectCategory')
|
||||
const serverRules = ref((await client.value.v2.instance.fetch()).rules || [])
|
||||
const reportReason = ref('')
|
||||
const selectedRuleIds = ref([])
|
||||
const availableStatuses = ref(status ? [status] : [])
|
||||
const selectedStatusIds = ref(status ? [status.id] : [])
|
||||
const additionalComments = ref('')
|
||||
const forwardReport = ref(false)
|
||||
|
||||
const dismissButton = ref<HTMLDivElement>()
|
||||
|
||||
loadStatuses() // Load statuses asynchronously ahead of time
|
||||
|
||||
function categoryChosen() {
|
||||
step.value = reportReason.value === 'dontlike' ? 'furtherActions' : 'selectStatuses'
|
||||
resetModal()
|
||||
}
|
||||
|
||||
async function loadStatuses() {
|
||||
if (status) {
|
||||
// Load the 5 statuses before and after the reported status
|
||||
const prevStatuses = await client.value.v1.accounts.listStatuses(account.id, {
|
||||
maxId: status.id,
|
||||
limit: 5,
|
||||
})
|
||||
const nextStatuses = await client.value.v1.accounts.listStatuses(account.id, {
|
||||
minId: status.id,
|
||||
limit: 5,
|
||||
})
|
||||
availableStatuses.value = availableStatuses.value.concat(prevStatuses)
|
||||
availableStatuses.value = availableStatuses.value.concat(nextStatuses)
|
||||
}
|
||||
else {
|
||||
// Reporting an account directly
|
||||
// Load the 10 most recent statuses
|
||||
const mostRecentStatuses = await client.value.v1.accounts.listStatuses(account.id, {
|
||||
limit: 10,
|
||||
})
|
||||
availableStatuses.value = mostRecentStatuses
|
||||
}
|
||||
availableStatuses.value.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
||||
}
|
||||
|
||||
async function submitReport() {
|
||||
await client.value.v1.reports.create({
|
||||
accountId: account.id,
|
||||
statusIds: selectedStatusIds.value,
|
||||
comment: additionalComments.value,
|
||||
forward: forwardReport.value,
|
||||
category: reportReason.value === 'spam' ? 'spam' : reportReason.value === 'violation' ? 'violation' : 'other',
|
||||
ruleIds: reportReason.value === 'violation' ? selectedRuleIds.value : null,
|
||||
})
|
||||
step.value = 'furtherActions'
|
||||
resetModal()
|
||||
}
|
||||
|
||||
function unfollow() {
|
||||
emit('close')
|
||||
toggleFollowAccount(useRelationship(account).value!, account)
|
||||
}
|
||||
|
||||
function mute() {
|
||||
emit('close')
|
||||
toggleMuteAccount(useRelationship(account).value!, account)
|
||||
}
|
||||
|
||||
function block() {
|
||||
emit('close')
|
||||
toggleBlockAccount(useRelationship(account).value!, account)
|
||||
}
|
||||
|
||||
function resetModal() {
|
||||
// TODO: extract this scroll/reset logic into ModalDialog element
|
||||
dismissButton.value?.scrollIntoView() // scroll to top
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div my-8 px-3 sm:px-8 flex="~ col gap-4" relative>
|
||||
<h2 mxa text-xl>
|
||||
<i18n-t :keypath="reportReason === 'dontlike' ? 'report.limiting' : 'report.reporting'">
|
||||
<b text-primary>@{{ account.acct }}</b>
|
||||
</i18n-t>
|
||||
</h2>
|
||||
<button ref="dismissButton" btn-action-icon absolute top--8 right-0 m1 aria-label="Close" @click="emit('close')">
|
||||
<div i-ri:close-line />
|
||||
</button>
|
||||
|
||||
<template v-if="step === 'selectCategory'">
|
||||
<h1 mxa text-4xl mb4>
|
||||
{{ status ? $t('report.whats_wrong_post') : $t('report.whats_wrong_account') }}
|
||||
</h1>
|
||||
<p text-xl>
|
||||
{{ $t('report.select_one') }}
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<input id="dontlike" v-model="reportReason" type="radio" value="dontlike">
|
||||
<label pl-2 for="dontlike" font-bold>{{ $t('report.dontlike') }}</label>
|
||||
<p pl-6>
|
||||
{{ $t('report.dontlike_desc') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input id="spam" v-model="reportReason" type="radio" value="spam">
|
||||
<label pl-2 for="spam" font-bold>{{ $t('report.spam') }}</label>
|
||||
<p pl-6>
|
||||
{{ $t('report.spam_desc') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="serverRules.length > 0">
|
||||
<input id="violation" v-model="reportReason" type="radio" value="violation">
|
||||
<label pl-2 for="violation" font-bold>{{ $t('report.violation') }}</label>
|
||||
<p v-if="reportReason === 'violation'" pl-6 pt-2 text-primary font-bold>
|
||||
{{ $t('report.select_many') }}
|
||||
</p>
|
||||
<ul pl-6>
|
||||
<li v-for="rule in serverRules" :key="rule.id" pt-2>
|
||||
<input
|
||||
:id="rule.id"
|
||||
v-model="selectedRuleIds"
|
||||
type="checkbox"
|
||||
:value="rule.id"
|
||||
:disabled="reportReason !== 'violation'"
|
||||
>
|
||||
<label pl-2 :for="rule.id">{{ rule.text }}</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input id="other" v-model="reportReason" type="radio" value="other">
|
||||
<label pl-2 for="other" font-bold>{{ $t('report.other') }}</label>
|
||||
<p pl-6>
|
||||
{{ $t('report.other_desc') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="reportReason && reportReason !== 'dontlike'">
|
||||
<h3 mt-8 mb-4 font-bold>
|
||||
{{ $t('report.anything_else') }}
|
||||
</h3>
|
||||
<textarea v-model="additionalComments" w-full h-20 p-3 border :placeholder="$t('report.additional_comments')" />
|
||||
<div v-if="getServerName(account) && getServerName(account) !== currentServer">
|
||||
<h3 mt-8 mb-2 font-bold>
|
||||
{{ $t('report.another_server') }}
|
||||
</h3>
|
||||
<p pb-1>
|
||||
{{ $t('report.forward_question') }}
|
||||
</p>
|
||||
<input id="forward" v-model="forwardReport" type="checkbox" value="rule.id">
|
||||
<label pl-2 for="forward"><b>{{ $t('report.forward', [getServerName(account)]) }}</b></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
btn-solid mxa mt-10
|
||||
:disabled="!reportReason || (reportReason === 'violation' && selectedRuleIds.length < 1)"
|
||||
@click="categoryChosen()"
|
||||
>
|
||||
{{ $t('action.next') }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<template v-else-if="step === 'selectStatuses'">
|
||||
<h1 mxa text-4xl mb4>
|
||||
{{ status ? $t('report.select_posts_other') : $t('report.select_posts') }}
|
||||
</h1>
|
||||
<p text-primary font-bold>
|
||||
{{ $t('report.select_many') }}
|
||||
</p>
|
||||
<table>
|
||||
<tr v-for="availableStatus in availableStatuses" :key="availableStatus.id">
|
||||
<td>
|
||||
<input
|
||||
:id="availableStatus.id"
|
||||
v-model="selectedStatusIds"
|
||||
type="checkbox"
|
||||
:value="availableStatus.id"
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
<label :for="availableStatus.id">
|
||||
<StatusCard :status="availableStatus" :actions="false" pointer-events-none />
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button
|
||||
btn-solid mxa mt-5
|
||||
@click="submitReport()"
|
||||
>
|
||||
{{ $t('report.submit') }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<template v-else-if="step === 'furtherActions'">
|
||||
<h1 mxa text-4xl mb4>
|
||||
{{ reportReason === 'dontlike' ? $t('report.further_actions.limit.title') : $t('report.further_actions.report.title') }}
|
||||
</h1>
|
||||
<p text-xl>
|
||||
{{ reportReason === 'dontlike' ? $t('report.further_actions.limit.description') : $t('report.further_actions.report.description') }}
|
||||
</p>
|
||||
|
||||
<div v-if="useRelationship(account).value?.following">
|
||||
<button btn-outline mxa mt-4 mb-2 @click="unfollow()">
|
||||
<i18n-t keypath="menu.unfollow_account">
|
||||
<b>@{{ account.acct }}</b>
|
||||
</i18n-t>
|
||||
</button><br>
|
||||
{{ $t('report.unfollow_desc') }}
|
||||
</div>
|
||||
<div v-if="!useRelationship(account).value?.muting">
|
||||
<button btn-outline mxa mt-4 mb-2 @click="mute()">
|
||||
<i18n-t keypath="menu.mute_account">
|
||||
<b>@{{ account.acct }}</b>
|
||||
</i18n-t>
|
||||
</button><br>
|
||||
{{ $t('report.mute_desc') }}
|
||||
</div>
|
||||
<div v-if="!useRelationship(account).value?.blocking">
|
||||
<button btn-outline mxa mt-4 mb-2 @click="block()">
|
||||
<i18n-t keypath="menu.block_account">
|
||||
<b>@{{ account.acct }}</b>
|
||||
</i18n-t>
|
||||
</button><br>
|
||||
{{ $t('report.block_desc') }}
|
||||
</div>
|
||||
<button btn-solid mxa mt-10 @click="emit('close')">
|
||||
{{ $t('action.done') }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
tr {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
tr:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
td {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { mastodon } from 'masto'
|
||||
import { toggleBlockAccount, toggleMuteAccount, useRelationship } from '~~/composables/masto/relationship'
|
||||
|
||||
const props = defineProps<{
|
||||
status: mastodon.v1.Status
|
||||
|
@ -260,6 +261,60 @@ function showFavoritedAndBoostedBy() {
|
|||
:command="command"
|
||||
@click="mentionUser(status.account)"
|
||||
/>
|
||||
|
||||
<CommonDropdownItem
|
||||
v-if="!useRelationship(status.account).value?.muting"
|
||||
:text="$t('menu.mute_account', [`@${status.account.acct}`])"
|
||||
icon="i-ri:volume-mute-line"
|
||||
:command="command"
|
||||
@click="toggleMuteAccount(useRelationship(status.account).value!, status.account)"
|
||||
/>
|
||||
<CommonDropdownItem
|
||||
v-else
|
||||
:text="$t('menu.unmute_account', [`@${status.account.acct}`])"
|
||||
icon="i-ri:volume-up-fill"
|
||||
:command="command"
|
||||
@click="toggleMuteAccount(useRelationship(status.account).value!, status.account)"
|
||||
/>
|
||||
|
||||
<CommonDropdownItem
|
||||
v-if="!useRelationship(status.account).value?.blocking"
|
||||
:text="$t('menu.block_account', [`@${status.account.acct}`])"
|
||||
icon="i-ri:forbid-2-line"
|
||||
:command="command"
|
||||
@click="toggleBlockAccount(useRelationship(status.account).value!, status.account)"
|
||||
/>
|
||||
<CommonDropdownItem
|
||||
v-else
|
||||
:text="$t('menu.unblock_account', [`@${status.account.acct}`])"
|
||||
icon="i-ri:checkbox-circle-line"
|
||||
:command="command"
|
||||
@click="toggleBlockAccount(useRelationship(status.account).value!, status.account)"
|
||||
/>
|
||||
|
||||
<template v-if="getServerName(status.account) && getServerName(status.account) !== currentServer">
|
||||
<CommonDropdownItem
|
||||
v-if="!useRelationship(status.account).value?.domainBlocking"
|
||||
:text="$t('menu.block_domain', [getServerName(status.account)])"
|
||||
icon="i-ri:shut-down-line"
|
||||
:command="command"
|
||||
@click="toggleBlockDomain(useRelationship(status.account).value!, status.account)"
|
||||
/>
|
||||
<CommonDropdownItem
|
||||
v-else
|
||||
:text="$t('menu.unblock_domain', [getServerName(status.account)])"
|
||||
icon="i-ri:restart-line"
|
||||
:command="command"
|
||||
@click="toggleBlockDomain(useRelationship(status.account).value!, status.account)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<CommonDropdownItem
|
||||
:text="$t('menu.report_account', [`@${status.account.acct}`])"
|
||||
icon="i-ri:flag-2-line"
|
||||
:command="command"
|
||||
@click="openReportDialog(status.account, status)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue