Compare commits

...

23 commits

Author SHA1 Message Date
Ilja
e594252668 clean up leftover
There was a comment to enable something for eslint. This was a leftover from when it was dissabled.
But the dissabling was removed in a privious commit f9393b0dab
2022-03-21 09:59:52 +01:00
Ilja
88ad5033a8 Use empty array for emji instead
Instead of relying on a key that isn't actually usefull, I just provide an empty array directly.
2022-03-21 09:49:06 +01:00
Ilja
89c409c6d2 Add default array for RichContent emoji
Reports don't currently return an emoji key. There's an MR to add it, but in case something doesn't return this key, we now have a default empty array.
2022-03-21 08:57:59 +01:00
Ilja
d0bfd9a808 Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma-fe into feat/report-notification 2022-03-20 09:53:57 +01:00
Ilja
f9393b0dab Use RichContent component for Reports
Note that this expects an emoji list for the reports. I made an MR in BE to provide that: https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3650
2022-03-20 09:45:35 +01:00
Ilja
1d42d05c1f Use Select component
After merging develop, the dropdown didn't format properly any more because selects have been made into a component. Here I turn the select into a component as well.
2022-03-20 09:31:36 +01:00
Ilja
c9e4b6e7a1 Make linter happy
`npm run lint` gave warnings for two files, fixed them with `./node_modules/.bin/eslint --fix $FILENAME`
2022-02-26 02:13:43 +01:00
Ilja
d0c4ad22cd Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma-fe into feat/report-notification 2022-02-26 02:08:13 +01:00
Ilja
819b760261 Fix up and code review
* Check if it works properly
    * Notifs are shown as BE returns them
    * The Interaction view has Reports, but only when you're mod or admin
* Do some extra translations
* Fix some console spam
2022-02-26 01:53:01 +01:00
Shpuld Shpuldson
52c22e863e Fix setting report state, add proper error handling 2021-02-03 12:02:37 +02:00
Shpuld Shpuldson
3e6309ef94 remove logs 2021-02-01 14:43:01 +02:00
Shpuld Shpuldson
3822a73a49 Fix report modal not working, add include_types 2021-02-01 12:55:23 +02:00
Shpuld Shpuldson
8334649c11 Merge branch 'develop' into feat/report-notification 2021-01-27 13:24:19 +02:00
Shpuld Shpuldson
54def7d210 remove mock data 2021-01-27 13:13:59 +02:00
Shpuld Shpuldson
06f795d1d6 add proper state switcher 2021-01-27 13:13:10 +02:00
Shpuld Shpuldson
e73553dca7 wip 2021-01-18 15:26:08 +02:00
Shpuld Shpuldson
9613f80f8e Merge branch 'develop' into feat/report-notification 2021-01-13 12:53:53 +02:00
Shpuld Shpuldson
8674f20023 separated component 2021-01-11 19:32:58 +02:00
Shpuld Shpuldson
a4e3cccf1c somewhat workign version still with fixture 2021-01-06 18:31:34 +02:00
Shpuld Shpuldson
ab2c2c66bf Merge branch 'develop' into feat/report-notification 2021-01-05 13:58:52 +02:00
Shpuld Shpuldson
5e96260a4f add test data for dev 2020-12-04 12:48:15 +02:00
Shpuld Shpuldson
1fd1553a1c Merge branch 'develop' into feat/report-notification 2020-12-04 11:20:01 +02:00
Shpuld Shpuldson
15bed586dc report notification wip 2020-11-15 13:57:02 +02:00
18 changed files with 320 additions and 21 deletions

View file

