Merge branch 'expert-settings-and-serverside' into 'develop'

Expert settings and serverside settings + new defaults

See merge request pleroma/pleroma-fe!1438
This commit is contained in:
HJ 2022-03-16 17:33:24 +00:00
commit b632d740c1
25 changed files with 665 additions and 292 deletions

View file

@ -1,14 +1,17 @@
import { get, set } from 'lodash' import { get, set } from 'lodash'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
import ModifiedIndicator from './modified_indicator.vue' import ModifiedIndicator from './modified_indicator.vue'
import ServerSideIndicator from './server_side_indicator.vue'
export default { export default {
components: { components: {
Checkbox, Checkbox,
ModifiedIndicator ModifiedIndicator,
ServerSideIndicator
}, },
props: [ props: [
'path', 'path',
'disabled' 'disabled',
'expert'
], ],
computed: { computed: {
pathDefault () { pathDefault () {
@ -26,8 +29,14 @@ export default {
defaultState () { defaultState () {
return get(this.$parent, this.pathDefault) return get(this.$parent, this.pathDefault)
}, },
isServerSide () {
return this.path.startsWith('serverSide_')
},
isChanged () { isChanged () {
return this.state !== this.defaultState return !this.path.startsWith('serverSide_') && this.state !== this.defaultState
},
matchesExpertLevel () {
return (this.expert || 0) <= this.$parent.expertLevel
} }
}, },
methods: { methods: {

View file

@ -1,5 +1,6 @@
<template> <template>
<label <label
v-if="matchesExpertLevel"
class="BooleanSetting" class="BooleanSetting"
> >
<Checkbox <Checkbox
@ -13,8 +14,7 @@
> >
<slot /> <slot />
</span> </span>
<ModifiedIndicator :changed="isChanged" /> <ModifiedIndicator :changed="isChanged" /><ServerSideIndicator :server-side="isServerSide" /> </Checkbox>
</Checkbox>
</label> </label>
</template> </template>

View file

@ -1,15 +1,18 @@
import { get, set } from 'lodash' import { get, set } from 'lodash'
import Select from 'src/components/select/select.vue' import Select from 'src/components/select/select.vue'
import ModifiedIndicator from './modified_indicator.vue' import ModifiedIndicator from './modified_indicator.vue'
import ServerSideIndicator from './server_side_indicator.vue'
export default { export default {
components: { components: {
Select, Select,
ModifiedIndicator ModifiedIndicator,
ServerSideIndicator
}, },
props: [ props: [
'path', 'path',
'disabled', 'disabled',
'options' 'options',
'expert'
], ],
computed: { computed: {
pathDefault () { pathDefault () {
@ -27,8 +30,14 @@ export default {
defaultState () { defaultState () {
return get(this.$parent, this.pathDefault) return get(this.$parent, this.pathDefault)
}, },
isServerSide () {
return this.path.startsWith('serverSide_')
},
isChanged () { isChanged () {
return this.state !== this.defaultState return !this.path.startsWith('serverSide_') && this.state !== this.defaultState
},
matchesExpertLevel () {
return (this.expert || 0) <= this.$parent.expertLevel
} }
}, },
methods: { methods: {

View file

@ -1,5 +1,6 @@
<template> <template>
<label <label
v-if="matchesExpertLevel"
class="ChoiceSetting" class="ChoiceSetting"
> >
<slot /> <slot />
@ -18,6 +19,7 @@
</option> </option>
</Select> </Select>
<ModifiedIndicator :changed="isChanged" /> <ModifiedIndicator :changed="isChanged" />
<ServerSideIndicator :server-side="isServerSide" />
</label> </label>
</template> </template>

View file

@ -7,7 +7,8 @@ export default {
props: { props: {
path: String, path: String,
disabled: Boolean, disabled: Boolean,
min: Number min: Number,
expert: Number
}, },
computed: { computed: {
pathDefault () { pathDefault () {
@ -27,6 +28,9 @@ export default {
}, },
isChanged () { isChanged () {
return this.state !== this.defaultState return this.state !== this.defaultState
},
matchesExpertLevel () {
return (this.expert || 0) <= this.$parent.expertLevel
} }
}, },
methods: { methods: {

View file

@ -1,5 +1,8 @@
<template> <template>
<span class="IntegerSetting"> <span
v-if="matchesExpertLevel"
class="IntegerSetting"
>
<label :for="path"> <label :for="path">
<slot /> <slot />
</label> </label>

View file

@ -0,0 +1,51 @@
<template>
<span
v-if="serverSide"
class="ServerSideIndicator"
>
<Popover
trigger="hover"
>
<template v-slot:trigger>
&nbsp;
<FAIcon
icon="server"
:aria-label="$t('settings.setting_server_side')"
/>
</template>
<template v-slot:content>
<div class="serverside-tooltip">
{{ $t('settings.setting_server_side') }}
</div>
</template>
</Popover>
</span>
</template>
<script>
import Popover from 'src/components/popover/popover.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faServer } from '@fortawesome/free-solid-svg-icons'
library.add(
faServer
)
export default {
components: { Popover },
props: ['serverSide']
}
</script>
<style lang="scss">
.ServerSideIndicator {
display: inline-block;
position: relative;
.serverside-tooltip {
margin: 0.5em 1em;
min-width: 10em;
text-align: center;
}
}
</style>

View file

@ -1,4 +1,5 @@
import { defaultState as configDefaultState } from 'src/modules/config.js' import { defaultState as configDefaultState } from 'src/modules/config.js'
import { defaultState as serverSideConfigDefaultState } from 'src/modules/serverSideConfig.js'
const SharedComputedObject = () => ({ const SharedComputedObject = () => ({
user () { user () {
@ -22,6 +23,14 @@ const SharedComputedObject = () => ({
} }
}]) }])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
...Object.keys(serverSideConfigDefaultState)
.map(key => ['serverSide_' + key, {
get () { return this.$store.state.serverSideConfig[key] },
set (value) {
this.$store.dispatch('setServerSideOption', { name: key, value })
}
}])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
// Special cases (need to transform values or perform actions first) // Special cases (need to transform values or perform actions first)
useStreamingApi: { useStreamingApi: {
get () { return this.$store.getters.mergedConfig.useStreamingApi }, get () { return this.$store.getters.mergedConfig.useStreamingApi },

View file

@ -3,6 +3,7 @@ import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
import AsyncComponentError from 'src/components/async_component_error/async_component_error.vue' import AsyncComponentError from 'src/components/async_component_error/async_component_error.vue'
import getResettableAsyncComponent from 'src/services/resettable_async_component.js' import getResettableAsyncComponent from 'src/services/resettable_async_component.js'
import Popover from '../popover/popover.vue' import Popover from '../popover/popover.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import { import {
@ -51,6 +52,7 @@ const SettingsModal = {
components: { components: {
Modal, Modal,
Popover, Popover,
Checkbox,
SettingsModalContent: getResettableAsyncComponent( SettingsModalContent: getResettableAsyncComponent(
() => import('./settings_modal_content.vue'), () => import('./settings_modal_content.vue'),
{ {
@ -159,6 +161,15 @@ const SettingsModal = {
}, },
modalPeeked () { modalPeeked () {
return this.$store.state.interface.settingsModalState === 'minimized' return this.$store.state.interface.settingsModalState === 'minimized'
},
expertLevel: {
get () {
return this.$store.state.config.expertLevel > 0
},
set (value) {
console.log(value)
this.$store.dispatch('setOption', { name: 'expertLevel', value: value ? 1 : 0 })
}
} }
} }
} }

View file

@ -48,4 +48,11 @@
} }
} }
} }
.settings-footer {
display: flex;
>* {
margin-right: 0.5em;
}
}
} }

View file

@ -53,7 +53,7 @@
<div class="panel-body"> <div class="panel-body">
<SettingsModalContent v-if="modalOpenedOnce" /> <SettingsModalContent v-if="modalOpenedOnce" />
</div> </div>
<div class="panel-footer"> <div class="panel-footer settings-footer">
<Popover <Popover
class="export" class="export"
trigger="click" trigger="click"
@ -108,6 +108,10 @@
</div> </div>
</template> </template>
</Popover> </Popover>
<Checkbox v-model="expertLevel">
{{ $t("settings.expert_mode") }}
</Checkbox>
</div> </div>
</div> </div>
</Modal> </Modal>

View file

@ -21,6 +21,7 @@
</li> </li>
<li> <li>
<BooleanSetting <BooleanSetting
v-if="user"
:disabled="hideFilteredStatuses" :disabled="hideFilteredStatuses"
path="hideMutedThreads" path="hideMutedThreads"
> >
@ -29,6 +30,7 @@
</li> </li>
<li> <li>
<BooleanSetting <BooleanSetting
v-if="user"
:disabled="hideFilteredStatuses" :disabled="hideFilteredStatuses"
path="hideMutedPosts" path="hideMutedPosts"
> >
@ -53,6 +55,7 @@
</BooleanSetting> </BooleanSetting>
</li> </li>
<ChoiceSetting <ChoiceSetting
v-if="user"
id="replyVisibility" id="replyVisibility"
path="replyVisibility" path="replyVisibility"
:options="replyVisibilityOptions" :options="replyVisibilityOptions"
@ -69,6 +72,19 @@
<div>{{ $t('settings.filtering_explanation') }}</div> <div>{{ $t('settings.filtering_explanation') }}</div>
</li> </li>
<h3>{{ $t('settings.attachments') }}</h3> <h3>{{ $t('settings.attachments') }}</h3>
<li v-if="expertLevel > 0">
<label for="maxThumbnails">
{{ $t('settings.max_thumbnails') }}
</label>
<input
id="maxThumbnails"
path.number="maxThumbnails"
class="number-input"
type="number"
min="0"
step="1"
>
</li>
<li> <li>
<IntegerSetting <IntegerSetting
path="maxThumbnails" path="maxThumbnails"
@ -89,7 +105,10 @@
</li> </li>
</ul> </ul>
</div> </div>
<div class="setting-item"> <div
v-if="expertLevel > 0"
class="setting-item"
>
<h2>{{ $t('settings.user_profiles') }}</h2> <h2>{{ $t('settings.user_profiles') }}</h2>
<ul class="setting-list"> <ul class="setting-list">
<li> <li>
@ -99,46 +118,6 @@
</li> </li>
</ul> </ul>
</div> </div>
<div class="setting-item">
<h2>{{ $t('settings.notifications') }}</h2>
<ul class="setting-list">
<li class="select-multiple">
<span class="label">{{ $t('settings.notification_visibility') }}</span>
<ul class="option-list">
<li>
<BooleanSetting path="notificationVisibility.likes">
{{ $t('settings.notification_visibility_likes') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="notificationVisibility.repeats">
{{ $t('settings.notification_visibility_repeats') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="notificationVisibility.follows">
{{ $t('settings.notification_visibility_follows') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="notificationVisibility.mentions">
{{ $t('settings.notification_visibility_mentions') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="notificationVisibility.moves">
{{ $t('settings.notification_visibility_moves') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="notificationVisibility.emojiReactions">
{{ $t('settings.notification_visibility_emoji_reactions') }}
</BooleanSetting>
</li>
</ul>
</li>
</ul>
</div>
</div> </div>
</template> </template>
<script src="./filtering_tab.js"></script> <script src="./filtering_tab.js"></script>

View file

@ -1,9 +1,11 @@
import BooleanSetting from '../helpers/boolean_setting.vue' import BooleanSetting from '../helpers/boolean_setting.vue'
import ChoiceSetting from '../helpers/choice_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue'
import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
import IntegerSetting from '../helpers/integer_setting.vue' import IntegerSetting from '../helpers/integer_setting.vue'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue' import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js' import SharedComputedObject from '../helpers/shared_computed_object.js'
import ServerSideIndicator from '../helpers/server_side_indicator.vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faGlobe faGlobe
@ -49,7 +51,9 @@ const GeneralTab = {
BooleanSetting, BooleanSetting,
ChoiceSetting, ChoiceSetting,
IntegerSetting, IntegerSetting,
InterfaceLanguageSwitcher InterfaceLanguageSwitcher,
ScopeSelector,
ServerSideIndicator
}, },
computed: { computed: {
postFormats () { postFormats () {
@ -69,6 +73,11 @@ const GeneralTab = {
}, },
instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable }, instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable },
...SharedComputedObject() ...SharedComputedObject()
},
methods: {
changeDefaultScope (value) {
this.$store.dispatch('setServerSideOption', { name: 'defaultScope', value })
}
} }
} }

View file

@ -45,26 +45,42 @@
</ul> </ul>
</li> </li>
<li> <li>
<BooleanSetting path="useStreamingApi"> <BooleanSetting
path="useStreamingApi"
expert="1"
>
{{ $t('settings.useStreamingApi') }} {{ $t('settings.useStreamingApi') }}
<br>
<small>
{{ $t('settings.useStreamingApiWarning') }}
</small>
</BooleanSetting> </BooleanSetting>
</li> </li>
<li> <li>
<BooleanSetting path="virtualScrolling"> <BooleanSetting
path="virtualScrolling"
expert="1"
>
{{ $t('settings.virtual_scrolling') }} {{ $t('settings.virtual_scrolling') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li> <li>
<BooleanSetting path="autohideFloatingPostButton"> <BooleanSetting
path="alwaysShowNewPostButton"
expert="1"
>
{{ $t('settings.always_show_post_button') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="autohideFloatingPostButton"
expert="1"
>
{{ $t('settings.autohide_floating_post_button') }} {{ $t('settings.autohide_floating_post_button') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li v-if="instanceShoutboxPresent"> <li v-if="instanceShoutboxPresent">
<BooleanSetting path="hideShoutbox"> <BooleanSetting
path="hideShoutbox"
expert="1"
>
{{ $t('settings.hide_shoutbox') }} {{ $t('settings.hide_shoutbox') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
@ -73,85 +89,6 @@
<div class="setting-item"> <div class="setting-item">
<h2>{{ $t('settings.post_look_feel') }}</h2> <h2>{{ $t('settings.post_look_feel') }}</h2>
<ul class="setting-list"> <ul class="setting-list">
<li>
<BooleanSetting path="collapseMessageWithSubject">
{{ $t('settings.collapse_subject') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="emojiReactionsOnTimeline">
{{ $t('settings.emoji_reactions_on_timeline') }}
</BooleanSetting>
</li>
<h3>{{ $t('settings.attachments') }}</h3>
<li>
<BooleanSetting path="useContainFit">
{{ $t('settings.use_contain_fit') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="hideNsfw">
{{ $t('settings.nsfw_clickthrough') }}
</BooleanSetting>
</li>
<ul class="setting-list suboptions">
<li>
<BooleanSetting
path="preloadImage"
:disabled="!hideNsfw"
>
{{ $t('settings.preload_images') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="useOneClickNsfw"
:disabled="!hideNsfw"
>
{{ $t('settings.use_one_click_nsfw') }}
</BooleanSetting>
</li>
</ul>
<li>
<BooleanSetting path="loopVideo">
{{ $t('settings.loop_video') }}
</BooleanSetting>
<ul
class="setting-list suboptions"
:class="[{disabled: !streaming}]"
>
<li>
<BooleanSetting
path="loopVideoSilentOnly"
:disabled="!loopVideo || !loopSilentAvailable"
>
{{ $t('settings.loop_video_silent_only') }}
</BooleanSetting>
<div
v-if="!loopSilentAvailable"
class="unavailable"
>
<FAIcon icon="globe" />! {{ $t('settings.limited_availability') }}
</div>
</li>
</ul>
</li>
<li>
<BooleanSetting path="playVideosInModal">
{{ $t('settings.play_videos_in_modal') }}
</BooleanSetting>
</li>
<h3>{{ $t('settings.fun') }}</h3>
<li>
<BooleanSetting path="greentext">
{{ $t('settings.greentext') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="mentionLinkShowYous">
{{ $t('settings.show_yous') }}
</BooleanSetting>
</li>
<li> <li>
<ChoiceSetting <ChoiceSetting
id="conversationDisplay" id="conversationDisplay"
@ -171,7 +108,10 @@
</BooleanSetting> </BooleanSetting>
</li> </li>
<li> <li>
<BooleanSetting path="conversationTreeFadeAncestors"> <BooleanSetting
path="conversationTreeFadeAncestors"
:expert="1"
>
{{ $t('settings.tree_fade_ancestors') }} {{ $t('settings.tree_fade_ancestors') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
@ -179,6 +119,7 @@
<IntegerSetting <IntegerSetting
path="maxDepthInThread" path="maxDepthInThread"
:min="3" :min="3"
:expert="1"
> >
{{ $t('settings.max_depth_in_thread') }} {{ $t('settings.max_depth_in_thread') }}
</IntegerSetting> </IntegerSetting>
@ -188,11 +129,105 @@
id="conversationOtherRepliesButton" id="conversationOtherRepliesButton"
path="conversationOtherRepliesButton" path="conversationOtherRepliesButton"
:options="conversationOtherRepliesButtonOptions" :options="conversationOtherRepliesButtonOptions"
:expert="1"
> >
{{ $t('settings.conversation_other_replies_button') }} {{ $t('settings.conversation_other_replies_button') }}
</ChoiceSetting> </ChoiceSetting>
</li> </li>
</ul> </ul>
<li>
<BooleanSetting path="collapseMessageWithSubject">
{{ $t('settings.collapse_subject') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="emojiReactionsOnTimeline"
expert="1"
>
{{ $t('settings.emoji_reactions_on_timeline') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
v-if="user"
path="serverSide_stripRichContent"
expert="1"
>
{{ $t('settings.no_rich_text_description') }}
</BooleanSetting>
</li>
<h3>{{ $t('settings.attachments') }}</h3>
<li>
<BooleanSetting
path="useContainFit"
expert="1"
>
{{ $t('settings.use_contain_fit') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="hideNsfw">
{{ $t('settings.nsfw_clickthrough') }}
</BooleanSetting>
</li>
<ul class="setting-list suboptions">
<li>
<BooleanSetting
path="preloadImage"
expert="1"
:disabled="!hideNsfw"
>
{{ $t('settings.preload_images') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="useOneClickNsfw"
expert="1"
:disabled="!hideNsfw"
>
{{ $t('settings.use_one_click_nsfw') }}
</BooleanSetting>
</li>
</ul>
<li>
<BooleanSetting
path="loopVideo"
expert="1"
>
{{ $t('settings.loop_video') }}
</BooleanSetting>
<ul
class="setting-list suboptions"
:class="[{disabled: !streaming}]"
>
<li>
<BooleanSetting
path="loopVideoSilentOnly"
expert="1"
:disabled="!loopVideo || !loopSilentAvailable"
>
{{ $t('settings.loop_video_silent_only') }}
</BooleanSetting>
<div
v-if="!loopSilentAvailable"
class="unavailable"
>
<FAIcon icon="globe" />! {{ $t('settings.limited_availability') }}
</div>
</li>
</ul>
</li>
<li>
<BooleanSetting
path="playVideosInModal"
expert="1"
>
{{ $t('settings.play_videos_in_modal') }}
</BooleanSetting>
</li>
<h3>{{ $t('settings.mention_links') }}</h3>
<li> <li>
<ChoiceSetting <ChoiceSetting
id="mentionLinkDisplay" id="mentionLinkDisplay"
@ -205,47 +240,103 @@
<ul <ul
class="setting-list suboptions" class="setting-list suboptions"
> >
<li <li v-if="mentionLinkDisplay === 'short'">
v-if="mentionLinkDisplay === 'short'" <BooleanSetting
> path="mentionLinkShowTooltip"
<BooleanSetting path="mentionLinkShowTooltip"> expert="1"
>
{{ $t('settings.mention_link_show_tooltip') }} {{ $t('settings.mention_link_show_tooltip') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li>
<BooleanSetting path="useAtIcon">
{{ $t('settings.use_at_icon') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="mentionLinkShowAvatar">
{{ $t('settings.mention_link_show_avatar') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="mentionLinkFadeDomain">
{{ $t('settings.mention_link_fade_domain') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="mentionLinkBoldenYou">
{{ $t('settings.mention_link_bolden_you') }}
</BooleanSetting>
</li>
</ul> </ul>
<li>
<BooleanSetting
path="useAtIcon"
expert="1"
>
{{ $t('settings.use_at_icon') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="mentionLinkShowAvatar">
{{ $t('settings.mention_link_show_avatar') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="mentionLinkFadeDomain"
expert="1"
>
{{ $t('settings.mention_link_fade_domain') }}
</BooleanSetting>
</li>
<li v-if="user">
<BooleanSetting
path="mentionLinkBoldenYou"
expert="1"
>
{{ $t('settings.mention_link_bolden_you') }}
</BooleanSetting>
</li>
<h3 v-if="expertLevel > 0">
{{ $t('settings.fun') }}
</h3>
<li>
<BooleanSetting
path="greentext"
expert="1"
>
{{ $t('settings.greentext') }}
</BooleanSetting>
</li>
<li v-if="user">
<BooleanSetting
path="mentionLinkShowYous"
expert="1"
>
{{ $t('settings.show_yous') }}
</BooleanSetting>
</li>
</ul> </ul>
</div> </div>
<div class="setting-item"> <div
v-if="user"
class="setting-item"
>
<h2>{{ $t('settings.composing') }}</h2> <h2>{{ $t('settings.composing') }}</h2>
<ul class="setting-list"> <ul class="setting-list">
<li> <li>
<BooleanSetting path="scopeCopy"> <label for="default-vis">
{{ $t('settings.default_vis') }} <ServerSideIndicator :server-side="true" />
<ScopeSelector
class="scope-selector"
:show-all="true"
:user-default="serverSide_defaultScope"
:initial-scope="serverSide_defaultScope"
:on-scope-change="changeDefaultScope"
/>
</label>
</li>
<li>
<!-- <BooleanSetting path="serverSide_defaultNSFW"> -->
<BooleanSetting path="sensitiveByDefault">
{{ $t('settings.sensitive_by_default') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="scopeCopy"
expert="1"
>
{{ $t('settings.scope_copy') }} {{ $t('settings.scope_copy') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li> <li>
<BooleanSetting path="alwaysShowSubjectInput"> <BooleanSetting
path="alwaysShowSubjectInput"
expert="1"
>
{{ $t('settings.subject_input_always_show') }} {{ $t('settings.subject_input_always_show') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
@ -254,6 +345,7 @@
id="subjectLineBehavior" id="subjectLineBehavior"
path="subjectLineBehavior" path="subjectLineBehavior"
:options="subjectLineOptions" :options="subjectLineOptions"
expert="1"
> >
{{ $t('settings.subject_line_behavior') }} {{ $t('settings.subject_line_behavior') }}
</ChoiceSetting> </ChoiceSetting>
@ -268,43 +360,39 @@
</ChoiceSetting> </ChoiceSetting>
</li> </li>
<li> <li>
<BooleanSetting path="minimalScopesMode"> <BooleanSetting
path="minimalScopesMode"
expert="1"
>
{{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.minimal_scopes_mode') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li> <li>
<BooleanSetting path="sensitiveByDefault"> <BooleanSetting
{{ $t('settings.sensitive_by_default') }} path="alwaysShowNewPostButton"
</BooleanSetting> expert="1"
</li> >
<li>
<BooleanSetting path="alwaysShowNewPostButton">
{{ $t('settings.always_show_post_button') }} {{ $t('settings.always_show_post_button') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li> <li>
<BooleanSetting path="autohideFloatingPostButton"> <BooleanSetting
path="autohideFloatingPostButton"
expert="1"
>
{{ $t('settings.autohide_floating_post_button') }} {{ $t('settings.autohide_floating_post_button') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li> <li>
<BooleanSetting path="padEmoji"> <BooleanSetting
path="padEmoji"
expert="1"
>
{{ $t('settings.pad_emoji') }} {{ $t('settings.pad_emoji') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
</ul> </ul>
</div> </div>
<div class="setting-item">
<h2>{{ $t('settings.notifications') }}</h2>
<ul class="setting-list">
<li>
<BooleanSetting path="webPushNotifications">
{{ $t('settings.enable_web_push_notifications') }}
</BooleanSetting>
</li>
</ul>
</div>
</div> </div>
</template> </template>

View file

@ -1,4 +1,5 @@
import Checkbox from 'src/components/checkbox/checkbox.vue' import BooleanSetting from '../helpers/boolean_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const NotificationsTab = { const NotificationsTab = {
data () { data () {
@ -9,12 +10,13 @@ const NotificationsTab = {
} }
}, },
components: { components: {
Checkbox BooleanSetting
}, },
computed: { computed: {
user () { user () {
return this.$store.state.users.currentUser return this.$store.state.users.currentUser
} },
...SharedComputedObject()
}, },
methods: { methods: {
updateNotificationSettings () { updateNotificationSettings () {

View file

@ -2,30 +2,77 @@
<div :label="$t('settings.notifications')"> <div :label="$t('settings.notifications')">
<div class="setting-item"> <div class="setting-item">
<h2>{{ $t('settings.notification_setting_filters') }}</h2> <h2>{{ $t('settings.notification_setting_filters') }}</h2>
<p> <ul class="setting-list">
<Checkbox v-model="notificationSettings.block_from_strangers"> <li>
{{ $t('settings.notification_setting_block_from_strangers') }} <BooleanSetting path="serverSide_blockNotificationsFromStrangers">
</Checkbox> {{ $t('settings.notification_setting_block_from_strangers') }}
</p> </BooleanSetting>
</li>
<li class="select-multiple">
<span class="label">{{ $t('settings.notification_visibility') }}</span>
<ul class="option-list">
<li>
<BooleanSetting path="notificationVisibility.likes">
{{ $t('settings.notification_visibility_likes') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="notificationVisibility.repeats">
{{ $t('settings.notification_visibility_repeats') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="notificationVisibility.follows">
{{ $t('settings.notification_visibility_follows') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="notificationVisibility.mentions">
{{ $t('settings.notification_visibility_mentions') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="notificationVisibility.moves">
{{ $t('settings.notification_visibility_moves') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="notificationVisibility.emojiReactions">
{{ $t('settings.notification_visibility_emoji_reactions') }}
</BooleanSetting>
</li>
</ul>
</li>
</ul>
</div> </div>
<div class="setting-item"> <div
v-if="expertLevel > 0"
class="setting-item"
>
<h2>{{ $t('settings.notification_setting_privacy') }}</h2> <h2>{{ $t('settings.notification_setting_privacy') }}</h2>
<p> <ul class="setting-list">
<Checkbox v-model="notificationSettings.hide_notification_contents"> <li>
{{ $t('settings.notification_setting_hide_notification_contents') }} <BooleanSetting
</Checkbox> path="webPushNotifications"
</p> expert="1"
>
{{ $t('settings.enable_web_push_notifications') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="serverSide_webPushHideContents"
expert="1"
>
{{ $t('settings.notification_setting_hide_notification_contents') }}
</BooleanSetting>
</li>
</ul>
</div> </div>
<div class="setting-item"> <div class="setting-item">
<p>{{ $t('settings.notification_mutes') }}</p> <p>{{ $t('settings.notification_mutes') }}</p>
<p>{{ $t('settings.notification_blocks') }}</p> <p>{{ $t('settings.notification_blocks') }}</p>
<button
class="btn button-default"
@click="updateNotificationSettings"
>
{{ $t('settings.save') }}
</button>
</div> </div>
</div> </div>
</template> </template>

View file

@ -8,6 +8,9 @@ import EmojiInput from 'src/components/emoji_input/emoji_input.vue'
import suggestor from 'src/components/emoji_input/suggestor.js' import suggestor from 'src/components/emoji_input/suggestor.js'
import Autosuggest from 'src/components/autosuggest/autosuggest.vue' import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
import BooleanSetting from '../helpers/boolean_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faTimes, faTimes,
@ -27,18 +30,10 @@ const ProfileTab = {
newName: this.$store.state.users.currentUser.name_unescaped, newName: this.$store.state.users.currentUser.name_unescaped,
newBio: unescape(this.$store.state.users.currentUser.description), newBio: unescape(this.$store.state.users.currentUser.description),
newLocked: this.$store.state.users.currentUser.locked, newLocked: this.$store.state.users.currentUser.locked,
newNoRichText: this.$store.state.users.currentUser.no_rich_text,
newDefaultScope: this.$store.state.users.currentUser.default_scope,
newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })), newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })),
hideFollows: this.$store.state.users.currentUser.hide_follows,
hideFollowers: this.$store.state.users.currentUser.hide_followers,
hideFollowsCount: this.$store.state.users.currentUser.hide_follows_count,
hideFollowersCount: this.$store.state.users.currentUser.hide_followers_count,
showRole: this.$store.state.users.currentUser.show_role, showRole: this.$store.state.users.currentUser.show_role,
role: this.$store.state.users.currentUser.role, role: this.$store.state.users.currentUser.role,
discoverable: this.$store.state.users.currentUser.discoverable,
bot: this.$store.state.users.currentUser.bot, bot: this.$store.state.users.currentUser.bot,
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
pickAvatarBtnVisible: true, pickAvatarBtnVisible: true,
bannerUploading: false, bannerUploading: false,
backgroundUploading: false, backgroundUploading: false,
@ -54,12 +49,14 @@ const ProfileTab = {
EmojiInput, EmojiInput,
Autosuggest, Autosuggest,
ProgressButton, ProgressButton,
Checkbox Checkbox,
BooleanSetting
}, },
computed: { computed: {
user () { user () {
return this.$store.state.users.currentUser return this.$store.state.users.currentUser
}, },
...SharedComputedObject(),
emojiUserSuggestor () { emojiUserSuggestor () {
return suggestor({ return suggestor({
emoji: [ emoji: [
@ -123,15 +120,7 @@ const ProfileTab = {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
display_name: this.newName, display_name: this.newName,
fields_attributes: this.newFields.filter(el => el != null), fields_attributes: this.newFields.filter(el => el != null),
default_scope: this.newDefaultScope,
no_rich_text: this.newNoRichText,
hide_follows: this.hideFollows,
hide_followers: this.hideFollowers,
discoverable: this.discoverable,
bot: this.bot, bot: this.bot,
allow_following_move: this.allowFollowingMove,
hide_follows_count: this.hideFollowsCount,
hide_followers_count: this.hideFollowersCount,
show_role: this.showRole show_role: this.showRole
/* eslint-enable camelcase */ /* eslint-enable camelcase */
} }).then((user) => { } }).then((user) => {

View file

@ -25,61 +25,6 @@
class="bio resize-height" class="bio resize-height"
/> />
</EmojiInput> </EmojiInput>
<p>
<Checkbox v-model="newLocked">
{{ $t('settings.lock_account_description') }}
</Checkbox>
</p>
<div>
<label for="default-vis">{{ $t('settings.default_vis') }}</label>
<div
id="default-vis"
class="visibility-tray"
>
<scope-selector
:show-all="true"
:user-default="newDefaultScope"
:initial-scope="newDefaultScope"
:on-scope-change="changeVis"
/>
</div>
</div>
<p>
<Checkbox v-model="newNoRichText">
{{ $t('settings.no_rich_text_description') }}
</Checkbox>
</p>
<p>
<Checkbox v-model="hideFollows">
{{ $t('settings.hide_follows_description') }}
</Checkbox>
</p>
<p class="setting-subitem">
<Checkbox
v-model="hideFollowsCount"
:disabled="!hideFollows"
>
{{ $t('settings.hide_follows_count_description') }}
</Checkbox>
</p>
<p>
<Checkbox v-model="hideFollowers">
{{ $t('settings.hide_followers_description') }}
</Checkbox>
</p>
<p class="setting-subitem">
<Checkbox
v-model="hideFollowersCount"
:disabled="!hideFollowers"
>
{{ $t('settings.hide_followers_count_description') }}
</Checkbox>
</p>
<p>
<Checkbox v-model="allowFollowingMove">
{{ $t('settings.allow_following_move') }}
</Checkbox>
</p>
<p v-if="role === 'admin' || role === 'moderator'"> <p v-if="role === 'admin' || role === 'moderator'">
<Checkbox v-model="showRole"> <Checkbox v-model="showRole">
<template v-if="role === 'admin'"> <template v-if="role === 'admin'">
@ -90,11 +35,6 @@
</template> </template>
</Checkbox> </Checkbox>
</p> </p>
<p>
<Checkbox v-model="discoverable">
{{ $t('settings.discoverable') }}
</Checkbox>
</p>
<div v-if="maxFields > 0"> <div v-if="maxFields > 0">
<p>{{ $t('settings.profile_fields.label') }}</p> <p>{{ $t('settings.profile_fields.label') }}</p>
<div <div
@ -269,6 +209,67 @@
{{ $t('settings.save') }} {{ $t('settings.save') }}
</button> </button>
</div> </div>
<div class="setting-item">
<h2>{{ $t('settings.account_privacy') }}</h2>
<ul class="setting-list">
<li>
<BooleanSetting path="serverSide_locked">
{{ $t('settings.lock_account_description') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="serverSide_discoverable">
{{ $t('settings.discoverable') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="serverSide_allowFollowingMove">
{{ $t('settings.allow_following_move') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="serverSide_hideFavorites">
{{ $t('settings.hide_favorites_description') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="serverSide_hideFollowers">
{{ $t('settings.hide_followers_description') }}
</BooleanSetting>
<ul
class="setting-list suboptions"
:class="[{disabled: !serverSide_hideFollowers}]"
>
<li>
<BooleanSetting
path="serverSide_hideFollowersCount"
:disabled="!serverSide_hideFollowers"
>
{{ $t('settings.hide_followers_count_description') }}
</BooleanSetting>
</li>
</ul>
</li>
<li>
<BooleanSetting path="serverSide_hideFollows">
{{ $t('settings.hide_follows_description') }}
</BooleanSetting>
<ul
class="setting-list suboptions"
:class="[{disabled: !serverSide_hideFollows}]"
>
<li>
<BooleanSetting
path="serverSide_hideFollowsCount"
:disabled="!serverSide_hideFollows"
>
{{ $t('settings.hide_follows_count_description') }}
</BooleanSetting>
</li>
</ul>
</li>
</ul>
</div>
</div> </div>
</template> </template>

View file

@ -19,7 +19,7 @@
@load="onLoad" @load="onLoad"
@error="onError" @error="onError"
> >
<slot/> <slot />
</div> </div>
</template> </template>

View file

@ -8,7 +8,11 @@
:class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }" :class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }"
:image-load-error="imageLoadError" :image-load-error="imageLoadError"
> >
<FAIcon v-if="bot" icon="robot" class="bot-indicator" /> <FAIcon
v-if="bot"
icon="robot"
class="bot-indicator"
/>
</StillImage> </StillImage>
<div <div
v-else v-else

View file

@ -260,11 +260,14 @@
}, },
"settings": { "settings": {
"app_name": "App name", "app_name": "App name",
"expert_mode": "Show advanced",
"save": "Save changes", "save": "Save changes",
"security": "Security", "security": "Security",
"setting_changed": "Setting is different from default", "setting_changed": "Setting is different from default",
"setting_server_side": "This setting is tied to your profile and affects all sessions and clients",
"enter_current_password_to_confirm": "Enter your current password to confirm your identity", "enter_current_password_to_confirm": "Enter your current password to confirm your identity",
"post_look_feel": "Posts Look & Feel", "post_look_feel": "Posts Look & Feel",
"mention_links": "Mention links",
"mfa": { "mfa": {
"otp": "OTP", "otp": "OTP",
"setup_otp": "Setup OTP", "setup_otp": "Setup OTP",
@ -403,6 +406,7 @@
"name": "Label", "name": "Label",
"value": "Content" "value": "Content"
}, },
"account_privacy": "Privacy",
"use_contain_fit": "Don't crop the attachment in thumbnails", "use_contain_fit": "Don't crop the attachment in thumbnails",
"name": "Name", "name": "Name",
"name_bio": "Name & bio", "name_bio": "Name & bio",
@ -420,6 +424,7 @@
"no_rich_text_description": "Strip rich text formatting from all posts", "no_rich_text_description": "Strip rich text formatting from all posts",
"no_blocks": "No blocks", "no_blocks": "No blocks",
"no_mutes": "No mutes", "no_mutes": "No mutes",
"hide_favorites_description": "Don't show list of my favorites (people still get notified)",
"hide_follows_description": "Don't show who I'm following", "hide_follows_description": "Don't show who I'm following",
"hide_followers_description": "Don't show who's following me", "hide_followers_description": "Don't show who's following me",
"hide_follows_count_description": "Don't show follow count", "hide_follows_count_description": "Don't show follow count",
@ -433,7 +438,7 @@
"valid_until": "Valid until", "valid_until": "Valid until",
"revoke_token": "Revoke", "revoke_token": "Revoke",
"panelRadius": "Panels", "panelRadius": "Panels",
"pause_on_unfocused": "Pause streaming when tab is not focused", "pause_on_unfocused": "Pause when tab is not focused",
"presets": "Presets", "presets": "Presets",
"profile_background": "Profile background", "profile_background": "Profile background",
"profile_banner": "Profile banner", "profile_banner": "Profile banner",
@ -480,10 +485,9 @@
"post_status_content_type": "Post status content type", "post_status_content_type": "Post status content type",
"sensitive_by_default": "Mark posts as sensitive by default", "sensitive_by_default": "Mark posts as sensitive by default",
"stop_gifs": "Pause animated images until you hover on them", "stop_gifs": "Pause animated images until you hover on them",
"streaming": "Enable automatic streaming of new posts when scrolled to the top", "streaming": "Automatically show new posts when scrolled to the top",
"user_mutes": "Users", "user_mutes": "Users",
"useStreamingApi": "Receive posts and notifications real-time", "useStreamingApi": "Receive posts and notifications real-time",
"useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
"text": "Text", "text": "Text",
"theme": "Theme", "theme": "Theme",
"theme_help": "Use hex color codes (#rrggbb) to customize your color theme.", "theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",

View file

@ -11,6 +11,7 @@ import statusesModule from './modules/statuses.js'
import usersModule from './modules/users.js' import usersModule from './modules/users.js'
import apiModule from './modules/api.js' import apiModule from './modules/api.js'
import configModule from './modules/config.js' import configModule from './modules/config.js'
import serverSideConfigModule from './modules/serverSideConfig.js'
import shoutModule from './modules/shout.js' import shoutModule from './modules/shout.js'
import oauthModule from './modules/oauth.js' import oauthModule from './modules/oauth.js'
import authFlowModule from './modules/auth_flow.js' import authFlowModule from './modules/auth_flow.js'
@ -90,6 +91,7 @@ const persistedStateOptions = {
users: usersModule, users: usersModule,
api: apiModule, api: apiModule,
config: configModule, config: configModule,
serverSideConfig: serverSideConfigModule,
shout: shoutModule, shout: shoutModule,
oauth: oauthModule, oauth: oauthModule,
authFlow: authFlowModule, authFlow: authFlowModule,

View file

@ -18,6 +18,7 @@ export const multiChoiceProperties = [
] ]
export const defaultState = { export const defaultState = {
expertLevel: 0, // used to track which settings to show and hide
colors: {}, colors: {},
theme: undefined, theme: undefined,
customTheme: undefined, customTheme: undefined,
@ -44,7 +45,7 @@ export const defaultState = {
alwaysShowNewPostButton: false, alwaysShowNewPostButton: false,
autohideFloatingPostButton: false, autohideFloatingPostButton: false,
pauseOnUnfocused: true, pauseOnUnfocused: true,
stopGifs: false, stopGifs: true,
replyVisibility: 'all', replyVisibility: 'all',
notificationVisibility: { notificationVisibility: {
follows: true, follows: true,
@ -72,7 +73,7 @@ export const defaultState = {
hideFilteredStatuses: undefined, // instance default hideFilteredStatuses: undefined, // instance default
playVideosInModal: false, playVideosInModal: false,
useOneClickNsfw: false, useOneClickNsfw: false,
useContainFit: false, useContainFit: true,
greentext: undefined, // instance default greentext: undefined, // instance default
useAtIcon: undefined, // instance default useAtIcon: undefined, // instance default
mentionLinkDisplay: undefined, // instance default mentionLinkDisplay: undefined, // instance default

View file

@ -0,0 +1,137 @@
import { get, set } from 'lodash'
const defaultApi = ({ rootState, commit }, { path, value }) => {
const params = {}
set(params, path, value)
return rootState
.api
.backendInteractor
.updateProfile({ params })
.then(result => {
commit('addNewUsers', [result])
commit('setCurrentUser', result)
})
}
const notificationsApi = ({ rootState, commit }, { path, value, oldValue }) => {
const settings = {}
set(settings, path, value)
return rootState
.api
.backendInteractor
.updateNotificationSettings({ settings })
.then(result => {
if (result.status === 'success') {
commit('confirmServerSideOption', { name, value })
} else {
commit('confirmServerSideOption', { name, value: oldValue })
}
})
}
/**
* Map that stores relation between path for reading (from user profile),
* for writing (into API) an what API to use.
*
* Shorthand - instead of { get, set, api? } object it's possible to use string
* in case default api is used and get = set
*
* If no api is specified, defaultApi is used (see above)
*/
export const settingsMap = {
'defaultScope': 'source.privacy',
'defaultNSFW': 'source.sensitive', // BROKEN: pleroma/pleroma#2837
'stripRichContent': {
get: 'source.pleroma.no_rich_text',
set: 'no_rich_text'
},
// Privacy
'locked': 'locked',
'acceptChatMessages': {
get: 'pleroma.accepts_chat_messages',
set: 'accepts_chat_messages'
},
'allowFollowingMove': {
get: 'pleroma.allow_following_move',
set: 'allow_following_move'
},
'discoverable': 'source.discoverable',
'hideFavorites': {
get: 'pleroma.hide_favorites',
set: 'hide_favorites'
},
'hideFollowers': {
get: 'pleroma.hide_followers',
set: 'hide_followers'
},
'hideFollows': {
get: 'pleroma.hide_follows',
set: 'hide_follows'
},
'hideFollowersCount': {
get: 'pleroma.hide_followers_count',
set: 'hide_followers_count'
},
'hideFollowsCount': {
get: 'pleroma.hide_follows_count',
set: 'hide_follows_count'
},
// NotificationSettingsAPIs
'webPushHideContents': {
get: 'pleroma.notification_settings.hide_notification_contents',
set: 'hide_notification_contents',
api: notificationsApi
},
'blockNotificationsFromStrangers': {
get: 'pleroma.notification_settings.block_from_strangers',
set: 'block_from_strangers',
api: notificationsApi
}
}
export const defaultState = Object.fromEntries(Object.keys(settingsMap).map(key => [key, null]))
const serverSideConfig = {
state: { ...defaultState },
mutations: {
confirmServerSideOption (state, { name, value }) {
set(state, name, value)
},
wipeServerSideOption (state, { name }) {
set(state, name, null)
},
wipeAllServerSideOptions (state) {
Object.keys(settingsMap).forEach(key => {
set(state, key, null)
})
},
// Set the settings based on their path location
setCurrentUser (state, user) {
Object.entries(settingsMap).forEach((map) => {
const [name, value] = map
const { get: path = value } = value
set(state, name, get(user._original, path))
})
}
},
actions: {
setServerSideOption ({ rootState, state, commit, dispatch }, { name, value }) {
const oldValue = get(state, name)
const map = settingsMap[name]
if (!map) throw new Error('Invalid server-side setting')
const { set: path = map, api = defaultApi } = map
commit('wipeServerSideOption', { name })
api({ rootState, commit }, { path, value, oldValue })
.catch((e) => {
console.warn('Error setting server-side option:', e)
commit('confirmServerSideOption', { name, value: oldValue })
})
},
logout ({ commit }) {
commit('wipeAllServerSideOptions')
}
}
}
export default serverSideConfig

View file

@ -44,6 +44,7 @@ export const parseUser = (data) => {
const mastoShort = masto && !data.hasOwnProperty('avatar') const mastoShort = masto && !data.hasOwnProperty('avatar')
output.id = String(data.id) output.id = String(data.id)
output._original = data // used for server-side settings
if (masto) { if (masto) {
output.screen_name = data.acct output.screen_name = data.acct