state.interface.settingsModalState
+ settingsModalState: state => state.interface.settingsModalState,
+ modModalState: state => state.interface.modModalState
})
},
beforeUpdate () {
diff --git a/src/components/user_card/user_card.scss b/src/components/user_card/user_card.scss
index 2e153120..07ab7bec 100644
--- a/src/components/user_card/user_card.scss
+++ b/src/components/user_card/user_card.scss
@@ -235,7 +235,7 @@
line-height: 22px;
flex-wrap: wrap;
- .following {
+ .following, .requested_by {
flex: 1 0 auto;
margin: 0;
margin-bottom: .25em;
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index c04250aa..1de9c0bb 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -122,6 +122,12 @@
>
{{ $t('user_card.follows_you') }}
+
+ {{ $t('user_card.requested_by') }}
+
{
diff --git a/src/modules/config.js b/src/modules/config.js
index 8adc76b4..038ec35a 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -55,7 +55,7 @@ export const defaultState = {
alwaysShowNewPostButton: false,
autohideFloatingPostButton: false,
pauseOnUnfocused: true,
- stopGifs: true,
+ stopGifs: undefined,
replyVisibility: 'all',
thirdColumnMode: 'notifications',
notificationVisibility: {
diff --git a/src/modules/interface.js b/src/modules/interface.js
index a86193ea..ae1a31c3 100644
--- a/src/modules/interface.js
+++ b/src/modules/interface.js
@@ -2,6 +2,9 @@ const defaultState = {
settingsModalState: 'hidden',
settingsModalLoaded: false,
settingsModalTargetTab: null,
+ modModalState: 'hidden',
+ modModalLoaded: false,
+ modModalTargetTab: null,
settings: {
currentSaveStateNotice: null,
noticeClearTimeout: null,
@@ -63,6 +66,30 @@ const interfaceMod = {
setSettingsModalTargetTab (state, value) {
state.settingsModalTargetTab = value
},
+ closeModModal (state) {
+ state.modModalState = 'hidden'
+ },
+ togglePeekModModal (state) {
+ switch (state.modModalState) {
+ case 'minimized':
+ state.modModalState = 'visible'
+ return
+ case 'visible':
+ state.modModalState = 'minimized'
+ return
+ default:
+ throw new Error('Illegal minimization state of mod modal')
+ }
+ },
+ openModModal (state) {
+ state.modModalState = 'visible'
+ if (!state.modModalLoaded) {
+ state.modModalLoaded = true
+ }
+ },
+ setModModalTargetTab (state, value) {
+ state.modModalTargetTab = value
+ },
pushGlobalNotice (state, notice) {
state.globalNotices.push(notice)
},
@@ -105,6 +132,18 @@ const interfaceMod = {
commit('setSettingsModalTargetTab', value)
commit('openSettingsModal')
},
+ closeModModal ({ commit }) {
+ commit('closeModModal')
+ },
+ openModModal ({ commit }) {
+ commit('openModModal')
+ },
+ togglePeekModModal ({ commit }) {
+ commit('togglePeekModModal')
+ },
+ clearModModalTargetTab ({ commit }) {
+ commit('setModModalTargetTab', null)
+ },
pushGlobalNotice (
{ commit, dispatch, state },
{
diff --git a/src/modules/reports.js b/src/modules/reports.js
index fea83e5f..5130b989 100644
--- a/src/modules/reports.js
+++ b/src/modules/reports.js
@@ -1,11 +1,17 @@
-import filter from 'lodash/filter'
+import { filter, find, forEach, remove } from 'lodash'
+
+const getReport = (state, id) => find(state.reports, { id })
+const updateReport = (state, { report, param, value }) => {
+ getReport(state, report.id)[param] = value
+}
const reports = {
state: {
userId: null,
statuses: [],
preTickedIds: [],
- modalActivated: false
+ modalActivated: false,
+ reports: []
},
mutations: {
openUserReportingModal (state, { userId, statuses, preTickedIds }) {
@@ -16,6 +22,38 @@ const reports = {
},
closeUserReportingModal (state) {
state.modalActivated = false
+ },
+ setReport (state, { report }) {
+ let existing = getReport(state, report.id)
+ if (existing) {
+ existing = report
+ } else {
+ state.reports.push(report)
+ }
+ },
+ updateReportStates (state, { reports }) {
+ forEach(reports, (report) => {
+ updateReport(state, { report, param: 'state', value: report.state })
+ })
+ },
+ addNoteToReport (state, { id, note, user }) {
+ // akkoma doesn't return the note from this API endpoint, and there's no
+ // good way to get it. the note data is spoofed in the frontend until
+ // reload.
+ // definitely worth adding this to the backend at some point
+ const report = getReport(state, id)
+ const date = new Date()
+
+ report.notes.push({
+ content: note,
+ user,
+ created_at: date.toISOString(),
+ id: date.getTime()
+ })
+ },
+ deleteNoteFromReport (state, { id, note }) {
+ const report = getReport(state, id)
+ remove(report.notes, { id: note })
}
},
actions: {
@@ -31,6 +69,22 @@ const reports = {
},
closeUserReportingModal ({ commit }) {
commit('closeUserReportingModal')
+ },
+ updateReportStates ({ rootState, commit }, { reports }) {
+ commit('updateReportStates', { reports })
+ return rootState.api.backendInteractor.updateReportStates({ reports })
+ },
+ getReport ({ rootState, commit }, { id }) {
+ return rootState.api.backendInteractor.getReport({ id })
+ .then(report => commit('setReport', { report }))
+ },
+ addNoteToReport ({ rootState, commit }, { id, note }) {
+ commit('addNoteToReport', { id, note, user: rootState.users.currentUser })
+ return rootState.api.backendInteractor.addNoteToReport({ id, note })
+ },
+ deleteNoteFromReport ({ rootState, commit }, { id, note }) {
+ commit('deleteNoteFromReport', { id, note })
+ return rootState.api.backendInteractor.deleteNoteFromReport({ id, note })
}
}
}
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 0cacf251..4e3e1ced 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1,5 +1,5 @@
import { each, map, concat, last, get } from 'lodash'
-import { parseStatus, parseSource, parseUser, parseNotification, parseAttachment, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
+import { parseStatus, parseSource, parseUser, parseNotification, parseReport, parseAttachment, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
import { RegistrationError, StatusCodeError } from '../errors/errors'
/* eslint-env browser */
@@ -19,6 +19,9 @@ const ADMIN_USERS_URL = '/api/pleroma/admin/users'
const SUGGESTIONS_URL = '/api/v1/suggestions'
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
+const ADMIN_REPORTS_URL = '/api/v1/pleroma/admin/reports'
+const ADMIN_REPORT_NOTES_URL = id => `/api/v1/pleroma/admin/reports/${id}/notes`
+const ADMIN_REPORT_NOTE_URL = (report, note) => `/api/v1/pleroma/admin/reports/${report}/notes/${note}`
const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa'
const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
@@ -342,7 +345,7 @@ const fetchUserRelationship = ({ id, credentials }) => {
return new Promise((resolve, reject) => response.json()
.then((json) => {
if (!response.ok) {
- return reject(new StatusCodeError(response.status, json, { url }, response))
+ return reject(new StatusCodeError(400, json, { url }, response))
}
return resolve(json)
}))
@@ -635,6 +638,57 @@ const deleteUser = ({ credentials, user }) => {
})
}
+const getReports = ({ state, limit, page, pageSize, credentials }) => {
+ let url = ADMIN_REPORTS_URL
+ const args = [
+ state && `state=${state}`,
+ limit && `limit=${limit}`,
+ page && `page=${page}`,
+ pageSize && `page_size=${pageSize}`
+ ].filter(_ => _).join('&')
+
+ url = url + (args ? '?' + args : '')
+ return fetch(url, { headers: authHeaders(credentials) })
+ .then((data) => data.json())
+ .then((data) => data.reports.map(parseReport))
+}
+
+const updateReportStates = ({ credentials, reports }) => {
+ // reports syntax: [{ id: int, state: string }...]
+ const updates = {
+ reports: reports.map(report => {
+ return {
+ id: report.id.toString(),
+ state: report.state
+ }
+ })
+ }
+
+ return promisedRequest({
+ url: ADMIN_REPORTS_URL,
+ method: 'PATCH',
+ payload: updates,
+ credentials
+ })
+}
+
+const addNoteToReport = ({ id, note, credentials }) => {
+ return promisedRequest({
+ url: ADMIN_REPORT_NOTES_URL(id),
+ method: 'POST',
+ payload: { content: note },
+ credentials
+ })
+}
+
+const deleteNoteFromReport = ({ report, note, credentials }) => {
+ return promisedRequest({
+ url: ADMIN_REPORT_NOTE_URL(report, note),
+ method: 'DELETE',
+ credentials
+ })
+}
+
const fetchTimeline = ({
timeline,
credentials,
@@ -1726,7 +1780,11 @@ const apiService = {
getSettingsProfile,
saveSettingsProfile,
listSettingsProfiles,
- deleteSettingsProfile
+ deleteSettingsProfile,
+ getReports,
+ updateReportStates,
+ addNoteToReport,
+ deleteNoteFromReport
}
export default apiService
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index 596151d8..4d6f80c2 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -5,6 +5,7 @@ import followRequestFetcher from '../../services/follow_request_fetcher/follow_r
import listsFetcher from '../../services/lists_fetcher/lists_fetcher.service.js'
import announcementsFetcher from '../../services/announcements_fetcher/announcements_fetcher.service.js'
import configFetcher from '../config_fetcher/config_fetcher.service.js'
+import reportsFetcher from '../reports_fetcher/reports_fetcher.service.js'
const backendInteractorService = credentials => ({
startFetchingTimeline ({ timeline, store, userId = false, listId = false, tag }) {
@@ -39,6 +40,10 @@ const backendInteractorService = credentials => ({
return announcementsFetcher.startFetching({ store, credentials })
},
+ startFetchingReports ({ store, state, limit, page, pageSize }) {
+ return reportsFetcher.startFetching({ store, credentials, state, limit, page, pageSize })
+ },
+
startUserSocket ({ store }) {
const serv = store.rootState.instance.server.replace('http', 'ws')
const url = serv + getMastodonSocketURI({ credentials, stream: 'user' })
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index b1aded33..689afb8b 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -88,6 +88,9 @@ export const parseUser = (data) => {
output.friends_count = data.following_count
output.bot = data.bot
+ if (data.akkoma) {
+ output.instance = data.akkoma.instance
+ }
if (data.pleroma) {
const relationship = data.pleroma.relationship
@@ -429,6 +432,24 @@ export const parseNotification = (data) => {
return output
}
+export const parseReport = (data) => {
+ const report = {}
+
+ report.account = parseUser(data.account)
+ report.actor = parseUser(data.actor)
+ report.statuses = data.statuses.map(parseStatus)
+ report.notes = data.notes.map(note => {
+ note.user = parseUser(note.user)
+ return note
+ })
+ report.state = data.state
+ report.content = data.content
+ report.created_at = data.created_at
+ report.id = data.id
+
+ return report
+}
+
const isNsfw = (status) => {
const nsfwRegex = /#nsfw/i
return (status.tags || []).includes('nsfw') || !!(status.text || '').match(nsfwRegex)
diff --git a/src/services/reports_fetcher/reports_fetcher.service.js b/src/services/reports_fetcher/reports_fetcher.service.js
new file mode 100644
index 00000000..f0bb9dcf
--- /dev/null
+++ b/src/services/reports_fetcher/reports_fetcher.service.js
@@ -0,0 +1,20 @@
+import apiService from '../api/api.service.js'
+import { promiseInterval } from '../promise_interval/promise_interval.js'
+import { forEach } from 'lodash'
+
+const fetchAndUpdate = ({ store, credentials, state, limit, page, pageSize }) => {
+ return apiService.getReports({ credentials, state, limit, page, pageSize })
+ .then(reports => forEach(reports, report => store.commit('setReport', { report })))
+}
+
+const startFetching = ({ store, credentials, state, limit, page, pageSize }) => {
+ const boundFetchAndUpdate = () => fetchAndUpdate({ store, credentials, state, limit, page, pageSize })
+ boundFetchAndUpdate()
+ return promiseInterval(boundFetchAndUpdate, 60000)
+}
+
+const reportsFetcher = {
+ startFetching
+}
+
+export default reportsFetcher
diff --git a/yarn.lock b/yarn.lock
index 86ae6b85..b8642176 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1092,12 +1092,12 @@
"@fortawesome/fontawesome-common-types@^0.3.0":
version "0.3.0"
- resolved "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.3.0.tgz"
+ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.3.0.tgz#949995a05c0d8801be7e0a594f775f1dbaa0d893"
integrity sha512-CA3MAZBTxVsF6SkfkHXDerkhcQs0QPofy43eFdbWJJkZiq3SfiaH1msOkac59rQaqto5EqWnASboY1dBuKen5w==
"@fortawesome/fontawesome-svg-core@1.3.0":
version "1.3.0"
- resolved "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.3.0.tgz"
+ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.3.0.tgz#343fac91fa87daa630d26420bfedfba560f85885"
integrity sha512-UIL6crBWhjTNQcONt96ExjUnKt1D68foe3xjEensLDclqQ6YagwCRYVQdrp/hW0ALRp/5Fv/VKw+MqTUWYYvPg==
dependencies:
"@fortawesome/fontawesome-common-types" "^0.3.0"
@@ -1141,9 +1141,9 @@
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
"@intlify/bundle-utils@next":
- version "3.1.2"
- resolved "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-3.1.2.tgz"
- integrity sha512-amgSo0NN5OKWYdcgFmfJqo2tcUcZ6C66Bxm5ALQnB0m3MUQtS9aJzKoIo+EU9XQiOVmlBFxRtNoZm+psHa5FNA==
+ version "3.2.1"
+ resolved "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-3.2.1.tgz"
+ integrity sha512-rf4cLBOnbqmpXVcCdcYHilZpMt1m82syh3WLBJlZvGxN2KkH9HeHVH4+bnibF/SDXCHNh6lM6wTpS/qw+PkcMg==
dependencies:
"@intlify/message-compiler" next
"@intlify/shared" next
@@ -1168,7 +1168,7 @@
dependencies:
"@intlify/shared" "9.2.2"
-"@intlify/message-compiler@9.2.2":
+"@intlify/message-compiler@9.2.2", "@intlify/message-compiler@next":
version "9.2.2"
resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz"
integrity sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==
@@ -1176,24 +1176,11 @@
"@intlify/shared" "9.2.2"
source-map "0.6.1"
-"@intlify/message-compiler@next":
- version "9.3.0-beta.3"
- resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.3.0-beta.3.tgz"
- integrity sha512-j8OwToBQgs01RBMX4GCDNQfcnmw3AiDG3moKIONTrfXcf+1yt/rWznLTYH/DXbKcFMAFijFpCzMYjUmH1jVFYA==
- dependencies:
- "@intlify/shared" "9.3.0-beta.3"
- source-map "0.6.1"
-
"@intlify/shared@9.2.2", "@intlify/shared@next":
version "9.2.2"
resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz"
integrity sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==
-"@intlify/shared@9.3.0-beta.3":
- version "9.3.0-beta.3"
- resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.3.0-beta.3.tgz"
- integrity sha512-Z/0TU4GhFKRxKh+0RbwJExik9zz57gXYgxSYaPn7YQdkQ/pabSioCY/SXnYxQHL6HzULF5tmqarFm6glbGqKhw==
-
"@intlify/vue-devtools@9.2.2":
version "9.2.2"
resolved "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz"
@@ -8095,9 +8082,9 @@ mute-stream@0.0.7:
integrity sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==
nan@^2.12.1:
- version "2.16.0"
- resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916"
- integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==
+ version "2.17.0"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
+ integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
nanoid@^3.3.4:
version "3.3.4"