@ -4,6 +4,8 @@ const tabModeDict = {
mentions: ['mention'], mentions: ['mention'],
'likes+repeats': ['repeat', 'like'], 'likes+repeats': ['repeat', 'like'],
follows: ['follow'], follows: ['follow'],
reactions: ['pleroma:emoji_reaction'],
reports: ['pleroma:report'],
moves: ['move'] moves: ['move']
} }
@ -11,7 +13,8 @@ const Interactions = {
data () { data () {
return { return {
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move, allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
filterMode: tabModeDict['mentions'] filterMode: tabModeDict['mentions'],
canSeeReports: ['moderator', 'admin'].includes(this.$store.state.users.currentUser.role)
} }
}, },
methods: { methods: {

View file

@ -21,6 +21,15 @@
key="follows" key="follows"
:label="$t('interactions.follows')" :label="$t('interactions.follows')"
/> />
<span
key="reactions"
:label="$t('interactions.emoji_reactions')"
/>
<span
v-if="canSeeReports"
key="reports"
:label="$t('interactions.reports')"
/>
<span <span
v-if="!allowFollowingMove" v-if="!allowFollowingMove"
key="moves" key="moves"

View file

@ -4,6 +4,7 @@ import Status from '../status/status.vue'
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
import UserCard from '../user_card/user_card.vue' import UserCard from '../user_card/user_card.vue'
import Timeago from '../timeago/timeago.vue' import Timeago from '../timeago/timeago.vue'
import Report from '../report/report.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx' import RichContent from 'src/components/rich_content/rich_content.jsx'
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js' import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
@ -46,6 +47,7 @@ const Notification = {
UserCard, UserCard,
Timeago, Timeago,
Status, Status,
Report,
RichContent RichContent
}, },
methods: { methods: {

View file

@ -109,6 +109,9 @@
</i18n> </i18n>
</small> </small>
</span> </span>
<span v-if="notification.type === 'pleroma:report'">
<small>{{ $t('notifications.submitted_report') }}</small>
</span>
</div> </div>
<div <div
v-if="isStatusNotification" v-if="isStatusNotification"
@ -183,6 +186,10 @@
@{{ notification.target.screen_name_ui }} @{{ notification.target.screen_name_ui }}
</router-link> </router-link>
</div> </div>
<Report
v-else-if="notification.type === 'pleroma:report'"
:report-id="notification.report.id"
/>
<template v-else> <template v-else>
<StatusContent <StatusContent
class="faint" class="faint"

View file

@ -59,9 +59,11 @@
height: 32px; height: 32px;
} }
.faint {
--link: var(--faintLink); --link: var(--faintLink);
--text: var(--faint); --text: var(--faint);
} }
}
.follow-request-accept { .follow-request-accept {
cursor: pointer; cursor: pointer;

View file

@ -0,0 +1,34 @@
import Select from '../select/select.vue'
import StatusContent from '../status_content/status_content.vue'
import Timeago from '../timeago/timeago.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const Report = {
props: [
'reportId'
],
components: {
Select,
StatusContent,
Timeago
},
computed: {
report () {
return this.$store.state.reports.reports[this.reportId] || {}
},
state: {
get: function () { return this.report.state },
set: function (val) { this.setReportState(val) }
}
},
methods: {
generateUserProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
},
setReportState (state) {
return this.$store.dispatch('setReportState', { id: this.report.id, state })
}
}
}
export default Report

View file

@ -0,0 +1,43 @@
@import '../../_variables.scss';
.Report {
.report-content {
margin: 0.5em 0 1em;
}
.report-state {
margin: 0.5em 0 1em;
}
.reported-status {
border: 1px solid $fallback--faint;
border-color: var(--faint, $fallback--faint);
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);
color: $fallback--text;
color: var(--text, $fallback--text);
display: block;
padding: 0.5em;
margin: 0.5em 0;
.status-content {
pointer-events: none;
}
.reported-status-heading {
display: flex;
width: 100%;
justify-content: space-between;
margin-bottom: 0.2em;
}
.reported-status-name {
font-weight: bold;
}
}
.note {
width: 100%;
margin-bottom: 0.5em;
}
}

View file

@ -0,0 +1,74 @@
<template>
<div class="Report">
<div class="reported-user">
<span>{{ $t('report.reported_user') }}</span>
<router-link :to="generateUserProfileLink(report.acct)">
@{{ report.acct.screen_name }}
</router-link>
</div>
<div class="reporter">
<span>{{ $t('report.reporter') }}</span>
<router-link :to="generateUserProfileLink(report.actor)">
@{{ report.actor.screen_name }}
</router-link>
</div>
<div class="report-state">
<span>{{ $t('report.state') }}</span>
<Select
:id="report-state"
v-model="state"
class="form-control"
>
<option
v-for="state in ['open', 'closed', 'resolved']"
:key="state"
:value="state"
>
{{ $t('report.state_' + state) }}
</option>
</Select>
</div>
<RichContent
class="report-content"
:html="report.content"
:emoji="[]"
/>
<div v-if="report.statuses.length">
<small>{{ $t('report.reported_statuses') }}</small>
<router-link
v-for="status in report.statuses"
:key="status.id"
:to="{ name: 'conversation', params: { id: status.id } }"
class="reported-status"
>
<div class="reported-status-heading">
<span class="reported-status-name">{{ status.user.name }}</span>
<Timeago
:time="status.created_at"
:auto-update="240"
class="faint"
/>
</div>
<status-content :status="status" />
</router-link>
</div>
<div v-if="report.notes.length">
<small>{{ $t('report.notes') }}</small>
<div
v-for="note in report.notes"
:key="note.id"
class="note"
>
<span>{{ note.content }}</span>
<Timeago
:time="note.created_at"
:auto-update="240"
class="faint"
/>
</div>
</div>
</div>
</template>
<script src="./report.js"></script>
<style src="./report.scss" lang="scss"></style>

