Merge branch 'develop' into feature/add_settings_search
This commit is contained in:
commit
41e94628cb
36 changed files with 655 additions and 543 deletions
36
CHANGELOG.md
36
CHANGELOG.md
|
@ -6,31 +6,40 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Changed
|
||||
### Added
|
||||
|
||||
- **breaking** PleromaFE login feature relies on `admin` scope presence in PleromaFE token (older versions of PleromaFE don't support it)
|
||||
- Moves emoji pack configuration from the main menu to settings tab, redesigns it and fixes bugs
|
||||
- `mailerEnabled` must be set to `true` in order to require password reset (password reset currently only works via email)
|
||||
- Remove fetching initial data for configuring server settings
|
||||
- Actions in users module (ActivateUsers, AddRight, DeactivateUsers, DeleteRight, DeleteUsers) now accept an array of users instead of one user
|
||||
- Leave dropdown menu open after clicking an action
|
||||
- Move current try/catch error handling from view files to module, add it where necessary
|
||||
- Display checkboxes in status card and fetch statuses only when status card was rendered from Statuses by instance page
|
||||
- Move statuses by instance state from local state to store state
|
||||
- Pass user's ID to actions that moderate users when action is called from user's profile page
|
||||
- Ability to see local statuses in Statuses by instance section
|
||||
- Ability to configure Oban.Cron settings and settings for notifications streamer
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix parsing tuples in Pleroma.Upload.Filter.Mogrify and Pleroma.Emails.Mailer settings
|
||||
|
||||
## [2.0] - 2020-02-27
|
||||
|
||||
### Added
|
||||
|
||||
- Optimistic update for actions in users module and fetching users after api function finished its execution
|
||||
- Relay management
|
||||
- Ability to fetch all statuses from a given instance
|
||||
- Grouped reports: now you can view reports, which are grouped by status (pagination is not implemented yet, though)
|
||||
- Ability to confirm users' emails and resend confirmation emails
|
||||
- Report notes
|
||||
- Ability to moderate users on the statuses page
|
||||
- Ability to moderate user on the user's page
|
||||
- Ability to remove setting's updated value and set it back to initial value
|
||||
- Ability to restart an application when settings that require instance reboot were changed
|
||||
- Mobile UI for Settings tab
|
||||
- Mobile and Tablet UI for all sections
|
||||
|
||||
### Changed
|
||||
|
||||
- **breaking** PleromaFE login feature relies on `admin` scope presence in PleromaFE token (older versions of PleromaFE don't support it)
|
||||
- `mailerEnabled` must be set to `true` in order to require password reset (password reset currently only works via email)
|
||||
- Render inputs for configuring settings based on description that comes from the BE
|
||||
- Remove fetching initial data for configuring server settings
|
||||
- Actions in users module (ActivateUsers, AddRight, DeactivateUsers, DeleteRight, DeleteUsers) now accept an array of users instead of one user
|
||||
- Leave dropdown menu open after clicking an action
|
||||
- Display checkboxes in status card and fetch statuses only when status card was rendered from Statuses by instance page
|
||||
- Move statuses by instance state from local state to store state
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -39,6 +48,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Remove duplicated success message
|
||||
- Fix styles for Statuses by instance page
|
||||
- Fix styles for Reports section
|
||||
- Fix listing remote emoji
|
||||
|
||||
## [1.2.0] - 2019-09-27
|
||||
|
||||
|
|
|
@ -11,40 +11,12 @@ const reports = [
|
|||
{ created_at: '2019-05-18T13:01:33.000Z', account: { acct: 'nick', display_name: 'Nick Keys', tags: [] }, actor: { acct: 'admin' }, state: 'closed', id: '4', content: '', statuses: [] }
|
||||
]
|
||||
|
||||
const groupedReports = [
|
||||
{ account: { avatar: 'http://localhost:4000/images/avi.png', confirmation_pending: false, deactivated: false, display_name: 'leo', id: '9oG0YghgBi94EATI9I', local: true, nickname: 'leo', roles: { admin: false, moderator: false }, tags: [] },
|
||||
actors: [{ acct: 'admin', avatar: 'http://localhost:4000/images/avi.png', deactivated: false, display_name: 'admin', id: '9oFz4pTauG0cnJ581w', local: true, nickname: 'admin', roles: { admin: false, moderator: false }, tags: [], url: 'http://localhost:4000/users/admin', username: 'admin' }],
|
||||
date: '2019-11-23T12:56:11.969772Z',
|
||||
reports: [
|
||||
{ created_at: '2019-05-21T21:35:33.000Z', account: { acct: 'benj', display_name: 'Benjamin Fame', tags: [] }, actor: { acct: 'admin' }, state: 'open', id: '2', content: 'This is a report', statuses: [] },
|
||||
{ created_at: '2019-05-20T22:45:33.000Z', account: { acct: 'alice', display_name: 'Alice Pool', tags: [] }, actor: { acct: 'admin2' }, state: 'resolved', id: '7', content: 'Please block this user', statuses: [
|
||||
{ account: { display_name: 'Alice Pool', avatar: '' }, visibility: 'public', sensitive: false, id: '11', content: 'Hey!', url: '', created_at: '2019-05-10T21:35:33.000Z' },
|
||||
{ account: { display_name: 'Alice Pool', avatar: '' }, visibility: 'unlisted', sensitive: true, id: '10', content: 'Bye!', url: '', created_at: '2019-05-10T21:00:33.000Z' }
|
||||
] }
|
||||
],
|
||||
status: {
|
||||
account: { acct: 'leo' },
|
||||
content: 'At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis',
|
||||
created_at: '2019-11-23T12:55:20.000Z',
|
||||
id: '9pFoQO69piu7cUDnJg',
|
||||
url: 'http://localhost:4000/notice/9pFoQO69piu7cUDnJg',
|
||||
visibility: 'unlisted',
|
||||
sensitive: true
|
||||
},
|
||||
status_deleted: false
|
||||
}
|
||||
]
|
||||
|
||||
export async function fetchReports(filter, page, pageSize, authHost, token) {
|
||||
return filter.length > 0
|
||||
? Promise.resolve({ data: { reports: reports.filter(report => report.state === filter) }})
|
||||
: Promise.resolve({ data: { reports }})
|
||||
}
|
||||
|
||||
export async function fetchGroupedReports(authHost, token) {
|
||||
return Promise.resolve({ data: { reports: groupedReports }})
|
||||
}
|
||||
|
||||
export async function changeState(reportsData, authHost, token) {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
|
|
|
@ -24,15 +24,6 @@ export async function fetchReports(filter, page, pageSize, authHost, token) {
|
|||
})
|
||||
}
|
||||
|
||||
export async function fetchGroupedReports(authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/grouped_reports`,
|
||||
method: 'get',
|
||||
headers: authHeaders(token)
|
||||
})
|
||||
}
|
||||
|
||||
export async function createNote(content, reportID, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
|
|
|
@ -21,6 +21,15 @@ export async function deleteStatus(id, authHost, token) {
|
|||
})
|
||||
}
|
||||
|
||||
export async function fetchStatuses({ godmode, localOnly, authHost, token, pageSize, page }) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/statuses?godmode=${godmode}&local_only=${localOnly}&page=${page}&page_size=${pageSize}`,
|
||||
method: 'get',
|
||||
headers: authHeaders(token)
|
||||
})
|
||||
}
|
||||
|
||||
export async function fetchStatusesByInstance({ instance, authHost, token, pageSize, page }) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
|
|
|
@ -267,9 +267,8 @@ export default {
|
|||
font-style: italic;
|
||||
}
|
||||
}
|
||||
@media
|
||||
only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.el-message {
|
||||
min-width: 80%;
|
||||
}
|
||||
|
|
|
@ -193,7 +193,7 @@ export default {
|
|||
deleteAccount: 'Delete Account',
|
||||
deleteAccounts: 'Delete Accounts',
|
||||
forceNsfw: 'Force posts to be NSFW',
|
||||
stripMedia: 'Force posts not to have media',
|
||||
stripMedia: 'Force posts to not have media',
|
||||
forceUnlisted: 'Force posts to be unlisted',
|
||||
sandbox: 'Force posts to be followers-only',
|
||||
disableRemoteSubscription: 'Disallow following user from remote instances',
|
||||
|
@ -244,7 +244,9 @@ export default {
|
|||
statuses: 'Statuses by instance',
|
||||
instanceFilter: 'Instance filter',
|
||||
loadMore: 'Load more',
|
||||
noInstances: 'No other instances found'
|
||||
noInstances: 'No other instances found',
|
||||
onlyLocalStatuses: 'Show only local statuses',
|
||||
showPrivateStatuses: 'Show private statuses'
|
||||
},
|
||||
userProfile: {
|
||||
tags: 'Tags',
|
||||
|
@ -254,8 +256,7 @@ export default {
|
|||
external: 'external',
|
||||
localUppercase: 'Local',
|
||||
nickname: 'Nickname',
|
||||
recentStatuses: 'Recent Statues',
|
||||
showPrivateStatuses: 'Show private statuses',
|
||||
recentStatuses: 'Recent Statuses',
|
||||
roles: 'Roles',
|
||||
activeUppercase: 'Active',
|
||||
active: 'active',
|
||||
|
@ -273,7 +274,6 @@ export default {
|
|||
},
|
||||
reports: {
|
||||
reports: 'Reports',
|
||||
groupedReports: 'Grouped reports',
|
||||
reply: 'Reply',
|
||||
from: 'From',
|
||||
showNotes: 'Show notes',
|
||||
|
|
|
@ -17,8 +17,6 @@ const getters = {
|
|||
errorLogs: state => state.errorLog.logs,
|
||||
users: state => state.users.fetchedUsers,
|
||||
authHost: state => state.user.authHost,
|
||||
settings: state => state.settings,
|
||||
instances: state => state.peers.fetchedPeers,
|
||||
statuses: state => state.status.fetchedStatuses
|
||||
settings: state => state.settings
|
||||
}
|
||||
export default getters
|
||||
|
|
|
@ -63,6 +63,9 @@ export const parseNonTuples = (key, value) => {
|
|||
return updated
|
||||
}
|
||||
if (key === ':args') {
|
||||
if (typeof value === 'string') {
|
||||
return [value]
|
||||
}
|
||||
const index = value.findIndex(el => typeof el === 'object' && el.tuple.includes('implode'))
|
||||
const updated = value.map((el, i) => i === index ? 'implode' : el)
|
||||
return updated
|
||||
|
@ -80,10 +83,15 @@ export const parseTuples = (tuples, key) => {
|
|||
accum[item.tuple[0]] = item.tuple[1].reduce((acc, mascot) => {
|
||||
return [...acc, { [mascot.tuple[0]]: { ...mascot.tuple[1], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}]
|
||||
}, [])
|
||||
} else if (item.tuple[0] === ':groups' || item.tuple[0] === ':replace' || item.tuple[0] === ':retries') {
|
||||
} else if (Array.isArray(item.tuple[1]) &&
|
||||
(item.tuple[0] === ':groups' || item.tuple[0] === ':replace' || item.tuple[0] === ':retries')) {
|
||||
accum[item.tuple[0]] = item.tuple[1].reduce((acc, group) => {
|
||||
return [...acc, { [group.tuple[0]]: { value: group.tuple[1], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}]
|
||||
}, [])
|
||||
} else if (item.tuple[0] === ':crontab') {
|
||||
accum[item.tuple[0]] = item.tuple[1].reduce((acc, group) => {
|
||||
return { ...acc, [group.tuple[1]]: group.tuple[0] }
|
||||
}, {})
|
||||
} else if (item.tuple[0] === ':match_actor') {
|
||||
accum[item.tuple[0]] = Object.keys(item.tuple[1]).reduce((acc, regex) => {
|
||||
return [...acc, { [regex]: { value: item.tuple[1][regex], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}]
|
||||
|
@ -218,7 +226,12 @@ export const wrapUpdatedSettings = (group, settings, currentState) => {
|
|||
const wrapValues = (settings, currentState) => {
|
||||
return Object.keys(settings).map(setting => {
|
||||
const [type, value] = settings[setting]
|
||||
if (type === 'keyword' || type.includes('keyword') || setting === ':replace') {
|
||||
if (
|
||||
type === 'keyword' ||
|
||||
type.includes('keyword') ||
|
||||
type.includes('tuple') && type.includes('list') ||
|
||||
setting === ':replace'
|
||||
) {
|
||||
return { 'tuple': [setting, wrapValues(value, currentState)] }
|
||||
} else if (type === 'atom' && value.length > 0) {
|
||||
return { 'tuple': [setting, `:${value}`] }
|
||||
|
@ -226,8 +239,8 @@ const wrapValues = (settings, currentState) => {
|
|||
return typeof value === 'string'
|
||||
? { 'tuple': [setting, value] }
|
||||
: { 'tuple': [setting, { 'tuple': value }] }
|
||||
} else if (type.includes('tuple') && type.includes('list')) {
|
||||
return { 'tuple': [setting, value] }
|
||||
} else if (type === 'reversed_tuple') {
|
||||
return { 'tuple': [value, setting] }
|
||||
} else if (type === 'map') {
|
||||
const mapValue = Object.keys(value).reduce((acc, key) => {
|
||||
acc[key] = setting === ':match_actor' ? value[key] : value[key][1]
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import { changeState, fetchReports, fetchGroupedReports, createNote, deleteNote } from '@/api/reports'
|
||||
import { changeState, fetchReports, createNote, deleteNote } from '@/api/reports'
|
||||
|
||||
const reports = {
|
||||
state: {
|
||||
fetchedReports: [],
|
||||
fetchedGroupedReports: [],
|
||||
totalReportsCount: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
groupReports: false,
|
||||
stateFilter: '',
|
||||
loading: true
|
||||
},
|
||||
|
@ -24,17 +22,11 @@ const reports = {
|
|||
SET_REPORTS: (state, reports) => {
|
||||
state.fetchedReports = reports
|
||||
},
|
||||
SET_GROUPED_REPORTS: (state, reports) => {
|
||||
state.fetchedGroupedReports = reports
|
||||
},
|
||||
SET_REPORTS_COUNT: (state, total) => {
|
||||
state.totalReportsCount = total
|
||||
},
|
||||
SET_REPORTS_FILTER: (state, filter) => {
|
||||
state.stateFilter = filter
|
||||
},
|
||||
SET_REPORTS_GROUPING: (state) => {
|
||||
state.groupReports = !state.groupReports
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
@ -46,14 +38,7 @@ const reports = {
|
|||
return updatedReportsIds.includes(report.id) ? { ...report, state: reportsData[0].state } : report
|
||||
})
|
||||
|
||||
const updatedGroupedReports = state.fetchedGroupedReports.map(group => {
|
||||
const updatedReportsIds = reportsData.map(({ id }) => id)
|
||||
const updatedReports = group.reports.map(report => updatedReportsIds.includes(report.id) ? { ...report, state: reportsData[0].state } : report)
|
||||
return { ...group, reports: updatedReports }
|
||||
})
|
||||
|
||||
commit('SET_REPORTS', updatedReports)
|
||||
commit('SET_GROUPED_REPORTS', updatedGroupedReports)
|
||||
},
|
||||
ClearFetchedReports({ commit }) {
|
||||
commit('SET_REPORTS', [])
|
||||
|
@ -67,19 +52,9 @@ const reports = {
|
|||
commit('SET_PAGE', page)
|
||||
commit('SET_LOADING', false)
|
||||
},
|
||||
async FetchGroupedReports({ commit, getters }) {
|
||||
commit('SET_LOADING', true)
|
||||
const { data } = await fetchGroupedReports(getters.authHost, getters.token)
|
||||
|
||||
commit('SET_GROUPED_REPORTS', data.reports)
|
||||
commit('SET_LOADING', false)
|
||||
},
|
||||
SetFilter({ commit }, filter) {
|
||||
commit('SET_REPORTS_FILTER', filter)
|
||||
},
|
||||
ToggleReportsGrouping({ commit }) {
|
||||
commit('SET_REPORTS_GROUPING')
|
||||
},
|
||||
CreateReportNote({ commit, getters, state, rootState }, { content, reportID }) {
|
||||
createNote(content, reportID, getters.authHost, getters.token)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { changeStatusScope, deleteStatus, fetchStatusesByInstance } from '@/api/status'
|
||||
import { changeStatusScope, deleteStatus, fetchStatuses, fetchStatusesByInstance } from '@/api/status'
|
||||
|
||||
const status = {
|
||||
state: {
|
||||
|
@ -6,11 +6,21 @@ const status = {
|
|||
loading: false,
|
||||
statusesByInstance: {
|
||||
selectedInstance: '',
|
||||
showLocal: false,
|
||||
showPrivate: false,
|
||||
page: 1,
|
||||
pageSize: 30
|
||||
pageSize: 20,
|
||||
buttonLoading: false,
|
||||
allLoaded: false
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
CHANGE_GODMODE_CHECKBOX_VALUE: (state, value) => {
|
||||
state.statusesByInstance.showPrivate = value
|
||||
},
|
||||
CHANGE_LOCAL_CHECKBOX_VALUE: (state, value) => {
|
||||
state.statusesByInstance.showLocal = value
|
||||
},
|
||||
CHANGE_PAGE: (state, page) => {
|
||||
state.statusesByInstance.page = page
|
||||
},
|
||||
|
@ -23,6 +33,12 @@ const status = {
|
|||
PUSH_STATUSES: (state, statuses) => {
|
||||
state.fetchedStatuses = [...state.fetchedStatuses, ...statuses]
|
||||
},
|
||||
SET_ALL_LOADED: (state, status) => {
|
||||
state.statusesByInstance.allLoaded = status
|
||||
},
|
||||
SET_BUTTON_LOADING: (state, status) => {
|
||||
state.statusesByInstance.buttonLoading = status
|
||||
},
|
||||
SET_LOADING: (state, status) => {
|
||||
state.loading = status
|
||||
}
|
||||
|
@ -36,8 +52,6 @@ const status = {
|
|||
dispatch('FetchUserStatuses', { userId, godmode })
|
||||
} else if (fetchStatusesByInstance) { // called from Statuses by Instance
|
||||
dispatch('FetchStatusesByInstance')
|
||||
} else { // called from GroupedReports
|
||||
dispatch('FetchGroupedReports')
|
||||
}
|
||||
},
|
||||
async DeleteStatus({ dispatch, getters }, { statusId, reportCurrentPage, userId, godmode, fetchStatusesByInstance }) {
|
||||
|
@ -48,14 +62,50 @@ const status = {
|
|||
dispatch('FetchUserStatuses', { userId, godmode })
|
||||
} else if (fetchStatusesByInstance) { // called from Statuses by Instance
|
||||
dispatch('FetchStatusesByInstance')
|
||||
} else { // called from GroupedReports
|
||||
dispatch('FetchGroupedReports')
|
||||
}
|
||||
},
|
||||
async FetchStatusesByInstance({ commit, getters, state }) {
|
||||
async FetchStatusesByInstance({ commit, getters, state, rootState }) {
|
||||
commit('SET_LOADING', true)
|
||||
const statuses = state.statusesByInstance.selectedInstance === ''
|
||||
? { data: [] }
|
||||
if (state.statusesByInstance.selectedInstance === '') {
|
||||
commit('SET_STATUSES_BY_INSTANCE', [])
|
||||
} else {
|
||||
const statuses = state.statusesByInstance.selectedInstance === rootState.user.authHost
|
||||
? await fetchStatuses(
|
||||
{
|
||||
godmode: state.statusesByInstance.showPrivate,
|
||||
localOnly: state.statusesByInstance.showLocal,
|
||||
authHost: getters.authHost,
|
||||
token: getters.token,
|
||||
pageSize: state.statusesByInstance.pageSize,
|
||||
page: state.statusesByInstance.page
|
||||
})
|
||||
: await fetchStatusesByInstance(
|
||||
{
|
||||
instance: state.statusesByInstance.selectedInstance,
|
||||
authHost: getters.authHost,
|
||||
token: getters.token,
|
||||
pageSize: state.statusesByInstance.pageSize,
|
||||
page: state.statusesByInstance.page
|
||||
})
|
||||
commit('SET_STATUSES_BY_INSTANCE', statuses.data)
|
||||
if (statuses.data.length < state.statusesByInstance.pageSize) {
|
||||
commit('SET_ALL_LOADED', true)
|
||||
}
|
||||
}
|
||||
commit('SET_LOADING', false)
|
||||
},
|
||||
async FetchStatusesPageByInstance({ commit, getters, rootState, state }) {
|
||||
commit('SET_BUTTON_LOADING', true)
|
||||
const statuses = state.statusesByInstance.selectedInstance === rootState.user.authHost
|
||||
? await fetchStatuses(
|
||||
{
|
||||
godmode: state.statusesByInstance.showPrivate,
|
||||
localOnly: state.statusesByInstance.showLocal,
|
||||
authHost: getters.authHost,
|
||||
token: getters.token,
|
||||
pageSize: state.statusesByInstance.pageSize,
|
||||
page: state.statusesByInstance.page
|
||||
})
|
||||
: await fetchStatusesByInstance(
|
||||
{
|
||||
instance: state.statusesByInstance.selectedInstance,
|
||||
|
@ -64,26 +114,29 @@ const status = {
|
|||
pageSize: state.statusesByInstance.pageSize,
|
||||
page: state.statusesByInstance.page
|
||||
})
|
||||
|
||||
commit('SET_STATUSES_BY_INSTANCE', statuses.data)
|
||||
commit('SET_LOADING', false)
|
||||
},
|
||||
async FetchStatusesPageByInstance({ commit, getters, state }) {
|
||||
commit('SET_LOADING', true)
|
||||
const statuses = await fetchStatusesByInstance(
|
||||
{
|
||||
instance: state.statusesByInstance.selectedInstance,
|
||||
authHost: getters.authHost,
|
||||
token: getters.token,
|
||||
pageSize: state.statusesByInstance.pageSize,
|
||||
page: state.statusesByInstance.page
|
||||
})
|
||||
|
||||
commit('PUSH_STATUSES', statuses.data)
|
||||
commit('SET_LOADING', false)
|
||||
commit('SET_BUTTON_LOADING', false)
|
||||
if (statuses.data.length < state.statusesByInstance.pageSize) {
|
||||
commit('SET_ALL_LOADED', true)
|
||||
}
|
||||
},
|
||||
HandleGodmodeCheckboxChange({ commit, dispatch }, value) {
|
||||
dispatch('HandlePageChange', 1)
|
||||
commit('SET_ALL_LOADED', false)
|
||||
|
||||
commit('CHANGE_GODMODE_CHECKBOX_VALUE', value)
|
||||
dispatch('FetchStatusesByInstance')
|
||||
},
|
||||
HandleLocalCheckboxChange({ commit, dispatch }, value) {
|
||||
dispatch('HandlePageChange', 1)
|
||||
commit('SET_ALL_LOADED', false)
|
||||
|
||||
commit('CHANGE_LOCAL_CHECKBOX_VALUE', value)
|
||||
dispatch('FetchStatusesByInstance')
|
||||
},
|
||||
HandleFilterChange({ commit }, instance) {
|
||||
commit('CHANGE_SELECTED_INSTANCE', instance)
|
||||
commit('SET_ALL_LOADED', false)
|
||||
},
|
||||
HandlePageChange({ commit }, page) {
|
||||
commit('CHANGE_PAGE', page)
|
||||
|
|
|
@ -44,6 +44,9 @@ export default {
|
|||
isDesktop() {
|
||||
return this.$store.state.app.device === 'desktop'
|
||||
},
|
||||
isMobile() {
|
||||
return this.$store.state.app.device === 'mobile'
|
||||
},
|
||||
shortcodePresent() {
|
||||
return this.shortcode.trim() === ''
|
||||
}
|
||||
|
|
|
@ -95,6 +95,9 @@ export default {
|
|||
isDesktop() {
|
||||
return this.$store.state.app.device === 'desktop'
|
||||
},
|
||||
isMobile() {
|
||||
return this.$store.state.app.device === 'mobile'
|
||||
},
|
||||
localPacks() {
|
||||
return this.$store.state.emojiPacks.localPacks
|
||||
},
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
sortable/>
|
||||
<el-table-column
|
||||
:label="$t('invites.token')"
|
||||
:min-width="isDesktop ? 350 : 125"
|
||||
:min-width="isDesktop ? 320 : 120"
|
||||
prop="token"/>
|
||||
<el-table-column
|
||||
v-if="isDesktop"
|
||||
|
@ -119,7 +119,9 @@
|
|||
<template slot-scope="scope">
|
||||
<el-tag
|
||||
:type="scope.row.used ? 'danger' : 'success'"
|
||||
disable-transitions>{{ scope.row.used ? $t('invites.used') : $t('invites.active') }}</el-tag>
|
||||
disable-transitions>
|
||||
{{ scope.row.used ? $t('invites.used') : $t('invites.active') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
|
@ -268,9 +270,8 @@ export default {
|
|||
margin: 0 0 10px 0;
|
||||
}
|
||||
}
|
||||
@media
|
||||
only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.invites-container {
|
||||
.actions-container {
|
||||
display: flex;
|
||||
|
@ -279,6 +280,9 @@ only screen and (max-width: 760px),
|
|||
align-items: center;
|
||||
margin: 15px 10px 7px 10px;
|
||||
}
|
||||
.cell {
|
||||
padding: 0;
|
||||
}
|
||||
.create-invite-token {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -296,7 +300,9 @@ only screen and (max-width: 760px),
|
|||
}
|
||||
.invite-token-table {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
margin: 0 5px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.invite-via-email {
|
||||
width: 100%;
|
||||
|
@ -308,6 +314,11 @@ only screen and (max-width: 760px),
|
|||
.info {
|
||||
margin: 0 0 10px 5px;
|
||||
}
|
||||
th {
|
||||
.cell {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.create-invite-token {
|
||||
width: 100%
|
||||
|
|
|
@ -1,47 +1,40 @@
|
|||
<template>
|
||||
<div v-if="!loading" class="moderation-log-container">
|
||||
<h1>{{ $t('moderationLog.moderationLog') }}</h1>
|
||||
<el-row type="flex" class="row-bg" justify="space-between">
|
||||
<el-col :span="9">
|
||||
<el-select
|
||||
v-model="user"
|
||||
class="user-select"
|
||||
clearable
|
||||
placeholder="Filter by admin/moderator"
|
||||
@change="fetchLogWithFilters">
|
||||
<el-option-group
|
||||
v-for="group in users"
|
||||
:key="group.label"
|
||||
:label="group.label">
|
||||
<el-option
|
||||
v-for="item in group.options"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id" />
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="6" class="search-container">
|
||||
<el-input
|
||||
v-model="search"
|
||||
placeholder="Search logs"
|
||||
prefix-icon="el-icon-search"
|
||||
clearable
|
||||
@input="handleDebounceSearchInput" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" class="row-bg" justify="space-between">
|
||||
<el-col :span="9" class="date-container">
|
||||
<el-date-picker
|
||||
:default-time="['00:00:00', '23:59:59']"
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
start-placeholder="Start date"
|
||||
end-placeholder="End date"
|
||||
unlink-panels
|
||||
@change="fetchLogWithFilters" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="moderation-log-nav-container">
|
||||
<el-select
|
||||
v-model="user"
|
||||
class="moderation-log-user-select"
|
||||
clearable
|
||||
placeholder="Filter by admin/moderator"
|
||||
@change="fetchLogWithFilters">
|
||||
<el-option-group
|
||||
v-for="group in users"
|
||||
:key="group.label"
|
||||
:label="group.label">
|
||||
<el-option
|
||||
v-for="item in group.options"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"/>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="search"
|
||||
placeholder="Search logs"
|
||||
clearable
|
||||
class="moderation-log-search"
|
||||
@input="handleDebounceSearchInput"/>
|
||||
</div>
|
||||
<el-date-picker
|
||||
:default-time="['00:00:00', '23:59:59']"
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
start-placeholder="Start date"
|
||||
end-placeholder="End date"
|
||||
unlink-panels
|
||||
class="moderation-log-date-panel"
|
||||
@change="fetchLogWithFilters" />
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(logEntry, index) in log"
|
||||
|
@ -56,6 +49,7 @@
|
|||
:hide-on-single-page="true"
|
||||
:page-size="50"
|
||||
:total="total"
|
||||
:small="isMobile"
|
||||
layout="prev, pager, next"
|
||||
@current-change="fetchLogWithFilters" />
|
||||
</div>
|
||||
|
@ -77,6 +71,9 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
isMobile() {
|
||||
return this.$store.state.app.device === 'mobile'
|
||||
},
|
||||
loading() {
|
||||
return this.$store.state.moderationLog.logLoading &&
|
||||
this.$store.state.moderationLog.adminsLoading
|
||||
|
@ -139,7 +136,17 @@ h1 {
|
|||
margin: 25px 45px 0 0;
|
||||
padding: 0px;
|
||||
}
|
||||
.user-select {
|
||||
.moderation-log-date-panel {
|
||||
width: 350px;
|
||||
}
|
||||
.moderation-log-nav-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.moderation-log-search {
|
||||
width: 350px;
|
||||
}
|
||||
.moderation-log-user-select {
|
||||
margin: 0 0 20px;
|
||||
width: 350px;
|
||||
}
|
||||
|
@ -149,4 +156,30 @@ h1 {
|
|||
.pagination {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.moderation-log-date-panel {
|
||||
width: 100%;
|
||||
}
|
||||
.moderation-log-user-select {
|
||||
margin: 0 0 10px;
|
||||
width: 55%;
|
||||
}
|
||||
.moderation-log-search {
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:801px) and (min-width: 481px) {
|
||||
.moderation-log-date-panel {
|
||||
width: 55%;
|
||||
}
|
||||
.moderation-log-user-select {
|
||||
margin: 0 0 10px;
|
||||
width: 55%;
|
||||
}
|
||||
.moderation-log-search {
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,159 +0,0 @@
|
|||
<template>
|
||||
<el-timeline class="reports-timeline">
|
||||
<el-timeline-item
|
||||
v-for="groupedReport in groupedReports"
|
||||
:key="groupedReport.id"
|
||||
:timestamp="parseTimestamp(groupedReport.date)"
|
||||
placement="top"
|
||||
class="timeline-item-container">
|
||||
<el-card class="grouped-report">
|
||||
<div class="header-container">
|
||||
<div>
|
||||
<h3 class="report-title">{{ $t('reports.reportsOn') }} {{ groupedReport.account.display_name }}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<el-dropdown trigger="click">
|
||||
<el-button plain size="small" icon="el-icon-edit" class="report-actions-button">{{ $t('reports.changeAllReports') }}<i class="el-icon-arrow-down el-icon--right"/></el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item @click.native="changeAllReports('resolved', groupedReport.reports)">{{ $t('reports.resolveAll') }}</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="changeAllReports('open', groupedReport.reports)">{{ $t('reports.reopenAll') }}</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="changeAllReports('closed', groupedReport.reports)">{{ $t('reports.closeAll') }}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<moderate-user-dropdown :account="groupedReport.account"/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<el-divider class="divider"/>
|
||||
<span class="report-row-key">{{ $t('reports.account') }}:</span>
|
||||
<img
|
||||
:src="groupedReport.account.avatar"
|
||||
alt="avatar"
|
||||
class="avatar-img">
|
||||
<a :href="groupedReport.account.url" target="_blank">
|
||||
<span>{{ groupedReport.account.nickname }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<el-divider class="divider"/>
|
||||
<span class="report-row-key">{{ $t('reports.actors') }}:</span>
|
||||
<span v-for="(actor, index) in groupedReport.actors" :key="actor.id">
|
||||
<a :href="actor.url" target="_blank">
|
||||
{{ actor.acct }}<span v-if="index < groupedReport.actors.length - 1">, </span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="groupedReport.status">
|
||||
<el-divider class="divider"/>
|
||||
<span class="report-row-key">{{ $t('reports.reportedStatus') }}:</span>
|
||||
<status :status="groupedReport.status" :show-checkbox="false" class="reported-status"/>
|
||||
</div>
|
||||
<div v-if="groupedReport.reports">
|
||||
<el-collapse>
|
||||
<el-collapse-item :title="$t('reports.reports')">
|
||||
<report-card :reports="groupedReport.reports"/>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import ModerateUserDropdown from './ModerateUserDropdown'
|
||||
import ReportCard from './ReportCard'
|
||||
import Status from '@/components/Status'
|
||||
|
||||
export default {
|
||||
name: 'Report',
|
||||
components: { ModerateUserDropdown, ReportCard, Status },
|
||||
props: {
|
||||
groupedReports: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeAllReports(reportState, groupOfReports) {
|
||||
const reportsData = groupOfReports.map(report => {
|
||||
return { id: report.id, state: reportState }
|
||||
})
|
||||
this.$store.dispatch('ChangeReportState', reportsData)
|
||||
},
|
||||
parseTimestamp(timestamp) {
|
||||
return moment(timestamp).format('L HH:mm')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel='stylesheet/scss' lang='scss'>
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.avatar-img {
|
||||
vertical-align: bottom;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.el-card__body {
|
||||
padding: 17px;
|
||||
}
|
||||
.el-card__header {
|
||||
background-color: #FAFAFA;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
.el-icon-arrow-right {
|
||||
margin-right: 6px;
|
||||
}
|
||||
.grouped-report {
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
.line {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
border: 0.5px solid #EBEEF5;
|
||||
margin: 15px 0 15px;
|
||||
}
|
||||
.report-title {
|
||||
margin: 0;
|
||||
}
|
||||
.report-row-key {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.reports-timeline {
|
||||
margin: 30px 45px 45px 19px;
|
||||
padding: 0px;
|
||||
}
|
||||
.reported-status {
|
||||
margin-top: 15px;
|
||||
}
|
||||
@media
|
||||
only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
.grouped-report {
|
||||
.header-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
height: auto;
|
||||
}
|
||||
.report-actions-button {
|
||||
margin: 3px 0 6px;
|
||||
}
|
||||
.report-title {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -97,9 +97,8 @@ export default {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@media
|
||||
only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.el-card__header {
|
||||
padding: 10px 17px;
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
v-model="notes[report.id]"
|
||||
:placeholder="$t('reports.leaveNote')"
|
||||
type="textarea"
|
||||
rows="3"/>
|
||||
rows="2"/>
|
||||
<div class="report-post-note">
|
||||
<el-button @click="handleNewNote(report.id)">{{ $t('reports.postNote') }}</el-button>
|
||||
</div>
|
||||
|
@ -180,6 +180,9 @@ export default {
|
|||
height: 15px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.divider {
|
||||
margin: 15px 0;
|
||||
}
|
||||
.el-card__body {
|
||||
padding: 17px;
|
||||
}
|
||||
|
@ -279,9 +282,8 @@ export default {
|
|||
font-style: italic;
|
||||
color: gray;
|
||||
}
|
||||
@media
|
||||
only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.report {
|
||||
.header-container {
|
||||
display: flex;
|
||||
|
@ -303,5 +305,11 @@ export default {
|
|||
margin-bottom: 7px;
|
||||
}
|
||||
}
|
||||
.reports-timeline {
|
||||
margin: 20px 10px;
|
||||
.el-timeline-item__wrapper {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -108,9 +108,8 @@ export default {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@media
|
||||
only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.el-card__header {
|
||||
padding: 10px 17px;
|
||||
}
|
||||
|
|
|
@ -54,12 +54,17 @@ export default {
|
|||
.select-field {
|
||||
width: 350px;
|
||||
}
|
||||
@media
|
||||
only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.select-field {
|
||||
width: 100%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:801px) and (min-width: 481px) {
|
||||
.select-field {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,22 +1,14 @@
|
|||
<template>
|
||||
<div class="reports-container">
|
||||
<h1 v-if="groupReports">
|
||||
{{ $t('reports.groupedReports') }}
|
||||
<span class="report-count">({{ normalizedReportsCount }})</span>
|
||||
</h1>
|
||||
<h1 v-else>
|
||||
<h1>
|
||||
{{ $t('reports.reports') }}
|
||||
<span class="report-count">({{ normalizedReportsCount }})</span>
|
||||
</h1>
|
||||
<div class="reports-filter-container">
|
||||
<reports-filter v-if="!groupReports"/>
|
||||
<el-checkbox v-model="groupReports" class="group-reports-checkbox">
|
||||
Group reports by statuses
|
||||
</el-checkbox>
|
||||
<reports-filter/>
|
||||
</div>
|
||||
<div class="block">
|
||||
<grouped-report v-loading="loading" v-if="groupReports" :grouped-reports="groupedReports"/>
|
||||
<report v-loading="loading" v-else :reports="reports"/>
|
||||
<report v-loading="loading" :reports="reports"/>
|
||||
<div v-if="reports.length === 0" class="no-reports-message">
|
||||
<p>There are no reports to display</p>
|
||||
</div>
|
||||
|
@ -25,32 +17,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import GroupedReport from './components/GroupedReport'
|
||||
import numeral from 'numeral'
|
||||
import Report from './components/Report'
|
||||
import ReportsFilter from './components/ReportsFilter'
|
||||
|
||||
export default {
|
||||
components: { GroupedReport, Report, ReportsFilter },
|
||||
components: { Report, ReportsFilter },
|
||||
computed: {
|
||||
groupedReports() {
|
||||
return this.$store.state.reports.fetchedGroupedReports
|
||||
},
|
||||
groupReports: {
|
||||
get() {
|
||||
return this.$store.state.reports.groupReports
|
||||
},
|
||||
set() {
|
||||
this.toggleReportsGrouping()
|
||||
}
|
||||
},
|
||||
loading() {
|
||||
return this.$store.state.reports.loading
|
||||
},
|
||||
normalizedReportsCount() {
|
||||
return this.groupReports
|
||||
? numeral(this.$store.state.reports.fetchedGroupedReports.length).format('0a')
|
||||
: numeral(this.$store.state.reports.totalReportsCount).format('0a')
|
||||
return numeral(this.$store.state.reports.totalReportsCount).format('0a')
|
||||
},
|
||||
reports() {
|
||||
return this.$store.state.reports.fetchedReports
|
||||
|
@ -58,12 +36,6 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('FetchReports', 1)
|
||||
this.$store.dispatch('FetchGroupedReports')
|
||||
},
|
||||
methods: {
|
||||
toggleReportsGrouping() {
|
||||
this.$store.dispatch('ToggleReportsGrouping')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -77,9 +49,6 @@ export default {
|
|||
margin: 22px 15px 22px 15px;
|
||||
padding-bottom: 0
|
||||
}
|
||||
.group-reports-checkbox {
|
||||
margin-top: 10px;
|
||||
}
|
||||
h1 {
|
||||
margin: 22px 0 0 15px;
|
||||
}
|
||||
|
@ -92,9 +61,8 @@ export default {
|
|||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
@media
|
||||
only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.reports-container {
|
||||
h1 {
|
||||
margin: 7px 10px 15px 10px;
|
||||
|
@ -103,8 +71,5 @@ only screen and (max-width: 760px),
|
|||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
#app > div > div.main-container > section > div > div.block > ul {
|
||||
margin: 45px 45px 5px 19px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
<el-form ref="frontendData" :model="frontendData" :label-width="labelWidth">
|
||||
<setting :setting-group="frontend" :data="frontendData"/>
|
||||
</el-form>
|
||||
<el-form ref="staticFeData" :model="staticFeData" :label-width="labelWidth">
|
||||
<setting :setting-group="staticFe" :data="staticFeData"/>
|
||||
</el-form>
|
||||
<el-divider class="divider thick-line"/>
|
||||
<el-form ref="assetsData" :model="assetsData" :label-width="labelWidth">
|
||||
<el-form-item class="grouped-settings-header">
|
||||
<span class="label-font">{{ $t('settings.assets') }}</span>
|
||||
|
@ -70,12 +74,6 @@ export default {
|
|||
frontendData() {
|
||||
return _.get(this.settings.settings, [':pleroma', ':frontend_configurations']) || {}
|
||||
},
|
||||
markup() {
|
||||
return this.settings.description.find(setting => setting.key === ':markup')
|
||||
},
|
||||
markupData() {
|
||||
return _.get(this.settings.settings, [':pleroma', ':markup']) || {}
|
||||
},
|
||||
isMobile() {
|
||||
return this.$store.state.app.device === 'mobile'
|
||||
},
|
||||
|
@ -93,6 +91,18 @@ export default {
|
|||
},
|
||||
loading() {
|
||||
return this.settings.loading
|
||||
},
|
||||
markup() {
|
||||
return this.settings.description.find(setting => setting.key === ':markup')
|
||||
},
|
||||
markupData() {
|
||||
return _.get(this.settings.settings, [':pleroma', ':markup']) || {}
|
||||
},
|
||||
staticFe() {
|
||||
return this.settings.description.find(setting => setting.key === ':static_fe')
|
||||
},
|
||||
staticFeData() {
|
||||
return _.get(this.settings.settings, [':pleroma', ':static_fe']) || {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
<el-form ref="httpData" :model="httpData" :label-width="labelWidth">
|
||||
<setting :setting-group="http" :data="httpData"/>
|
||||
</el-form>
|
||||
<el-form ref="teslaAdapter" :model="teslaAdapterData" :label-width="labelWidth">
|
||||
<setting :setting-group="teslaAdapter" :data="teslaAdapterData"/>
|
||||
</el-form>
|
||||
<el-divider class="divider thick-line"/>
|
||||
<el-form ref="corsPlugData" :model="corsPlugData" :label-width="labelWidth">
|
||||
<el-form-item class="grouped-settings-header">
|
||||
<span class="label-font">{{ $t('settings.corsPlug') }}</span>
|
||||
|
@ -86,12 +82,6 @@ export default {
|
|||
loading() {
|
||||
return this.settings.loading
|
||||
},
|
||||
teslaAdapter() {
|
||||
return this.settings.description.find(setting => setting.group === ':tesla')
|
||||
},
|
||||
teslaAdapterData() {
|
||||
return _.get(this.settings.settings, [':tesla']) || {}
|
||||
},
|
||||
webCacheTtl() {
|
||||
return this.settings.description.find(setting => setting.key === ':web_cache_ttl')
|
||||
},
|
||||
|
|
|
@ -95,11 +95,12 @@
|
|||
</el-input>
|
||||
<!-- special inputs -->
|
||||
<auto-linker-input v-if="settingGroup.group === ':auto_linker'" :data="data" :setting-group="settingGroup" :setting="setting"/>
|
||||
<mascots-input v-if="setting.key === ':mascots'" :data="keywordData" :setting-group="settingGroup" :setting="setting"/>
|
||||
<crontab-input v-if="setting.key === ':crontab'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting"/>
|
||||
<editable-keyword-input v-if="editableKeyword(setting.key, setting.type)" :data="keywordData" :setting-group="settingGroup" :setting="setting"/>
|
||||
<icons-input v-if="setting.key === ':icons'" :data="iconsData" :setting-group="settingGroup" :setting="setting"/>
|
||||
<proxy-url-input v-if="setting.key === ':proxy_url'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting" :parents="settingParent"/>
|
||||
<mascots-input v-if="setting.key === ':mascots'" :data="keywordData" :setting-group="settingGroup" :setting="setting"/>
|
||||
<multiple-select v-if="setting.key === ':backends' || setting.key === ':args'" :data="data" :setting-group="settingGroup" :setting="setting"/>
|
||||
<proxy-url-input v-if="setting.key === ':proxy_url'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting" :parents="settingParent"/>
|
||||
<prune-input v-if="setting.key === ':prune'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting"/>
|
||||
<rate-limit-input v-if="settingGroup.key === ':rate_limit'" :data="data" :setting-group="settingGroup" :setting="setting"/>
|
||||
<!-------------------->
|
||||
|
@ -117,7 +118,7 @@
|
|||
|
||||
<script>
|
||||
import i18n from '@/lang'
|
||||
import { AutoLinkerInput, EditableKeywordInput, IconsInput, MascotsInput, MultipleSelect, ProxyUrlInput, PruneInput, RateLimitInput } from './inputComponents'
|
||||
import { AutoLinkerInput, CrontabInput, EditableKeywordInput, IconsInput, MascotsInput, MultipleSelect, ProxyUrlInput, PruneInput, RateLimitInput } from './inputComponents'
|
||||
import { processNested } from '@/store/modules/normalizers'
|
||||
import _ from 'lodash'
|
||||
import marked from 'marked'
|
||||
|
@ -126,6 +127,7 @@ export default {
|
|||
name: 'Inputs',
|
||||
components: {
|
||||
AutoLinkerInput,
|
||||
CrontabInput,
|
||||
EditableKeywordInput,
|
||||
IconsInput,
|
||||
MascotsInput,
|
||||
|
@ -198,7 +200,7 @@ export default {
|
|||
return Array.isArray(this.data[':icons']) ? this.data[':icons'] : []
|
||||
},
|
||||
inputValue() {
|
||||
if ([':esshd', ':cors_plug', ':quack', ':http_signatures', ':tesla'].includes(this.settingGroup.group) &&
|
||||
if ([':esshd', ':cors_plug', ':quack', ':http_signatures', ':tesla', ':swoosh'].includes(this.settingGroup.group) &&
|
||||
this.data[this.setting.key]) {
|
||||
return this.setting.type === 'atom' && this.data[this.setting.key].value[0] === ':'
|
||||
? this.data[this.setting.key].value.substr(1)
|
||||
|
@ -249,8 +251,8 @@ export default {
|
|||
methods: {
|
||||
editableKeyword(key, type) {
|
||||
return key === ':replace' ||
|
||||
(Array.isArray(type) && type.includes('keyword') && type.includes('integer')) ||
|
||||
type === 'map' ||
|
||||
(Array.isArray(type) && type.includes('keyword') && type.includes('integer')) ||
|
||||
(Array.isArray(type) && type.includes('keyword') && type.findIndex(el => el.includes('list') && el.includes('string')) !== -1)
|
||||
},
|
||||
getFormattedDescription(desc) {
|
||||
|
|
|
@ -23,6 +23,17 @@
|
|||
<el-form ref="pleromaUser" :model="pleromaUserData" :label-width="labelWidth">
|
||||
<setting :setting-group="pleromaUser" :data="pleromaUserData"/>
|
||||
</el-form>
|
||||
<el-divider class="divider thick-line"/>
|
||||
<el-form ref="uriSchemes" :model="uriSchemesData" :label-width="labelWidth">
|
||||
<setting :setting-group="uriSchemes" :data="uriSchemesData"/>
|
||||
</el-form>
|
||||
<el-divider class="divider thick-line"/>
|
||||
<el-form ref="feed" :model="feedData" :label-width="labelWidth">
|
||||
<setting :setting-group="feed" :data="feedData"/>
|
||||
</el-form>
|
||||
<el-form ref="streamer" :model="streamerData" :label-width="labelWidth">
|
||||
<setting :setting-group="streamer" :data="streamerData"/>
|
||||
</el-form>
|
||||
<div class="submit-button-container">
|
||||
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
|
||||
</div>
|
||||
|
@ -50,6 +61,12 @@ export default {
|
|||
adminTokenData() {
|
||||
return _.get(this.settings.settings, [':pleroma', ':admin_token']) || {}
|
||||
},
|
||||
feed() {
|
||||
return this.settings.description.find(setting => setting.key === ':feed')
|
||||
},
|
||||
feedData() {
|
||||
return _.get(this.settings.settings, [':pleroma', ':feed']) || {}
|
||||
},
|
||||
fetchInitialPosts() {
|
||||
return this.settings.description.find(setting => setting.key === ':fetch_initial_posts')
|
||||
},
|
||||
|
@ -97,6 +114,18 @@ export default {
|
|||
},
|
||||
scheduledActivityData() {
|
||||
return _.get(this.settings.settings, [':pleroma', 'Pleroma.ScheduledActivity']) || {}
|
||||
},
|
||||
streamer() {
|
||||
return this.$store.state.settings.description.find(setting => setting.key === ':streamer')
|
||||
},
|
||||
streamerData() {
|
||||
return _.get(this.settings.settings, [':pleroma', ':streamer']) || {}
|
||||
},
|
||||
uriSchemes() {
|
||||
return this.settings.description.find(setting => setting.key === ':uri_schemes')
|
||||
},
|
||||
uriSchemesData() {
|
||||
return _.get(this.settings.settings, [':pleroma', ':uri_schemes']) || {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -29,6 +29,14 @@
|
|||
<el-form ref="mrfVocabulary" :model="mrfVocabularyData" :label-width="labelWidth">
|
||||
<setting :setting-group="mrfVocabulary" :data="mrfVocabularyData"/>
|
||||
</el-form>
|
||||
<el-divider class="divider thick-line"/>
|
||||
<el-form ref="mrfObjectAge" :model="mrfObjectAgeData" :label-width="labelWidth">
|
||||
<setting :setting-group="mrfObjectAge" :data="mrfObjectAgeData"/>
|
||||
</el-form>
|
||||
<el-divider class="divider thick-line"/>
|
||||
<el-form ref="modules" :model="modulesData" :label-width="labelWidth">
|
||||
<setting :setting-group="modules" :data="modulesData"/>
|
||||
</el-form>
|
||||
<div class="submit-button-container">
|
||||
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
|
||||
</div>
|
||||
|
@ -66,6 +74,12 @@ export default {
|
|||
loading() {
|
||||
return this.settings.loading
|
||||
},
|
||||
modules() {
|
||||
return this.settings.description.find(setting => setting.key === ':modules')
|
||||
},
|
||||
modulesData() {
|
||||
return _.get(this.settings.settings, [':pleroma', ':modules']) || {}
|
||||
},
|
||||
mrfSimple() {
|
||||
return this.settings.description.find(setting => setting.key === ':mrf_simple')
|
||||
},
|
||||
|
@ -90,6 +104,12 @@ export default {
|
|||
mrfKeywordData() {
|
||||
return _.get(this.settings.settings, [':pleroma', ':mrf_keyword']) || {}
|
||||
},
|
||||
mrfObjectAge() {
|
||||
return this.settings.description.find(setting => setting.key === ':mrf_object_age')
|
||||
},
|
||||
mrfObjectAgeData() {
|
||||
return _.get(this.settings.settings, [':pleroma', ':mrf_object_age']) || {}
|
||||
},
|
||||
mrfSubchain() {
|
||||
return this.settings.description.find(setting => setting.key === ':mrf_subchain')
|
||||
},
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
<setting :setting-group="mailer" :data="mailerData"/>
|
||||
</el-form>
|
||||
<el-divider class="divider thick-line"/>
|
||||
<el-form ref="swoosh" :model="swooshData" :label-width="labelWidth">
|
||||
<setting :setting-group="swoosh" :data="swooshData"/>
|
||||
</el-form>
|
||||
<el-divider class="divider thick-line"/>
|
||||
<el-form ref="emailNotifications" :model="emailNotificationsData" :label-width="labelWidth">
|
||||
<setting :setting-group="emailNotifications" :data="emailNotificationsData"/>
|
||||
</el-form>
|
||||
|
@ -61,6 +65,12 @@ export default {
|
|||
mailerData() {
|
||||
return _.get(this.settings.settings, [':pleroma', 'Pleroma.Emails.Mailer']) || {}
|
||||
},
|
||||
swoosh() {
|
||||
return this.settings.description.find(setting => setting.group === ':swoosh')
|
||||
},
|
||||
swooshData() {
|
||||
return _.get(this.settings.settings, [':swoosh']) || {}
|
||||
},
|
||||
userEmail() {
|
||||
return this.settings.description.find(setting => setting.key === 'Pleroma.Emails.UserEmail')
|
||||
},
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
<template>
|
||||
<el-form :label-width="labelWidth" :label-position="isMobile ? 'top' : 'right'" class="crontab">
|
||||
<el-form-item v-for="worker in workers" :key="worker" :label="worker" class="crontab-container">
|
||||
<el-input
|
||||
:value="data[worker]"
|
||||
:placeholder="getSuggestion(worker) || null"
|
||||
class="input setting-input"
|
||||
@input="update($event, worker)"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CrontabInput',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
setting: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
settingGroup: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isDesktop() {
|
||||
return this.$store.state.app.device === 'desktop'
|
||||
},
|
||||
isMobile() {
|
||||
return this.$store.state.app.device === 'mobile'
|
||||
},
|
||||
isTablet() {
|
||||
return this.$store.state.app.device === 'tablet'
|
||||
},
|
||||
labelWidth() {
|
||||
if (this.isMobile) {
|
||||
return '100%'
|
||||
} else {
|
||||
return '380px'
|
||||
}
|
||||
},
|
||||
workers() {
|
||||
return this.setting.suggestions.map(worker => worker[1])
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getSuggestion(worker) {
|
||||
return this.setting.suggestions.find(suggestion => suggestion[1] === worker)[0]
|
||||
},
|
||||
update(value, worker) {
|
||||
const currentValue = this.$store.state.settings.settings[this.settingGroup.group][this.settingGroup.key][this.setting.key]
|
||||
const updatedValue = { ...currentValue, [worker]: value }
|
||||
const updatedValueWithType = Object.keys(currentValue).reduce((acc, key) => {
|
||||
if (key === worker) {
|
||||
return { ...acc, [key]: ['reversed_tuple', value] }
|
||||
} else {
|
||||
return { ...acc, [key]: ['reversed_tuple', currentValue[key]] }
|
||||
}
|
||||
}, {})
|
||||
|
||||
this.$store.dispatch('UpdateSettings',
|
||||
{ group: this.settingGroup.group, key: this.settingGroup.key, input: this.setting.key, value: updatedValueWithType, type: this.setting.type }
|
||||
)
|
||||
this.$store.dispatch('UpdateState',
|
||||
{ group: this.settingGroup.group, key: this.settingGroup.key, input: this.setting.key, value: updatedValue }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel='stylesheet/scss' lang='scss'>
|
||||
@import '../../styles/main';
|
||||
@include settings
|
||||
</style>
|
|
@ -96,15 +96,18 @@ export default {
|
|||
this.updateSetting(updatedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
|
||||
},
|
||||
updateSetting(value, group, key, input, type) {
|
||||
const updatedSettings = type !== 'map'
|
||||
? value.reduce((acc, element) => {
|
||||
return { ...acc, [Object.keys(element)[0]]: ['list', Object.values(element)[0].value] }
|
||||
}, {})
|
||||
: value.reduce((acc, element) => {
|
||||
return { ...acc, [Object.keys(element)[0]]: Object.values(element)[0].value }
|
||||
}, {})
|
||||
const updatedSettings = this.wrapUpdatedSettings(value, input, type)
|
||||
this.$store.dispatch('UpdateSettings', { group, key, input, value: updatedSettings, type })
|
||||
this.$store.dispatch('UpdateState', { group, key, input, value })
|
||||
},
|
||||
wrapUpdatedSettings(value, input, type) {
|
||||
return type === 'map'
|
||||
? value.reduce((acc, element) => {
|
||||
return { ...acc, [Object.keys(element)[0]]: Object.values(element)[0].value }
|
||||
}, {})
|
||||
: value.reduce((acc, element) => {
|
||||
return { ...acc, [Object.keys(element)[0]]: ['list', Object.values(element)[0].value] }
|
||||
}, {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export { default as AutoLinkerInput } from './AutoLinkerInput'
|
||||
export { default as EditableKeywordInput } from './EditableKeywordInput'
|
||||
export { default as CrontabInput } from './CrontabInput'
|
||||
export { default as IconsInput } from './IconsInput'
|
||||
export { default as MascotsInput } from './MascotsInput'
|
||||
export { default as MultipleSelect } from './MultipleSelect'
|
||||
|
|
|
@ -339,6 +339,12 @@
|
|||
}
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.crontab {
|
||||
width: 100%;
|
||||
label {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.delete-setting-button {
|
||||
margin: 4px 0 0 5px;
|
||||
height: 28px;
|
||||
|
@ -382,6 +388,10 @@
|
|||
margin: 0;
|
||||
padding: 0 15px 10px 0;
|
||||
}
|
||||
.el-form-item.crontab-container:first-child {
|
||||
margin: 0;
|
||||
padding: 0 ;
|
||||
}
|
||||
.el-form-item:first-child .mascot-form-item {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,15 @@
|
|||
:selected-users="selectedUsers"
|
||||
@apply-action="clearSelection"/>
|
||||
</div>
|
||||
<div v-if="currentInstance" class="checkbox-container">
|
||||
<el-checkbox v-model="showLocal" class="show-private-statuses">
|
||||
{{ $t('statuses.onlyLocalStatuses') }}
|
||||
</el-checkbox>
|
||||
<el-checkbox v-model="showPrivate" class="show-private-statuses">
|
||||
{{ $t('statuses.showPrivateStatuses') }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<p v-if="statuses.length === 0" class="no-statuses">{{ $t('userProfile.noStatuses') }}</p>
|
||||
<div v-for="status in statuses" :key="status.id" class="status-container">
|
||||
<status
|
||||
:status="status"
|
||||
|
@ -30,13 +39,13 @@
|
|||
@status-selection="handleStatusSelection" />
|
||||
</div>
|
||||
<div v-if="statuses.length > 0" class="statuses-pagination">
|
||||
<el-button @click="handleLoadMore">{{ $t('statuses.loadMore') }}</el-button>
|
||||
<el-button v-if="!allLoaded" :loading="buttonLoading" @click="handleLoadMore">{{ $t('statuses.loadMore') }}</el-button>
|
||||
<el-button v-else icon="el-icon-check" circle/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import MultipleUsersMenu from '@/views/users/components/MultipleUsersMenu'
|
||||
import Status from '@/components/Status'
|
||||
|
||||
|
@ -52,10 +61,18 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'instances',
|
||||
'statuses'
|
||||
]),
|
||||
allLoaded() {
|
||||
return this.$store.state.status.statusesByInstance.allLoaded
|
||||
},
|
||||
buttonLoading() {
|
||||
return this.$store.state.status.statusesByInstance.buttonLoading
|
||||
},
|
||||
currentInstance() {
|
||||
return this.selectedInstance === this.$store.state.user.authHost
|
||||
},
|
||||
instances() {
|
||||
return [this.$store.state.user.authHost, ...this.$store.state.peers.fetchedPeers]
|
||||
},
|
||||
isDesktop() {
|
||||
return this.$store.state.app.device === 'desktop'
|
||||
},
|
||||
|
@ -75,6 +92,25 @@ export default {
|
|||
set(instance) {
|
||||
this.$store.dispatch('HandleFilterChange', instance)
|
||||
}
|
||||
},
|
||||
showLocal: {
|
||||
get() {
|
||||
return this.$store.state.status.statusesByInstance.showLocal
|
||||
},
|
||||
set(value) {
|
||||
this.$store.dispatch('HandleLocalCheckboxChange', value)
|
||||
}
|
||||
},
|
||||
showPrivate: {
|
||||
get() {
|
||||
return this.$store.state.status.statusesByInstance.showPrivate
|
||||
},
|
||||
set(value) {
|
||||
this.$store.dispatch('HandleGodmodeCheckboxChange', value)
|
||||
}
|
||||
},
|
||||
statuses() {
|
||||
return this.$store.state.status.fetchedStatuses
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
@ -97,7 +133,6 @@ export default {
|
|||
if (this.selectedUsers.find(selectedUser => user.id === selectedUser.id) !== undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
this.selectedUsers = [...this.selectedUsers, user]
|
||||
}
|
||||
}
|
||||
|
@ -111,6 +146,9 @@ export default {
|
|||
margin: 0 0 10px;
|
||||
}
|
||||
}
|
||||
.checkbox-container {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.filter-container {
|
||||
display: flex;
|
||||
height: 36px;
|
||||
|
@ -129,19 +167,22 @@ h1 {
|
|||
margin: 22px 0 0 0;
|
||||
}
|
||||
|
||||
@media
|
||||
only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
@media only screen and (max-width:480px) {
|
||||
.checkbox-container {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.filter-container {
|
||||
display: flex;
|
||||
height: 36px;
|
||||
flex-direction: column;
|
||||
margin: 10px 10px
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.select-field {
|
||||
width: 100%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.select-instance {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -139,9 +139,8 @@ export default {
|
|||
.create-account-form-item-without-margin {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
@media
|
||||
only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.create-user-dialog {
|
||||
width: 85%
|
||||
}
|
||||
|
|
|
@ -61,12 +61,17 @@ export default {
|
|||
.select-field {
|
||||
width: 350px;
|
||||
}
|
||||
@media
|
||||
only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.select-field {
|
||||
width: 100%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:801px) and (min-width: 481px) {
|
||||
.select-field {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -274,9 +274,8 @@ export default {
|
|||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
@media
|
||||
only screen and (max-width: 760px),
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.password-reset-token-dialog {
|
||||
width: 85%
|
||||
}
|
||||
|
|
|
@ -23,81 +23,73 @@
|
|||
</p>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-card class="user-profile-card">
|
||||
<div class="el-table el-table--fit el-table--enable-row-hover el-table--enable-row-transition el-table--medium">
|
||||
<table class="user-profile-table">
|
||||
<tbody>
|
||||
<tr class="el-table__row">
|
||||
<td>{{ $t('userProfile.nickname') }}</td>
|
||||
<td>
|
||||
{{ user.nickname }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="el-table__row">
|
||||
<td class="name-col">ID</td>
|
||||
<td class="value-col">
|
||||
{{ user.id }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="el-table__row">
|
||||
<td>{{ $t('userProfile.tags') }}</td>
|
||||
<td>
|
||||
<el-tag v-for="tag in user.tags" :key="tag" class="user-profile-tag">{{ tag }}</el-tag>
|
||||
<span v-if="user.tags.length === 0">—</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="el-table__row">
|
||||
<td>{{ $t('userProfile.roles') }}</td>
|
||||
<td>
|
||||
<el-tag v-if="user.roles.admin" class="user-profile-tag">
|
||||
{{ $t('users.admin') }}
|
||||
</el-tag>
|
||||
<el-tag v-if="user.roles.moderator" class="user-profile-tag">
|
||||
{{ $t('users.moderator') }}
|
||||
</el-tag>
|
||||
<span v-if="!user.roles.moderator && !user.roles.admin">—</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="el-table__row">
|
||||
<td>{{ $t('userProfile.localUppercase') }}</td>
|
||||
<td>
|
||||
<el-tag v-if="user.local" type="info">{{ $t('userProfile.local') }}</el-tag>
|
||||
<el-tag v-if="!user.local" type="info">{{ $t('userProfile.external') }}</el-tag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="el-table__row">
|
||||
<td>{{ $t('userProfile.activeUppercase') }}</td>
|
||||
<td>
|
||||
<el-tag v-if="!user.deactivated" type="success">{{ $t('userProfile.active') }}</el-tag>
|
||||
<el-tag v-if="user.deactivated" type="danger">{{ $t('userProfile.deactivated') }}</el-tag>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-row type="flex" class="row-bg" justify="space-between">
|
||||
<el-col :span="18">
|
||||
<h2 class="recent-statuses">{{ $t('userProfile.recentStatuses') }}</h2>
|
||||
</el-col>
|
||||
<el-col :span="6" class="show-private">
|
||||
<el-checkbox v-model="showPrivate" @change="onTogglePrivate">
|
||||
{{ $t('userProfile.showPrivateStatuses') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-col :span="16">
|
||||
<div class="user-profile-container">
|
||||
<el-card class="user-profile-card">
|
||||
<div class="el-table el-table--fit el-table--enable-row-hover el-table--enable-row-transition el-table--medium">
|
||||
<table class="user-profile-table">
|
||||
<tbody>
|
||||
<tr class="el-table__row">
|
||||
<td>{{ $t('userProfile.nickname') }}</td>
|
||||
<td>
|
||||
{{ user.nickname }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="el-table__row">
|
||||
<td class="name-col">ID</td>
|
||||
<td class="value-col">
|
||||
{{ user.id }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="el-table__row">
|
||||
<td>{{ $t('userProfile.tags') }}</td>
|
||||
<td>
|
||||
<el-tag v-for="tag in user.tags" :key="tag" class="user-profile-tag">{{ tag }}</el-tag>
|
||||
<span v-if="user.tags.length === 0">—</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="el-table__row">
|
||||
<td>{{ $t('userProfile.roles') }}</td>
|
||||
<td>
|
||||
<el-tag v-if="user.roles.admin" class="user-profile-tag">
|
||||
{{ $t('users.admin') }}
|
||||
</el-tag>
|
||||
<el-tag v-if="user.roles.moderator" class="user-profile-tag">
|
||||
{{ $t('users.moderator') }}
|
||||
</el-tag>
|
||||
<span v-if="!user.roles.moderator && !user.roles.admin">—</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="el-table__row">
|
||||
<td>{{ $t('userProfile.localUppercase') }}</td>
|
||||
<td>
|
||||
<el-tag v-if="user.local" type="info">{{ $t('userProfile.local') }}</el-tag>
|
||||
<el-tag v-if="!user.local" type="info">{{ $t('userProfile.external') }}</el-tag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="el-table__row">
|
||||
<td>{{ $t('userProfile.activeUppercase') }}</td>
|
||||
<td>
|
||||
<el-tag v-if="!user.deactivated" type="success">{{ $t('userProfile.active') }}</el-tag>
|
||||
<el-tag v-if="user.deactivated" type="danger">{{ $t('userProfile.deactivated') }}</el-tag>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
<div class="recent-statuses-container">
|
||||
<h2 class="recent-statuses">{{ $t('userProfile.recentStatuses') }}</h2>
|
||||
<el-checkbox v-model="showPrivate" class="show-private-statuses" @change="onTogglePrivate">
|
||||
{{ $t('statuses.showPrivateStatuses') }}
|
||||
</el-checkbox>
|
||||
<el-timeline v-if="!statusesLoading" class="statuses">
|
||||
<el-timeline-item v-for="status in statuses" :key="status.id">
|
||||
<status :status="status" :show-checkbox="false" :user-id="user.id" :godmode="showPrivate"/>
|
||||
</el-timeline-item>
|
||||
<p v-if="statuses.length === 0" class="no-statuses">{{ $t('userProfile.noStatuses') }}</p>
|
||||
</el-timeline>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
|
@ -193,6 +185,11 @@ table {
|
|||
margin-left: 28px;
|
||||
color: #606266;
|
||||
}
|
||||
.recent-statuses-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 67%;
|
||||
}
|
||||
.recent-statuses-header {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
@ -205,6 +202,10 @@ table {
|
|||
line-height: 67px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.show-private-statuses {
|
||||
margin-left: 28px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.recent-statuses {
|
||||
margin-left: 28px;
|
||||
}
|
||||
|
@ -218,6 +219,11 @@ table {
|
|||
}
|
||||
.user-profile-card {
|
||||
margin: 0 20px;
|
||||
width: 30%;
|
||||
height: fit-content;
|
||||
}
|
||||
.user-profile-container {
|
||||
display: flex;
|
||||
}
|
||||
.user-profile-table {
|
||||
margin: 0;
|
||||
|
@ -225,4 +231,65 @@ table {
|
|||
.user-profile-tag {
|
||||
margin: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.avatar-name-container {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.recent-statuses {
|
||||
margin: 20px 10px 15px 10px;
|
||||
}
|
||||
.recent-statuses-container {
|
||||
width: 100%;
|
||||
margin: 0 10px;
|
||||
}
|
||||
.show-private-statuses {
|
||||
margin: 0 10px 20px 10px;
|
||||
}
|
||||
.user-page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 0;
|
||||
margin: 7px 0 15px 10px;
|
||||
}
|
||||
.user-profile-card {
|
||||
margin: 0 10px;
|
||||
width: 95%;
|
||||
td {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
.user-profile-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:801px) and (min-width: 481px) {
|
||||
.avatar-name-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.recent-statuses {
|
||||
margin: 20px 10px 15px 0;
|
||||
}
|
||||
.recent-statuses-container {
|
||||
width: 97%;
|
||||
margin: 0 20px;
|
||||
}
|
||||
.show-private-statuses {
|
||||
margin: 0 10px 20px 0;
|
||||
}
|
||||
.user-page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 0;
|
||||
margin: 7px 0 20px 20px;
|
||||
}
|
||||
.user-profile-card {
|
||||
margin: 0 20px;
|
||||
width: fit-content;
|
||||
}
|
||||
.user-profile-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
import Vuex from 'vuex'
|
||||
import { mount, createLocalVue, config } from '@vue/test-utils'
|
||||
import Element from 'element-ui'
|
||||
import GroupedReport from '@/views/reports/components/GroupedReport'
|
||||
import storeConfig from './store.conf'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import flushPromises from 'flush-promises'
|
||||
|
||||
config.mocks["$t"] = () => {}
|
||||
|
||||
const localVue = createLocalVue()
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Element)
|
||||
|
||||
jest.mock('@/api/reports')
|
||||
|
||||
describe('Grouped report', () => {
|
||||
let store
|
||||
|
||||
beforeEach(async() => {
|
||||
store = new Vuex.Store(cloneDeep(storeConfig))
|
||||
store.dispatch('FetchGroupedReports')
|
||||
await flushPromises()
|
||||
})
|
||||
|
||||
it('changes state of all reports in a group', async (done) => {
|
||||
const groupedReports = store.state.reports.fetchedGroupedReports
|
||||
const wrapper = mount(GroupedReport, {
|
||||
store,
|
||||
localVue,
|
||||
propsData: {
|
||||
groupedReports
|
||||
}
|
||||
})
|
||||
|
||||
expect(groupedReports[0].reports[0].state).toBe('open')
|
||||
expect(groupedReports[0].reports[1].state).toBe('resolved')
|
||||
|
||||
const button = wrapper.find(`.grouped-report .el-dropdown-menu__item:nth-child(3)`)
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(store.state.reports.fetchedGroupedReports[0].reports[0].state).toBe('closed')
|
||||
expect(store.state.reports.fetchedGroupedReports[0].reports[1].state).toBe('closed')
|
||||
done()
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue