logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://hacktivis.me/git/pleroma-fe.git
commit: b632d740c13ff4e5c98a7a101f26ca60cd2629bb
parent 186078ae2a330be82d5a3928b37525a7fd52cb12
Author: HJ <30-hj@users.noreply.git.pleroma.social>
Date:   Wed, 16 Mar 2022 17:33:24 +0000

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

Expert settings and serverside settings + new defaults

See merge request pleroma/pleroma-fe!1438

Diffstat:

Msrc/components/settings_modal/helpers/boolean_setting.js15++++++++++++---
Msrc/components/settings_modal/helpers/boolean_setting.vue4++--
Msrc/components/settings_modal/helpers/choice_setting.js15++++++++++++---
Msrc/components/settings_modal/helpers/choice_setting.vue2++
Msrc/components/settings_modal/helpers/integer_setting.js6+++++-
Msrc/components/settings_modal/helpers/integer_setting.vue5++++-
Asrc/components/settings_modal/helpers/server_side_indicator.vue51+++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/settings_modal/helpers/shared_computed_object.js9+++++++++
Msrc/components/settings_modal/settings_modal.js11+++++++++++
Msrc/components/settings_modal/settings_modal.scss7+++++++
Msrc/components/settings_modal/settings_modal.vue6+++++-
Msrc/components/settings_modal/tabs/filtering_tab.vue61++++++++++++++++++++-----------------------------------------
Msrc/components/settings_modal/tabs/general_tab.js11++++++++++-
Msrc/components/settings_modal/tabs/general_tab.vue310+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/components/settings_modal/tabs/notifications_tab.js8+++++---
Msrc/components/settings_modal/tabs/notifications_tab.vue81++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/components/settings_modal/tabs/profile_tab.js23++++++-----------------
Msrc/components/settings_modal/tabs/profile_tab.vue121++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/components/still-image/still-image.vue2+-
Msrc/components/user_avatar/user_avatar.vue6+++++-
Msrc/i18n/en.json10+++++++---
Msrc/main.js2++
Msrc/modules/config.js5+++--
Asrc/modules/serverSideConfig.js137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/services/entity_normalizer/entity_normalizer.service.js1+
25 files changed, 641 insertions(+), 268 deletions(-)

diff --git a/src/components/settings_modal/helpers/boolean_setting.js b/src/components/settings_modal/helpers/boolean_setting.js @@ -1,14 +1,17 @@ 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' export default { components: { Checkbox, - ModifiedIndicator + ModifiedIndicator, + ServerSideIndicator }, props: [ 'path', - 'disabled' + 'disabled', + 'expert' ], computed: { pathDefault () { @@ -26,8 +29,14 @@ export default { defaultState () { return get(this.$parent, this.pathDefault) }, + isServerSide () { + return this.path.startsWith('serverSide_') + }, isChanged () { - return this.state !== this.defaultState + return !this.path.startsWith('serverSide_') && this.state !== this.defaultState + }, + matchesExpertLevel () { + return (this.expert || 0) <= this.$parent.expertLevel } }, methods: { diff --git a/src/components/settings_modal/helpers/boolean_setting.vue b/src/components/settings_modal/helpers/boolean_setting.vue @@ -1,5 +1,6 @@ <template> <label + v-if="matchesExpertLevel" class="BooleanSetting" > <Checkbox @@ -13,8 +14,7 @@ > <slot /> </span> - <ModifiedIndicator :changed="isChanged" /> - </Checkbox> + <ModifiedIndicator :changed="isChanged" /><ServerSideIndicator :server-side="isServerSide" /> </Checkbox> </label> </template> diff --git a/src/components/settings_modal/helpers/choice_setting.js b/src/components/settings_modal/helpers/choice_setting.js @@ -1,15 +1,18 @@ 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' export default { components: { Select, - ModifiedIndicator + ModifiedIndicator, + ServerSideIndicator }, props: [ 'path', 'disabled', - 'options' + 'options', + 'expert' ], computed: { pathDefault () { @@ -27,8 +30,14 @@ export default { defaultState () { return get(this.$parent, this.pathDefault) }, + isServerSide () { + return this.path.startsWith('serverSide_') + }, isChanged () { - return this.state !== this.defaultState + return !this.path.startsWith('serverSide_') && this.state !== this.defaultState + }, + matchesExpertLevel () { + return (this.expert || 0) <= this.$parent.expertLevel } }, methods: { diff --git a/src/components/settings_modal/helpers/choice_setting.vue b/src/components/settings_modal/helpers/choice_setting.vue @@ -1,5 +1,6 @@ <template> <label + v-if="matchesExpertLevel" class="ChoiceSetting" > <slot /> @@ -18,6 +19,7 @@ </option> </Select> <ModifiedIndicator :changed="isChanged" /> + <ServerSideIndicator :server-side="isServerSide" /> </label> </template> diff --git a/src/components/settings_modal/helpers/integer_setting.js b/src/components/settings_modal/helpers/integer_setting.js @@ -7,7 +7,8 @@ export default { props: { path: String, disabled: Boolean, - min: Number + min: Number, + expert: Number }, computed: { pathDefault () { @@ -27,6 +28,9 @@ export default { }, isChanged () { return this.state !== this.defaultState + }, + matchesExpertLevel () { + return (this.expert || 0) <= this.$parent.expertLevel } }, methods: { diff --git a/src/components/settings_modal/helpers/integer_setting.vue b/src/components/settings_modal/helpers/integer_setting.vue @@ -1,5 +1,8 @@ <template> - <span class="IntegerSetting"> + <span + v-if="matchesExpertLevel" + class="IntegerSetting" + > <label :for="path"> <slot /> </label> diff --git a/src/components/settings_modal/helpers/server_side_indicator.vue b/src/components/settings_modal/helpers/server_side_indicator.vue @@ -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> diff --git a/src/components/settings_modal/helpers/shared_computed_object.js b/src/components/settings_modal/helpers/shared_computed_object.js @@ -1,4 +1,5 @@ import { defaultState as configDefaultState } from 'src/modules/config.js' +import { defaultState as serverSideConfigDefaultState } from 'src/modules/serverSideConfig.js' const SharedComputedObject = () => ({ user () { @@ -22,6 +23,14 @@ const SharedComputedObject = () => ({ } }]) .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) useStreamingApi: { get () { return this.$store.getters.mergedConfig.useStreamingApi }, diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js @@ -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 getResettableAsyncComponent from 'src/services/resettable_async_component.js' import Popover from '../popover/popover.vue' +import Checkbox from 'src/components/checkbox/checkbox.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { cloneDeep } from 'lodash' import { @@ -51,6 +52,7 @@ const SettingsModal = { components: { Modal, Popover, + Checkbox, SettingsModalContent: getResettableAsyncComponent( () => import('./settings_modal_content.vue'), { @@ -159,6 +161,15 @@ const SettingsModal = { }, modalPeeked () { 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 }) + } } } } diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss @@ -48,4 +48,11 @@ } } } + + .settings-footer { + display: flex; + >* { + margin-right: 0.5em; + } + } } diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue @@ -53,7 +53,7 @@ <div class="panel-body"> <SettingsModalContent v-if="modalOpenedOnce" /> </div> - <div class="panel-footer"> + <div class="panel-footer settings-footer"> <Popover class="export" trigger="click" @@ -108,6 +108,10 @@ </div> </template> </Popover> + + <Checkbox v-model="expertLevel"> + {{ $t("settings.expert_mode") }} + </Checkbox> </div> </div> </Modal> diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue @@ -21,6 +21,7 @@ </li> <li> <BooleanSetting + v-if="user" :disabled="hideFilteredStatuses" path="hideMutedThreads" > @@ -29,6 +30,7 @@ </li> <li> <BooleanSetting + v-if="user" :disabled="hideFilteredStatuses" path="hideMutedPosts" > @@ -53,6 +55,7 @@ </BooleanSetting> </li> <ChoiceSetting + v-if="user" id="replyVisibility" path="replyVisibility" :options="replyVisibilityOptions" @@ -69,6 +72,19 @@ <div>{{ $t('settings.filtering_explanation') }}</div> </li> <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> <IntegerSetting path="maxThumbnails" @@ -89,7 +105,10 @@ </li> </ul> </div> - <div class="setting-item"> + <div + v-if="expertLevel > 0" + class="setting-item" + > <h2>{{ $t('settings.user_profiles') }}</h2> <ul class="setting-list"> <li> @@ -99,46 +118,6 @@ </li> </ul> </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> </template> <script src="./filtering_tab.js"></script> diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js @@ -1,9 +1,11 @@ import BooleanSetting from '../helpers/boolean_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 InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' +import ServerSideIndicator from '../helpers/server_side_indicator.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faGlobe @@ -49,7 +51,9 @@ const GeneralTab = { BooleanSetting, ChoiceSetting, IntegerSetting, - InterfaceLanguageSwitcher + InterfaceLanguageSwitcher, + ScopeSelector, + ServerSideIndicator }, computed: { postFormats () { @@ -69,6 +73,11 @@ const GeneralTab = { }, instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable }, ...SharedComputedObject() + }, + methods: { + changeDefaultScope (value) { + this.$store.dispatch('setServerSideOption', { name: 'defaultScope', value }) + } } } diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue @@ -45,26 +45,42 @@ </ul> </li> <li> - <BooleanSetting path="useStreamingApi"> + <BooleanSetting + path="useStreamingApi" + expert="1" + > {{ $t('settings.useStreamingApi') }} - <br> - <small> - {{ $t('settings.useStreamingApiWarning') }} - </small> </BooleanSetting> </li> <li> - <BooleanSetting path="virtualScrolling"> + <BooleanSetting + path="virtualScrolling" + expert="1" + > {{ $t('settings.virtual_scrolling') }} </BooleanSetting> </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') }} </BooleanSetting> </li> <li v-if="instanceShoutboxPresent"> - <BooleanSetting path="hideShoutbox"> + <BooleanSetting + path="hideShoutbox" + expert="1" + > {{ $t('settings.hide_shoutbox') }} </BooleanSetting> </li> @@ -74,18 +90,79 @@ <h2>{{ $t('settings.post_look_feel') }}</h2> <ul class="setting-list"> <li> + <ChoiceSetting + id="conversationDisplay" + path="conversationDisplay" + :options="conversationDisplayOptions" + > + {{ $t('settings.conversation_display') }} + </ChoiceSetting> + </li> + <ul + v-if="conversationDisplay !== 'linear'" + class="setting-list suboptions" + > + <li> + <BooleanSetting path="conversationTreeAdvanced"> + {{ $t('settings.tree_advanced') }} + </BooleanSetting> + </li> + <li> + <BooleanSetting + path="conversationTreeFadeAncestors" + :expert="1" + > + {{ $t('settings.tree_fade_ancestors') }} + </BooleanSetting> + </li> + <li> + <IntegerSetting + path="maxDepthInThread" + :min="3" + :expert="1" + > + {{ $t('settings.max_depth_in_thread') }} + </IntegerSetting> + </li> + <li> + <ChoiceSetting + id="conversationOtherRepliesButton" + path="conversationOtherRepliesButton" + :options="conversationOtherRepliesButtonOptions" + :expert="1" + > + {{ $t('settings.conversation_other_replies_button') }} + </ChoiceSetting> + </li> + </ul> + <li> <BooleanSetting path="collapseMessageWithSubject"> {{ $t('settings.collapse_subject') }} </BooleanSetting> </li> <li> - <BooleanSetting path="emojiReactionsOnTimeline"> + <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"> + <BooleanSetting + path="useContainFit" + expert="1" + > {{ $t('settings.use_contain_fit') }} </BooleanSetting> </li> @@ -98,6 +175,7 @@ <li> <BooleanSetting path="preloadImage" + expert="1" :disabled="!hideNsfw" > {{ $t('settings.preload_images') }} @@ -106,6 +184,7 @@ <li> <BooleanSetting path="useOneClickNsfw" + expert="1" :disabled="!hideNsfw" > {{ $t('settings.use_one_click_nsfw') }} @@ -113,7 +192,10 @@ </li> </ul> <li> - <BooleanSetting path="loopVideo"> + <BooleanSetting + path="loopVideo" + expert="1" + > {{ $t('settings.loop_video') }} </BooleanSetting> <ul @@ -123,6 +205,7 @@ <li> <BooleanSetting path="loopVideoSilentOnly" + expert="1" :disabled="!loopVideo || !loopSilentAvailable" > {{ $t('settings.loop_video_silent_only') }} @@ -137,62 +220,14 @@ </ul> </li> <li> - <BooleanSetting path="playVideosInModal"> + <BooleanSetting + path="playVideosInModal" + expert="1" + > {{ $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> - <ChoiceSetting - id="conversationDisplay" - path="conversationDisplay" - :options="conversationDisplayOptions" - > - {{ $t('settings.conversation_display') }} - </ChoiceSetting> - </li> - <ul - v-if="conversationDisplay !== 'linear'" - class="setting-list suboptions" - > - <li> - <BooleanSetting path="conversationTreeAdvanced"> - {{ $t('settings.tree_advanced') }} - </BooleanSetting> - </li> - <li> - <BooleanSetting path="conversationTreeFadeAncestors"> - {{ $t('settings.tree_fade_ancestors') }} - </BooleanSetting> - </li> - <li> - <IntegerSetting - path="maxDepthInThread" - :min="3" - > - {{ $t('settings.max_depth_in_thread') }} - </IntegerSetting> - </li> - <li> - <ChoiceSetting - id="conversationOtherRepliesButton" - path="conversationOtherRepliesButton" - :options="conversationOtherRepliesButtonOptions" - > - {{ $t('settings.conversation_other_replies_button') }} - </ChoiceSetting> - </li> - </ul> + <h3>{{ $t('settings.mention_links') }}</h3> <li> <ChoiceSetting id="mentionLinkDisplay" @@ -205,47 +240,103 @@ <ul class="setting-list suboptions" > - <li - v-if="mentionLinkDisplay === 'short'" - > - <BooleanSetting path="mentionLinkShowTooltip"> + <li v-if="mentionLinkDisplay === 'short'"> + <BooleanSetting + path="mentionLinkShowTooltip" + expert="1" + > {{ $t('settings.mention_link_show_tooltip') }} </BooleanSetting> </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> + <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> </div> - <div class="setting-item"> + <div + v-if="user" + class="setting-item" + > <h2>{{ $t('settings.composing') }}</h2> <ul class="setting-list"> <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') }} </BooleanSetting> </li> <li> - <BooleanSetting path="alwaysShowSubjectInput"> + <BooleanSetting + path="alwaysShowSubjectInput" + expert="1" + > {{ $t('settings.subject_input_always_show') }} </BooleanSetting> </li> @@ -254,6 +345,7 @@ id="subjectLineBehavior" path="subjectLineBehavior" :options="subjectLineOptions" + expert="1" > {{ $t('settings.subject_line_behavior') }} </ChoiceSetting> @@ -268,43 +360,39 @@ </ChoiceSetting> </li> <li> - <BooleanSetting path="minimalScopesMode"> + <BooleanSetting + path="minimalScopesMode" + expert="1" + > {{ $t('settings.minimal_scopes_mode') }} </BooleanSetting> </li> <li> - <BooleanSetting path="sensitiveByDefault"> - {{ $t('settings.sensitive_by_default') }} - </BooleanSetting> - </li> - <li> - <BooleanSetting path="alwaysShowNewPostButton"> + <BooleanSetting + path="alwaysShowNewPostButton" + expert="1" + > {{ $t('settings.always_show_post_button') }} </BooleanSetting> </li> <li> - <BooleanSetting path="autohideFloatingPostButton"> + <BooleanSetting + path="autohideFloatingPostButton" + expert="1" + > {{ $t('settings.autohide_floating_post_button') }} </BooleanSetting> </li> <li> - <BooleanSetting path="padEmoji"> + <BooleanSetting + path="padEmoji" + expert="1" + > {{ $t('settings.pad_emoji') }} </BooleanSetting> </li> </ul> </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> </template> diff --git a/src/components/settings_modal/tabs/notifications_tab.js b/src/components/settings_modal/tabs/notifications_tab.js @@ -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 = { data () { @@ -9,12 +10,13 @@ const NotificationsTab = { } }, components: { - Checkbox + BooleanSetting }, computed: { user () { return this.$store.state.users.currentUser - } + }, + ...SharedComputedObject() }, methods: { updateNotificationSettings () { diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue @@ -2,30 +2,77 @@ <div :label="$t('settings.notifications')"> <div class="setting-item"> <h2>{{ $t('settings.notification_setting_filters') }}</h2> - <p> - <Checkbox v-model="notificationSettings.block_from_strangers"> - {{ $t('settings.notification_setting_block_from_strangers') }} - </Checkbox> - </p> + <ul class="setting-list"> + <li> + <BooleanSetting path="serverSide_blockNotificationsFromStrangers"> + {{ $t('settings.notification_setting_block_from_strangers') }} + </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 class="setting-item"> + <div + v-if="expertLevel > 0" + class="setting-item" + > <h2>{{ $t('settings.notification_setting_privacy') }}</h2> - <p> - <Checkbox v-model="notificationSettings.hide_notification_contents"> - {{ $t('settings.notification_setting_hide_notification_contents') }} - </Checkbox> - </p> + <ul class="setting-list"> + <li> + <BooleanSetting + path="webPushNotifications" + 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 class="setting-item"> <p>{{ $t('settings.notification_mutes') }}</p> <p>{{ $t('settings.notification_blocks') }}</p> - <button - class="btn button-default" - @click="updateNotificationSettings" - > - {{ $t('settings.save') }} - </button> </div> </div> </template> diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js @@ -8,6 +8,9 @@ import EmojiInput from 'src/components/emoji_input/emoji_input.vue' import suggestor from 'src/components/emoji_input/suggestor.js' import Autosuggest from 'src/components/autosuggest/autosuggest.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 { faTimes, @@ -27,18 +30,10 @@ const ProfileTab = { newName: this.$store.state.users.currentUser.name_unescaped, newBio: unescape(this.$store.state.users.currentUser.description), 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 })), - 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, role: this.$store.state.users.currentUser.role, - discoverable: this.$store.state.users.currentUser.discoverable, bot: this.$store.state.users.currentUser.bot, - allowFollowingMove: this.$store.state.users.currentUser.allow_following_move, pickAvatarBtnVisible: true, bannerUploading: false, backgroundUploading: false, @@ -54,12 +49,14 @@ const ProfileTab = { EmojiInput, Autosuggest, ProgressButton, - Checkbox + Checkbox, + BooleanSetting }, computed: { user () { return this.$store.state.users.currentUser }, + ...SharedComputedObject(), emojiUserSuggestor () { return suggestor({ emoji: [ @@ -123,15 +120,7 @@ const ProfileTab = { /* eslint-disable camelcase */ display_name: this.newName, 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, - allow_following_move: this.allowFollowingMove, - hide_follows_count: this.hideFollowsCount, - hide_followers_count: this.hideFollowersCount, show_role: this.showRole /* eslint-enable camelcase */ } }).then((user) => { diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue @@ -25,61 +25,6 @@ class="bio resize-height" /> </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'"> <Checkbox v-model="showRole"> <template v-if="role === 'admin'"> @@ -90,11 +35,6 @@ </template> </Checkbox> </p> - <p> - <Checkbox v-model="discoverable"> - {{ $t('settings.discoverable') }} - </Checkbox> - </p> <div v-if="maxFields > 0"> <p>{{ $t('settings.profile_fields.label') }}</p> <div @@ -269,6 +209,67 @@ {{ $t('settings.save') }} </button> </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> </template> diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue @@ -19,7 +19,7 @@ @load="onLoad" @error="onError" > - <slot/> + <slot /> </div> </template> diff --git a/src/components/user_avatar/user_avatar.vue b/src/components/user_avatar/user_avatar.vue @@ -8,7 +8,11 @@ :class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }" :image-load-error="imageLoadError" > - <FAIcon v-if="bot" icon="robot" class="bot-indicator" /> + <FAIcon + v-if="bot" + icon="robot" + class="bot-indicator" + /> </StillImage> <div v-else diff --git a/src/i18n/en.json b/src/i18n/en.json @@ -260,11 +260,14 @@ }, "settings": { "app_name": "App name", + "expert_mode": "Show advanced", "save": "Save changes", "security": "Security", "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", "post_look_feel": "Posts Look & Feel", + "mention_links": "Mention links", "mfa": { "otp": "OTP", "setup_otp": "Setup OTP", @@ -403,6 +406,7 @@ "name": "Label", "value": "Content" }, + "account_privacy": "Privacy", "use_contain_fit": "Don't crop the attachment in thumbnails", "name": "Name", "name_bio": "Name & bio", @@ -420,6 +424,7 @@ "no_rich_text_description": "Strip rich text formatting from all posts", "no_blocks": "No blocks", "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_followers_description": "Don't show who's following me", "hide_follows_count_description": "Don't show follow count", @@ -433,7 +438,7 @@ "valid_until": "Valid until", "revoke_token": "Revoke", "panelRadius": "Panels", - "pause_on_unfocused": "Pause streaming when tab is not focused", + "pause_on_unfocused": "Pause when tab is not focused", "presets": "Presets", "profile_background": "Profile background", "profile_banner": "Profile banner", @@ -480,10 +485,9 @@ "post_status_content_type": "Post status content type", "sensitive_by_default": "Mark posts as sensitive by default", "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", "useStreamingApi": "Receive posts and notifications real-time", - "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)", "text": "Text", "theme": "Theme", "theme_help": "Use hex color codes (#rrggbb) to customize your color theme.", diff --git a/src/main.js b/src/main.js @@ -11,6 +11,7 @@ import statusesModule from './modules/statuses.js' import usersModule from './modules/users.js' import apiModule from './modules/api.js' import configModule from './modules/config.js' +import serverSideConfigModule from './modules/serverSideConfig.js' import shoutModule from './modules/shout.js' import oauthModule from './modules/oauth.js' import authFlowModule from './modules/auth_flow.js' @@ -90,6 +91,7 @@ const persistedStateOptions = { users: usersModule, api: apiModule, config: configModule, + serverSideConfig: serverSideConfigModule, shout: shoutModule, oauth: oauthModule, authFlow: authFlowModule, diff --git a/src/modules/config.js b/src/modules/config.js @@ -18,6 +18,7 @@ export const multiChoiceProperties = [ ] export const defaultState = { + expertLevel: 0, // used to track which settings to show and hide colors: {}, theme: undefined, customTheme: undefined, @@ -44,7 +45,7 @@ export const defaultState = { alwaysShowNewPostButton: false, autohideFloatingPostButton: false, pauseOnUnfocused: true, - stopGifs: false, + stopGifs: true, replyVisibility: 'all', notificationVisibility: { follows: true, @@ -72,7 +73,7 @@ export const defaultState = { hideFilteredStatuses: undefined, // instance default playVideosInModal: false, useOneClickNsfw: false, - useContainFit: false, + useContainFit: true, greentext: undefined, // instance default useAtIcon: undefined, // instance default mentionLinkDisplay: undefined, // instance default diff --git a/src/modules/serverSideConfig.js b/src/modules/serverSideConfig.js @@ -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 diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js @@ -44,6 +44,7 @@ export const parseUser = (data) => { const mastoShort = masto && !data.hasOwnProperty('avatar') output.id = String(data.id) + output._original = data // used for server-side settings if (masto) { output.screen_name = data.acct