View file

@ -1,4 +1,3 @@
import Status from '../status/status.vue' import Status from '../status/status.vue'
import List from '../list/list.vue' import List from '../list/list.vue'
import Checkbox from '../checkbox/checkbox.vue' import Checkbox from '../checkbox/checkbox.vue'
@ -21,14 +20,17 @@ const UserReportingModal = {
} }
}, },
computed: { computed: {
reportModal () {
return this.$store.state.reports.reportModal
},
isLoggedIn () { isLoggedIn () {
return !!this.$store.state.users.currentUser return !!this.$store.state.users.currentUser
}, },
isOpen () { isOpen () {
return this.isLoggedIn && this.$store.state.reports.modalActivated return this.isLoggedIn && this.reportModal.activated
}, },
userId () { userId () {
return this.$store.state.reports.userId return this.reportModal.userId
}, },
user () { user () {
return this.$store.getters.findUser(this.userId) return this.$store.getters.findUser(this.userId)
@ -37,10 +39,10 @@ const UserReportingModal = {
return !this.user.is_local && this.user.screen_name.substr(this.user.screen_name.indexOf('@') + 1) return !this.user.is_local && this.user.screen_name.substr(this.user.screen_name.indexOf('@') + 1)
}, },
statuses () { statuses () {
return this.$store.state.reports.statuses return this.reportModal.statuses
}, },
preTickedIds () { preTickedIds () {
return this.$store.state.reports.preTickedIds return this.reportModal.preTickedIds
} }
}, },
watch: { watch: {

View file

@ -66,6 +66,7 @@
"more": "More", "more": "More",
"loading": "Loading…", "loading": "Loading…",
"generic_error": "An error occured", "generic_error": "An error occured",
"generic_error_message": "An error occured: {0}",
"error_retry": "Please try again", "error_retry": "Please try again",
"retry": "Try again", "retry": "Try again",
"optional": "optional", "optional": "optional",
@ -154,7 +155,8 @@
"repeated_you": "repeated your status", "repeated_you": "repeated your status",
"no_more_notifications": "No more notifications", "no_more_notifications": "No more notifications",
"migrated_to": "migrated to", "migrated_to": "migrated to",
"reacted_with": "reacted with {0}" "reacted_with": "reacted with {0}",
"submitted_report": "submitted a report"
}, },
"polls": { "polls": {
"add_poll": "Add poll", "add_poll": "Add poll",
@ -189,6 +191,8 @@
"interactions": { "interactions": {
"favs_repeats": "Repeats and favorites", "favs_repeats": "Repeats and favorites",
"follows": "New follows", "follows": "New follows",
"emoji_reactions": "Emoji Reactions",
"reports": "Reports",
"moves": "User migrates", "moves": "User migrates",
"load_older": "Load older interactions" "load_older": "Load older interactions"
}, },
@ -255,6 +259,16 @@
"searching_for": "Searching for", "searching_for": "Searching for",
"error": "Not found." "error": "Not found."
}, },
"report": {
"reporter": "Reporter:",
"reported_user": "Reported user:",
"reported_statuses": "Reported statuses:",
"notes": "Notes:",
"state": "State:",
"state_open": "Open",
"state_closed": "Closed",
"state_resolved": "Resolved"
},
"selectable_list": { "selectable_list": {
"select_all": "Select all" "select_all": "Select all"
}, },

View file

@ -745,6 +745,8 @@
"favs_repeats": "Herhalingen en favorieten", "favs_repeats": "Herhalingen en favorieten",
"follows": "Nieuwe gevolgden", "follows": "Nieuwe gevolgden",
"moves": "Gebruikermigraties", "moves": "Gebruikermigraties",
"emoji_reactions": "Emoji Reacties",
"reports": "Rapportages",
"load_older": "Oudere interacties laden" "load_older": "Oudere interacties laden"
}, },
"remote_user_resolver": { "remote_user_resolver": {
@ -752,6 +754,17 @@
"error": "Niet gevonden.", "error": "Niet gevonden.",
"remote_user_resolver": "Externe gebruikers-zoeker" "remote_user_resolver": "Externe gebruikers-zoeker"
}, },
"report": {
"reporter": "Reporteerder:",
"reported_user": "Gerapporteerde gebruiker:",
"reported_statuses": "Gerapporteerde statussen:",
"notes": "Notas:",
"state": "Status:",
"state_open": "Open",
"state_closed": "Gesloten",
"state_resolved": "Opgelost"
},
"selectable_list": { "selectable_list": {
"select_all": "Alles selecteren" "select_all": "Alles selecteren"
}, },

View file

@ -55,6 +55,7 @@ export const defaultState = {
moves: true, moves: true,
emojiReactions: true, emojiReactions: true,
followRequest: true, followRequest: true,
reports: true,
chatMention: true chatMention: true
}, },
webPushNotifications: false, webPushNotifications: false,

View file

@ -2,20 +2,29 @@ import filter from 'lodash/filter'
const reports = { const reports = {
state: { state: {
reportModal: {
userId: null, userId: null,
statuses: [], statuses: [],
preTickedIds: [], preTickedIds: [],
modalActivated: false activated: false
},
reports: {}
}, },
mutations: { mutations: {
openUserReportingModal (state, { userId, statuses, preTickedIds }) { openUserReportingModal (state, { userId, statuses, preTickedIds }) {
state.userId = userId state.reportModal.userId = userId
state.statuses = statuses state.reportModal.statuses = statuses
state.preTickedIds = preTickedIds state.reportModal.preTickedIds = preTickedIds
state.modalActivated = true state.reportModal.activated = true
}, },
closeUserReportingModal (state) { closeUserReportingModal (state) {
state.modalActivated = false state.reportModal.activated = false
},
setReportState (reportsState, { id, state }) {
reportsState.reports[id].state = state
},
addReport (state, report) {
state.reports[report.id] = report
} }
}, },
actions: { actions: {
@ -31,6 +40,23 @@ const reports = {
}, },
closeUserReportingModal ({ commit }) { closeUserReportingModal ({ commit }) {
commit('closeUserReportingModal') commit('closeUserReportingModal')
},
setReportState ({ commit, dispatch, rootState }, { id, state }) {
const oldState = rootState.reports.reports[id].state
commit('setReportState', { id, state })
rootState.api.backendInteractor.setReportState({ id, state }).catch(e => {
console.error('Failed to set report state', e)
dispatch('pushGlobalNotice', {
level: 'error',
messageKey: 'general.generic_error_message',
messageArgs: [e.message],
timeout: 5000
})
commit('setReportState', { id, state: oldState })
})
},
addReport ({ commit }, report) {
commit('addReport', report)
} }
} }
} }

View file

@ -337,6 +337,10 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
} }
if (notification.type === 'pleroma:report') {
dispatch('addReport', notification.report)
}
if (notification.type === 'pleroma:emoji_reaction') { if (notification.type === 'pleroma:emoji_reaction') {
dispatch('fetchEmojiReactionsBy', notification.status.id) dispatch('fetchEmojiReactionsBy', notification.status.id)
} }

View file

