commit
5e656cc0b4
@ -0,0 +1 @@
|
||||
Implemented a very basic instance administration screen
|
@ -0,0 +1,21 @@
|
||||
const ScreenReaderNotice = {
|
||||
props: {
|
||||
ariaLive: {
|
||||
type: String,
|
||||
defualt: 'assertive'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
currentText: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
announce (text) {
|
||||
this.currentText = text
|
||||
setTimeout(() => { this.currentText = '' }, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ScreenReaderNotice
|
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div
|
||||
class="visible-for-screenreader-only"
|
||||
:aria-live="ariaLive"
|
||||
>
|
||||
{{ currentText }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./screen_reader_notice.js"></script>
|
@ -0,0 +1,64 @@
|
||||
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||
import ChoiceSetting from '../helpers/choice_setting.vue'
|
||||
import IntegerSetting from '../helpers/integer_setting.vue'
|
||||
import StringSetting from '../helpers/string_setting.vue'
|
||||
import GroupSetting from '../helpers/group_setting.vue'
|
||||
import Popover from 'src/components/popover/popover.vue'
|
||||
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faGlobe
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faGlobe
|
||||
)
|
||||
|
||||
const FrontendsTab = {
|
||||
provide () {
|
||||
return {
|
||||
defaultDraftMode: true,
|
||||
defaultSource: 'admin'
|
||||
}
|
||||
},
|
||||
components: {
|
||||
BooleanSetting,
|
||||
ChoiceSetting,
|
||||
IntegerSetting,
|
||||
StringSetting,
|
||||
GroupSetting,
|
||||
Popover
|
||||
},
|
||||
created () {
|
||||
if (this.user.rights.admin) {
|
||||
this.$store.dispatch('loadFrontendsStuff')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
frontends () {
|
||||
return this.$store.state.adminSettings.frontends
|
||||
},
|
||||
...SharedComputedObject()
|
||||
},
|
||||
methods: {
|
||||
update (frontend, suggestRef) {
|
||||
const ref = suggestRef || frontend.refs[0]
|
||||
const { name } = frontend
|
||||
const payload = { name, ref }
|
||||
|
||||
this.$store.state.api.backendInteractor.installFrontend({ payload })
|
||||
.then((externalUser) => {
|
||||
this.$store.dispatch('loadFrontendsStuff')
|
||||
})
|
||||
},
|
||||
setDefault (frontend, suggestRef) {
|
||||
const ref = suggestRef || frontend.refs[0]
|
||||
const { name } = frontend
|
||||
|
||||
this.$store.commit('updateAdminDraft', { path: [':pleroma', ':frontends', ':primary'], value: { name, ref } })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FrontendsTab
|
@ -0,0 +1,13 @@
|
||||
.frontends-tab {
|
||||
.cards-list {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dd {
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
max-width: 10em;
|
||||
}
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<div
|
||||
class="frontends-tab"
|
||||
:label="$t('admin_dash.tabs.frontends')"
|
||||
>
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('admin_dash.tabs.frontends') }}</h2>
|
||||
<p>{{ $t('admin_dash.frontend.wip_notice') }}</p>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<h3>{{ $t('admin_dash.frontend.default_frontend') }}</h3>
|
||||
<p>{{ $t('admin_dash.frontend.default_frontend_tip') }}</p>
|
||||
<p>{{ $t('admin_dash.frontend.default_frontend_tip2') }}</p>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<StringSetting path=":pleroma.:frontends.:primary.name" />
|
||||
</li>
|
||||
<li>
|
||||
<StringSetting path=":pleroma.:frontends.:primary.ref" />
|
||||
</li>
|
||||
<li>
|
||||
<GroupSetting path=":pleroma.:frontends.:primary" />
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="setting-list">
|
||||
<h3>{{ $t('admin_dash.frontend.available_frontends') }}</h3>
|
||||
<ul class="cards-list">
|
||||
<li
|
||||
v-for="frontend in frontends"
|
||||
:key="frontend.name"
|
||||
>
|
||||
<strong>{{ frontend.name }}</strong>
|
||||
{{ ' ' }}
|
||||
<span v-if="adminDraft[':pleroma'][':frontends'][':primary'].name === frontend.name">
|
||||
<i18n-t
|
||||
v-if="adminDraft[':pleroma'][':frontends'][':primary'].ref === frontend.refs[0]"
|
||||
keypath="admin_dash.frontend.is_default"
|
||||
/>
|
||||
<i18n-t
|
||||
v-else
|
||||
keypath="admin_dash.frontend.is_default_custom"
|
||||
>
|
||||
<template #version>
|
||||
<code>{{ adminDraft[':pleroma'][':frontends'][':primary'].ref }}</code>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</span>
|
||||
<dl>
|
||||
<dt>{{ $t('admin_dash.frontend.repository') }}</dt>
|
||||
<dd>
|
||||
<a
|
||||
:href="frontend.git"
|
||||
target="_blank"
|
||||
>{{ frontend.git }}</a>
|
||||
</dd>
|
||||
<template v-if="expertLevel">
|
||||
<dt>{{ $t('admin_dash.frontend.versions') }}</dt>
|
||||
<dd
|
||||
v-for="ref in frontend.refs"
|
||||
:key="ref"
|
||||
>
|
||||
<code>{{ ref }}</code>
|
||||
</dd>
|
||||
</template>
|
||||
<dt v-if="expertLevel">
|
||||
{{ $t('admin_dash.frontend.build_url') }}
|
||||
</dt>
|
||||
<dd v-if="expertLevel">
|
||||
<a
|
||||
:href="frontend.build_url"
|
||||
target="_blank"
|
||||
>{{ frontend.build_url }}</a>
|
||||
</dd>
|
||||
</dl>
|
||||
<div>
|
||||
<span class="btn-group">
|
||||
<button
|
||||
class="button button-default btn"
|
||||
type="button"
|
||||
@click="update(frontend)"
|
||||
>
|
||||
{{
|
||||
frontend.installed
|
||||
? $t('admin_dash.frontend.reinstall')
|
||||
: $t('admin_dash.frontend.install')
|
||||
}}
|
||||
</button>
|
||||
<Popover
|
||||
v-if="frontend.refs.length > 1"
|
||||
trigger="click"
|
||||
class="button-dropdown"
|
||||
placement="bottom"
|
||||
>
|
||||
<template #content>
|
||||
<div class="dropdown-menu">
|
||||
<button
|
||||
v-for="ref in frontend.refs"
|
||||
:key="ref"
|
||||
class="button-default dropdown-item"
|
||||
@click="update(frontend, ref)"
|
||||
>
|
||||
<i18n-t keypath="admin_dash.frontend.install_version">
|
||||
<template #version>
|
||||
<code>{{ ref }}</code>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template #trigger>
|
||||
<button
|
||||
class="button button-default btn dropdown-button"
|
||||
type="button"
|
||||
:title="$t('admin_dash.frontend.more_install_options')"
|
||||
>
|
||||
<FAIcon icon="chevron-down" />
|
||||
</button>
|
||||
</template>
|
||||
</Popover>
|
||||
</span>
|
||||
<span
|
||||
v-if="frontend.installed && frontend.name !== 'admin-fe'"
|
||||
class="btn-group"
|
||||
>
|
||||
<button
|
||||
class="button button-default btn"
|
||||
type="button"
|
||||
:disabled="
|
||||
adminDraft[':pleroma'][':frontends'][':primary'].name === frontend.name &&
|
||||
adminDraft[':pleroma'][':frontends'][':primary'].ref === frontend.refs[0]
|
||||
"
|
||||
@click="setDefault(frontend)"
|
||||
>
|
||||
{{
|
||||
$t('admin_dash.frontend.set_default')
|
||||
}}
|
||||
</button>
|
||||
{{ ' ' }}
|
||||
<Popover
|
||||
v-if="frontend.refs.length > 1"
|
||||
trigger="click"
|
||||
class="button-dropdown"
|
||||
placement="bottom"
|
||||
>
|
||||
<template #content>
|
||||
<div class="dropdown-menu">
|
||||
<button
|
||||
v-for="ref in frontend.refs.slice(1)"
|
||||
:key="ref"
|
||||
class="button-default dropdown-item"
|
||||
@click="setDefault(frontend, ref)"
|
||||
>
|
||||
<i18n-t keypath="admin_dash.frontend.set_default_version">
|
||||
<template #version>
|
||||
<code>{{ ref }}</code>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template #trigger>
|
||||
<button
|
||||
class="button button-default btn dropdown-button"
|
||||
type="button"
|
||||
:title="$t('admin_dash.frontend.more_default_options')"
|
||||
>
|
||||
<FAIcon icon="chevron-down" />
|
||||
</button>
|
||||
</template>
|
||||
</Popover>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./frontends_tab.js"></script>
|
||||
|
||||
<style lang="scss" src="./frontends_tab.scss"></style>
|
@ -0,0 +1,38 @@
|
||||
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||
import ChoiceSetting from '../helpers/choice_setting.vue'
|
||||
import IntegerSetting from '../helpers/integer_setting.vue'
|
||||
import StringSetting from '../helpers/string_setting.vue'
|
||||
import GroupSetting from '../helpers/group_setting.vue'
|
||||
import AttachmentSetting from '../helpers/attachment_setting.vue'
|
||||
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faGlobe
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faGlobe
|
||||
)
|
||||
|
||||
const InstanceTab = {
|
||||
provide () {
|
||||
return {
|
||||
defaultDraftMode: true,
|
||||
defaultSource: 'admin'
|
||||
}
|
||||
},
|
||||
components: {
|
||||
BooleanSetting,
|
||||
ChoiceSetting,
|
||||
IntegerSetting,
|
||||
StringSetting,
|
||||
AttachmentSetting,
|
||||
GroupSetting
|
||||
},
|
||||
computed: {
|
||||
...SharedComputedObject()
|
||||
}
|
||||
}
|
||||
|
||||
export default InstanceTab
|
@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<div :label="$t('admin_dash.tabs.instance')">
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('admin_dash.instance.instance') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<StringSetting path=":pleroma.:instance.:name" />
|
||||
</li>
|
||||
<li>
|
||||
<StringSetting path=":pleroma.:instance.:email" />
|
||||
</li>
|
||||
<li>
|
||||
<StringSetting path=":pleroma.:instance.:description" />
|
||||
</li>
|
||||
<li>
|
||||
<StringSetting path=":pleroma.:instance.:short_description" />
|
||||
</li>
|
||||
<li>
|
||||
<AttachmentSetting path=":pleroma.:instance.:instance_thumbnail" />
|
||||
</li>
|
||||
<li>
|
||||
<AttachmentSetting path=":pleroma.:instance.:background_image" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('admin_dash.instance.registrations') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting path=":pleroma.:instance.:registrations_open" />
|
||||
<ul class="setting-list suboptions">
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path=":pleroma.:instance.:invites_enabled"
|
||||
parent-path=":pleroma.:instance.:registrations_open"
|
||||
parent-invert
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path=":pleroma.:instance.:birthday_required" />
|
||||
<ul class="setting-list suboptions">
|
||||
<li>
|
||||
<IntegerSetting
|
||||
path=":pleroma.:instance.:birthday_min_age"
|
||||
parent-path=":pleroma.:instance.:birthday_required"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path=":pleroma.:instance.:account_activation_required" />
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path=":pleroma.:instance.:account_approval_required" />
|
||||
</li>
|
||||
<li>
|
||||
<h3>{{ $t('admin_dash.instance.captcha_header') }}</h3>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting :path="[':pleroma', 'Pleroma.Captcha', ':enabled']" />
|
||||
<ul class="setting-list suboptions">
|
||||
<li>
|
||||
<ChoiceSetting
|
||||
:path="[':pleroma', 'Pleroma.Captcha', ':method']"
|
||||
:parent-path="[':pleroma', 'Pleroma.Captcha', ':enabled']"
|
||||
:option-label-map="{
|
||||
'Pleroma.Captcha.Native': $t('admin_dash.captcha.native'),
|
||||
'Pleroma.Captcha.Kocaptcha': $t('admin_dash.captcha.kocaptcha')
|
||||
}"
|
||||
/>
|
||||
<IntegerSetting
|
||||
:path="[':pleroma', 'Pleroma.Captcha', ':seconds_valid']"
|
||||
:parent-path="[':pleroma', 'Pleroma.Captcha', ':enabled']"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
v-if="adminDraft[':pleroma']['Pleroma.Captcha'][':enabled'] && adminDraft[':pleroma']['Pleroma.Captcha'][':method'] === 'Pleroma.Captcha.Kocaptcha'"
|
||||
>
|
||||
<h4>{{ $t('admin_dash.instance.kocaptcha') }}</h4>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<StringSetting :path="[':pleroma', 'Pleroma.Captcha.Kocaptcha', ':endpoint']" />
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('admin_dash.instance.access') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting
|
||||
override-backend-description
|
||||
override-backend-description-label
|
||||
path=":pleroma.:instance.:public"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<ChoiceSetting
|
||||
override-backend-description
|
||||
override-backend-description-label
|
||||
path=":pleroma.:instance.:limit_to_local_content"
|
||||
/>
|
||||
</li>
|
||||
<li v-if="expertLevel">
|
||||
<h3>{{ $t('admin_dash.instance.restrict.header') }}</h3>
|
||||
<p>
|
||||
{{ $t('admin_dash.instance.restrict.description') }}
|
||||
</p>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<h4>{{ $t('admin_dash.instance.restrict.timelines') }}</h4>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path=":pleroma.:restrict_unauthenticated.:timelines.:local"
|
||||
indeterminate-state=":if_instance_is_private"
|
||||
swap-description-and-label
|
||||
hide-description
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path=":pleroma.:restrict_unauthenticated.:timelines.:federated"
|
||||
indeterminate-state=":if_instance_is_private"
|
||||
swap-description-and-label
|
||||
hide-description
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<GroupSetting path=":pleroma.:restrict_unauthenticated.:timelines" />
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h4>{{ $t('admin_dash.instance.restrict.profiles') }}</h4>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path=":pleroma.:restrict_unauthenticated.:profiles.:local"
|
||||
indeterminate-state=":if_instance_is_private"
|
||||
swap-description-and-label
|
||||
hide-description
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path=":pleroma.:restrict_unauthenticated.:profiles.:remote"
|
||||
indeterminate-state=":if_instance_is_private"
|
||||
swap-description-and-label
|
||||
hide-description
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<GroupSetting path=":pleroma.:restrict_unauthenticated.:profiles" />
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h4>{{ $t('admin_dash.instance.restrict.activities') }}</h4>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path=":pleroma.:restrict_unauthenticated.:activities.:local"
|
||||
indeterminate-state=":if_instance_is_private"
|
||||
swap-description-and-label
|
||||
hide-description
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path=":pleroma.:restrict_unauthenticated.:activities.:remote"
|
||||
indeterminate-state=":if_instance_is_private"
|
||||
swap-description-and-label
|
||||
hide-description
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<GroupSetting path=":pleroma.:restrict_unauthenticated.:activities" />
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./instance_tab.js"></script>
|
@ -0,0 +1,29 @@
|
||||
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||
import ChoiceSetting from '../helpers/choice_setting.vue'
|
||||
import IntegerSetting from '../helpers/integer_setting.vue'
|
||||
import StringSetting from '../helpers/string_setting.vue'
|
||||
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faGlobe
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faGlobe
|
||||
)
|
||||
|
||||
const LimitsTab = {
|
||||
data () {},
|
||||
components: {
|
||||
BooleanSetting,
|
||||
ChoiceSetting,
|
||||
IntegerSetting,
|
||||
StringSetting
|
||||
},
|
||||
computed: {
|
||||
...SharedComputedObject()
|
||||
}
|
||||
}
|
||||
|
||||
export default LimitsTab
|
@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div :label="$t('admin_dash.tabs.limits')">
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('admin_dash.limits.arbitrary_limits') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<h3>{{ $t('admin_dash.limits.posts') }}</h3>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<IntegerSetting
|
||||
source="admin"
|
||||
path=":pleroma.:instance.:limit"
|
||||
draft-mode
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<IntegerSetting
|
||||
source="admin"
|
||||
path=":pleroma.:instance.:remote_limit"
|
||||
expert="1"
|
||||
draft-mode
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h3>{{ $t('admin_dash.limits.uploads') }}</h3>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<IntegerSetting
|
||||
source="admin"
|
||||
path=":pleroma.:instance.:description_limit"
|
||||
draft-mode
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<IntegerSetting
|
||||
source="admin"
|
||||
path=":pleroma.:instance.:upload_limit"
|
||||
draft-mode
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<IntegerSetting
|
||||
source="admin"
|
||||
path=":pleroma.:instance.:max_media_attachments"
|
||||
draft-mode
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h3>{{ $t('admin_dash.limits.users') }}</h3>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<IntegerSetting
|
||||
source="admin"
|
||||
path=":pleroma.:instance.:max_pinned_statuses"
|
||||
draft-mode
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<IntegerSetting
|
||||
source="admin"
|
||||
path=":pleroma.:instance.:user_bio_length"
|
||||
draft-mode
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<IntegerSetting
|
||||
source="admin"
|
||||
path=":pleroma.:instance.:user_name_length"
|
||||
draft-mode
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<h4>{{ $t('admin_dash.limits.profile_fields') }}</h4>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<IntegerSetting
|
||||
source="admin"
|
||||
path=":pleroma.:instance.:max_account_fields"
|
||||
draft-mode
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<IntegerSetting
|
||||
source="admin"
|
||||
path=":pleroma.:instance.:max_remote_account_fields"
|
||||
draft-mode
|
||||
expert="1"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<IntegerSetting
|
||||
source="admin"
|
||||
path=":pleroma.:instance.:account_field_name_length"
|
||||
draft-mode
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<IntegerSetting
|
||||
source="admin"
|
||||
path=":pleroma.:instance.:account_field_value_length"
|
||||
draft-mode
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h4>{{ $t('admin_dash.limits.user_uploads') }}</h4>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<IntegerSetting
|
||||
source="admin"
|
||||
path=":pleroma.:instance.:avatar_upload_limit"
|
||||
draft-mode
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<IntegerSetting
|
||||
source="admin"
|
||||
path=":pleroma.:instance.:banner_upload_limit"
|
||||
draft-mode
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./limits_tab.js"></script>
|
@ -0,0 +1,43 @@
|
||||
import Setting from './setting.js'
|
||||
import { fileTypeExt } from 'src/services/file_type/file_type.service.js'
|
||||
import MediaUpload from 'src/components/media_upload/media_upload.vue'
|
||||
import Attachment from 'src/components/attachment/attachment.vue'
|
||||
|
||||
export default {
|
||||
...Setting,
|
||||
props: {
|
||||
...Setting.props,
|
||||
acceptTypes: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'image/*'
|
||||
}
|
||||
},
|
||||
components: {
|
||||
...Setting.components,
|
||||
MediaUpload,
|
||||
Attachment
|
||||
},
|
||||
computed: {
|
||||
...Setting.computed,
|
||||
attachment () {
|
||||
const path = this.realDraftMode ? this.draft : this.state
|
||||
// The "server" part is primarily for local dev, but could be useful for alt-domain or multiuser usage.
|
||||
const url = path.includes('://') ? path : this.$store.state.instance.server + path
|
||||
return {
|
||||
mimetype: fileTypeExt(url),
|
||||
url
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...Setting.methods,
|
||||
setMediaFile (fileInfo) {
|
||||
if (this.realDraftMode) {
|
||||
this.draft = fileInfo.url
|
||||
} else {
|
||||
this.configSink(this.path, fileInfo.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<span
|
||||
v-if="matchesExpertLevel"
|
||||
class="AttachmentSetting"
|
||||
>
|
||||
<label
|
||||
:for="path"
|
||||
:class="{ 'faint': shouldBeDisabled }"
|
||||
>
|
||||
<template v-if="backendDescriptionLabel">
|
||||
{{ backendDescriptionLabel + ' ' }}
|
||||
</template>
|
||||
<template v-else-if="source === 'admin'">
|
||||
MISSING LABEL FOR {{ path }}
|
||||
</template>
|
||||
<slot v-else />
|
||||
|
||||
</label>
|
||||
<p
|
||||
v-if="backendDescriptionDescription"
|
||||
class="setting-description"
|
||||
:class="{ 'faint': shouldBeDisabled }"
|
||||
>
|
||||
{{ backendDescriptionDescription + ' ' }}
|
||||
</p>
|
||||
<div class="attachment-input">
|
||||
<div>{{ $t('settings.url') }}</div>
|
||||
<div class="controls">
|
||||
<input
|
||||
:id="path"
|
||||
class="string-input"
|
||||
:disabled="shouldBeDisabled"
|
||||
:value="realDraftMode ? draft : state"
|
||||
@change="update"
|
||||
>
|
||||
{{ ' ' }}
|
||||
<ModifiedIndicator
|
||||
:changed="isChanged"
|
||||
:onclick="reset"
|
||||
/>
|
||||
<ProfileSettingIndicator :is-profile="isProfileSetting" />
|
||||
</div>
|
||||
<div>{{ $t('settings.preview') }}</div>
|
||||
<Attachment
|
||||
class="attachment"
|
||||
:compact="compact"
|
||||
:attachment="attachment"
|
||||
size="small"
|
||||
hide-description
|
||||
@setMedia="onMedia"
|
||||
@naturalSizeLoad="onNaturalSizeLoad"
|
||||
/>
|
||||
<div class="controls">
|
||||
<MediaUpload
|
||||
ref="mediaUpload"
|
||||
class="media-upload-icon"
|
||||
:drop-files="dropFiles"
|
||||
normal-button
|
||||
:accept-types="acceptTypes"
|
||||
@uploaded="setMediaFile"
|
||||
@upload-failed="uploadFailed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DraftButtons />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script src="./attachment_setting.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.AttachmentSetting {
|
||||
.attachment {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 15em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.attachment-input {
|
||||
margin-left: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
input,
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,56 +1,31 @@
|
||||
import { get, set } from 'lodash'
|
||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||
import ModifiedIndicator from './modified_indicator.vue'
|
||||
import ServerSideIndicator from './server_side_indicator.vue'
|
||||
import Setting from './setting.js'
|
||||
|
||||
export default {
|
||||
...Setting,
|
||||
props: {
|
||||
...Setting.props,
|
||||
indeterminateState: [String, Object]
|
||||
},
|
||||
components: {
|
||||
Checkbox,
|
||||
ModifiedIndicator,
|
||||
ServerSideIndicator
|
||||
...Setting.components,
|
||||
Checkbox
|
||||
},
|
||||
props: [
|
||||
'path',
|
||||
'disabled',
|
||||
'expert'
|
||||
],
|
||||
computed: {
|
||||
pathDefault () {
|
||||
const [firstSegment, ...rest] = this.path.split('.')
|
||||
return [firstSegment + 'DefaultValue', ...rest].join('.')
|
||||
},
|
||||
state () {
|
||||
const value = get(this.$parent, this.path)
|
||||
if (value === undefined) {
|
||||
return this.defaultState
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
},
|
||||
defaultState () {
|
||||
return get(this.$parent, this.pathDefault)
|
||||
},
|
||||
isServerSide () {
|
||||
return this.path.startsWith('serverSide_')
|
||||
},
|
||||
isChanged () {
|
||||
return !this.path.startsWith('serverSide_') && this.state !== this.defaultState
|
||||
},
|
||||
matchesExpertLevel () {
|
||||
return (this.expert || 0) <= this.$parent.expertLevel
|
||||
...Setting.computed,
|
||||
isIndeterminate () {
|
||||
return this.visibleState === this.indeterminateState
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update (e) {
|
||||
const [firstSegment, ...rest] = this.path.split('.')
|
||||
set(this.$parent, this.path, e)
|
||||
// Updating nested properties does not trigger update on its parent.
|
||||
// probably still not as reliable, but works for depth=1 at least
|
||||
if (rest.length > 0) {
|
||||
set(this.$parent, firstSegment, { ...get(this.$parent, firstSegment) })
|
||||
...Setting.methods,
|
||||
getValue (e) {
|
||||
// Basic tri-state toggle implementation
|
||||
if (!!this.indeterminateState && !e && this.visibleState === true) {
|
||||
// If we have indeterminate state, switching from true to false first goes through indeterminate
|
||||
return this.indeterminateState
|
||||
}
|
||||
},
|
||||
reset () {
|
||||
set(this.$parent, this.path, this.defaultState)
|
||||
return e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,51 +1,41 @@
|
||||
import { get, set } from 'lodash'
|
||||
import Select from 'src/components/select/select.vue'
|
||||
import ModifiedIndicator from './modified_indicator.vue'
|
||||
import ServerSideIndicator from './server_side_indicator.vue'
|
||||
import Setting from './setting.js'
|
||||
|
||||
export default {
|
||||
...Setting,
|
||||
components: {
|
||||
Select,
|
||||
ModifiedIndicator,
|
||||
ServerSideIndicator
|
||||
...Setting.components,
|
||||
Select
|
||||
},
|
||||
props: {
|
||||
...Setting.props,
|
||||
options: {
|
||||
type: Array,
|
||||
required: false
|
||||
},
|
||||
optionLabelMap: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: {}
|
||||
}
|
||||
},
|
||||
props: [
|
||||
'path',
|
||||
'disabled',
|
||||
'options',
|
||||
'expert'
|
||||
],
|
||||
computed: {
|
||||
pathDefault () {
|
||||
const [firstSegment, ...rest] = this.path.split('.')
|
||||
return [firstSegment + 'DefaultValue', ...rest].join('.')
|
||||
},
|
||||
state () {
|
||||
const value = get(this.$parent, this.path)
|
||||
if (value === undefined) {
|
||||
return this.defaultState
|
||||
} else {
|
||||
return value
|
||||
...Setting.computed,
|
||||
realOptions () {
|
||||
if (this.realSource === 'admin') {
|
||||
return this.backendDescriptionSuggestions.map(x => ({
|
||||
key: x,
|
||||
value: x,
|
||||
label: this.optionLabelMap[x] || x
|
||||
}))
|
||||
}
|
||||
},
|
||||
defaultState () {
|
||||
return get(this.$parent, this.pathDefault)
|
||||
},
|
||||
isServerSide () {
|
||||
return this.path.startsWith('serverSide_')
|
||||
},
|
||||
isChanged () {
|
||||
return !this.path.startsWith('serverSide_') && this.state !== this.defaultState
|
||||
},
|
||||
matchesExpertLevel () {
|
||||
return (this.expert || 0) <= this.$parent.expertLevel
|
||||
return this.options
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update (e) {
|
||||
set(this.$parent, this.path, e)
|
||||
},
|
||||
reset () {
|
||||
set(this.$parent, this.path, this.defaultState)
|
||||
...Setting.methods,
|
||||
getValue (e) {
|
||||
return e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,88 @@
|
||||
<!-- this is a helper exclusive to Setting components -->
|
||||
<!-- TODO make it reusable -->
|
||||
<template>
|
||||
<span
|
||||
class="DraftButtons"
|
||||
>
|
||||
<Popover
|
||||
v-if="$parent.isDirty"
|
||||
trigger="hover"
|
||||
normal-button
|
||||
:trigger-attrs="{ 'aria-label': $t('settings.commit_value_tooltip') }"
|
||||
@click="$parent.commitDraft"
|
||||
>
|
||||
<template #trigger>
|
||||
{{ $t('settings.commit_value') }}
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="modified-tooltip">
|
||||
{{ $t('settings.commit_value_tooltip') }}
|
||||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
<Popover
|
||||
v-if="$parent.isDirty"
|
||||
trigger="hover"
|
||||
normal-button
|
||||
:trigger-attrs="{ 'aria-label': $t('settings.reset_value_tooltip') }"
|
||||
@click="$parent.reset"
|
||||
>
|
||||
<template #trigger>
|
||||
{{ $t('settings.reset_value') }}
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="modified-tooltip">
|
||||
{{ $t('settings.reset_value_tooltip') }}
|
||||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
<Popover
|
||||
v-if="$parent.canHardReset"
|
||||
trigger="hover"
|
||||
normal-button
|
||||
:trigger-attrs="{ 'aria-label': $t('settings.hard_reset_value_tooltip') }"
|
||||
@click="$parent.hardReset"
|
||||
>
|
||||
<template #trigger>
|
||||
{{ $t('settings.hard_reset_value') }}
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="modified-tooltip">
|
||||
{{ $t('settings.hard_reset_value_tooltip') }}
|
||||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Popover from 'src/components/popover/popover.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faWrench } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faWrench
|
||||
)
|
||||
|
||||
export default {
|
||||
components: { Popover },
|
||||
props: ['changed']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.DraftButtons {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
.button-default {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.draft-tooltip {
|
||||
margin: 0.5em 1em;
|
||||
min-width: 10em;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<NumberSetting
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<slot />
|
||||
</NumberSetting>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NumberSetting from './number_setting.vue'
|
||||
export default {
|
||||
components: {
|
||||
NumberSetting
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,13 @@
|
||||
import { isEqual } from 'lodash'
|
||||
|
||||
import Setting from './setting.js'
|
||||
|
||||
export default {
|
||||
...Setting,
|
||||
computed: {
|
||||
...Setting.computed,
|
||||
isDirty () {
|
||||
return !isEqual(this.state, this.draft)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<span
|
||||
v-if="matchesExpertLevel"
|
||||
class="GroupSetting"
|
||||
>
|
||||
<ModifiedIndicator
|
||||
:changed="isChanged"
|
||||
:onclick="reset"
|
||||
/>
|
||||
<ProfileSettingIndicator :is-profile="isProfileSetting" />
|
||||
<DraftButtons />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script src="./group_setting.js"></script>
|
@ -1,44 +0,0 @@
|
||||
import { get, set } from 'lodash'
|
||||
import ModifiedIndicator from './modified_indicator.vue'
|
||||
export default {
|
||||
components: {
|
||||
ModifiedIndicator
|
||||
},
|
||||
props: {
|
||||
path: String,
|
||||
disabled: Boolean,
|
||||
min: Number,
|
||||
expert: [Number, String]
|
||||
},
|
||||
computed: {
|
||||
pathDefault () {
|
||||
const [firstSegment, ...rest] = this.path.split('.')
|
||||
return [firstSegment + 'DefaultValue', ...rest].join('.')
|
||||
},
|
||||
state () {
|
||||
const value = get(this.$parent, this.path)
|
||||
if (value === undefined) {
|
||||
return this.defaultState
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
},
|
||||
defaultState () {
|
||||
return get(this.$parent, this.pathDefault)
|
||||
},
|
||||
isChanged () {
|
||||
return this.state !== this.defaultState
|
||||
},
|
||||
matchesExpertLevel () {
|
||||
return (this.expert || 0) <= this.$parent.expertLevel
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update (e) {
|
||||
set(this.$parent, this.path, parseInt(e.target.value))
|
||||
},
|
||||
reset () {
|
||||
set(this.$parent, this.path, this.defaultState)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +1,17 @@
|
||||
<template>
|
||||
<span
|
||||
v-if="matchesExpertLevel"
|
||||
class="IntegerSetting"
|
||||
<NumberSetting
|
||||
v-bind="$attrs"
|
||||
truncate="1"
|
||||
>
|
||||
<label :for="path">
|
||||
<slot />
|
||||
</label>
|
||||
<input
|
||||
:id="path"
|
||||
class="number-input"
|
||||
type="number"
|
||||
step="1"
|
||||
:disabled="disabled"
|
||||
:min="min || 0"
|
||||
:value="state"
|
||||
@change="update"
|
||||
>
|
||||
{{ ' ' }}
|
||||
<ModifiedIndicator
|
||||
:changed="isChanged"
|
||||
:onclick="reset"
|
||||
/>
|
||||
</span>
|
||||
</NumberSetting>
|
||||
</template>
|
||||
|
||||
<script src="./integer_setting.js"></script>
|
||||
<script>
|
||||
import NumberSetting from './number_setting.vue'
|
||||
export default {
|
||||
components: {
|
||||
NumberSetting
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -0,0 +1,24 @@
|
||||
import Setting from './setting.js'
|
||||
|
||||
export default {
|
||||
...Setting,
|
||||
props: {
|
||||
...Setting.props,
|
||||
truncate: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...Setting.methods,
|
||||
getValue (e) {
|
||||
if (!this.truncate === 1) {
|
||||
return parseInt(e.target.value)
|
||||
} else if (this.truncate > 1) {
|
||||
return Math.trunc(e.target.value / this.truncate) * this.truncate
|
||||
}
|
||||
return parseFloat(e.target.value)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<span
|
||||
v-if="matchesExpertLevel"
|
||||
class="NumberSetting"
|
||||
>
|
||||
<label
|
||||
:for="path"
|
||||
:class="{ 'faint': shouldBeDisabled }"
|
||||
>
|
||||
<template v-if="backendDescriptionLabel">
|
||||
{{ backendDescriptionLabel + ' ' }}
|
||||
</template>
|
||||
<template v-else-if="source === 'admin'">
|
||||
MISSING LABEL FOR {{ path }}
|
||||
</template>
|
||||
<slot v-else />
|
||||
</label>
|
||||
<input
|
||||
:id="path"
|
||||
class="number-input"
|
||||
type="number"
|
||||
:step="step || 1"
|
||||
:disabled="shouldBeDisabled"
|
||||
:min="min || 0"
|
||||
:value="realDraftMode ? draft :state"
|
||||
@change="update"
|
||||
>
|
||||
{{ ' ' }}
|
||||
<ModifiedIndicator
|
||||
:changed="isChanged"
|
||||
:onclick="reset"
|
||||
/>
|
||||
<ProfileSettingIndicator :is-profile="isProfileSetting" />
|
||||
<DraftButtons />
|
||||
<p
|
||||
v-if="backendDescriptionDescription"
|
||||
class="setting-description"
|
||||
:class="{ 'faint': shouldBeDisabled }"
|
||||
>
|
||||
{{ backendDescriptionDescription + ' ' }}
|
||||
</p>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script src="./number_setting.js"></script>
|
@ -0,0 +1,237 @@
|
||||
import ModifiedIndicator from './modified_indicator.vue'
|
||||
import ProfileSettingIndicator from './profile_setting_indicator.vue'
|
||||
import DraftButtons from './draft_buttons.vue'
|
||||
import { get, set, cloneDeep } from 'lodash'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ModifiedIndicator,
|
||||
DraftButtons,
|
||||
ProfileSettingIndicator
|
||||
},
|
||||
props: {
|
||||
path: {
|
||||
type: [String, Array],
|
||||
required: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
parentPath: {
|
||||
type: [String, Array]
|
||||
},
|
||||
parentInvert: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
expert: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
source: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
hideDescription: {
|
||||
type: Boolean
|
||||
},
|
||||
swapDescriptionAndLabel: {
|
||||
type: Boolean
|
||||
},
|
||||
overrideBackendDescription: {
|
||||
type: Boolean
|
||||
},
|
||||
overrideBackendDescriptionLabel: {
|
||||
type: Boolean
|
||||
},
|
||||
draftMode: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
inject: {
|
||||
defaultSource: {
|
||||
default: 'default'
|
||||
},
|
||||
defaultDraftMode: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localDraft: null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.realDraftMode && this.realSource !== 'admin') {
|
||||
this.draft = this.state
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
draft: {
|
||||
// TODO allow passing shared draft object?
|
||||
get () {
|
||||
if (this.realSource === 'admin') {
|
||||
return get(this.$store.state.adminSettings.draft, this.canonPath)
|
||||
} else {
|
||||
return this.localDraft
|
||||
}
|
||||
},
|
||||
set (value) {
|
||||
if (this.realSource === 'admin') {
|
||||
this.$store.commit('updateAdminDraft', { path: this.canonPath, value })
|
||||
} else {
|
||||
this.localDraft = value
|
||||
}
|
||||
}
|
||||
},
|
||||
state () {
|
||||
const value = get(this.configSource, this.canonPath)
|
||||
if (value === undefined) {
|
||||
return this.defaultState
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
},
|
||||
visibleState () {
|
||||
return this.realDraftMode ? this.draft : this.state
|
||||
},
|
||||
realSource () {
|
||||
return this.source || this.defaultSource
|
||||
},
|
||||
realDraftMode () {
|
||||
return typeof this.draftMode === 'undefined' ? this.defaultDraftMode : this.draftMode
|
||||
},
|
||||
backendDescription () {
|
||||
return get(this.$store.state.adminSettings.descriptions, this.path)
|
||||
},
|
||||
backendDescriptionLabel () {
|
||||
if (this.realSource !== 'admin') return ''
|
||||
if (!this.backendDescription || this.overrideBackendDescriptionLabel) {
|
||||
return this.$t([
|
||||
'admin_dash',
|
||||
'temp_overrides',
|
||||
...this.canonPath.map(p => p.replace(/\./g, '_DOT_')),
|
||||
'label'
|
||||
].join('.'))
|
||||
} else {
|
||||
return this.swapDescriptionAndLabel
|
||||
? this.backendDescription?.description
|
||||
: this.backendDescription?.label
|
||||
}
|
||||
},
|
||||
backendDescriptionDescription () {
|
||||
if (this.realSource !== 'admin') return ''
|
||||
if (this.hideDescription) return null
|
||||
if (!this.backendDescription || this.overrideBackendDescription) {
|
||||
return this.$t([
|
||||
'admin_dash',
|
||||
'temp_overrides',
|
||||
...this.canonPath.map(p => p.replace(/\./g, '_DOT_')),
|
||||
'description'
|
||||
].join('.'))
|
||||
} else {
|
||||
return this.swapDescriptionAndLabel
|
||||
? this.backendDescription?.label
|
||||
: this.backendDescription?.description
|
||||
}
|
||||
},
|
||||
backendDescriptionSuggestions () {
|
||||
return this.backendDescription?.suggestions
|
||||
},
|
||||
shouldBeDisabled () {
|
||||
const parentValue = this.parentPath !== undefined ? get(this.configSource, this.parentPath) : null
|
||||
return this.disabled || (parentValue !== null ? (this.parentInvert ? parentValue : !parentValue) : false)
|
||||
},
|
||||
configSource () {
|
||||
switch (this.realSource) {
|
||||
case 'profile':
|
||||
return this.$store.state.profileConfig
|
||||
case 'admin':
|
||||
return this.$store.state.adminSettings.config
|
||||
default:
|
||||
return this.$store.getters.mergedConfig
|
||||
}
|
||||
},
|
||||
configSink () {
|
||||
switch (this.realSource) {
|
||||
case 'profile':
|
||||
return (k, v) => this.$store.dispatch('setProfileOption', { name: k, value: v })
|
||||
case 'admin':
|
||||
return (k, v) => this.$store.dispatch('pushAdminSetting', { path: k, value: v })
|
||||
default:
|
||||
return (k, v) => this.$store.dispatch('setOption', { name: k, value: v })
|
||||
}
|
||||
},
|
||||
defaultState () {
|
||||
switch (this.realSource) {
|
||||
case 'profile':
|
||||
return {}
|
||||
default:
|
||||
return get(this.$store.getters.defaultConfig, this.path)
|
||||
}
|
||||
},
|
||||
isProfileSetting () {
|
||||
return this.realSource === 'profile'
|
||||
},
|
||||
isChanged () {
|
||||
switch (this.realSource) {
|
||||
case 'profile':
|
||||
case 'admin':
|
||||
return false
|
||||
default:
|
||||
return this.state !== this.defaultState
|
||||
}
|
||||
},
|
||||
canonPath () {
|
||||
return Array.isArray(this.path) ? this.path : this.path.split('.')
|
||||
},
|
||||
isDirty () {
|
||||
if (this.realSource === 'admin' && this.canonPath.length > 3) {
|
||||
return false // should not show draft buttons for "grouped" values
|
||||
} else {
|
||||
return this.realDraftMode && this.draft !== this.state
|
||||
}
|
||||
},
|
||||
canHardReset () {
|
||||
return this.realSource === 'admin' && this.$store.state.adminSettings.modifiedPaths.has(this.canonPath.join(' -> '))
|
||||
},
|
||||
matchesExpertLevel () {
|
||||
return (this.expert || 0) <= this.$store.state.config.expertLevel > 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getValue (e) {
|
||||
return e.target.value
|
||||
},
|
||||
update (e) {
|
||||
if (this.realDraftMode) {
|
||||
this.draft = this.getValue(e)
|
||||
} else {
|
||||
this.configSink(this.path, this.getValue(e))
|
||||
}
|
||||
},
|
||||
commitDraft () {
|
||||
if (this.realDraftMode) {
|
||||
this.configSink(this.path, this.draft)
|
||||
}
|
||||
},
|
||||
reset () {
|
||||
if (this.realDraftMode) {
|
||||
this.draft = cloneDeep(this.state)
|
||||
} else {
|
||||
set(this.$store.getters.mergedConfig, this.path, cloneDeep(this.defaultState))
|
||||
}
|
||||
},
|
||||
hardReset () {
|
||||
switch (this.realSource) {
|
||||
case 'admin':
|
||||
return this.$store.dispatch('resetAdminSetting', { path: this.path })
|
||||
.then(() => { this.draft = this.state })
|
||||
default:
|
||||
console.warn('Hard reset not implemented yet!')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +1,40 @@
|
||||
import { get, set } from 'lodash'
|
||||
import ModifiedIndicator from './modified_indicator.vue'
|
||||
import Select from 'src/components/select/select.vue'
|
||||
import Setting from './setting.js'
|
||||
|
||||
export const allCssUnits = ['cm', 'mm', 'in', 'px', 'pt', 'pc', 'em', 'ex', 'ch', 'rem', 'vw', 'vh', 'vmin', 'vmax', '%']
|
||||
export const defaultHorizontalUnits = ['px', 'rem', 'vw']
|
||||
export const defaultVerticalUnits = ['px', 'rem', 'vh']
|
||||
|
||||
export default {
|
||||
...Setting,
|
||||
components: {
|
||||
ModifiedIndicator,
|
||||
...Setting.components,
|
||||
Select
|
||||
},
|
||||
props: {
|
||||
path: String,
|
||||
disabled: Boolean,
|
||||
...Setting.props,
|
||||
min: Number,
|
||||
units: {
|
||||
type: [String],
|
||||
type: Array,
|
||||
default: () => allCssUnits
|
||||
},
|
||||
expert: [Number, String]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pathDefault () {
|
||||
const [firstSegment, ...rest] = this.path.split('.')
|
||||
return [firstSegment + 'DefaultValue', ...rest].join('.')
|
||||
},
|
||||
...Setting.computed,
|
||||
stateUnit () {
|
||||
return (this.state || '').replace(/\d+/, '')
|
||||
return this.state.replace(/\d+/, '')
|
||||
},
|
||||
stateValue () {
|
||||
return (this.state || '').replace(/\D+/, '')
|
||||
},
|
||||
state () {
|
||||
const value = get(this.$parent, this.path)
|
||||
if (value === undefined) {
|
||||
return this.defaultState
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
},
|
||||
defaultState () {
|
||||
return get(this.$parent, this.pathDefault)
|
||||
},
|
||||
isChanged () {
|
||||
return this.state !== this.defaultState
|
||||
},
|
||||
matchesExpertLevel () {
|
||||
return (this.expert || 0) <= this.$parent.expertLevel
|
||||
return this.state.replace(/\D+/, '')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update (e) {
|
||||
set(this.$parent, this.path, e)
|
||||
},
|
||||
reset () {
|
||||
set(this.$parent, this.path, this.defaultState)
|
||||
},
|
||||
...Setting.methods,
|
||||
updateValue (e) {
|
||||
set(this.$parent, this.path, parseInt(e.target.value) + this.stateUnit)
|
||||
this.configSink(this.path, parseInt(e.target.value) + this.stateUnit)
|
||||
},
|
||||
updateUnit (e) {
|
||||
set(this.$parent, this.path, this.stateValue + e.target.value)
|
||||
this.configSink(this.path, this.stateValue + e.target.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
import Setting from './setting.js'
|
||||
|
||||
export default {
|
||||
...Setting
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<label
|
||||
v-if="matchesExpertLevel"
|
||||
class="StringSetting"
|
||||
>
|
||||
<label
|
||||
:for="path"
|
||||
:class="{ 'faint': shouldBeDisabled }"
|
||||
>
|
||||
<template v-if="backendDescriptionLabel">
|
||||
{{ backendDescriptionLabel + ' ' }}
|
||||
</template>
|
||||
<template v-else-if="source === 'admin'">
|
||||
MISSING LABEL FOR {{ path }}
|
||||
</template>
|
||||
<slot v-else />
|
||||
</label>
|
||||
<input
|
||||
:id="path"
|
||||
class="string-input"
|
||||
:disabled="shouldBeDisabled"
|
||||
:value="realDraftMode ? draft : state"
|
||||
@change="update"
|
||||
>
|
||||
{{ ' ' }}
|
||||
<ModifiedIndicator
|
||||
:changed="isChanged"
|
||||
:onclick="reset"
|
||||
/>
|
||||
<ProfileSettingIndicator :is-profile="isProfileSetting" />
|
||||
<DraftButtons />
|
||||
<p
|
||||
v-if="backendDescriptionDescription"
|
||||
class="setting-description"
|
||||
:class="{ 'faint': shouldBeDisabled }"
|
||||
>
|
||||
{{ backendDescriptionDescription + ' ' }}
|
||||
</p>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script src="./string_setting.js"></script>
|
@ -0,0 +1,93 @@
|
||||
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
|
||||
|
||||
import InstanceTab from './admin_tabs/instance_tab.vue'
|
||||
import LimitsTab from './admin_tabs/limits_tab.vue'
|
||||
import FrontendsTab from './admin_tabs/frontends_tab.vue'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faWrench,
|
||||
faHand,
|
||||
faLaptopCode,
|
||||
faPaintBrush,
|
||||
faBell,
|
||||
faDownload,
|
||||
faEyeSlash,
|
||||
faInfo
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faWrench,
|
||||
faHand,
|
||||
faLaptopCode,
|
||||
faPaintBrush,
|
||||
faBell,
|
||||
faDownload,
|
||||
faEyeSlash,
|
||||
faInfo
|
||||
)
|
||||
|
||||
const SettingsModalAdminContent = {
|
||||
components: {
|
||||
TabSwitcher,
|
||||
|
||||
InstanceTab,
|
||||
LimitsTab,
|
||||
FrontendsTab
|
||||
},
|
||||
computed: {
|
||||
user () {
|
||||
return this.$store.state.users.currentUser
|
||||
},
|
||||
isLoggedIn () {
|
||||
return !!this.$store.state.users.currentUser
|
||||
},
|
||||
open () {
|
||||
return this.$store.state.interface.settingsModalState !== 'hidden'
|
||||
},
|
||||
bodyLock () {
|
||||
return this.$store.state.interface.settingsModalState === 'visible'
|
||||
},
|
||||
adminDbLoaded () {
|
||||
return this.$store.state.adminSettings.loaded
|
||||
},
|
||||
adminDescriptionsLoaded () {
|
||||
return this.$store.state.adminSettings.descriptions !== null
|
||||
},
|
||||
noDb () {
|
||||
return this.$store.state.adminSettings.dbConfigEnabled === false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.user.rights.admin) {
|
||||
this.$store.dispatch('loadAdminStuff')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen () {
|
||||
const targetTab = this.$store.state.interface.settingsModalTargetTab
|
||||
// We're being told to open in specific tab
|
||||
if (targetTab) {
|
||||
const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => {
|
||||
return elm.props && elm.props['data-tab-name'] === targetTab
|
||||
})
|
||||
if (tabIndex >= 0) {
|
||||
this.$refs.tabSwitcher.setTab(tabIndex)
|
||||
}
|
||||
}
|
||||
// Clear the state of target tab, so that next time settings is opened
|
||||
// it doesn't force it.
|
||||
this.$store.dispatch('clearSettingsModalTargetTab')
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.onOpen()
|
||||
},
|
||||
watch: {
|
||||
open: function (value) {
|
||||
if (value) this.onOpen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SettingsModalAdminContent
|
@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<tab-switcher
|
||||
v-if="adminDescriptionsLoaded && (noDb || adminDbLoaded)"
|
||||
ref="tabSwitcher"
|
||||
class="settings_tab-switcher"
|
||||
:side-tab-bar="true"
|
||||
:scrollable-tabs="true"
|
||||
:render-only-focused="true"
|
||||
:body-scroll-lock="bodyLock"
|
||||
>
|
||||
<div
|
||||
v-if="noDb"
|
||||
:label="$t('admin_dash.tabs.nodb')"
|
||||
icon="exclamation-triangle"
|
||||
data-tab-name="nodb-notice"
|
||||
>
|
||||
<div :label="$t('admin_dash.tabs.nodb')">
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('admin_dash.nodb.heading') }}</h2>
|
||||
<i18n-t keypath="admin_dash.nodb.text">
|
||||
<template #documentation>
|
||||
<a
|
||||
href="https://docs-develop.pleroma.social/backend/configuration/howto_database_config/"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t("admin_dash.nodb.documentation") }}
|
||||
</a>
|
||||
</template>
|
||||
<template #property>
|
||||
<code>config :pleroma, configurable_from_database</code>
|
||||
</template>
|
||||
<template #value>
|
||||
<code>true</code>
|
||||
</template>
|
||||
</i18n-t>
|
||||
<p>{{ $t('admin_dash.nodb.text2') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="adminDbLoaded"
|
||||
:label="$t('admin_dash.tabs.instance')"
|
||||
icon="wrench"
|
||||
data-tab-name="general"
|
||||
>
|
||||
<InstanceTab />
|
||||
</div>
|
||||
<div
|
||||
v-if="adminDbLoaded"
|
||||
:label="$t('admin_dash.tabs.limits')"
|
||||
icon="hand"
|
||||
data-tab-name="limits"
|
||||
>
|
||||
<LimitsTab />
|
||||
</div>
|
||||
<div
|
||||
:label="$t('admin_dash.tabs.frontends')"
|
||||
icon="laptop-code"
|
||||
data-tab-name="frontends"
|
||||
>
|
||||
<FrontendsTab />
|
||||
</div>
|
||||
</tab-switcher>
|
||||
</template>
|
||||
|
||||
<script src="./settings_modal_admin_content.js"></script>
|
||||
|
||||
<style src="./settings_modal_admin_content.scss" lang="scss"></style>
|
@ -0,0 +1,52 @@
|
||||
@import "src/variables";
|
||||
|
||||
.settings_tab-switcher {
|
||||
height: 100%;
|
||||
|
||||
.setting-item {
|
||||
border-bottom: 2px solid var(--fg, $fallback--fg);
|
||||
margin: 1em 1em 1.4em;
|
||||
padding-bottom: 1.4em;
|
||||
|
||||
> div,
|
||||
> label {
|
||||
display: block;
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.select-multiple {
|
||||
display: flex;
|
||||
|
||||
.option-list {
|
||||
margin: 0;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
select {
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.unavailable,
|
||||
.unavailable svg {
|
||||
color: var(--cRed, $fallback--cRed);
|
||||
color: $fallback--cRed;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue