aboutsummaryrefslogtreecommitdiff
path: root/src/components/settings_modal
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/settings_modal')
-rw-r--r--src/components/settings_modal/helpers/boolean_setting.js24
-rw-r--r--src/components/settings_modal/helpers/boolean_setting.vue12
-rw-r--r--src/components/settings_modal/helpers/choice_setting.js18
-rw-r--r--src/components/settings_modal/helpers/choice_setting.vue12
-rw-r--r--src/components/settings_modal/helpers/integer_setting.js44
-rw-r--r--src/components/settings_modal/helpers/integer_setting.vue27
-rw-r--r--src/components/settings_modal/helpers/modified_indicator.vue14
-rw-r--r--src/components/settings_modal/helpers/server_side_indicator.vue51
-rw-r--r--src/components/settings_modal/helpers/shared_computed_object.js9
-rw-r--r--src/components/settings_modal/helpers/size_setting.js67
-rw-r--r--src/components/settings_modal/helpers/size_setting.vue54
-rw-r--r--src/components/settings_modal/settings_modal.js15
-rw-r--r--src/components/settings_modal/settings_modal.scss26
-rw-r--r--src/components/settings_modal/settings_modal.vue43
-rw-r--r--src/components/settings_modal/settings_modal_content.js9
-rw-r--r--src/components/settings_modal/settings_modal_content.vue1
-rw-r--r--src/components/settings_modal/tabs/data_import_export_tab.js29
-rw-r--r--src/components/settings_modal/tabs/data_import_export_tab.vue61
-rw-r--r--src/components/settings_modal/tabs/filtering_tab.js13
-rw-r--r--src/components/settings_modal/tabs/filtering_tab.vue169
-rw-r--r--src/components/settings_modal/tabs/general_tab.js60
-rw-r--r--src/components/settings_modal/tabs/general_tab.vue422
-rw-r--r--src/components/settings_modal/tabs/mutes_and_blocks_tab.js2
-rw-r--r--src/components/settings_modal/tabs/mutes_and_blocks_tab.scss2
-rw-r--r--src/components/settings_modal/tabs/mutes_and_blocks_tab.vue36
-rw-r--r--src/components/settings_modal/tabs/notifications_tab.js8
-rw-r--r--src/components/settings_modal/tabs/notifications_tab.vue86
-rw-r--r--src/components/settings_modal/tabs/profile_tab.js79
-rw-r--r--src/components/settings_modal/tabs/profile_tab.scss8
-rw-r--r--src/components/settings_modal/tabs/profile_tab.vue170
-rw-r--r--src/components/settings_modal/tabs/security_tab/mfa.js6
-rw-r--r--src/components/settings_modal/tabs/security_tab/mfa_totp.js2
-rw-r--r--src/components/settings_modal/tabs/security_tab/security_tab.js57
-rw-r--r--src/components/settings_modal/tabs/security_tab/security_tab.vue110
-rw-r--r--src/components/settings_modal/tabs/theme_tab/preview.vue14
-rw-r--r--src/components/settings_modal/tabs/theme_tab/theme_tab.js31
-rw-r--r--src/components/settings_modal/tabs/theme_tab/theme_tab.scss49
-rw-r--r--src/components/settings_modal/tabs/theme_tab/theme_tab.vue58
-rw-r--r--src/components/settings_modal/tabs/version_tab.vue2
39 files changed, 1435 insertions, 465 deletions
diff --git a/src/components/settings_modal/helpers/boolean_setting.js b/src/components/settings_modal/helpers/boolean_setting.js
index 5c52f697..2e6992cb 100644
--- 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,13 +29,28 @@ 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: {
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) })
+ }
+ },
+ reset () {
+ set(this.$parent, this.path, this.defaultState)
}
}
}
diff --git a/src/components/settings_modal/helpers/boolean_setting.vue b/src/components/settings_modal/helpers/boolean_setting.vue
index c3ee6583..41142966 100644
--- a/src/components/settings_modal/helpers/boolean_setting.vue
+++ b/src/components/settings_modal/helpers/boolean_setting.vue
@@ -1,11 +1,12 @@
<template>
<label
+ v-if="matchesExpertLevel"
class="BooleanSetting"
>
<Checkbox
- :checked="state"
+ :model-value="state"
:disabled="disabled"
- @change="update"
+ @update:modelValue="update"
>
<span
v-if="!!$slots.default"
@@ -13,7 +14,12 @@
>
<slot />
</span>
- <ModifiedIndicator :changed="isChanged" />
+ {{ ' ' }}
+ <ModifiedIndicator
+ :changed="isChanged"
+ :onclick="reset"
+ />
+ <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
index a15f6bac..3da559fe 100644
--- 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,13 +30,22 @@ 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: {
update (e) {
set(this.$parent, this.path, e)
+ },
+ reset () {
+ set(this.$parent, this.path, this.defaultState)
}
}
}
diff --git a/src/components/settings_modal/helpers/choice_setting.vue b/src/components/settings_modal/helpers/choice_setting.vue
index fa17661b..d141a0d6 100644
--- a/src/components/settings_modal/helpers/choice_setting.vue
+++ b/src/components/settings_modal/helpers/choice_setting.vue
@@ -1,12 +1,14 @@
<template>
<label
+ v-if="matchesExpertLevel"
class="ChoiceSetting"
>
<slot />
+ {{ ' ' }}
<Select
- :value="state"
+ :model-value="state"
:disabled="disabled"
- @change="update"
+ @update:modelValue="update"
>
<option
v-for="option in options"
@@ -17,7 +19,11 @@
{{ option.value === defaultState ? $t('settings.instance_default_simple') : '' }}
</option>
</Select>
- <ModifiedIndicator :changed="isChanged" />
+ <ModifiedIndicator
+ :changed="isChanged"
+ :onclick="reset"
+ />
+ <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
new file mode 100644
index 00000000..e64d0cee
--- /dev/null
+++ b/src/components/settings_modal/helpers/integer_setting.js
@@ -0,0 +1,44 @@
+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)
+ }
+ }
+}
diff --git a/src/components/settings_modal/helpers/integer_setting.vue b/src/components/settings_modal/helpers/integer_setting.vue
new file mode 100644
index 00000000..695e2673
--- /dev/null
+++ b/src/components/settings_modal/helpers/integer_setting.vue
@@ -0,0 +1,27 @@
+<template>
+ <span
+ v-if="matchesExpertLevel"
+ class="IntegerSetting"
+ >
+ <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>
+</template>
+
+<script src="./integer_setting.js"></script>
diff --git a/src/components/settings_modal/helpers/modified_indicator.vue b/src/components/settings_modal/helpers/modified_indicator.vue
index ad212db9..8311533a 100644
--- a/src/components/settings_modal/helpers/modified_indicator.vue
+++ b/src/components/settings_modal/helpers/modified_indicator.vue
@@ -6,14 +6,14 @@
<Popover
trigger="hover"
>
- <template v-slot:trigger>
+ <template #trigger>
&nbsp;
<FAIcon
icon="wrench"
:aria-label="$t('settings.setting_changed')"
/>
</template>
- <template v-slot:content>
+ <template #content>
<div class="modified-tooltip">
{{ $t('settings.setting_changed') }}
</div>
@@ -41,11 +41,11 @@ export default {
.ModifiedIndicator {
display: inline-block;
position: relative;
+}
- .modified-tooltip {
- margin: 0.5em 1em;
- min-width: 10em;
- text-align: center;
- }
+.modified-tooltip {
+ margin: 0.5em 1em;
+ min-width: 10em;
+ text-align: center;
}
</style>
diff --git a/src/components/settings_modal/helpers/server_side_indicator.vue b/src/components/settings_modal/helpers/server_side_indicator.vue
new file mode 100644
index 00000000..bf181959
--- /dev/null
+++ 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 #trigger>
+ &nbsp;
+ <FAIcon
+ icon="server"
+ :aria-label="$t('settings.setting_server_side')"
+ />
+ </template>
+ <template #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
index 2c833c0c..12431dca 100644
--- 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/helpers/size_setting.js b/src/components/settings_modal/helpers/size_setting.js
new file mode 100644
index 00000000..58697412
--- /dev/null
+++ b/src/components/settings_modal/helpers/size_setting.js
@@ -0,0 +1,67 @@
+import { get, set } from 'lodash'
+import ModifiedIndicator from './modified_indicator.vue'
+import Select from 'src/components/select/select.vue'
+
+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 {
+ components: {
+ ModifiedIndicator,
+ Select
+ },
+ props: {
+ path: String,
+ disabled: Boolean,
+ min: Number,
+ units: {
+ type: [String],
+ default: () => allCssUnits
+ },
+ expert: [Number, String]
+ },
+ computed: {
+ pathDefault () {
+ const [firstSegment, ...rest] = this.path.split('.')
+ return [firstSegment + 'DefaultValue', ...rest].join('.')
+ },
+ stateUnit () {
+ 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
+ }
+ },
+ methods: {
+ update (e) {
+ set(this.$parent, this.path, e)
+ },
+ reset () {
+ set(this.$parent, this.path, this.defaultState)
+ },
+ updateValue (e) {
+ set(this.$parent, this.path, parseInt(e.target.value) + this.stateUnit)
+ },
+ updateUnit (e) {
+ set(this.$parent, this.path, this.stateValue + e.target.value)
+ }
+ }
+}
diff --git a/src/components/settings_modal/helpers/size_setting.vue b/src/components/settings_modal/helpers/size_setting.vue
new file mode 100644
index 00000000..90c9f538
--- /dev/null
+++ b/src/components/settings_modal/helpers/size_setting.vue
@@ -0,0 +1,54 @@
+<template>
+ <span
+ v-if="matchesExpertLevel"
+ class="SizeSetting"
+ >
+ <label
+ :for="path"
+ class="size-label"
+ >
+ <slot />
+ </label>
+ <input
+ :id="path"
+ class="number-input"
+ type="number"
+ step="1"
+ :disabled="disabled"
+ :min="min || 0"
+ :value="stateValue"
+ @change="updateValue"
+ >
+ <Select
+ :id="path"
+ :model-value="stateUnit"
+ :disabled="disabled"
+ class="css-unit-input"
+ @change="updateUnit"
+ >
+ <option
+ v-for="option in units"
+ :key="option"
+ :value="option"
+ >
+ {{ option }}
+ </option>
+ </Select>
+ {{ ' ' }}
+ <ModifiedIndicator
+ :changed="isChanged"
+ :onclick="reset"
+ />
+ </span>
+</template>
+
+<script src="./size_setting.js"></script>
+
+<style lang="scss">
+.css-unit-input, .css-unit-input select {
+ margin-left: 0.5em;
+ width: 4em !important;
+ max-width: 4em !important;
+ min-width: 4em !important;
+}
+</style>
diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
index 04043483..0a72dca1 100644
--- 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,11 +52,12 @@ const SettingsModal = {
components: {
Modal,
Popover,
+ Checkbox,
SettingsModalContent: getResettableAsyncComponent(
() => import('./settings_modal_content.vue'),
{
- loading: PanelLoading,
- error: AsyncComponentError,
+ loadingComponent: PanelLoading,
+ errorComponent: AsyncComponentError,
delay: 0
}
)
@@ -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
index 90446b36..13cb0e65 100644
--- a/src/components/settings_modal/settings_modal.scss
+++ b/src/components/settings_modal/settings_modal.scss
@@ -2,6 +2,18 @@
.settings-modal {
overflow: hidden;
+ .setting-list,
+ .option-list {
+ list-style-type: none;
+ padding-left: 2em;
+ li {
+ margin-bottom: 0.5em;
+ }
+ .suboptions {
+ margin-top: 0.3em
+ }
+ }
+
&.peek {
.settings-modal-panel {
/* Explanation:
@@ -42,10 +54,22 @@
overflow-y: hidden;
.btn {
- min-height: 28px;
+ min-height: 2em;
min-width: 10em;
padding: 0 2em;
}
}
}
+
+ .settings-footer {
+ display: flex;
+ >* {
+ margin-right: 0.5em;
+ }
+
+ .extra-content {
+ display: flex;
+ flex-grow: 1;
+ }
+ }
}
diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue
index 583c2ecc..7b457371 100644
--- a/src/components/settings_modal/settings_modal.vue
+++ b/src/components/settings_modal/settings_modal.vue
@@ -11,23 +11,14 @@
{{ $t('settings.settings') }}
</span>
<transition name="fade">
- <template v-if="currentSaveStateNotice">
- <div
- v-if="currentSaveStateNotice.error"
- class="alert error"
- @click.prevent
- >
- {{ $t('settings.saving_err') }}
- </div>
-
- <div
- v-if="!currentSaveStateNotice.error"
- class="alert transparent"
- @click.prevent
- >
- {{ $t('settings.saving_ok') }}
- </div>
- </template>
+ <div
+ v-if="currentSaveStateNotice"
+ class="alert"
+ :class="{ transparent: !currentSaveStateNotice.error, error: currentSaveStateNotice.error}"
+ @click.prevent
+ >
+ {{ currentSaveStateNotice.error ? $t('settings.saving_err') : $t('settings.saving_ok') }}
+ </div>
</transition>
<button
class="btn button-default"
@@ -53,7 +44,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"
@@ -62,18 +53,19 @@
:bound-to="{ x: 'container' }"
remove-padding
>
- <template v-slot:trigger>
+ <template #trigger>
<button
class="btn button-default"
:title="$t('general.close')"
>
<span>{{ $t("settings.file_export_import.backup_restore") }}</span>
+ {{ ' ' }}
<FAIcon
icon="chevron-down"
/>
</button>
</template>
- <template v-slot:content="{close}">
+ <template #content="{close}">
<div class="dropdown-menu">
<button
class="button-default dropdown-item dropdown-item-icon"
@@ -108,6 +100,17 @@
</div>
</template>
</Popover>
+
+ <Checkbox
+ :model-value="!!expertLevel"
+ @update:modelValue="expertLevel = Number($event)"
+ >
+ {{ $t("settings.expert_mode") }}
+ </Checkbox>
+ <span
+ id="unscrolled-content"
+ class="extra-content"
+ />
</div>
</div>
</Modal>
diff --git a/src/components/settings_modal/settings_modal_content.js b/src/components/settings_modal/settings_modal_content.js
index 9dcf1b5a..9ac0301f 100644
--- a/src/components/settings_modal/settings_modal_content.js
+++ b/src/components/settings_modal/settings_modal_content.js
@@ -1,4 +1,4 @@
-import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import DataImportExportTab from './tabs/data_import_export_tab.vue'
import MutesAndBlocksTab from './tabs/mutes_and_blocks_tab.vue'
@@ -53,6 +53,9 @@ const SettingsModalContent = {
},
open () {
return this.$store.state.interface.settingsModalState !== 'hidden'
+ },
+ bodyLock () {
+ return this.$store.state.interface.settingsModalState === 'visible'
}
},
methods: {
@@ -60,8 +63,8 @@ const SettingsModalContent = {
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.data && elm.data.attrs['data-tab-name'] === 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)
diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_content.vue
index c9ed2a38..0be76d22 100644
--- a/src/components/settings_modal/settings_modal_content.vue
+++ b/src/components/settings_modal/settings_modal_content.vue
@@ -4,6 +4,7 @@
class="settings_tab-switcher"
:side-tab-bar="true"
:scrollable-tabs="true"
+ :body-scroll-lock="bodyLock"
>
<div
:label="$t('settings.general')"
diff --git a/src/components/settings_modal/tabs/data_import_export_tab.js b/src/components/settings_modal/tabs/data_import_export_tab.js
index f4b736d2..4895733c 100644
--- a/src/components/settings_modal/tabs/data_import_export_tab.js
+++ b/src/components/settings_modal/tabs/data_import_export_tab.js
@@ -7,11 +7,16 @@ const DataImportExportTab = {
data () {
return {
activeTab: 'profile',
- newDomainToMute: ''
+ newDomainToMute: '',
+ listBackupsError: false,
+ addBackupError: false,
+ addedBackup: false,
+ backups: []
}
},
created () {
this.$store.dispatch('fetchTokens')
+ this.fetchBackups()
},
components: {
Importer,
@@ -72,6 +77,28 @@ const DataImportExportTab = {
}
return user.screen_name
}).join('\n')
+ },
+ addBackup () {
+ this.$store.state.api.backendInteractor.addBackup()
+ .then((res) => {
+ this.addedBackup = true
+ this.addBackupError = false
+ })
+ .catch((error) => {
+ this.addedBackup = false
+ this.addBackupError = error
+ })
+ .then(() => this.fetchBackups())
+ },
+ fetchBackups () {
+ this.$store.state.api.backendInteractor.listBackups()
+ .then((res) => {
+ this.backups = res
+ this.listBackupsError = false
+ })
+ .catch((error) => {
+ this.listBackupsError = error.error
+ })
}
}
}
diff --git a/src/components/settings_modal/tabs/data_import_export_tab.vue b/src/components/settings_modal/tabs/data_import_export_tab.vue
index a406077d..e3b7f407 100644
--- a/src/components/settings_modal/tabs/data_import_export_tab.vue
+++ b/src/components/settings_modal/tabs/data_import_export_tab.vue
@@ -53,6 +53,67 @@
:export-button-label="$t('settings.mute_export_button')"
/>
</div>
+ <div class="setting-item">
+ <h2>{{ $t('settings.account_backup') }}</h2>
+ <p>{{ $t('settings.account_backup_description') }}</p>
+ <table>
+ <thead>
+ <tr>
+ <th>{{ $t('settings.account_backup_table_head') }}</th>
+ <th />
+ </tr>
+ </thead>
+ <tbody>
+ <tr
+ v-for="backup in backups"
+ :key="backup.id"
+ >
+ <td>{{ backup.inserted_at }}</td>
+ <td class="actions">
+ <a
+ v-if="backup.processed"
+ target="_blank"
+ :href="backup.url"
+ >
+ {{ $t('settings.download_backup') }}
+ </a>
+ <span
+ v-else
+ >
+ {{ $t('settings.backup_not_ready') }}
+ </span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <div
+ v-if="listBackupsError"
+ class="alert error"
+ >
+ {{ $t('settings.list_backups_error', { error }) }}
+ <button
+ :title="$t('settings.hide_list_backups_error_action')"
+ @click="listBackupsError = false"
+ >
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="times"
+ />
+ </button>
+ </div>
+ <button
+ class="btn button-default"
+ @click="addBackup"
+ >
+ {{ $t('settings.add_backup') }}
+ </button>
+ <p v-if="addedBackup">
+ {{ $t('settings.added_backup') }}
+ </p>
+ <template v-if="addBackupError !== false">
+ <p>{{ $t('settings.add_backup_error', { error: addBackupError }) }}</p>
+ </template>
+ </div>
</div>
</template>
diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js
index 4eaf4217..5354e5db 100644
--- a/src/components/settings_modal/tabs/filtering_tab.js
+++ b/src/components/settings_modal/tabs/filtering_tab.js
@@ -1,6 +1,7 @@
import { filter, trim } from 'lodash'
import BooleanSetting from '../helpers/boolean_setting.vue'
import ChoiceSetting from '../helpers/choice_setting.vue'
+import IntegerSetting from '../helpers/integer_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
@@ -17,7 +18,8 @@ const FilteringTab = {
},
components: {
BooleanSetting,
- ChoiceSetting
+ ChoiceSetting,
+ IntegerSetting
},
computed: {
...SharedComputedObject(),
@@ -36,15 +38,6 @@ const FilteringTab = {
},
// Updating nested properties
watch: {
- notificationVisibility: {
- handler (value) {
- this.$store.dispatch('setOption', {
- name: 'notificationVisibility',
- value: this.$store.getters.mergedConfig.notificationVisibility
- })
- },
- deep: true
- },
replyVisibility () {
this.$store.dispatch('queueFlushAll')
}
diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue
index 6fc9ceaa..97046ff0 100644
--- a/src/components/settings_modal/tabs/filtering_tab.vue
+++ b/src/components/settings_modal/tabs/filtering_tab.vue
@@ -1,73 +1,110 @@
<template>
<div :label="$t('settings.filtering')">
<div class="setting-item">
- <div 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>
- </div>
- <ChoiceSetting
- id="replyVisibility"
- path="replyVisibility"
- :options="replyVisibilityOptions"
- >
- {{ $t('settings.replies_in_timeline') }}
- </ChoiceSetting>
- <div>
- <BooleanSetting path="hidePostStats">
- {{ $t('settings.hide_post_stats') }}
- </BooleanSetting>
- </div>
- <div>
- <BooleanSetting path="hideUserStats">
- {{ $t('settings.hide_user_stats') }}
- </BooleanSetting>
- </div>
+ <h2>{{ $t('settings.posts') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="hideFilteredStatuses">
+ {{ $t('settings.hide_filtered_statuses') }}
+ </BooleanSetting>
+ <ul
+ class="setting-list suboptions"
+ :class="[{disabled: !streaming}]"
+ >
+ <li>
+ <BooleanSetting
+ :disabled="hideFilteredStatuses"
+ path="hideWordFilteredPosts"
+ >
+ {{ $t('settings.hide_wordfiltered_statuses') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting
+ v-if="user"
+ :disabled="hideFilteredStatuses"
+ path="hideMutedThreads"
+ >
+ {{ $t('settings.hide_muted_threads') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting
+ v-if="user"
+ :disabled="hideFilteredStatuses"
+ path="hideMutedPosts"
+ >
+ {{ $t('settings.hide_muted_posts') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <BooleanSetting path="muteBotStatuses">
+ {{ $t('settings.mute_bot_posts') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="hidePostStats">
+ {{ $t('settings.hide_post_stats') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="hideBotIndication">
+ {{ $t('settings.hide_bot_indication') }}
+ </BooleanSetting>
+ </li>
+ <ChoiceSetting
+ v-if="user"
+ id="replyVisibility"
+ path="replyVisibility"
+ :options="replyVisibilityOptions"
+ >
+ {{ $t('settings.replies_in_timeline') }}
+ </ChoiceSetting>
+ <li>
+ <h3>{{ $t('settings.wordfilter') }}</h3>
+ <textarea
+ id="muteWords"
+ v-model="muteWordsString"
+ class="resize-height"
+ />
+ <div>{{ $t('settings.filtering_explanation') }}</div>
+ </li>
+ <h3>{{ $t('settings.attachments') }}</h3>
+ <li>
+ <IntegerSetting
+ path="maxThumbnails"
+ expert="1"
+ :min="0"
+ >
+ {{ $t('settings.max_thumbnails') }}
+ </IntegerSetting>
+ </li>
+ <li>
+ <BooleanSetting path="hideAttachments">
+ {{ $t('settings.hide_attachments_in_tl') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="hideAttachmentsInConv">
+ {{ $t('settings.hide_attachments_in_convo') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</div>
- <div class="setting-item">
- <div>
- <p>{{ $t('settings.filtering_explanation') }}</p>
- <textarea
- id="muteWords"
- v-model="muteWordsString"
- class="resize-height"
- />
- </div>
- <div>
- <BooleanSetting path="hideFilteredStatuses">
- {{ $t('settings.hide_filtered_statuses') }}
- </BooleanSetting>
- </div>
+ <div
+ v-if="expertLevel > 0"
+ class="setting-item"
+ >
+ <h2>{{ $t('settings.user_profiles') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="hideUserStats">
+ {{ $t('settings.hide_user_stats') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</div>
</div>
</template>
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index eeda61bf..ea24d6ad 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -1,8 +1,12 @@
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 SizeSetting, { defaultHorizontalUnits } from '../helpers/size_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
@@ -20,6 +24,31 @@ const GeneralTab = {
value: mode,
label: this.$t(`settings.subject_line_${mode === 'masto' ? 'mastodon' : mode}`)
})),
+ conversationDisplayOptions: ['tree', 'linear'].map(mode => ({
+ key: mode,
+ value: mode,
+ label: this.$t(`settings.conversation_display_${mode}`)
+ })),
+ conversationOtherRepliesButtonOptions: ['below', 'inside'].map(mode => ({
+ key: mode,
+ value: mode,
+ label: this.$t(`settings.conversation_other_replies_button_${mode}`)
+ })),
+ mentionLinkDisplayOptions: ['short', 'full_for_remote', 'full'].map(mode => ({
+ key: mode,
+ value: mode,
+ label: this.$t(`settings.mention_link_display_${mode}`)
+ })),
+ thirdColumnModeOptions: ['none', 'notifications', 'postform'].map(mode => ({
+ key: mode,
+ value: mode,
+ label: this.$t(`settings.third_column_mode_${mode}`)
+ })),
+ userPopoverAvatarActionOptions: ['close', 'zoom', 'open'].map(mode => ({
+ key: mode,
+ value: mode,
+ label: this.$t(`settings.user_popover_avatar_action_${mode}`)
+ })),
loopSilentAvailable:
// Firefox
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
@@ -32,9 +61,16 @@ const GeneralTab = {
components: {
BooleanSetting,
ChoiceSetting,
- InterfaceLanguageSwitcher
+ IntegerSetting,
+ SizeSetting,
+ InterfaceLanguageSwitcher,
+ ScopeSelector,
+ ServerSideIndicator
},
computed: {
+ horizontalUnits () {
+ return defaultHorizontalUnits
+ },
postFormats () {
return this.$store.state.instance.postFormats || []
},
@@ -45,13 +81,35 @@ const GeneralTab = {
label: this.$t(`post_status.content_type["${format}"]`)
}))
},
+ columns () {
+ const mode = this.$store.getters.mergedConfig.thirdColumnMode
+
+ const notif = mode === 'none' ? [] : ['notifs']
+
+ if (this.$store.getters.mergedConfig.sidebarRight || mode === 'postform') {
+ return [...notif, 'content', 'sidebar']
+ } else {
+ return ['sidebar', 'content', ...notif]
+ }
+ },
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
instanceWallpaperUsed () {
return this.$store.state.instance.background &&
!this.$store.state.users.currentUser.background_image
},
instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable },
+ language: {
+ get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
+ set: function (val) {
+ this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
+ }
+ },
...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
index f2ec7d64..8561647b 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -4,41 +4,25 @@
<h2>{{ $t('settings.interface') }}</h2>
<ul class="setting-list">
<li>
- <interface-language-switcher />
+ <interface-language-switcher
+ :prompt-text="$t('settings.interfaceLanguage')"
+ :language="language"
+ :set-language="val => language = val"
+ />
</li>
<li v-if="instanceSpecificPanelPresent">
<BooleanSetting path="hideISP">
{{ $t('settings.hide_isp') }}
</BooleanSetting>
</li>
- <li>
- <BooleanSetting path="sidebarRight">
- {{ $t('settings.right_sidebar') }}
- </BooleanSetting>
- </li>
<li v-if="instanceWallpaperUsed">
<BooleanSetting path="hideInstanceWallpaper">
{{ $t('settings.hide_wallpaper') }}
</BooleanSetting>
</li>
- <li v-if="instanceShoutboxPresent">
- <BooleanSetting path="hideShoutbox">
- {{ $t('settings.hide_shoutbox') }}
- </BooleanSetting>
- </li>
- </ul>
- </div>
- <div class="setting-item">
- <h2>{{ $t('nav.timeline') }}</h2>
- <ul class="setting-list">
- <li>
- <BooleanSetting path="hideMutedPosts">
- {{ $t('settings.hide_muted_posts') }}
- </BooleanSetting>
- </li>
<li>
- <BooleanSetting path="collapseMessageWithSubject">
- {{ $t('settings.collapse_subject') }}
+ <BooleanSetting path="stopGifs">
+ {{ $t('settings.stop_gifs') }}
</BooleanSetting>
</li>
<li>
@@ -60,111 +44,191 @@
</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="emojiReactionsOnTimeline">
- {{ $t('settings.emoji_reactions_on_timeline') }}
+ <BooleanSetting
+ path="virtualScrolling"
+ expert="1"
+ >
+ {{ $t('settings.virtual_scrolling') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="virtualScrolling">
- {{ $t('settings.virtual_scrolling') }}
- </BooleanSetting>
+ <ChoiceSetting
+ id="userPopoverAvatarAction"
+ path="userPopoverAvatarAction"
+ :options="userPopoverAvatarActionOptions"
+ expert="1"
+ >
+ {{ $t('settings.user_popover_avatar_action') }}
+ </ChoiceSetting>
</li>
- </ul>
- </div>
-
- <div class="setting-item">
- <h2>{{ $t('settings.composing') }}</h2>
- <ul class="setting-list">
<li>
- <BooleanSetting path="scopeCopy">
- {{ $t('settings.scope_copy') }}
+ <BooleanSetting
+ path="userPopoverOverlay"
+ expert="1"
+ >
+ {{ $t('settings.user_popover_avatar_overlay') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="alwaysShowSubjectInput">
- {{ $t('settings.subject_input_always_show') }}
+ <BooleanSetting
+ path="alwaysShowNewPostButton"
+ expert="1"
+ >
+ {{ $t('settings.always_show_post_button') }}
</BooleanSetting>
</li>
<li>
- <ChoiceSetting
- id="subjectLineBehavior"
- path="subjectLineBehavior"
- :options="subjectLineOptions"
+ <BooleanSetting
+ path="autohideFloatingPostButton"
+ expert="1"
>
- {{ $t('settings.subject_line_behavior') }}
- </ChoiceSetting>
+ {{ $t('settings.autohide_floating_post_button') }}
+ </BooleanSetting>
</li>
- <li v-if="postFormats.length > 0">
- <ChoiceSetting
- id="postContentType"
- path="postContentType"
- :options="postContentOptions"
+ <li v-if="instanceShoutboxPresent">
+ <BooleanSetting
+ path="hideShoutbox"
+ expert="1"
>
- {{ $t('settings.post_status_content_type') }}
- </ChoiceSetting>
+ {{ $t('settings.hide_shoutbox') }}
+ </BooleanSetting>
</li>
<li>
- <BooleanSetting path="minimalScopesMode">
- {{ $t('settings.minimal_scopes_mode') }}
- </BooleanSetting>
+ <h3>{{ $t('settings.columns') }}</h3>
</li>
<li>
- <BooleanSetting path="sensitiveByDefault">
- {{ $t('settings.sensitive_by_default') }}
+ <BooleanSetting path="disableStickyHeaders">
+ {{ $t('settings.disable_sticky_headers') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="alwaysShowNewPostButton">
- {{ $t('settings.always_show_post_button') }}
+ <BooleanSetting path="showScrollbars">
+ {{ $t('settings.show_scrollbars') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="autohideFloatingPostButton">
- {{ $t('settings.autohide_floating_post_button') }}
+ <BooleanSetting path="sidebarRight">
+ {{ $t('settings.right_sidebar') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="padEmoji">
- {{ $t('settings.pad_emoji') }}
+ <BooleanSetting path="navbarColumnStretch">
+ {{ $t('settings.navbar_column_stretch') }}
</BooleanSetting>
</li>
+ <li>
+ <ChoiceSetting
+ v-if="user"
+ id="thirdColumnMode"
+ path="thirdColumnMode"
+ :options="thirdColumnModeOptions"
+ >
+ {{ $t('settings.third_column_mode') }}
+ </ChoiceSetting>
+ </li>
+ <li v-if="expertLevel > 0">
+ {{ $t('settings.column_sizes') }}
+ <div class="column-settings">
+ <SizeSetting
+ v-for="column in columns"
+ :key="column"
+ :path="column + 'ColumnWidth'"
+ :units="horizontalUnits"
+ expert="1"
+ >
+ {{ $t('settings.column_sizes_' + column) }}
+ </SizeSetting>
+ </div>
+ </li>
</ul>
</div>
-
<div class="setting-item">
- <h2>{{ $t('settings.attachments') }}</h2>
+ <h2>{{ $t('settings.post_look_feel') }}</h2>
<ul class="setting-list">
<li>
- <BooleanSetting path="hideAttachments">
- {{ $t('settings.hide_attachments_in_tl') }}
+ <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="hideAttachmentsInConv">
- {{ $t('settings.hide_attachments_in_convo') }}
+ <BooleanSetting
+ path="emojiReactionsOnTimeline"
+ expert="1"
+ >
+ {{ $t('settings.emoji_reactions_on_timeline') }}
</BooleanSetting>
</li>
<li>
- <label for="maxThumbnails">
- {{ $t('settings.max_thumbnails') }}
- </label>
- <input
- id="maxThumbnails"
- path.number="maxThumbnails"
- class="number-input"
- type="number"
- min="0"
- step="1"
+ <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">
@@ -175,6 +239,7 @@
<li>
<BooleanSetting
path="preloadImage"
+ expert="1"
:disabled="!hideNsfw"
>
{{ $t('settings.preload_images') }}
@@ -183,6 +248,7 @@
<li>
<BooleanSetting
path="useOneClickNsfw"
+ expert="1"
:disabled="!hideNsfw"
>
{{ $t('settings.use_one_click_nsfw') }}
@@ -190,12 +256,10 @@
</li>
</ul>
<li>
- <BooleanSetting path="stopGifs">
- {{ $t('settings.stop_gifs') }}
- </BooleanSetting>
- </li>
- <li>
- <BooleanSetting path="loopVideo">
+ <BooleanSetting
+ path="loopVideo"
+ expert="1"
+ >
{{ $t('settings.loop_video') }}
</BooleanSetting>
<ul
@@ -205,6 +269,7 @@
<li>
<BooleanSetting
path="loopVideoSilentOnly"
+ expert="1"
:disabled="!loopVideo || !loopSilentAvailable"
>
{{ $t('settings.loop_video_silent_only') }}
@@ -219,35 +284,171 @@
</ul>
</li>
<li>
- <BooleanSetting path="playVideosInModal">
+ <BooleanSetting
+ path="playVideosInModal"
+ expert="1"
+ >
{{ $t('settings.play_videos_in_modal') }}
</BooleanSetting>
</li>
+ <h3>{{ $t('settings.mention_links') }}</h3>
<li>
- <BooleanSetting path="useContainFit">
- {{ $t('settings.use_contain_fit') }}
+ <ChoiceSetting
+ id="mentionLinkDisplay"
+ path="mentionLinkDisplay"
+ :options="mentionLinkDisplayOptions"
+ >
+ {{ $t('settings.mention_link_display') }}
+ </ChoiceSetting>
+ </li>
+ <li>
+ <BooleanSetting
+ path="mentionLinkShowTooltip"
+ expert="1"
+ >
+ {{ $t('settings.mention_link_use_tooltip') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting
+ path="useAtIcon"
+ expert="1"
+ >
+ {{ $t('settings.use_at_icon') }}
</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 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">
- <h2>{{ $t('settings.fun') }}</h2>
+ <div
+ v-if="user"
+ class="setting-item"
+ >
+ <h2>{{ $t('settings.composing') }}</h2>
<ul class="setting-list">
<li>
- <BooleanSetting path="greentext">
- {{ $t('settings.greentext') }}
+ <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"
+ expert="1"
+ >
+ {{ $t('settings.subject_input_always_show') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <ChoiceSetting
+ id="subjectLineBehavior"
+ path="subjectLineBehavior"
+ :options="subjectLineOptions"
+ expert="1"
+ >
+ {{ $t('settings.subject_line_behavior') }}
+ </ChoiceSetting>
+ </li>
+ <li v-if="postFormats.length > 0">
+ <ChoiceSetting
+ id="postContentType"
+ path="postContentType"
+ :options="postContentOptions"
+ >
+ {{ $t('settings.post_status_content_type') }}
+ </ChoiceSetting>
+ </li>
+ <li>
+ <BooleanSetting
+ path="minimalScopesMode"
+ expert="1"
+ >
+ {{ $t('settings.minimal_scopes_mode') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <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>
+ <BooleanSetting
+ path="padEmoji"
+ expert="1"
+ >
+ {{ $t('settings.pad_emoji') }}
</BooleanSetting>
</li>
</ul>
@@ -256,3 +457,16 @@
</template>
<script src="./general_tab.js"></script>
+
+<style lang="scss">
+.column-settings {
+ display: flex;
+ justify-content: space-evenly;
+ flex-wrap: wrap;
+}
+.column-settings .size-label {
+ display: block;
+ margin-bottom: 0.5em;
+ margin-top: 0.5em;
+}
+</style>
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
index 40a87b81..6cfeea35 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
@@ -2,7 +2,7 @@ import get from 'lodash/get'
import map from 'lodash/map'
import reject from 'lodash/reject'
import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
-import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import BlockCard from 'src/components/block_card/block_card.vue'
import MuteCard from 'src/components/mute_card/mute_card.vue'
import DomainMuteCard from 'src/components/domain_mute_card/domain_mute_card.vue'
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss b/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss
index ceb64efb..2adff847 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss
@@ -8,7 +8,7 @@
.bulk-actions {
text-align: right;
padding: 0 1em;
- min-height: 28px;
+ min-height: 2em;
}
.bulk-action-button {
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
index 32a21415..ed4b15a4 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
@@ -10,7 +10,7 @@
:query="queryUserIds"
:placeholder="$t('settings.search_user_to_block')"
>
- <template v-slot="row">
+ <template #default="row">
<BlockCard
:user-id="row.item"
/>
@@ -21,7 +21,7 @@
:refresh="true"
:get-key="i => i"
>
- <template v-slot:header="{selected}">
+ <template #header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -29,7 +29,7 @@
:click="() => blockUsers(selected)"
>
{{ $t('user_card.block') }}
- <template v-slot:progress>
+ <template #progress>
{{ $t('user_card.block_progress') }}
</template>
</ProgressButton>
@@ -39,16 +39,16 @@
:click="() => unblockUsers(selected)"
>
{{ $t('user_card.unblock') }}
- <template v-slot:progress>
+ <template #progress>
{{ $t('user_card.unblock_progress') }}
</template>
</ProgressButton>
</div>
</template>
- <template v-slot:item="{item}">
+ <template #item="{item}">
<BlockCard :user-id="item" />
</template>
- <template v-slot:empty>
+ <template #empty>
{{ $t('settings.no_blocks') }}
</template>
</BlockList>
@@ -56,14 +56,14 @@
<div :label="$t('settings.mutes_tab')">
<tab-switcher>
- <div label="Users">
+ <div :label="$t('settings.user_mutes')">
<div class="usersearch-wrapper">
<Autosuggest
:filter="filterUnMutedUsers"
:query="queryUserIds"
:placeholder="$t('settings.search_user_to_mute')"
>
- <template v-slot="row">
+ <template #default="row">
<MuteCard
:user-id="row.item"
/>
@@ -74,7 +74,7 @@
:refresh="true"
:get-key="i => i"
>
- <template v-slot:header="{selected}">
+ <template #header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -82,7 +82,7 @@
:click="() => muteUsers(selected)"
>
{{ $t('user_card.mute') }}
- <template v-slot:progress>
+ <template #progress>
{{ $t('user_card.mute_progress') }}
</template>
</ProgressButton>
@@ -92,16 +92,16 @@
:click="() => unmuteUsers(selected)"
>
{{ $t('user_card.unmute') }}
- <template v-slot:progress>
+ <template #progress>
{{ $t('user_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
- <template v-slot:item="{item}">
+ <template #item="{item}">
<MuteCard :user-id="item" />
</template>
- <template v-slot:empty>
+ <template #empty>
{{ $t('settings.no_mutes') }}
</template>
</MuteList>
@@ -114,7 +114,7 @@
:query="queryKnownDomains"
:placeholder="$t('settings.type_domains_to_mute')"
>
- <template v-slot="row">
+ <template #default="row">
<DomainMuteCard
:domain="row.item"
/>
@@ -125,7 +125,7 @@
:refresh="true"
:get-key="i => i"
>
- <template v-slot:header="{selected}">
+ <template #header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -133,16 +133,16 @@
:click="() => unmuteDomains(selected)"
>
{{ $t('domain_mute_card.unmute') }}
- <template v-slot:progress>
+ <template #progress>
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
- <template v-slot:item="{item}">
+ <template #item="{item}">
<DomainMuteCard :domain="item" />
</template>
- <template v-slot:empty>
+ <template #empty>
{{ $t('settings.no_mutes') }}
</template>
</DomainMuteList>
diff --git a/src/components/settings_modal/tabs/notifications_tab.js b/src/components/settings_modal/tabs/notifications_tab.js
index 3e44c95d..3c6ab87f 100644
--- 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
index 7e0568ea..dd3806ed 100644
--- a/src/components/settings_modal/tabs/notifications_tab.vue
+++ b/src/components/settings_modal/tabs/notifications_tab.vue
@@ -2,30 +2,82 @@
<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>
+ <li>
+ <BooleanSetting path="notificationVisibility.polls">
+ {{ $t('settings.notification_visibility_polls') }}
+ </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
index 64079fcd..b86faef0 100644
--- a/src/components/settings_modal/tabs/profile_tab.js
+++ b/src/components/settings_modal/tabs/profile_tab.js
@@ -8,6 +8,11 @@ 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 InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
+import BooleanSetting from '../helpers/boolean_setting.vue'
+import SharedComputedObject from '../helpers/shared_computed_object.js'
+import localeService from 'src/services/locale/locale.service.js'
+
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes,
@@ -27,25 +32,18 @@ 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,
banner: null,
bannerPreview: null,
background: null,
- backgroundPreview: null
+ backgroundPreview: null,
+ emailLanguage: this.$store.state.users.currentUser.language || ''
}
},
components: {
@@ -54,26 +52,31 @@ const ProfileTab = {
EmojiInput,
Autosuggest,
ProgressButton,
- Checkbox
+ Checkbox,
+ BooleanSetting,
+ InterfaceLanguageSwitcher
},
computed: {
user () {
return this.$store.state.users.currentUser
},
+ ...SharedComputedObject(),
emojiUserSuggestor () {
return suggestor({
emoji: [
- ...this.$store.state.instance.emoji,
+ ...this.$store.getters.standardEmojiList,
...this.$store.state.instance.customEmoji
],
store: this.$store
})
},
emojiSuggestor () {
- return suggestor({ emoji: [
- ...this.$store.state.instance.emoji,
- ...this.$store.state.instance.customEmoji
- ] })
+ return suggestor({
+ emoji: [
+ ...this.$store.getters.standardEmojiList,
+ ...this.$store.state.instance.customEmoji
+ ]
+ })
},
userSuggestor () {
return suggestor({ store: this.$store })
@@ -114,27 +117,25 @@ const ProfileTab = {
},
methods: {
updateProfile () {
+ const params = {
+ note: this.newBio,
+ locked: this.newLocked,
+ // Backend notation.
+ /* eslint-disable camelcase */
+ display_name: this.newName,
+ fields_attributes: this.newFields.filter(el => el != null),
+ bot: this.bot,
+ show_role: this.showRole
+ /* eslint-enable camelcase */
+ }
+
+ if (this.emailLanguage) {
+ params.language = localeService.internalToBackendLocale(this.emailLanguage)
+ }
+
this.$store.state.api.backendInteractor
- .updateProfile({
- params: {
- note: this.newBio,
- locked: this.newLocked,
- // Backend notation.
- /* 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) => {
+ .updateProfile({ params })
+ .then((user) => {
this.newFields.splice(user.fields.length)
merge(this.newFields, user.fields)
this.$store.commit('addNewUsers', [user])
@@ -204,8 +205,8 @@ const ProfileTab = {
submitAvatar (cropper, file) {
const that = this
return new Promise((resolve, reject) => {
- function updateAvatar (avatar) {
- that.$store.state.api.backendInteractor.updateProfileImages({ avatar })
+ function updateAvatar (avatar, avatarName) {
+ that.$store.state.api.backendInteractor.updateProfileImages({ avatar, avatarName })
.then((user) => {
that.$store.commit('addNewUsers', [user])
that.$store.commit('setCurrentUser', user)
@@ -218,9 +219,9 @@ const ProfileTab = {
}
if (cropper) {
- cropper.getCroppedCanvas().toBlob(updateAvatar, file.type)
+ cropper.getCroppedCanvas().toBlob((data) => updateAvatar(data, file.name), file.type)
} else {
- updateAvatar(file)
+ updateAvatar(file, file.name)
}
})
},
diff --git a/src/components/settings_modal/tabs/profile_tab.scss b/src/components/settings_modal/tabs/profile_tab.scss
index 111eaed3..201f1a76 100644
--- a/src/components/settings_modal/tabs/profile_tab.scss
+++ b/src/components/settings_modal/tabs/profile_tab.scss
@@ -54,16 +54,20 @@
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
background-color: rgba(0, 0, 0, 0.6);
opacity: 0.7;
- color: white;
width: 1.5em;
height: 1.5em;
text-align: center;
line-height: 1.5em;
font-size: 1.5em;
cursor: pointer;
+
&:hover {
opacity: 1;
}
+
+ svg {
+ color: white;
+ }
}
.oauth-tokens {
@@ -85,7 +89,7 @@
&-bulk-actions {
text-align: right;
padding: 0 1em;
- min-height: 28px;
+ min-height: 2em;
button {
width: 10em;
diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue
index bb3c301d..642d54ca 100644
--- 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
@@ -128,8 +68,9 @@
class="delete-field button-unstyled -hover-highlight"
@click="deleteField(i)"
>
+ <!-- TODO something is wrong with v-show here -->
<FAIcon
- v-show="newFields.length > 1"
+ v-if="newFields.length > 1"
icon="times"
/>
</button>
@@ -148,6 +89,13 @@
{{ $t('settings.bot') }}
</Checkbox>
</p>
+ <p>
+ <interface-language-switcher
+ :prompt-text="$t('settings.email_language')"
+ :language="emailLanguage"
+ :set-language="val => emailLanguage = val"
+ />
+ </p>
<button
:disabled="newName && newName.length === 0"
class="btn button-default"
@@ -166,14 +114,17 @@
:src="user.profile_image_url_original"
class="current-avatar"
>
- <FAIcon
+ <button
v-if="!isDefaultAvatar && pickAvatarBtnVisible"
:title="$t('settings.reset_avatar')"
- class="reset-button"
- icon="times"
- type="button"
+ class="button-unstyled reset-button"
@click="resetAvatar"
- />
+ >
+ <FAIcon
+ icon="times"
+ type="button"
+ />
+ </button>
</div>
<p>{{ $t('settings.set_new_avatar') }}</p>
<button
@@ -195,14 +146,17 @@
<h2>{{ $t('settings.profile_banner') }}</h2>
<div class="banner-background-preview">
<img :src="user.cover_photo">
- <FAIcon
+ <button
v-if="!isDefaultBanner"
+ class="button-unstyled reset-button"
:title="$t('settings.reset_profile_banner')"
- class="reset-button"
- icon="times"
- type="button"
@click="resetBanner"
- />
+ >
+ <FAIcon
+ icon="times"
+ type="button"
+ />
+ </button>
</div>
<p>{{ $t('settings.set_new_profile_banner') }}</p>
<img
@@ -234,14 +188,17 @@
<h2>{{ $t('settings.profile_background') }}</h2>
<div class="banner-background-preview">
<img :src="user.background_image">
- <FAIcon
+ <button
v-if="!isDefaultBackground"
+ class="button-unstyled reset-button"
:title="$t('settings.reset_profile_background')"
- class="reset-button"
- icon="times"
- type="button"
@click="resetBackground"
- />
+ >
+ <FAIcon
+ icon="times"
+ type="button"
+ />
+ </button>
</div>
<p>{{ $t('settings.set_new_profile_background') }}</p>
<img
@@ -269,6 +226,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/settings_modal/tabs/security_tab/mfa.js b/src/components/settings_modal/tabs/security_tab/mfa.js
index abf37062..5337d150 100644
--- a/src/components/settings_modal/tabs/security_tab/mfa.js
+++ b/src/components/settings_modal/tabs/security_tab/mfa.js
@@ -32,8 +32,8 @@ const Mfa = {
components: {
'recovery-codes': RecoveryCodes,
'totp-item': TOTP,
- 'qrcode': VueQrcode,
- 'confirm': Confirm
+ qrcode: VueQrcode,
+ confirm: Confirm
},
computed: {
canSetupOTP () {
@@ -139,7 +139,7 @@ const Mfa = {
// fetch settings from server
async fetchSettings () {
- let result = await this.backendInteractor.settingsMFA()
+ const result = await this.backendInteractor.settingsMFA()
if (result.error) return
this.settings = result.settings
this.settings.available = true
diff --git a/src/components/settings_modal/tabs/security_tab/mfa_totp.js b/src/components/settings_modal/tabs/security_tab/mfa_totp.js
index 8408d8e9..b0adb530 100644
--- a/src/components/settings_modal/tabs/security_tab/mfa_totp.js
+++ b/src/components/settings_modal/tabs/security_tab/mfa_totp.js
@@ -10,7 +10,7 @@ export default {
inProgress: false // progress peform request to disable otp method
}),
components: {
- 'confirm': Confirm
+ confirm: Confirm
},
computed: {
isActivated () {
diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.js b/src/components/settings_modal/tabs/security_tab/security_tab.js
index 65d20fc0..d253bc79 100644
--- a/src/components/settings_modal/tabs/security_tab/security_tab.js
+++ b/src/components/settings_modal/tabs/security_tab/security_tab.js
@@ -13,13 +13,23 @@ const SecurityTab = {
deletingAccount: false,
deleteAccountConfirmPasswordInput: '',
deleteAccountError: false,
- changePasswordInputs: [ '', '', '' ],
+ changePasswordInputs: ['', '', ''],
changedPassword: false,
- changePasswordError: false
+ changePasswordError: false,
+ moveAccountTarget: '',
+ moveAccountPassword: '',
+ movedAccount: false,
+ moveAccountError: false,
+ aliases: [],
+ listAliasesError: false,
+ addAliasTarget: '',
+ addedAlias: false,
+ addAliasError: false
}
},
created () {
this.$store.dispatch('fetchTokens')
+ this.fetchAliases()
},
components: {
ProgressButton,
@@ -92,6 +102,49 @@ const SecurityTab = {
}
})
},
+ moveAccount () {
+ const params = {
+ targetAccount: this.moveAccountTarget,
+ password: this.moveAccountPassword
+ }
+ this.$store.state.api.backendInteractor.moveAccount(params)
+ .then((res) => {
+ if (res.status === 'success') {
+ this.movedAccount = true
+ this.moveAccountError = false
+ } else {
+ this.movedAccount = false
+ this.moveAccountError = res.error
+ }
+ })
+ },
+ removeAlias (alias) {
+ this.$store.state.api.backendInteractor.deleteAlias({ alias })
+ .then(() => this.fetchAliases())
+ },
+ addAlias () {
+ this.$store.state.api.backendInteractor.addAlias({ alias: this.addAliasTarget })
+ .then((res) => {
+ this.addedAlias = true
+ this.addAliasError = false
+ this.addAliasTarget = ''
+ })
+ .catch((error) => {
+ this.addedAlias = false
+ this.addAliasError = error
+ })
+ .then(() => this.fetchAliases())
+ },
+ fetchAliases () {
+ this.$store.state.api.backendInteractor.listAliases()
+ .then((res) => {
+ this.aliases = res.aliases
+ this.listAliasesError = false
+ })
+ .catch((error) => {
+ this.listAliasesError = error.error
+ })
+ },
logout () {
this.$store.dispatch('logout')
this.$router.replace('/')
diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.vue b/src/components/settings_modal/tabs/security_tab/security_tab.vue
index 275d4616..6e03bef4 100644
--- a/src/components/settings_modal/tabs/security_tab/security_tab.vue
+++ b/src/components/settings_modal/tabs/security_tab/security_tab.vue
@@ -103,6 +103,114 @@
</table>
</div>
<mfa />
+
+ <div class="setting-item">
+ <h2>{{ $t('settings.account_alias') }}</h2>
+ <table>
+ <thead>
+ <tr>
+ <th>{{ $t('settings.account_alias_table_head') }}</th>
+ <th />
+ </tr>
+ </thead>
+ <tbody>
+ <tr
+ v-for="alias in aliases"
+ :key="alias"
+ >
+ <td>{{ alias }}</td>
+ <td class="actions">
+ <button
+ class="btn button-default"
+ @click="removeAlias(alias)"
+ >
+ {{ $t('settings.remove_alias') }}
+ </button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <div
+ v-if="listAliasesError"
+ class="alert error"
+ >
+ {{ $t('settings.list_aliases_error', { error }) }}
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="times"
+ :title="$t('settings.hide_list_aliases_error_action')"
+ @click="listAliasesError = false"
+ />
+ </div>
+ <div>
+ <i18n
+ path="settings.new_alias_target"
+ tag="p"
+ >
+ <code
+ place="example"
+ >
+ foo@example.org
+ </code>
+ </i18n>
+ <input
+ v-model="addAliasTarget"
+ >
+ </div>
+ <button
+ class="btn button-default"
+ @click="addAlias"
+ >
+ {{ $t('settings.save') }}
+ </button>
+ <p v-if="addedAlias">
+ {{ $t('settings.added_alias') }}
+ </p>
+ <template v-if="addAliasError !== false">
+ <p>{{ $t('settings.add_alias_error', { error: addAliasError }) }}</p>
+ </template>
+ </div>
+
+ <div class="setting-item">
+ <h2>{{ $t('settings.move_account') }}</h2>
+ <p>{{ $t('settings.move_account_notes') }}</p>
+ <div>
+ <i18n
+ path="settings.move_account_target"
+ tag="p"
+ >
+ <code
+ place="example"
+ >
+ foo@example.org
+ </code>
+ </i18n>
+ <input
+ v-model="moveAccountTarget"
+ >
+ </div>
+ <div>
+ <p>{{ $t('settings.current_password') }}</p>
+ <input
+ v-model="moveAccountPassword"
+ type="password"
+ autocomplete="current-password"
+ >
+ </div>
+ <button
+ class="btn button-default"
+ @click="moveAccount"
+ >
+ {{ $t('settings.save') }}
+ </button>
+ <p v-if="movedAccount">
+ {{ $t('settings.moved_account') }}
+ </p>
+ <template v-if="moveAccountError !== false">
+ <p>{{ $t('settings.move_account_error', { error: moveAccountError }) }}</p>
+ </template>
+ </div>
+
<div class="setting-item">
<h2>{{ $t('settings.delete_account') }}</h2>
<p v-if="!deletingAccount">
@@ -133,7 +241,7 @@
class="btn button-default"
@click="confirmDelete"
>
- {{ $t('settings.save') }}
+ {{ $t('settings.delete_account') }}
</button>
</div>
</div>
diff --git a/src/components/settings_modal/tabs/theme_tab/preview.vue b/src/components/settings_modal/tabs/theme_tab/preview.vue
index 7ac7b9d3..ba6bd529 100644
--- a/src/components/settings_modal/tabs/theme_tab/preview.vue
+++ b/src/components/settings_modal/tabs/theme_tab/preview.vue
@@ -29,14 +29,17 @@
{{ $t('settings.style.preview.content') }}
</h4>
- <i18n path="settings.style.preview.text">
+ <i18n-t
+ scope="global"
+ keypath="settings.style.preview.text"
+ >
<code style="font-family: var(--postCodeFont)">
{{ $t('settings.style.preview.mono') }}
</code>
<a style="color: var(--link)">
{{ $t('settings.style.preview.link') }}
</a>
- </i18n>
+ </i18n-t>
<div class="icons">
<FAIcon
@@ -72,15 +75,16 @@
:^)
</div>
<div class="content">
- <i18n
- path="settings.style.preview.fine_print"
+ <i18n-t
+ keypath="settings.style.preview.fine_print"
tag="span"
class="faint"
+ scope="global"
>
<a style="color: var(--faintLink)">
{{ $t('settings.style.preview.faint_link') }}
</a>
- </i18n>
+ </i18n-t>
</div>
</div>
<div class="separator" />
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
index 0b6669fc..4a739f73 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.js
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
@@ -1,4 +1,3 @@
-import { set, delete as del } from 'vue'
import {
rgb2hex,
hex2rgb,
@@ -34,7 +33,7 @@ import OpacityInput from 'src/components/opacity_input/opacity_input.vue'
import ShadowControl from 'src/components/shadow_control/shadow_control.vue'
import FontControl from 'src/components/font_control/font_control.vue'
import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue'
-import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import Select from 'src/components/select/select.vue'
@@ -96,11 +95,11 @@ export default {
...Object.keys(SLOT_INHERITANCE)
.map(key => [key, ''])
- .reduce((acc, [key, val]) => ({ ...acc, [ key + 'ColorLocal' ]: val }), {}),
+ .reduce((acc, [key, val]) => ({ ...acc, [key + 'ColorLocal']: val }), {}),
...Object.keys(OPACITIES)
.map(key => [key, ''])
- .reduce((acc, [key, val]) => ({ ...acc, [ key + 'OpacityLocal' ]: val }), {}),
+ .reduce((acc, [key, val]) => ({ ...acc, [key + 'OpacityLocal']: val }), {}),
shadowSelected: undefined,
shadowsLocal: {},
@@ -213,12 +212,12 @@ export default {
currentColors () {
return Object.keys(SLOT_INHERITANCE)
.map(key => [key, this[key + 'ColorLocal']])
- .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {})
+ .reduce((acc, [key, val]) => ({ ...acc, [key]: val }), {})
},
currentOpacity () {
return Object.keys(OPACITIES)
.map(key => [key, this[key + 'OpacityLocal']])
- .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {})
+ .reduce((acc, [key, val]) => ({ ...acc, [key]: val }), {})
},
currentRadii () {
return {
@@ -280,6 +279,9 @@ export default {
opacity
)
+ // Temporary patch for null-y value errors
+ if (layers.flat().some(v => v == null)) return acc
+
return {
...acc,
...textColors.reduce((acc, textColorKey) => {
@@ -301,6 +303,7 @@ export default {
return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
} catch (e) {
console.warn('Failure computing contrasts', e)
+ return {}
}
},
previewRules () {
@@ -320,9 +323,9 @@ export default {
},
set (val) {
if (val) {
- set(this.shadowsLocal, this.shadowSelected, this.currentShadowFallback.map(_ => Object.assign({}, _)))
+ this.shadowsLocal[this.shadowSelected] = this.currentShadowFallback.map(_ => Object.assign({}, _))
} else {
- del(this.shadowsLocal, this.shadowSelected)
+ delete this.shadowsLocal[this.shadowSelected]
}
}
},
@@ -334,7 +337,7 @@ export default {
return this.shadowsLocal[this.shadowSelected]
},
set (v) {
- set(this.shadowsLocal, this.shadowSelected, v)
+ this.shadowsLocal[this.shadowSelected] = v
}
},
themeValid () {
@@ -378,6 +381,10 @@ export default {
// To separate from other random JSON files and possible future source formats
_pleroma_theme_version: 2, theme, source
}
+ },
+ isActive () {
+ const tabSwitcher = this.$parent
+ return tabSwitcher ? tabSwitcher.isActive('theme') : false
}
},
components: {
@@ -557,7 +564,7 @@ export default {
.filter(_ => _.endsWith('ColorLocal') || _.endsWith('OpacityLocal'))
.filter(_ => !v1OnlyNames.includes(_))
.forEach(key => {
- set(this.$data, key, undefined)
+ this.$data[key] = undefined
})
},
@@ -565,7 +572,7 @@ export default {
Object.keys(this.$data)
.filter(_ => _.endsWith('RadiusLocal'))
.forEach(key => {
- set(this.$data, key, undefined)
+ this.$data[key] = undefined
})
},
@@ -573,7 +580,7 @@ export default {
Object.keys(this.$data)
.filter(_ => _.endsWith('OpacityLocal'))
.forEach(key => {
- set(this.$data, key, undefined)
+ this.$data[key] = undefined
})
},
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
index 0db21537..bad6f51b 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
@@ -245,36 +245,12 @@
border-color: var(--border, $fallback--border);
}
- .panel-heading {
- .badge, .alert, .btn, .faint {
- margin-left: 1em;
- white-space: nowrap;
- }
- .faint {
- text-overflow: ellipsis;
- min-width: 2em;
- overflow-x: hidden;
- }
- .flex-spacer {
- flex: 1;
- }
- }
.btn {
- margin-left: 0;
- padding: 0 1em;
min-width: 3em;
- min-height: 30px;
}
}
}
- .apply-container {
- justify-content: center;
- position: absolute;
- bottom: 8px;
- right: 5px;
- }
-
.radius-item,
.color-item {
min-width: 20em;
@@ -334,16 +310,25 @@
padding: 20px;
}
- .apply-container {
- .btn {
- min-height: 28px;
- min-width: 10em;
- padding: 0 2em;
- }
- }
-
.btn {
margin-left: .25em;
margin-right: .25em;
}
}
+
+.extra-content {
+ .apply-container {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ flex-grow: 1;
+
+ .btn {
+ flex-grow: 1;
+ min-height: 2em;
+ min-width: 0;
+ max-width: 10em;
+ padding: 0;
+ }
+ }
+}
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
index c02986ed..ff2fece9 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
@@ -903,6 +903,7 @@
<div class="tab-header shadow-selector">
<div class="select-container">
{{ $t('settings.style.shadows.component') }}
+ {{ ' ' }}
<Select
id="shadow-switcher"
v-model="shadowSelected"
@@ -924,6 +925,7 @@
>
{{ $t('settings.style.shadows.override') }}
</label>
+ {{ ' ' }}
<input
id="override"
v-model="currentShadowOverriden"
@@ -949,27 +951,30 @@
:fallback="currentShadowFallback"
/>
<div v-if="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'">
- <i18n
- path="settings.style.shadows.filter_hint.always_drop_shadow"
+ <i18n-t
+ scope="global"
+ keypath="settings.style.shadows.filter_hint.always_drop_shadow"
tag="p"
>
<code>filter: drop-shadow()</code>
- </i18n>
+ </i18n-t>
<p>{{ $t('settings.style.shadows.filter_hint.avatar_inset') }}</p>
- <i18n
- path="settings.style.shadows.filter_hint.drop_shadow_syntax"
+ <i18n-t
+ scope="global"
+ keypath="settings.style.shadows.filter_hint.drop_shadow_syntax"
tag="p"
>
<code>drop-shadow</code>
<code>spread-radius</code>
<code>inset</code>
- </i18n>
- <i18n
- path="settings.style.shadows.filter_hint.inset_classic"
+ </i18n-t>
+ <i18n-t
+ scope="global"
+ keypath="settings.style.shadows.filter_hint.inset_classic"
tag="p"
>
<code>box-shadow</code>
- </i18n>
+ </i18n-t>
<p>{{ $t('settings.style.shadows.filter_hint.spread_zero') }}</p>
</div>
</div>
@@ -1016,21 +1021,26 @@
</tab-switcher>
</keep-alive>
- <div class="apply-container">
- <button
- class="btn button-default submit"
- :disabled="!themeValid"
- @click="setCustomTheme"
- >
- {{ $t('general.apply') }}
- </button>
- <button
- class="btn button-default"
- @click="clearAll"
- >
- {{ $t('settings.style.switcher.reset') }}
- </button>
- </div>
+ <teleport
+ v-if="isActive"
+ to="#unscrolled-content"
+ >
+ <div class="apply-container">
+ <button
+ class="btn button-default submit"
+ :disabled="!themeValid"
+ @click="setCustomTheme"
+ >
+ {{ $t('general.apply') }}
+ </button>
+ <button
+ class="btn button-default"
+ @click="clearAll"
+ >
+ {{ $t('settings.style.switcher.reset') }}
+ </button>
+ </div>
+ </teleport>
</div>
</template>
diff --git a/src/components/settings_modal/tabs/version_tab.vue b/src/components/settings_modal/tabs/version_tab.vue
index d35ff25e..0330d49f 100644
--- a/src/components/settings_modal/tabs/version_tab.vue
+++ b/src/components/settings_modal/tabs/version_tab.vue
@@ -28,4 +28,4 @@
</div>
</div>
</template>
-<script src="./version_tab.js">
+<script src="./version_tab.js" />