@ -87,6 +87,7 @@ const PLEROMA_CHAT_URL = id => `/api/v1/pleroma/chats/by-account-id/${id}`
const PLEROMA_CHAT_MESSAGES_URL = id => `/api/v1/pleroma/chats/${id}/messages` const PLEROMA_CHAT_MESSAGES_URL = id => `/api/v1/pleroma/chats/${id}/messages`
const PLEROMA_CHAT_READ_URL = id => `/api/v1/pleroma/chats/${id}/read` const PLEROMA_CHAT_READ_URL = id => `/api/v1/pleroma/chats/${id}/read`
const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}` const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
const PLEROMA_ADMIN_REPORTS = '/api/pleroma/admin/reports'
const oldfetch = window.fetch const oldfetch = window.fetch
@ -497,7 +498,8 @@ const fetchTimeline = ({
userId = false, userId = false,
tag = false, tag = false,
withMuted = false, withMuted = false,
replyVisibility = 'all' replyVisibility = 'all',
includeTypes = []
}) => { }) => {
const timelineUrls = { const timelineUrls = {
public: MASTODON_PUBLIC_TIMELINE, public: MASTODON_PUBLIC_TIMELINE,
@ -544,6 +546,11 @@ const fetchTimeline = ({
if (replyVisibility !== 'all') { if (replyVisibility !== 'all') {
params.push(['reply_visibility', replyVisibility]) params.push(['reply_visibility', replyVisibility])
} }
if (includeTypes.length > 0) {
includeTypes.forEach(type => {
params.push(['include_types[]', type])
})
}
params.push(['limit', 20]) params.push(['limit', 20])
@ -1266,6 +1273,38 @@ const deleteChatMessage = ({ chatId, messageId, credentials }) => {
}) })
} }
const setReportState = ({ id, state, credentials }) => {
// TODO: Can't use promisedRequest because on OK this does not return json
// See https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1322
return fetch(PLEROMA_ADMIN_REPORTS, {
headers: {
...authHeaders(credentials),
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: 'PATCH',
body: JSON.stringify({
reports: [{
id,
state
}]
})
})
.then(data => {
if (data.status >= 500) {
throw Error(data.statusText)
} else if (data.status >= 400) {
return data.json()
}
return data
})
.then(data => {
if (data.errors) {
throw Error(data.errors[0].message)
}
})
}
const apiService = { const apiService = {
verifyCredentials, verifyCredentials,
fetchTimeline, fetchTimeline,
@ -1351,7 +1390,8 @@ const apiService = {
chatMessages, chatMessages,
sendChatMessage, sendChatMessage,
readChat, readChat,
deleteChatMessage deleteChatMessage,
setReportState
} }
export default apiService export default apiService

View file

@ -387,6 +387,13 @@ export const parseNotification = (data) => {
: parseUser(data.target) : parseUser(data.target)
output.from_profile = parseUser(data.account) output.from_profile = parseUser(data.account)
output.emoji = data.emoji output.emoji = data.emoji
if (data.report) {
output.report = data.report
output.report.content = data.report.content
output.report.acct = parseUser(data.report.account)
output.report.actor = parseUser(data.report.actor)
output.report.statuses = data.report.statuses.map(parseStatus)
}
} else { } else {
const parsedNotice = parseStatus(data.notice) const parsedNotice = parseStatus(data.notice)
output.type = data.ntype output.type = data.ntype

View file

@ -14,7 +14,8 @@ export const visibleTypes = store => {
rootState.config.notificationVisibility.follows && 'follow', rootState.config.notificationVisibility.follows && 'follow',
rootState.config.notificationVisibility.followRequest && 'follow_request', rootState.config.notificationVisibility.followRequest && 'follow_request',
rootState.config.notificationVisibility.moves && 'move', rootState.config.notificationVisibility.moves && 'move',
rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction' rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction',
rootState.config.notificationVisibility.reports && 'pleroma:report'
].filter(_ => _)) ].filter(_ => _))
} }
@ -98,6 +99,9 @@ export const prepareNotificationObject = (notification, i18n) => {
case 'follow_request': case 'follow_request':
i18nString = 'follow_request' i18nString = 'follow_request'
break break
case 'pleroma:report':
i18nString = 'submitted_report'
break
} }
if (notification.type === 'pleroma:emoji_reaction') { if (notification.type === 'pleroma:emoji_reaction') {

View file

@ -1,6 +1,18 @@
import apiService from '../api/api.service.js' import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js' import { promiseInterval } from '../promise_interval/promise_interval.js'
// For using include_types when fetching notifications.
// Note: chat_mention excluded as pleroma-fe polls them separately
const mastoApiNotificationTypes = [
'mention',
'favourite',
'reblog',
'follow',
'move',
'pleroma:emoji_reaction',
'pleroma:report'
]
const update = ({ store, notifications, older }) => { const update = ({ store, notifications, older }) => {
store.dispatch('addNewNotifications', { notifications, older }) store.dispatch('addNewNotifications', { notifications, older })
} }
@ -12,6 +24,7 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => {
const timelineData = rootState.statuses.notifications const timelineData = rootState.statuses.notifications
const hideMutedPosts = getters.mergedConfig.hideMutedPosts const hideMutedPosts = getters.mergedConfig.hideMutedPosts
args['includeTypes'] = mastoApiNotificationTypes
args['withMuted'] = !hideMutedPosts args['withMuted'] = !hideMutedPosts
args['timeline'] = 'notifications' args['timeline'] = 'notifications'
@ -63,6 +76,7 @@ const fetchNotifications = ({ store, args, older }) => {
messageArgs: [error.message], messageArgs: [error.message],
timeout: 5000 timeout: 5000
}) })
console.error(error)
}) })
} }