Merge branch 'develop' into 'feature/cache-invalidation'
# Conflicts: # CHANGELOG.md # src/views/settings/components/Inputs.vue # src/views/settings/components/tabs.js
This commit is contained in:
commit
fdb2b6d257
28 changed files with 478 additions and 225 deletions
|
@ -19,6 +19,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Add ability to manually evict and ban URLs from the Pleroma MediaProxy cache
|
- Add ability to manually evict and ban URLs from the Pleroma MediaProxy cache
|
||||||
- Add Invalidation settings on MediaProxy tab
|
- Add Invalidation settings on MediaProxy tab
|
||||||
- Ability to configure S3 settings on Upload tab
|
- Ability to configure S3 settings on Upload tab
|
||||||
|
- Show number of open reports in Sidebar Menu
|
||||||
|
- Add confirmation message when deleting a user
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -32,6 +34,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Ability to add custom values in Pleroma.Upload.Filter.Mogrify setting
|
- Ability to add custom values in Pleroma.Upload.Filter.Mogrify setting
|
||||||
- Change types of the following settings: ':groups', ':replace', ':federated_timeline_removal', ':reject', ':match_actor'. Update functions that parses and wraps settings data according to this change.
|
- Change types of the following settings: ':groups', ':replace', ':federated_timeline_removal', ':reject', ':match_actor'. Update functions that parses and wraps settings data according to this change.
|
||||||
- Move rendering Crontab setting from a separate component to EditableKeyword component
|
- Move rendering Crontab setting from a separate component to EditableKeyword component
|
||||||
|
- Show only those MRF settings that have been enabled in MRF Policies setting
|
||||||
|
- Move Auto Linker settings to Link Formatter Tab as its configuration was moved to :pleroma, Pleroma.Formatter
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -43,6 +47,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Link settings that enable registrations and invites
|
- Link settings that enable registrations and invites
|
||||||
|
- Ability to upload logo, background, default user avatar, instance thumbnail, and NSFW hiding images
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
19
src/api/mediaUpload.js
Normal file
19
src/api/mediaUpload.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { getToken } from '@/utils/auth'
|
||||||
|
import { baseName } from './utils'
|
||||||
|
|
||||||
|
const UPLOAD_URL = '/api/v1/media'
|
||||||
|
|
||||||
|
export function uploadMedia({ formData, authHost }) {
|
||||||
|
const url = baseName(authHost) + UPLOAD_URL
|
||||||
|
|
||||||
|
return fetch(url, {
|
||||||
|
body: formData,
|
||||||
|
method: 'POST',
|
||||||
|
headers: authHeaders()
|
||||||
|
})
|
||||||
|
.then((data) => data.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
const authHeaders = () => {
|
||||||
|
return { 'Authorization': `Bearer ${getToken()}` }
|
||||||
|
}
|
|
@ -228,6 +228,7 @@ export default {
|
||||||
revokeRightConfirmation: 'Are you sure you want to revoke {right} rights from all selected users?',
|
revokeRightConfirmation: 'Are you sure you want to revoke {right} rights from all selected users?',
|
||||||
activateMultipleUsersConfirmation: 'Are you sure you want to activate accounts of all selected users?',
|
activateMultipleUsersConfirmation: 'Are you sure you want to activate accounts of all selected users?',
|
||||||
deactivateMultipleUsersConfirmation: 'Are you sure you want to deactivate accounts of all selected users?',
|
deactivateMultipleUsersConfirmation: 'Are you sure you want to deactivate accounts of all selected users?',
|
||||||
|
deleteUsersConfirmation: 'Are you sure you want to delete this account? This action cannot be undone.',
|
||||||
deleteMultipleUsersConfirmation: 'Are you sure you want to delete accounts of all selected users?',
|
deleteMultipleUsersConfirmation: 'Are you sure you want to delete accounts of all selected users?',
|
||||||
addTagForMultipleUsersConfirmation: 'Are you sure you want to apply tag to all selected users?',
|
addTagForMultipleUsersConfirmation: 'Are you sure you want to apply tag to all selected users?',
|
||||||
removeTagFromMultipleUsersConfirmation: 'Are you sure you want to remove tag from all selected users?',
|
removeTagFromMultipleUsersConfirmation: 'Are you sure you want to remove tag from all selected users?',
|
||||||
|
@ -373,10 +374,10 @@ export default {
|
||||||
instance: 'Instance',
|
instance: 'Instance',
|
||||||
upload: 'Upload',
|
upload: 'Upload',
|
||||||
mailer: 'Mailer',
|
mailer: 'Mailer',
|
||||||
|
linkFormatter: 'Link Formatter',
|
||||||
logger: 'Logger',
|
logger: 'Logger',
|
||||||
activityPub: 'ActivityPub',
|
activityPub: 'ActivityPub',
|
||||||
auth: 'Authentication',
|
auth: 'Authentication',
|
||||||
autoLinker: 'Auto Linker',
|
|
||||||
captcha: 'Captcha',
|
captcha: 'Captcha',
|
||||||
frontend: 'Frontend',
|
frontend: 'Frontend',
|
||||||
http: 'HTTP',
|
http: 'HTTP',
|
||||||
|
@ -407,7 +408,10 @@ export default {
|
||||||
instanceReboot: 'Reboot Instance',
|
instanceReboot: 'Reboot Instance',
|
||||||
restartApp: 'You must restart the instance to apply settings',
|
restartApp: 'You must restart the instance to apply settings',
|
||||||
restartSuccess: 'Instance rebooted successfully!',
|
restartSuccess: 'Instance rebooted successfully!',
|
||||||
removeSettingConfirmation: 'Are you sure you want to remove this setting\'s value from the database?'
|
removeSettingConfirmation: 'Are you sure you want to remove this setting\'s value from the database?',
|
||||||
|
changeImage: 'Change image',
|
||||||
|
uploadImage: 'Upload image',
|
||||||
|
remove: 'Remove'
|
||||||
},
|
},
|
||||||
invites: {
|
invites: {
|
||||||
inviteTokens: 'Invite tokens',
|
inviteTokens: 'Invite tokens',
|
||||||
|
|
|
@ -9,28 +9,6 @@ export const getBooleanValue = value => {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkPartialUpdate = (settings, updatedSettings, description) => {
|
|
||||||
return Object.keys(updatedSettings).reduce((acc, group) => {
|
|
||||||
acc[group] = Object.keys(updatedSettings[group]).reduce((acc, key) => {
|
|
||||||
if (!partialUpdate(group, key)) {
|
|
||||||
const updated = Object.keys(settings[group][key]).reduce((acc, settingName) => {
|
|
||||||
const setting = description
|
|
||||||
.find(element => element.group === group && element.key === key).children
|
|
||||||
.find(child => child.key === settingName)
|
|
||||||
const type = setting ? setting.type : ''
|
|
||||||
acc[settingName] = [type, settings[group][key][settingName]]
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
acc[key] = updated
|
|
||||||
return acc
|
|
||||||
}
|
|
||||||
acc[key] = updatedSettings[group][key]
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCurrentValue = (type, value, path) => {
|
const getCurrentValue = (type, value, path) => {
|
||||||
if (type === 'state') {
|
if (type === 'state') {
|
||||||
return _.get(value, path)
|
return _.get(value, path)
|
||||||
|
@ -50,7 +28,7 @@ const getCurrentValue = (type, value, path) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getValueWithoutKey = (key, [type, value]) => {
|
const getValueWithoutKey = (key, [type, value]) => {
|
||||||
if (type === 'atom' && value.length > 1) {
|
if (prependWithСolon(type, value)) {
|
||||||
return `:${value}`
|
return `:${value}`
|
||||||
} else if (key === ':backends') {
|
} else if (key === ':backends') {
|
||||||
const index = value.findIndex(el => el === ':ex_syslogger')
|
const index = value.findIndex(el => el === ':ex_syslogger')
|
||||||
|
@ -158,8 +136,9 @@ const parseProxyUrl = value => {
|
||||||
return { socks5: false, host: null, port: null }
|
return { socks5: false, host: null, port: null }
|
||||||
}
|
}
|
||||||
|
|
||||||
const partialUpdate = (group, key) => {
|
const prependWithСolon = (type, value) => {
|
||||||
return !(group === ':auto_linker' && key === ':opts')
|
return (type === 'atom' && value.length > 0) ||
|
||||||
|
(Array.isArray(type) && type.includes('boolean') && type.includes('atom') && typeof value === 'string')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const processNested = (valueForState, valueForUpdatedSettings, group, parentKey, parents, settings, updatedSettings) => {
|
export const processNested = (valueForState, valueForUpdatedSettings, group, parentKey, parents, settings, updatedSettings) => {
|
||||||
|
@ -246,7 +225,7 @@ const wrapValues = (settings, currentState) => {
|
||||||
))
|
))
|
||||||
) {
|
) {
|
||||||
return { 'tuple': [setting, wrapValues(value, currentState)] }
|
return { 'tuple': [setting, wrapValues(value, currentState)] }
|
||||||
} else if (type === 'atom' && value.length > 0) {
|
} else if (prependWithСolon(type, value)) {
|
||||||
return { 'tuple': [setting, `:${value}`] }
|
return { 'tuple': [setting, `:${value}`] }
|
||||||
} else if (type.includes('tuple') && (type.includes('string') || type.includes('atom'))) {
|
} else if (type.includes('tuple') && (type.includes('string') || type.includes('atom'))) {
|
||||||
return typeof value === 'string'
|
return typeof value === 'string'
|
||||||
|
|
|
@ -2,12 +2,13 @@ import { changeState, fetchReports, createNote, deleteNote } from '@/api/reports
|
||||||
|
|
||||||
const reports = {
|
const reports = {
|
||||||
state: {
|
state: {
|
||||||
fetchedReports: [],
|
|
||||||
totalReportsCount: 0,
|
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
|
fetchedReports: [],
|
||||||
|
loading: true,
|
||||||
|
openReportsCount: 0,
|
||||||
pageSize: 50,
|
pageSize: 50,
|
||||||
stateFilter: '',
|
stateFilter: '',
|
||||||
loading: true
|
totalReportsCount: 0
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
SET_LAST_REPORT_ID: (state, id) => {
|
SET_LAST_REPORT_ID: (state, id) => {
|
||||||
|
@ -16,6 +17,9 @@ const reports = {
|
||||||
SET_LOADING: (state, status) => {
|
SET_LOADING: (state, status) => {
|
||||||
state.loading = status
|
state.loading = status
|
||||||
},
|
},
|
||||||
|
SET_OPEN_REPORTS_COUNT: (state, total) => {
|
||||||
|
state.openReportsCount = total
|
||||||
|
},
|
||||||
SET_PAGE: (state, page) => {
|
SET_PAGE: (state, page) => {
|
||||||
state.currentPage = page
|
state.currentPage = page
|
||||||
},
|
},
|
||||||
|
@ -30,7 +34,7 @@ const reports = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async ChangeReportState({ commit, getters, state }, reportsData) {
|
async ChangeReportState({ commit, dispatch, getters, state }, reportsData) {
|
||||||
changeState(reportsData, getters.authHost, getters.token)
|
changeState(reportsData, getters.authHost, getters.token)
|
||||||
|
|
||||||
const updatedReports = state.fetchedReports.map(report => {
|
const updatedReports = state.fetchedReports.map(report => {
|
||||||
|
@ -39,6 +43,7 @@ const reports = {
|
||||||
})
|
})
|
||||||
|
|
||||||
commit('SET_REPORTS', updatedReports)
|
commit('SET_REPORTS', updatedReports)
|
||||||
|
dispatch('FetchOpenReportsCount')
|
||||||
},
|
},
|
||||||
ClearFetchedReports({ commit }) {
|
ClearFetchedReports({ commit }) {
|
||||||
commit('SET_REPORTS', [])
|
commit('SET_REPORTS', [])
|
||||||
|
@ -52,7 +57,14 @@ const reports = {
|
||||||
commit('SET_PAGE', page)
|
commit('SET_PAGE', page)
|
||||||
commit('SET_LOADING', false)
|
commit('SET_LOADING', false)
|
||||||
},
|
},
|
||||||
SetFilter({ commit }, filter) {
|
async FetchOpenReportsCount({ commit, getters, state }) {
|
||||||
|
commit('SET_LOADING', true)
|
||||||
|
const { data } = await fetchReports('open', state.currentPage, state.pageSize, getters.authHost, getters.token)
|
||||||
|
|
||||||
|
commit('SET_OPEN_REPORTS_COUNT', data.total)
|
||||||
|
commit('SET_LOADING', false)
|
||||||
|
},
|
||||||
|
SetReportsFilter({ commit }, filter) {
|
||||||
commit('SET_REPORTS_FILTER', filter)
|
commit('SET_REPORTS_FILTER', filter)
|
||||||
},
|
},
|
||||||
CreateReportNote({ commit, getters, state, rootState }, { content, reportID }) {
|
CreateReportNote({ commit, getters, state, rootState }, { content, reportID }) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { fetchDescription, fetchSettings, removeSettings, updateSettings } from '@/api/settings'
|
import { fetchDescription, fetchSettings, removeSettings, updateSettings } from '@/api/settings'
|
||||||
import { checkPartialUpdate, formSearchObject, parseNonTuples, parseTuples, valueHasTuples, wrapUpdatedSettings } from './normalizers'
|
import { formSearchObject, parseNonTuples, parseTuples, valueHasTuples, wrapUpdatedSettings } from './normalizers'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
|
@ -101,9 +101,8 @@ const settings = {
|
||||||
commit('SET_ACTIVE_TAB', tab)
|
commit('SET_ACTIVE_TAB', tab)
|
||||||
},
|
},
|
||||||
async SubmitChanges({ getters, commit, state }) {
|
async SubmitChanges({ getters, commit, state }) {
|
||||||
const updatedData = checkPartialUpdate(state.settings, state.updatedSettings, state.description)
|
const configs = Object.keys(state.updatedSettings).reduce((acc, group) => {
|
||||||
const configs = Object.keys(updatedData).reduce((acc, group) => {
|
return [...acc, ...wrapUpdatedSettings(group, state.updatedSettings[group], state.settings)]
|
||||||
return [...acc, ...wrapUpdatedSettings(group, updatedData[group], state.settings)]
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
await updateSettings(configs, getters.authHost, getters.token)
|
await updateSettings(configs, getters.authHost, getters.token)
|
||||||
|
|
|
@ -193,11 +193,14 @@ const users = {
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const deletedUsersIds = users.map(deletedUser => deletedUser.id)
|
const updatedUsers = users.map(user => {
|
||||||
const updatedUsers = state.fetchedUsers.filter(user => !deletedUsersIds.includes(user.id))
|
return { ...user, deactivated: true }
|
||||||
commit('SET_USERS', updatedUsers)
|
})
|
||||||
|
commit('SWAP_USERS', updatedUsers)
|
||||||
|
|
||||||
dispatch('FetchUserProfile', { userId: _userId, godmode: false })
|
if (_userId) {
|
||||||
|
dispatch('FetchUserProfile', { userId: _userId, godmode: false })
|
||||||
|
}
|
||||||
dispatch('SuccessMessage')
|
dispatch('SuccessMessage')
|
||||||
},
|
},
|
||||||
async FetchUsers({ commit, dispatch, getters, state }, { page }) {
|
async FetchUsers({ commit, dispatch, getters, state }, { page }) {
|
||||||
|
|
|
@ -1,8 +1,19 @@
|
||||||
|
<template>
|
||||||
|
<span>
|
||||||
|
<svg-icon :icon-class="icon"/>
|
||||||
|
<span slot="title">{{ title }}</span>
|
||||||
|
<el-badge :value="count" type="primary" class="count-badge" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'MenuItem',
|
name: 'Item',
|
||||||
functional: true,
|
|
||||||
props: {
|
props: {
|
||||||
|
count: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
icon: {
|
icon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
|
@ -11,19 +22,13 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
}
|
}
|
||||||
},
|
|
||||||
render(h, context) {
|
|
||||||
const { icon, title } = context.props
|
|
||||||
const vnodes = []
|
|
||||||
|
|
||||||
if (icon) {
|
|
||||||
vnodes.push(<svg-icon icon-class={icon}/>)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (title) {
|
|
||||||
vnodes.push(<span slot='title'>{(title)}</span>)
|
|
||||||
}
|
|
||||||
return vnodes
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style rel='stylesheet/scss' lang='scss' scoped>
|
||||||
|
.count-badge {
|
||||||
|
margin-left: 5px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -4,14 +4,21 @@
|
||||||
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
|
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
|
||||||
<app-link :to="resolvePath(onlyOneChild.path)">
|
<app-link :to="resolvePath(onlyOneChild.path)">
|
||||||
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
|
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
|
||||||
<item v-if="onlyOneChild.meta" :icon="onlyOneChild.meta.icon||item.meta.icon" :title="generateTitle(onlyOneChild.meta.title)" />
|
<item
|
||||||
|
v-if="onlyOneChild.meta"
|
||||||
|
:count="showCount(item) ? normalizedReportsCount : null"
|
||||||
|
:icon="onlyOneChild.meta.icon||item.meta.icon"
|
||||||
|
:title="generateTitle(onlyOneChild.meta.title)" />
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
</app-link>
|
</app-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)">
|
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)">
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<item v-if="item.meta" :icon="item.meta.icon" :title="generateTitle(item.meta.title)" />
|
<item
|
||||||
|
v-if="item.meta"
|
||||||
|
:count="showCount(item) ? normalizedReportsCount : null"
|
||||||
|
:icon="item.meta.icon"
|
||||||
|
:title="generateTitle(item.meta.title)" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-for="child in item.children">
|
<template v-for="child in item.children">
|
||||||
|
@ -26,7 +33,11 @@
|
||||||
|
|
||||||
<app-link v-else :to="resolvePath(child.path)" :key="child.name">
|
<app-link v-else :to="resolvePath(child.path)" :key="child.name">
|
||||||
<el-menu-item :index="resolvePath(child.path)">
|
<el-menu-item :index="resolvePath(child.path)">
|
||||||
<item v-if="child.meta" :icon="child.meta.icon" :title="generateTitle(child.meta.title)" />
|
<item
|
||||||
|
v-if="child.meta"
|
||||||
|
:count="showCount(item) ? normalizedReportsCount : null"
|
||||||
|
:icon="child.meta.icon"
|
||||||
|
:title="generateTitle(child.meta.title)" />
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
</app-link>
|
</app-link>
|
||||||
</template>
|
</template>
|
||||||
|
@ -43,6 +54,7 @@ import { isExternal } from '@/utils'
|
||||||
import Item from './Item'
|
import Item from './Item'
|
||||||
import AppLink from './Link'
|
import AppLink from './Link'
|
||||||
import FixiOSBug from './FixiOSBug'
|
import FixiOSBug from './FixiOSBug'
|
||||||
|
import numeral from 'numeral'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SidebarItem',
|
name: 'SidebarItem',
|
||||||
|
@ -71,6 +83,9 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
invitesEnabled() {
|
invitesEnabled() {
|
||||||
return this.basePath === '/invites' ? this.$store.state.app.invitesEnabled : true
|
return this.basePath === '/invites' ? this.$store.state.app.invitesEnabled : true
|
||||||
|
},
|
||||||
|
normalizedReportsCount() {
|
||||||
|
return numeral(this.$store.state.reports.openReportsCount).format('0a')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -104,6 +119,9 @@ export default {
|
||||||
}
|
}
|
||||||
return path.resolve(this.basePath, routePath)
|
return path.resolve(this.basePath, routePath)
|
||||||
},
|
},
|
||||||
|
showCount(item) {
|
||||||
|
return item.path === '/reports'
|
||||||
|
},
|
||||||
isExternalLink(routePath) {
|
isExternalLink(routePath) {
|
||||||
return isExternal(routePath)
|
return isExternal(routePath)
|
||||||
},
|
},
|
||||||
|
|
|
@ -31,6 +31,9 @@ export default {
|
||||||
isCollapse() {
|
isCollapse() {
|
||||||
return !this.sidebar.opened
|
return !this.sidebar.opened
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$store.dispatch('FetchOpenReportsCount')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -38,11 +38,11 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$store.dispatch('SetFilter', this.$data.filter)
|
this.$store.dispatch('SetReportsFilter', this.$data.filter)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleFilters() {
|
toggleFilters() {
|
||||||
this.$store.dispatch('SetFilter', this.$data.filter)
|
this.$store.dispatch('SetReportsFilter', this.$data.filter)
|
||||||
this.$store.dispatch('ClearFetchedReports')
|
this.$store.dispatch('ClearFetchedReports')
|
||||||
this.$store.dispatch('FetchReports', 1)
|
this.$store.dispatch('FetchReports', 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,16 @@
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
|
<image-upload-input
|
||||||
|
v-if="isImageUrl"
|
||||||
|
:data="data"
|
||||||
|
:setting-group="settingGroup"
|
||||||
|
:setting="setting"
|
||||||
|
:input-value="inputValue"
|
||||||
|
@change="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"
|
||||||
|
/>
|
||||||
<el-input
|
<el-input
|
||||||
v-if="setting.type === 'string' || (setting.type.includes('string') && setting.type.includes('atom'))"
|
v-else-if="setting.type === 'string' || (setting.type.includes('string') && setting.type.includes('atom'))"
|
||||||
:value="inputValue"
|
:value="inputValue"
|
||||||
:placeholder="setting.suggestions ? setting.suggestions[0] : null"
|
:placeholder="setting.suggestions ? setting.suggestions[0] : null"
|
||||||
:data-search="setting.key || setting.group"
|
:data-search="setting.key || setting.group"
|
||||||
|
@ -94,9 +102,9 @@
|
||||||
<template slot="prepend">:</template>
|
<template slot="prepend">:</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
<!-- special inputs -->
|
<!-- special inputs -->
|
||||||
<auto-linker-input v-if="settingGroup.group === ':auto_linker'" :data="data" :setting-group="settingGroup" :setting="setting"/>
|
|
||||||
<editable-keyword-input v-if="editableKeyword(setting.key, setting.type)" :data="keywordData" :setting-group="settingGroup" :setting="setting" :parents="settingParent"/>
|
<editable-keyword-input v-if="editableKeyword(setting.key, setting.type)" :data="keywordData" :setting-group="settingGroup" :setting="setting" :parents="settingParent"/>
|
||||||
<icons-input v-if="setting.key === ':icons'" :data="iconsData" :setting-group="settingGroup" :setting="setting"/>
|
<icons-input v-if="setting.key === ':icons'" :data="iconsData" :setting-group="settingGroup" :setting="setting"/>
|
||||||
|
<link-formatter-input v-if="booleanCombinedInput" :data="data" :setting-group="settingGroup" :setting="setting"/>
|
||||||
<mascots-input v-if="setting.key === ':mascots'" :data="keywordData" :setting-group="settingGroup" :setting="setting"/>
|
<mascots-input v-if="setting.key === ':mascots'" :data="keywordData" :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"/>
|
<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"/>
|
<prune-input v-if="setting.key === ':prune'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting"/>
|
||||||
|
@ -120,9 +128,10 @@
|
||||||
<script>
|
<script>
|
||||||
import i18n from '@/lang'
|
import i18n from '@/lang'
|
||||||
import {
|
import {
|
||||||
AutoLinkerInput,
|
|
||||||
EditableKeywordInput,
|
EditableKeywordInput,
|
||||||
IconsInput,
|
IconsInput,
|
||||||
|
ImageUploadInput,
|
||||||
|
LinkFormatterInput,
|
||||||
MascotsInput,
|
MascotsInput,
|
||||||
ProxyUrlInput,
|
ProxyUrlInput,
|
||||||
PruneInput,
|
PruneInput,
|
||||||
|
@ -137,9 +146,10 @@ import marked from 'marked'
|
||||||
export default {
|
export default {
|
||||||
name: 'Inputs',
|
name: 'Inputs',
|
||||||
components: {
|
components: {
|
||||||
AutoLinkerInput,
|
|
||||||
EditableKeywordInput,
|
EditableKeywordInput,
|
||||||
IconsInput,
|
IconsInput,
|
||||||
|
ImageUploadInput,
|
||||||
|
LinkFormatterInput,
|
||||||
MascotsInput,
|
MascotsInput,
|
||||||
ProxyUrlInput,
|
ProxyUrlInput,
|
||||||
PruneInput,
|
PruneInput,
|
||||||
|
@ -203,6 +213,9 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
booleanCombinedInput() {
|
||||||
|
return Array.isArray(this.setting.type) && this.setting.type.includes('boolean')
|
||||||
|
},
|
||||||
canBeDeleted() {
|
canBeDeleted() {
|
||||||
const { group, key } = this.settingGroup
|
const { group, key } = this.settingGroup
|
||||||
return _.get(this.$store.state.settings.db, [group, key]) &&
|
return _.get(this.$store.state.settings.db, [group, key]) &&
|
||||||
|
@ -267,7 +280,7 @@ export default {
|
||||||
':parsers',
|
':parsers',
|
||||||
':providers',
|
':providers',
|
||||||
':method',
|
':method',
|
||||||
':rewrite_policy',
|
':policies',
|
||||||
'Pleroma.Web.Auth.Authenticator'
|
'Pleroma.Web.Auth.Authenticator'
|
||||||
].includes(this.setting.key) ||
|
].includes(this.setting.key) ||
|
||||||
(this.settingGroup.key === 'Pleroma.Emails.Mailer' && this.setting.key === ':adapter')
|
(this.settingGroup.key === 'Pleroma.Emails.Mailer' && this.setting.key === ':adapter')
|
||||||
|
@ -277,6 +290,9 @@ export default {
|
||||||
},
|
},
|
||||||
updatedSettings() {
|
updatedSettings() {
|
||||||
return this.$store.state.settings.updatedSettings
|
return this.$store.state.settings.updatedSettings
|
||||||
|
},
|
||||||
|
isImageUrl() {
|
||||||
|
return [':background', ':logo', ':nsfwCensorImage', ':default_user_avatar', ':instance_thumbnail'].includes(this.setting.key)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="!loading" :class="isSidebarOpen" class="form-container">
|
<div v-if="!loading" :class="isSidebarOpen" class="form-container">
|
||||||
<el-form :model="autoLinkerData" :label-position="labelPosition" :label-width="labelWidth">
|
<el-form :model="linkFormatterData" :label-position="labelPosition" :label-width="labelWidth">
|
||||||
<setting :setting-group="autoLinker" :data="autoLinkerData"/>
|
<setting :setting-group="linkFormatter" :data="linkFormatterData"/>
|
||||||
</el-form>
|
</el-form>
|
||||||
<div class="submit-button-container">
|
<div class="submit-button-container">
|
||||||
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
|
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
|
||||||
|
@ -16,17 +16,17 @@ import Setting from './Setting'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AutoLinker',
|
name: 'LinkFormatter',
|
||||||
components: { Setting },
|
components: { Setting },
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters([
|
...mapGetters([
|
||||||
'settings'
|
'settings'
|
||||||
]),
|
]),
|
||||||
autoLinker() {
|
linkFormatter() {
|
||||||
return this.settings.description.find(setting => setting.key === ':opts')
|
return this.settings.description.find(setting => setting.key === 'Pleroma.Formatter')
|
||||||
},
|
},
|
||||||
autoLinkerData() {
|
linkFormatterData() {
|
||||||
return _.get(this.settings.settings, [':auto_linker', ':opts']) || {}
|
return _.get(this.settings.settings, [':pleroma', 'Pleroma.Formatter']) || {}
|
||||||
},
|
},
|
||||||
isMobile() {
|
isMobile() {
|
||||||
return this.$store.state.app.device === 'mobile'
|
return this.$store.state.app.device === 'mobile'
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="!loading" :class="isSidebarOpen" class="form-container">
|
<div v-if="!loading" :class="isSidebarOpen" class="form-container">
|
||||||
<div v-for="setting in mrfSettings" :key="setting.key">
|
<div v-for="setting in mrfSettings" :key="setting.key">
|
||||||
<el-form :model="getSettingData(setting)" :label-position="labelPosition" :label-width="labelWidth">
|
<el-form v-if="showMrfPolicy(setting.key)" :model="getSettingData(setting)" :label-position="labelPosition" :label-width="labelWidth">
|
||||||
<setting :setting-group="setting" :data="getSettingData(setting)"/>
|
<setting :setting-group="setting" :data="getSettingData(setting)"/>
|
||||||
|
<el-divider v-if="setting" class="divider thick-line"/>
|
||||||
</el-form>
|
</el-form>
|
||||||
<el-divider v-if="setting" class="divider thick-line"/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="submit-button-container">
|
<div class="submit-button-container">
|
||||||
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
|
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
|
||||||
|
@ -70,6 +70,16 @@ export default {
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: i18n.t('settings.success')
|
message: i18n.t('settings.success')
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
showMrfPolicy(key) {
|
||||||
|
const selectedMrfPolicies = _.get(this.settings.settings, [':pleroma', ':mrf', ':policies'])
|
||||||
|
const mappedPolicies = this.mrfSettings.reduce((acc, { key, related_policy }) => {
|
||||||
|
if (key !== ':mrf') {
|
||||||
|
acc[key] = related_policy
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
return !Object.keys(mappedPolicies).includes(key) || selectedMrfPolicies.includes(mappedPolicies[key])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
export { default as ActivityPub } from './ActivityPub'
|
export { default as ActivityPub } from './ActivityPub'
|
||||||
export { default as Authentication } from './Authentication'
|
export { default as Authentication } from './Authentication'
|
||||||
export { default as AutoLinker } from './AutoLinker'
|
|
||||||
export { default as Captcha } from './Captcha'
|
export { default as Captcha } from './Captcha'
|
||||||
export { default as Esshd } from './Esshd'
|
export { default as Esshd } from './Esshd'
|
||||||
export { default as Frontend } from './Frontend'
|
export { default as Frontend } from './Frontend'
|
||||||
|
@ -8,6 +7,7 @@ export { default as Gopher } from './Gopher'
|
||||||
export { default as Http } from './Http'
|
export { default as Http } from './Http'
|
||||||
export { default as Instance } from './Instance'
|
export { default as Instance } from './Instance'
|
||||||
export { default as JobQueue } from './JobQueue'
|
export { default as JobQueue } from './JobQueue'
|
||||||
|
export { default as LinkFormatter } from './LinkFormatter'
|
||||||
export { default as Logger } from './Logger'
|
export { default as Logger } from './Logger'
|
||||||
export { default as Mailer } from './Mailer'
|
export { default as Mailer } from './Mailer'
|
||||||
export { default as MediaProxy } from './MediaProxy'
|
export { default as MediaProxy } from './MediaProxy'
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div v-if="setting.key === ':class' || setting.key === ':rel'" :data-search="setting.key || setting.group">
|
|
||||||
<el-switch :value="autoLinkerBooleanValue(setting.key)" @change="processTwoTypeValue($event, setting.key)"/>
|
|
||||||
<el-input v-if="autoLinkerBooleanValue(setting.key)" :value="autoLinkerStringValue(setting.key)" @input="processTwoTypeValue($event, setting.key)"/>
|
|
||||||
</div>
|
|
||||||
<div v-if="setting.key === ':truncate'" :data-search="setting.key || setting.group">
|
|
||||||
<el-switch :value="autoLinkerBooleanValue(setting.key)" @change="processTwoTypeValue($event, setting.key)"/>
|
|
||||||
<el-input-number v-if="autoLinkerBooleanValue(setting.key)" :value="autoLinkerIntegerValue(setting.key)" @input="processTwoTypeValue($event, setting.key)"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'AutoLinkerInput',
|
|
||||||
props: {
|
|
||||||
data: {
|
|
||||||
type: [Object, Array],
|
|
||||||
default: function() {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setting: {
|
|
||||||
type: Object,
|
|
||||||
default: function() {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
settingGroup: {
|
|
||||||
type: Object,
|
|
||||||
default: function() {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
autoLinkerBooleanValue(key) {
|
|
||||||
const value = this.data[this.setting.key]
|
|
||||||
return typeof value === 'string' || typeof value === 'number'
|
|
||||||
},
|
|
||||||
autoLinkerIntegerValue(key) {
|
|
||||||
const value = this.data[this.setting.key]
|
|
||||||
return value || 0
|
|
||||||
},
|
|
||||||
autoLinkerStringValue(key) {
|
|
||||||
const value = this.data[this.setting.key]
|
|
||||||
return value || ''
|
|
||||||
},
|
|
||||||
processTwoTypeValue(value, input) {
|
|
||||||
if (value === true) {
|
|
||||||
const data = input === ':truncate' ? 0 : ''
|
|
||||||
this.updateSetting(data, this.settingGroup.group, this.settingGroup.key, input, this.setting.type)
|
|
||||||
} else {
|
|
||||||
this.updateSetting(value, this.settingGroup.group, this.settingGroup.key, input, this.setting.type)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateSetting(value, group, key, input, type) {
|
|
||||||
this.$store.dispatch('UpdateSettings', { group, key, input, value, type })
|
|
||||||
this.$store.dispatch('UpdateState', { group, key, input, value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style rel='stylesheet/scss' lang='scss'>
|
|
||||||
@import '../../styles/main';
|
|
||||||
@include settings
|
|
||||||
</style>
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
<template>
|
||||||
|
<div class="image-upload-area">
|
||||||
|
<div class="input-row">
|
||||||
|
<div :style="dimensions" class="image-upload-wrapper">
|
||||||
|
<div :style="dimensions" class="image-upload-overlay">
|
||||||
|
<input
|
||||||
|
:aria-label="$t('settings.changeImage')"
|
||||||
|
class="input-file"
|
||||||
|
type="file"
|
||||||
|
accept=".jpg,.jpeg,.png"
|
||||||
|
@change="handleFiles" >
|
||||||
|
<div class="caption">
|
||||||
|
{{ $t('settings.changeImage') }}
|
||||||
|
</div>
|
||||||
|
<el-image
|
||||||
|
v-loading="loading"
|
||||||
|
:src="imageUrl(inputValue)"
|
||||||
|
:style="dimensions"
|
||||||
|
class="uploaded-image"
|
||||||
|
fit="cover" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="image-button-group">
|
||||||
|
<el-button class="upload-button" size="small">
|
||||||
|
{{ $t('settings.uploadImage') }}
|
||||||
|
<input
|
||||||
|
:aria-label="$t('settings.changeImage')"
|
||||||
|
class="input-file"
|
||||||
|
type="file"
|
||||||
|
accept=".jpg,.jpeg,.png"
|
||||||
|
@change="handleFiles">
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="!isDefault" type="danger" size="small" style="margin-left: 5px;" @click="removeFile()">
|
||||||
|
{{ $t('settings.remove') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { baseName } from '../../../../api/utils'
|
||||||
|
import { uploadMedia } from '../../../../api/mediaUpload'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ImageUploadInput',
|
||||||
|
props: {
|
||||||
|
inputValue: {
|
||||||
|
type: [String, Object],
|
||||||
|
default: function() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setting: {
|
||||||
|
type: Object,
|
||||||
|
default: function() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'authHost'
|
||||||
|
]),
|
||||||
|
fullSize() {
|
||||||
|
if (_.includes([':background', ':nsfwCensorImage'], this.setting.key)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
dimensions() {
|
||||||
|
return {
|
||||||
|
width: this.fullSize ? '100%' : '100px',
|
||||||
|
height: this.fullSize ? '250px' : '100px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isDefault() {
|
||||||
|
return this.defaultImage === this.inputValue
|
||||||
|
},
|
||||||
|
defaultImage() {
|
||||||
|
return this.baseName + _.get(this.setting, 'suggestions[0]')
|
||||||
|
},
|
||||||
|
baseName() {
|
||||||
|
return baseName(this.authHost)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
imageUrl(url) {
|
||||||
|
if (_.isString(url)) {
|
||||||
|
const isUrl = url.startsWith('http') || url.startsWith('https')
|
||||||
|
return isUrl ? url : this.baseName + url
|
||||||
|
} else {
|
||||||
|
return this.defaultImage
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleFiles(event) {
|
||||||
|
const file = event.target.files[0]
|
||||||
|
if (!file) { return }
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = ({ target }) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
this.loading = true
|
||||||
|
uploadMedia({ formData, authHost: this.authHost }).then(response => {
|
||||||
|
this.loading = false
|
||||||
|
this.$emit('change', response.url)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
},
|
||||||
|
removeFile() {
|
||||||
|
this.$emit('change', this.defaultImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style rel='stylesheet/scss' lang='scss'>
|
||||||
|
@import '../../styles/main';
|
||||||
|
@include settings;
|
||||||
|
|
||||||
|
.image-upload-area {
|
||||||
|
.input-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-file {
|
||||||
|
z-index: 100;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-button-group {
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.upload-button {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-upload-wrapper {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.image-upload-overlay {
|
||||||
|
transition: box-shadow .1s;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
.caption {
|
||||||
|
visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 10px;
|
||||||
|
text-transform: uppercase;;
|
||||||
|
color: #fff;
|
||||||
|
z-index: 9;
|
||||||
|
transition: box-shadow .1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploaded-image {
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 2px 10px 0 rgba(0,0,0,.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
visibility: visible;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
.el-image__error {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption {
|
||||||
|
visibility: visible;
|
||||||
|
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.1), inset 0 0 120px 25px rgba(0, 0, 0, 0.8);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,90 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="setting.type.includes('string')" :data-search="setting.key || setting.group">
|
||||||
|
<el-switch :value="autoLinkerBooleanValue" @change="processTwoTypeValue($event, setting.key)"/>
|
||||||
|
<el-input
|
||||||
|
v-if="autoLinkerBooleanValue"
|
||||||
|
:value="autoLinkerStringValue"
|
||||||
|
@input="processTwoTypeValue($event, setting.key)"/>
|
||||||
|
</div>
|
||||||
|
<div v-if="setting.type.includes('integer')" :data-search="setting.key || setting.group">
|
||||||
|
<el-switch :value="autoLinkerBooleanValue" @change="processTwoTypeValue($event, setting.key)"/>
|
||||||
|
<el-input-number
|
||||||
|
v-if="autoLinkerBooleanValue"
|
||||||
|
:value="autoLinkerIntegerValue"
|
||||||
|
@input="processTwoTypeValue($event, setting.key)"/>
|
||||||
|
</div>
|
||||||
|
<div v-if="setting.type.includes('atom')" :data-search="setting.key || setting.group">
|
||||||
|
<el-switch :value="autoLinkerBooleanValue" @change="processTwoTypeValue($event, setting.key)"/>
|
||||||
|
<el-input
|
||||||
|
v-if="autoLinkerBooleanValue"
|
||||||
|
:value="autoLinkerAtomValue"
|
||||||
|
@input="processTwoTypeValue($event, setting.key)">
|
||||||
|
<template slot="prepend">:</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'LinkFormatterInput',
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
type: [Object, Array],
|
||||||
|
default: function() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setting: {
|
||||||
|
type: Object,
|
||||||
|
default: function() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
settingGroup: {
|
||||||
|
type: Object,
|
||||||
|
default: function() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
autoLinkerAtomValue() {
|
||||||
|
return this.data[this.setting.key] &&
|
||||||
|
this.data[this.setting.key][0] === ':' ? this.data[this.setting.key].substr(1) : this.data[this.setting.key]
|
||||||
|
},
|
||||||
|
autoLinkerBooleanValue() {
|
||||||
|
const value = this.data[this.setting.key]
|
||||||
|
return typeof value === 'string' || typeof value === 'number'
|
||||||
|
},
|
||||||
|
autoLinkerIntegerValue() {
|
||||||
|
const value = this.data[this.setting.key]
|
||||||
|
return value || 0
|
||||||
|
},
|
||||||
|
autoLinkerStringValue() {
|
||||||
|
const value = this.data[this.setting.key]
|
||||||
|
return value || ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
processTwoTypeValue(value, input) {
|
||||||
|
if (value === true) {
|
||||||
|
const data = input === ':truncate' ? 0 : ''
|
||||||
|
this.updateSetting(data, this.settingGroup.group, this.settingGroup.key, input, this.setting.type)
|
||||||
|
} else {
|
||||||
|
this.updateSetting(value, this.settingGroup.group, this.settingGroup.key, input, this.setting.type)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateSetting(value, group, key, input, type) {
|
||||||
|
this.$store.dispatch('UpdateSettings', { group, key, input, value, type })
|
||||||
|
this.$store.dispatch('UpdateState', { group, key, input, value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style rel='stylesheet/scss' lang='scss'>
|
||||||
|
@import '../../styles/main';
|
||||||
|
@include settings
|
||||||
|
</style>
|
|
@ -56,7 +56,7 @@ export default {
|
||||||
inputValue() {
|
inputValue() {
|
||||||
if (this.setting.key === 'Pleroma.Web.Auth.Authenticator') {
|
if (this.setting.key === 'Pleroma.Web.Auth.Authenticator') {
|
||||||
return this.data.value
|
return this.data.value
|
||||||
} else if (this.setting.key === ':rewrite_policy') {
|
} else if (this.setting.key === ':policies') {
|
||||||
return typeof this.data[this.setting.key] === 'string'
|
return typeof this.data[this.setting.key] === 'string'
|
||||||
? [this.data[this.setting.key]]
|
? [this.data[this.setting.key]]
|
||||||
: this.data[this.setting.key]
|
: this.data[this.setting.key]
|
||||||
|
@ -71,7 +71,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
options(suggestions) {
|
options(suggestions) {
|
||||||
const prefixes = {
|
const prefixes = {
|
||||||
':rewrite_policy': 'Pleroma.Web.ActivityPub.MRF.',
|
':policies': 'Pleroma.Web.ActivityPub.MRF.',
|
||||||
'Pleroma.Web.Auth.Authenticator': 'Pleroma.Web.Auth.',
|
'Pleroma.Web.Auth.Authenticator': 'Pleroma.Web.Auth.',
|
||||||
':method': 'Pleroma.Captcha.',
|
':method': 'Pleroma.Captcha.',
|
||||||
':adapter': 'Swoosh.Adapters.',
|
':adapter': 'Swoosh.Adapters.',
|
||||||
|
@ -100,14 +100,6 @@ export default {
|
||||||
this.setting.key === ':args'
|
this.setting.key === ':args'
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
rewritePolicyOptions(suggestions) {
|
|
||||||
return suggestions.map(element => {
|
|
||||||
const label = element.split('Pleroma.Web.ActivityPub.MRF.')[1]
|
|
||||||
? element.split('Pleroma.Web.ActivityPub.MRF.')[1]
|
|
||||||
: element
|
|
||||||
return { value: element, label }
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateSetting(value, group, key, input, type) {
|
updateSetting(value, group, key, input, type) {
|
||||||
const updatedValue = getBooleanValue(value)
|
const updatedValue = getBooleanValue(value)
|
||||||
this.$store.dispatch('UpdateSettings', { group, key, input, value: updatedValue, type })
|
this.$store.dispatch('UpdateSettings', { group, key, input, value: updatedValue, type })
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
export { default as AutoLinkerInput } from './AutoLinkerInput'
|
|
||||||
export { default as EditableKeywordInput } from './EditableKeywordInput'
|
export { default as EditableKeywordInput } from './EditableKeywordInput'
|
||||||
export { default as IconsInput } from './IconsInput'
|
export { default as IconsInput } from './IconsInput'
|
||||||
|
export { default as ImageUploadInput } from './ImageUploadInput'
|
||||||
|
export { default as LinkFormatterInput } from './LinkFormatterInput'
|
||||||
export { default as MascotsInput } from './MascotsInput'
|
export { default as MascotsInput } from './MascotsInput'
|
||||||
export { default as ProxyUrlInput } from './ProxyUrlInput'
|
export { default as ProxyUrlInput } from './ProxyUrlInput'
|
||||||
export { default as PruneInput } from './PruneInput'
|
export { default as PruneInput } from './PruneInput'
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
export const tabs = description => {
|
export const tabs = description => {
|
||||||
return {
|
return {
|
||||||
'activity-pub': {
|
'activity-pub': {
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
<div class="settings-search-input-container"/>
|
<div class="settings-search-input-container"/>
|
||||||
<activity-pub v-if="activeTab === 'activityPub'"/>
|
<activity-pub v-if="activeTab === 'activityPub'"/>
|
||||||
<authentication v-if="activeTab === 'auth'"/>
|
<authentication v-if="activeTab === 'auth'"/>
|
||||||
<auto-linker v-if="activeTab === 'autoLinker'"/>
|
<link-formatter v-if="activeTab === 'linkFormatter'"/>
|
||||||
<esshd v-if="activeTab === 'esshd'"/>
|
<esshd v-if="activeTab === 'esshd'"/>
|
||||||
<captcha v-if="activeTab === 'captcha'"/>
|
<captcha v-if="activeTab === 'captcha'"/>
|
||||||
<frontend v-if="activeTab === 'frontend'"/>
|
<frontend v-if="activeTab === 'frontend'"/>
|
||||||
|
@ -97,7 +97,6 @@ import { tabs } from './components/tabs'
|
||||||
import {
|
import {
|
||||||
ActivityPub,
|
ActivityPub,
|
||||||
Authentication,
|
Authentication,
|
||||||
AutoLinker,
|
|
||||||
Captcha,
|
Captcha,
|
||||||
Esshd,
|
Esshd,
|
||||||
Frontend,
|
Frontend,
|
||||||
|
@ -105,6 +104,7 @@ import {
|
||||||
Http,
|
Http,
|
||||||
Instance,
|
Instance,
|
||||||
JobQueue,
|
JobQueue,
|
||||||
|
LinkFormatter,
|
||||||
Logger,
|
Logger,
|
||||||
Mailer,
|
Mailer,
|
||||||
MediaProxy,
|
MediaProxy,
|
||||||
|
@ -122,7 +122,6 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
ActivityPub,
|
ActivityPub,
|
||||||
Authentication,
|
Authentication,
|
||||||
AutoLinker,
|
|
||||||
Captcha,
|
Captcha,
|
||||||
Esshd,
|
Esshd,
|
||||||
Frontend,
|
Frontend,
|
||||||
|
@ -130,6 +129,7 @@ export default {
|
||||||
Http,
|
Http,
|
||||||
Instance,
|
Instance,
|
||||||
JobQueue,
|
JobQueue,
|
||||||
|
LinkFormatter,
|
||||||
Logger,
|
Logger,
|
||||||
Mailer,
|
Mailer,
|
||||||
MediaProxy,
|
MediaProxy,
|
||||||
|
@ -147,7 +147,7 @@ export default {
|
||||||
options: [
|
options: [
|
||||||
{ value: 'activityPub', label: i18n.t('settings.activityPub') },
|
{ value: 'activityPub', label: i18n.t('settings.activityPub') },
|
||||||
{ value: 'auth', label: i18n.t('settings.auth') },
|
{ value: 'auth', label: i18n.t('settings.auth') },
|
||||||
{ value: 'autoLinker', label: i18n.t('settings.autoLinker') },
|
{ value: 'linkFormatter', label: i18n.t('settings.linkFormatter') },
|
||||||
{ value: 'esshd', label: i18n.t('settings.esshd') },
|
{ value: 'esshd', label: i18n.t('settings.esshd') },
|
||||||
{ value: 'captcha', label: i18n.t('settings.captcha') },
|
{ value: 'captcha', label: i18n.t('settings.captcha') },
|
||||||
{ value: 'frontend', label: i18n.t('settings.frontend') },
|
{ value: 'frontend', label: i18n.t('settings.frontend') },
|
||||||
|
@ -222,9 +222,7 @@ export default {
|
||||||
querySearch(queryString, cb) {
|
querySearch(queryString, cb) {
|
||||||
const results = this.searchData.filter(searchObj => searchObj.search.find(el => el.includes(queryString.toLowerCase())))
|
const results = this.searchData.filter(searchObj => searchObj.search.find(el => el.includes(queryString.toLowerCase())))
|
||||||
.map(searchObj => {
|
.map(searchObj => {
|
||||||
return searchObj.groupKey === ':opts'
|
return { value: `${searchObj.label} in ${searchObj.groupLabel}`, group: searchObj.groupKey, key: searchObj.key }
|
||||||
? { value: `${searchObj.label} in Auto Linker`, group: searchObj.groupKey, key: searchObj.key }
|
|
||||||
: { value: `${searchObj.label} in ${searchObj.groupLabel}`, group: searchObj.groupKey, key: searchObj.key }
|
|
||||||
})
|
})
|
||||||
cb(results)
|
cb(results)
|
||||||
},
|
},
|
||||||
|
|
|
@ -143,7 +143,20 @@ export default {
|
||||||
this.$store.dispatch('ResendConfirmationEmail', [user])
|
this.$store.dispatch('ResendConfirmationEmail', [user])
|
||||||
},
|
},
|
||||||
handleDeletion(user) {
|
handleDeletion(user) {
|
||||||
this.$store.dispatch('DeleteUsers', { users: [user], _userId: user.id })
|
this.$confirm(
|
||||||
|
this.$t('users.deleteUsersConfirmation'),
|
||||||
|
{
|
||||||
|
confirmButtonText: 'Delete',
|
||||||
|
cancelButtonText: 'Cancel',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
this.$store.dispatch('DeleteUsers', { users: [user], _userId: user.id })
|
||||||
|
}).catch(() => {
|
||||||
|
this.$message({
|
||||||
|
type: 'info',
|
||||||
|
message: 'Delete canceled'
|
||||||
|
})
|
||||||
|
})
|
||||||
},
|
},
|
||||||
handleEmailConfirmation(user) {
|
handleEmailConfirmation(user) {
|
||||||
this.$store.dispatch('ConfirmUsersEmail', { users: [user], _userId: user.id, _statusId: this.statusId })
|
this.$store.dispatch('ConfirmUsersEmail', { users: [user], _userId: user.id, _statusId: this.statusId })
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
import { checkPartialUpdate } from '@/store/modules/normalizers'
|
|
||||||
import _ from 'lodash'
|
|
||||||
|
|
||||||
describe('Partial update', () => {
|
|
||||||
it('partial update for settings that do not allow partial update', () => {
|
|
||||||
const settings = { ':auto_linker': { ':opts':
|
|
||||||
{ ':strip_prefix': true, ':new_window': false, ':rel': 'ugc', ':truncate': 3 }
|
|
||||||
}}
|
|
||||||
const updatedSettings = { ':auto_linker': { ':opts': { ':new_window': false }}}
|
|
||||||
const description = [{
|
|
||||||
children: [
|
|
||||||
{ key: ':strip_prefix', type: 'boolean' },
|
|
||||||
{ key: ':truncate', type: ['integer', false] },
|
|
||||||
{ key: ':new_window', type: 'boolean' }],
|
|
||||||
description: 'Configuration for the auto_linker library',
|
|
||||||
group: ':auto_linker',
|
|
||||||
key: ':opts',
|
|
||||||
label: 'Opts',
|
|
||||||
type: 'group'
|
|
||||||
}]
|
|
||||||
|
|
||||||
const expectedData = { ':auto_linker': { ':opts': {
|
|
||||||
':strip_prefix': ['boolean', true],
|
|
||||||
':new_window': ['boolean', false],
|
|
||||||
':rel': ['', 'ugc'],
|
|
||||||
':truncate': [['integer', false], 3]
|
|
||||||
}}}
|
|
||||||
const updatedData = checkPartialUpdate(settings, updatedSettings, description)
|
|
||||||
expect(_.isEqual(updatedData, expectedData)).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('partial update for settings that allow partial update', () => {
|
|
||||||
const settings = { ':pleroma': { 'Pleroma.Captcha': { ':enabled': true, ':seconds_valid': 70, ':method': 'Pleroma.Captcha.Kocaptcha' }}}
|
|
||||||
const updatedSettings = { ':pleroma': { 'Pleroma.Captcha': { ':seconds_valid': ['integer', 70] }}}
|
|
||||||
const description = [{
|
|
||||||
children: [],
|
|
||||||
description: 'Captcha-related settings',
|
|
||||||
group: ':pleroma',
|
|
||||||
key: 'Pleroma.Captcha',
|
|
||||||
label: 'Pleroma.Captcha',
|
|
||||||
type: 'group'
|
|
||||||
}]
|
|
||||||
|
|
||||||
const expectedData = { ':pleroma': { 'Pleroma.Captcha': { ':seconds_valid': ['integer', 70] }}}
|
|
||||||
const updatedData = checkPartialUpdate(settings, updatedSettings, description)
|
|
||||||
expect(_.isEqual(updatedData, expectedData)).toBeTruthy()
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -26,7 +26,7 @@ describe('Reports filter', () => {
|
||||||
it('shows open reports when "Open" filter is applied', async (done) => {
|
it('shows open reports when "Open" filter is applied', async (done) => {
|
||||||
expect(store.state.reports.fetchedReports.length).toEqual(7)
|
expect(store.state.reports.fetchedReports.length).toEqual(7)
|
||||||
|
|
||||||
store.dispatch('SetFilter', 'open')
|
store.dispatch('SetReportsFilter', 'open')
|
||||||
store.dispatch('ClearFetchedReports')
|
store.dispatch('ClearFetchedReports')
|
||||||
store.dispatch('FetchReports', 1)
|
store.dispatch('FetchReports', 1)
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
@ -38,7 +38,7 @@ describe('Reports filter', () => {
|
||||||
it('shows resolved reports when "Resolved" filter is applied', async (done) => {
|
it('shows resolved reports when "Resolved" filter is applied', async (done) => {
|
||||||
expect(store.state.reports.fetchedReports.length).toEqual(7)
|
expect(store.state.reports.fetchedReports.length).toEqual(7)
|
||||||
|
|
||||||
store.dispatch('SetFilter', 'resolved')
|
store.dispatch('SetReportsFilter', 'resolved')
|
||||||
store.dispatch('ClearFetchedReports')
|
store.dispatch('ClearFetchedReports')
|
||||||
store.dispatch('FetchReports')
|
store.dispatch('FetchReports')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
@ -50,7 +50,7 @@ describe('Reports filter', () => {
|
||||||
it('shows closed reports when "Closed" filter is applied', async (done) => {
|
it('shows closed reports when "Closed" filter is applied', async (done) => {
|
||||||
expect(store.state.reports.fetchedReports.length).toEqual(7)
|
expect(store.state.reports.fetchedReports.length).toEqual(7)
|
||||||
|
|
||||||
store.dispatch('SetFilter', 'closed')
|
store.dispatch('SetReportsFilter', 'closed')
|
||||||
store.dispatch('ClearFetchedReports')
|
store.dispatch('ClearFetchedReports')
|
||||||
store.dispatch('FetchReports')
|
store.dispatch('FetchReports')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
@ -62,13 +62,13 @@ describe('Reports filter', () => {
|
||||||
it('shows all users after removing filters', async (done) => {
|
it('shows all users after removing filters', async (done) => {
|
||||||
expect(store.state.reports.fetchedReports.length).toEqual(7)
|
expect(store.state.reports.fetchedReports.length).toEqual(7)
|
||||||
|
|
||||||
store.dispatch('SetFilter', 'open')
|
store.dispatch('SetReportsFilter', 'open')
|
||||||
store.dispatch('ClearFetchedReports')
|
store.dispatch('ClearFetchedReports')
|
||||||
store.dispatch('FetchReports')
|
store.dispatch('FetchReports')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(store.state.reports.fetchedReports.length).toEqual(2)
|
expect(store.state.reports.fetchedReports.length).toEqual(2)
|
||||||
|
|
||||||
store.dispatch('SetFilter', '')
|
store.dispatch('SetReportsFilter', '')
|
||||||
store.dispatch('ClearFetchedReports')
|
store.dispatch('ClearFetchedReports')
|
||||||
store.dispatch('FetchReports')
|
store.dispatch('FetchReports')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
|
|
@ -58,8 +58,6 @@ describe('Settings search', () => {
|
||||||
wrapper.vm.handleSearchSelect({ group: ':media_proxy', key: ':ssl_options' })
|
wrapper.vm.handleSearchSelect({ group: ':media_proxy', key: ':ssl_options' })
|
||||||
expect(store.state.settings.activeTab).toBe('media-proxy')
|
expect(store.state.settings.activeTab).toBe('media-proxy')
|
||||||
|
|
||||||
wrapper.vm.handleSearchSelect({ group: ':opts', key: ':opts' })
|
|
||||||
expect(store.state.settings.activeTab).toBe('auto-linker')
|
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -156,11 +156,13 @@ describe('Users actions', () => {
|
||||||
stubs: ['router-link']
|
stubs: ['router-link']
|
||||||
})
|
})
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(store.state.users.fetchedUsers.length).toEqual(3)
|
expect(store.state.users.fetchedUsers[1].deactivated).toBe(false)
|
||||||
|
|
||||||
wrapper.find(htmlElement(2, 2)).trigger('click')
|
wrapper.find(htmlElement(2, 2)).trigger('click')
|
||||||
|
store.dispatch('DeleteUsers', { users: [{ active: true, deactivated: false, id: '10', nickname: 'bob', local: false, external: true, roles: { admin: false, moderator: false }, tags: ['sandbox'] }] })
|
||||||
|
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(store.state.users.fetchedUsers.length).toEqual(2)
|
expect(store.state.users.fetchedUsers[1].deactivated).toBe(true)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -225,11 +225,11 @@ describe('Apply users actions to multiple users', () => {
|
||||||
expect(wrapper.vm.deleteMultipleUsers).toHaveBeenCalled()
|
expect(wrapper.vm.deleteMultipleUsers).toHaveBeenCalled()
|
||||||
|
|
||||||
const remove = wrapper.vm.mappers().remove
|
const remove = wrapper.vm.mappers().remove
|
||||||
expect(store.state.users.fetchedUsers.length).toEqual(3)
|
expect(store.state.users.fetchedUsers.filter(user => user.deactivated).length).toEqual(1)
|
||||||
remove()
|
remove()
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
|
||||||
expect(store.state.users.fetchedUsers.length).toEqual(0)
|
expect(store.state.users.fetchedUsers.length).toEqual(3)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue