Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • pleroma/admin-fe
  • linafilippova/admin-fe
  • Exilat_a_Tolosa/admin-fe
  • mkljczk/admin-fe
  • maxf/admin-fe
  • kphrx/admin-fe
  • vaartis/admin-fe
  • ELR/admin-fe
  • eugenijm/admin-fe
  • jp/admin-fe
  • mkfain/admin-fe
  • lorenzoancora/admin-fe
  • alexgleason/admin-fe
  • seanking/admin-fe
  • ilja/admin-fe
15 results
Show changes
Showing
with 3006 additions and 27 deletions
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<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/settings';
@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>
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :data-search="setting.key || setting.group" class="mascot-container">
<div v-for="mascot in data" :key="getId(mascot)" class="mascot">
<el-form-item label="Name" label-width="85px" class="mascot-form-item">
<div class="mascot-name-container">
<el-input :value="getName(mascot)" placeholder="Name" class="mascot-name-input" @input="parseMascots($event, 'name', mascot)"/>
<el-button :size="isDesktop ? 'medium' : 'mini'" class="icon-minus-button" icon="el-icon-minus" circle @click="deleteMascotsRow(mascot)"/>
</div>
</el-form-item>
<el-form-item label="URL" label-width="85px" class="mascot-form-item">
<el-input :value="getUrl(mascot)" placeholder="URL" class="mascot-input" @input="parseMascots($event, 'url', mascot)"/>
</el-form-item>
<el-form-item label="Mime type" label-width="85px" class="mascot-form-item">
<el-input :value="getMimeType(mascot)" placeholder="Mime type" class="mascot-input" @input="parseMascots($event, 'mimeType', mascot)"/>
</el-form-item>
</div>
<el-button :size="isDesktop ? 'medium' : 'mini'" icon="el-icon-plus" circle @click="addRowToMascots"/>
</div>
</template>
<script>
export default {
name: 'MascotsInput',
props: {
data: {
type: Array,
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
}
},
computed: {
isDesktop() {
return this.$store.state.app.device === 'desktop'
}
},
methods: {
addRowToMascots() {
const updatedValue = [...this.data, { '': { ':url': '', ':mime_type': '', id: this.generateID() }}]
this.updateSetting(updatedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
deleteMascotsRow(mascot) {
const deletedId = this.getId(mascot)
const filteredValues = this.data.filter(mascot => Object.values(mascot)[0].id !== deletedId)
this.updateSetting(filteredValues, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
generateID() {
return `f${(~~(Math.random() * 1e8)).toString(16)}`
},
getId(mascot) {
const { id } = Object.values(mascot)[0]
return id
},
getName(mascot) {
return Object.keys(mascot)[0]
},
getUrl(mascot) {
const [value] = Object.values(mascot)
return value[':url']
},
getMimeType(mascot) {
const [value] = Object.values(mascot)
return value[':mime_type']
},
parseMascots(value, inputType, mascot) {
const updatedId = this.getId(mascot)
const updatedValue = this.data.map((mascot, index) => {
if (Object.values(mascot)[0].id === updatedId) {
if (inputType === 'name') {
return { [value]: Object.values(this.data[index])[0] }
} else if (inputType === 'url') {
return { [Object.keys(mascot)[0]]: { ...Object.values(this.data[index])[0], ':url': value }}
} else {
return { [Object.keys(mascot)[0]]: { ...Object.values(this.data[index])[0], ':mime_type': value }}
}
}
return mascot
})
this.updateSetting(updatedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
updateSetting(value, group, key, input, type) {
const mascotsWithoutIDs = value.reduce((acc, mascot) => {
const { id, ...mascotValue } = Object.values(mascot)[0]
return { ...acc, [Object.keys(mascot)[0]]: ['', mascotValue] }
}, {})
this.$store.dispatch('UpdateSettings', { group, key, input, value: mascotsWithoutIDs, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../../styles/settings';
@include settings
</style>
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :data-search="setting.key || setting.group" class="proxy-url-input">
<el-input
:value="proxyUrlData.host"
placeholder="host (e.g. localhost or 127.0.0.1)"
class="proxy-url-host-input"
@input="updateProxyUrl($event, 'host')"/>
<span v-if="isDesktop">:</span>
<el-input
:value="proxyUrlData.port"
placeholder="port (e.g 9020 or 3090)"
class="proxy-url-value-input"
@input="updateProxyUrl($event, 'port')"/>
<div class="socks5-checkbox-container">
<el-checkbox :value="proxyUrlData.socks5" @change="updateProxyUrl($event, 'socks5')"/>
<span class="socks5-checkbox">Socks5</span>
</div>
</div>
</template>
<script>
import { processNested } from '@/store/modules/normalizers'
export default {
name: 'ProxyUrlInput',
props: {
data: {
type: [Object, Array],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
},
parents: {
type: Array,
default: function() {
return []
},
required: false
}
},
computed: {
isDesktop() {
return this.$store.state.app.device === 'desktop'
},
settings() {
return this.$store.state.settings.settings
},
updatedSettings() {
return this.$store.state.settings.updatedSettings
},
proxyUrlData() {
return Object.keys(this.data).length === 0 ? { socks5: false, host: null, port: null } : this.data
}
},
methods: {
updateProxyUrl(value, inputType) {
let data
if (inputType === 'socks5') {
data = { ...this.proxyUrlData, socks5: value }
} else if (inputType === 'host') {
data = { ...this.proxyUrlData, host: value }
} else {
data = { ...this.proxyUrlData, port: value }
}
this.updateSetting(data, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
updateSetting(value, group, key, input, type) {
const assembledData = value.socks5
? [':socks5', value.host, value.port]
: `${value.host}:${value.port}`
if (this.parents.length > 0) {
const { valueForState,
valueForUpdatedSettings,
setting } = processNested(value, assembledData, group, key, this.parents.reverse(), this.settings, this.updatedSettings)
this.$store.dispatch('UpdateSettings',
{ group, key, input: setting.key, value: valueForUpdatedSettings, type: setting.type })
this.$store.dispatch('UpdateState',
{ group, key, input: setting.key, value: valueForState })
} else {
this.$store.dispatch('UpdateSettings', { group, key, input, value: assembledData, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
}
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../../styles/settings';
@include settings
</style>
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :data-search="setting.key || setting.group">
<el-radio-group v-model="prune" class="prune-options">
<el-radio label=":disabled">Disabled</el-radio>
<el-radio label=":maxlen">Limit-based</el-radio>
<el-radio label=":maxage">Time-based</el-radio>
</el-radio-group>
<el-form-item v-if="prune === ':maxlen'" label="Max length" label-width="100" label-position="left">
<el-input-number
:value="data[1]"
:min="0"
placeholder="1500"
size="large"
class="top-margin"
@change="updateIntInput($event, ':maxlen')"/>
</el-form-item>
<el-form-item v-if="prune === ':maxage'" label="Max age" label-width="100" label-position="left">
<el-input-number
:value="data[1]"
:min="0"
placeholder="3600"
size="large"
class="top-margin"
@change="updateIntInput($event, ':maxage')"/>
</el-form-item>
</div>
</template>
<script>
export default {
name: 'PruneInput',
props: {
data: {
type: [Object, Array],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
}
},
computed: {
prune: {
get: function() {
return this.data[0]
},
set: function(value) {
this.updateRadioInput(value)
}
}
},
methods: {
updateIntInput(value, input) {
this.updateSetting([input, value], this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
updateSetting(value, group, key, input, type) {
const updatedSetting = value.includes(':disabled') ? ':disabled' : value
this.$store.dispatch('UpdateSettings', { group, key, input, value: updatedSetting, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
},
updateRadioInput(value) {
const processedValue = value === ':disabled' ? [value] : [value, 0]
this.updateSetting(processedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../../styles/settings';
@include settings
</style>
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :data-search="setting.key || setting.group" class="rate-limit-container">
<div v-if="!rateLimitAuthUsers">
<el-input-number
:value="rateLimitAllUsers[0]"
:controls="false"
placeholder="scale"
class="scale-input"
@input="parseRateLimiter($event, setting.key, 'scale', 'oneLimit', rateLimitAllUsers)"/>
<span>:</span>
<el-input-number
:value="rateLimitAllUsers[1]"
:controls="false"
placeholder="limit"
class="limit-input"
@input="parseRateLimiter($event, setting.key, 'limit', 'oneLimit', rateLimitAllUsers)"/>
<div class="limit-button-container">
<el-button :size="isDesktop ? 'medium' : 'mini'" icon="el-icon-plus" circle @click="toggleLimits([['', ''], ['', '']], setting.key)"/>
<p class="expl limit-expl">{{ $t('settings.setLimits') }}</p>
</div>
</div>
<div v-if="rateLimitAuthUsers">
<el-form-item class="rate-limit">
<div class="rate-limit-label-container">
<span class="rate-limit-label">
{{ $t('settings.unauthenticatedUsers') }}:
</span>
</div>
<div class="rate-limit-content">
<el-input-number
:value="rateLimitUnauthUsers[0]"
:controls="false"
placeholder="scale"
class="scale-input"
@input="parseRateLimiter(
$event, setting.key, 'scale', 'unauthUsersLimit', [rateLimitUnauthUsers, rateLimitAuthUsers]
)"/>
<span>:</span>
<el-input-number
:value="rateLimitUnauthUsers[1]"
:controls="false"
placeholder="limit"
class="limit-input"
@input="parseRateLimiter(
$event, setting.key, 'limit', 'unauthUsersLimit', [rateLimitUnauthUsers, rateLimitAuthUsers]
)"/>
</div>
</el-form-item>
<el-form-item class="rate-limit">
<div class="rate-limit-label-container">
<span class="rate-limit-label">
{{ $t('settings.authenticatedUsers') }}:
</span>
</div>
<div class="rate-limit-content">
<el-input-number
:value="rateLimitAuthUsers[0]"
:controls="false"
placeholder="scale"
class="scale-input"
@input="parseRateLimiter($event, setting.key, 'scale', 'authUserslimit', [rateLimitUnauthUsers, rateLimitAuthUsers])"/>
<span>:</span>
<el-input-number
:value="rateLimitAuthUsers[1]"
:controls="false"
placeholder="limit"
class="limit-input"
@input="parseRateLimiter($event, setting.key, 'limit', 'authUserslimit', [rateLimitUnauthUsers, rateLimitAuthUsers])"/>
</div>
</el-form-item>
<div class="limit-button-container">
<el-button :size="isDesktop ? 'medium' : 'mini'" class="icon-minus-button" icon="el-icon-minus" circle @click="toggleLimits(['', ''], setting.key)"/>
<p class="expl limit-expl">{{ $t('settings.setLimitsForAll') }}</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'RateLimitInput',
props: {
data: {
type: [Object, Array],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
}
},
computed: {
isDesktop() {
return this.$store.state.app.device === 'desktop'
},
rateLimitAllUsers() {
return this.data[this.setting.key] ? this.data[this.setting.key] : ['', '']
},
rateLimitAuthUsers() {
return this.data[this.setting.key] && Array.isArray(this.data[this.setting.key][0])
? this.data[this.setting.key][1]
: false
},
rateLimitUnauthUsers() {
return this.data[this.setting.key] && Array.isArray(this.data[this.setting.key][1])
? this.data[this.setting.key][0]
: false
}
},
methods: {
parseRateLimiter(value, input, typeOfInput, typeOfLimit, currentValue) {
let valueToSend
if (typeOfLimit === 'oneLimit') {
valueToSend = typeOfInput === 'scale' ? [value, currentValue[1]] : [currentValue[0], value]
} else if (typeOfLimit === 'unauthUsersLimit') {
valueToSend = typeOfInput === 'scale'
? [[value, currentValue[0][1]], [currentValue[1][0], currentValue[1][1]]]
: [[currentValue[0][0], value], [currentValue[1][0], currentValue[1][1]]]
} else if (typeOfLimit === 'authUserslimit') {
valueToSend = typeOfInput === 'scale'
? [[currentValue[0][0], currentValue[0][1]], [value, currentValue[1][1]]]
: [[currentValue[0][0], currentValue[0][1]], [currentValue[1][0], value]]
}
this.updateSetting(valueToSend, this.settingGroup.group, this.settingGroup.key, input, this.setting.type)
},
toggleLimits(value, input) {
this.updateSetting(value, this.settingGroup.group, this.settingGroup.key, input)
},
updateSetting(value, group, key, input, type) {
const updatedSettings = Array.isArray(value[0])
? value.map(element => { return { 'tuple': element } })
: { 'tuple': value }
this.$store.dispatch('UpdateSettings', { group, key, input, value: updatedSettings, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../../styles/settings';
@include settings
</style>
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<el-switch
:value="data[setting.key]"
:data-search="setting.key"
class="switch-input"
@change="updateSetting($event, settingGroup.group, settingGroup.key, setting.key, setting.type)"/>
</template>
<script>
export default {
name: 'RegInvitesInput',
props: {
data: {
type: [Object, Array],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
}
},
methods: {
updateSetting(value, group, key, input, type) {
const registrationsOpen = this.$store.state.settings.settings[group][key][':registrations_open']
const invitesEnabled = this.$store.state.settings.settings[group][key][':invites_enabled']
if (input === ':registrations_open' && value && invitesEnabled) {
this.$confirm(
'Enabling this setting requires invites to be disabled. Are you sure you want to open registrations?',
'Warning',
{ confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
this.$store.dispatch('UpdateSettings', { group, key, input, value, type })
this.$store.dispatch('UpdateSettings', { group, key, input: ':invites_enabled', value: false, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
this.$store.dispatch('UpdateState', { group, key, input: ':invites_enabled', value: false })
})
} else if (input === ':invites_enabled' && value && registrationsOpen) {
this.$confirm(
'Enabling this setting requires registrations to be disabled. Are you sure you want to enable invitations?',
'Warning',
{ confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
this.$store.dispatch('UpdateSettings', { group, key, input, value, type })
this.$store.dispatch('UpdateSettings', { group, key, input: ':registrations_open', value: false, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
this.$store.dispatch('UpdateState', { group, key, input: ':registrations_open', value: false })
})
} else {
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/settings';
@include settings
</style>
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div class="input">
<el-select
v-if="renderMultipleSelect(setting.type)"
:value="inputValue"
:data-search="setting.key"
multiple
filterable
allow-create
class="input"
@change="updateSetting($event, settingGroup.group, settingGroup.key, setting.key, setting.type)">
<el-option v-for="(option, index) in options(setting.suggestions)" :key="index" :value="option.value" :label="option.label" />
</el-select>
<el-select
v-if="setting.type === 'module' || (setting.type.includes('atom') && setting.type.includes('dropdown'))"
:value="inputValue === false ? 'false' : inputValue"
:data-search="setting.key || setting.group"
clearable
class="input"
@change="updateSetting($event, settingGroup.group, settingGroup.key, setting.key, setting.type)">
<el-option
v-for="(option, index) in options(setting.suggestions)"
:value="option.value"
:label="option.label"
:key="index"/>
</el-select>
</div>
</template>
<script>
import { getBooleanValue } from '@/store/modules/normalizers'
export default {
name: 'SelectInputWithReducedLabels',
props: {
data: {
type: [Array, Object],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
}
},
computed: {
inputValue() {
if (this.setting.key === 'Pleroma.Web.Auth.Authenticator') {
return this.data.value
} else if (this.setting.key === ':policies') {
return typeof this.data[this.setting.key] === 'string'
? [this.data[this.setting.key]]
: this.data[this.setting.key]
} else {
return this.data[this.setting.key]
}
},
isMobile() {
return this.$store.state.app.device === 'mobile'
}
},
methods: {
options(suggestions) {
const prefixes = {
':policies': 'Pleroma.Web.ActivityPub.MRF.',
'Pleroma.Web.Auth.Authenticator': 'Pleroma.Web.Auth.',
':method': 'Pleroma.Captcha.',
':adapter': 'Swoosh.Adapters.',
':providers': 'Pleroma.Web.Metadata.Providers.',
':parsers': 'Pleroma.Web.RichMedia.Parsers.',
':ttl_setters': 'Pleroma.Web.RichMedia.Parser.',
':scrub_policy': 'Pleroma.HTML.',
':federation_publisher_modules': 'Pleroma.Web.',
':uploader': 'Pleroma.Uploaders.',
':filters': 'Pleroma.Upload.Filter.'
}
return suggestions.map(element => {
const label = element.split(prefixes[this.setting.key])[1]
? element.split(prefixes[this.setting.key])[1]
: element
return { value: element, label }
})
},
renderMultipleSelect(type) {
return Array.isArray(type) && this.setting.key !== ':backends' && this.setting.key !== ':args' && (
type.includes('module') ||
(type.includes('list') && type.includes('string')) ||
(type.includes('list') && type.includes('atom')) ||
(type.includes('regex') && type.includes('string')) ||
this.setting.key === ':args'
)
},
updateSetting(value, group, key, input, type) {
const updatedValue = getBooleanValue(value)
this.$store.dispatch('UpdateSettings', { group, key, input, value: updatedValue, type })
this.$store.dispatch('UpdateState', { group, key, input, value: updatedValue })
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../../styles/settings';
@include settings
</style>
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :data-search="setting.key || setting.group" class="sender-input">
<el-input
:value="sender.email"
placeholder="email address"
class="email-address-input"
@input="updateSender($event, 'email')"/>
<el-input
:value="sender.nickname"
placeholder="nickname"
class="nickname-input"
@input="updateSender($event, 'nickname')"/>
</div>
</template>
<script>
import { processNested } from '@/store/modules/normalizers'
export default {
name: 'SenderInput',
props: {
data: {
type: [Object, Array],
default: function() {
return {}
}
},
parents: {
type: Array,
default: function() {
return []
},
required: false
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
}
},
computed: {
isDesktop() {
return this.$store.state.app.device === 'desktop'
},
settings() {
return this.$store.state.settings.settings
},
updatedSettings() {
return this.$store.state.settings.updatedSettings
},
sender() {
return Object.keys(this.data).length === 0 ? { email: null, nickname: null } : this.data
}
},
methods: {
updateSender(value, inputType) {
let data
if (inputType === 'email') {
data = { ...this.sender, email: value }
} else {
data = { ...this.sender, nickname: value }
}
this.updateSetting(data, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
updateSetting(value, group, key, input, type) {
const assembledData = value.nickname
? [value.nickname, value.email]
: value.email
if (this.parents.length > 0) {
const { valueForState,
valueForUpdatedSettings,
setting } = processNested(value, assembledData, group, key, this.parents.reverse(), this.settings, this.updatedSettings)
this.$store.dispatch('UpdateSettings',
{ group, key, input: setting.key, value: valueForUpdatedSettings, type: setting.type })
this.$store.dispatch('UpdateState',
{ group, key, input: setting.key, value: valueForState })
} else {
this.$store.dispatch('UpdateSettings', { group, key, input, value: assembledData, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
}
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../../styles/settings';
@include settings
</style>
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div class="multiple-select-container">
<el-select
v-if="setting.key === ':args'"
:value="data[setting.key]"
:data-search="setting.key || setting.group"
multiple
filterable
allow-create
class="input"
@change="updateSetting($event, settingGroup.group, settingGroup.key, setting.key, setting.type)">
<el-option value="strip"/>
<el-option value="auto-orient"/>
<!-- eslint-disable -->
<el-option value='{"implode", "1"}'/>
<!-- eslint-enable -->
</el-select>
</div>
</template>
<script>
export default {
name: 'SpecificMultipleSelect',
props: {
data: {
type: [Object, Array],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
}
},
methods: {
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/settings';
@include settings
</style>
// SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
// SPDX-License-Identifier: AGPL-3.0-only
export { default as BooleanCombinedInput } from './BooleanCombinedInput'
export { default as EditableKeywordInput } from './EditableKeywordInput'
export { default as EditorInput } from './EditorInput'
export { default as IconsInput } from './IconsInput'
export { default as ImageUploadInput } from './ImageUploadInput'
export { default as MascotsInput } from './MascotsInput'
export { default as ProxyUrlInput } from './ProxyUrlInput'
export { default as PruneInput } from './PruneInput'
export { default as RateLimitInput } from './RateLimitInput'
export { default as RegInvitesInput } from './RegInvitesInput'
export { default as SelectInputWithReducedLabels } from './SelectInputWithReducedLabels'
export { default as SenderInput } from './SenderInput'
export { default as SpecificMultipleSelect } from './SpecificMultipleSelect'
// SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
// SPDX-License-Identifier: AGPL-3.0-only
export const tabs = description => {
return {
'activity-pub': {
label: 'settings.activityPub',
settings: [':activitypub', ':user']
},
'authentication': {
label: 'settings.auth',
settings: [':auth', ':ldap', ':oauth2', 'Pleroma.Web.Auth.Authenticator']
},
'esshd': {
label: 'settings.esshd',
settings: [':esshd']
},
'captcha': {
label: 'settings.captcha',
settings: ['Pleroma.Captcha', 'Pleroma.Captcha.Kocaptcha']
},
'emoji': {
label: 'settings.emoji',
settings: [':emoji']
},
'frontend': {
label: 'settings.frontend',
settings: [':assets', ':chat', ':frontends', ':emoji', ':frontend_configurations', ':markup', ':static_fe', 'Pleroma.Web.Preload']
},
'gopher': {
label: 'settings.gopher',
settings: [':gopher']
},
'http': {
label: 'settings.http',
settings: [':cors_plug', ':http', ':http_security', ':web_cache_ttl']
},
'instance': {
label: 'settings.instance',
settings: [':admin_token', ':instance', ':instance_panel', ':instances_favicons', ':welcome', ':manifest', 'Pleroma.User', 'Pleroma.ScheduledActivity', ':uri_schemes', ':feed', ':streamer', ':restrict_unauthenticated']
},
'job-queue': {
label: 'settings.jobQueue',
settings: ['Pleroma.Workers.PurgeExpiredActivity', ':connections_pool', ':hackney_pools', 'Oban', ':pools', ':workers']
},
'link-formatter': {
label: 'settings.linkFormatter',
settings: ['Pleroma.Formatter']
},
'mailer': {
label: 'settings.mailer',
settings: [':email_notifications', 'Pleroma.Emails.Mailer', 'Pleroma.Emails.UserEmail', ':swoosh', 'Pleroma.Emails.NewUsersDigestEmail']
},
'media-proxy': {
label: 'settings.mediaProxy',
settings: [':media_proxy', ':media_preview_proxy', 'Pleroma.Web.MediaProxy.Invalidation.Http', 'Pleroma.Web.MediaProxy.Invalidation.Script']
},
'metadata': {
label: 'settings.metadata',
settings: ['Pleroma.Web.Metadata', ':rich_media']
},
'mrf': {
label: 'settings.mrf',
settings: description.filter(el => el.tab === 'mrf').map(setting => setting.key)
},
'rate-limiters': {
label: 'settings.rateLimiters',
settings: [':rate_limit']
},
'web-push': {
label: 'settings.webPush',
settings: [':vapid_details']
},
'upload': {
label: 'settings.upload',
settings: ['Pleroma.Upload.Filter.AnonymizeFilename', 'Pleroma.Upload.Filter.Mogrify', 'Pleroma.Uploaders.S3', 'Pleroma.Uploaders.Local', 'Pleroma.Upload', ':s3']
},
'other': {
label: 'settings.other',
settings: [':mime', 'Pleroma.User.Backup', 'Pleroma.Web.Plugs.RemoteIp', 'Pleroma.Web.Endpoint.MetricsExporter', ':modules', 'Pleroma.Web.ApiSpec.CastAndValidate', ':terms_of_services']
}
}
}
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="rebootIsSidebarOpen" class="settings-container">
<div class="reboot-button-container">
<reboot-button/>
</div>
<div v-if="isDesktop">
<div :class="isSidebarOpen">
<h1 class="settings-header">{{ $t('settings.settings') }}</h1>
<div class="docs-search-container">
<el-link
:underline="false"
href="https://docs-develop.pleroma.social/backend/administration/CLI_tasks/config/"
target="_blank">
<el-button class="settings-docs-button">
<span>
<i class="el-icon-document"/>
{{ $t('settings.seeDocs') }}
</span>
</el-button>
</el-link>
<el-autocomplete
v-model="searchQuery"
:fetch-suggestions="querySearch"
:trigger-on-focus="false"
clearable
placeholder="Search"
prefix-icon="el-icon-search"
class="settings-search-input"
@select="handleSearchSelect"/>
</div>
</div>
<component :is="componentName"/>
</div>
<div v-if="isMobile || isTablet">
<div :class="isSidebarOpen" class="settings-header-container">
<h1 class="settings-header">{{ $t('settings.settings') }}</h1>
<el-link
:underline="false"
href="https://docs-develop.pleroma.social/backend/administration/CLI_tasks/config/"
target="_blank">
<el-button class="settings-docs-button">
<span>
<i class="el-icon-document"/>
{{ $t('settings.seeDocs') }}
</span>
</el-button>
</el-link>
</div>
<div class="settings-search-container">
<el-autocomplete
v-model="searchQuery"
:fetch-suggestions="querySearch"
:trigger-on-focus="false"
clearable
placeholder="Search"
prefix-icon="el-icon-search"
class="settings-search-input"
@select="handleSearchSelect"/>
</div>
<component :is="componentName"/>
</div>
</div>
</template>
<script>
import { tabs } from './components/tabs'
import {
ActivityPub,
Authentication,
Captcha,
Emoji,
Esshd,
Frontend,
Gopher,
Http,
Instance,
JobQueue,
LinkFormatter,
Mailer,
MediaProxy,
Metadata,
Mrf,
Other,
RateLimiters,
Upload,
WebPush
} from './components'
import RebootButton from '@/components/RebootButton'
export default {
components: {
ActivityPub,
Authentication,
Captcha,
Emoji,
Esshd,
Frontend,
Gopher,
Http,
Instance,
JobQueue,
LinkFormatter,
Mailer,
MediaProxy,
Metadata,
Mrf,
Other,
RateLimiters,
RebootButton,
Upload,
WebPush
},
data() {
return {
searchQuery: ''
}
},
computed: {
componentName() {
return this.$route.path.split('/settings/').pop()
},
configDisabled() {
return this.$store.state.settings.configDisabled
},
isDesktop() {
return this.$store.state.app.device === 'desktop'
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
isSidebarOpen() {
return this.$store.state.app.sidebar.opened ? 'header-sidebar-opened' : 'header-sidebar-closed'
},
isTablet() {
return this.$store.state.app.device === 'tablet'
},
rebootIsSidebarOpen() {
return this.$store.state.app.sidebar.opened ? 'reboot-sidebar-opened' : 'reboot-sidebar-closed'
},
searchData() {
return this.$store.state.settings.searchData
},
tabs() {
return tabs(this.$store.state.settings.description)
}
},
mounted: function() {
this.$store.dispatch('GetNodeInfo')
this.$store.dispatch('NeedReboot')
this.$store.dispatch('FetchSettings')
},
methods: {
handleSearchSelect(selectedValue) {
this.$store.dispatch('SetSearchQuery', selectedValue.key)
const tab = Object.keys(this.tabs).find(tab => {
return this.tabs[tab].settings.includes(selectedValue.group === ':pleroma' ? selectedValue.key : selectedValue.group)
})
if (this.$router.currentRoute.path === `/settings/${tab}`) {
this.scrollTo(selectedValue.key)
} else if (tab) {
this.$router.push({ path: `/settings/${tab}` })
}
},
scrollTo(searchQuery) {
const selectedSetting = document.querySelector(`[data-search="${searchQuery}"]`)
if (selectedSetting) {
selectedSetting.scrollIntoView({ block: 'start', behavior: 'smooth' })
}
},
querySearch(queryString, cb) {
const results = this.searchData.filter(searchObj => searchObj.search.find(el => el.includes(queryString.toLowerCase())))
.map(searchObj => {
return { value: `${searchObj.label} in ${searchObj.groupLabel}`, group: searchObj.groupKey, key: searchObj.key }
})
cb(results)
},
settingsCantBeChanged(settings) {
const existingSettings = settings.filter(setting => {
if ([':esshd', ':cors_plug', ':swoosh', ':mime'].includes(setting)) {
return this.$store.state.settings.description.findIndex(el => el.group === setting) !== -1
} else if (setting === 'Pleroma.Web.Auth.Authenticator' || setting === ':admin_token') {
return this.$store.state.settings.description.findIndex(el => el.children[0].key === setting) !== -1
} else {
return this.$store.state.settings.description.findIndex(el => el.key === setting) !== -1
}
})
return existingSettings.length === 0
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss' scoped>
@import '../styles/settings';
@include settings
</style>
// SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
// SPDX-License-Identifier: AGPL-3.0-only
const rules = [{
name: 'renderIfNotEqual',
key: ':proxy_remote',
groupKey: 'Pleroma.Upload',
group: ':pleroma',
targetKey: ':uploader',
targetGroup: 'Pleroma.Upload',
notEqual: 'Pleroma.Uploaders.Local'
}]
const renderIfNotEqual = (state, { group, groupKey, targetKey, notEqual }) => {
return state[group][groupKey][targetKey] !== notEqual
}
const rulesMap = {
renderIfNotEqual
}
export const settingFollowsRules = (settingKey, settingGroupKey, state) => {
const rule = rules.find(rule => rule.groupKey === settingGroupKey && rule.key === settingKey)
if (!rule) return true
const ruleFn = rulesMap[rule.name]
if (!ruleFn) return true
return ruleFn(state, rule)
}
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div v-if="!loadingPeers" class="statuses-container">
<div class="statuses-header">
<h1>
{{ $t('statuses.statuses') }}
</h1>
<reboot-button/>
</div>
<div class="statuses-header-container">
<el-button-group>
<el-button plain class="direct-button">
{{ $t('statuses.direct') }}: {{ normalizedCount(statusVisibility.direct) }}
</el-button>
<el-button plain class="private-button">
{{ $t('statuses.private') }}: {{ normalizedCount(statusVisibility.private) }}
</el-button>
<el-button plain class="public-button">
{{ $t('statuses.public') }}: {{ normalizedCount(statusVisibility.public) }}
</el-button>
<el-button plain class="unlisted-button">
{{ $t('statuses.unlisted') }}: {{ normalizedCount(statusVisibility.unlisted) }}
</el-button>
</el-button-group>
</div>
<div class="filter-container">
<el-select
v-model="selectedInstance"
:placeholder="$t('statuses.instanceFilter')"
:no-data-text="$t('statuses.noInstances')"
filterable
clearable
class="select-instance"
@change="handleFilterChange">
<el-option
v-for="(instance,index) in instances"
:key="index"
:label="instance"
:value="instance"/>
</el-select>
<multiple-users-menu
: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"
:account="status.account"
:show-checkbox="isDesktop"
:fetch-statuses-by-instance="true"
@status-selection="handleStatusSelection" />
</div>
<div v-if="statuses.length > 0" class="statuses-pagination">
<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 MultipleUsersMenu from '@/views/users/components/MultipleUsersMenu'
import Status from '@/components/Status'
import RebootButton from '@/components/RebootButton'
import numeral from 'numeral'
export default {
name: 'Statuses',
components: {
MultipleUsersMenu,
RebootButton,
Status
},
data() {
return {
selectedUsers: []
}
},
computed: {
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'
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
isTablet() {
return this.$store.state.app.device === 'tablet'
},
loadingPeers() {
return this.$store.state.peers.loading
},
page() {
return this.$store.state.status.statusesByInstance.page
},
pageSize() {
return this.$store.state.status.statusesByInstance.pageSize
},
selectedInstance: {
get() {
return this.$store.state.status.statusesByInstance.selectedInstance
},
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
},
statusVisibility() {
return this.$store.state.status.statusVisibility
}
},
mounted() {
this.$store.dispatch('GetNodeInfo')
this.$store.dispatch('NeedReboot')
this.$store.dispatch('FetchPeers')
this.$store.dispatch('FetchStatusesCount')
},
destroyed() {
this.clearSelection()
this.$store.dispatch('ClearState')
},
methods: {
clearSelection() {
this.selectedUsers = []
},
handleFilterChange() {
this.$store.dispatch('HandlePageChange', 1)
this.$store.dispatch('FetchStatusesByInstance')
},
handleLoadMore() {
this.$store.dispatch('HandlePageChange', this.page + 1)
this.$store.dispatch('FetchStatusesPageByInstance')
},
handleStatusSelection(user) {
if (this.selectedUsers.find(selectedUser => user.id === selectedUser.id) !== undefined) {
return
}
this.selectedUsers = [...this.selectedUsers, user]
},
normalizedCount(count) {
return numeral(count).format('0a')
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
.statuses-container {
padding: 0 15px;
h1 {
margin: 10px 0 15px 0;
}
.status-container {
margin: 0 0 10px;
}
}
.statuses-header-container {
.el-button.is-plain:focus, .el-button.is-plain:hover {
border-color: #DCDFE6;
color: #606266;
cursor: default
}
}
.checkbox-container {
margin-bottom: 15px;
}
.filter-container {
display: flex;
height: 36px;
justify-content: space-between;
align-items: center;
margin: 22px 0 15px 0;
}
.reboot-button {
padding: 10px;
margin: 0;
width: 145px;
}
.select-instance {
width: 396px;
}
.statuses-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.statuses-header-container {
display: flex;
align-items: center;
justify-content: space-between;
}
.statuses-pagination {
padding: 15px 0;
text-align: center;
}
@media only screen and (max-width:480px) {
.checkbox-container {
margin-bottom: 10px;
}
.filter-container {
display: flex;
height: 36px;
flex-direction: column;
margin: 10px 0;
}
.select-field {
width: 100%;
margin-bottom: 5px;
}
.select-instance {
width: 100%;
}
.statuses-header-container {
flex-direction: column;
align-items: flex-start;
.el-button-group {
width: 100%;
}
.el-button {
padding: 10px 6.5px;
width: 50%;
}
.el-button-group>.el-button:first-child {
border-bottom-left-radius: 0;
}
.el-button-group>.el-button:not(:first-child):not(:last-child).private-button {
border-top-right-radius: 4px;
}
.el-button-group>.el-button:not(:first-child):not(:last-child).public-button {
border-bottom-left-radius: 4px;
border-top: white;
}
.el-button-group>.el-button:last-child {
border-top-right-radius: 0;
border-top: white;
}
.reboot-button {
margin: 10px 0 0 0;
}
}
}
</style>
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div v-if="!loading" class="status-show-container">
<header v-if="isDesktop || isTablet" class="user-page-header">
<div class="avatar-name-container">
<router-link
v-if="propertyExists(user, 'id')"
:to="{ name: 'UsersShow', params: { id: user.id }}"
class="router-link">
<div class="avatar-name-header">
<el-avatar v-if="propertyExists(user, 'avatar')" :src="user.avatar" size="large" />
<h1 v-if="propertyExists(user, 'nickname')">{{ user.nickname }}</h1>
<h1 v-else class="invalid">({{ $t('users.invalidNickname') }})</h1>
</div>
</router-link>
<a v-if="propertyExists(user, 'url')" :href="user.url" target="_blank">
<i :title="$t('userProfile.openAccountInInstance')" class="el-icon-top-right"/>
</a>
</div>
<div class="left-header-container">
<moderation-dropdown
:user="user"
:page="'statusPage'"
:status-id="status.id"
@open-reset-token-dialog="openResetPasswordDialog"/>
<reboot-button/>
</div>
</header>
<div v-if="isMobile" class="status-page-header-container">
<header class="user-page-header">
<div class="avatar-name-container">
<el-avatar v-if="propertyExists(user, 'avatar')" :src="user.avatar" size="large" />
<h1 v-if="propertyExists(user, 'nickname')">{{ user.nickname }}</h1>
</div>
<reboot-button/>
</header>
<moderation-dropdown
:user="user"
:page="'statusPage'"
@open-reset-token-dialog="openResetPasswordDialog"/>
</div>
<reset-password-dialog
:reset-password-dialog-open="resetPasswordDialogOpen"
@close-reset-token-dialog="closeResetPasswordDialog"/>
<div class="status-container">
<status :status="status" :account="user" :show-checkbox="false" :godmode="showPrivate"/>
</div>
<div class="recent-statuses-container-show">
<h2 v-if="propertyExists(user, 'nickname')" class="recent-statuses">
{{ $t('userProfile.recentStatuses') }} by {{ user.nickname }}
</h2>
<h2 v-else 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" :account="status.account" :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>
</div>
</div>
</template>
<script>
import Status from '@/components/Status'
import ModerationDropdown from '../users/components/ModerationDropdown'
import RebootButton from '@/components/RebootButton'
import ResetPasswordDialog from '@/views/users/components/ResetPasswordDialog'
export default {
name: 'StatusShow',
components: { ModerationDropdown, RebootButton, ResetPasswordDialog, Status },
data() {
return {
showPrivate: false,
resetPasswordDialogOpen: false
}
},
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'
},
loading() {
return this.$store.state.status.loading
},
status() {
return this.$store.state.status.fetchedStatus
},
statuses() {
return this.$store.state.userProfile.statuses
},
statusesLoading() {
return this.$store.state.userProfile.statusesLoading
},
user() {
return this.$store.state.status.statusAuthor
}
},
beforeMount: function() {
this.$store.dispatch('NeedReboot')
this.$store.dispatch('GetNodeInfo')
this.$store.dispatch('FetchStatus', this.$route.params.id)
},
methods: {
closeResetPasswordDialog() {
this.resetPasswordDialogOpen = false
this.$store.dispatch('RemovePasswordToken')
},
onTogglePrivate() {
this.$store.dispatch('FetchUserStatuses', { userId: this.user.id, godmode: this.showPrivate })
},
openResetPasswordDialog() {
this.resetPasswordDialogOpen = true
},
propertyExists(account, property) {
return account[property]
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
.avatar-name-container {
display: flex;
align-items: center;
.el-icon-top-right {
font-size: 2em;
line-height: 36px;
color: #606266;
}
}
.avatar-name-header {
display: flex;
height: 40px;
align-items: center;
}
.invalid {
color: gray;
}
.no-statuses {
margin-left: 28px;
color: #606266;
}
.password-reset-token {
margin: 0 0 14px 0;
}
.password-reset-token-dialog {
width: 50%
}
.reboot-button {
padding: 10px;
margin-left: 6px;
}
.recent-statuses-container-show {
display: flex;
flex-direction: column;
.el-timeline-item {
margin-left: 20px;
}
.recent-statuses {
margin-left: 20px;
}
.show-private-statuses {
margin-left: 20px;
margin-bottom: 20px;
}
}
.reset-password-link {
text-decoration: underline;
}
.router-link {
text-decoration: none;
}
.status-container {
margin: 0 15px 0 20px;
}
.statuses {
padding: 0 20px 0 0;
}
.user-page-header {
display: flex;
justify-content: space-between;
margin: 22px 15px 22px 20px;
padding: 0;
align-items: center;
h1 {
display: inline;
margin: 0 0 0 10px;
}
}
@media only screen and (min-width: 1824px) {
.status-show-container {
max-width: 1824px;
margin: auto;
}
}
@media only screen and (max-width:480px) {
.avatar-name-container {
margin-bottom: 10px;
}
.el-timeline-item__wrapper {
padding-left: 18px;
}
.left-header-container {
align-items: center;
display: flex;
justify-content: space-between;
}
.password-reset-token-dialog {
width: 85%
}
.recent-statuses {
margin: 20px 10px 15px 10px;
}
.recent-statuses-container-show {
width: 100%;
margin: 0 0 0 10px;
.el-timeline-item {
margin-left: 0;
}
.recent-statuses {
margin-left: 0;
}
.show-private-statuses {
margin: 0 10px 20px 0;
}
}
.status-card {
.el-card__body {
padding: 15px;
}
}
.status-container {
margin: 0 10px;
}
.statuses {
padding-right: 10px;
margin-left: 0;
.el-timeline-item__wrapper {
margin-right: 10px;
}
}
.user-page-header {
padding: 0;
margin: 7px 15px 5px 10px;
}
.status-page-header-container {
width: 100%;
.el-dropdown {
width: stretch;
margin: 0 10px 15px 10px;
}
}
}
@media only screen and (max-width:801px) and (min-width: 481px) {
.recent-statuses-container-show {
width: 97%;
margin: 0 20px;
.el-timeline-item {
margin-left: 2px;
}
.recent-statuses {
margin: 20px 10px 15px 0;
}
.show-private-statuses {
margin: 0 10px 20px 0;
}
}
.show-private-statuses {
margin: 0 10px 20px 0;
}
.user-page-header {
padding: 0;
margin: 7px 15px 20px 20px;
}
}
</style>
/*
* SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
* SPDX-License-Identifier: AGPL-3.0-only
*/
@mixin relays {
.follow-relay {
width: 350px;
margin-right: 7px;
}
.relays-container {
margin: 0 15px;
}
.relays-header-container {
display: flex;
align-items: center;
justify-content: space-between;
}
@media only screen and (max-width:480px) {
.follow-relay {
width: 75%;
margin-right: 5px;
input {
width: 100%;
}
}
.follow-relay-container {
display: flex;
justify-content: space-between;
margin: 0 5px;
}
.relays-container {
margin: 0 10px;
}
}
}
\ No newline at end of file
/*
* SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
* SPDX-License-Identifier: AGPL-3.0-only
*/
@mixin settings {
a {
text-decoration: underline;
}
.center-label label {
text-align: center;
span {
float: left;
}
}
.code {
background-color: #adbed67a;
border-radius: 3px;
font-family: monospace;
padding: 0 3px 0 3px;
}
.delete-setting-button {
margin-left: 5px;
}
.description-container {
overflow-wrap: break-word;
.el-form-item__content {
line-height: 20px;
}
}
.divider {
margin: 0 0 18px 0;
}
.divider.thick-line {
height: 2px;
}
.docs-search-container {
display: flex;
justify-content: flex-end;
margin-right: 30px;
}
.editable-keyword-container {
width: 100%;
}
.el-form-item .rate-limit {
margin-right: 0;
}
.el-input-group__prepend {
padding-left: 10px;
padding-right: 10px;
}
.el-tabs__header {
z-index: 2002;
}
.email-address-input {
width: 50%;
margin-right: 10px;
}
.esshd-list {
margin: 0;
}
.expl, .expl > p {
color: #666666;
font-size: 13px;
line-height: 22px;
margin: 5px 0 0 0;
overflow-wrap: break-word;
overflow: hidden;
text-overflow: ellipsis;
code {
display: inline;
line-height: 22px;
font-size: 13px;
padding: 2px 3px;
}
}
.form-container {
margin-bottom: 80px;
}
.frontend-container {
margin-right: 30px;
}
.frontend-form-input {
margin-top: 20px;
}
.frontends-button-container {
width: 100%;
margin-top: 15px;
}
.frontends-table {
width: 100%;
margin-right: 30px;
}
.grouped-settings-header {
margin: 0 0 14px 0;
}
.highlight {
background-color: #e6e6e6;
}
.icons-button-container {
width: 100%;
margin-bottom: 10px;
}
.icons-button-desc {
font-size: 14px;
color: #606266;
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei";
margin-left: 5px;
}
.icon-container {
flex-direction: column;
width: 95%;
}
.icon-values-container {
display: flex;
margin: 0 10px 10px 0;
}
.icon-key-input {
width: 30%;
margin-right: 8px
}
.icon-minus-button {
width: 36px;
height: 36px;
}
.icon-value-input {
width: 70%;
margin-left: 8px;
}
.icons-container {
display: flex;
}
.input-container {
display: flex;
align-items: flex-start;
justify-content: space-between;
.el-form-item {
margin-right: 30px;
width: 100%
}
.el-select {
width: 100%;
}
}
.install-frontend-button {
margin-top: 15px;
float: right;
}
.keyword-container {
width: 100%
}
label {
overflow: hidden;
text-overflow: ellipsis;
}
.label-font {
font-size: 14px;
color: #606266;
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei";
font-weight: 700;
}
.limit-button-container {
display: flex;
align-items: baseline;
}
.limit-expl {
margin-left: 10px;
}
.limit-input {
width: 47%;
margin: 0 0 5px 1%
}
.line {
width: 100%;
height: 0;
border: 1px solid #eee;
margin-bottom: 18px;
}
.mascot {
margin-bottom: 15px;
}
.mascot-container {
width: 100%;
}
.mascot-input {
margin-bottom: 7px;
}
.mascot-name-container {
display: flex;
margin-bottom: 7px;
}
.mascot-name-input {
margin-right: 10px
}
.multiple-select-container {
width: 100%;
}
.name-input {
width: 30%;
margin-right: 8px
}
.nickname-input {
width: 50%;
}
.no-top-margin {
margin-top: 0;
p {
margin-right: 30px;
}
}
.pattern-input {
width: 20%;
margin-right: 8px
}
.proxy-url-input {
display: flex;
align-items: center;
margin-bottom: 10px;
width: 100%;
}
.proxy-url-host-input {
width: 35%;
margin-right: 8px
}
.proxy-url-value-input {
width: 35%;
margin-left: 8px;
margin-right: 10px
}
.prune-options {
display: flex;
height: 36px;
align-items: baseline;
.el-radio {
margin-top: 11px;
}
}
.rate-limit {
.el-form-item__content {
width: 100%;
display: flex;
}
}
.rate-limit-container {
width: 100%;
}
.rate-limit-content {
width: 70%;
}
.rate-limit-label {
float: right;
}
.rate-limit-label-container {
font-size: 14px;
color: #606266;
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei";
font-weight: 700;
height: fit-content;
width: 30%;
margin-right: 10px;
}
.reboot-button {
width: 145px;
text-align: left;
padding: 10px;
float: right;
margin: 0 30px 0 0;
}
.reboot-button-container {
width: 100%;
position: fixed;
top: 60px;
right: 0;
z-index: 2000;
}
.replacement-input {
width: 80%;
margin-left: 8px;
margin-right: 10px
}
.sender-input {
display: flex;
align-items: center;
margin-bottom: 10px;
width: 100%;
}
.scale-input {
width: 47%;
margin: 0 1% 5px 0
}
.setting-input {
display: flex;
margin-bottom: 10px;
}
.setting-label {
font-size: 14px;
color: #606266;
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei";
font-weight: 700;
line-height: 20px;
margin: 0 0 14px 0;
}
.settings-container {
max-width: 1824px;
margin: auto;
.el-tabs {
margin-top: 20px
}
}
.settings-delete-button {
margin-left: 5px;
}
.settings-docs-button {
min-width: 163px;
text-align: left;
padding: 10px;
}
.settings-header {
margin: 10px 15px 15px 15px;
}
.header-sidebar-opened {
max-width: 1585px;
}
.header-sidebar-closed {
max-width: 1728px;
}
.settings-search-input {
width: 350px;
margin-left: 5px;
}
.single-input {
margin-right: 10px
}
.socks5-checkbox {
font-size: 14px;
color: #606266;
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei";
font-weight: 700;
margin-left: 10px;
}
.socks5-checkbox-container {
width: 40%;
height: 36px;
margin-right: 5px;
display: flex;
align-items: center;
}
.ssl-tls-opts {
margin: 36px 0 0 0;
}
.submit-button {
float: right;
margin: 0 30px 22px 0;
}
.submit-button-container {
width: 100%;
position: fixed;
bottom: 0px;
right: 0;
z-index: 2000;
}
.switch-input {
height: 36px;
}
.text {
line-height: 20px;
margin-right: 15px
}
.tuple-input {
margin-right: 15px;
}
.tuple-input:last-child {
margin-right: 0;
}
.tuple-input-container {
display: flex;
}
.upload-container {
display: flex;
align-items: baseline;
}
.value-input {
width: 70%;
margin-left: 8px;
margin-right: 10px
}
@media only screen and (min-width: 1824px) {
.header-sidebar-closed {
max-width: 1772px;
}
.header-sidebar-opened {
max-width: 1630px;
}
.reboot-button-container {
width: 100%;
max-width: inherit;
margin-left: auto;
margin-right: auto;
right: auto;
}
.reboot-sidebar-opened {
max-width: 1630px;
}
.reboot-sidebar-closed {
max-width: 1772px;
}
.sidebar-closed {
max-width: 1586px;
}
.sidebar-opened {
max-width: 1442px;
}
.submit-button-container {
width: 100%;
max-width: inherit;
margin-left: auto;
margin-right: auto;
right: auto;
}
}
@media only screen and (max-width:480px) {
.crontab {
width: 100%;
label {
width: 100%;
}
}
.delete-setting-button {
margin: 4px 0 0 5px;
height: 28px;
}
.delete-setting-button-container {
flex: 0 0 auto;
}
.description > p {
line-height: 18px;
margin: 0 5px 7px 15px;
code {
display: inline;
line-height: 18px;
padding: 2px 3px;
font-size: 14px;
}
}
.description-container {
margin: 0 15px 22px 15px;
}
.divider {
margin: 0 0 10px 0;
}
.divider .thick-line {
height: 2px;
}
.frontend-container {
margin: 0 15px 10px 15px;
.description-container {
margin: 0;
}
}
.frontend-form-input {
margin-top: 0;
}
h1 {
font-size: 24px;
}
.input {
flex: 1 1 auto;
}
.input-container {
width: 100%;
.el-form-item:first-child {
margin: 0;
padding: 0 15px 10px 15px;
}
.el-form-item.crontab-container:first-child {
margin: 0;
padding: 0 ;
}
.el-form-item:first-child .mascot-form-item {
padding: 0;
}
.el-form-item:first-child .rate-limit {
padding: 0;
}
.settings-delete-button {
margin-top: 4px;
float: right;
}
}
.input-row {
display: flex;
justify-content: space-between;
}
.label-with-margin {
margin-left: 15px;
}
.limit-input {
width: 45%;
}
.proxy-url-input {
flex-direction: column;
align-items: flex-start;
margin-bottom: 0;
}
.proxy-url-host-input {
width: 100%;
margin-bottom: 5px;
}
.proxy-url-value-input {
width: 100%;
margin-left: 0;
}
.prune-options {
flex-direction: column;
height: 80px;
}
.rate-limit {
.el-form-item__content {
flex-direction: column;
}
}
.rate-limit-content {
width: 100%;
}
.rate-limit-label {
float: left;
}
.rate-limit-label-container {
width: 100%;
}
.reboot-button {
margin: 0 15px 0 0;
}
.reboot-button-container {
top: 57px;
}
.scale-input {
width: 45%;
}
.settings-header {
width: fit-content;
display: inline-block;
margin: 10px 15px 15px 15px;
}
.settings-search-input {
margin: 0 15px 25px 15px;
width: stretch;
}
.socks5-checkbox-container {
width: 100%;
}
.submit-button {
margin: 0 15px 22px 0;
}
.el-input__inner {
padding: 0 5px 0 5px
}
.el-form-item__label:not(.no-top-margin) {
padding-bottom: 5px;
line-height: 22px;
margin-top: 7px;
width: 100%;
pointer-events: none;
span {
width: 100%;
display: flex;
justify-content: space-between;
align-items: baseline;
}
button {
pointer-events: auto;
}
}
.el-message {
min-width: 80%;
}
.el-message-box {
width: 80%;
}
.el-select__tags {
overflow: hidden;
}
.expl, .expl > p {
line-height: 16px;
}
.icon-key-input {
width: 40%;
margin-right: 4px
}
.icon-minus-button {
width: 28px;
height: 28px;
margin-top: 4px;
}
.icon-values-container {
margin: 0 7px 7px 0;
}
.icon-value-input {
width: 60%;
margin-left: 4px;
}
.icons-button-container {
line-height: 24px;
}
.line {
margin-bottom: 10px;
}
.mascot-form-item {
.el-form-item__label:not(.no-top-margin) {
margin: 0;
padding: 0;
}
}
.mascot-container {
margin-bottom: 5px;
}
.name-input {
width: 40%;
margin-right: 5px
}
p.expl {
line-height: 20px;
}
.pattern-input {
width: 40%;
margin-right: 4px
}
.replacement-input {
width: 60%;
margin-left: 4px;
margin-right: 5px
}
.settings-header-container {
display: flex;
justify-content: space-between;
margin-right: 15px;
}
.value-input {
width: 60%;
margin-left: 5px;
margin-right: 8px
}
}
@media only screen and (max-width:818px) and (min-width: 481px) {
.delete-setting-button {
margin: 4px 0 0 10px;
height: 28px;
}
.delete-setting-button-container {
flex: 0 0 auto;
}
.description > p {
line-height: 18px;
margin: 0 15px 10px 0;
}
.icon-minus-button {
width: 28px;
height: 28px;
margin-top: 4px;
}
.input {
flex: 1 1 auto;
}
.input-container {
.el-form-item__label {
span {
margin-left: 10px;
}
}
}
.input-row {
display: flex;
justify-content: space-between;
}
.rate-limit-content {
width: 65%;
}
.rate-limit-label-container {
width: 35%;
}
.settings-delete-button {
float: right;
}
.settings-header-container {
display: flex;
justify-content: space-between;
margin-right: 15px;
}
.settings-search-container {
display: flex;
justify-content: flex-end;
margin-right: 15px;
}
.settings-search-input {
width: 250px;
margin: 0 0 15px 15px;
}
}
}
@mixin tiptap {
.editor {
position: relative;
border-radius: 4px;
border: 1px solid #DCDFE6;
padding: 10px;
&__content {
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
padding-left: 10px;
* {
caret-color: currentColor;
}
pre {
border-radius: 5px;
font-size: 0.8rem;
overflow-x: auto;
code {
display: block;
}
}
p code {
border-radius: 5px;
font-size: 0.8rem;
font-weight: bold;
}
ul,
ol {
padding-left: 1rem;
}
li > p,
li > ol,
li > ul {
margin: 0;
}
a {
color: inherit;
}
blockquote {
border-left: 3px solid rgba(#000000, 0.1);
color: rgba(#000000, 0.8);
padding-left: 0.8rem;
font-style: italic;
p {
margin: 0;
}
}
img {
max-width: 100%;
border-radius: 3px;
}
table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
margin: 0;
overflow: hidden;
td, th {
min-width: 1em;
border: 2px solid #dddddd;
padding: 3px 5px;
vertical-align: top;
box-sizing: border-box;
position: relative;
> * {
margin-bottom: 0;
}
}
th {
font-weight: bold;
text-align: left;
}
.selectedCell:after {
z-index: 2;
position: absolute;
content: "";
left: 0; right: 0; top: 0; bottom: 0;
background: rgba(200, 200, 255, 0.4);
pointer-events: none;
}
.column-resize-handle {
position: absolute;
right: -2px; top: 0; bottom: 0;
width: 4px;
z-index: 20;
background-color: #adf;
pointer-events: none;
}
}
.tableWrapper {
margin: 1em 0;
overflow-x: auto;
}
.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}
}
}
.editor-form-item {
margin-right: 30px;
}
.menubar {
margin-bottom: 1rem;
transition: visibility 0.2s 0.4s, opacity 0.2s 0.4s;
&.is-hidden {
visibility: hidden;
opacity: 0;
}
&.is-focused {
visibility: visible;
opacity: 1;
transition: visibility 0.2s, opacity 0.2s;
}
&__button {
font-weight: bold;
display: inline-flex;
background: transparent;
border: 0;
color: #000000;
padding: 0.2rem 0.5rem;
margin-right: 0.2rem;
border-radius: 3px;
cursor: pointer;
&:hover {
background-color: rgba(#000000, 0.05);
}
&.is-active {
background-color: rgba(#000000, 0.1);
}
}
span#{&}__button {
font-size: 13.3333px;
}
}
}
@mixin emoji {
.create-pack {
display: flex;
justify-content: space-between
}
.create-pack-button {
margin-left: 10px;
}
.emoji-header-container {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 15px 22px 15px;
}
.emoji-name-warning {
color: #666666;
font-size: 13px;
line-height: 22px;
margin: 5px 0 0 0;
overflow-wrap: break-word;
overflow: hidden;
text-overflow: ellipsis;
}
.emoji-packs-header-button-container {
display: flex;
}
.emoji-packs-form {
margin-top: 15px;
}
.emoji-packs-header {
display: flex;
align-items: center;
justify-content: space-between;
margin: 10px 15px 15px 15px;
}
.emoji-packs-tabs {
margin: 0 15px 15px 15px;
}
.import-pack-button {
margin-left: 10px;
width: 30%;
max-width: 700px;
}
h1 {
margin: 0;
}
.line {
width: 100%;
height: 0;
border: 1px solid #eee;
margin-bottom: 22px;
}
.pagination {
margin: 25px 0;
text-align: center;
}
.reboot-button {
padding: 10px;
margin: 0;
width: 145px;
}
@media only screen and (min-width: 1824px) {
.emoji-packs {
max-width: 1824px;
margin: auto;
}
}
@media only screen and (max-width:480px) {
.create-pack {
height: 82px;
flex-direction: column;
}
.create-pack-button {
margin-left: 0;
}
.divider {
margin: 15px 0;
}
.el-message {
min-width: 80%;
}
.el-message-box {
width: 80%;
}
.emoji-header-container {
flex-direction: column;
align-items: flex-start;
}
.emoji-packs-form {
margin: 0 7px;
label {
padding-right: 8px;
}
.el-form-item {
margin-bottom: 15px;
}
}
.emoji-packs-header {
margin: 15px;
}
.emoji-packs-header-button-container {
height: 82px;
flex-direction: column;
.el-button+.el-button {
margin: 7px 0 0 0;
width: fit-content;
}
}
.import-pack-button {
width: 90%;
}
.reload-emoji-button {
width: fit-content;
}
}
}
const data = {
state: {
iconsMap: []
},
generate(iconsMap) {
this.state.iconsMap = iconsMap
}
}
export default data
<!--
SPDX-FileCopyrightText: 2017-2019 PanJiaChen <https://github.com/PanJiaChen/vue-element-admin>
SPDX-License-Identifier: MIT
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div class="icons-container">
<p class="warn-content">
<a href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/icon.html" target="_blank">Add and use
</a>
</p>
<div class="icons-wrapper">
<div v-for='item of iconsMap' :key='item' @click='handleClipboard(generateIconCode(item),$event)'>
<div v-for="item of iconsMap" :key="item" @click="handleClipboard(generateIconCode(item),$event)">
<el-tooltip placement="top">
<div slot="content">
{{generateIconCode(item)}}
{{ generateIconCode(item) }}
</div>
<div class='icon-item'>
<svg-icon :icon-class="item" />
<span>{{item}}</span>
<div class="icon-item">
<svg-icon :icon-class="item" class-name="disabled" />
<span>{{ item }}</span>
</div>
</el-tooltip>
</div>
......@@ -16,23 +28,17 @@
</div>
</template>
<script>
import icons from './generateIconsView'
import clipboard from '@/utils/clipboard' // use clipboard directly
import icons from './requireIcons'
import clipboard from '@/utils/clipboard'
export default {
data() {
name: 'Icons',
data: function() {
return {
iconsMap: []
iconsMap: icons
}
},
mounted() {
const iconsMap = icons.state.iconsMap.map((i) => {
return i.default.id.split('-')[1]
})
this.iconsMap = iconsMap
},
methods: {
generateIconCode(symbol) {
return `<svg-icon icon-class="${symbol}" />`
......@@ -46,7 +52,7 @@ export default {
<style rel="stylesheet/scss" lang="scss" scoped>
.icons-container {
margin: 40px 20px 0;
margin: 10px 20px 0;
overflow: hidden;
.icons-wrapper {
margin: 0 auto;
......@@ -66,5 +72,8 @@ export default {
font-size: 24px;
margin-top: 10px;
}
.disabled{
pointer-events: none;
}
}
</style>
// SPDX-FileCopyrightText: 2017-2019 PanJiaChen <https://github.com/PanJiaChen/vue-element-admin>
// SPDX-License-Identifier: MIT
const req = require.context('../../icons/svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys()
const re = /\.\/(.*)\.svg/
const icons = requireAll(req).map(i => {
return i.match(re)[1]
})
export default icons