commit
916cefc15e
@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<div class="async-component-error">
|
||||||
|
<div>
|
||||||
|
<h4>
|
||||||
|
{{ $t('general.generic_error') }}
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
{{ $t('general.error_retry') }}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
@click="retry"
|
||||||
|
>
|
||||||
|
{{ $t('general.retry') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
retry () {
|
||||||
|
this.$emit('resetAsyncComponent')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.async-component-error {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
.btn {
|
||||||
|
margin: .5em;
|
||||||
|
padding: .5em 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<div class="panel-loading">
|
||||||
|
<span class="loading-text">
|
||||||
|
<i class="icon-spin4 animate-spin" />
|
||||||
|
{{ $t('general.loading') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import 'src/_variables.scss';
|
||||||
|
|
||||||
|
.panel-loading {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 2em;
|
||||||
|
color: $fallback--text;
|
||||||
|
color: var(--text, $fallback--text);
|
||||||
|
.loading-text i {
|
||||||
|
font-size: 3em;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
color: $fallback--text;
|
||||||
|
color: var(--text, $fallback--text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,132 +0,0 @@
|
|||||||
/* eslint-env browser */
|
|
||||||
import { filter, trim } from 'lodash'
|
|
||||||
|
|
||||||
import TabSwitcher from '../tab_switcher/tab_switcher.js'
|
|
||||||
import StyleSwitcher from '../style_switcher/style_switcher.vue'
|
|
||||||
import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue'
|
|
||||||
import { extractCommit } from '../../services/version/version.service'
|
|
||||||
import { instanceDefaultProperties, defaultState as configDefaultState } from '../../modules/config.js'
|
|
||||||
import Checkbox from '../checkbox/checkbox.vue'
|
|
||||||
|
|
||||||
const pleromaFeCommitUrl = 'https://git.pleroma.social/sjw/pleroma-fe/commit/'
|
|
||||||
const pleromaBeCommitUrl = 'https://git.pleroma.social/sjw/pleroma/commit/'
|
|
||||||
|
|
||||||
const multiChoiceProperties = [
|
|
||||||
'postContentType',
|
|
||||||
'subjectLineBehavior'
|
|
||||||
]
|
|
||||||
|
|
||||||
const settings = {
|
|
||||||
data () {
|
|
||||||
const instance = this.$store.state.instance
|
|
||||||
|
|
||||||
return {
|
|
||||||
loopSilentAvailable:
|
|
||||||
// Firefox
|
|
||||||
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
|
|
||||||
// Chrome-likes
|
|
||||||
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') ||
|
|
||||||
// Future spec, still not supported in Nightly 63 as of 08/2018
|
|
||||||
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks'),
|
|
||||||
|
|
||||||
backendVersion: instance.backendVersion,
|
|
||||||
frontendVersion: instance.frontendVersion,
|
|
||||||
muteWordsStringLocal: this.$store.getters.mergedConfig.muteWords.join('\n')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
TabSwitcher,
|
|
||||||
StyleSwitcher,
|
|
||||||
InterfaceLanguageSwitcher,
|
|
||||||
Checkbox
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
user () {
|
|
||||||
return this.$store.state.users.currentUser
|
|
||||||
},
|
|
||||||
currentSaveStateNotice () {
|
|
||||||
return this.$store.state.interface.settings.currentSaveStateNotice
|
|
||||||
},
|
|
||||||
postFormats () {
|
|
||||||
return this.$store.state.instance.postFormats || []
|
|
||||||
},
|
|
||||||
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
|
|
||||||
frontendVersionLink () {
|
|
||||||
return pleromaFeCommitUrl + this.frontendVersion
|
|
||||||
},
|
|
||||||
backendVersionLink () {
|
|
||||||
return pleromaBeCommitUrl + extractCommit(this.backendVersion)
|
|
||||||
},
|
|
||||||
// Getting localized values for instance-default properties
|
|
||||||
...instanceDefaultProperties
|
|
||||||
.filter(key => multiChoiceProperties.includes(key))
|
|
||||||
.map(key => [
|
|
||||||
key + 'DefaultValue',
|
|
||||||
function () {
|
|
||||||
return this.$store.getters.instanceDefaultConfig[key]
|
|
||||||
}
|
|
||||||
])
|
|
||||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
|
|
||||||
...instanceDefaultProperties
|
|
||||||
.filter(key => !multiChoiceProperties.includes(key))
|
|
||||||
.map(key => [
|
|
||||||
key + 'LocalizedValue',
|
|
||||||
function () {
|
|
||||||
return this.$t('settings.values.' + this.$store.getters.instanceDefaultConfig[key])
|
|
||||||
}
|
|
||||||
])
|
|
||||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
|
|
||||||
// Generating computed values for vuex properties
|
|
||||||
...Object.keys(configDefaultState)
|
|
||||||
.map(key => [key, {
|
|
||||||
get () { return this.$store.getters.mergedConfig[key] },
|
|
||||||
set (value) {
|
|
||||||
this.$store.dispatch('setOption', { name: key, value })
|
|
||||||
}
|
|
||||||
}])
|
|
||||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
|
|
||||||
// Special cases (need to transform values or perform actions first)
|
|
||||||
muteWordsString: {
|
|
||||||
get () {
|
|
||||||
return this.muteWordsStringLocal
|
|
||||||
},
|
|
||||||
set (value) {
|
|
||||||
this.muteWordsStringLocal = value
|
|
||||||
this.$store.dispatch('setOption', {
|
|
||||||
name: 'muteWords',
|
|
||||||
value: filter(value.split('\n'), (word) => trim(word).length > 0)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
useStreamingApi: {
|
|
||||||
get () { return this.$store.getters.mergedConfig.useStreamingApi },
|
|
||||||
set (value) {
|
|
||||||
const promise = value
|
|
||||||
? this.$store.dispatch('enableMastoSockets')
|
|
||||||
: this.$store.dispatch('disableMastoSockets')
|
|
||||||
|
|
||||||
promise.then(() => {
|
|
||||||
this.$store.dispatch('setOption', { name: 'useStreamingApi', value })
|
|
||||||
}).catch((e) => {
|
|
||||||
console.error('Failed starting MastoAPI Streaming socket', e)
|
|
||||||
this.$store.dispatch('disableMastoSockets')
|
|
||||||
this.$store.dispatch('setOption', { name: 'useStreamingApi', value: false })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Updating nested properties
|
|
||||||
watch: {
|
|
||||||
notificationVisibility: {
|
|
||||||
handler (value) {
|
|
||||||
this.$store.dispatch('setOption', {
|
|
||||||
name: 'notificationVisibility',
|
|
||||||
value: this.$store.getters.mergedConfig.notificationVisibility
|
|
||||||
})
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default settings
|
|
@ -1,424 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="settings panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<div class="title">
|
|
||||||
{{ $t('settings.settings') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<keep-alive>
|
|
||||||
<tab-switcher>
|
|
||||||
<div :label="$t('settings.general')">
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.interface') }}</h2>
|
|
||||||
<ul class="setting-list">
|
|
||||||
<li>
|
|
||||||
<interface-language-switcher />
|
|
||||||
</li>
|
|
||||||
<li v-if="instanceSpecificPanelPresent">
|
|
||||||
<Checkbox v-model="hideISP">
|
|
||||||
{{ $t('settings.hide_isp') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('nav.timeline') }}</h2>
|
|
||||||
<ul class="setting-list">
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="hideMutedPosts">
|
|
||||||
{{ $t('settings.hide_muted_posts') }} {{ $t('settings.instance_default', { value: hideMutedPostsLocalizedValue }) }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="collapseMessageWithSubject">
|
|
||||||
{{ $t('settings.collapse_subject') }} {{ $t('settings.instance_default', { value: collapseMessageWithSubjectLocalizedValue }) }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="streaming">
|
|
||||||
{{ $t('settings.streaming') }}
|
|
||||||
</Checkbox>
|
|
||||||
<ul
|
|
||||||
class="setting-list suboptions"
|
|
||||||
:class="[{disabled: !streaming}]"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<Checkbox
|
|
||||||
v-model="pauseOnUnfocused"
|
|
||||||
:disabled="!streaming"
|
|
||||||
>
|
|
||||||
{{ $t('settings.pause_on_unfocused') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="useStreamingApi">
|
|
||||||
{{ $t('settings.useStreamingApi') }}
|
|
||||||
<br>
|
|
||||||
<small>
|
|
||||||
{{ $t('settings.useStreamingApiWarning') }}
|
|
||||||
</small>
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="autoLoad">
|
|
||||||
{{ $t('settings.autoload') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="hoverPreview">
|
|
||||||
{{ $t('settings.reply_link_preview') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="emojiReactionsOnTimeline">
|
|
||||||
{{ $t('settings.emoji_reactions_on_timeline') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.composing') }}</h2>
|
|
||||||
<ul class="setting-list">
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="scopeCopy">
|
|
||||||
{{ $t('settings.scope_copy') }} {{ $t('settings.instance_default', { value: scopeCopyLocalizedValue }) }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="alwaysShowSubjectInput">
|
|
||||||
{{ $t('settings.subject_input_always_show') }} {{ $t('settings.instance_default', { value: alwaysShowSubjectInputLocalizedValue }) }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div>
|
|
||||||
{{ $t('settings.subject_line_behavior') }}
|
|
||||||
<label
|
|
||||||
for="subjectLineBehavior"
|
|
||||||
class="select"
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
id="subjectLineBehavior"
|
|
||||||
v-model="subjectLineBehavior"
|
|
||||||
>
|
|
||||||
<option value="email">
|
|
||||||
{{ $t('settings.subject_line_email') }}
|
|
||||||
{{ subjectLineBehaviorDefaultValue == 'email' ? $t('settings.instance_default_simple') : '' }}
|
|
||||||
</option>
|
|
||||||
<option value="masto">
|
|
||||||
{{ $t('settings.subject_line_mastodon') }}
|
|
||||||
{{ subjectLineBehaviorDefaultValue == 'mastodon' ? $t('settings.instance_default_simple') : '' }}
|
|
||||||
</option>
|
|
||||||
<option value="noop">
|
|
||||||
{{ $t('settings.subject_line_noop') }}
|
|
||||||
{{ subjectLineBehaviorDefaultValue == 'noop' ? $t('settings.instance_default_simple') : '' }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<i class="icon-down-open" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li v-if="postFormats.length > 0">
|
|
||||||
<div>
|
|
||||||
{{ $t('settings.post_status_content_type') }}
|
|
||||||
<label
|
|
||||||
for="postContentType"
|
|
||||||
class="select"
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
id="postContentType"
|
|
||||||
v-model="postContentType"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
v-for="postFormat in postFormats"
|
|
||||||
:key="postFormat"
|
|
||||||
:value="postFormat"
|
|
||||||
>
|
|
||||||
{{ $t(`post_status.content_type["${postFormat}"]`) }}
|
|
||||||
{{ postContentTypeDefaultValue === postFormat ? $t('settings.instance_default_simple') : '' }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<i class="icon-down-open" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="minimalScopesMode">
|
|
||||||
{{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.instance_default', { value: minimalScopesModeLocalizedValue }) }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="autohideFloatingPostButton">
|
|
||||||
{{ $t('settings.autohide_floating_post_button') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="padEmoji">
|
|
||||||
{{ $t('settings.pad_emoji') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.attachments') }}</h2>
|
|
||||||
<ul class="setting-list">
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="hideAttachments">
|
|
||||||
{{ $t('settings.hide_attachments_in_tl') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="hideAttachmentsInConv">
|
|
||||||
{{ $t('settings.hide_attachments_in_convo') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label for="maxThumbnails">
|
|
||||||
{{ $t('settings.max_thumbnails') }}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="maxThumbnails"
|
|
||||||
v-model.number="maxThumbnails"
|
|
||||||
class="number-input"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step="1"
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="hideNsfw">
|
|
||||||
{{ $t('settings.nsfw_clickthrough') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<ul class="setting-list suboptions">
|
|
||||||
<li>
|
|
||||||
<Checkbox
|
|
||||||
v-model="preloadImage"
|
|
||||||
:disabled="!hideNsfw"
|
|
||||||
>
|
|
||||||
{{ $t('settings.preload_images') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox
|
|
||||||
v-model="useOneClickNsfw"
|
|
||||||
:disabled="!hideNsfw"
|
|
||||||
>
|
|
||||||
{{ $t('settings.use_one_click_nsfw') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="stopGifs">
|
|
||||||
{{ $t('settings.stop_gifs') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="loopVideo">
|
|
||||||
{{ $t('settings.loop_video') }}
|
|
||||||
</Checkbox>
|
|
||||||
<ul
|
|
||||||
class="setting-list suboptions"
|
|
||||||
:class="[{disabled: !streaming}]"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<Checkbox
|
|
||||||
v-model="loopVideoSilentOnly"
|
|
||||||
:disabled="!loopVideo || !loopSilentAvailable"
|
|
||||||
>
|
|
||||||
{{ $t('settings.loop_video_silent_only') }}
|
|
||||||
</Checkbox>
|
|
||||||
<div
|
|
||||||
v-if="!loopSilentAvailable"
|
|
||||||
class="unavailable"
|
|
||||||
>
|
|
||||||
<i class="icon-globe" />! {{ $t('settings.limited_availability') }}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="playVideosInModal">
|
|
||||||
{{ $t('settings.play_videos_in_modal') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="useContainFit">
|
|
||||||
{{ $t('settings.use_contain_fit') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.notifications') }}</h2>
|
|
||||||
<ul class="setting-list">
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="webPushNotifications">
|
|
||||||
{{ $t('settings.enable_web_push_notifications') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.fun') }}</h2>
|
|
||||||
<ul class="setting-list">
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="greentext">
|
|
||||||
{{ $t('settings.greentext') }} {{ $t('settings.instance_default', { value: greentextLocalizedValue }) }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div :label="$t('settings.theme')">
|
|
||||||
<div class="setting-item">
|
|
||||||
<style-switcher />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
<Checkbox v-model="notificationVisibility.likes">
|
|
||||||
{{ $t('settings.notification_visibility_likes') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="notificationVisibility.repeats">
|
|
||||||
{{ $t('settings.notification_visibility_repeats') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="notificationVisibility.follows">
|
|
||||||
{{ $t('settings.notification_visibility_follows') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="notificationVisibility.mentions">
|
|
||||||
{{ $t('settings.notification_visibility_mentions') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="notificationVisibility.moves">
|
|
||||||
{{ $t('settings.notification_visibility_moves') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="notificationVisibility.emojiReactions">
|
|
||||||
{{ $t('settings.notification_visibility_emoji_reactions') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{ $t('settings.replies_in_timeline') }}
|
|
||||||
<label
|
|
||||||
for="replyVisibility"
|
|
||||||
class="select"
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
id="replyVisibility"
|
|
||||||
v-model="replyVisibility"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
value="all"
|
|
||||||
selected
|
|
||||||
>{{ $t('settings.reply_visibility_all') }}</option>
|
|
||||||
<option value="following">{{ $t('settings.reply_visibility_following') }}</option>
|
|
||||||
<option value="self">{{ $t('settings.reply_visibility_self') }}</option>
|
|
||||||
</select>
|
|
||||||
<i class="icon-down-open" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Checkbox v-model="hidePostStats">
|
|
||||||
{{ $t('settings.hide_post_stats') }} {{ $t('settings.instance_default', { value: hidePostStatsLocalizedValue }) }}
|
|
||||||
</Checkbox>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Checkbox v-model="hideUserStats">
|
|
||||||
{{ $t('settings.hide_user_stats') }} {{ $t('settings.instance_default', { value: hideUserStatsLocalizedValue }) }}
|
|
||||||
</Checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
|
||||||
<div>
|
|
||||||
<p>{{ $t('settings.filtering_explanation') }}</p>
|
|
||||||
<textarea
|
|
||||||
id="muteWords"
|
|
||||||
v-model="muteWordsString"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Checkbox v-model="hideFilteredStatuses">
|
|
||||||
{{ $t('settings.hide_filtered_statuses') }} {{ $t('settings.instance_default', { value: hideFilteredStatusesLocalizedValue }) }}
|
|
||||||
</Checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div :label="$t('settings.version.title')">
|
|
||||||
<div class="setting-item">
|
|
||||||
<ul class="setting-list">
|
|
||||||
<li>
|
|
||||||
<p>{{ $t('settings.version.backend_version') }}</p>
|
|
||||||
<ul class="option-list">
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
:href="backendVersionLink"
|
|
||||||
target="_blank"
|
|
||||||
>{{ backendVersion }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>{{ $t('settings.version.frontend_version') }}</p>
|
|
||||||
<ul class="option-list">
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
:href="frontendVersionLink"
|
|
||||||
target="_blank"
|
|
||||||
>{{ frontendVersion }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</tab-switcher>
|
|
||||||
</keep-alive>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./settings.js">
|
|
||||||
</script>
|
|
@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
instanceDefaultProperties,
|
||||||
|
multiChoiceProperties,
|
||||||
|
defaultState as configDefaultState
|
||||||
|
} from 'src/modules/config.js'
|
||||||
|
|
||||||
|
const SharedComputedObject = () => ({
|
||||||
|
user () {
|
||||||
|
return this.$store.state.users.currentUser
|
||||||
|
},
|
||||||
|
// Getting localized values for instance-default properties
|
||||||
|
...instanceDefaultProperties
|
||||||
|
.filter(key => multiChoiceProperties.includes(key))
|
||||||
|
.map(key => [
|
||||||
|
key + 'DefaultValue',
|
||||||
|
function () {
|
||||||
|
return this.$store.getters.instanceDefaultConfig[key]
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
|
||||||
|
...instanceDefaultProperties
|
||||||
|
.filter(key => !multiChoiceProperties.includes(key))
|
||||||
|
.map(key => [
|
||||||
|
key + 'LocalizedValue',
|
||||||
|
function () {
|
||||||
|
return this.$t('settings.values.' + this.$store.getters.instanceDefaultConfig[key])
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
|
||||||
|
// Generating computed values for vuex properties
|
||||||
|
...Object.keys(configDefaultState)
|
||||||
|
.map(key => [key, {
|
||||||
|
get () { return this.$store.getters.mergedConfig[key] },
|
||||||
|
set (value) {
|
||||||
|
this.$store.dispatch('setOption', { 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 },
|
||||||
|
set (value) {
|
||||||
|
const promise = value
|
||||||
|
? this.$store.dispatch('enableMastoSockets')
|
||||||
|
: this.$store.dispatch('disableMastoSockets')
|
||||||
|
|
||||||
|
promise.then(() => {
|
||||||
|
this.$store.dispatch('setOption', { name: 'useStreamingApi', value })
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error('Failed starting MastoAPI Streaming socket', e)
|
||||||
|
this.$store.dispatch('disableMastoSockets')
|
||||||
|
this.$store.dispatch('setOption', { name: 'useStreamingApi', value: false })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default SharedComputedObject
|
@ -0,0 +1,42 @@
|
|||||||
|
import Modal from 'src/components/modal/modal.vue'
|
||||||
|
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'
|
||||||
|
|
||||||
|
const SettingsModal = {
|
||||||
|
components: {
|
||||||
|
Modal,
|
||||||
|
SettingsModalContent: getResettableAsyncComponent(
|
||||||
|
() => import('./settings_modal_content.vue'),
|
||||||
|
{
|
||||||
|
loading: PanelLoading,
|
||||||
|
error: AsyncComponentError,
|
||||||
|
delay: 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
closeModal () {
|
||||||
|
this.$store.dispatch('closeSettingsModal')
|
||||||
|
},
|
||||||
|
peekModal () {
|
||||||
|
this.$store.dispatch('togglePeekSettingsModal')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentSaveStateNotice () {
|
||||||
|
return this.$store.state.interface.settings.currentSaveStateNotice
|
||||||
|
},
|
||||||
|
modalActivated () {
|
||||||
|
return this.$store.state.interface.settingsModalState !== 'hidden'
|
||||||
|
},
|
||||||
|
modalOpenedOnce () {
|
||||||
|
return this.$store.state.interface.settingsModalLoaded
|
||||||
|
},
|
||||||
|
modalPeeked () {
|
||||||
|
return this.$store.state.interface.settingsModalState === 'minimized'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingsModal
|
@ -0,0 +1,44 @@
|
|||||||
|
@import 'src/_variables.scss';
|
||||||
|
.settings-modal {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.peek {
|
||||||
|
.settings-modal-panel {
|
||||||
|
/* Explanation:
|
||||||
|
* Modal is positioned vertically centered.
|
||||||
|
* 100vh - 100% = Distance between modal's top+bottom boundaries and screen
|
||||||
|
* (100vh - 100%) / 2 = Distance between bottom (or top) boundary and screen
|
||||||
|
* + 100% - we move modal completely off-screen, it's top boundary touches
|
||||||
|
* bottom of the screen
|
||||||
|
* - 50px - leaving tiny amount of space so that titlebar + tiny amount of modal is visible
|
||||||
|
*/
|
||||||
|
transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal-panel {
|
||||||
|
overflow: hidden;
|
||||||
|
transition: transform;
|
||||||
|
transition-timing-function: ease-in-out;
|
||||||
|
transition-duration: 300ms;
|
||||||
|
width: 1000px;
|
||||||
|
max-width: 90vw;
|
||||||
|
height: 90vh;
|
||||||
|
|
||||||
|
@media all and (max-width: 800px) {
|
||||||
|
max-width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-body {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
min-height: 28px;
|
||||||
|
min-width: 10em;
|
||||||
|
padding: 0 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<Modal
|
||||||
|
:is-open="modalActivated"
|
||||||
|
class="settings-modal"
|
||||||
|
:class="{ peek: modalPeeked }"
|
||||||
|
:no-background="modalPeeked"
|
||||||
|
>
|
||||||
|
<div class="settings-modal-panel panel">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<span class="title">
|
||||||
|
{{ $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>
|
||||||
|
</transition>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
@click="peekModal"
|
||||||
|
>
|
||||||
|
{{ $t('general.peek') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
@click="closeModal"
|
||||||
|
>
|
||||||
|
{{ $t('general.close') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<SettingsModalContent v-if="modalOpenedOnce" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./settings_modal.js"></script>
|
||||||
|
|
||||||
|
<style src="./settings_modal.scss" lang="scss"></style>
|
@ -0,0 +1,34 @@
|
|||||||
|
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
|
||||||
|
|
||||||
|
import DataImportExportTab from './tabs/data_import_export_tab.vue'
|
||||||
|
import MutesAndBlocksTab from './tabs/mutes_and_blocks_tab.vue'
|
||||||
|
import NotificationsTab from './tabs/notifications_tab.vue'
|
||||||
|
import FilteringTab from './tabs/filtering_tab.vue'
|
||||||
|
import SecurityTab from './tabs/security_tab/security_tab.vue'
|
||||||
|
import ProfileTab from './tabs/profile_tab.vue'
|
||||||
|
import GeneralTab from './tabs/general_tab.vue'
|
||||||
|
import VersionTab from './tabs/version_tab.vue'
|
||||||
|
import ThemeTab from './tabs/theme_tab/theme_tab.vue'
|
||||||
|
|
||||||
|
const SettingsModalContent = {
|
||||||
|
components: {
|
||||||
|
TabSwitcher,
|
||||||
|
|
||||||
|
DataImportExportTab,
|
||||||
|
MutesAndBlocksTab,
|
||||||
|
NotificationsTab,
|
||||||
|
FilteringTab,
|
||||||
|
SecurityTab,
|
||||||
|
ProfileTab,
|
||||||
|
GeneralTab,
|
||||||
|
VersionTab,
|
||||||
|
ThemeTab
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isLoggedIn () {
|
||||||
|
return !!this.$store.state.users.currentUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingsModalContent
|
@ -0,0 +1,43 @@
|
|||||||
|
@import 'src/_variables.scss';
|
||||||
|
.settings_tab-switcher {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.setting-item {
|
||||||
|
border-bottom: 2px solid var(--fg, $fallback--fg);
|
||||||
|
margin: 1em 1em 1.4em;
|
||||||
|
padding-bottom: 1.4em;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
margin-bottom: .5em;
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
min-width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unavailable,
|
||||||
|
.unavailable i {
|
||||||
|
color: var(--cRed, $fallback--cRed);
|
||||||
|
color: $fallback--cRed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number-input {
|
||||||
|
max-width: 6em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<tab-switcher
|
||||||
|
ref="tabSwitcher"
|
||||||
|
class="settings_tab-switcher"
|
||||||
|
:side-tab-bar="true"
|
||||||
|
:scrollable-tabs="true"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:label="$t('settings.general')"
|
||||||
|
icon="wrench"
|
||||||
|
>
|
||||||
|
<GeneralTab />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isLoggedIn"
|
||||||
|
:label="$t('settings.profile_tab')"
|
||||||
|
icon="user"
|
||||||
|
>
|
||||||
|
<ProfileTab />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isLoggedIn"
|
||||||
|
:label="$t('settings.security_tab')"
|
||||||
|
icon="lock"
|
||||||
|
>
|
||||||
|
<SecurityTab />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:label="$t('settings.filtering')"
|
||||||
|
icon="filter"
|
||||||
|
>
|
||||||
|
<FilteringTab />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:label="$t('settings.theme')"
|
||||||
|
icon="brush"
|
||||||
|
>
|
||||||
|
<ThemeTab />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isLoggedIn"
|
||||||
|
:label="$t('settings.notifications')"
|
||||||
|
icon="bell-ringing-o"
|
||||||
|
>
|
||||||
|
<NotificationsTab />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isLoggedIn"
|
||||||
|
:label="$t('settings.data_import_export_tab')"
|
||||||
|
icon="download"
|
||||||
|
>
|
||||||
|
<DataImportExportTab />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isLoggedIn"
|
||||||
|
:label="$t('settings.mutes_and_blocks')"
|
||||||
|
:fullHeight="true"
|
||||||
|
icon="eye-off"
|
||||||
|
>
|
||||||
|
<MutesAndBlocksTab />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:label="$t('settings.version.title')"
|
||||||
|
icon="info-circled"
|
||||||
|
>
|
||||||
|
<VersionTab />
|
||||||
|
</div>
|
||||||
|
</tab-switcher>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./settings_modal_content.js"></script>
|
||||||
|
|
||||||
|
<style src="./settings_modal_content.scss" lang="scss"></style>
|
@ -0,0 +1,65 @@
|
|||||||
|
import Importer from 'src/components/importer/importer.vue'
|
||||||
|
import Exporter from 'src/components/exporter/exporter.vue'
|
||||||
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
|
|
||||||
|
const DataImportExportTab = {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
activeTab: 'profile',
|
||||||
|
newDomainToMute: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.$store.dispatch('fetchTokens')
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Importer,
|
||||||
|
Exporter,
|
||||||
|
Checkbox
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
user () {
|
||||||
|
return this.$store.state.users.currentUser
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getFollowsContent () {
|
||||||
|
return this.$store.state.api.backendInteractor.exportFriends({ id: this.$store.state.users.currentUser.id })
|
||||||
|
.then(this.generateExportableUsersContent)
|
||||||
|
},
|
||||||
|
getBlocksContent () {
|
||||||
|
return this.$store.state.api.backendInteractor.fetchBlocks()
|
||||||
|
.then(this.generateExportableUsersContent)
|
||||||
|
},
|
||||||
|
importFollows (file) {
|
||||||
|
return this.$store.state.api.backendInteractor.importFollows({ file })
|
||||||
|
.then((status) => {
|
||||||
|
if (!status) {
|
||||||
|
throw new Error('failed')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
importBlocks (file) {
|
||||||
|
return this.$store.state.api.backendInteractor.importBlocks({ file })
|
||||||
|
.then((status) => {
|
||||||
|
if (!status) {
|
||||||
|
throw new Error('failed')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
generateExportableUsersContent (users) {
|
||||||
|
// Get addresses
|
||||||
|
return users.map((user) => {
|
||||||
|
// check is it's a local user
|
||||||
|
if (user && user.is_local) {
|
||||||
|
// append the instance address
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
return user.screen_name + '@' + location.hostname
|
||||||
|
}
|
||||||
|
return user.screen_name
|
||||||
|
}).join('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DataImportExportTab
|
@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:label="$t('settings.data_import_export_tab')"
|
||||||
|
>
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.follow_import') }}</h2>
|
||||||
|
<p>{{ $t('settings.import_followers_from_a_csv_file') }}</p>
|
||||||
|
<Importer
|
||||||
|
:submit-handler="importFollows"
|
||||||
|
:success-message="$t('settings.follows_imported')"
|
||||||
|
:error-message="$t('settings.follow_import_error')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.follow_export') }}</h2>
|
||||||
|
<Exporter
|
||||||
|
:get-content="getFollowsContent"
|
||||||
|
filename="friends.csv"
|
||||||
|
:export-button-label="$t('settings.follow_export_button')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.block_import') }}</h2>
|
||||||
|
<p>{{ $t('settings.import_blocks_from_a_csv_file') }}</p>
|
||||||
|
<Importer
|
||||||
|
:submit-handler="importBlocks"
|
||||||
|
:success-message="$t('settings.blocks_imported')"
|
||||||
|
:error-message="$t('settings.block_import_error')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.block_export') }}</h2>
|
||||||
|
<Exporter
|
||||||
|
:get-content="getBlocksContent"
|
||||||
|
filename="blocks.csv"
|
||||||
|
:export-button-label="$t('settings.block_export_button')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./data_import_export_tab.js"></script>
|
||||||
|
<!-- <style lang="scss" src="./profile.scss"></style> -->
|
@ -0,0 +1,44 @@
|
|||||||
|
import { filter, trim } from 'lodash'
|
||||||
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
|
|
||||||
|
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||||
|
|
||||||
|
const FilteringTab = {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
muteWordsStringLocal: this.$store.getters.mergedConfig.muteWords.join('\n')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Checkbox
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...SharedComputedObject(),
|
||||||
|
muteWordsString: {
|
||||||
|
get () {
|
||||||
|
return this.muteWordsStringLocal
|
||||||
|
},
|
||||||
|
set (value) {
|
||||||
|
this.muteWordsStringLocal = value
|
||||||
|
this.$store.dispatch('setOption', {
|
||||||
|
name: 'muteWords',
|
||||||
|
value: filter(value.split('\n'), (word) => trim(word).length > 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Updating nested properties
|
||||||
|
watch: {
|
||||||
|
notificationVisibility: {
|
||||||
|
handler (value) {
|
||||||
|
this.$store.dispatch('setOption', {
|
||||||
|
name: 'notificationVisibility',
|
||||||
|
value: this.$store.getters.mergedConfig.notificationVisibility
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilteringTab
|
@ -0,0 +1,86 @@
|
|||||||
|
<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>
|
||||||
|
<Checkbox v-model="notificationVisibility.likes">
|
||||||
|
{{ $t('settings.notification_visibility_likes') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="notificationVisibility.repeats">
|
||||||
|
{{ $t('settings.notification_visibility_repeats') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="notificationVisibility.follows">
|
||||||
|
{{ $t('settings.notification_visibility_follows') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="notificationVisibility.mentions">
|
||||||
|
{{ $t('settings.notification_visibility_mentions') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="notificationVisibility.moves">
|
||||||
|
{{ $t('settings.notification_visibility_moves') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="notificationVisibility.emojiReactions">
|
||||||
|
{{ $t('settings.notification_visibility_emoji_reactions') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ $t('settings.replies_in_timeline') }}
|
||||||
|
<label
|
||||||
|
for="replyVisibility"
|
||||||
|
class="select"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
id="replyVisibility"
|
||||||
|
v-model="replyVisibility"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
value="all"
|
||||||
|
selected
|
||||||
|
>{{ $t('settings.reply_visibility_all') }}</option>
|
||||||
|
<option value="following">{{ $t('settings.reply_visibility_following') }}</option>
|
||||||
|
<option value="self">{{ $t('settings.reply_visibility_self') }}</option>
|
||||||
|
</select>
|
||||||
|
<i class="icon-down-open" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Checkbox v-model="hidePostStats">
|
||||||
|
{{ $t('settings.hide_post_stats') }} {{ $t('settings.instance_default', { value: hidePostStatsLocalizedValue }) }}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Checkbox v-model="hideUserStats">
|
||||||
|
{{ $t('settings.hide_user_stats') }} {{ $t('settings.instance_default', { value: hideUserStatsLocalizedValue }) }}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div>
|
||||||
|
<p>{{ $t('settings.filtering_explanation') }}</p>
|
||||||
|
<textarea
|
||||||
|
id="muteWords"
|
||||||
|
v-model="muteWordsString"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Checkbox v-model="hideFilteredStatuses">
|
||||||
|
{{ $t('settings.hide_filtered_statuses') }} {{ $t('settings.instance_default', { value: hideFilteredStatusesLocalizedValue }) }}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script src="./filtering_tab.js"></script>
|
@ -0,0 +1,31 @@
|
|||||||
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
|
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
|
||||||
|
|
||||||
|
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||||
|
|
||||||
|
const GeneralTab = {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
loopSilentAvailable:
|
||||||
|
// Firefox
|
||||||
|
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
|
||||||
|
// Chrome-likes
|
||||||
|
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') ||
|
||||||
|
// Future spec, still not supported in Nightly 63 as of 08/2018
|
||||||
|
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Checkbox,
|
||||||
|
InterfaceLanguageSwitcher
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
postFormats () {
|
||||||
|
return this.$store.state.instance.postFormats || []
|
||||||
|
},
|
||||||
|
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
|
||||||
|
...SharedComputedObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GeneralTab
|
@ -0,0 +1,272 @@
|
|||||||
|
<template>
|
||||||
|
<div :label="$t('settings.general')">
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.interface') }}</h2>
|
||||||
|
<ul class="setting-list">
|
||||||
|
<li>
|
||||||
|
<interface-language-switcher />
|
||||||
|
</li>
|
||||||
|
<li v-if="instanceSpecificPanelPresent">
|
||||||
|
<Checkbox v-model="hideISP">
|
||||||
|
{{ $t('settings.hide_isp') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('nav.timeline') }}</h2>
|
||||||
|
<ul class="setting-list">
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="hideMutedPosts">
|
||||||
|
{{ $t('settings.hide_muted_posts') }} {{ $t('settings.instance_default', { value: hideMutedPostsLocalizedValue }) }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="collapseMessageWithSubject">
|
||||||
|
{{ $t('settings.collapse_subject') }} {{ $t('settings.instance_default', { value: collapseMessageWithSubjectLocalizedValue }) }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="streaming">
|
||||||
|
{{ $t('settings.streaming') }}
|
||||||
|
</Checkbox>
|
||||||
|
<ul
|
||||||
|
class="setting-list suboptions"
|
||||||
|
:class="[{disabled: !streaming}]"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<Checkbox
|
||||||
|
v-model="pauseOnUnfocused"
|
||||||
|
:disabled="!streaming"
|
||||||
|
>
|
||||||
|
{{ $t('settings.pause_on_unfocused') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="useStreamingApi">
|
||||||
|
{{ $t('settings.useStreamingApi') }}
|
||||||
|
<br>
|
||||||
|
<small>
|
||||||
|
{{ $t('settings.useStreamingApiWarning') }}
|
||||||
|
</small>
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="autoLoad">
|
||||||
|
{{ $t('settings.autoload') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="hoverPreview">
|
||||||
|
{{ $t('settings.reply_link_preview') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="emojiReactionsOnTimeline">
|
||||||
|
{{ $t('settings.emoji_reactions_on_timeline') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.composing') }}</h2>
|
||||||
|
<ul class="setting-list">
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="scopeCopy">
|
||||||
|
{{ $t('settings.scope_copy') }} {{ $t('settings.instance_default', { value: scopeCopyLocalizedValue }) }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="alwaysShowSubjectInput">
|
||||||
|
{{ $t('settings.subject_input_always_show') }} {{ $t('settings.instance_default', { value: alwaysShowSubjectInputLocalizedValue }) }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
{{ $t('settings.subject_line_behavior') }}
|
||||||
|
<label
|
||||||
|
for="subjectLineBehavior"
|
||||||
|
class="select"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
id="subjectLineBehavior"
|
||||||
|
v-model="subjectLineBehavior"
|
||||||
|
>
|
||||||
|
<option value="email">
|
||||||
|
{{ $t('settings.subject_line_email') }}
|
||||||
|
{{ subjectLineBehaviorDefaultValue == 'email' ? $t('settings.instance_default_simple') : '' }}
|
||||||
|
</option>
|
||||||
|
<option value="masto">
|
||||||
|
{{ $t('settings.subject_line_mastodon') }}
|
||||||
|
{{ subjectLineBehaviorDefaultValue == 'mastodon' ? $t('settings.instance_default_simple') : '' }}
|
||||||
|
</option>
|
||||||
|
<option value="noop">
|
||||||
|
{{ $t('settings.subject_line_noop') }}
|
||||||
|
{{ subjectLineBehaviorDefaultValue == 'noop' ? $t('settings.instance_default_simple') : '' }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<i class="icon-down-open" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li v-if="postFormats.length > 0">
|
||||||
|
<div>
|
||||||
|
{{ $t('settings.post_status_content_type') }}
|
||||||
|
<label
|
||||||
|
for="postContentType"
|
||||||
|
class="select"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
id="postContentType"
|
||||||
|
v-model="postContentType"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="postFormat in postFormats"
|
||||||
|
:key="postFormat"
|
||||||
|
:value="postFormat"
|
||||||
|
>
|
||||||
|
{{ $t(`post_status.content_type["${postFormat}"]`) }}
|
||||||
|
{{ postContentTypeDefaultValue === postFormat ? $t('settings.instance_default_simple') : '' }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<i class="icon-down-open" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="minimalScopesMode">
|
||||||
|
{{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.instance_default', { value: minimalScopesModeLocalizedValue }) }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="autohideFloatingPostButton">
|
||||||
|
{{ $t('settings.autohide_floating_post_button') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="padEmoji">
|
||||||
|
{{ $t('settings.pad_emoji') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.attachments') }}</h2>
|
||||||
|
<ul class="setting-list">
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="hideAttachments">
|
||||||
|
{{ $t('settings.hide_attachments_in_tl') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="hideAttachmentsInConv">
|
||||||
|
{{ $t('settings.hide_attachments_in_convo') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="maxThumbnails">
|
||||||
|
{{ $t('settings.max_thumbnails') }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="maxThumbnails"
|
||||||
|
v-model.number="maxThumbnails"
|
||||||
|
class="number-input"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="hideNsfw">
|
||||||
|
{{ $t('settings.nsfw_clickthrough') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<ul class="setting-list suboptions">
|
||||||
|
<li>
|
||||||
|
<Checkbox
|
||||||
|
v-model="preloadImage"
|
||||||
|
:disabled="!hideNsfw"
|
||||||
|
>
|
||||||
|
{{ $t('settings.preload_images') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox
|
||||||
|
v-model="useOneClickNsfw"
|
||||||
|
:disabled="!hideNsfw"
|
||||||
|
>
|
||||||
|
{{ $t('settings.use_one_click_nsfw') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="stopGifs">
|
||||||
|
{{ $t('settings.stop_gifs') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="loopVideo">
|
||||||
|
{{ $t('settings.loop_video') }}
|
||||||
|
</Checkbox>
|
||||||
|
<ul
|
||||||
|
class="setting-list suboptions"
|
||||||
|
:class="[{disabled: !streaming}]"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<Checkbox
|
||||||
|
v-model="loopVideoSilentOnly"
|
||||||
|
:disabled="!loopVideo || !loopSilentAvailable"
|
||||||
|
>
|
||||||
|
{{ $t('settings.loop_video_silent_only') }}
|
||||||
|
</Checkbox>
|
||||||
|
<div
|
||||||
|
v-if="!loopSilentAvailable"
|
||||||
|
class="unavailable"
|
||||||
|
>
|
||||||
|
<i class="icon-globe" />! {{ $t('settings.limited_availability') }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="playVideosInModal">
|
||||||
|
{{ $t('settings.play_videos_in_modal') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="useContainFit">
|
||||||
|
{{ $t('settings.use_contain_fit') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.notifications') }}</h2>
|
||||||
|
<ul class="setting-list">
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="webPushNotifications">
|
||||||
|
{{ $t('settings.enable_web_push_notifications') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.fun') }}</h2>
|
||||||
|
<ul class="setting-list">
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="greentext">
|
||||||
|
{{ $t('settings.greentext') }} {{ $t('settings.instance_default', { value: greentextLocalizedValue }) }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./general_tab.js"></script>
|
@ -0,0 +1,124 @@
|
|||||||
|
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 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'
|
||||||
|
import SelectableList from 'src/components/selectable_list/selectable_list.vue'
|
||||||
|
import ProgressButton from 'src/components/progress_button/progress_button.vue'
|
||||||
|
import withSubscription from 'src/components/../hocs/with_subscription/with_subscription'
|
||||||
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
|
|
||||||
|
const BlockList = withSubscription({
|
||||||
|
fetch: (props, $store) => $store.dispatch('fetchBlocks'),
|
||||||
|
select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
|
||||||
|
childPropName: 'items'
|
||||||
|
})(SelectableList)
|
||||||
|
|
||||||
|
const MuteList = withSubscription({
|
||||||
|
fetch: (props, $store) => $store.dispatch('fetchMutes'),
|
||||||
|
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
|
||||||
|
childPropName: 'items'
|
||||||
|
})(SelectableList)
|
||||||
|
|
||||||
|
const DomainMuteList = withSubscription({
|
||||||
|
fetch: (props, $store) => $store.dispatch('fetchDomainMutes'),
|
||||||
|
select: (props, $store) => get($store.state.users.currentUser, 'domainMutes', []),
|
||||||
|
childPropName: 'items'
|
||||||
|
})(SelectableList)
|
||||||
|
|
||||||
|
const MutesAndBlocks = {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
activeTab: 'profile',
|
||||||
|
newDomainToMute: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.$store.dispatch('fetchTokens')
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
TabSwitcher,
|
||||||
|
BlockList,
|
||||||
|
MuteList,
|
||||||
|
DomainMuteList,
|
||||||
|
BlockCard,
|
||||||
|
MuteCard,
|
||||||
|
DomainMuteCard,
|
||||||
|
ProgressButton,
|
||||||
|
Autosuggest,
|
||||||
|
Checkbox
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
importFollows (file) {
|
||||||
|
return this.$store.state.api.backendInteractor.importFollows({ file })
|
||||||
|
.then((status) => {
|
||||||
|
if (!status) {
|
||||||
|
throw new Error('failed')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
importBlocks (file) {
|
||||||
|
return this.$store.state.api.backendInteractor.importBlocks({ file })
|
||||||
|
.then((status) => {
|
||||||
|
if (!status) {
|
||||||
|
throw new Error('failed')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
generateExportableUsersContent (users) {
|
||||||
|
// Get addresses
|
||||||
|
return users.map((user) => {
|
||||||
|
// check is it's a local user
|
||||||
|
if (user && user.is_local) {
|
||||||
|
// append the instance address
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
return user.screen_name + '@' + location.hostname
|
||||||
|
}
|
||||||
|
return user.screen_name
|
||||||
|
}).join('\n')
|
||||||
|
},
|
||||||
|
activateTab (tabName) {
|
||||||
|
this.activeTab = tabName
|
||||||
|
},
|
||||||
|
filterUnblockedUsers (userIds) {
|
||||||
|
return reject(userIds, (userId) => {
|
||||||
|
const relationship = this.$store.getters.relationship(this.userId)
|
||||||
|
return relationship.blocking || userId === this.$store.state.users.currentUser.id
|
||||||
|
})
|
||||||
|
},
|
||||||
|
filterUnMutedUsers (userIds) {
|
||||||
|
return reject(userIds, (userId) => {
|
||||||
|
const relationship = this.$store.getters.relationship(this.userId)
|
||||||
|
return relationship.muting || userId === this.$store.state.users.currentUser.id
|
||||||
|
})
|
||||||
|
},
|
||||||
|
queryUserIds (query) {
|
||||||
|
return this.$store.dispatch('searchUsers', { query })
|
||||||
|
.then((users) => map(users, 'id'))
|
||||||
|
},
|
||||||
|
blockUsers (ids) {
|
||||||
|
return this.$store.dispatch('blockUsers', ids)
|
||||||
|
},
|
||||||
|
unblockUsers (ids) {
|
||||||
|
return this.$store.dispatch('unblockUsers', ids)
|
||||||
|
},
|
||||||
|
muteUsers (ids) {
|
||||||
|
return this.$store.dispatch('muteUsers', ids)
|
||||||
|
},
|
||||||
|
unmuteUsers (ids) {
|
||||||
|
return this.$store.dispatch('unmuteUsers', ids)
|
||||||
|
},
|
||||||
|
unmuteDomains (domains) {
|
||||||
|
return this.$store.dispatch('unmuteDomains', domains)
|
||||||
|
},
|
||||||
|
muteDomain () {
|
||||||
|
return this.$store.dispatch('muteDomain', this.newDomainToMute)
|
||||||
|
.then(() => { this.newDomainToMute = '' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MutesAndBlocks
|
@ -0,0 +1,29 @@
|
|||||||
|
.mutes-and-blocks-tab {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.usersearch-wrapper {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-actions {
|
||||||
|
text-align: right;
|
||||||
|
padding: 0 1em;
|
||||||
|
min-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-action-button {
|
||||||
|
width: 10em
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-mute-form {
|
||||||
|
padding: 1em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-mute-button {
|
||||||
|
align-self: flex-end;
|
||||||
|
margin-top: 1em;
|
||||||
|
width: 10em
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<tab-switcher
|
||||||
|
:scrollable-tabs="true"
|
||||||
|
class="mutes-and-blocks-tab"
|
||||||
|
>
|
||||||
|
<div :label="$t('settings.blocks_tab')">
|
||||||
|
<div class="usersearch-wrapper">
|
||||||
|
<Autosuggest
|
||||||
|
:filter="filterUnblockedUsers"
|
||||||
|
:query="queryUserIds"
|
||||||
|
:placeholder="$t('settings.search_user_to_block')"
|
||||||
|
>
|
||||||
|
<BlockCard
|
||||||
|
slot-scope="row"
|
||||||
|
:user-id="row.item"
|
||||||
|
/>
|
||||||
|
</Autosuggest>
|
||||||
|
</div>
|
||||||
|
<BlockList
|
||||||
|
:refresh="true"
|
||||||
|
:get-key="i => i"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
slot="header"
|
||||||
|
slot-scope="{selected}"
|
||||||
|
>
|
||||||
|
<div class="bulk-actions">
|
||||||
|
<ProgressButton
|
||||||
|
v-if="selected.length > 0"
|
||||||
|
class="btn btn-default bulk-action-button"
|
||||||
|
:click="() => blockUsers(selected)"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.block') }}
|
||||||
|
<template slot="progress">
|
||||||
|
{{ $t('user_card.block_progress') }}
|
||||||
|
</template>
|
||||||
|
</ProgressButton>
|
||||||
|
<ProgressButton
|
||||||
|
v-if="selected.length > 0"
|
||||||
|
class="btn btn-default"
|
||||||
|
:click="() => unblockUsers(selected)"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.unblock') }}
|
||||||
|
<template slot="progress">
|
||||||
|
{{ $t('user_card.unblock_progress') }}
|
||||||
|
</template>
|
||||||
|
</ProgressButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
slot="item"
|
||||||
|
slot-scope="{item}"
|
||||||
|
>
|
||||||
|
<BlockCard :user-id="item" />
|
||||||
|
</template>
|
||||||
|
<template slot="empty">
|
||||||
|
{{ $t('settings.no_blocks') }}
|
||||||
|
</template>
|
||||||
|
</BlockList>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :label="$t('settings.mutes_tab')">
|
||||||
|
<tab-switcher>
|
||||||
|
<div label="Users">
|
||||||
|
<div class="usersearch-wrapper">
|
||||||
|
<Autosuggest
|
||||||
|
:filter="filterUnMutedUsers"
|
||||||
|
:query="queryUserIds"
|
||||||
|
:placeholder="$t('settings.search_user_to_mute')"
|
||||||
|
>
|
||||||
|
<MuteCard
|
||||||
|
slot-scope="row"
|
||||||
|
:user-id="row.item"
|
||||||
|
/>
|
||||||
|
</Autosuggest>
|
||||||
|
</div>
|
||||||
|
<MuteList
|
||||||
|
:refresh="true"
|
||||||
|
:get-key="i => i"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
slot="header"
|
||||||
|
slot-scope="{selected}"
|
||||||
|
>
|
||||||
|
<div class="bulk-actions">
|
||||||
|
<ProgressButton
|
||||||
|
v-if="selected.length > 0"
|
||||||
|
class="btn btn-default"
|
||||||
|
:click="() => muteUsers(selected)"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.mute') }}
|
||||||
|
<template slot="progress">
|
||||||
|
{{ $t('user_card.mute_progress') }}
|
||||||
|
</template>
|
||||||
|
</ProgressButton>
|
||||||
|
<ProgressButton
|
||||||
|
v-if="selected.length > 0"
|
||||||
|
class="btn btn-default"
|
||||||
|
:click="() => unmuteUsers(selected)"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.unmute') }}
|
||||||
|
<template slot="progress">
|
||||||
|
{{ $t('user_card.unmute_progress') }}
|
||||||
|
</template>
|
||||||
|
</ProgressButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
slot="item"
|
||||||
|
slot-scope="{item}"
|
||||||
|
>
|
||||||
|
<MuteCard :user-id="item" />
|
||||||
|
</template>
|
||||||
|
<template slot="empty">
|
||||||
|
{{ $t('settings.no_mutes') }}
|
||||||
|
</template>
|
||||||
|
</MuteList>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :label="$t('settings.domain_mutes')">
|
||||||
|
<div class="domain-mute-form">
|
||||||
|
<input
|
||||||
|
v-model="newDomainToMute"
|
||||||
|
:placeholder="$t('settings.type_domains_to_mute')"
|
||||||
|
type="text"
|
||||||
|
@keyup.enter="muteDomain"
|
||||||
|
>
|
||||||
|
<ProgressButton
|
||||||
|
class="btn btn-default domain-mute-button"
|
||||||
|
:click="muteDomain"
|
||||||
|
>
|
||||||
|
{{ $t('domain_mute_card.mute') }}
|
||||||
|
<template slot="progress">
|
||||||
|
{{ $t('domain_mute_card.mute_progress') }}
|
||||||
|
</template>
|
||||||
|
</ProgressButton>
|
||||||
|
</div>
|
||||||
|
<DomainMuteList
|
||||||
|
:refresh="true"
|
||||||
|
:get-key="i => i"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
slot="header"
|
||||||
|
slot-scope="{selected}"
|
||||||
|
>
|
||||||
|
<div class="bulk-actions">
|
||||||
|
<ProgressButton
|
||||||
|
v-if="selected.length > 0"
|
||||||
|
class="btn btn-default"
|
||||||
|
:click="() => unmuteDomains(selected)"
|
||||||
|
>
|
||||||
|
{{ $t('domain_mute_card.unmute') }}
|
||||||
|
<template slot="progress">
|
||||||
|
{{ $t('domain_mute_card.unmute_progress') }}
|
||||||
|
</template>
|
||||||
|
</ProgressButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
slot="item"
|
||||||
|
slot-scope="{item}"
|
||||||
|
>
|
||||||
|
<DomainMuteCard :domain="item" />
|
||||||
|
</template>
|
||||||
|
<template slot="empty">
|
||||||
|
{{ $t('settings.no_mutes') }}
|
||||||
|
</template>
|
||||||
|
</DomainMuteList>
|
||||||
|
</div>
|
||||||
|
</tab-switcher>
|
||||||
|
</div>
|
||||||
|
</tab-switcher>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./mutes_and_blocks_tab.js"></script>
|
||||||
|
<style lang="scss" src="./mutes_and_blocks_tab.scss"></style>
|
@ -0,0 +1,27 @@
|
|||||||
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
|
|
||||||
|
const NotificationsTab = {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
activeTab: 'profile',
|
||||||
|
notificationSettings: this.$store.state.users.currentUser.notification_settings,
|
||||||
|
newDomainToMute: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Checkbox
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
user () {
|
||||||
|
return this.$store.state.users.currentUser
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateNotificationSettings () {
|
||||||
|
this.$store.state.api.backendInteractor
|
||||||
|
.updateNotificationSettings({ settings: this.notificationSettings })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotificationsTab
|
@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<div :label="$t('settings.notifications')">
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.notification_setting_filters') }}</h2>
|
||||||
|
<div class="select-multiple">
|
||||||
|
<span class="label">{{ $t('settings.notification_setting') }}</span>
|
||||||
|
<ul class="option-list">
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="notificationSettings.follows">
|
||||||
|
{{ $t('settings.notification_setting_follows') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="notificationSettings.followers">
|
||||||
|
{{ $t('settings.notification_setting_followers') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="notificationSettings.non_follows">
|
||||||
|
{{ $t('settings.notification_setting_non_follows') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="notificationSettings.non_followers">
|
||||||
|
{{ $t('settings.notification_setting_non_followers') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.notification_setting_privacy') }}</h2>
|
||||||
|
<p>
|
||||||
|
<Checkbox v-model="notificationSettings.privacy_option">
|
||||||
|
{{ $t('settings.notification_setting_privacy_option') }}
|
||||||
|
</Checkbox>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<p>{{ $t('settings.notification_mutes') }}</p>
|
||||||
|
<p>{{ $t('settings.notification_blocks') }}</p>
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
@click="updateNotificationSettings"
|
||||||
|
>
|
||||||
|
{{ $t('general.submit') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./notifications_tab.js"></script>
|
||||||
|
<!-- <style lang="scss" src="./profile.scss"></style> -->
|
@ -0,0 +1,179 @@
|
|||||||
|
import unescape from 'lodash/unescape'
|
||||||
|
import ImageCropper from 'src/components/image_cropper/image_cropper.vue'
|
||||||
|
import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
|
||||||
|
import fileSizeFormatService from 'src/components/../services/file_size_format/file_size_format.js'
|
||||||
|
import ProgressButton from 'src/components/progress_button/progress_button.vue'
|
||||||
|
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'
|
||||||
|
|
||||||
|
const ProfileTab = {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
newName: this.$store.state.users.currentUser.name,
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
|
||||||
|
pickAvatarBtnVisible: true,
|
||||||
|
bannerUploading: false,
|
||||||
|
backgroundUploading: false,
|
||||||
|
banner: null,
|
||||||
|
bannerPreview: null,
|
||||||
|
background: null,
|
||||||
|
backgroundPreview: null,
|
||||||
|
bannerUploadError: null,
|
||||||
|
backgroundUploadError: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ScopeSelector,
|
||||||
|
ImageCropper,
|
||||||
|
EmojiInput,
|
||||||
|
Autosuggest,
|
||||||
|
ProgressButton,
|
||||||
|
Checkbox
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
user () {
|
||||||
|
return this.$store.state.users.currentUser
|
||||||
|
},
|
||||||
|
emojiUserSuggestor () {
|
||||||
|
return suggestor({
|
||||||
|
emoji: [
|
||||||
|
...this.$store.state.instance.emoji,
|
||||||
|
...this.$store.state.instance.customEmoji
|
||||||
|
],
|
||||||
|
users: this.$store.state.users.users,
|
||||||
|
updateUsersList: (query) => this.$store.dispatch('searchUsers', { query })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
emojiSuggestor () {
|
||||||
|
return suggestor({ emoji: [
|
||||||
|
...this.$store.state.instance.emoji,
|
||||||
|
...this.$store.state.instance.customEmoji
|
||||||
|
] })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateProfile () {
|
||||||
|
this.$store.state.api.backendInteractor
|
||||||
|
.updateProfile({
|
||||||
|
params: {
|
||||||
|
note: this.newBio,
|
||||||
|
locked: this.newLocked,
|
||||||
|
// Backend notation.
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
display_name: this.newName,
|
||||||
|
default_scope: this.newDefaultScope,
|
||||||
|
no_rich_text: this.newNoRichText,
|
||||||
|
hide_follows: this.hideFollows,
|
||||||
|
hide_followers: this.hideFollowers,
|
||||||
|
discoverable: this.discoverable,
|
||||||
|
allow_following_move: this.allowFollowingMove,
|
||||||
|
hide_follows_count: this.hideFollowsCount,
|
||||||
|
hide_followers_count: this.hideFollowersCount,
|
||||||
|
show_role: this.showRole
|
||||||
|
/* eslint-enable camelcase */
|
||||||
|
} }).then((user) => {
|
||||||
|
this.$store.commit('addNewUsers', [user])
|
||||||
|
this.$store.commit('setCurrentUser', user)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
changeVis (visibility) {
|
||||||
|
this.newDefaultScope = visibility
|
||||||
|
},
|
||||||
|
uploadFile (slot, e) {
|
||||||
|
const file = e.target.files[0]
|
||||||
|
if (!file) { return }
|
||||||
|
if (file.size > this.$store.state.instance[slot + 'limit']) {
|
||||||
|
const filesize = fileSizeFormatService.fileSizeFormat(file.size)
|
||||||
|
const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit'])
|
||||||
|
this[slot + 'UploadError'] = [
|
||||||
|
this.$t('upload.error.base'),
|
||||||
|
this.$t(
|
||||||
|
'upload.error.file_too_big',
|
||||||
|
{
|
||||||
|
filesize: filesize.num,
|
||||||
|
filesizeunit: filesize.unit,
|
||||||
|
allowedsize: allowedsize.num,
|
||||||
|
allowedsizeunit: allowedsize.unit
|
||||||
|
}
|
||||||
|
)
|
||||||
|
].join(' ')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = ({ target }) => {
|
||||||
|
const img = target.result
|
||||||
|
this[slot + 'Preview'] = img
|
||||||
|
this[slot] = file
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
},
|
||||||
|
submitAvatar (cropper, file) {
|
||||||
|
const that = this
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
function updateAvatar (avatar) {
|
||||||
|
that.$store.state.api.backendInteractor.updateAvatar({ avatar })
|
||||||
|
.then((user) => {
|
||||||
|
that.$store.commit('addNewUsers', [user])
|
||||||
|
that.$store.commit('setCurrentUser', user)
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
reject(new Error(that.$t('upload.error.base') + ' ' + err.message))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cropper) {
|
||||||
|
cropper.getCroppedCanvas().toBlob(updateAvatar, file.type)
|
||||||
|
} else {
|
||||||
|
updateAvatar(file)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submitBanner () {
|
||||||
|
if (!this.bannerPreview) { return }
|
||||||
|
|
||||||
|
this.bannerUploading = true
|
||||||
|
this.$store.state.api.backendInteractor.updateBanner({ banner: this.banner })
|
||||||
|
.then((user) => {
|
||||||
|
this.$store.commit('addNewUsers', [user])
|
||||||
|
this.$store.commit('setCurrentUser', user)
|
||||||
|
this.bannerPreview = null
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.bannerUploadError = this.$t('upload.error.base') + ' ' + err.message
|
||||||
|
})
|
||||||
|
.then(() => { this.bannerUploading = false })
|
||||||
|
},
|
||||||
|
submitBg () {
|
||||||
|
if (!this.backgroundPreview) { return }
|
||||||
|
let background = this.background
|
||||||
|
this.backgroundUploading = true
|
||||||
|
this.$store.state.api.backendInteractor.updateBg({ background }).then((data) => {
|
||||||
|
if (!data.error) {
|
||||||
|
this.$store.commit('addNewUsers', [data])
|
||||||
|
this.$store.commit('setCurrentUser', data)
|
||||||
|
this.backgroundPreview = null
|
||||||
|
} else {
|
||||||
|
this.backgroundUploadError = this.$t('upload.error.base') + data.error
|
||||||
|
}
|
||||||
|
this.backgroundUploading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProfileTab
|
@ -0,0 +1,82 @@
|
|||||||
|
@import '../../../_variables.scss';
|
||||||
|
.profile-tab {
|
||||||
|
.bio {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visibility-tray {
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=file] {
|
||||||
|
padding: 5px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploading {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-changer {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-avatar {
|
||||||
|
display: block;
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
border-radius: $fallback--avatarRadius;
|
||||||
|
border-radius: var(--avatarRadius, $fallback--avatarRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.oauth-tokens {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-usersearch-wrapper {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-bulk-actions {
|
||||||
|
text-align: right;
|
||||||
|
padding: 0 1em;
|
||||||
|
min-height: 28px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-domain-mute-form {
|
||||||
|
padding: 1em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
button {
|
||||||
|
align-self: flex-end;
|
||||||
|
margin-top: 1em;
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-subitem {
|
||||||
|
margin-left: 1.75em;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,213 @@
|
|||||||
|
<template>
|
||||||
|
<div class="profile-tab">
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.name_bio') }}</h2>
|
||||||
|
<p>{{ $t('settings.name') }}</p>
|
||||||
|
<EmojiInput
|
||||||
|
v-model="newName"
|
||||||
|
enable-emoji-picker
|
||||||
|
:suggest="emojiSuggestor"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
v-model="newName"
|
||||||
|
classname="name-changer"
|
||||||
|
>
|
||||||
|
</EmojiInput>
|
||||||
|
<p>{{ $t('settings.bio') }}</p>
|
||||||
|
<EmojiInput
|
||||||
|
v-model="newBio"
|
||||||
|
enable-emoji-picker
|
||||||
|
:suggest="emojiUserSuggestor"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
v-model="newBio"
|
||||||
|
classname="bio"
|
||||||
|
/>
|
||||||
|
</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'">
|
||||||
|
{{ $t('settings.show_admin_badge') }}
|
||||||
|
</template>
|
||||||
|
<template v-if="role === 'moderator'">
|
||||||
|
{{ $t('settings.show_moderator_badge') }}
|
||||||
|
</template>
|
||||||
|
</Checkbox>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<Checkbox v-model="discoverable">
|
||||||
|
{{ $t('settings.discoverable') }}
|
||||||
|
</Checkbox>
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
:disabled="newName && newName.length === 0"
|
||||||
|
class="btn btn-default"
|
||||||
|
@click="updateProfile"
|
||||||
|
>
|
||||||
|
{{ $t('general.submit') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.avatar') }}</h2>
|
||||||
|
<p class="visibility-notice">
|
||||||
|
{{ $t('settings.avatar_size_instruction') }}
|
||||||
|
</p>
|
||||||
|
<p>{{ $t('settings.current_avatar') }}</p>
|
||||||
|
<img
|
||||||
|
:src="user.profile_image_url_original"
|
||||||
|
class="current-avatar"
|
||||||
|
>
|
||||||
|
<p>{{ $t('settings.set_new_avatar') }}</p>
|
||||||
|
<button
|
||||||
|
v-show="pickAvatarBtnVisible"
|
||||||
|
id="pick-avatar"
|
||||||
|
class="btn"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{{ $t('settings.upload_a_photo') }}
|
||||||
|
</button>
|
||||||
|
<image-cropper
|
||||||
|
trigger="#pick-avatar"
|
||||||
|
:submit-handler="submitAvatar"
|
||||||
|
@open="pickAvatarBtnVisible=false"
|
||||||
|
@close="pickAvatarBtnVisible=true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.profile_banner') }}</h2>
|
||||||
|
<p>{{ $t('settings.current_profile_banner') }}</p>
|
||||||
|
<img
|
||||||
|
:src="user.cover_photo"
|
||||||
|
class="banner"
|
||||||
|
>
|
||||||
|
<p>{{ $t('settings.set_new_profile_banner') }}</p>
|
||||||
|
<img
|
||||||
|
v-if="bannerPreview"
|
||||||
|
class="banner"
|
||||||
|
:src="bannerPreview"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
@change="uploadFile('banner', $event)"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
v-if="bannerUploading"
|
||||||
|
class=" icon-spin4 animate-spin uploading"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
v-else-if="bannerPreview"
|
||||||
|
class="btn btn-default"
|
||||||
|
@click="submitBanner"
|
||||||
|
>
|
||||||
|
{{ $t('general.submit') }}
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
v-if="bannerUploadError"
|
||||||
|
class="alert error"
|
||||||
|
>
|
||||||
|
Error: {{ bannerUploadError }}
|
||||||
|
<i
|
||||||
|
class="button-icon icon-cancel"
|
||||||
|
@click="clearUploadError('banner')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.profile_background') }}</h2>
|
||||||
|
<p>{{ $t('settings.set_new_profile_background') }}</p>
|
||||||
|
<img
|
||||||
|
v-if="backgroundPreview"
|
||||||
|
class="bg"
|
||||||
|
:src="backgroundPreview"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
@change="uploadFile('background', $event)"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
v-if="backgroundUploading"
|
||||||
|
class=" icon-spin4 animate-spin uploading"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
v-else-if="backgroundPreview"
|
||||||
|
class="btn btn-default"
|
||||||
|
@click="submitBg"
|
||||||
|
>
|
||||||
|
{{ $t('general.submit') }}
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
v-if="backgroundUploadError"
|
||||||
|
class="alert error"
|
||||||
|
>
|
||||||
|
Error: {{ backgroundUploadError }}
|
||||||
|
<i
|
||||||
|
class="button-icon icon-cancel"
|
||||||
|
@click="clearUploadError('background')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./profile_tab.js"></script>
|
||||||
|
<style lang="scss" src="./profile_tab.scss"></style>
|
@ -0,0 +1,106 @@
|
|||||||
|
import ProgressButton from 'src/components/progress_button/progress_button.vue'
|
||||||
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
|
import Mfa from './mfa.vue'
|
||||||
|
|
||||||
|
const SecurityTab = {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
newEmail: '',
|
||||||
|
changeEmailError: false,
|
||||||
|
changeEmailPassword: '',
|
||||||
|
changedEmail: false,
|
||||||
|
deletingAccount: false,
|
||||||
|
deleteAccountConfirmPasswordInput: '',
|
||||||
|
deleteAccountError: false,
|
||||||
|
changePasswordInputs: [ '', '', '' ],
|
||||||
|
changedPassword: false,
|
||||||
|
changePasswordError: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.$store.dispatch('fetchTokens')
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ProgressButton,
|
||||||
|
Mfa,
|
||||||
|
Checkbox
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
user () {
|
||||||
|
return this.$store.state.users.currentUser
|
||||||
|
},
|
||||||
|
pleromaBackend () {
|
||||||
|
return this.$store.state.instance.pleromaBackend
|
||||||
|
},
|
||||||
|
oauthTokens () {
|
||||||
|
return this.$store.state.oauthTokens.tokens.map(oauthToken => {
|
||||||
|
return {
|
||||||
|
id: oauthToken.id,
|
||||||
|
appName: oauthToken.app_name,
|
||||||
|
validUntil: new Date(oauthToken.valid_until).toLocaleDateString()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
confirmDelete () {
|
||||||
|
this.deletingAccount = true
|
||||||
|
},
|
||||||
|
deleteAccount () {
|
||||||
|
this.$store.state.api.backendInteractor.deleteAccount({ password: this.deleteAccountConfirmPasswordInput })
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
this.$store.dispatch('logout')
|
||||||
|
this.$router.push({ name: 'root' })
|
||||||
|
} else {
|
||||||
|
this.deleteAccountError = res.error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
changePassword () {
|
||||||
|
const params = {
|
||||||
|
password: this.changePasswordInputs[0],
|
||||||
|
newPassword: this.changePasswordInputs[1],
|
||||||
|
newPasswordConfirmation: this.changePasswordInputs[2]
|
||||||
|
}
|
||||||
|
this.$store.state.api.backendInteractor.changePassword(params)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
this.changedPassword = true
|
||||||
|
this.changePasswordError = false
|
||||||
|
this.logout()
|
||||||
|
} else {
|
||||||
|
this.changedPassword = false
|
||||||
|
this.changePasswordError = res.error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
changeEmail () {
|
||||||
|
const params = {
|
||||||
|
email: this.newEmail,
|
||||||
|
password: this.changeEmailPassword
|
||||||
|
}
|
||||||
|
this.$store.state.api.backendInteractor.changeEmail(params)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
this.changedEmail = true
|
||||||
|
this.changeEmailError = false
|
||||||
|
} else {
|
||||||
|
this.changedEmail = false
|
||||||
|
this.changeEmailError = res.error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
logout () {
|
||||||
|
this.$store.dispatch('logout')
|
||||||
|
this.$router.replace('/')
|
||||||
|
},
|
||||||
|
revokeToken (id) {
|
||||||
|
if (window.confirm(`${this.$i18n.t('settings.revoke_token')}?`)) {
|
||||||
|
this.$store.dispatch('revokeToken', id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SecurityTab
|
@ -0,0 +1,143 @@
|
|||||||
|
<template>
|
||||||
|
<div :label="$t('settings.security_tab')">
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.change_email') }}</h2>
|
||||||
|
<div>
|
||||||
|
<p>{{ $t('settings.new_email') }}</p>
|
||||||
|
<input
|
||||||
|
v-model="newEmail"
|
||||||
|
type="email"
|
||||||
|
autocomplete="email"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>{{ $t('settings.current_password') }}</p>
|
||||||
|
<input
|
||||||
|
v-model="changeEmailPassword"
|
||||||
|
type="password"
|
||||||
|
autocomplete="current-password"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
@click="changeEmail"
|
||||||
|
>
|
||||||
|
{{ $t('general.submit') }}
|
||||||
|
</button>
|
||||||
|
<p v-if="changedEmail">
|
||||||
|
{{ $t('settings.changed_email') }}
|
||||||
|
</p>
|
||||||
|
<template v-if="changeEmailError !== false">
|
||||||
|
<p>{{ $t('settings.change_email_error') }}</p>
|
||||||
|
<p>{{ changeEmailError }}</p>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.change_password') }}</h2>
|
||||||
|
<div>
|
||||||
|
<p>{{ $t('settings.current_password') }}</p>
|
||||||
|
<input
|
||||||
|
v-model="changePasswordInputs[0]"
|
||||||
|
type="password"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>{{ $t('settings.new_password') }}</p>
|
||||||
|
<input
|
||||||
|
v-model="changePasswordInputs[1]"
|
||||||
|
type="password"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>{{ $t('settings.confirm_new_password') }}</p>
|
||||||
|
<input
|
||||||
|
v-model="changePasswordInputs[2]"
|
||||||
|
type="password"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
@click="changePassword"
|
||||||
|
>
|
||||||
|
{{ $t('general.submit') }}
|
||||||
|
</button>
|
||||||
|
<p v-if="changedPassword">
|
||||||
|
{{ $t('settings.changed_password') }}
|
||||||
|
</p>
|
||||||
|
<p v-else-if="changePasswordError !== false">
|
||||||
|
{{ $t('settings.change_password_error') }}
|
||||||
|
</p>
|
||||||
|
<p v-if="changePasswordError">
|
||||||
|
{{ changePasswordError }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.oauth_tokens') }}</h2>
|
||||||
|
<table class="oauth-tokens">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ $t('settings.app_name') }}</th>
|
||||||
|
<th>{{ $t('settings.valid_until') }}</th>
|
||||||
|
<th />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="oauthToken in oauthTokens"
|
||||||
|
:key="oauthToken.id"
|
||||||
|
>
|
||||||
|
<td>{{ oauthToken.appName }}</td>
|
||||||
|
<td>{{ oauthToken.validUntil }}</td>
|
||||||
|
<td class="actions">
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
@click="revokeToken(oauthToken.id)"
|
||||||
|
>
|
||||||
|
{{ $t('settings.revoke_token') }}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<mfa />
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.delete_account') }}</h2>
|
||||||
|
<p v-if="!deletingAccount">
|
||||||
|
{{ $t('settings.delete_account_description') }}
|
||||||
|
</p>
|
||||||
|
<div v-if="deletingAccount">
|
||||||
|
<p>{{ $t('settings.delete_account_instructions') }}</p>
|
||||||
|
<p>{{ $t('login.password') }}</p>
|
||||||
|
<input
|
||||||
|
v-model="deleteAccountConfirmPasswordInput"
|
||||||
|
type="password"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
@click="deleteAccount"
|
||||||
|
>
|
||||||
|
{{ $t('settings.delete_account') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p v-if="deleteAccountError !== false">
|
||||||
|
{{ $t('settings.delete_account_error') }}
|
||||||
|
</p>
|
||||||
|
<p v-if="deleteAccountError">
|
||||||
|
{{ deleteAccountError }}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
v-if="!deletingAccount"
|
||||||
|
class="btn btn-default"
|
||||||
|
@click="confirmDelete"
|
||||||
|
>
|
||||||
|
{{ $t('general.submit') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./security_tab.js"></script>
|
||||||
|
<!-- <style lang="scss" src="./profile.scss"></style> -->
|
@ -0,0 +1,24 @@
|
|||||||
|
import { extractCommit } from 'src/services/version/version.service'
|
||||||
|
|
||||||
|
const pleromaFeCommitUrl = 'https://git.pleroma.social/sjw/pleroma-fe/commit/'
|
||||||
|
const pleromaBeCommitUrl = 'https://git.pleroma.social/sjw/pleroma/commit/'
|
||||||
|
|
||||||
|
const VersionTab = {
|
||||||
|
data () {
|
||||||
|
const instance = this.$store.state.instance
|
||||||
|
return {
|
||||||
|
backendVersion: instance.backendVersion,
|
||||||
|
frontendVersion: instance.frontendVersion
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
frontendVersionLink () {
|
||||||
|
return pleromaFeCommitUrl + this.frontendVersion
|
||||||
|
},
|
||||||
|
backendVersionLink () {
|
||||||
|
return pleromaBeCommitUrl + extractCommit(this.backendVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VersionTab
|
@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<div :label="$t('settings.version.title')">
|
||||||
|
<div class="setting-item">
|
||||||
|
<ul class="setting-list">
|
||||||
|
<li>
|
||||||
|
<p>{{ $t('settings.version.backend_version') }}</p>
|
||||||
|
<ul class="option-list">
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
:href="backendVersionLink"
|
||||||
|
target="_blank"
|
||||||
|
>{{ backendVersion }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>{{ $t('settings.version.frontend_version') }}</p>
|
||||||
|
<ul class="option-list">
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
:href="frontendVersionLink"
|
||||||
|
target="_blank"
|
||||||
|
>{{ frontendVersion }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script src="./version_tab.js">
|
@ -1,393 +0,0 @@
|
|||||||
import unescape from 'lodash/unescape'
|
|
||||||
import get from 'lodash/get'
|
|
||||||
import map from 'lodash/map'
|
|
||||||
import reject from 'lodash/reject'
|
|
||||||
import TabSwitcher from '../tab_switcher/tab_switcher.js'
|
|
||||||
import ImageCropper from '../image_cropper/image_cropper.vue'
|
|
||||||
import StyleSwitcher from '../style_switcher/style_switcher.vue'
|
|
||||||
import ScopeSelector from '../scope_selector/scope_selector.vue'
|
|
||||||
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
|
|
||||||
import BlockCard from '../block_card/block_card.vue'
|
|
||||||
import MuteCard from '../mute_card/mute_card.vue'
|
|
||||||
import DomainMuteCard from '../domain_mute_card/domain_mute_card.vue'
|
|
||||||
import SelectableList from '../selectable_list/selectable_list.vue'
|
|
||||||
import ProgressButton from '../progress_button/progress_button.vue'
|
|
||||||
import EmojiInput from '../emoji_input/emoji_input.vue'
|
|
||||||
import suggestor from '../emoji_input/suggestor.js'
|
|
||||||
import Autosuggest from '../autosuggest/autosuggest.vue'
|
|
||||||
import Importer from '../importer/importer.vue'
|
|
||||||
import Exporter from '../exporter/exporter.vue'
|
|
||||||
import withSubscription from '../../hocs/with_subscription/with_subscription'
|
|
||||||
import Checkbox from '../checkbox/checkbox.vue'
|
|
||||||
import Mfa from './mfa.vue'
|
|
||||||
|
|
||||||
const BlockList = withSubscription({
|
|
||||||
fetch: (props, $store) => $store.dispatch('fetchBlocks'),
|
|
||||||
select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
|
|
||||||
childPropName: 'items'
|
|
||||||
})(SelectableList)
|
|
||||||
|
|
||||||
const MuteList = withSubscription({
|
|
||||||
fetch: (props, $store) => $store.dispatch('fetchMutes'),
|
|
||||||
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
|
|
||||||
childPropName: 'items'
|
|
||||||
})(SelectableList)
|
|
||||||
|
|
||||||
const DomainMuteList = withSubscription({
|
|
||||||
fetch: (props, $store) => $store.dispatch('fetchDomainMutes'),
|
|
||||||
select: (props, $store) => get($store.state.users.currentUser, 'domainMutes', []),
|
|
||||||
childPropName: 'items'
|
|
||||||
})(SelectableList)
|
|
||||||
|
|
||||||
const UserSettings = {
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
newEmail: '',
|
|
||||||
newName: this.$store.state.users.currentUser.name,
|
|
||||||
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,
|
|
||||||
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,
|
|
||||||
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
|
|
||||||
pickAvatarBtnVisible: true,
|
|
||||||
bannerUploading: false,
|
|
||||||
backgroundUploading: false,
|
|
||||||
banner: null,
|
|
||||||
bannerPreview: null,
|
|
||||||
background: null,
|
|
||||||
backgroundPreview: null,
|
|
||||||
bannerUploadError: null,
|
|
||||||
backgroundUploadError: null,
|
|
||||||
changeEmailError: false,
|
|
||||||
changeEmailPassword: '',
|
|
||||||
changedEmail: false,
|
|
||||||
deletingAccount: false,
|
|
||||||
deleteAccountConfirmPasswordInput: '',
|
|
||||||
deleteAccountError: false,
|
|
||||||
changePasswordInputs: [ '', '', '' ],
|
|
||||||
changedPassword: false,
|
|
||||||
changePasswordError: false,
|
|
||||||
activeTab: 'profile',
|
|
||||||
notificationSettings: this.$store.state.users.currentUser.notification_settings,
|
|
||||||
newDomainToMute: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.$store.dispatch('fetchTokens')
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
StyleSwitcher,
|
|
||||||
ScopeSelector,
|
|
||||||
TabSwitcher,
|
|
||||||
ImageCropper,
|
|
||||||
BlockList,
|
|
||||||
MuteList,
|
|
||||||
DomainMuteList,
|
|
||||||
EmojiInput,
|
|
||||||
Autosuggest,
|
|
||||||
BlockCard,
|
|
||||||
MuteCard,
|
|
||||||
DomainMuteCard,
|
|
||||||
ProgressButton,
|
|
||||||
Importer,
|
|
||||||
Exporter,
|
|
||||||
Mfa,
|
|
||||||
Checkbox
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
user () {
|
|
||||||
return this.$store.state.users.currentUser
|
|
||||||
},
|
|
||||||
emojiUserSuggestor () {
|
|
||||||
return suggestor({
|
|
||||||
emoji: [
|
|
||||||
...this.$store.state.instance.emoji,
|
|
||||||
...this.$store.state.instance.customEmoji
|
|
||||||
],
|
|
||||||
users: this.$store.state.users.users,
|
|
||||||
updateUsersList: (query) => this.$store.dispatch('searchUsers', { query })
|
|
||||||
})
|
|
||||||
},
|
|
||||||
emojiSuggestor () {
|
|
||||||
return suggestor({ emoji: [
|
|
||||||
...this.$store.state.instance.emoji,
|
|
||||||
...this.$store.state.instance.customEmoji
|
|
||||||
] })
|
|
||||||
},
|
|
||||||
pleromaBackend () {
|
|
||||||
return this.$store.state.instance.pleromaBackend
|
|
||||||
},
|
|
||||||
minimalScopesMode () {
|
|
||||||
return this.$store.state.instance.minimalScopesMode
|
|
||||||
},
|
|
||||||
vis () {
|
|
||||||
return {
|
|
||||||
public: { selected: this.newDefaultScope === 'public' },
|
|
||||||
unlisted: { selected: this.newDefaultScope === 'unlisted' },
|
|
||||||
private: { selected: this.newDefaultScope === 'private' },
|
|
||||||
direct: { selected: this.newDefaultScope === 'direct' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
currentSaveStateNotice () {
|
|
||||||
return this.$store.state.interface.settings.currentSaveStateNotice
|
|
||||||
},
|
|
||||||
oauthTokens () {
|
|
||||||
return this.$store.state.oauthTokens.tokens.map(oauthToken => {
|
|
||||||
return {
|
|
||||||
id: oauthToken.id,
|
|
||||||
appName: oauthToken.app_name,
|
|
||||||
validUntil: new Date(oauthToken.valid_until).toLocaleDateString()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateProfile () {
|
|
||||||
this.$store.state.api.backendInteractor
|
|
||||||
.updateProfile({
|
|
||||||
params: {
|
|
||||||
note: this.newBio,
|
|
||||||
locked: this.newLocked,
|
|
||||||
// Backend notation.
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
display_name: this.newName,
|
|
||||||
default_scope: this.newDefaultScope,
|
|
||||||
no_rich_text: this.newNoRichText,
|
|
||||||
hide_follows: this.hideFollows,
|
|
||||||
hide_followers: this.hideFollowers,
|
|
||||||
discoverable: this.discoverable,
|
|
||||||
allow_following_move: this.allowFollowingMove,
|
|
||||||
hide_follows_count: this.hideFollowsCount,
|
|
||||||
hide_followers_count: this.hideFollowersCount,
|
|
||||||
show_role: this.showRole
|
|
||||||
/* eslint-enable camelcase */
|
|
||||||
} }).then((user) => {
|
|
||||||
this.$store.commit('addNewUsers', [user])
|
|
||||||
this.$store.commit('setCurrentUser', user)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateNotificationSettings () {
|
|
||||||
this.$store.state.api.backendInteractor
|
|
||||||
.updateNotificationSettings({ settings: this.notificationSettings })
|
|
||||||
},
|
|
||||||
changeVis (visibility) {
|
|
||||||
this.newDefaultScope = visibility
|
|
||||||
},
|
|
||||||
uploadFile (slot, e) {
|
|
||||||
const file = e.target.files[0]
|
|
||||||
if (!file) { return }
|
|
||||||
if (file.size > this.$store.state.instance[slot + 'limit']) {
|
|
||||||
const filesize = fileSizeFormatService.fileSizeFormat(file.size)
|
|
||||||
const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit'])
|
|
||||||
this[slot + 'UploadError'] = this.$t('upload.error.base') + ' ' + this.$t('upload.error.file_too_big', { filesize: filesize.num, filesizeunit: filesize.unit, allowedsize: allowedsize.num, allowedsizeunit: allowedsize.unit })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
const reader = new FileReader()
|
|
||||||
reader.onload = ({ target }) => {
|
|
||||||
const img = target.result
|
|
||||||
this[slot + 'Preview'] = img
|
|
||||||
this[slot] = file
|
|
||||||
}
|
|
||||||
reader.readAsDataURL(file)
|
|
||||||
},
|
|
||||||
submitAvatar (cropper, file) {
|
|
||||||
const that = this
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
function updateAvatar (avatar) {
|
|
||||||
that.$store.state.api.backendInteractor.updateAvatar({ avatar })
|
|
||||||
.then((user) => {
|
|
||||||
that.$store.commit('addNewUsers', [user])
|
|
||||||
that.$store.commit('setCurrentUser', user)
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
reject(new Error(that.$t('upload.error.base') + ' ' + err.message))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cropper) {
|
|
||||||
cropper.getCroppedCanvas().toBlob(updateAvatar, file.type)
|
|
||||||
} else {
|
|
||||||
updateAvatar(file)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
clearUploadError (slot) {
|
|
||||||
this[slot + 'UploadError'] = null
|
|
||||||
},
|
|
||||||
submitBanner () {
|
|
||||||
if (!this.bannerPreview) { return }
|
|
||||||
|
|
||||||
this.bannerUploading = true
|
|
||||||
this.$store.state.api.backendInteractor.updateBanner({ banner: this.banner })
|
|
||||||
.then((user) => {
|
|
||||||
this.$store.commit('addNewUsers', [user])
|
|
||||||
this.$store.commit('setCurrentUser', user)
|
|
||||||
this.bannerPreview = null
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
this.bannerUploadError = this.$t('upload.error.base') + ' ' + err.message
|
|
||||||
})
|
|
||||||
.then(() => { this.bannerUploading = false })
|
|
||||||
},
|
|
||||||
submitBg () {
|
|
||||||
if (!this.backgroundPreview) { return }
|
|
||||||
let background = this.background
|
|
||||||
this.backgroundUploading = true
|
|
||||||
this.$store.state.api.backendInteractor.updateBg({ background }).then((data) => {
|
|
||||||
if (!data.error) {
|
|
||||||
this.$store.commit('addNewUsers', [data])
|
|
||||||
this.$store.commit('setCurrentUser', data)
|
|
||||||
this.backgroundPreview = null
|
|
||||||
} else {
|
|
||||||
this.backgroundUploadError = this.$t('upload.error.base') + data.error
|
|
||||||
}
|
|
||||||
this.backgroundUploading = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
importFollows (file) {
|
|
||||||
return this.$store.state.api.backendInteractor.importFollows({ file })
|
|
||||||
.then((status) => {
|
|
||||||
if (!status) {
|
|
||||||
throw new Error('failed')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
importBlocks (file) {
|
|
||||||
return this.$store.state.api.backendInteractor.importBlocks({ file })
|
|
||||||
.then((status) => {
|
|
||||||
if (!status) {
|
|
||||||
throw new Error('failed')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
generateExportableUsersContent (users) {
|
|
||||||
// Get addresses
|
|
||||||
return users.map((user) => {
|
|
||||||
// check is it's a local user
|
|
||||||
if (user && user.is_local) {
|
|
||||||
// append the instance address
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
return user.screen_name + '@' + location.hostname
|
|
||||||
}
|
|
||||||
return user.screen_name
|
|
||||||
}).join('\n')
|
|
||||||
},
|
|
||||||
getFollowsContent () {
|
|
||||||
return this.$store.state.api.backendInteractor.exportFriends({ id: this.$store.state.users.currentUser.id })
|
|
||||||
.then(this.generateExportableUsersContent)
|
|
||||||
},
|
|
||||||
getBlocksContent () {
|
|
||||||
return this.$store.state.api.backendInteractor.fetchBlocks()
|
|
||||||
.then(this.generateExportableUsersContent)
|
|
||||||
},
|
|
||||||
confirmDelete () {
|
|
||||||
this.deletingAccount = true
|
|
||||||
},
|
|
||||||
deleteAccount () {
|
|
||||||
this.$store.state.api.backendInteractor.deleteAccount({ password: this.deleteAccountConfirmPasswordInput })
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === 'success') {
|
|
||||||
this.$store.dispatch('logout')
|
|
||||||
this.$router.push({ name: 'root' })
|
|
||||||
} else {
|
|
||||||
this.deleteAccountError = res.error
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
changePassword () {
|
|
||||||
const params = {
|
|
||||||
password: this.changePasswordInputs[0],
|
|
||||||
newPassword: this.changePasswordInputs[1],
|
|
||||||
newPasswordConfirmation: this.changePasswordInputs[2]
|
|
||||||
}
|
|
||||||
this.$store.state.api.backendInteractor.changePassword(params)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === 'success') {
|
|
||||||
this.changedPassword = true
|
|
||||||
this.changePasswordError = false
|
|
||||||
this.logout()
|
|
||||||
} else {
|
|
||||||
this.changedPassword = false
|
|
||||||
this.changePasswordError = res.error
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
changeEmail () {
|
|
||||||
const params = {
|
|
||||||
email: this.newEmail,
|
|
||||||
password: this.changeEmailPassword
|
|
||||||
}
|
|
||||||
this.$store.state.api.backendInteractor.changeEmail(params)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === 'success') {
|
|
||||||
this.changedEmail = true
|
|
||||||
this.changeEmailError = false
|
|
||||||
} else {
|
|
||||||
this.changedEmail = false
|
|
||||||
this.changeEmailError = res.error
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
activateTab (tabName) {
|
|
||||||
this.activeTab = tabName
|
|
||||||
},
|
|
||||||
logout () {
|
|
||||||
this.$store.dispatch('logout')
|
|
||||||
this.$router.replace('/')
|
|
||||||
},
|
|
||||||
revokeToken (id) {
|
|
||||||
if (window.confirm(`${this.$i18n.t('settings.revoke_token')}?`)) {
|
|
||||||
this.$store.dispatch('revokeToken', id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
filterUnblockedUsers (userIds) {
|
|
||||||
return reject(userIds, (userId) => {
|
|
||||||
const relationship = this.$store.getters.relationship(this.userId)
|
|
||||||
return relationship.blocking || userId === this.$store.state.users.currentUser.id
|
|
||||||
})
|
|
||||||
},
|
|
||||||
filterUnMutedUsers (userIds) {
|
|
||||||
return reject(userIds, (userId) => {
|
|
||||||
const relationship = this.$store.getters.relationship(this.userId)
|
|
||||||
return relationship.muting || userId === this.$store.state.users.currentUser.id
|
|
||||||
})
|
|
||||||
},
|
|
||||||
queryUserIds (query) {
|
|
||||||
return this.$store.dispatch('searchUsers', { query })
|
|
||||||
.then((users) => map(users, 'id'))
|
|
||||||
},
|
|
||||||
blockUsers (ids) {
|
|
||||||
return this.$store.dispatch('blockUsers', ids)
|
|
||||||
},
|
|
||||||
unblockUsers (ids) {
|
|
||||||
return this.$store.dispatch('unblockUsers', ids)
|
|
||||||
},
|
|
||||||
muteUsers (ids) {
|
|
||||||
return this.$store.dispatch('muteUsers', ids)
|
|
||||||
},
|
|
||||||
unmuteUsers (ids) {
|
|
||||||
return this.$store.dispatch('unmuteUsers', ids)
|
|
||||||
},
|
|
||||||
unmuteDomains (domains) {
|
|
||||||
return this.$store.dispatch('unmuteDomains', domains)
|
|
||||||
},
|
|
||||||
muteDomain () {
|
|
||||||
return this.$store.dispatch('muteDomain', this.newDomainToMute)
|
|
||||||
.then(() => { this.newDomainToMute = '' })
|
|
||||||
},
|
|
||||||
identity (value) {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UserSettings
|
|
@ -1,728 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="settings panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<div class="title">
|
|
||||||
{{ $t('settings.user_settings') }}
|
|
||||||
</div>
|
|
||||||
<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>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body profile-edit">
|
|
||||||
<tab-switcher>
|
|
||||||
<div :label="$t('settings.profile_tab')">
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.name_bio') }}</h2>
|
|
||||||
<p>{{ $t('settings.name') }}</p>
|
|
||||||
<EmojiInput
|
|
||||||
v-model="newName"
|
|
||||||
enable-emoji-picker
|
|
||||||
:suggest="emojiSuggestor"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
id="username"
|
|
||||||
v-model="newName"
|
|
||||||
classname="name-changer"
|
|
||||||
>
|
|
||||||
</EmojiInput>
|
|
||||||
<p>{{ $t('settings.bio') }}</p>
|
|
||||||
<EmojiInput
|
|
||||||
v-model="newBio"
|
|
||||||
enable-emoji-picker
|
|
||||||
:suggest="emojiUserSuggestor"
|
|
||||||
>
|
|
||||||
<textarea
|
|
||||||
v-model="newBio"
|
|
||||||
classname="bio"
|
|
||||||
/>
|
|
||||||
</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'">
|
|
||||||
{{ $t('settings.show_admin_badge') }}
|
|
||||||
</template>
|
|
||||||
<template v-if="role === 'moderator'">
|
|
||||||
{{ $t('settings.show_moderator_badge') }}
|
|
||||||
</template>
|
|
||||||
</Checkbox>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<Checkbox v-model="discoverable">
|
|
||||||
{{ $t('settings.discoverable') }}
|
|
||||||
</Checkbox>
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
:disabled="newName && newName.length === 0"
|
|
||||||
class="btn btn-default"
|
|
||||||
@click="updateProfile"
|
|
||||||
>
|
|
||||||
{{ $t('general.submit') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.avatar') }}</h2>
|
|
||||||
<p class="visibility-notice">
|
|
||||||
{{ $t('settings.avatar_size_instruction') }}
|
|
||||||
</p>
|
|
||||||
<p>{{ $t('settings.current_avatar') }}</p>
|
|
||||||
<img
|
|
||||||
:src="user.profile_image_url_original"
|
|
||||||
class="current-avatar"
|
|
||||||
>
|
|
||||||
<p>{{ $t('settings.set_new_avatar') }}</p>
|
|
||||||
<button
|
|
||||||
v-show="pickAvatarBtnVisible"
|
|
||||||
id="pick-avatar"
|
|
||||||
class="btn"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{{ $t('settings.upload_a_photo') }}
|
|
||||||
</button>
|
|
||||||
<image-cropper
|
|
||||||
trigger="#pick-avatar"
|
|
||||||
:submit-handler="submitAvatar"
|
|
||||||
@open="pickAvatarBtnVisible=false"
|
|
||||||
@close="pickAvatarBtnVisible=true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.profile_banner') }}</h2>
|
|
||||||
<p>{{ $t('settings.current_profile_banner') }}</p>
|
|
||||||
<img
|
|
||||||
:src="user.cover_photo"
|
|
||||||
class="banner"
|
|
||||||
>
|
|
||||||
<p>{{ $t('settings.set_new_profile_banner') }}</p>
|
|
||||||
<img
|
|
||||||
v-if="bannerPreview"
|
|
||||||
class="banner"
|
|
||||||
:src="bannerPreview"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
@change="uploadFile('banner', $event)"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<i
|
|
||||||
v-if="bannerUploading"
|
|
||||||
class=" icon-spin4 animate-spin uploading"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
v-else-if="bannerPreview"
|
|
||||||
class="btn btn-default"
|
|
||||||
@click="submitBanner"
|
|
||||||
>
|
|
||||||
{{ $t('general.submit') }}
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
v-if="bannerUploadError"
|
|
||||||
class="alert error"
|
|
||||||
>
|
|
||||||
Error: {{ bannerUploadError }}
|
|
||||||
<i
|
|
||||||
class="button-icon icon-cancel"
|
|
||||||
@click="clearUploadError('banner')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.profile_background') }}</h2>
|
|
||||||
<p>{{ $t('settings.set_new_profile_background') }}</p>
|
|
||||||
<img
|
|
||||||
v-if="backgroundPreview"
|
|
||||||
class="bg"
|
|
||||||
:src="backgroundPreview"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
@change="uploadFile('background', $event)"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<i
|
|
||||||
v-if="backgroundUploading"
|
|
||||||
class=" icon-spin4 animate-spin uploading"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
v-else-if="backgroundPreview"
|
|
||||||
class="btn btn-default"
|
|
||||||
@click="submitBg"
|
|
||||||
>
|
|
||||||
{{ $t('general.submit') }}
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
v-if="backgroundUploadError"
|
|
||||||
class="alert error"
|
|
||||||
>
|
|
||||||
Error: {{ backgroundUploadError }}
|
|
||||||
<i
|
|
||||||
class="button-icon icon-cancel"
|
|
||||||
@click="clearUploadError('background')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div :label="$t('settings.security_tab')">
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.change_email') }}</h2>
|
|
||||||
<div>
|
|
||||||
<p>{{ $t('settings.new_email') }}</p>
|
|
||||||
<input
|
|
||||||
v-model="newEmail"
|
|
||||||
type="email"
|
|
||||||
autocomplete="email"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p>{{ $t('settings.current_password') }}</p>
|
|
||||||
<input
|
|
||||||
v-model="changeEmailPassword"
|
|
||||||
type="password"
|
|
||||||
autocomplete="current-password"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="btn btn-default"
|
|
||||||
@click="changeEmail"
|
|
||||||
>
|
|
||||||
{{ $t('general.submit') }}
|
|
||||||
</button>
|
|
||||||
<p v-if="changedEmail">
|
|
||||||
{{ $t('settings.changed_email') }}
|
|
||||||
</p>
|
|
||||||
<template v-if="changeEmailError !== false">
|
|
||||||
<p>{{ $t('settings.change_email_error') }}</p>
|
|
||||||
<p>{{ changeEmailError }}</p>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.change_password') }}</h2>
|
|
||||||
<div>
|
|
||||||
<p>{{ $t('settings.current_password') }}</p>
|
|
||||||
<input
|
|
||||||
v-model="changePasswordInputs[0]"
|
|
||||||
type="password"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p>{{ $t('settings.new_password') }}</p>
|
|
||||||
<input
|
|
||||||
v-model="changePasswordInputs[1]"
|
|
||||||
type="password"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p>{{ $t('settings.confirm_new_password') }}</p>
|
|
||||||
<input
|
|
||||||
v-model="changePasswordInputs[2]"
|
|
||||||
type="password"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="btn btn-default"
|
|
||||||
@click="changePassword"
|
|
||||||
>
|
|
||||||
{{ $t('general.submit') }}
|
|
||||||
</button>
|
|
||||||
<p v-if="changedPassword">
|
|
||||||
{{ $t('settings.changed_password') }}
|
|
||||||
</p>
|
|
||||||
<p v-else-if="changePasswordError !== false">
|
|
||||||
{{ $t('settings.change_password_error') }}
|
|
||||||
</p>
|
|
||||||
<p v-if="changePasswordError">
|
|
||||||
{{ changePasswordError }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.oauth_tokens') }}</h2>
|
|
||||||
<table class="oauth-tokens">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{ $t('settings.app_name') }}</th>
|
|
||||||
<th>{{ $t('settings.valid_until') }}</th>
|
|
||||||
<th />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
v-for="oauthToken in oauthTokens"
|
|
||||||
:key="oauthToken.id"
|
|
||||||
>
|
|
||||||
<td>{{ oauthToken.appName }}</td>
|
|
||||||
<td>{{ oauthToken.validUntil }}</td>
|
|
||||||
<td class="actions">
|
|
||||||
<button
|
|
||||||
class="btn btn-default"
|
|
||||||
@click="revokeToken(oauthToken.id)"
|
|
||||||
>
|
|
||||||
{{ $t('settings.revoke_token') }}
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<mfa />
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.delete_account') }}</h2>
|
|
||||||
<p v-if="!deletingAccount">
|
|
||||||
{{ $t('settings.delete_account_description') }}
|
|
||||||
</p>
|
|
||||||
<div v-if="deletingAccount">
|
|
||||||
<p>{{ $t('settings.delete_account_instructions') }}</p>
|
|
||||||
<p>{{ $t('login.password') }}</p>
|
|
||||||
<input
|
|
||||||
v-model="deleteAccountConfirmPasswordInput"
|
|
||||||
type="password"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="btn btn-default"
|
|
||||||
@click="deleteAccount"
|
|
||||||
>
|
|
||||||
{{ $t('settings.delete_account') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p v-if="deleteAccountError !== false">
|
|
||||||
{{ $t('settings.delete_account_error') }}
|
|
||||||
</p>
|
|
||||||
<p v-if="deleteAccountError">
|
|
||||||
{{ deleteAccountError }}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
v-if="!deletingAccount"
|
|
||||||
class="btn btn-default"
|
|
||||||
@click="confirmDelete"
|
|
||||||
>
|
|
||||||
{{ $t('general.submit') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="pleromaBackend"
|
|
||||||
:label="$t('settings.notifications')"
|
|
||||||
>
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.notification_setting_filters') }}</h2>
|
|
||||||
<div class="select-multiple">
|
|
||||||
<span class="label">{{ $t('settings.notification_setting') }}</span>
|
|
||||||
<ul class="option-list">
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="notificationSettings.follows">
|
|
||||||
{{ $t('settings.notification_setting_follows') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="notificationSettings.followers">
|
|
||||||
{{ $t('settings.notification_setting_followers') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="notificationSettings.non_follows">
|
|
||||||
{{ $t('settings.notification_setting_non_follows') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox v-model="notificationSettings.non_followers">
|
|
||||||
{{ $t('settings.notification_setting_non_followers') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.notification_setting_privacy') }}</h2>
|
|
||||||
<p>
|
|
||||||
<Checkbox v-model="notificationSettings.privacy_option">
|
|
||||||
{{ $t('settings.notification_setting_privacy_option') }}
|
|
||||||
</Checkbox>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
|
||||||
<p>{{ $t('settings.notification_mutes') }}</p>
|
|
||||||
<p>{{ $t('settings.notification_blocks') }}</p>
|
|
||||||
<button
|
|
||||||
class="btn btn-default"
|
|
||||||
@click="updateNotificationSettings"
|
|
||||||
>
|
|
||||||
{{ $t('general.submit') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="pleromaBackend"
|
|
||||||
:label="$t('settings.data_import_export_tab')"
|
|
||||||
>
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.follow_import') }}</h2>
|
|
||||||
<p>{{ $t('settings.import_followers_from_a_csv_file') }}</p>
|
|
||||||
<Importer
|
|
||||||
:submit-handler="importFollows"
|
|
||||||
:success-message="$t('settings.follows_imported')"
|
|
||||||
:error-message="$t('settings.follow_import_error')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.follow_export') }}</h2>
|
|
||||||
<Exporter
|
|
||||||
:get-content="getFollowsContent"
|
|
||||||
filename="friends.csv"
|
|
||||||
:export-button-label="$t('settings.follow_export_button')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.block_import') }}</h2>
|
|
||||||
<p>{{ $t('settings.import_blocks_from_a_csv_file') }}</p>
|
|
||||||
<Importer
|
|
||||||
:submit-handler="importBlocks"
|
|
||||||
:success-message="$t('settings.blocks_imported')"
|
|
||||||
:error-message="$t('settings.block_import_error')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2>{{ $t('settings.block_export') }}</h2>
|
|
||||||
<Exporter
|
|
||||||
:get-content="getBlocksContent"
|
|
||||||
filename="blocks.csv"
|
|
||||||
:export-button-label="$t('settings.block_export_button')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div :label="$t('settings.blocks_tab')">
|
|
||||||
<div class="profile-edit-usersearch-wrapper">
|
|
||||||
<Autosuggest
|
|
||||||
:filter="filterUnblockedUsers"
|
|
||||||
:query="queryUserIds"
|
|
||||||
:placeholder="$t('settings.search_user_to_block')"
|
|
||||||
>
|
|
||||||
<BlockCard
|
|
||||||
slot-scope="row"
|
|
||||||
:user-id="row.item"
|
|
||||||
/>
|
|
||||||
</Autosuggest>
|
|
||||||
</div>
|
|
||||||
<BlockList
|
|
||||||
:refresh="true"
|
|
||||||
:get-key="identity"
|
|
||||||
>
|
|
||||||
<template
|
|
||||||
slot="header"
|
|
||||||
slot-scope="{selected}"
|
|
||||||
>
|
|
||||||
<div class="profile-edit-bulk-actions">
|
|
||||||
<ProgressButton
|
|
||||||
v-if="selected.length > 0"
|
|
||||||
class="btn btn-default"
|
|
||||||
:click="() => blockUsers(selected)"
|
|
||||||
>
|
|
||||||
{{ $t('user_card.block') }}
|
|
||||||
<template slot="progress">
|
|
||||||
{{ $t('user_card.block_progress') }}
|
|
||||||
</template>
|
|
||||||
</ProgressButton>
|
|
||||||
<ProgressButton
|
|
||||||
v-if="selected.length > 0"
|
|
||||||
class="btn btn-default"
|
|
||||||
:click="() => unblockUsers(selected)"
|
|
||||||
>
|
|
||||||
{{ $t('user_card.unblock') }}
|
|
||||||
<template slot="progress">
|
|
||||||
{{ $t('user_card.unblock_progress') }}
|
|
||||||
</template>
|
|
||||||
</ProgressButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template
|
|
||||||
slot="item"
|
|
||||||
slot-scope="{item}"
|
|
||||||
>
|
|
||||||
<BlockCard :user-id="item" />
|
|
||||||
</template>
|
|
||||||
<template slot="empty">
|
|
||||||
{{ $t('settings.no_blocks') }}
|
|
||||||
</template>
|
|
||||||
</BlockList>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div :label="$t('settings.mutes_tab')">
|
|
||||||
<tab-switcher>
|
|
||||||
<div label="Users">
|
|
||||||
<div class="profile-edit-usersearch-wrapper">
|
|
||||||
<Autosuggest
|
|
||||||
:filter="filterUnMutedUsers"
|
|
||||||
:query="queryUserIds"
|
|
||||||
:placeholder="$t('settings.search_user_to_mute')"
|
|
||||||
>
|
|
||||||
<MuteCard
|
|
||||||
slot-scope="row"
|
|
||||||
:user-id="row.item"
|
|
||||||
/>
|
|
||||||
</Autosuggest>
|
|
||||||
</div>
|
|
||||||
<MuteList
|
|
||||||
:refresh="true"
|
|
||||||
:get-key="identity"
|
|
||||||
>
|
|
||||||
<template
|
|
||||||
slot="header"
|
|
||||||
slot-scope="{selected}"
|
|
||||||
>
|
|
||||||
<div class="profile-edit-bulk-actions">
|
|
||||||
<ProgressButton
|
|
||||||
v-if="selected.length > 0"
|
|
||||||
class="btn btn-default"
|
|
||||||
:click="() => muteUsers(selected)"
|
|
||||||
>
|
|
||||||
{{ $t('user_card.mute') }}
|
|
||||||
<template slot="progress">
|
|
||||||
{{ $t('user_card.mute_progress') }}
|
|
||||||
</template>
|
|
||||||
</ProgressButton>
|
|
||||||
<ProgressButton
|
|
||||||
v-if="selected.length > 0"
|
|
||||||
class="btn btn-default"
|
|
||||||
:click="() => unmuteUsers(selected)"
|
|
||||||
>
|
|
||||||
{{ $t('user_card.unmute') }}
|
|
||||||
<template slot="progress">
|
|
||||||
{{ $t('user_card.unmute_progress') }}
|
|
||||||
</template>
|
|
||||||
</ProgressButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template
|
|
||||||
slot="item"
|
|
||||||
slot-scope="{item}"
|
|
||||||
>
|
|
||||||
<MuteCard :user-id="item" />
|
|
||||||
</template>
|
|
||||||
<template slot="empty">
|
|
||||||
{{ $t('settings.no_mutes') }}
|
|
||||||
</template>
|
|
||||||
</MuteList>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div :label="$t('settings.domain_mutes')">
|
|
||||||
<div class="profile-edit-domain-mute-form">
|
|
||||||
<input
|
|
||||||
v-model="newDomainToMute"
|
|
||||||
:placeholder="$t('settings.type_domains_to_mute')"
|
|
||||||
type="text"
|
|
||||||
@keyup.enter="muteDomain"
|
|
||||||
>
|
|
||||||
<ProgressButton
|
|
||||||
class="btn btn-default"
|
|
||||||
:click="muteDomain"
|
|
||||||
>
|
|
||||||
{{ $t('domain_mute_card.mute') }}
|
|
||||||
<template slot="progress">
|
|
||||||
{{ $t('domain_mute_card.mute_progress') }}
|
|
||||||
</template>
|
|
||||||
</ProgressButton>
|
|
||||||
</div>
|
|
||||||
<DomainMuteList
|
|
||||||
:refresh="true"
|
|
||||||
:get-key="identity"
|
|
||||||
>
|
|
||||||
<template
|
|
||||||
slot="header"
|
|
||||||
slot-scope="{selected}"
|
|
||||||
>
|
|
||||||
<div class="profile-edit-bulk-actions">
|
|
||||||
<ProgressButton
|
|
||||||
v-if="selected.length > 0"
|
|
||||||
class="btn btn-default"
|
|
||||||
:click="() => unmuteDomains(selected)"
|
|
||||||
>
|
|
||||||
{{ $t('domain_mute_card.unmute') }}
|
|
||||||
<template slot="progress">
|
|
||||||
{{ $t('domain_mute_card.unmute_progress') }}
|
|
||||||
</template>
|
|
||||||
</ProgressButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template
|
|
||||||
slot="item"
|
|
||||||
slot-scope="{item}"
|
|
||||||
>
|
|
||||||
<DomainMuteCard :domain="item" />
|
|
||||||
</template>
|
|
||||||
<template slot="empty">
|
|
||||||
{{ $t('settings.no_mutes') }}
|
|
||||||
</template>
|
|
||||||
</DomainMuteList>
|
|
||||||
</div>
|
|
||||||
</tab-switcher>
|
|
||||||
</div>
|
|
||||||
</tab-switcher>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./user_settings.js">
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import '../../_variables.scss';
|
|
||||||
|
|
||||||
.profile-edit {
|
|
||||||
.bio {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.visibility-tray {
|
|
||||||
padding-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=file] {
|
|
||||||
padding: 5px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploading {
|
|
||||||
font-size: 1.5em;
|
|
||||||
margin: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-changer {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-avatar {
|
|
||||||
display: block;
|
|
||||||
width: 150px;
|
|
||||||
height: 150px;
|
|
||||||
border-radius: $fallback--avatarRadius;
|
|
||||||
border-radius: var(--avatarRadius, $fallback--avatarRadius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.oauth-tokens {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-usersearch-wrapper {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-bulk-actions {
|
|
||||||
text-align: right;
|
|
||||||
padding: 0 1em;
|
|
||||||
min-height: 28px;
|
|
||||||
|
|
||||||
button {
|
|
||||||
width: 10em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-domain-mute-form {
|
|
||||||
padding: 1em;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
button {
|
|
||||||
align-self: flex-end;
|
|
||||||
margin-top: 1em;
|
|
||||||
width: 10em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-subitem {
|
|
||||||
margin-left: 1.75em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,206 +1,206 @@
|
|||||||
{
|
{
|
||||||
"chat": {
|
"chat": {
|
||||||
"title": "الدردشة"
|
"title": "الدردشة"
|
||||||
|
},
|
||||||
|
"features_panel": {
|
||||||
|
"chat": "الدردشة",
|
||||||
|
"gopher": "غوفر",
|
||||||
|
"media_proxy": "بروكسي الوسائط",
|
||||||
|
"scope_options": "",
|
||||||
|
"text_limit": "الحد الأقصى للنص",
|
||||||
|
"title": "الميّزات",
|
||||||
|
"who_to_follow": "للمتابعة"
|
||||||
|
},
|
||||||
|
"finder": {
|
||||||
|
"error_fetching_user": "خطأ أثناء جلب صفحة المستخدم",
|
||||||
|
"find_user": "البحث عن مستخدِم"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"apply": "تطبيق",
|
||||||
|
"submit": "إرسال"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"login": "تسجيل الدخول",
|
||||||
|
"logout": "الخروج",
|
||||||
|
"password": "الكلمة السرية",
|
||||||
|
"placeholder": "مثال lain",
|
||||||
|
"register": "انشاء حساب",
|
||||||
|
"username": "إسم المستخدم"
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"chat": "الدردشة المحلية",
|
||||||
|
"friend_requests": "طلبات المتابَعة",
|
||||||
|
"mentions": "الإشارات",
|
||||||
|
"public_tl": "الخيط الزمني العام",
|
||||||
|
"timeline": "الخيط الزمني",
|
||||||
|
"twkn": "كافة الشبكة المعروفة"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"broken_favorite": "منشور مجهول، جارٍ البحث عنه…",
|
||||||
|
"favorited_you": "أعجِب بمنشورك",
|
||||||
|
"followed_you": "يُتابعك",
|
||||||
|
"load_older": "تحميل الإشعارات الأقدم",
|
||||||
|
"notifications": "الإخطارات",
|
||||||
|
"read": "مقروء!",
|
||||||
|
"repeated_you": "شارَك منشورك"
|
||||||
|
},
|
||||||
|
"post_status": {
|
||||||
|
"account_not_locked_warning": "",
|
||||||
|
"account_not_locked_warning_link": "مقفل",
|
||||||
|
"attachments_sensitive": "اعتبر المرفقات كلها كمحتوى حساس",
|
||||||
|
"content_type": {
|
||||||
|
"text/plain": "نص صافٍ"
|
||||||
},
|
},
|
||||||
"features_panel": {
|
"content_warning": "الموضوع (اختياري)",
|
||||||
"chat": "الدردشة",
|
"default": "وصلت للتوّ إلى لوس أنجلس.",
|
||||||
"gopher": "غوفر",
|
"direct_warning": "",
|
||||||
"media_proxy": "بروكسي الوسائط",
|
"posting": "النشر",
|
||||||
"scope_options": "",
|
"scope": {
|
||||||
"text_limit": "الحد الأقصى للنص",
|
"direct": "",
|
||||||
"title": "الميّزات",
|
"private": "",
|
||||||
"who_to_follow": "للمتابعة"
|
"public": "علني - يُنشر على الخيوط الزمنية العمومية",
|
||||||
},
|
"unlisted": "غير مُدرَج - لا يُنشَر على الخيوط الزمنية العمومية"
|
||||||
"finder": {
|
}
|
||||||
"error_fetching_user": "خطأ أثناء جلب صفحة المستخدم",
|
},
|
||||||
"find_user": "البحث عن مستخدِم"
|
"registration": {
|
||||||
},
|
"bio": "السيرة الذاتية",
|
||||||
"general": {
|
"email": "عنوان البريد الإلكتروني",
|
||||||
"apply": "تطبيق",
|
"fullname": "الإسم المعروض",
|
||||||
"submit": "إرسال"
|
"password_confirm": "تأكيد الكلمة السرية",
|
||||||
},
|
"registration": "التسجيل",
|
||||||
"login": {
|
"token": "رمز الدعوة"
|
||||||
"login": "تسجيل الدخول",
|
},
|
||||||
"logout": "الخروج",
|
"settings": {
|
||||||
"password": "الكلمة السرية",
|
"attachmentRadius": "المُرفَقات",
|
||||||
"placeholder": "مثال lain",
|
"attachments": "المُرفَقات",
|
||||||
"register": "انشاء حساب",
|
"autoload": "",
|
||||||
"username": "إسم المستخدم"
|
"avatar": "الصورة الرمزية",
|
||||||
},
|
"avatarAltRadius": "الصور الرمزية (الإشعارات)",
|
||||||
"nav": {
|
"avatarRadius": "الصور الرمزية",
|
||||||
"chat": "الدردشة المحلية",
|
"background": "الخلفية",
|
||||||
"friend_requests": "طلبات المتابَعة",
|
"bio": "السيرة الذاتية",
|
||||||
"mentions": "الإشارات",
|
"btnRadius": "الأزرار",
|
||||||
"public_tl": "الخيط الزمني العام",
|
"cBlue": "أزرق (الرد، المتابَعة)",
|
||||||
"timeline": "الخيط الزمني",
|
"cGreen": "أخضر (إعادة النشر)",
|
||||||
"twkn": "كافة الشبكة المعروفة"
|
"cOrange": "برتقالي (مفضلة)",
|
||||||
},
|
"cRed": "أحمر (إلغاء)",
|
||||||
"notifications": {
|
"change_password": "تغيير كلمة السر",
|
||||||
"broken_favorite": "منشور مجهول، جارٍ البحث عنه…",
|
"change_password_error": "وقع هناك خلل أثناء تعديل كلمتك السرية.",
|
||||||
"favorited_you": "أعجِب بمنشورك",
|
"changed_password": "تم تغيير كلمة المرور بنجاح!",
|
||||||
"followed_you": "يُتابعك",
|
"collapse_subject": "",
|
||||||
"load_older": "تحميل الإشعارات الأقدم",
|
"confirm_new_password": "تأكيد كلمة السر الجديدة",
|
||||||
"notifications": "الإخطارات",
|
"current_avatar": "صورتك الرمزية الحالية",
|
||||||
"read": "مقروء!",
|
"current_password": "كلمة السر الحالية",
|
||||||
"repeated_you": "شارَك منشورك"
|
"current_profile_banner": "الرأسية الحالية لصفحتك الشخصية",
|
||||||
},
|
"data_import_export_tab": "تصدير واستيراد البيانات",
|
||||||
"post_status": {
|
"default_vis": "أسلوب العرض الافتراضي",
|
||||||
"account_not_locked_warning": "",
|
"delete_account": "حذف الحساب",
|
||||||
"account_not_locked_warning_link": "مقفل",
|
"delete_account_description": "حذف حسابك و كافة منشوراتك نهائيًا.",
|
||||||
"attachments_sensitive": "اعتبر المرفقات كلها كمحتوى حساس",
|
"delete_account_error": "",
|
||||||
"content_type": {
|
"delete_account_instructions": "يُرجى إدخال كلمتك السرية أدناه لتأكيد عملية حذف الحساب.",
|
||||||
"text/plain": "نص صافٍ"
|
"export_theme": "حفظ النموذج",
|
||||||
},
|
"filtering": "التصفية",
|
||||||
"content_warning": "الموضوع (اختياري)",
|
"filtering_explanation": "سيتم إخفاء كافة المنشورات التي تحتوي على هذه الكلمات، كلمة واحدة في كل سطر",
|
||||||
"default": "وصلت للتوّ إلى لوس أنجلس.",
|
"follow_export": "تصدير الاشتراكات",
|
||||||
"direct_warning": "",
|
"follow_export_button": "تصدير الاشتراكات كملف csv",
|
||||||
"posting": "النشر",
|
"follow_export_processing": "التصدير جارٍ، سوف يُطلَب منك تنزيل ملفك بعد حين",
|
||||||
"scope": {
|
"follow_import": "استيراد الاشتراكات",
|
||||||
"direct": "",
|
"follow_import_error": "خطأ أثناء استيراد المتابِعين",
|
||||||
"private": "",
|
"follows_imported": "",
|
||||||
"public": "علني - يُنشر على الخيوط الزمنية العمومية",
|
"foreground": "الأمامية",
|
||||||
"unlisted": "غير مُدرَج - لا يُنشَر على الخيوط الزمنية العمومية"
|
"general": "الإعدادات العامة",
|
||||||
}
|
"hide_attachments_in_convo": "إخفاء المرفقات على المحادثات",
|
||||||
},
|
"hide_attachments_in_tl": "إخفاء المرفقات على الخيط الزمني",
|
||||||
"registration": {
|
"hide_post_stats": "",
|
||||||
"bio": "السيرة الذاتية",
|
"hide_user_stats": "",
|
||||||
"email": "عنوان البريد الإلكتروني",
|
"import_followers_from_a_csv_file": "",
|
||||||
"fullname": "الإسم المعروض",
|
"import_theme": "تحميل نموذج",
|
||||||
"password_confirm": "تأكيد الكلمة السرية",
|
"inputRadius": "",
|
||||||
"registration": "التسجيل",
|
"instance_default": "",
|
||||||
"token": "رمز الدعوة"
|
"interfaceLanguage": "لغة الواجهة",
|
||||||
},
|
"invalid_theme_imported": "",
|
||||||
"settings": {
|
"limited_availability": "غير متوفر على متصفحك",
|
||||||
"attachmentRadius": "المُرفَقات",
|
"links": "الروابط",
|
||||||
"attachments": "المُرفَقات",
|
"lock_account_description": "",
|
||||||
"autoload": "",
|
"loop_video": "",
|
||||||
"avatar": "الصورة الرمزية",
|
"loop_video_silent_only": "",
|
||||||
"avatarAltRadius": "الصور الرمزية (الإشعارات)",
|
"name": "الاسم",
|
||||||
"avatarRadius": "الصور الرمزية",
|
"name_bio": "الاسم والسيرة الذاتية",
|
||||||
"background": "الخلفية",
|
"new_password": "كلمة السر الجديدة",
|
||||||
"bio": "السيرة الذاتية",
|
"no_rich_text_description": "",
|
||||||
"btnRadius": "الأزرار",
|
"notification_visibility": "نوع الإشعارات التي تريد عرضها",
|
||||||
"cBlue": "أزرق (الرد، المتابَعة)",
|
"notification_visibility_follows": "يتابع",
|
||||||
"cGreen": "أخضر (إعادة النشر)",
|
"notification_visibility_likes": "الإعجابات",
|
||||||
"cOrange": "برتقالي (مفضلة)",
|
"notification_visibility_mentions": "الإشارات",
|
||||||
"cRed": "أحمر (إلغاء)",
|
"notification_visibility_repeats": "",
|
||||||
"change_password": "تغيير كلمة السر",
|
"nsfw_clickthrough": "",
|
||||||
"change_password_error": "وقع هناك خلل أثناء تعديل كلمتك السرية.",
|
"oauth_tokens": "رموز OAuth",
|
||||||
"changed_password": "تم تغيير كلمة المرور بنجاح!",
|
"token": "رمز",
|
||||||
"collapse_subject": "",
|
"refresh_token": "رمز التحديث",
|
||||||
"confirm_new_password": "تأكيد كلمة السر الجديدة",
|
"valid_until": "صالح حتى",
|
||||||
"current_avatar": "صورتك الرمزية الحالية",
|
"revoke_token": "سحب",
|
||||||
"current_password": "كلمة السر الحالية",
|
"panelRadius": "",
|
||||||
"current_profile_banner": "الرأسية الحالية لصفحتك الشخصية",
|
"pause_on_unfocused": "",
|
||||||
"data_import_export_tab": "تصدير واستيراد البيانات",
|
"presets": "النماذج",
|
||||||
"default_vis": "أسلوب العرض الافتراضي",
|
"profile_background": "خلفية الصفحة الشخصية",
|
||||||
"delete_account": "حذف الحساب",
|
"profile_banner": "رأسية الصفحة الشخصية",
|
||||||
"delete_account_description": "حذف حسابك و كافة منشوراتك نهائيًا.",
|
"profile_tab": "الملف الشخصي",
|
||||||
"delete_account_error": "",
|
"radii_help": "",
|
||||||
"delete_account_instructions": "يُرجى إدخال كلمتك السرية أدناه لتأكيد عملية حذف الحساب.",
|
"replies_in_timeline": "الردود على الخيط الزمني",
|
||||||
"export_theme": "حفظ النموذج",
|
"reply_link_preview": "",
|
||||||
"filtering": "التصفية",
|
"reply_visibility_all": "عرض كافة الردود",
|
||||||
"filtering_explanation": "سيتم إخفاء كافة المنشورات التي تحتوي على هذه الكلمات، كلمة واحدة في كل سطر",
|
"reply_visibility_following": "",
|
||||||
"follow_export": "تصدير الاشتراكات",
|
"reply_visibility_self": "",
|
||||||
"follow_export_button": "تصدير الاشتراكات كملف csv",
|
"saving_err": "خطأ أثناء حفظ الإعدادات",
|
||||||
"follow_export_processing": "التصدير جارٍ، سوف يُطلَب منك تنزيل ملفك بعد حين",
|
"saving_ok": "تم حفظ الإعدادات",
|
||||||
"follow_import": "استيراد الاشتراكات",
|
"security_tab": "الأمان",
|
||||||
"follow_import_error": "خطأ أثناء استيراد المتابِعين",
|
"set_new_avatar": "اختيار صورة رمزية جديدة",
|
||||||
"follows_imported": "",
|
"set_new_profile_background": "اختيار خلفية جديدة للملف الشخصي",
|
||||||
"foreground": "الأمامية",
|
"set_new_profile_banner": "اختيار رأسية جديدة للصفحة الشخصية",
|
||||||
"general": "الإعدادات العامة",
|
"settings": "الإعدادات",
|
||||||
"hide_attachments_in_convo": "إخفاء المرفقات على المحادثات",
|
"stop_gifs": "",
|
||||||
"hide_attachments_in_tl": "إخفاء المرفقات على الخيط الزمني",
|
"streaming": "",
|
||||||
"hide_post_stats": "",
|
"text": "النص",
|
||||||
"hide_user_stats": "",
|
"theme": "المظهر",
|
||||||
"import_followers_from_a_csv_file": "",
|
"theme_help": "",
|
||||||
"import_theme": "تحميل نموذج",
|
"tooltipRadius": "",
|
||||||
"inputRadius": "",
|
"user_settings": "إعدادات المستخدم",
|
||||||
"instance_default": "",
|
"values": {
|
||||||
"interfaceLanguage": "لغة الواجهة",
|
"false": "لا",
|
||||||
"invalid_theme_imported": "",
|
"true": "نعم"
|
||||||
"limited_availability": "غير متوفر على متصفحك",
|
|
||||||
"links": "الروابط",
|
|
||||||
"lock_account_description": "",
|
|
||||||
"loop_video": "",
|
|
||||||
"loop_video_silent_only": "",
|
|
||||||
"name": "الاسم",
|
|
||||||
"name_bio": "الاسم والسيرة الذاتية",
|
|
||||||
"new_password": "كلمة السر الجديدة",
|
|
||||||
"no_rich_text_description": "",
|
|
||||||
"notification_visibility": "نوع الإشعارات التي تريد عرضها",
|
|
||||||
"notification_visibility_follows": "يتابع",
|
|
||||||
"notification_visibility_likes": "الإعجابات",
|
|
||||||
"notification_visibility_mentions": "الإشارات",
|
|
||||||
"notification_visibility_repeats": "",
|
|
||||||
"nsfw_clickthrough": "",
|
|
||||||
"oauth_tokens": "رموز OAuth",
|
|
||||||
"token": "رمز",
|
|
||||||
"refresh_token": "رمز التحديث",
|
|
||||||
"valid_until": "صالح حتى",
|
|
||||||
"revoke_token": "سحب",
|
|
||||||
"panelRadius": "",
|
|
||||||
"pause_on_unfocused": "",
|
|
||||||
"presets": "النماذج",
|
|
||||||
"profile_background": "خلفية الصفحة الشخصية",
|
|
||||||
"profile_banner": "رأسية الصفحة الشخصية",
|
|
||||||
"profile_tab": "الملف الشخصي",
|
|
||||||
"radii_help": "",
|
|
||||||
"replies_in_timeline": "الردود على الخيط الزمني",
|
|
||||||
"reply_link_preview": "",
|
|
||||||
"reply_visibility_all": "عرض كافة الردود",
|
|
||||||
"reply_visibility_following": "",
|
|
||||||
"reply_visibility_self": "",
|
|
||||||
"saving_err": "خطأ أثناء حفظ الإعدادات",
|
|
||||||
"saving_ok": "تم حفظ الإعدادات",
|
|
||||||
"security_tab": "الأمان",
|
|
||||||
"set_new_avatar": "اختيار صورة رمزية جديدة",
|
|
||||||
"set_new_profile_background": "اختيار خلفية جديدة للملف الشخصي",
|
|
||||||
"set_new_profile_banner": "اختيار رأسية جديدة للصفحة الشخصية",
|
|
||||||
"settings": "الإعدادات",
|
|
||||||
"stop_gifs": "",
|
|
||||||
"streaming": "",
|
|
||||||
"text": "النص",
|
|
||||||
"theme": "المظهر",
|
|
||||||
"theme_help": "",
|
|
||||||
"tooltipRadius": "",
|
|
||||||
"user_settings": "إعدادات المستخدم",
|
|
||||||
"values": {
|
|
||||||
"false": "لا",
|
|
||||||
"true": "نعم"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"timeline": {
|
|
||||||
"collapse": "",
|
|
||||||
"conversation": "محادثة",
|
|
||||||
"error_fetching": "خطأ أثناء جلب التحديثات",
|
|
||||||
"load_older": "تحميل المنشورات القديمة",
|
|
||||||
"no_retweet_hint": "",
|
|
||||||
"repeated": "",
|
|
||||||
"show_new": "عرض الجديد",
|
|
||||||
"up_to_date": "تم تحديثه"
|
|
||||||
},
|
|
||||||
"user_card": {
|
|
||||||
"approve": "قبول",
|
|
||||||
"block": "حظر",
|
|
||||||
"blocked": "تم حظره!",
|
|
||||||
"deny": "رفض",
|
|
||||||
"follow": "اتبع",
|
|
||||||
"followees": "",
|
|
||||||
"followers": "مُتابِعون",
|
|
||||||
"following": "",
|
|
||||||
"follows_you": "يتابعك!",
|
|
||||||
"mute": "كتم",
|
|
||||||
"muted": "تم كتمه",
|
|
||||||
"per_day": "في اليوم",
|
|
||||||
"remote_follow": "مُتابَعة عن بُعد",
|
|
||||||
"statuses": "المنشورات"
|
|
||||||
},
|
|
||||||
"user_profile": {
|
|
||||||
"timeline_title": "الخيط الزمني للمستخدم"
|
|
||||||
},
|
|
||||||
"who_to_follow": {
|
|
||||||
"more": "المزيد",
|
|
||||||
"who_to_follow": "للمتابعة"
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"timeline": {
|
||||||
|
"collapse": "",
|
||||||
|
"conversation": "محادثة",
|
||||||
|
"error_fetching": "خطأ أثناء جلب التحديثات",
|
||||||
|
"load_older": "تحميل المنشورات القديمة",
|
||||||
|
"no_retweet_hint": "",
|
||||||
|
"repeated": "",
|
||||||
|
"show_new": "عرض الجديد",
|
||||||
|
"up_to_date": "تم تحديثه"
|
||||||
|
},
|
||||||
|
"user_card": {
|
||||||
|
"approve": "قبول",
|
||||||
|
"block": "حظر",
|
||||||
|
"blocked": "تم حظره!",
|
||||||
|
"deny": "رفض",
|
||||||
|
"follow": "اتبع",
|
||||||
|
"followees": "",
|
||||||
|
"followers": "مُتابِعون",
|
||||||
|
"following": "",
|
||||||
|
"follows_you": "يتابعك!",
|
||||||
|
"mute": "كتم",
|
||||||
|
"muted": "تم كتمه",
|
||||||
|
"per_day": "في اليوم",
|
||||||
|
"remote_follow": "مُتابَعة عن بُعد",
|
||||||
|
"statuses": "المنشورات"
|
||||||
|
},
|
||||||
|
"user_profile": {
|
||||||
|
"timeline_title": "الخيط الزمني للمستخدم"
|
||||||
|
},
|
||||||
|
"who_to_follow": {
|
||||||
|
"more": "المزيد",
|
||||||
|
"who_to_follow": "للمتابعة"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,323 +1,489 @@
|
|||||||
{
|
{
|
||||||
"general": {
|
"general": {
|
||||||
"submit": "Invia",
|
"submit": "Invia",
|
||||||
"apply": "Applica",
|
"apply": "Applica",
|
||||||
"more": "Altro",
|
"more": "Altro",
|
||||||
"generic_error": "Errore",
|
"generic_error": "Errore",
|
||||||
"optional": "facoltativo",
|
"optional": "facoltativo",
|
||||||
"show_more": "Mostra tutto",
|
"show_more": "Mostra tutto",
|
||||||
"show_less": "Ripiega",
|
"show_less": "Ripiega",
|
||||||
"dismiss": "Chiudi",
|
"dismiss": "Chiudi",
|
||||||
"cancel": "Annulla",
|
"cancel": "Annulla",
|
||||||
"disable": "Disabilita",
|
"disable": "Disabilita",
|
||||||
"enable": "Abilita",
|
"enable": "Abilita",
|
||||||
"confirm": "Conferma",
|
"confirm": "Conferma",
|
||||||
"verify": "Verifica"
|
"verify": "Verifica"
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"mentions": "Menzioni",
|
||||||
|
"public_tl": "Sequenza pubblica",
|
||||||
|
"timeline": "Sequenza personale",
|
||||||
|
"twkn": "Sequenza globale",
|
||||||
|
"chat": "Chat della stanza",
|
||||||
|
"friend_requests": "Vogliono seguirti",
|
||||||
|
"about": "Informazioni",
|
||||||
|
"administration": "Amministrazione",
|
||||||
|
"back": "Indietro",
|
||||||
|
"interactions": "Interazioni",
|
||||||
|
"dms": "Messaggi diretti",
|
||||||
|
"user_search": "Ricerca utenti",
|
||||||
|
"search": "Ricerca",
|
||||||
|
"who_to_follow": "Chi seguire",
|
||||||
|
"preferences": "Preferenze"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"followed_you": "ti segue",
|
||||||
|
"notifications": "Notifiche",
|
||||||
|
"read": "Letto!",
|
||||||
|
"broken_favorite": "Stato sconosciuto, lo sto cercando…",
|
||||||
|
"favorited_you": "ha gradito il tuo messaggio",
|
||||||
|
"load_older": "Carica notifiche precedenti",
|
||||||
|
"repeated_you": "ha condiviso il tuo messaggio",
|
||||||
|
"follow_request": "vuole seguirti",
|
||||||
|
"no_more_notifications": "Fine delle notifiche",
|
||||||
|
"migrated_to": "è migrato verso",
|
||||||
|
"reacted_with": "ha reagito con {0}"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"attachments": "Allegati",
|
||||||
|
"autoload": "Abilita caricamento automatico quando raggiungi il fondo pagina",
|
||||||
|
"avatar": "Icona utente",
|
||||||
|
"bio": "Introduzione",
|
||||||
|
"current_avatar": "La tua icona attuale",
|
||||||
|
"current_profile_banner": "Il tuo stendardo attuale",
|
||||||
|
"filtering": "Filtri",
|
||||||
|
"filtering_explanation": "Tutti i post contenenti queste parole saranno silenziati, una per riga",
|
||||||
|
"hide_attachments_in_convo": "Nascondi gli allegati presenti nelle conversazioni",
|
||||||
|
"hide_attachments_in_tl": "Nascondi gli allegati presenti nelle sequenze",
|
||||||
|
"name": "Nome",
|
||||||
|
"name_bio": "Nome ed introduzione",
|
||||||
|
"nsfw_clickthrough": "Fai click per visualizzare gli allegati offuscati",
|
||||||
|
"profile_background": "Sfondo della tua pagina",
|
||||||
|
"profile_banner": "Stendardo del tuo profilo",
|
||||||
|
"reply_link_preview": "Visualizza le risposte al passaggio del cursore",
|
||||||
|
"set_new_avatar": "Scegli una nuova icona",
|
||||||
|
"set_new_profile_background": "Scegli un nuovo sfondo per la tua pagina",
|
||||||
|
"set_new_profile_banner": "Scegli un nuovo stendardo per il tuo profilo",
|
||||||
|
"settings": "Impostazioni",
|
||||||
|
"theme": "Tema",
|
||||||
|
"user_settings": "Impostazioni Utente",
|
||||||
|
"attachmentRadius": "Allegati",
|
||||||
|
"avatarAltRadius": "Icone utente (Notifiche)",
|
||||||
|
"avatarRadius": "Icone utente",
|
||||||
|
"background": "Sfondo",
|
||||||
|
"btnRadius": "Pulsanti",
|
||||||
|
"cBlue": "Blu (risposte, seguire)",
|
||||||
|
"cGreen": "Verde (ripeti)",
|
||||||
|
"cOrange": "Arancione (gradire)",
|
||||||
|
"cRed": "Rosso (annulla)",
|
||||||
|
"change_password": "Cambia password",
|
||||||
|
"change_password_error": "C'è stato un problema durante il cambiamento della password.",
|
||||||
|
"changed_password": "Password cambiata correttamente!",
|
||||||
|
"collapse_subject": "Ripiega messaggi con Oggetto",
|
||||||
|
"confirm_new_password": "Conferma la nuova password",
|
||||||
|
"current_password": "La tua password attuale",
|
||||||
|
"data_import_export_tab": "Importa o esporta dati",
|
||||||
|
"default_vis": "Visibilità predefinita dei messaggi",
|
||||||
|
"delete_account": "Elimina profilo",
|
||||||
|
"delete_account_description": "Elimina definitivamente i tuoi dati e disattiva il tuo profilo.",
|
||||||
|
"delete_account_error": "C'è stato un problema durante l'eliminazione del tuo profilo. Se il problema persiste contatta l'amministratore della tua stanza.",
|
||||||
|
"delete_account_instructions": "Digita la tua password nel campo sottostante per confermare l'eliminazione del tuo profilo.",
|
||||||
|
"export_theme": "Salva impostazioni",
|
||||||
|
"follow_export": "Esporta la lista di chi segui",
|
||||||
|
"follow_export_button": "Esporta la lista di chi segui in un file CSV",
|
||||||
|
"follow_export_processing": "Sto elaborando, presto ti sarà chiesto di scaricare il tuo file",
|
||||||
|
"follow_import": "Importa la lista di chi segui",
|
||||||
|
"follow_import_error": "Errore nell'importazione della lista di chi segui",
|
||||||
|
"follows_imported": "Importazione riuscita! L'elaborazione richiederà un po' di tempo.",
|
||||||
|
"foreground": "Primo piano",
|
||||||
|
"general": "Generale",
|
||||||
|
"hide_post_stats": "Nascondi statistiche dei messaggi (es. il numero di preferenze)",
|
||||||
|
"hide_user_stats": "Nascondi statistiche dell'utente (es. il numero dei tuoi seguaci)",
|
||||||
|
"import_followers_from_a_csv_file": "Importa una lista di chi segui da un file CSV",
|
||||||
|
"import_theme": "Carica impostazioni",
|
||||||
|
"inputRadius": "Campi di testo",
|
||||||
|
"instance_default": "(predefinito: {value})",
|
||||||
|
"interfaceLanguage": "Lingua dell'interfaccia",
|
||||||
|
"invalid_theme_imported": "Il file selezionato non è un tema supportato da Pleroma. Il tuo tema non è stato modificato.",
|
||||||
|
"limited_availability": "Non disponibile nel tuo browser",
|
||||||
|
"links": "Collegamenti",
|
||||||
|
"lock_account_description": "Limita il tuo account solo a seguaci approvati",
|
||||||
|
"loop_video": "Riproduci video in ciclo continuo",
|
||||||
|
"loop_video_silent_only": "Riproduci solo video senza audio in ciclo continuo (es. le \"gif\" di Mastodon)",
|
||||||
|
"new_password": "Nuova password",
|
||||||
|
"notification_visibility": "Tipi di notifiche da mostrare",
|
||||||
|
"notification_visibility_follows": "Nuove persone ti seguono",
|
||||||
|
"notification_visibility_likes": "Preferiti",
|
||||||
|
"notification_visibility_mentions": "Menzioni",
|
||||||
|
"notification_visibility_repeats": "Condivisioni",
|
||||||
|
"no_rich_text_description": "Togli la formattazione del testo da tutti i messaggi",
|
||||||
|
"oauth_tokens": "Token OAuth",
|
||||||
|
"token": "Token",
|
||||||
|
"refresh_token": "Aggiorna token",
|
||||||
|
"valid_until": "Valido fino a",
|
||||||
|
"revoke_token": "Revoca",
|
||||||
|
"panelRadius": "Pannelli",
|
||||||
|
"pause_on_unfocused": "Interrompi l'aggiornamento continuo mentre la scheda è in secondo piano",
|
||||||
|
"presets": "Valori predefiniti",
|
||||||
|
"profile_tab": "Profilo",
|
||||||
|
"radii_help": "Imposta il raggio degli angoli (in pixel)",
|
||||||
|
"replies_in_timeline": "Risposte nella sequenza personale",
|
||||||
|
"reply_visibility_all": "Mostra tutte le risposte",
|
||||||
|
"reply_visibility_following": "Mostra solo le risposte rivolte a me o agli utenti che seguo",
|
||||||
|
"reply_visibility_self": "Mostra solo risposte rivolte a me",
|
||||||
|
"saving_err": "Errore nel salvataggio delle impostazioni",
|
||||||
|
"saving_ok": "Impostazioni salvate",
|
||||||
|
"security_tab": "Sicurezza",
|
||||||
|
"stop_gifs": "Riproduci GIF al passaggio del cursore",
|
||||||
|
"streaming": "Mostra automaticamente i nuovi messaggi quando sei in cima alla pagina",
|
||||||
|
"text": "Testo",
|
||||||
|
"theme_help": "Usa codici colore esadecimali (#rrggbb) per personalizzare il tuo schema di colori.",
|
||||||
|
"tooltipRadius": "Suggerimenti/avvisi",
|
||||||
|
"values": {
|
||||||
|
"false": "no",
|
||||||
|
"true": "sì"
|
||||||
},
|
},
|
||||||
"nav": {
|
"avatar_size_instruction": "La taglia minima per l'icona personale è 150x150 pixel.",
|
||||||
"mentions": "Menzioni",
|
"domain_mutes": "Domini",
|
||||||
"public_tl": "Sequenza pubblica",
|
"discoverable": "Permetti la scoperta di questo profilo da servizi di ricerca ed altro",
|
||||||
"timeline": "Sequenza personale",
|
"composing": "Composizione",
|
||||||
"twkn": "Sequenza globale",
|
"changed_email": "Email cambiata con successo!",
|
||||||
"chat": "Chat della stanza",
|
"change_email_error": "C'è stato un problema nel cambiare la tua email.",
|
||||||
"friend_requests": "Vogliono seguirti",
|
"change_email": "Cambia email",
|
||||||
"about": "Informazioni",
|
"blocks_tab": "Bloccati",
|
||||||
"administration": "Amministrazione",
|
"blocks_imported": "Blocchi importati! Saranno elaborati a breve.",
|
||||||
"back": "Indietro",
|
"block_import_error": "Errore nell'importazione",
|
||||||
"interactions": "Interazioni",
|
"block_import": "Importa blocchi",
|
||||||
"dms": "Messaggi diretti",
|
"block_export_button": "Esporta i tuoi blocchi in un file CSV",
|
||||||
"user_search": "Ricerca utenti",
|
"block_export": "Esporta blocchi",
|
||||||
"search": "Ricerca",
|
"allow_following_move": "Consenti",
|
||||||
"who_to_follow": "Chi seguire",
|
"mfa": {
|
||||||
"preferences": "Preferenze"
|
"verify": {
|
||||||
|
"desc": "Per abilitare l'autenticazione bifattoriale, inserisci il codice fornito dalla tua applicazione:"
|
||||||
|
},
|
||||||
|
"scan": {
|
||||||
|
"secret_code": "Codice",
|
||||||
|
"desc": "Con la tua applicazione bifattoriale, acquisisci questo QR o inserisci il codice manualmente:",
|
||||||
|
"title": "Acquisisci"
|
||||||
|
},
|
||||||
|
"authentication_methods": "Metodi di accesso",
|
||||||
|
"recovery_codes_warning": "Appuntati i codici o salvali in un posto sicuro, altrimenti rischi di non rivederli mai più. Se perderai l'accesso sia alla tua applicazione bifattoriale che ai codici di recupero non potrai più accedere al tuo profilo.",
|
||||||
|
"waiting_a_recovery_codes": "Ricevo codici di recupero…",
|
||||||
|
"recovery_codes": "Codici di recupero.",
|
||||||
|
"warning_of_generate_new_codes": "Alla generazione di nuovi codici di recupero, quelli vecchi saranno disattivati.",
|
||||||
|
"generate_new_recovery_codes": "Genera nuovi codici di recupero",
|
||||||
|
"title": "Accesso bifattoriale",
|
||||||
|
"confirm_and_enable": "Conferma ed abilita OTP",
|
||||||
|
"wait_pre_setup_otp": "preimposto OTP",
|
||||||
|
"setup_otp": "Imposta OTP",
|
||||||
|
"otp": "OTP"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"enter_current_password_to_confirm": "Inserisci la tua password per identificarti",
|
||||||
"followed_you": "ti segue",
|
"security": "Sicurezza",
|
||||||
"notifications": "Notifiche",
|
"app_name": "Nome applicazione",
|
||||||
"read": "Letto!",
|
"style": {
|
||||||
"broken_favorite": "Stato sconosciuto, lo sto cercando...",
|
"switcher": {
|
||||||
"favorited_you": "ha gradito il tuo messaggio",
|
"help": {
|
||||||
"load_older": "Carica notifiche precedenti",
|
"older_version_imported": "Il tema importato è stato creato per una versione precedente dell'interfaccia.",
|
||||||
"repeated_you": "ha condiviso il tuo messaggio",
|
"future_version_imported": "Il tema importato è stato creato per una versione più recente dell'interfaccia.",
|
||||||
"follow_request": "vuole seguirti",
|
"v2_imported": "Il tema importato è stato creato per una vecchia interfaccia. Non tutto potrebbe essere come prima.",
|
||||||
"no_more_notifications": "Fine delle notifiche",
|
"upgraded_from_v2": "L'interfaccia è stata aggiornata, il tema potrebbe essere diverso da come lo intendevi.",
|
||||||
"migrated_to": "è migrato verso",
|
"migration_snapshot_ok": "Ho caricato l'anteprima del tema. Puoi provare a caricarne i contenuti.",
|
||||||
"reacted_with": "ha reagito con"
|
"fe_downgraded": "L'interfaccia è stata portata ad una versione precedente.",
|
||||||
},
|
"fe_upgraded": "Lo schema dei temi è stato aggiornato insieme all'interfaccia.",
|
||||||
"settings": {
|
"snapshot_missing": "Il tema non è provvisto di anteprima, quindi potrebbe essere diverso da come appare.",
|
||||||
"attachments": "Allegati",
|
"snapshot_present": "Tutti i valori sono sostituiti dall'anteprima del tema. Puoi invece caricare i suoi contenuti.",
|
||||||
"autoload": "Abilita caricamento automatico quando raggiungi il fondo pagina",
|
"snapshot_source_mismatch": "Conflitto di versione: probabilmente l'interfaccia è stata portata ad una versione precedente e poi aggiornata di nuovo. Se hai modificato il tema con una versione precedente dell'interfaccia, usa la vecchia versione del tema, altrimenti puoi usare la nuova.",
|
||||||
"avatar": "Icona utente",
|
"migration_napshot_gone": "Anteprima del tema non trovata, non tutto potrebbe essere come ricordi."
|
||||||
"bio": "Introduzione",
|
|
||||||
"current_avatar": "La tua icona attuale",
|
|
||||||
"current_profile_banner": "Il tuo stendardo attuale",
|
|
||||||
"filtering": "Filtri",
|
|
||||||
"filtering_explanation": "Tutti i post contenenti queste parole saranno silenziati, una per riga",
|
|
||||||
"hide_attachments_in_convo": "Nascondi gli allegati presenti nelle conversazioni",
|
|
||||||
"hide_attachments_in_tl": "Nascondi gli allegati presenti nelle sequenze",
|
|
||||||
"name": "Nome",
|
|
||||||
"name_bio": "Nome ed introduzione",
|
|
||||||
"nsfw_clickthrough": "Fai click per visualizzare gli allegati nascosti",
|
|
||||||
"profile_background": "Sfondo della tua pagina",
|
|
||||||
"profile_banner": "Stendardo del tuo profilo",
|
|
||||||
"reply_link_preview": "Visualizza le risposte al passaggio del cursore",
|
|
||||||
"set_new_avatar": "Scegli una nuova icona",
|
|
||||||
"set_new_profile_background": "Scegli un nuovo sfondo per la tua pagina",
|
|
||||||
"set_new_profile_banner": "Scegli un nuovo stendardo per il tuo profilo",
|
|
||||||
"settings": "Impostazioni",
|
|
||||||
"theme": "Tema",
|
|
||||||
"user_settings": "Impostazioni Utente",
|
|
||||||
"attachmentRadius": "Allegati",
|
|
||||||
"avatarAltRadius": "Icone utente (Notifiche)",
|
|
||||||
"avatarRadius": "Icone utente",
|
|
||||||
"background": "Sfondo",
|
|
||||||
"btnRadius": "Pulsanti",
|
|
||||||
"cBlue": "Blu (risposte, seguire)",
|
|
||||||
"cGreen": "Verde (ripeti)",
|
|
||||||
"cOrange": "Arancione (gradire)",
|
|
||||||
"cRed": "Rosso (annulla)",
|
|
||||||
"change_password": "Cambia password",
|
|
||||||
"change_password_error": "C'è stato un problema durante il cambiamento della password.",
|
|
||||||
"changed_password": "Password cambiata correttamente!",
|
|
||||||
"collapse_subject": "Ripiega messaggi con Oggetto",
|
|
||||||
"confirm_new_password": "Conferma la nuova password",
|
|
||||||
"current_password": "La tua password attuale",
|
|
||||||
"data_import_export_tab": "Importa o esporta dati",
|
|
||||||
"default_vis": "Visibilità predefinita dei messaggi",
|
|
||||||
"delete_account": "Elimina profilo",
|
|
||||||
"delete_account_description": "Elimina definitivamente i tuoi dati e disattiva il tuo profilo.",
|
|
||||||
"delete_account_error": "C'è stato un problema durante l'eliminazione del tuo profilo. Se il problema persiste contatta l'amministratore della tua stanza.",
|
|
||||||
"delete_account_instructions": "Digita la tua password nel campo sottostante per confermare l'eliminazione del tuo profilo.",
|
|
||||||
"export_theme": "Salva impostazioni",
|
|
||||||
"follow_export": "Esporta la lista di chi segui",
|
|
||||||
"follow_export_button": "Esporta la lista di chi segui in un file CSV",
|
|
||||||
"follow_export_processing": "Sto elaborando, presto ti sarà chiesto di scaricare il tuo file",
|
|
||||||
"follow_import": "Importa la lista di chi segui",
|
|
||||||
"follow_import_error": "Errore nell'importazione della lista di chi segui",
|
|
||||||
"follows_imported": "Importazione riuscita! L'elaborazione richiederà un po' di tempo.",
|
|
||||||
"foreground": "Primo piano",
|
|
||||||
"general": "Generale",
|
|
||||||
"hide_post_stats": "Nascondi statistiche dei messaggi (es. il numero di preferenze)",
|
|
||||||
"hide_user_stats": "Nascondi statistiche dell'utente (es. il numero dei tuoi seguaci)",
|
|
||||||
"import_followers_from_a_csv_file": "Importa una lista di chi segui da un file CSV",
|
|
||||||
"import_theme": "Carica impostazioni",
|
|
||||||
"inputRadius": "Campi di testo",
|
|
||||||
"instance_default": "(predefinito: {value})",
|
|
||||||
"interfaceLanguage": "Lingua dell'interfaccia",
|
|
||||||
"invalid_theme_imported": "Il file selezionato non è un tema supportato da Pleroma. Il tuo tema non è stato modificato.",
|
|
||||||
"limited_availability": "Non disponibile nel tuo browser",
|
|
||||||
"links": "Collegamenti",
|
|
||||||
"lock_account_description": "Limita il tuo account solo a seguaci approvati",
|
|
||||||
"loop_video": "Riproduci video in ciclo continuo",
|
|
||||||
"loop_video_silent_only": "Riproduci solo video senza audio in ciclo continuo (es. le \"gif\" di Mastodon)",
|
|
||||||
"new_password": "Nuova password",
|
|
||||||
"notification_visibility": "Tipi di notifiche da mostrare",
|
|
||||||
"notification_visibility_follows": "Nuove persone ti seguono",
|
|
||||||
"notification_visibility_likes": "Preferiti",
|
|
||||||
"notification_visibility_mentions": "Menzioni",
|
|
||||||
"notification_visibility_repeats": "Condivisioni",
|
|
||||||
"no_rich_text_description": "Togli la formattazione del testo da tutti i messaggi",
|
|
||||||
"oauth_tokens": "Token OAuth",
|
|
||||||
"token": "Token",
|
|
||||||
"refresh_token": "Aggiorna token",
|
|
||||||
"valid_until": "Valido fino a",
|
|
||||||
"revoke_token": "Revoca",
|
|
||||||
"panelRadius": "Pannelli",
|
|
||||||
"pause_on_unfocused": "Interrompi l'aggiornamento continuo mentre la scheda è in secondo piano",
|
|
||||||
"presets": "Valori predefiniti",
|
|
||||||
"profile_tab": "Profilo",
|
|
||||||
"radii_help": "Imposta il raggio degli angoli (in pixel)",
|
|
||||||
"replies_in_timeline": "Risposte nella sequenza personale",
|
|
||||||
"reply_visibility_all": "Mostra tutte le risposte",
|
|
||||||
"reply_visibility_following": "Mostra solo le risposte rivolte a me o agli utenti che seguo",
|
|
||||||
"reply_visibility_self": "Mostra solo risposte rivolte a me",
|
|
||||||
"saving_err": "Errore nel salvataggio delle impostazioni",
|
|
||||||
"saving_ok": "Impostazioni salvate",
|
|
||||||
"security_tab": "Sicurezza",
|
|
||||||
"stop_gifs": "Riproduci GIF al passaggio del cursore",
|
|
||||||
"streaming": "Mostra automaticamente i nuovi messaggi quando sei in cima alla pagina",
|
|
||||||
"text": "Testo",
|
|
||||||
"theme_help": "Usa codici colore esadecimali (#rrggbb) per personalizzare il tuo schema di colori.",
|
|
||||||
"tooltipRadius": "Descrizioni/avvisi",
|
|
||||||
"values": {
|
|
||||||
"false": "no",
|
|
||||||
"true": "sì"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"timeline": {
|
|
||||||
"error_fetching": "Errore nell'aggiornamento",
|
|
||||||
"load_older": "Carica messaggi più vecchi",
|
|
||||||
"show_new": "Mostra nuovi",
|
|
||||||
"up_to_date": "Aggiornato",
|
|
||||||
"collapse": "Riduci",
|
|
||||||
"conversation": "Conversazione",
|
|
||||||
"no_retweet_hint": "Il messaggio è diretto o solo per seguaci e non può essere condiviso",
|
|
||||||
"repeated": "condiviso"
|
|
||||||
},
|
|
||||||
"user_card": {
|
|
||||||
"follow": "Segui",
|
|
||||||
"followees": "Chi stai seguendo",
|
|
||||||
"followers": "Seguaci",
|
|
||||||
"following": "Seguìto!",
|
|
||||||
"follows_you": "Ti segue!",
|
|
||||||
"mute": "Silenzia",
|
|
||||||
"muted": "Silenziato",
|
|
||||||
"per_day": "al giorno",
|
|
||||||
"statuses": "Messaggi",
|
|
||||||
"approve": "Approva",
|
|
||||||
"block": "Blocca",
|
|
||||||
"blocked": "Bloccato!",
|
|
||||||
"deny": "Nega",
|
|
||||||
"remote_follow": "Segui da remoto"
|
|
||||||
},
|
|
||||||
"chat": {
|
|
||||||
"title": "Chat"
|
|
||||||
},
|
|
||||||
"features_panel": {
|
|
||||||
"chat": "Chat",
|
|
||||||
"gopher": "Gopher",
|
|
||||||
"media_proxy": "Proxy multimedia",
|
|
||||||
"scope_options": "Opzioni visibilità",
|
|
||||||
"text_limit": "Lunghezza massima",
|
|
||||||
"title": "Caratteristiche",
|
|
||||||
"who_to_follow": "Chi seguire"
|
|
||||||
},
|
|
||||||
"finder": {
|
|
||||||
"error_fetching_user": "Errore nel recupero dell'utente",
|
|
||||||
"find_user": "Trova utente"
|
|
||||||
},
|
|
||||||
"login": {
|
|
||||||
"login": "Accedi",
|
|
||||||
"logout": "Disconnettiti",
|
|
||||||
"password": "Password",
|
|
||||||
"placeholder": "es. Lupo Lucio",
|
|
||||||
"register": "Registrati",
|
|
||||||
"username": "Nome utente",
|
|
||||||
"description": "Accedi con OAuth",
|
|
||||||
"hint": "Accedi per partecipare alla discussione",
|
|
||||||
"authentication_code": "Codice di autenticazione",
|
|
||||||
"enter_recovery_code": "Inserisci un codice di recupero",
|
|
||||||
"enter_two_factor_code": "Inserisci un codice two-factor",
|
|
||||||
"recovery_code": "Codice di recupero",
|
|
||||||
"heading": {
|
|
||||||
"totp": "Autenticazione two-factor",
|
|
||||||
"recovery": "Recupero two-factor"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"post_status": {
|
|
||||||
"account_not_locked_warning": "Il tuo profilo non è {0}. Chiunque può seguirti e vedere i tuoi messaggi riservati ai tuoi seguaci.",
|
|
||||||
"account_not_locked_warning_link": "protetto",
|
|
||||||
"attachments_sensitive": "Nascondi gli allegati",
|
|
||||||
"content_type": {
|
|
||||||
"text/plain": "Testo normale"
|
|
||||||
},
|
},
|
||||||
"content_warning": "Oggetto (facoltativo)",
|
"use_source": "Nuova versione",
|
||||||
"default": "Sono appena atterrato a Fiumicino.",
|
"use_snapshot": "Versione precedente",
|
||||||
"direct_warning": "Questo post sarà visibile solo dagli utenti menzionati.",
|
"keep_as_is": "Mantieni tal quale",
|
||||||
"posting": "Sto pubblicando",
|
"load_theme": "Carica tema",
|
||||||
"scope": {
|
"clear_opacity": "Rimuovi opacità",
|
||||||
"direct": "Diretto - Visibile solo agli utenti menzionati",
|
"clear_all": "Azzera tutto",
|
||||||
"private": "Solo per seguaci - Visibile solo dai tuoi seguaci",
|
"reset": "Reimposta",
|
||||||
"public": "Pubblico - Visibile sulla sequenza pubblica",
|
"save_load_hint": "Le opzioni \"mantieni\" conservano le impostazioni correnti quando selezioni o carichi un tema, e le salvano quando ne esporti uno. Quando nessuna casella è selezionata, tutte le impostazioni correnti saranno salvate nel tema.",
|
||||||
"unlisted": "Non elencato - Non visibile sulla sequenza pubblica"
|
"keep_fonts": "Mantieni font",
|
||||||
}
|
"keep_roundness": "Mantieni vertici",
|
||||||
},
|
"keep_opacity": "Mantieni opacità",
|
||||||
"registration": {
|
"keep_shadows": "Mantieni ombre",
|
||||||
"bio": "Introduzione",
|
"keep_color": "Mantieni colori"
|
||||||
"email": "Email",
|
},
|
||||||
"fullname": "Nome visualizzato",
|
"common": {
|
||||||
"password_confirm": "Conferma password",
|
"opacity": "Opacità",
|
||||||
"registration": "Registrazione",
|
"color": "Colore"
|
||||||
"token": "Codice d'invito"
|
}
|
||||||
},
|
},
|
||||||
"user_profile": {
|
"enable_web_push_notifications": "Abilita notifiche web push",
|
||||||
"timeline_title": "Sequenza dell'Utente"
|
"fun": "Divertimento",
|
||||||
},
|
"notification_mutes": "Per non ricevere notifiche da uno specifico utente, zittiscilo.",
|
||||||
"who_to_follow": {
|
"notification_setting_privacy_option": "Nascondi mittente e contenuti delle notifiche push",
|
||||||
"more": "Altro",
|
"notification_setting_privacy": "Privacy",
|
||||||
"who_to_follow": "Chi seguire"
|
"notification_setting_followers": "Utenti che ti seguono",
|
||||||
},
|
"notification_setting_non_followers": "Utenti che non ti seguono",
|
||||||
"about": {
|
"notification_setting_non_follows": "Utenti che non segui",
|
||||||
"mrf": {
|
"notification_setting_follows": "Utenti che segui",
|
||||||
"federation": "Federazione",
|
"notification_setting": "Ricevi notifiche da:",
|
||||||
"keyword": {
|
"notification_setting_filters": "Filtri",
|
||||||
"reject": "Rifiuta",
|
"notifications": "Notifiche",
|
||||||
"replace": "Sostituisci",
|
"greentext": "Frecce da meme",
|
||||||
"is_replaced_by": "→",
|
"upload_a_photo": "Carica un'immagine",
|
||||||
"keyword_policies": "Regole per parole chiave",
|
"type_domains_to_mute": "Inserisci domini da zittire",
|
||||||
"ftl_removal": "Rimozione dalla sequenza globale"
|
"theme_help_v2_2": "Le icone dietro alcuni elementi sono indicatori del contrasto fra testo e sfondo, passaci sopra col puntatore per ulteriori informazioni. Se si usano delle trasparenze, questi indicatori mostrano il peggior caso possibile.",
|
||||||
},
|
"theme_help_v2_1": "Puoi anche forzare colore ed opacità di alcuni elementi selezionando la casella. Usa il pulsante \"Azzera\" per azzerare tutte le forzature.",
|
||||||
"simple": {
|
"useStreamingApiWarning": "(Sconsigliato, sperimentale, può saltare messaggi)",
|
||||||
"reject": "Rifiuta",
|
"useStreamingApi": "Ricevi messaggi e notifiche in tempo reale",
|
||||||
"accept": "Accetta",
|
"user_mutes": "Utenti",
|
||||||
"simple_policies": "Regole specifiche alla stanza",
|
"post_status_content_type": "Tipo di contenuto dei messaggi",
|
||||||
"accept_desc": "Questa stanza accetta messaggi solo dalle seguenti stanze:",
|
"subject_line_noop": "Non copiare",
|
||||||
"reject_desc": "Questa stanza non accetterà messaggi dalle stanze seguenti:",
|
"subject_line_mastodon": "Come in Mastodon: copia tal quale",
|
||||||
"quarantine": "Quarantena",
|
"subject_line_email": "Come nelle email: \"re: oggetto\"",
|
||||||
"quarantine_desc": "Questa stanza inoltrerà solo messaggi pubblici alle seguenti stanze:",
|
"subject_line_behavior": "Copia oggetto quando rispondi",
|
||||||
"ftl_removal": "Rimozione dalla sequenza globale",
|
"subject_input_always_show": "Mostra sempre il campo Oggetto",
|
||||||
"ftl_removal_desc": "Questa stanza rimuove le seguenti stanze dalla sequenza globale:",
|
"minimal_scopes_mode": "Riduci opzioni di visibilità",
|
||||||
"media_removal": "Rimozione multimedia",
|
"scope_copy": "Risposte ereditano la visibilità (messaggi privati lo fanno sempre)",
|
||||||
"media_removal_desc": "Questa istanza rimuove gli allegati dalle seguenti stanze:",
|
"search_user_to_mute": "Cerca utente da zittire",
|
||||||
"media_nsfw": "Allegati oscurati forzatamente",
|
"search_user_to_block": "Cerca utente da bloccare",
|
||||||
"media_nsfw_desc": "Questa stanza oscura gli allegati dei messaggi provenienti da queste stanze:"
|
"autohide_floating_post_button": "Nascondi automaticamente il pulsante di composizione (mobile)",
|
||||||
},
|
"show_moderator_badge": "Mostra l'insegna di moderatore sulla mia pagina",
|
||||||
"mrf_policies": "Regole RM abilitate",
|
"show_admin_badge": "Mostra l'insegna di amministratore sulla mia pagina",
|
||||||
"mrf_policies_desc": "Le regole RM cambiano il comportamento federativo della stanza. Vigono le seguenti regole:"
|
"hide_followers_count_description": "Non mostrare quanti seguaci ho",
|
||||||
},
|
"hide_follows_count_description": "Non mostrare quanti utenti seguo",
|
||||||
"staff": "Equipaggio"
|
"hide_followers_description": "Non mostrare i miei seguaci",
|
||||||
},
|
"hide_follows_description": "Non mostrare chi seguo",
|
||||||
"domain_mute_card": {
|
"no_mutes": "Nessun utente zittito",
|
||||||
"mute": "Zittisci",
|
"no_blocks": "Nessun utente bloccato",
|
||||||
"mute_progress": "Zittisco...",
|
"notification_visibility_emoji_reactions": "Reazioni",
|
||||||
"unmute": "Ascolta",
|
"notification_visibility_moves": "Migrazioni utenti",
|
||||||
"unmute_progress": "Procedo..."
|
"new_email": "Nuova email",
|
||||||
},
|
"use_contain_fit": "Non ritagliare le anteprime degli allegati",
|
||||||
"exporter": {
|
"play_videos_in_modal": "Riproduci video in un riquadro a sbalzo",
|
||||||
"export": "Esporta",
|
"mutes_tab": "Zittiti",
|
||||||
"processing": "In elaborazione, il tuo file sarà scaricabile a breve"
|
"interface": "Interfaccia",
|
||||||
},
|
"instance_default_simple": "(predefinito)",
|
||||||
"image_cropper": {
|
"checkboxRadius": "Caselle di selezione",
|
||||||
"crop_picture": "Ritaglia immagine",
|
"import_blocks_from_a_csv_file": "Importa blocchi da un file CSV",
|
||||||
"save": "Salva",
|
"hide_filtered_statuses": "Nascondi messaggi filtrati",
|
||||||
"save_without_cropping": "Salva senza ritagliare",
|
"use_one_click_nsfw": "Apri media offuscati con un solo click",
|
||||||
"cancel": "Annulla"
|
"preload_images": "Precarica immagini",
|
||||||
|
"hide_isp": "Nascondi pannello della stanza",
|
||||||
|
"max_thumbnails": "Numero massimo di anteprime per messaggio",
|
||||||
|
"hide_muted_posts": "Nascondi messaggi degli utenti zittiti",
|
||||||
|
"accent": "Accento",
|
||||||
|
"emoji_reactions_on_timeline": "Mostra emoji di reazione sulle sequenze",
|
||||||
|
"pad_emoji": "Affianca spazi agli emoji inseriti tramite selettore",
|
||||||
|
"notification_blocks": "Bloccando un utente non riceverai più le sue notifiche né lo seguirai più."
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"error_fetching": "Errore nell'aggiornamento",
|
||||||
|
"load_older": "Carica messaggi più vecchi",
|
||||||
|
"show_new": "Mostra nuovi",
|
||||||
|
"up_to_date": "Aggiornato",
|
||||||
|
"collapse": "Riduci",
|
||||||
|
"conversation": "Conversazione",
|
||||||
|
"no_retweet_hint": "Il messaggio è diretto o solo per seguaci e non può essere condiviso",
|
||||||
|
"repeated": "condiviso"
|
||||||
|
},
|
||||||
|
"user_card": {
|
||||||
|
"follow": "Segui",
|
||||||
|
"followees": "Chi stai seguendo",
|
||||||
|
"followers": "Seguaci",
|
||||||
|
"following": "Seguìto!",
|
||||||
|
"follows_you": "Ti segue!",
|
||||||
|
"mute": "Silenzia",
|
||||||
|
"muted": "Silenziato",
|
||||||
|
"per_day": "al giorno",
|
||||||
|
"statuses": "Messaggi",
|
||||||
|
"approve": "Approva",
|
||||||
|
"block": "Blocca",
|
||||||
|
"blocked": "Bloccato!",
|
||||||
|
"deny": "Nega",
|
||||||
|
"remote_follow": "Segui da remoto"
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"title": "Chat"
|
||||||
|
},
|
||||||
|
"features_panel": {
|
||||||
|
"chat": "Chat",
|
||||||
|
"gopher": "Gopher",
|
||||||
|
"media_proxy": "Proxy multimedia",
|
||||||
|
"scope_options": "Opzioni visibilità",
|
||||||
|
"text_limit": "Lunghezza massima",
|
||||||
|
"title": "Caratteristiche",
|
||||||
|
"who_to_follow": "Chi seguire"
|
||||||
|
},
|
||||||
|
"finder": {
|
||||||
|
"error_fetching_user": "Errore nel recupero dell'utente",
|
||||||
|
"find_user": "Trova utente"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"login": "Accedi",
|
||||||
|
"logout": "Disconnettiti",
|
||||||
|
"password": "Password",
|
||||||
|
"placeholder": "es. Lupo Lucio",
|
||||||
|
"register": "Registrati",
|
||||||
|
"username": "Nome utente",
|
||||||
|
"description": "Accedi con OAuth",
|
||||||
|
"hint": "Accedi per partecipare alla discussione",
|
||||||
|
"authentication_code": "Codice di autenticazione",
|
||||||
|
"enter_recovery_code": "Inserisci un codice di recupero",
|
||||||
|
"enter_two_factor_code": "Inserisci un codice two-factor",
|
||||||
|
"recovery_code": "Codice di recupero",
|
||||||
|
"heading": {
|
||||||
|
"totp": "Autenticazione two-factor",
|
||||||
|
"recovery": "Recupero two-factor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post_status": {
|
||||||
|
"account_not_locked_warning": "Il tuo profilo non è {0}. Chiunque può seguirti e vedere i tuoi messaggi riservati ai tuoi seguaci.",
|
||||||
|
"account_not_locked_warning_link": "protetto",
|
||||||
|
"attachments_sensitive": "Nascondi gli allegati",
|
||||||
|
"content_type": {
|
||||||
|
"text/plain": "Testo normale",
|
||||||
|
"text/bbcode": "BBCode",
|
||||||
|
"text/markdown": "Markdown",
|
||||||
|
"text/html": "HTML"
|
||||||
},
|
},
|
||||||
"importer": {
|
"content_warning": "Oggetto (facoltativo)",
|
||||||
"submit": "Invia",
|
"default": "Sono appena atterrato a Fiumicino.",
|
||||||
"success": "Importato.",
|
"direct_warning": "Questo post sarà visibile solo dagli utenti menzionati.",
|
||||||
"error": "L'importazione non è andata a buon fine."
|
"posting": "Sto pubblicando",
|
||||||
|
"scope": {
|
||||||
|
"direct": "Diretto - Visibile solo agli utenti menzionati",
|
||||||
|
"private": "Solo per seguaci - Visibile solo dai tuoi seguaci",
|
||||||
|
"public": "Pubblico - Visibile sulla sequenza pubblica",
|
||||||
|
"unlisted": "Non elencato - Non visibile sulla sequenza pubblica"
|
||||||
},
|
},
|
||||||
"media_modal": {
|
"scope_notice": {
|
||||||
"previous": "Precedente",
|
"unlisted": "Questo messaggio non sarà visibile sulla sequenza locale né su quella pubblica",
|
||||||
"next": "Prossimo"
|
"private": "Questo messaggio sarà visibile solo ai tuoi seguaci",
|
||||||
|
"public": "Questo messaggio sarà visibile a tutti"
|
||||||
},
|
},
|
||||||
"polls": {
|
"direct_warning_to_first_only": "Questo messaggio sarà visibile solo agli utenti menzionati all'inizio.",
|
||||||
"add_poll": "Sondaggio",
|
"direct_warning_to_all": "Questo messaggio sarà visibile a tutti i menzionati.",
|
||||||
"add_option": "Alternativa",
|
"new_status": "Nuovo messaggio"
|
||||||
"option": "Opzione",
|
},
|
||||||
"votes": "voti",
|
"registration": {
|
||||||
"vote": "Vota",
|
"bio": "Introduzione",
|
||||||
"type": "Tipo di sondaggio",
|
"email": "Email",
|
||||||
"single_choice": "Scelta singola",
|
"fullname": "Nome visualizzato",
|
||||||
"multiple_choices": "Scelta multipla",
|
"password_confirm": "Conferma password",
|
||||||
"expiry": "Scadenza",
|
"registration": "Registrazione",
|
||||||
"expires_in": "Scade fra {0}",
|
"token": "Codice d'invito",
|
||||||
"expired": "Scaduto {0} fa",
|
"validations": {
|
||||||
"not_enough_options": "Aggiungi altre risposte"
|
"password_confirmation_match": "dovrebbe essere uguale alla password",
|
||||||
|
"password_confirmation_required": "non può essere vuoto",
|
||||||
|
"password_required": "non può essere vuoto",
|
||||||
|
"email_required": "non può essere vuoto",
|
||||||
|
"fullname_required": "non può essere vuoto",
|
||||||
|
"username_required": "non può essere vuoto"
|
||||||
},
|
},
|
||||||
"interactions": {
|
"bio_placeholder": "es.\nCiao, sono Lupo Lucio.\nSono un lupo fantastico che vive nel Fantabosco. Forse mi hai visto alla Melevisione.",
|
||||||
"favs_repeats": "Condivisi e preferiti"
|
"fullname_placeholder": "es. Lupo Lucio",
|
||||||
|
"username_placeholder": "es. mister_wolf",
|
||||||
|
"new_captcha": "Clicca l'immagine per avere un altro captcha",
|
||||||
|
"captcha": "CAPTCHA"
|
||||||
|
},
|
||||||
|
"user_profile": {
|
||||||
|
"timeline_title": "Sequenza dell'Utente"
|
||||||
|
},
|
||||||
|
"who_to_follow": {
|
||||||
|
"more": "Altro",
|
||||||
|
"who_to_follow": "Chi seguire"
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"mrf": {
|
||||||
|
"federation": "Federazione",
|
||||||
|
"keyword": {
|
||||||
|
"reject": "Rifiuta",
|
||||||
|
"replace": "Sostituisci",
|
||||||
|
"is_replaced_by": "→",
|
||||||
|
"keyword_policies": "Regole per parole chiave",
|
||||||
|
"ftl_removal": "Rimozione dalla sequenza globale"
|
||||||
|
},
|
||||||
|
"simple": {
|
||||||
|
"reject": "Rifiuta",
|
||||||
|
"accept": "Accetta",
|
||||||
|
"simple_policies": "Regole specifiche alla stanza",
|
||||||
|
"accept_desc": "Questa stanza accetta messaggi solo dalle seguenti stanze:",
|
||||||
|
"reject_desc": "Questa stanza non accetterà messaggi dalle stanze seguenti:",
|
||||||
|
"quarantine": "Quarantena",
|
||||||
|
"quarantine_desc": "Questa stanza inoltrerà solo messaggi pubblici alle seguenti stanze:",
|
||||||
|
"ftl_removal": "Rimozione dalla sequenza globale",
|
||||||
|
"ftl_removal_desc": "Questa stanza rimuove le seguenti stanze dalla sequenza globale:",
|
||||||
|
"media_removal": "Rimozione multimedia",
|
||||||
|
"media_removal_desc": "Questa istanza rimuove gli allegati dalle seguenti stanze:",
|
||||||
|
"media_nsfw": "Allegati oscurati forzatamente",
|
||||||
|
"media_nsfw_desc": "Questa stanza oscura gli allegati dei messaggi provenienti da queste stanze:"
|
||||||
|
},
|
||||||
|
"mrf_policies": "Regole RM abilitate",
|
||||||
|
"mrf_policies_desc": "Le regole RM cambiano il comportamento federativo della stanza. Vigono le seguenti regole:"
|
||||||
},
|
},
|
||||||
"emoji": {
|
"staff": "Equipaggio"
|
||||||
"load_all": "Carico tutti i {emojiAmount} emoji",
|
},
|
||||||
"load_all_hint": "Primi {saneAmount} emoji caricati, caricarli tutti potrebbe causare rallentamenti.",
|
"domain_mute_card": {
|
||||||
"unicode": "Emoji Unicode",
|
"mute": "Zittisci",
|
||||||
"custom": "Emoji personale",
|
"mute_progress": "Zittisco…",
|
||||||
"add_emoji": "Inserisci Emoji",
|
"unmute": "Ascolta",
|
||||||
"search_emoji": "Cerca un emoji",
|
"unmute_progress": "Procedo…"
|
||||||
"keep_open": "Tieni aperto il menù",
|
},
|
||||||
"emoji": "Emoji",
|
"exporter": {
|
||||||
"stickers": "Adesivi"
|
"export": "Esporta",
|
||||||
}
|
"processing": "In elaborazione, il tuo file sarà scaricabile a breve"
|
||||||
|
},
|
||||||
|
"image_cropper": {
|
||||||
|
"crop_picture": "Ritaglia immagine",
|
||||||
|
"save": "Salva",
|
||||||
|
"save_without_cropping": "Salva senza ritagliare",
|
||||||
|
"cancel": "Annulla"
|
||||||
|
},
|
||||||
|
"importer": {
|
||||||
|
"submit": "Invia",
|
||||||
|
"success": "Importato.",
|
||||||
|
"error": "L'importazione non è andata a buon fine."
|
||||||
|
},
|
||||||
|
"media_modal": {
|
||||||
|
"previous": "Precedente",
|
||||||
|
"next": "Prossimo"
|
||||||
|
},
|
||||||
|
"polls": {
|
||||||
|
"add_poll": "Sondaggio",
|
||||||
|
"add_option": "Alternativa",
|
||||||
|
"option": "Opzione",
|
||||||
|
"votes": "voti",
|
||||||
|
"vote": "Vota",
|
||||||
|
"type": "Tipo di sondaggio",
|
||||||
|
"single_choice": "Scelta singola",
|
||||||
|
"multiple_choices": "Scelta multipla",
|
||||||
|
"expiry": "Scadenza",
|
||||||
|
"expires_in": "Scade fra {0}",
|
||||||
|
"expired": "Scaduto {0} fa",
|
||||||
|
"not_enough_options": "Aggiungi altre risposte"
|
||||||
|
},
|
||||||
|
"interactions": {
|
||||||
|
"favs_repeats": "Condivisi e preferiti",
|
||||||
|
"load_older": "Carica vecchie interazioni",
|
||||||
|
"moves": "Utenti migrati",
|
||||||
|
"follows": "Nuovi seguìti"
|
||||||
|
},
|
||||||
|
"emoji": {
|
||||||
|
"load_all": "Carico tutti i {emojiAmount} emoji",
|
||||||
|
"load_all_hint": "Primi {saneAmount} emoji caricati, caricarli tutti potrebbe causare rallentamenti.",
|
||||||
|
"unicode": "Emoji Unicode",
|
||||||
|
"custom": "Emoji personale",
|
||||||
|
"add_emoji": "Inserisci Emoji",
|
||||||
|
"search_emoji": "Cerca un emoji",
|
||||||
|
"keep_open": "Tieni aperto il menù",
|
||||||
|
"emoji": "Emoji",
|
||||||
|
"stickers": "Adesivi"
|
||||||
|
},
|
||||||
|
"selectable_list": {
|
||||||
|
"select_all": "Seleziona tutto"
|
||||||
|
},
|
||||||
|
"remote_user_resolver": {
|
||||||
|
"error": "Non trovato.",
|
||||||
|
"searching_for": "Cerco",
|
||||||
|
"remote_user_resolver": "Cerca utenti remoti"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,32 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
/* By default async components don't have any way to recover, if component is
|
||||||
|
* failed, it is failed forever. This helper tries to remedy that by recreating
|
||||||
|
* async component when retry is requested (by user). You need to emit the
|
||||||
|
* `resetAsyncComponent` event from child to reset the component. Generally,
|
||||||
|
* this should be done from error component but could be done from loading or
|
||||||
|
* actual target component itself if needs to be.
|
||||||
|
*/
|
||||||
|
function getResettableAsyncComponent (asyncComponent, options) {
|
||||||
|
const asyncComponentFactory = () => () => ({
|
||||||
|
component: asyncComponent(),
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
|
||||||
|
const observe = Vue.observable({ c: asyncComponentFactory() })
|
||||||
|
|
||||||
|
return {
|
||||||
|
functional: true,
|
||||||
|
render (createElement, { data, children }) {
|
||||||
|
// emit event resetAsyncComponent to reloading
|
||||||
|
data.on = {}
|
||||||
|
data.on.resetAsyncComponent = () => {
|
||||||
|
observe.c = asyncComponentFactory()
|
||||||
|
// parent.$forceUpdate()
|
||||||
|
}
|
||||||
|
return createElement(observe.c, data, children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getResettableAsyncComponent
|
Loading…
Reference in new issue