Merge branch 'develop' of git.pleroma.social:pleroma/pleroma-fe into develop

This commit is contained in:
sadposter 2020-06-19 11:01:06 +01:00
commit 77bd79100b
110 changed files with 9373 additions and 7855 deletions

View file

@ -4,24 +4,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Changed ### Changed
- Greentext now has separate color slot for it
- Removed the use of with_move parameters when fetching notifications - Removed the use of with_move parameters when fetching notifications
### Fixed
- Weird bug related to post being sent seemingly after pasting with keyboard (hopefully)
- Multiple issues with muted statuses/notifications
## [Unreleased patch] ## [Unreleased patch]
### Add ### Add
- Added private notifications option for push notifications - Added private notifications option for push notifications
- 'Copy link' button for statuses (in the ellipsis menu) - 'Copy link' button for statuses (in the ellipsis menu)
- Autocomplete domains from list of known instances
### Changed ### Changed
- Registration page no longer requires email if the server is configured not to require it - Registration page no longer requires email if the server is configured not to require it
- Change heart to thumbs up in reaction picker - Change heart to thumbs up in reaction picker
- Close the media modal on navigation events - Close the media modal on navigation events
- Add colons to the emoji alt text, to make them copyable - Add colons to the emoji alt text, to make them copyable
- Add better visual indication for drag-and-drop for files
### Fixed ### Fixed
- Custom Emoji will display in poll options now.
- Status ellipsis menu closes properly when selecting certain options - Status ellipsis menu closes properly when selecting certain options
- Cropped images look correct in Chrome - Cropped images look correct in Chrome
- Newlines in the muted words settings work again - Newlines in the muted words settings work again
- Clicking on non-latin hashtags won't open a new window - Clicking on non-latin hashtags won't open a new window
- Uploading and drag-dropping multiple files works correctly now.
- Subject field now appears disabled when posting
- Fix status ellipsis menu being cut off in notifications column
- Fixed autocomplete sometimes not returning the right user when there's already some results
## [2.0.3] - 2020-05-02 ## [2.0.3] - 2020-05-02
### Fixed ### Fixed

View file

@ -22,23 +22,20 @@
"cropperjs": "^1.4.3", "cropperjs": "^1.4.3",
"diff": "^3.0.1", "diff": "^3.0.1",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"karma-mocha-reporter": "^2.2.1",
"localforage": "^1.5.0", "localforage": "^1.5.0",
"object-path": "^0.11.3",
"phoenix": "^1.3.0", "phoenix": "^1.3.0",
"portal-vue": "^2.1.4", "portal-vue": "^2.1.4",
"sanitize-html": "^1.13.0",
"v-click-outside": "^2.1.1", "v-click-outside": "^2.1.1",
"vue": "^2.5.13", "vue": "^2.6.11",
"vue-chat-scroll": "^1.2.1", "vue-chat-scroll": "^1.2.1",
"vue-i18n": "^7.3.2", "vue-i18n": "^7.3.2",
"vue-router": "^3.0.1", "vue-router": "^3.0.1",
"vue-template-compiler": "^2.3.4", "vue-template-compiler": "^2.6.11",
"vuelidate": "^0.7.4", "vuelidate": "^0.7.4",
"vuex": "^3.0.1", "vuex": "^3.0.1"
"whatwg-fetch": "^2.0.3"
}, },
"devDependencies": { "devDependencies": {
"karma-mocha-reporter": "^2.2.1",
"@babel/core": "^7.7.5", "@babel/core": "^7.7.5",
"@babel/plugin-transform-runtime": "^7.7.6", "@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.6", "@babel/preset-env": "^7.7.6",

View file

@ -6,6 +6,7 @@ import InstanceSpecificPanel from './components/instance_specific_panel/instance
import FeaturesPanel from './components/features_panel/features_panel.vue' import FeaturesPanel from './components/features_panel/features_panel.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue' import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
import ChatPanel from './components/chat_panel/chat_panel.vue' import ChatPanel from './components/chat_panel/chat_panel.vue'
import SettingsModal from './components/settings_modal/settings_modal.vue'
import MediaModal from './components/media_modal/media_modal.vue' import MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue' import SideDrawer from './components/side_drawer/side_drawer.vue'
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue' import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
@ -29,6 +30,7 @@ export default {
SideDrawer, SideDrawer,
MobilePostStatusButton, MobilePostStatusButton,
MobileNav, MobileNav,
SettingsModal,
UserReportingModal, UserReportingModal,
PostStatusModal PostStatusModal
}, },
@ -45,7 +47,8 @@ export default {
}), }),
created () { created () {
// Load the locale from the storage // Load the locale from the storage
this.$i18n.locale = this.$store.getters.mergedConfig.interfaceLanguage const val = this.$store.getters.mergedConfig.interfaceLanguage
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
window.addEventListener('resize', this.updateMobileState) window.addEventListener('resize', this.updateMobileState)
}, },
destroyed () { destroyed () {
@ -117,6 +120,9 @@ export default {
onSearchBarToggled (hidden) { onSearchBarToggled (hidden) {
this.searchBarHidden = hidden this.searchBarHidden = hidden
}, },
openSettingsModal () {
this.$store.dispatch('openSettingsModal')
},
updateMobileState () { updateMobileState () {
const mobileLayout = windowWidth() <= 800 const mobileLayout = windowWidth() <= 800
const changed = mobileLayout !== this.isMobileLayout const changed = mobileLayout !== this.isMobileLayout

View file

@ -566,7 +566,7 @@ main-router {
min-height: 0; min-height: 0;
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
margin-left: .25em; margin-left: .5em;
min-width: 1px; min-width: 1px;
align-self: stretch; align-self: stretch;
} }
@ -860,51 +860,6 @@ nav {
} }
} }
.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;
}
.btn {
min-height: 28px;
min-width: 10em;
padding: 0 2em;
}
.number-input {
max-width: 6em;
}
}
.select-multiple { .select-multiple {
display: flex; display: flex;
.option-list { .option-list {

View file

@ -46,15 +46,16 @@
@toggled="onSearchBarToggled" @toggled="onSearchBarToggled"
@click.stop.native @click.stop.native
/> />
<router-link <a
href="#"
class="mobile-hidden" class="mobile-hidden"
:to="{ name: 'settings'}" @click.stop="openSettingsModal"
> >
<i <i
class="button-icon icon-cog nav-icon" class="button-icon icon-cog nav-icon"
:title="$t('nav.preferences')" :title="$t('nav.preferences')"
/> />
</router-link> </a>
<a <a
v-if="currentUser && currentUser.role === 'admin'" v-if="currentUser && currentUser.role === 'admin'"
href="/pleroma/admin/#/login-pleroma" href="/pleroma/admin/#/login-pleroma"
@ -125,6 +126,7 @@
<MobilePostStatusButton /> <MobilePostStatusButton />
<UserReportingModal /> <UserReportingModal />
<PostStatusModal /> <PostStatusModal />
<SettingsModal />
<portal-target name="modal" /> <portal-target name="modal" />
</div> </div>
</template> </template>

View file

@ -7,10 +7,8 @@ import Interactions from 'components/interactions/interactions.vue'
import DMs from 'components/dm_timeline/dm_timeline.vue' import DMs from 'components/dm_timeline/dm_timeline.vue'
import UserProfile from 'components/user_profile/user_profile.vue' import UserProfile from 'components/user_profile/user_profile.vue'
import Search from 'components/search/search.vue' import Search from 'components/search/search.vue'
import Settings from 'components/settings/settings.vue'
import Registration from 'components/registration/registration.vue' import Registration from 'components/registration/registration.vue'
import PasswordReset from 'components/password_reset/password_reset.vue' import PasswordReset from 'components/password_reset/password_reset.vue'
import UserSettings from 'components/user_settings/user_settings.vue'
import FollowRequests from 'components/follow_requests/follow_requests.vue' import FollowRequests from 'components/follow_requests/follow_requests.vue'
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue' import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
import Notifications from 'components/notifications/notifications.vue' import Notifications from 'components/notifications/notifications.vue'
@ -56,12 +54,10 @@ export default (store) => {
{ name: 'external-user-profile', path: '/users/:id', component: UserProfile }, { name: 'external-user-profile', path: '/users/:id', component: UserProfile },
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute }, { name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute }, { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'settings', path: '/settings', component: Settings },
{ name: 'registration', path: '/registration', component: Registration }, { name: 'registration', path: '/registration', component: Registration },
{ name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true }, { name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true },
{ name: 'registration-token', path: '/registration/:token', component: Registration }, { name: 'registration-token', path: '/registration/:token', component: Registration },
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute }, { name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
{ name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute },
{ name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute }, { name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute },
{ name: 'login', path: '/login', component: AuthForm }, { name: 'login', path: '/login', component: AuthForm },
{ name: 'chat', path: '/chat', component: ChatPanel, props: () => ({ floating: false }) }, { name: 'chat', path: '/chat', component: ChatPanel, props: () => ({ floating: false }) },

View file

@ -3,6 +3,7 @@
<Popover <Popover
trigger="click" trigger="click"
placement="bottom" placement="bottom"
:bound-to="{ x: 'container' }"
> >
<div <div
slot="content" slot="content"

View file

@ -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>

View file

@ -5,9 +5,20 @@ const DomainMuteCard = {
components: { components: {
ProgressButton ProgressButton
}, },
computed: {
user () {
return this.$store.state.users.currentUser
},
muted () {
return this.user.domainMutes.includes(this.domain)
}
},
methods: { methods: {
unmuteDomain () { unmuteDomain () {
return this.$store.dispatch('unmuteDomain', this.domain) return this.$store.dispatch('unmuteDomain', this.domain)
},
muteDomain () {
return this.$store.dispatch('muteDomain', this.domain)
} }
} }
} }

View file

@ -4,6 +4,7 @@
{{ domain }} {{ domain }}
</div> </div>
<ProgressButton <ProgressButton
v-if="muted"
:click="unmuteDomain" :click="unmuteDomain"
class="btn btn-default" class="btn btn-default"
> >
@ -12,6 +13,16 @@
{{ $t('domain_mute_card.unmute_progress') }} {{ $t('domain_mute_card.unmute_progress') }}
</template> </template>
</ProgressButton> </ProgressButton>
<ProgressButton
v-else
:click="muteDomain"
class="btn btn-default"
>
{{ $t('domain_mute_card.mute') }}
<template slot="progress">
{{ $t('domain_mute_card.mute_progress') }}
</template>
</ProgressButton>
</div> </div>
</template> </template>
@ -34,5 +45,9 @@
button { button {
width: 10em; width: 10em;
} }
.autosuggest-results & {
padding-left: 1em;
}
} }
</style> </style>

View file

@ -13,7 +13,7 @@ import { debounce } from 'lodash'
const debounceUserSearch = debounce((data, input) => { const debounceUserSearch = debounce((data, input) => {
data.updateUsersList(input) data.updateUsersList(input)
}, 500, { leading: true, trailing: false }) }, 500)
export default data => input => { export default data => input => {
const firstChar = input[0] const firstChar = input[0]
@ -97,8 +97,8 @@ export const suggestUsers = data => input => {
replacement: '@' + screen_name + ' ' replacement: '@' + screen_name + ' '
})) }))
// BE search users if there are no matches // BE search users to get more comprehensive results
if (newUsers.length === 0 && data.updateUsersList) { if (data.updateUsersList) {
debounceUserSearch(data, noPrefix) debounceUserSearch(data, noPrefix)
} }
return newUsers return newUsers

View file

@ -3,6 +3,7 @@
trigger="click" trigger="click"
placement="top" placement="top"
class="extra-button-popover" class="extra-button-popover"
:bound-to="{ x: 'container' }"
> >
<div <div
slot="content" slot="content"

View file

@ -78,6 +78,7 @@
video, video,
canvas { canvas {
object-fit: contain; object-fit: contain;
height: 100%;
} }
} }

View file

@ -32,7 +32,7 @@ import _ from 'lodash'
export default { export default {
computed: { computed: {
languageCodes () { languageCodes () {
return Object.keys(languagesObject) return languagesObject.languages
}, },
languageNames () { languageNames () {
@ -43,7 +43,6 @@ export default {
get: function () { return this.$store.getters.mergedConfig.interfaceLanguage }, get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
set: function (val) { set: function (val) {
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
this.$i18n.locale = val
} }
} }
}, },

View file

@ -5,10 +5,15 @@ import fileSizeFormatService from '../../services/file_size_format/file_size_for
const mediaUpload = { const mediaUpload = {
data () { data () {
return { return {
uploading: false, uploadCount: 0,
uploadReady: true uploadReady: true
} }
}, },
computed: {
uploading () {
return this.uploadCount > 0
}
},
methods: { methods: {
uploadFile (file) { uploadFile (file) {
const self = this const self = this
@ -23,29 +28,21 @@ const mediaUpload = {
formData.append('file', file) formData.append('file', file)
self.$emit('uploading') self.$emit('uploading')
self.uploading = true self.uploadCount++
statusPosterService.uploadMedia({ store, formData }) statusPosterService.uploadMedia({ store, formData })
.then((fileData) => { .then((fileData) => {
self.$emit('uploaded', fileData) self.$emit('uploaded', fileData)
self.uploading = false self.decreaseUploadCount()
}, (error) => { // eslint-disable-line handle-callback-err }, (error) => { // eslint-disable-line handle-callback-err
self.$emit('upload-failed', 'default') self.$emit('upload-failed', 'default')
self.uploading = false self.decreaseUploadCount()
}) })
}, },
fileDrop (e) { decreaseUploadCount () {
if (e.dataTransfer.files.length > 0) { this.uploadCount--
e.preventDefault() // allow dropping text like before if (this.uploadCount === 0) {
this.uploadFile(e.dataTransfer.files[0]) this.$emit('all-uploaded')
}
},
fileDrag (e) {
let types = e.dataTransfer.types
if (types.contains('Files')) {
e.dataTransfer.dropEffect = 'copy'
} else {
e.dataTransfer.dropEffect = 'none'
} }
}, },
clearFile () { clearFile () {
@ -54,11 +51,13 @@ const mediaUpload = {
this.uploadReady = true this.uploadReady = true
}) })
}, },
change ({ target }) { multiUpload (files) {
for (var i = 0; i < target.files.length; i++) { for (const file of files) {
let file = target.files[i]
this.uploadFile(file) this.uploadFile(file)
} }
},
change ({ target }) {
this.multiUpload(target.files)
} }
}, },
props: [ props: [
@ -67,7 +66,7 @@ const mediaUpload = {
watch: { watch: {
'dropFiles': function (fileInfos) { 'dropFiles': function (fileInfos) {
if (!this.uploading) { if (!this.uploading) {
this.uploadFile(fileInfos[0]) this.multiUpload(fileInfos)
} }
} }
} }

View file

@ -1,10 +1,5 @@
<template> <template>
<div <div class="media-upload">
class="media-upload"
@drop.prevent
@dragover.prevent="fileDrag"
@drop="fileDrop"
>
<label <label
class="label" class="label"
:title="$t('tool_tip.media_upload')" :title="$t('tool_tip.media_upload')"

View file

@ -1,8 +1,9 @@
<template> <template>
<div <div
v-show="isOpen" v-show="isOpen"
v-body-scroll-lock="isOpen" v-body-scroll-lock="isOpen && !noBackground"
class="modal-view" class="modal-view"
:class="classes"
@click.self="$emit('backdropClicked')" @click.self="$emit('backdropClicked')"
> >
<slot /> <slot />
@ -15,6 +16,18 @@ export default {
isOpen: { isOpen: {
type: Boolean, type: Boolean,
default: true default: true
},
noBackground: {
type: Boolean,
default: false
}
},
computed: {
classes () {
return {
'modal-background': !this.noBackground,
'open': this.isOpen
}
} }
} }
} }
@ -32,12 +45,22 @@ export default {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
overflow: auto; overflow: auto;
pointer-events: none;
animation-duration: 0.2s; animation-duration: 0.2s;
background-color: rgba(0, 0, 0, 0.5);
animation-name: modal-background-fadein; animation-name: modal-background-fadein;
opacity: 0;
body:not(.scroll-locked) & { > * {
opacity: 0; pointer-events: initial;
}
&.modal-background {
pointer-events: initial;
background-color: rgba(0, 0, 0, 0.5);
}
&.open {
opacity: 1;
} }
} }

View file

@ -1,3 +1,4 @@
import StatusContent from '../status_content/status_content.vue'
import Status from '../status/status.vue' import Status from '../status/status.vue'
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
import UserCard from '../user_card/user_card.vue' import UserCard from '../user_card/user_card.vue'
@ -16,10 +17,11 @@ const Notification = {
}, },
props: [ 'notification' ], props: [ 'notification' ],
components: { components: {
Status, StatusContent,
UserAvatar, UserAvatar,
UserCard, UserCard,
Timeago Timeago,
Status
}, },
methods: { methods: {
toggleUserExpanded () { toggleUserExpanded () {

View file

@ -157,11 +157,9 @@
</router-link> </router-link>
</div> </div>
<template v-else> <template v-else>
<status <status-content
class="faint" class="faint"
:compact="true" :status="notification.action"
:statusoid="notification.action"
:no-heading="true"
/> />
</template> </template>
</div> </div>

View file

@ -36,6 +36,8 @@
border-bottom: 1px solid; border-bottom: 1px solid;
border-color: $fallback--border; border-color: $fallback--border;
border-color: var(--border, $fallback--border); border-color: var(--border, $fallback--border);
word-wrap: break-word;
word-break: break-word;
&:hover .animated.avatar { &:hover .animated.avatar {
canvas { canvas {
@ -46,35 +48,26 @@
} }
} }
.muted {
padding: .25em .6em;
}
.non-mention { .non-mention {
display: flex; display: flex;
flex: 1; flex: 1;
flex-wrap: nowrap; flex-wrap: nowrap;
padding: 0.6em; padding: 0.6em;
min-width: 0; min-width: 0;
.avatar-container { .avatar-container {
width: 32px; width: 32px;
height: 32px; height: 32px;
} }
.status-el {
.status { .status-body {
padding: 0.25em 0; color: $fallback--faint;
color: $fallback--faint; color: var(--faint, $fallback--faint);
color: var(--faint, $fallback--faint); a {
a { color: var(--faintLink);
color: var(--faintLink);
}
.status-content a {
color: var(--postFaintLink);
}
} }
padding: 0; .status-content a {
.media-body { color: var(--postFaintLink);
margin: 0;
} }
} }
} }

View file

@ -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>

View file

@ -17,7 +17,7 @@
<span class="result-percentage"> <span class="result-percentage">
{{ percentageForOption(option.votes_count) }}% {{ percentageForOption(option.votes_count) }}%
</span> </span>
<span>{{ option.title }}</span> <span v-html="option.title_html"></span>
</div> </div>
<div <div
class="result-fill" class="result-fill"

View file

@ -1,4 +1,3 @@
const Popover = { const Popover = {
name: 'Popover', name: 'Popover',
props: { props: {
@ -10,6 +9,9 @@ const Popover = {
// 'container' for using offsetParent as boundaries for either axis // 'container' for using offsetParent as boundaries for either axis
// or 'viewport' // or 'viewport'
boundTo: Object, boundTo: Object,
// Takes a selector to use as a replacement for the parent container
// for getting boundaries for x an y axis
boundToSelector: String,
// Takes a top/bottom/left/right object, how much space to leave // Takes a top/bottom/left/right object, how much space to leave
// between boundary and popover element // between boundary and popover element
margin: Object, margin: Object,
@ -27,6 +29,10 @@ const Popover = {
} }
}, },
methods: { methods: {
containerBoundingClientRect () {
const container = this.boundToSelector ? this.$el.closest(this.boundToSelector) : this.$el.offsetParent
return container.getBoundingClientRect()
},
updateStyles () { updateStyles () {
if (this.hidden) { if (this.hidden) {
this.styles = { this.styles = {
@ -45,7 +51,8 @@ const Popover = {
// Minor optimization, don't call a slow reflow call if we don't have to // Minor optimization, don't call a slow reflow call if we don't have to
const parentBounds = this.boundTo && const parentBounds = this.boundTo &&
(this.boundTo.x === 'container' || this.boundTo.y === 'container') && (this.boundTo.x === 'container' || this.boundTo.y === 'container') &&
this.$el.offsetParent.getBoundingClientRect() this.containerBoundingClientRect()
const margin = this.margin || {} const margin = this.margin || {}
// What are the screen bounds for the popover? Viewport vs container // What are the screen bounds for the popover? Viewport vs container

View file

@ -82,7 +82,9 @@ const PostStatusForm = {
contentType contentType
}, },
caret: 0, caret: 0,
pollFormVisible: false pollFormVisible: false,
showDropIcon: 'hide',
dropStopTimeout: null
} }
}, },
computed: { computed: {
@ -218,7 +220,6 @@ const PostStatusForm = {
}, },
addMediaFile (fileInfo) { addMediaFile (fileInfo) {
this.newStatus.files.push(fileInfo) this.newStatus.files.push(fileInfo)
this.enableSubmit()
}, },
removeMediaFile (fileInfo) { removeMediaFile (fileInfo) {
let index = this.newStatus.files.indexOf(fileInfo) let index = this.newStatus.files.indexOf(fileInfo)
@ -227,7 +228,6 @@ const PostStatusForm = {
uploadFailed (errString, templateArgs) { uploadFailed (errString, templateArgs) {
templateArgs = templateArgs || {} templateArgs = templateArgs || {}
this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs) this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs)
this.enableSubmit()
}, },
disableSubmit () { disableSubmit () {
this.submitDisabled = true this.submitDisabled = true
@ -250,13 +250,27 @@ const PostStatusForm = {
} }
}, },
fileDrop (e) { fileDrop (e) {
if (e.dataTransfer.files.length > 0) { if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
e.preventDefault() // allow dropping text like before e.preventDefault() // allow dropping text like before
this.dropFiles = e.dataTransfer.files this.dropFiles = e.dataTransfer.files
clearTimeout(this.dropStopTimeout)
this.showDropIcon = 'hide'
} }
}, },
fileDragStop (e) {
// The false-setting is done with delay because just using leave-events
// directly caused unwanted flickering, this is not perfect either but
// much less noticable.
clearTimeout(this.dropStopTimeout)
this.showDropIcon = 'fade'
this.dropStopTimeout = setTimeout(() => (this.showDropIcon = 'hide'), 500)
},
fileDrag (e) { fileDrag (e) {
e.dataTransfer.dropEffect = 'copy' e.dataTransfer.dropEffect = 'copy'
if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
clearTimeout(this.dropStopTimeout)
this.showDropIcon = 'show'
}
}, },
onEmojiInputInput (e) { onEmojiInputInput (e) {
this.$nextTick(() => { this.$nextTick(() => {

View file

@ -6,7 +6,15 @@
<form <form
autocomplete="off" autocomplete="off"
@submit.prevent="postStatus(newStatus)" @submit.prevent="postStatus(newStatus)"
@dragover.prevent="fileDrag"
> >
<div
v-show="showDropIcon !== 'hide'"
:style="{ animation: showDropIcon === 'show' ? 'fade-in 0.25s' : 'fade-out 0.5s' }"
class="drop-indicator icon-upload"
@dragleave="fileDragStop"
@drop.stop="fileDrop"
/>
<div class="form-group"> <div class="form-group">
<i18n <i18n
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'" v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'"
@ -73,6 +81,7 @@
v-model="newStatus.spoilerText" v-model="newStatus.spoilerText"
type="text" type="text"
:placeholder="$t('post_status.content_warning')" :placeholder="$t('post_status.content_warning')"
:disabled="posting"
class="form-post-subject" class="form-post-subject"
> >
</EmojiInput> </EmojiInput>
@ -96,9 +105,7 @@
:disabled="posting" :disabled="posting"
class="form-post-body" class="form-post-body"
@keydown.meta.enter="postStatus(newStatus)" @keydown.meta.enter="postStatus(newStatus)"
@keyup.ctrl.enter="postStatus(newStatus)" @keydown.ctrl.enter="postStatus(newStatus)"
@drop="fileDrop"
@dragover.prevent="fileDrag"
@input="resize" @input="resize"
@compositionupdate="resize" @compositionupdate="resize"
@paste="paste" @paste="paste"
@ -172,6 +179,7 @@
@uploading="disableSubmit" @uploading="disableSubmit"
@uploaded="addMediaFile" @uploaded="addMediaFile"
@upload-failed="uploadFailed" @upload-failed="uploadFailed"
@all-uploaded="enableSubmit"
/> />
<div <div
class="emoji-icon" class="emoji-icon"
@ -446,7 +454,8 @@
form { form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 0.6em; margin: 0.6em;
position: relative;
} }
.form-group { .form-group {
@ -504,5 +513,35 @@
cursor: pointer; cursor: pointer;
z-index: 4; z-index: 4;
} }
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 0.6; }
}
@keyframes fade-out {
from { opacity: 0.6; }
to { opacity: 0; }
}
.drop-indicator {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
font-size: 5em;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.6;
color: $fallback--text;
color: var(--text, $fallback--text);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
border: 2px dashed $fallback--text;
border: 2px dashed var(--text, $fallback--text);
}
} }
</style> </style>

View file

@ -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/pleroma/pleroma-fe/commit/'
const pleromaBeCommitUrl = 'https://git.pleroma.social/pleroma/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

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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;
}
}
}
}

View file

@ -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>

View file

@ -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

View file

@ -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;
}
}
}

View file

@ -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>

View file

@ -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

View file

@ -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> -->

View file

@ -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

View file

@ -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>

View file

@ -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

View file

@ -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>

View file

@ -0,0 +1,136 @@
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'
}
},
created () {
this.$store.dispatch('fetchTokens')
this.$store.dispatch('getKnownDomains')
},
components: {
TabSwitcher,
BlockList,
MuteList,
DomainMuteList,
BlockCard,
MuteCard,
DomainMuteCard,
ProgressButton,
Autosuggest,
Checkbox
},
computed: {
knownDomains () {
return this.$store.state.instance.knownDomains
},
user () {
return this.$store.state.users.currentUser
}
},
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.user.id
})
},
filterUnMutedUsers (userIds) {
return reject(userIds, (userId) => {
const relationship = this.$store.getters.relationship(this.userId)
return relationship.muting || userId === this.user.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)
},
filterUnMutedDomains (urls) {
return urls.filter(url => !this.user.domainMutes.includes(url))
},
queryKnownDomains (query) {
return new Promise((resolve, reject) => {
resolve(this.knownDomains.filter(url => url.toLowerCase().includes(query)))
})
},
unmuteDomains (domains) {
return this.$store.dispatch('unmuteDomains', domains)
}
}
}
export default MutesAndBlocks

View file

@ -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
}
}

View file

@ -0,0 +1,171 @@
<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">
<Autosuggest
:filter="filterUnMutedDomains"
:query="queryKnownDomains"
:placeholder="$t('settings.type_domains_to_mute')"
>
<DomainMuteCard
slot-scope="row"
:domain="row.item"
/>
</Autosuggest>
</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>

View file

@ -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

View file

@ -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> -->

View file

@ -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

View file

@ -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;
}
}

View file

@ -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>

View file

@ -137,20 +137,20 @@
<script src="./mfa.js"></script> <script src="./mfa.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../../../_variables.scss';
.warning {
color: $fallback--cOrange;
color: var(--cOrange, $fallback--cOrange);
}
.mfa-settings { .mfa-settings {
.mfa-heading, .method-item { .mfa-heading, .method-item {
overflow: hidden;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;
align-items: baseline; align-items: baseline;
} }
.warning {
color: $fallback--cOrange;
color: var(--cOrange, $fallback--cOrange);
}
.setup-otp { .setup-otp {
display: flex; display: flex;
justify-content: center; justify-content: center;

View file

@ -1,5 +1,5 @@
<template> <template>
<div> <div class="mfa-backup-codes">
<h4 v-if="displayTitle"> <h4 v-if="displayTitle">
{{ $t('settings.mfa.recovery_codes') }} {{ $t('settings.mfa.recovery_codes') }}
</h4> </h4>
@ -21,13 +21,15 @@
</template> </template>
<script src="./mfa_backup_codes.js"></script> <script src="./mfa_backup_codes.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../../../_variables.scss';
.warning { .mfa-backup-codes {
color: $fallback--cOrange; .warning {
color: var(--cOrange, $fallback--cOrange); color: $fallback--cOrange;
} color: var(--cOrange, $fallback--cOrange);
.backup-codes { }
font-family: var(--postCodeFont, monospace); .backup-codes {
font-family: var(--postCodeFont, monospace);
}
} }
</style> </style>

View file

@ -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

View file

@ -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> -->

View file

@ -3,7 +3,7 @@ import {
rgb2hex, rgb2hex,
hex2rgb, hex2rgb,
getContrastRatioLayers getContrastRatioLayers
} from '../../services/color_convert/color_convert.js' } from 'src/services/color_convert/color_convert.js'
import { import {
DEFAULT_SHADOWS, DEFAULT_SHADOWS,
generateColors, generateColors,
@ -14,26 +14,27 @@ import {
getThemes, getThemes,
shadows2to3, shadows2to3,
colors2to3 colors2to3
} from '../../services/style_setter/style_setter.js' } from 'src/services/style_setter/style_setter.js'
import { import {
SLOT_INHERITANCE SLOT_INHERITANCE
} from '../../services/theme_data/pleromafe.js' } from 'src/services/theme_data/pleromafe.js'
import { import {
CURRENT_VERSION, CURRENT_VERSION,
OPACITIES, OPACITIES,
getLayers, getLayers,
getOpacitySlot getOpacitySlot
} from '../../services/theme_data/theme_data.service.js' } from 'src/services/theme_data/theme_data.service.js'
import ColorInput from '../color_input/color_input.vue' import ColorInput from 'src/components/color_input/color_input.vue'
import RangeInput from '../range_input/range_input.vue' import RangeInput from 'src/components/range_input/range_input.vue'
import OpacityInput from '../opacity_input/opacity_input.vue' import OpacityInput from 'src/components/opacity_input/opacity_input.vue'
import ShadowControl from '../shadow_control/shadow_control.vue' import ShadowControl from 'src/components/shadow_control/shadow_control.vue'
import FontControl from '../font_control/font_control.vue' import FontControl from 'src/components/font_control/font_control.vue'
import ContrastRatio from '../contrast_ratio/contrast_ratio.vue' import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue'
import TabSwitcher from '../tab_switcher/tab_switcher.js' import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
import ExportImport from 'src/components/export_import/export_import.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import Preview from './preview.vue' import Preview from './preview.vue'
import ExportImport from '../export_import/export_import.vue'
import Checkbox from '../checkbox/checkbox.vue'
// List of color values used in v1 // List of color values used in v1
const v1OnlyNames = [ const v1OnlyNames = [

View file

@ -1,5 +1,6 @@
@import '../../_variables.scss'; @import 'src/_variables.scss';
.style-switcher { .theme-tab {
padding-bottom: 2em;
.theme-warning { .theme-warning {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
@ -54,10 +55,6 @@
} }
} }
.tab-switcher {
margin: 0 -1em;
}
.reset-container { .reset-container {
flex-wrap: wrap; flex-wrap: wrap;
} }
@ -98,20 +95,25 @@
align-items: baseline; align-items: baseline;
width: 100%; width: 100%;
min-height: 30px; min-height: 30px;
margin-bottom: 1em;
.btn {
min-width: 1px;
flex: 0 auto;
padding: 0 1em;
}
p { p {
flex: 1; flex: 1;
margin: 0; margin: 0;
margin-right: .5em; margin-right: .5em;
} }
}
margin-bottom: 1em; .tab-header-buttons {
display: flex;
flex-direction: column;
.btn {
min-width: 1px;
flex: 0 auto;
padding: 0 1em;
margin-bottom: .5em;
}
} }
.shadow-selector { .shadow-selector {
@ -161,7 +163,7 @@
border-bottom: 1px dashed; border-bottom: 1px dashed;
border-color: $fallback--border; border-color: $fallback--border;
border-color: var(--border, $fallback--border); border-color: var(--border, $fallback--border);
margin: 1em -1em 0; margin: 1em 0;
padding: 1em; padding: 1em;
background: var(--body-background-image); background: var(--body-background-image);
background-size: cover; background-size: cover;
@ -328,6 +330,14 @@
padding: 20px; padding: 20px;
} }
.apply-container {
.btn {
min-height: 28px;
min-width: 10em;
padding: 0 2em;
}
}
.btn { .btn {
margin-left: .25em; margin-left: .25em;
margin-right: .25em; margin-right: .25em;

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="style-switcher"> <div class="theme-tab">
<div class="presets-container"> <div class="presets-container">
<div class="save-load"> <div class="save-load">
<div <div
@ -126,18 +126,20 @@
> >
<div class="tab-header"> <div class="tab-header">
<p>{{ $t('settings.theme_help') }}</p> <p>{{ $t('settings.theme_help') }}</p>
<button <div class="tab-header-buttons">
class="btn" <button
@click="clearOpacity" class="btn"
> @click="clearOpacity"
{{ $t('settings.style.switcher.clear_opacity') }} >
</button> {{ $t('settings.style.switcher.clear_opacity') }}
<button </button>
class="btn" <button
@click="clearV1" class="btn"
> @click="clearV1"
{{ $t('settings.style.switcher.clear_all') }} >
</button> {{ $t('settings.style.switcher.clear_all') }}
</button>
</div>
</div> </div>
<p>{{ $t('settings.theme_help_v2_1') }}</p> <p>{{ $t('settings.theme_help_v2_1') }}</p>
<h4>{{ $t('settings.style.common_colors.main') }}</h4> <h4>{{ $t('settings.style.common_colors.main') }}</h4>
@ -254,6 +256,13 @@
:label="$t('settings.links')" :label="$t('settings.links')"
/> />
<ContrastRatio :contrast="previewContrast.postLink" /> <ContrastRatio :contrast="previewContrast.postLink" />
<ColorInput
v-model="postGreentextColorLocal"
name="postGreentextColor"
:fallback="previewTheme.colors.cGreen"
:label="$t('settings.greentext')"
/>
<ContrastRatio :contrast="previewContrast.postGreentext" />
<h4>{{ $t('settings.style.advanced_colors.alert') }}</h4> <h4>{{ $t('settings.style.advanced_colors.alert') }}</h4>
<ColorInput <ColorInput
v-model="alertErrorColorLocal" v-model="alertErrorColorLocal"
@ -951,6 +960,6 @@
</div> </div>
</template> </template>
<script src="./style_switcher.js"></script> <script src="./theme_tab.js"></script>
<style src="./style_switcher.scss" lang="scss"></style> <style src="./theme_tab.scss" lang="scss"></style>

View file

@ -0,0 +1,24 @@
import { extractCommit } from 'src/services/version/version.service'
const pleromaFeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma-fe/commit/'
const pleromaBeCommitUrl = 'https://git.pleroma.social/pleroma/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

View file

@ -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">

View file

@ -62,6 +62,9 @@ const SideDrawer = {
}, },
touchMove (e) { touchMove (e) {
GestureService.updateSwipe(e, this.closeGesture) GestureService.updateSwipe(e, this.closeGesture)
},
openSettingsModal () {
this.$store.dispatch('openSettingsModal')
} }
} }
} }

View file

@ -122,9 +122,12 @@
</router-link> </router-link>
</li> </li>
<li @click="toggleDrawer"> <li @click="toggleDrawer">
<router-link :to="{ name: 'settings' }"> <a
href="#"
@click="openSettingsModal"
>
<i class="button-icon icon-cog" /> {{ $t("settings.settings") }} <i class="button-icon icon-cog" /> {{ $t("settings.settings") }}
</router-link> </a>
</li> </li>
<li @click="toggleDrawer"> <li @click="toggleDrawer">
<router-link :to="{ name: 'about'}"> <router-link :to="{ name: 'about'}">

View file

@ -12,7 +12,8 @@ import StatusPopover from '../status_popover/status_popover.vue'
import EmojiReactions from '../emoji_reactions/emoji_reactions.vue' import EmojiReactions from '../emoji_reactions/emoji_reactions.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import { filter, unescape, uniqBy } from 'lodash' import { muteWordHits } from '../../services/status_parser/status_parser.js'
import { unescape, uniqBy } from 'lodash'
import { mapGetters, mapState } from 'vuex' import { mapGetters, mapState } from 'vuex'
const Status = { const Status = {
@ -44,6 +45,12 @@ const Status = {
muteWords () { muteWords () {
return this.mergedConfig.muteWords return this.mergedConfig.muteWords
}, },
showReasonMutedThread () {
return (
this.status.thread_muted ||
(this.status.reblog && this.status.reblog.thread_muted)
) && !this.inConversation
},
repeaterClass () { repeaterClass () {
const user = this.statusoid.user const user = this.statusoid.user
return highlightClass(user) return highlightClass(user)
@ -93,20 +100,42 @@ const Status = {
return !!this.currentUser return !!this.currentUser
}, },
muteWordHits () { muteWordHits () {
const statusText = this.status.text.toLowerCase() return muteWordHits(this.status, this.muteWords)
const statusSummary = this.status.summary.toLowerCase()
const hits = filter(this.muteWords, (muteWord) => {
return statusText.includes(muteWord.toLowerCase()) || statusSummary.includes(muteWord.toLowerCase())
})
return hits
}, },
muted () { muted () {
const relationship = this.$store.getters.relationship(this.status.user.id) const { status } = this
return !this.unmuted && ( const { reblog } = status
(!(this.inProfile && this.status.user.id === this.profileUserId) && relationship.muting) || const relationship = this.$store.getters.relationship(status.user.id)
(!this.inConversation && this.status.thread_muted) || const relationshipReblog = reblog && this.$store.getters.relationship(reblog.user.id)
this.muteWordHits.length > 0) const reasonsToMute = (
// Post is muted according to BE
status.muted ||
// Reprööt of a muted post according to BE
(reblog && reblog.muted) ||
// Muted user
relationship.muting ||
// Muted user of a reprööt
(relationshipReblog && relationshipReblog.muting) ||
// Thread is muted
status.thread_muted ||
// Wordfiltered
this.muteWordHits.length > 0
)
const excusesNotToMute = (
(
this.inProfile && (
// Don't mute user's posts on user timeline (except reblogs)
(!reblog && status.user.id === this.profileUserId) ||
// Same as above but also allow self-reblogs
(reblog && reblog.user.id === this.profileUserId)
)
) ||
// Don't mute statuses in muted conversation when said conversation is opened
(this.inConversation && status.thread_muted)
// No excuses if post has muted words
) && !this.muteWordHits.length > 0
return !this.unmuted && !excusesNotToMute && reasonsToMute
}, },
hideFilteredStatuses () { hideFilteredStatuses () {
return this.mergedConfig.hideFilteredStatuses return this.mergedConfig.hideFilteredStatuses

View file

@ -17,12 +17,33 @@
</div> </div>
<template v-if="muted && !isPreview"> <template v-if="muted && !isPreview">
<div class="media status container muted"> <div class="media status container muted">
<small> <small class="username">
<i
v-if="muted && retweet"
class="button-icon icon-retweet"
/>
<router-link :to="userProfileLink"> <router-link :to="userProfileLink">
{{ status.user.screen_name }} {{ status.user.screen_name }}
</router-link> </router-link>
</small> </small>
<small class="muteWords">{{ muteWordHits.join(', ') }}</small> <small
v-if="showReasonMutedThread"
class="mute-thread"
>
{{ $t('status.thread_muted') }}
</small>
<small
v-if="showReasonMutedThread && muteWordHits.length > 0"
class="mute-thread"
>
{{ $t('status.thread_muted_and_words') }}
</small>
<small
class="mute-words"
:title="muteWordHits.join(', ')"
>
{{ muteWordHits.join(', ') }}
</small>
<a <a
href="#" href="#"
class="unmute" class="unmute"
@ -637,19 +658,48 @@ $status-margin: 0.75em;
} }
.muted { .muted {
padding: 0.25em 0.5em; padding: .25em .6em;
button { height: 1.2em;
line-height: 1.2em;
text-overflow: ellipsis;
overflow: hidden;
display: flex;
flex-wrap: nowrap;
.username, .mute-thread, .mute-words {
word-wrap: normal;
word-break: normal;
white-space: nowrap;
}
.username, .mute-words {
text-overflow: ellipsis;
overflow: hidden;
}
.username {
flex: 0 1 auto;
margin-right: .2em;
}
.mute-thread {
flex: 0 0 auto;
}
.mute-words {
flex: 1 0 5em;
margin-left: .2em;
&::before {
content: ' '
}
}
.unmute {
flex: 0 0 auto;
margin-left: auto;
display: block;
margin-left: auto; margin-left: auto;
} }
.muteWords {
margin-left: 10px;
}
}
a.unmute {
display: block;
margin-left: auto;
} }
.reply-body { .reply-body {

View file

@ -164,23 +164,23 @@ $status-margin: 0.75em;
word-break: break-all; word-break: break-all;
} }
img, video {
max-width: 100%;
max-height: 400px;
vertical-align: middle;
object-fit: contain;
&.emoji {
width: 50px;
height: 50px;
}
}
.status-content { .status-content {
font-family: var(--postFont, sans-serif); font-family: var(--postFont, sans-serif);
line-height: 1.4em; line-height: 1.4em;
white-space: pre-wrap; white-space: pre-wrap;
img, video {
max-width: 100%;
max-height: 400px;
vertical-align: middle;
object-fit: contain;
&.emoji {
width: 50px;
height: 50px;
}
}
blockquote { blockquote {
margin: 0.2em 0 0.2em 2em; margin: 0.2em 0 0.2em 2em;
font-style: italic; font-style: italic;
@ -226,7 +226,7 @@ $status-margin: 0.75em;
.greentext { .greentext {
color: $fallback--cGreen; color: $fallback--cGreen;
color: var(--cGreen, $fallback--cGreen); color: var(--postGreentext, $fallback--cGreen);
} }
.timeline :not(.panel-disabled) > { .timeline :not(.panel-disabled) > {

View file

@ -23,13 +23,6 @@
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.contain-fit {
.still-image {
img {
height: 100%;
}
}
}
.still-image { .still-image {
position: relative; position: relative;
@ -38,6 +31,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
align-items: center;
&:hover canvas { &:hover canvas {
display: none; display: none;
@ -45,8 +39,8 @@
img { img {
width: 100%; width: 100%;
min-height: 100%;
object-fit: contain; object-fit: contain;
align-self: center;
} }
&.animated { &.animated {

View file

@ -24,6 +24,11 @@ export default Vue.component('tab-switcher', {
required: false, required: false,
type: Boolean, type: Boolean,
default: false default: false
},
sideTabBar: {
required: false,
type: Boolean,
default: false
} }
}, },
data () { data () {
@ -55,6 +60,9 @@ export default Vue.component('tab-switcher', {
this.onSwitch.call(null, this.$slots.default[index].key) this.onSwitch.call(null, this.$slots.default[index].key)
} }
this.active = index this.active = index
if (this.scrollableTabs) {
this.$refs.contents.scrollTop = 0
}
} }
} }
}, },
@ -64,7 +72,6 @@ export default Vue.component('tab-switcher', {
if (!slot.tag) return if (!slot.tag) return
const classesTab = ['tab'] const classesTab = ['tab']
const classesWrapper = ['tab-wrapper'] const classesWrapper = ['tab-wrapper']
if (this.activeIndex === index) { if (this.activeIndex === index) {
classesTab.push('active') classesTab.push('active')
classesWrapper.push('active') classesWrapper.push('active')
@ -87,8 +94,14 @@ export default Vue.component('tab-switcher', {
<button <button
disabled={slot.data.attrs.disabled} disabled={slot.data.attrs.disabled}
onClick={this.activateTab(index)} onClick={this.activateTab(index)}
class={classesTab.join(' ')}> class={classesTab.join(' ')}
{slot.data.attrs.label}</button> type="button"
>
{!slot.data.attrs.icon ? '' : (<i class={'tab-icon icon-' + slot.data.attrs.icon}/>)}
<span class="text">
{slot.data.attrs.label}
</span>
</button>
</div> </div>
) )
}) })
@ -96,20 +109,32 @@ export default Vue.component('tab-switcher', {
const contents = this.$slots.default.map((slot, index) => { const contents = this.$slots.default.map((slot, index) => {
if (!slot.tag) return if (!slot.tag) return
const active = this.activeIndex === index const active = this.activeIndex === index
if (this.renderOnlyFocused) { const classes = [ active ? 'active' : 'hidden' ]
return active if (slot.data.attrs.fullHeight) {
? <div class="active">{slot}</div> classes.push('full-height')
: <div class="hidden"></div>
} }
return <div class={active ? 'active' : 'hidden' }>{slot}</div> const renderSlot = (!this.renderOnlyFocused || active)
? slot
: ''
return (
<div class={classes}>
{
this.sideTabBar
? <h1 class="mobile-label">{slot.data.attrs.label}</h1>
: ''
}
{renderSlot}
</div>
)
}) })
return ( return (
<div class="tab-switcher"> <div class={'tab-switcher ' + (this.sideTabBar ? 'side-tabs' : 'top-tabs')}>
<div class="tabs"> <div class="tabs">
{tabs} {tabs}
</div> </div>
<div class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')}> <div ref="contents" class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')}>
{contents} {contents}
</div> </div>
</div> </div>

View file

@ -2,7 +2,144 @@
.tab-switcher { .tab-switcher {
display: flex; display: flex;
flex-direction: column;
.tab-icon {
font-size: 2em;
display: block;
}
&.top-tabs {
flex-direction: column;
> .tabs {
width: 100%;
overflow-y: hidden;
overflow-x: auto;
padding-top: 5px;
flex-direction: row;
&::after, &::before {
content: '';
flex: 1 1 auto;
border-bottom: 1px solid;
border-bottom-color: $fallback--border;
border-bottom-color: var(--border, $fallback--border);
}
.tab-wrapper {
height: 28px;
&:not(.active)::after {
left: 0;
right: 0;
bottom: 0;
border-bottom: 1px solid;
border-bottom-color: $fallback--border;
border-bottom-color: var(--border, $fallback--border);
}
}
.tab {
width: 100%;
min-width: 1px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
padding-bottom: 99px;
margin-bottom: 6px - 99px;
}
}
.contents.scrollable-tabs {
flex-basis: 0;
}
}
&.side-tabs {
flex-direction: row;
@media all and (max-width: 800px) {
overflow-x: auto;
}
> .contents {
flex: 1 1 auto;
}
> .tabs {
flex: 0 0 auto;
overflow-y: auto;
overflow-x: hidden;
flex-direction: column;
&::after, &::before {
flex-shrink: 0;
flex-basis: .5em;
content: '';
border-right: 1px solid;
border-right-color: $fallback--border;
border-right-color: var(--border, $fallback--border);
}
&::after {
flex-grow: 1;
}
&::before {
flex-grow: 0;
}
.tab-wrapper {
min-width: 10em;
display: flex;
flex-direction: column;
@media all and (max-width: 800px) {
min-width: 1em;
}
&:not(.active)::after {
top: 0;
right: 0;
bottom: 0;
border-right: 1px solid;
border-right-color: $fallback--border;
border-right-color: var(--border, $fallback--border);
}
&::before {
flex: 0 0 6px;
content: '';
border-right: 1px solid;
border-right-color: $fallback--border;
border-right-color: var(--border, $fallback--border);
}
&:last-child .tab {
margin-bottom: 0;
}
}
.tab {
flex: 1;
box-sizing: content-box;
min-width: 10em;
min-width: 1px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
padding-left: 1em;
padding-right: calc(1em + 200px);
margin-right: -200px;
margin-left: 1em;
@media all and (max-width: 800px) {
padding-left: .25em;
padding-right: calc(.25em + 200px);
margin-right: calc(.25em - 200px);
margin-left: .25em;
.text {
display: none
}
}
}
}
}
.contents { .contents {
flex: 1 0 auto; flex: 1 0 auto;
@ -11,88 +148,89 @@
.hidden { .hidden {
display: none; display: none;
} }
.full-height:not(.hidden) {
height: 100%;
display: flex;
flex-direction: column;
> *:not(.mobile-label) {
flex: 1;
}
}
&.scrollable-tabs { &.scrollable-tabs {
flex-basis: 0;
overflow-y: auto; overflow-y: auto;
} }
} }
.tab {
position: relative;
white-space: nowrap;
padding: 6px 1em;
background-color: $fallback--fg;
background-color: var(--tab, $fallback--fg);
&, &:active .tab-icon {
color: $fallback--text;
color: var(--tabText, $fallback--text);
}
&:not(.active) {
z-index: 4;
&:hover {
z-index: 6;
}
}
&.active {
background: transparent;
z-index: 5;
color: $fallback--text;
color: var(--tabActiveText, $fallback--text);
}
img {
max-height: 26px;
vertical-align: top;
margin-top: -5px;
}
}
.tabs { .tabs {
display: flex; display: flex;
position: relative; position: relative;
width: 100%;
overflow-y: hidden;
overflow-x: auto;
padding-top: 5px;
box-sizing: border-box; box-sizing: border-box;
&::after, &::before { &::after, &::before {
display: block; display: block;
content: '';
flex: 1 1 auto; flex: 1 1 auto;
border-bottom: 1px solid;
border-bottom-color: $fallback--border;
border-bottom-color: var(--border, $fallback--border);
} }
}
.tab-wrapper { .tab-wrapper {
height: 28px; position: relative;
position: relative; display: flex;
display: flex; flex: 0 0 auto;
flex: 0 0 auto;
.tab { &:not(.active) {
width: 100%; &::after {
min-width: 1px; content: '';
position: relative; position: absolute;
border-bottom-left-radius: 0; z-index: 7;
border-bottom-right-radius: 0;
padding: 6px 1em;
padding-bottom: 99px;
margin-bottom: 6px - 99px;
white-space: nowrap;
color: $fallback--text;
color: var(--tabText, $fallback--text);
background-color: $fallback--fg;
background-color: var(--tab, $fallback--fg);
&:not(.active) {
z-index: 4;
&:hover {
z-index: 6;
}
}
&.active {
background: transparent;
z-index: 5;
color: $fallback--text;
color: var(--tabActiveText, $fallback--text);
}
img {
max-height: 26px;
vertical-align: top;
margin-top: -5px;
}
}
&:not(.active) {
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
z-index: 7;
border-bottom: 1px solid;
border-bottom-color: $fallback--border;
border-bottom-color: var(--border, $fallback--border);
}
} }
} }
}
.mobile-label {
padding-left: .3em;
padding-bottom: .25em;
margin-top: .5em;
margin-left: .2em;
margin-bottom: .25em;
border-bottom: 1px solid var(--border, $fallback--border);
@media all and (min-width: 800px) {
display: none;
}
} }
} }

View file

@ -50,15 +50,6 @@
> >
{{ user.name }} {{ user.name }}
</div> </div>
<router-link
v-if="!isOtherUser"
:to="{ name: 'user-settings' }"
>
<i
class="button-icon icon-wrench usersettings"
:title="$t('tool_tip.user_settings')"
/>
</router-link>
<a <a
v-if="isOtherUser && !user.is_local" v-if="isOtherUser && !user.is_local"
:href="user.statusnet_profile_url" :href="user.statusnet_profile_url"
@ -118,7 +109,7 @@
type="color" type="color"
> >
<label <label
for="style-switcher" for="theme_tab"
class="userHighlightSel select" class="userHighlightSel select"
> >
<select <select

View file

@ -3,6 +3,7 @@ import UserCard from '../user_card/user_card.vue'
import FollowCard from '../follow_card/follow_card.vue' import FollowCard from '../follow_card/follow_card.vue'
import Timeline from '../timeline/timeline.vue' import Timeline from '../timeline/timeline.vue'
import Conversation from '../conversation/conversation.vue' import Conversation from '../conversation/conversation.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
import List from '../list/list.vue' import List from '../list/list.vue'
import withLoadMore from '../../hocs/with_load_more/with_load_more' import withLoadMore from '../../hocs/with_load_more/with_load_more'
@ -123,6 +124,14 @@ const UserProfile = {
onTabSwitch (tab) { onTabSwitch (tab) {
this.tab = tab this.tab = tab
this.$router.replace({ query: { tab } }) this.$router.replace({ query: { tab } })
},
linkClicked ({ target }) {
if (target.tagName === 'SPAN') {
target = target.parentNode
}
if (target.tagName === 'A') {
window.open(target.href, '_blank')
}
} }
}, },
watch: { watch: {
@ -146,6 +155,7 @@ const UserProfile = {
FollowerList, FollowerList,
FriendList, FriendList,
FollowCard, FollowCard,
TabSwitcher,
Conversation Conversation
} }
} }

View file

@ -11,6 +11,31 @@
:allow-zooming-avatar="true" :allow-zooming-avatar="true"
rounded="top" rounded="top"
/> />
<div
v-if="user.fields_html && user.fields_html.length > 0"
class="user-profile-fields"
>
<dl
v-for="(field, index) in user.fields_html"
:key="index"
class="user-profile-field"
>
<!-- eslint-disable vue/no-v-html -->
<dt
:title="user.fields_text[index].name"
class="user-profile-field-name"
@click.prevent="linkClicked"
v-html="field.name"
/>
<dd
:title="user.fields_text[index].value"
class="user-profile-field-value"
@click.prevent="linkClicked"
v-html="field.value"
/>
<!-- eslint-enable vue/no-v-html -->
</dl>
</div>
<tab-switcher <tab-switcher
:active-tab="tab" :active-tab="tab"
:render-only-focused="true" :render-only-focused="true"
@ -108,11 +133,60 @@
<script src="./user_profile.js"></script> <script src="./user_profile.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss';
.user-profile { .user-profile {
flex: 2; flex: 2;
flex-basis: 500px; flex-basis: 500px;
.user-profile-fields {
margin: 0 0.5em;
img {
object-fit: contain;
vertical-align: middle;
max-width: 100%;
max-height: 400px;
&.emoji {
width: 18px;
height: 18px;
}
}
.user-profile-field {
display: flex;
margin: 0.25em auto;
max-width: 32em;
border: 1px solid var(--border, $fallback--border);
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);
.user-profile-field-name {
flex: 0 1 30%;
font-weight: 500;
text-align: right;
color: var(--lightText);
min-width: 120px;
border-right: 1px solid var(--border, $fallback--border);
}
.user-profile-field-value {
flex: 1 1 70%;
color: var(--text);
margin: 0 0 0 0.25em;
}
.user-profile-field-name, .user-profile-field-value {
line-height: 18px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding: 0.5em 1.5em;
box-sizing: border-box;
}
}
}
.userlist-placeholder { .userlist-placeholder {
display: flex; display: flex;
justify-content: center; justify-content: center;

View file

@ -1,411 +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,
mascot: this.$store.state.users.currentUser.mascot,
mascotPreview: null,
mascotUploadError: 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')
this.$store.dispatch('fetchMascot')
},
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
})
},
submitMascot () {
if (!this.mascotPreview) { return }
let mascot = this.mascot
this.mascotUploading = true
this.$store.state.api.backendInteractor.updateMascot({ mascot }).then((data) => {
if (!data.error) {
this.mascotPreview = null
this.$store.commit('updateMascot', data.url)
} else {
this.mascotUploadError = this.$t('upload.error.base') + ' ' + data.error
}
this.mascotUploading = 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

View file

@ -1,778 +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 class="setting-item">
<h2>{{ $t('settings.mascot') }}</h2>
<p>{{ $t('settings.current_mascot') }}</p>
<img
:src="user.mascot"
class="current-mascot"
>
<p>{{ $t('settings.set_new_mascot') }}</p>
<img
v-if="mascotPreview"
class="mascot"
:src="mascotPreview"
>
<div>
<input
type="file"
@change="uploadFile('mascot', $event)"
>
</div>
<i
v-if="mascotUploading"
class=" icon-spin4 animate-spin uploading"
/>
<button
v-else-if="mascotPreview"
class="btn btn-default"
@click="submitMascot"
>
{{ $t('general.submit') }}
</button>
<div
v-if="mascotUploadError"
class="alert error"
>
Error: {{ mascotUploadError }}
<i
class="button-icon icon-cancel"
@click="clearUploadError('mascot')"
/>
</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);
}
.current-mascot {
display: block;
max-height: 250px;
}
.mascot {
max-width: 100%;
}
.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>

View file

@ -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": "البحث عن مستخدِم"
},
"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": "نص صافٍ"
},
"content_warning": "الموضوع (اختياري)",
"default": "وصلت للتوّ إلى لوس أنجلس.",
"direct_warning": "",
"posting": "النشر",
"scope": {
"direct": "",
"private": "",
"public": "علني - يُنشر على الخيوط الزمنية العمومية",
"unlisted": "غير مُدرَج - لا يُنشَر على الخيوط الزمنية العمومية"
}
},
"registration": {
"bio": "السيرة الذاتية",
"email": "عنوان البريد الإلكتروني",
"fullname": "الإسم المعروض",
"password_confirm": "تأكيد الكلمة السرية",
"registration": "التسجيل",
"token": "رمز الدعوة"
},
"settings": {
"attachmentRadius": "المُرفَقات",
"attachments": "المُرفَقات",
"autoload": "",
"avatar": "الصورة الرمزية",
"avatarAltRadius": "الصور الرمزية (الإشعارات)",
"avatarRadius": "الصور الرمزية",
"background": "الخلفية",
"bio": "السيرة الذاتية",
"btnRadius": "الأزرار",
"cBlue": "أزرق (الرد، المتابَعة)",
"cGreen": "أخضر (إعادة النشر)",
"cOrange": "برتقالي (مفضلة)",
"cRed": "أحمر (إلغاء)",
"change_password": "تغيير كلمة السر",
"change_password_error": "وقع هناك خلل أثناء تعديل كلمتك السرية.",
"changed_password": "تم تغيير كلمة المرور بنجاح!",
"collapse_subject": "",
"confirm_new_password": "تأكيد كلمة السر الجديدة",
"current_avatar": "صورتك الرمزية الحالية",
"current_password": "كلمة السر الحالية",
"current_profile_banner": "الرأسية الحالية لصفحتك الشخصية",
"data_import_export_tab": "تصدير واستيراد البيانات",
"default_vis": "أسلوب العرض الافتراضي",
"delete_account": "حذف الحساب",
"delete_account_description": "حذف حسابك و كافة منشوراتك نهائيًا.",
"delete_account_error": "",
"delete_account_instructions": "يُرجى إدخال كلمتك السرية أدناه لتأكيد عملية حذف الحساب.",
"export_theme": "حفظ النموذج",
"filtering": "التصفية",
"filtering_explanation": "سيتم إخفاء كافة المنشورات التي تحتوي على هذه الكلمات، كلمة واحدة في كل سطر",
"follow_export": "تصدير الاشتراكات",
"follow_export_button": "تصدير الاشتراكات كملف csv",
"follow_export_processing": "التصدير جارٍ، سوف يُطلَب منك تنزيل ملفك بعد حين",
"follow_import": "استيراد الاشتراكات",
"follow_import_error": "خطأ أثناء استيراد المتابِعين",
"follows_imported": "",
"foreground": "الأمامية",
"general": "الإعدادات العامة",
"hide_attachments_in_convo": "إخفاء المرفقات على المحادثات",
"hide_attachments_in_tl": "إخفاء المرفقات على الخيط الزمني",
"hide_post_stats": "",
"hide_user_stats": "",
"import_followers_from_a_csv_file": "",
"import_theme": "تحميل نموذج",
"inputRadius": "",
"instance_default": "",
"interfaceLanguage": "لغة الواجهة",
"invalid_theme_imported": "",
"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": "للمتابعة"
} }
} },
"registration": {
"bio": "السيرة الذاتية",
"email": "عنوان البريد الإلكتروني",
"fullname": "الإسم المعروض",
"password_confirm": "تأكيد الكلمة السرية",
"registration": "التسجيل",
"token": "رمز الدعوة"
},
"settings": {
"attachmentRadius": "المُرفَقات",
"attachments": "المُرفَقات",
"autoload": "",
"avatar": "الصورة الرمزية",
"avatarAltRadius": "الصور الرمزية (الإشعارات)",
"avatarRadius": "الصور الرمزية",
"background": "الخلفية",
"bio": "السيرة الذاتية",
"btnRadius": "الأزرار",
"cBlue": "أزرق (الرد، المتابَعة)",
"cGreen": "أخضر (إعادة النشر)",
"cOrange": "برتقالي (مفضلة)",
"cRed": "أحمر (إلغاء)",
"change_password": "تغيير كلمة السر",
"change_password_error": "وقع هناك خلل أثناء تعديل كلمتك السرية.",
"changed_password": "تم تغيير كلمة المرور بنجاح!",
"collapse_subject": "",
"confirm_new_password": "تأكيد كلمة السر الجديدة",
"current_avatar": "صورتك الرمزية الحالية",
"current_password": "كلمة السر الحالية",
"current_profile_banner": "الرأسية الحالية لصفحتك الشخصية",
"data_import_export_tab": "تصدير واستيراد البيانات",
"default_vis": "أسلوب العرض الافتراضي",
"delete_account": "حذف الحساب",
"delete_account_description": "حذف حسابك و كافة منشوراتك نهائيًا.",
"delete_account_error": "",
"delete_account_instructions": "يُرجى إدخال كلمتك السرية أدناه لتأكيد عملية حذف الحساب.",
"export_theme": "حفظ النموذج",
"filtering": "التصفية",
"filtering_explanation": "سيتم إخفاء كافة المنشورات التي تحتوي على هذه الكلمات، كلمة واحدة في كل سطر",
"follow_export": "تصدير الاشتراكات",
"follow_export_button": "تصدير الاشتراكات كملف csv",
"follow_export_processing": "التصدير جارٍ، سوف يُطلَب منك تنزيل ملفك بعد حين",
"follow_import": "استيراد الاشتراكات",
"follow_import_error": "خطأ أثناء استيراد المتابِعين",
"follows_imported": "",
"foreground": "الأمامية",
"general": "الإعدادات العامة",
"hide_attachments_in_convo": "إخفاء المرفقات على المحادثات",
"hide_attachments_in_tl": "إخفاء المرفقات على الخيط الزمني",
"hide_post_stats": "",
"hide_user_stats": "",
"import_followers_from_a_csv_file": "",
"import_theme": "تحميل نموذج",
"inputRadius": "",
"instance_default": "",
"interfaceLanguage": "لغة الواجهة",
"invalid_theme_imported": "",
"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": "للمتابعة"
}
}

File diff suppressed because it is too large Load diff

View file

@ -34,9 +34,9 @@
}, },
"domain_mute_card": { "domain_mute_card": {
"mute": "Mute", "mute": "Mute",
"mute_progress": "Muting...", "mute_progress": "Muting",
"unmute": "Unmute", "unmute": "Unmute",
"unmute_progress": "Unmuting..." "unmute_progress": "Unmuting"
}, },
"exporter": { "exporter": {
"export": "Export", "export": "Export",
@ -59,7 +59,10 @@
"apply": "Apply", "apply": "Apply",
"submit": "Submit", "submit": "Submit",
"more": "More", "more": "More",
"loading": "Loading…",
"generic_error": "An error occured", "generic_error": "An error occured",
"error_retry": "Please try again",
"retry": "Try again",
"optional": "optional", "optional": "optional",
"show_more": "Show more", "show_more": "Show more",
"show_less": "Show less", "show_less": "Show less",
@ -68,7 +71,9 @@
"disable": "Disable", "disable": "Disable",
"enable": "Enable", "enable": "Enable",
"confirm": "Confirm", "confirm": "Confirm",
"verify": "Verify" "verify": "Verify",
"close": "Close",
"peek": "Peek"
}, },
"image_cropper": { "image_cropper": {
"crop_picture": "Crop picture", "crop_picture": "Crop picture",
@ -94,9 +99,9 @@
"enter_recovery_code": "Enter a recovery code", "enter_recovery_code": "Enter a recovery code",
"enter_two_factor_code": "Enter a two-factor code", "enter_two_factor_code": "Enter a two-factor code",
"recovery_code": "Recovery code", "recovery_code": "Recovery code",
"heading" : { "heading": {
"totp" : "Two-factor authentication", "totp": "Two-factor authentication",
"recovery" : "Two-factor recovery" "recovery": "Two-factor recovery"
} }
}, },
"media_modal": { "media_modal": {
@ -121,7 +126,7 @@
"preferences": "Preferences" "preferences": "Preferences"
}, },
"notifications": { "notifications": {
"broken_favorite": "Unknown status, searching for it...", "broken_favorite": "Unknown status, searching for it",
"favorited_you": "favorited your status", "favorited_you": "favorited your status",
"followed_you": "followed you", "followed_you": "followed you",
"follow_request": "wants to follow you", "follow_request": "wants to follow you",
@ -226,17 +231,17 @@
"security": "Security", "security": "Security",
"enter_current_password_to_confirm": "Enter your current password to confirm your identity", "enter_current_password_to_confirm": "Enter your current password to confirm your identity",
"mfa": { "mfa": {
"otp" : "OTP", "otp": "OTP",
"setup_otp" : "Setup OTP", "setup_otp": "Setup OTP",
"wait_pre_setup_otp" : "presetting OTP", "wait_pre_setup_otp": "presetting OTP",
"confirm_and_enable" : "Confirm & enable OTP", "confirm_and_enable": "Confirm & enable OTP",
"title": "Two-factor Authentication", "title": "Two-factor Authentication",
"generate_new_recovery_codes" : "Generate new recovery codes", "generate_new_recovery_codes": "Generate new recovery codes",
"warning_of_generate_new_codes" : "When you generate new recovery codes, your old codes wont work anymore.", "warning_of_generate_new_codes": "When you generate new recovery codes, your old codes wont work anymore.",
"recovery_codes" : "Recovery codes.", "recovery_codes": "Recovery codes.",
"waiting_a_recovery_codes": "Receiving backup codes...", "waiting_a_recovery_codes": "Receiving backup codes",
"recovery_codes_warning" : "Write the codes down or save them somewhere secure - otherwise you won't see them again. If you lose access to your 2FA app and recovery codes you'll be locked out of your account.", "recovery_codes_warning": "Write the codes down or save them somewhere secure - otherwise you won't see them again. If you lose access to your 2FA app and recovery codes you'll be locked out of your account.",
"authentication_methods" : "Authentication methods", "authentication_methods": "Authentication methods",
"scan": { "scan": {
"title": "Scan", "title": "Scan",
"desc": "Using your two-factor app, scan this QR code or enter text key:", "desc": "Using your two-factor app, scan this QR code or enter text key:",
@ -279,6 +284,7 @@
"current_mascot": "Your current mascot", "current_mascot": "Your current mascot",
"current_password": "Current password", "current_password": "Current password",
"current_profile_banner": "Your current profile banner", "current_profile_banner": "Your current profile banner",
"mutes_and_blocks": "Mutes and Blocks",
"data_import_export_tab": "Data Import / Export", "data_import_export_tab": "Data Import / Export",
"default_vis": "Default visibility scope", "default_vis": "Default visibility scope",
"delete_account": "Delete Account", "delete_account": "Delete Account",
@ -398,7 +404,7 @@
"theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.", "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
"theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.", "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
"tooltipRadius": "Tooltips/alerts", "tooltipRadius": "Tooltips/alerts",
"type_domains_to_mute": "Type in domains to mute", "type_domains_to_mute": "Search domains to mute",
"upload_a_photo": "Upload a photo", "upload_a_photo": "Upload a photo",
"user_settings": "User Settings", "user_settings": "User Settings",
"values": { "values": {
@ -624,7 +630,9 @@
"mute_conversation": "Mute conversation", "mute_conversation": "Mute conversation",
"unmute_conversation": "Unmute conversation", "unmute_conversation": "Unmute conversation",
"status_unavailable": "Status unavailable", "status_unavailable": "Status unavailable",
"copy_link": "Copy link to status" "copy_link": "Copy link to status",
"thread_muted": "Thread muted",
"thread_muted_and_words": ", has words:"
}, },
"user_card": { "user_card": {
"approve": "Approve", "approve": "Approve",
@ -654,11 +662,11 @@
"subscribe": "Subscribe", "subscribe": "Subscribe",
"unsubscribe": "Unsubscribe", "unsubscribe": "Unsubscribe",
"unblock": "Unblock", "unblock": "Unblock",
"unblock_progress": "Unblocking...", "unblock_progress": "Unblocking",
"block_progress": "Blocking...", "block_progress": "Blocking",
"unmute": "Unmute", "unmute": "Unmute",
"unmute_progress": "Unmuting...", "unmute_progress": "Unmuting",
"mute_progress": "Muting...", "mute_progress": "Muting",
"hide_repeats": "Hide repeats", "hide_repeats": "Hide repeats",
"show_repeats": "Show repeats", "show_repeats": "Show repeats",
"admin_menu": { "admin_menu": {
@ -709,7 +717,7 @@
"accept_follow_request": "Accept follow request", "accept_follow_request": "Accept follow request",
"reject_follow_request": "Reject follow request" "reject_follow_request": "Reject follow request"
}, },
"upload":{ "upload": {
"error": { "error": {
"base": "Upload failed.", "base": "Upload failed.",
"file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", "file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",

View file

@ -57,12 +57,12 @@
"enter_recovery_code": "Inserta el código de recuperación", "enter_recovery_code": "Inserta el código de recuperación",
"enter_two_factor_code": "Inserta el código de dos factores", "enter_two_factor_code": "Inserta el código de dos factores",
"recovery_code": "Código de recuperación", "recovery_code": "Código de recuperación",
"heading" : { "heading": {
"totp" : "Autenticación de dos factores", "totp": "Autenticación de dos factores",
"recovery" : "Recuperación de dos factores" "recovery": "Recuperación de dos factores"
} }
}, },
"media_modal": { "media_modal": {
"previous": "Anterior", "previous": "Anterior",
"next": "Siguiente" "next": "Siguiente"
}, },
@ -103,7 +103,7 @@
"single_choice": "Elección única", "single_choice": "Elección única",
"multiple_choices": "Elección múltiple", "multiple_choices": "Elección múltiple",
"expiry": "Tiempo de vida de la encuesta", "expiry": "Tiempo de vida de la encuesta",
"expires_in": "La encuensta termina en {0}", "expires_in": "La encuesta termina en {0}",
"expired": "La encuesta terminó hace {0}", "expired": "La encuesta terminó hace {0}",
"not_enough_options": "Muy pocas opciones únicas en la encuesta" "not_enough_options": "Muy pocas opciones únicas en la encuesta"
}, },
@ -137,7 +137,7 @@
}, },
"content_warning": "Tema (opcional)", "content_warning": "Tema (opcional)",
"default": "Acabo de aterrizar en L.A.", "default": "Acabo de aterrizar en L.A.",
"direct_warning_to_all": "Esta publicación será visible para todos los usarios mencionados.", "direct_warning_to_all": "Esta publicación será visible para todos los usuarios mencionados.",
"direct_warning_to_first_only": "Esta publicación solo será visible para los usuarios mencionados al comienzo del mensaje.", "direct_warning_to_first_only": "Esta publicación solo será visible para los usuarios mencionados al comienzo del mensaje.",
"posting": "Publicando", "posting": "Publicando",
"scope_notice": { "scope_notice": {
@ -146,7 +146,7 @@
"unlisted": "Esta publicación no será visible en la Línea Temporal Pública ni en Toda La Red Conocida" "unlisted": "Esta publicación no será visible en la Línea Temporal Pública ni en Toda La Red Conocida"
}, },
"scope": { "scope": {
"direct": "Directo - Solo para los usuarios mencionados.", "direct": "Directo - Solo para los usuarios mencionados",
"private": "Solo-seguidores - Solo tus seguidores leerán la publicación", "private": "Solo-seguidores - Solo tus seguidores leerán la publicación",
"public": "Público - Entradas visibles en las Líneas Temporales Públicas", "public": "Público - Entradas visibles en las Líneas Temporales Públicas",
"unlisted": "Sin listar - Entradas no visibles en las Líneas Temporales Públicas" "unlisted": "Sin listar - Entradas no visibles en las Líneas Temporales Públicas"
@ -173,7 +173,7 @@
"password_confirmation_match": "la contraseña no coincide" "password_confirmation_match": "la contraseña no coincide"
} }
}, },
"selectable_list": { "selectable_list": {
"select_all": "Seleccionar todo" "select_all": "Seleccionar todo"
}, },
"settings": { "settings": {
@ -181,17 +181,17 @@
"security": "Seguridad", "security": "Seguridad",
"enter_current_password_to_confirm": "Introduce la contraseña actual para confirmar tu identidad", "enter_current_password_to_confirm": "Introduce la contraseña actual para confirmar tu identidad",
"mfa": { "mfa": {
"otp" : "OTP", "otp": "OTP",
"setup_otp" : "Configurar OTP", "setup_otp": "Configurar OTP",
"wait_pre_setup_otp" : "preconfiguración OTP", "wait_pre_setup_otp": "preconfiguración OTP",
"confirm_and_enable" : "Confirmar y habilitar OTP", "confirm_and_enable": "Confirmar y habilitar OTP",
"title": "Autentificación de dos factores", "title": "Autentificación de dos factores",
"generate_new_recovery_codes" : "Generar códigos de recuperación nuevos", "generate_new_recovery_codes": "Generar códigos de recuperación nuevos",
"warning_of_generate_new_codes" : "Cuando generas nuevos códigos de recuperación, los antiguos dejarán de funcionar.", "warning_of_generate_new_codes": "Cuando generas nuevos códigos de recuperación, los antiguos dejarán de funcionar.",
"recovery_codes" : "Códigos de recuperación.", "recovery_codes": "Códigos de recuperación.",
"waiting_a_recovery_codes": "Recibiendo códigos de respaldo", "waiting_a_recovery_codes": "Recibiendo códigos de respaldo",
"recovery_codes_warning" : "Anote los códigos o guárdelos en un lugar seguro, de lo contrario no los volverá a ver. Si pierde el acceso a su aplicación 2FA y los códigos de recuperación, su cuenta quedará bloqueada.", "recovery_codes_warning": "Anote los códigos o guárdelos en un lugar seguro, de lo contrario no los volverá a ver. Si pierde el acceso a su aplicación 2FA y los códigos de recuperación, su cuenta quedará bloqueada.",
"authentication_methods" : "Métodos de autentificación", "authentication_methods": "Métodos de autentificación",
"scan": { "scan": {
"title": "Escanear", "title": "Escanear",
"desc": "Usando su aplicación de dos factores, escanee este código QR o ingrese la clave de texto:", "desc": "Usando su aplicación de dos factores, escanee este código QR o ingrese la clave de texto:",
@ -210,7 +210,7 @@
"background": "Fondo", "background": "Fondo",
"bio": "Biografía", "bio": "Biografía",
"block_export": "Exportar usuarios bloqueados", "block_export": "Exportar usuarios bloqueados",
"block_export_button": "Exporta la lista de tus usarios bloqueados a un archivo csv", "block_export_button": "Exporta la lista de tus usuarios bloqueados a un archivo csv",
"block_import": "Importar usuarios bloqueados", "block_import": "Importar usuarios bloqueados",
"block_import_error": "Error importando la lista de usuarios bloqueados", "block_import_error": "Error importando la lista de usuarios bloqueados",
"blocks_imported": "¡Lista de usuarios bloqueados importada! El procesado puede tardar un poco.", "blocks_imported": "¡Lista de usuarios bloqueados importada! El procesado puede tardar un poco.",
@ -222,7 +222,7 @@
"cRed": "Rojo (Cancelar)", "cRed": "Rojo (Cancelar)",
"change_password": "Cambiar contraseña", "change_password": "Cambiar contraseña",
"change_password_error": "Hubo un problema cambiando la contraseña.", "change_password_error": "Hubo un problema cambiando la contraseña.",
"changed_password": "Contraseña cambiada correctamente!", "changed_password": "¡Contraseña cambiada correctamente!",
"collapse_subject": "Colapsar entradas con tema", "collapse_subject": "Colapsar entradas con tema",
"composing": "Redactando", "composing": "Redactando",
"confirm_new_password": "Confirmar la nueva contraseña", "confirm_new_password": "Confirmar la nueva contraseña",
@ -286,7 +286,7 @@
"notification_visibility_repeats": "Repeticiones (Repeats)", "notification_visibility_repeats": "Repeticiones (Repeats)",
"no_rich_text_description": "Eliminar el formato de texto enriquecido de todas las entradas", "no_rich_text_description": "Eliminar el formato de texto enriquecido de todas las entradas",
"no_blocks": "No hay usuarios bloqueados", "no_blocks": "No hay usuarios bloqueados",
"no_mutes": "No hay usuarios sinlenciados", "no_mutes": "No hay usuarios silenciados",
"hide_follows_description": "No mostrar a quién sigo", "hide_follows_description": "No mostrar a quién sigo",
"hide_followers_description": "No mostrar quién me sigue", "hide_followers_description": "No mostrar quién me sigue",
"hide_follows_count_description": "No mostrar el número de cuentas que sigo", "hide_follows_count_description": "No mostrar el número de cuentas que sigo",
@ -305,7 +305,7 @@
"profile_background": "Fondo del Perfil", "profile_background": "Fondo del Perfil",
"profile_banner": "Cabecera del Perfil", "profile_banner": "Cabecera del Perfil",
"profile_tab": "Perfil", "profile_tab": "Perfil",
"radii_help": "Estable el redondeo de las esquinas de la interfaz (en píxeles)", "radii_help": "Establezca el redondeo de las esquinas de la interfaz (en píxeles)",
"replies_in_timeline": "Réplicas en la línea temporal", "replies_in_timeline": "Réplicas en la línea temporal",
"reply_link_preview": "Activar la previsualización del enlace de responder al pasar el ratón por encima", "reply_link_preview": "Activar la previsualización del enlace de responder al pasar el ratón por encima",
"reply_visibility_all": "Mostrar todas las réplicas", "reply_visibility_all": "Mostrar todas las réplicas",
@ -337,7 +337,7 @@
"theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación. Use el botón \"Borrar todo\" para deshacer los cambios.", "theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación. Use el botón \"Borrar todo\" para deshacer los cambios.",
"theme_help_v2_2": "Los iconos debajo de algunas entradas son indicadores de contraste de fondo/texto, desplace el ratón por encima para obtener información más detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.", "theme_help_v2_2": "Los iconos debajo de algunas entradas son indicadores de contraste de fondo/texto, desplace el ratón por encima para obtener información más detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.",
"tooltipRadius": "Información/alertas", "tooltipRadius": "Información/alertas",
"upload_a_photo": "Subir una foto", "upload_a_photo": "Subir una foto",
"user_settings": "Ajustes del Usuario", "user_settings": "Ajustes del Usuario",
"values": { "values": {
"false": "no", "false": "no",
@ -583,7 +583,7 @@
"profile_does_not_exist": "Lo sentimos, este perfil no existe.", "profile_does_not_exist": "Lo sentimos, este perfil no existe.",
"profile_loading_error": "Lo sentimos, hubo un error al cargar este perfil." "profile_loading_error": "Lo sentimos, hubo un error al cargar este perfil."
}, },
"user_reporting": { "user_reporting": {
"title": "Reportando a {0}", "title": "Reportando a {0}",
"add_comment_description": "El informe será enviado a los moderadores de su instancia. Puedes proporcionar una explicación de por qué estás reportando esta cuenta a continuación:", "add_comment_description": "El informe será enviado a los moderadores de su instancia. Puedes proporcionar una explicación de por qué estás reportando esta cuenta a continuación:",
"additional_comments": "Comentarios adicionales", "additional_comments": "Comentarios adicionales",
@ -603,7 +603,7 @@
"favorite": "Favorito", "favorite": "Favorito",
"user_settings": "Ajustes de usuario" "user_settings": "Ajustes de usuario"
}, },
"upload":{ "upload": {
"error": { "error": {
"base": "Subida fallida.", "base": "Subida fallida.",
"file_too_big": "Archivo demasiado grande [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", "file_too_big": "Archivo demasiado grande [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
@ -635,4 +635,4 @@
"too_many_requests": "Has alcanzado el límite de intentos, vuelve a intentarlo más tarde.", "too_many_requests": "Has alcanzado el límite de intentos, vuelve a intentarlo más tarde.",
"password_reset_disabled": "El restablecimiento de contraseñas está deshabilitado. Póngase en contacto con el administrador de su instancia." "password_reset_disabled": "El restablecimiento de contraseñas está deshabilitado. Póngase en contacto con el administrador de su instancia."
} }
} }

View file

@ -4,7 +4,19 @@
"find_user": "Otsi kasutajaid" "find_user": "Otsi kasutajaid"
}, },
"general": { "general": {
"submit": "Postita" "submit": "Postita",
"verify": "Kinnita",
"confirm": "Kinnita",
"enable": "Luba",
"disable": "Keela",
"cancel": "Tühista",
"dismiss": "Olgu",
"show_less": "Kuva vähem",
"show_more": "Kuva rohkem",
"optional": "valikuline",
"generic_error": "Esines viga",
"more": "Rohkem",
"apply": "Rakenda"
}, },
"login": { "login": {
"login": "Logi sisse", "login": "Logi sisse",
@ -12,29 +24,95 @@
"password": "Parool", "password": "Parool",
"placeholder": "nt lain", "placeholder": "nt lain",
"register": "Registreeru", "register": "Registreeru",
"username": "Kasutajanimi" "username": "Kasutajanimi",
"heading": {
"recovery": "Kaheastmelise autentimise taaste",
"totp": "Kaheastmeline autentimine"
},
"recovery_code": "Taastekood",
"enter_two_factor_code": "Sisesta kaheastmelise autentimise kood",
"enter_recovery_code": "Sisesta taastekood",
"authentication_code": "Autentimiskood",
"hint": "Logi sisse, et liituda vestlusega",
"description": "Logi sisse OAuthiga"
}, },
"nav": { "nav": {
"mentions": "Mainimised", "mentions": "Mainimised",
"public_tl": "Avalik Ajajoon", "public_tl": "Avalik Ajajoon",
"timeline": "Ajajoon", "timeline": "Ajajoon",
"twkn": "Kogu Teadaolev Võrgustik" "twkn": "Kogu Teadaolev Võrgustik",
"preferences": "Eelistused",
"who_to_follow": "Keda jälgida",
"search": "Otsing",
"user_search": "Kasutajaotsing",
"dms": "Privaatsõnumid",
"interactions": "Interaktsioonid",
"friend_requests": "Jägimistaotlused",
"chat": "Kohalik vestlus",
"back": "Tagasi",
"administration": "Administreerimine",
"about": "Meist"
}, },
"notifications": { "notifications": {
"followed_you": "alustas sinu jälgimist", "followed_you": "alustas sinu jälgimist",
"notifications": "Teavitused", "notifications": "Teated",
"read": "Loe!" "read": "Loe!",
"reacted_with": "reageeris {0}",
"migrated_to": "kolis",
"no_more_notifications": "Rohkem teateid ei ole",
"repeated_you": "taaspostitas su staatuse",
"load_older": "Laadi vanemad teated",
"follow_request": "soovib Teid jälgida",
"favorited_you": "lisas su staatuse lemmikuks",
"broken_favorite": "Tundmatu staatus, otsin…"
}, },
"post_status": { "post_status": {
"default": "Just sõitsin elektrirongiga Tallinnast Pääskülla.", "default": "Just sõitsin elektrirongiga Tallinnast Pääskülla.",
"posting": "Postitan" "posting": "Postitan",
"scope": {
"unlisted": "Peidetud - Ära postita avalikele ajajoontele",
"public": "Avalil - Postita avalikele ajajoontele",
"private": "Jälgijatele - Postita ainult jälgijatele",
"direct": "Privaatne - Postita ainult mainitud kasutajatele"
},
"scope_notice": {
"unlisted": "See postitus ei ole nähtav avalikul ega kogu võrgu ajajoonel",
"private": "See postitus on nähtav ainult Teie jälgijatele",
"public": "See postitus on nähtav kõigile"
},
"direct_warning_to_first_only": "See postitus on nähtav ainult kirja alguses mainitud kasutajatele.",
"direct_warning_to_all": "See postitus on nähtav kõikidele mainitud kasutajatele.",
"content_warning": "Pealkiri (valikuline)",
"content_type": {
"text/bbcode": "BBCode",
"text/markdown": "Markdown",
"text/html": "HTML",
"text/plain": "Lihttekst"
},
"attachments_sensitive": "Märgi manused sensitiivseks",
"account_not_locked_warning_link": "lukus",
"account_not_locked_warning": "Teie konto ei ole {0}. Kõik võivad Teid jälgida, et näha Teie ainult-jälgijatele postitusi.",
"new_status": "Postita uus staatus"
}, },
"registration": { "registration": {
"bio": "Bio", "bio": "Bio",
"email": "E-post", "email": "E-post",
"fullname": "Kuvatav nimi", "fullname": "Kuvatav nimi",
"password_confirm": "Parooli kinnitamine", "password_confirm": "Parooli kinnitamine",
"registration": "Registreerimine" "registration": "Registreerimine",
"validations": {
"password_confirmation_match": "peaks olema sama kui salasõna",
"password_confirmation_required": "ei saa jätta tühjaks",
"password_required": "ei saa jätta tühjaks",
"email_required": "ei saa jätta tühjaks",
"fullname_required": "ei saa jätta tühjaks",
"username_required": "ei saa jätta tühjaks"
},
"fullname_placeholder": "Näiteks Lain Iwakura",
"username_placeholder": "Näiteks lain",
"new_captcha": "Vajuta pildile, et saada uus captcha",
"captcha": "CAPTCHA",
"token": "Kutse võti"
}, },
"settings": { "settings": {
"attachments": "Manused", "attachments": "Manused",
@ -44,7 +122,7 @@
"current_avatar": "Sinu praegune profiilipilt", "current_avatar": "Sinu praegune profiilipilt",
"current_profile_banner": "Praegune profiilibänner", "current_profile_banner": "Praegune profiilibänner",
"filtering": "Sisu filtreerimine", "filtering": "Sisu filtreerimine",
"filtering_explanation": "Kõiki staatuseid, mis sisaldavad neid sõnu, ei kuvata. Üks sõna reale.", "filtering_explanation": "Kõiki staatuseid, mis sisaldavad neid sõnu, ei kuvata. Üks sõna reale",
"hide_attachments_in_convo": "Peida manused vastlustes", "hide_attachments_in_convo": "Peida manused vastlustes",
"hide_attachments_in_tl": "Peida manused ajajoonel", "hide_attachments_in_tl": "Peida manused ajajoonel",
"name": "Nimi", "name": "Nimi",
@ -58,7 +136,201 @@
"set_new_profile_banner": "Vali uus profiilibänner", "set_new_profile_banner": "Vali uus profiilibänner",
"settings": "Sätted", "settings": "Sätted",
"theme": "Teema", "theme": "Teema",
"user_settings": "Kasutaja sätted" "user_settings": "Kasutaja sätted",
"subject_line_noop": "Ära kopeeri",
"subject_line_mastodon": "Nagu mastodon: kopeeri nagu on",
"subject_line_email": "Nagu e-post: \"vs: pealkiri\"",
"subject_line_behavior": "Kopeeri pealkiri vastamisel",
"subject_input_always_show": "Alati kuva pealkirja välja",
"minimal_scopes_mode": "Peida postituse nähtavussätted",
"scope_copy": "Kopeeri nähtavussätted vastamisel (Privaatsed on alati kopeeritud)",
"security_tab": "Turvalisus",
"search_user_to_mute": "Otsi, keda soovid vaigistada",
"search_user_to_block": "Otsi, keda soovid blokeerida",
"saving_ok": "Sätted salvestatud",
"saving_err": "Sätete salvestamine ebaõnnestus",
"autohide_floating_post_button": "Automaatselt peida uue postituse nupp (mobiilil)",
"reply_visibility_self": "Näita ainult vastuseid, mis on suunatud mulle",
"reply_visibility_following": "Näita ainult vastuseid, mis on suunatud mulle või kasutajatele, keda jälgin",
"reply_visibility_all": "Näita kõiki vastuseid",
"replies_in_timeline": "Vastused ajajoonel",
"radii_help": "Liidese ümardamine (pikslites)",
"profile_tab": "Profiil",
"presets": "Salvestatud sätted",
"pause_on_unfocused": "Peata reaalajas voog kui leht pole fookuses",
"panelRadius": "Paneelid",
"revoke_token": "Keela",
"valid_until": "Kehtiv kuni",
"refresh_token": "Värskendustoken",
"token": "Token",
"oauth_tokens": "OAuth tokenid",
"show_moderator_badge": "Näita Moderaator silti mu profiilil",
"show_admin_badge": "Näita Admin silti mu profiilil",
"hide_followers_count_description": "Ära näita minu jälgijate arvu",
"hide_follows_count_description": "Ära näita minu jälgimiste arvu",
"hide_followers_description": "Ära näita minu jälgijaid",
"hide_follows_description": "Ära näita minu jälgimisi",
"no_mutes": "Vaigistusi pole",
"no_blocks": "Blokeeringuid pole",
"no_rich_text_description": "Muuda kõik postitused lihttekstiks",
"notification_visibility_emoji_reactions": "Reaktsioonid",
"notification_visibility_moves": "Kasutaja kolimised",
"notification_visibility_repeats": "Taaspostitused",
"notification_visibility_mentions": "Mainimised",
"notification_visibility_likes": "Lemmikud",
"notification_visibility_follows": "Jälgimised",
"notification_visibility": "Milliseid teateid kuvatakse",
"new_password": "Uus salasõna",
"new_email": "Uus e-post",
"use_contain_fit": "Näita eelvaadetes täis suuruses pilte",
"play_videos_in_modal": "Näita videoid eraldi raamis",
"mutes_tab": "Vaigistused",
"loop_video_silent_only": "Loop videod, millel pole heli (nt. Mastodoni \"gifid\")",
"loop_video": "Loop videod",
"lock_account_description": "Piira oma konto ainult lubatud jälgijatele",
"links": "Lingid",
"limited_availability": "Pole Teie veebilehitsejas saadaval",
"invalid_theme_imported": "Valitud fail ei ole Pleroma kujundus. Kujundusele muudatusi ei tehtud.",
"interfaceLanguage": "Liidese keel",
"interface": "Liides",
"instance_default_simple": "(vaikimisi)",
"instance_default": "(vaikimisi: {value})",
"checkboxRadius": "Märkeruudud",
"inputRadius": "Sisestuskastid",
"import_theme": "Lae sätted",
"import_followers_from_a_csv_file": "Impordi jälgimised csv failist",
"import_blocks_from_a_csv_file": "Impordi blokeeringud csv failist",
"hide_filtered_statuses": "Peida filtreeritud staatused",
"hide_user_stats": "Peida kasutaja statistika (nt. jälgijate arv)",
"hide_post_stats": "Peida postituse statistika (nt. lemmikute arv)",
"use_one_click_nsfw": "Ava NSFW manused ühe klikiga",
"preload_images": "Piltide eellaadimine",
"hide_isp": "Peida instantsipõhine paneel",
"max_thumbnails": "Maksimaalne lubatud eelvaadete arv postituste kohta",
"hide_muted_posts": "Peida vaigistatud kasutajate postitused",
"general": "Üldine",
"foreground": "Esiplaan",
"accent": "Rõhk",
"follows_imported": "Jälgimised imporditud! Nende töötlemine võtab natuke aega.",
"follow_import_error": "Jälgimiste importimisel tekkis viga",
"follow_import": "Impordi jälgimised",
"follow_export_button": "Ekspordi oma jälgimised csv failiks",
"follow_export": "Ekspordi jälgimised",
"export_theme": "Salvesta sätted",
"emoji_reactions_on_timeline": "Näita reaktsioone ajajoonel",
"pad_emoji": "Lisa emotikonidele tühikud ette ja järgi neid menüüst valides",
"avatar_size_instruction": "Profiilipildi soovitatud minimaalne suurus on 150x150 pikslit.",
"domain_mutes": "Domeenid",
"discoverable": "Luba selle konto ilmumine otsingutulemustes ning muudes teenustes",
"delete_account_instructions": "Konto kustutamise kinnitamiseks sisestage oma salasõna.",
"delete_account_error": "Teie konto kustutamisel tekkis viga. Kui see jätkub, palun võtke kontakti administraatoriga.",
"delete_account_description": "Jäädavalt kustuta oma andmed ja konto.",
"delete_account": "Kustuta konto",
"default_vis": "Vaikimisi nähtavus",
"data_import_export_tab": "Andmete import / eksport",
"current_password": "Praegune salasõna",
"confirm_new_password": "Kinnita uus salasõna",
"composing": "Koostamine",
"collapse_subject": "Peida postituste pealkirjad",
"changed_password": "Salasõna edukalt muudetud!",
"change_password_error": "Esines viga salasõna muutmisel.",
"change_password": "Muuda salasõna",
"changed_email": "E-post edukalt muudetud!",
"change_email_error": "Esines viga e-posti muutmisel.",
"change_email": "Muuda e-posti",
"cRed": "Punane (Tühista)",
"cOrange": "Oranž (Lisa lemmikuks)",
"cGreen": "Roheline (Taaspostita)",
"cBlue": "Sinine (Vasta, jälgi)",
"btnRadius": "Nupud",
"blocks_tab": "Blokeeringud",
"blocks_imported": "Blokeeringud imporditud! Nende töötlemine võtab natuke aega.",
"block_import_error": "Blokeeringute importimisel esines viga",
"block_import": "Blokeeringute import",
"block_export_button": "Ekspordi oma blokeeringud csv failiks",
"block_export": "Blokeeringute eksport",
"background": "Taust",
"avatarRadius": "Profiilipildid",
"avatarAltRadius": "Profiilipildid (Teated)",
"attachmentRadius": "Manused",
"allow_following_move": "Luba automaatjälgimine kui jälgitav konto kolib",
"mfa": {
"verify": {
"desc": "Et lubada kaheastmelist autentimist, sisestage kood oma äpist:"
},
"scan": {
"desc": "Kasutades oma kaheastmelise autentimise äppi, skännige see QR kood või sisestage tekstiline võti:",
"secret_code": "Võti",
"title": "Skänni"
},
"authentication_methods": "Autentimismeetodid",
"recovery_codes_warning": "Kirjutage need koodid üles ning hoidke need kindlas kohas. Kui Te kaotate ligipääsu oma kaheastmelise autentimise äppile ning nendele koodidele, ei ole Teil võimalik oma kontosse sisse logida.",
"waiting_a_recovery_codes": "Laen taastekoode…",
"recovery_codes": "Taastekoodid.",
"warning_of_generate_new_codes": "Kui Te loote uued taastekoodid, Teie vanad koodid ei tööta enam.",
"generate_new_recovery_codes": "Loo uued taastekoodid",
"title": "Kaheastmeline autentimine",
"confirm_and_enable": "Kinnita & luba OTP",
"wait_pre_setup_otp": "sean üles OTP",
"setup_otp": "Sea üles OTP",
"otp": "OTP"
},
"enter_current_password_to_confirm": "Sisetage isiku tõestamiseks oma salasõna",
"security": "Turvalisus",
"app_name": "Rakenduse nimi",
"style": {
"switcher": {
"help": {
"snapshot_present": "Kujunduse eelvaade on laetud, nii et kõik väärtused on üle kirjutatud. Te saate laadida ka kujunduse päris sisu.",
"older_version_imported": "Teie imporditud fail oli loodud vanemas versioonis.",
"future_version_imported": "Teie imporditud fail oli loodud uuemas versioonis.",
"v2_imported": "Teie imporditud fail oli vanema versiooni jaoks. Me üritame hoida ühilduvust, kuid ikkagi võib esineda erinevusi.",
"upgraded_from_v2": "PleromaFE-d uuendati, teie kujundus võib välja näha natuke erinev, kui mäletate."
},
"use_source": "Uus versioon",
"use_snapshot": "Vana versioon",
"keep_as_is": "Jäta nii, nagu on",
"load_theme": "Lae kujundus",
"clear_opacity": "Tühista läbipaistvus",
"clear_all": "Tühista kõik",
"reset": "Taasta algne",
"keep_fonts": "Jäta fondid",
"keep_roundness": "Jäta ümarus",
"keep_opacity": "Jäta läbipaistvus",
"keep_shadows": "Jäta varjud",
"keep_color": "Jäta värvid"
}
},
"enable_web_push_notifications": "Luba veebipõhised push-teated",
"notification_blocks": "Kasutaja blokeerimisel ei tule neilt enam teateid ning nendele teilt ka mitte.",
"notification_setting_privacy_option": "Peida saatja ning sisu push-teadetelt",
"notification_setting": "Saa teateid nendelt:",
"notifications": "Teated",
"notification_mutes": "Kui soovid mõnelt kasutajalt mitte teateid saada, kasuta vaigistust.",
"notification_setting_privacy": "Privaatsus",
"notification_setting_non_followers": "Kasutajatelt, kes sind ei jälgi",
"notification_setting_followers": "Kasutajatelt, kes jälgivad sind",
"notification_setting_non_follows": "Kasutajatelt, keda sa ei jälgi",
"notification_setting_follows": "Kasutajatelt, keda jälgid",
"notification_setting_filters": "Filtrid",
"greentext": "Meemi nooled",
"fun": "Naljad",
"values": {
"true": "jah",
"false": "ei"
},
"upload_a_photo": "Lae üles foto",
"type_domains_to_mute": "Trüki siia domeene, mida vaigistada",
"tooltipRadius": "Vihjed/hoiatused",
"theme_help_v2_1": "Te saate ka mõndade komponentide värvust ning läbipaistvust üle kirjutada vajutades ruudule. Kasuta \"Tühista kõik\" nuppu, et need tühistada.",
"theme_help": "Kasuta hex värvikoode (#rrggbb) oma kujunduse isikupärastamiseks.",
"text": "Tekst",
"useStreamingApiWarning": "(Pole soovituslik, eksperimentaalne, on teada, et jätab postitusi vahele)",
"useStreamingApi": "Saa postitusi ning teateid reaalajas",
"user_mutes": "Kasutajad",
"streaming": "Luba uute postituste automaatvoog kui oled lehekülje alguses",
"stop_gifs": "Mängi GIFid hiirega ületades",
"post_status_content_type": "Postituse sisutüüp"
}, },
"timeline": { "timeline": {
"conversation": "Vestlus", "conversation": "Vestlus",
@ -79,5 +351,111 @@
"muted": "Vaigistatud", "muted": "Vaigistatud",
"per_day": "päevas", "per_day": "päevas",
"statuses": "Staatuseid" "statuses": "Staatuseid"
},
"about": {
"mrf": {
"mrf_policies_desc": "MRF poliitikad mõjutavad selle instansi föderatsiooni käitumist. Järgmised poliitikad on lubatud:",
"simple": {
"media_nsfw_desc": "See instants määrab nendest instantsidest postituste meedia sensitiivseks:",
"media_nsfw": "Meedia määratakse sensitiivseks",
"media_removal_desc": "See instants eemaldab meedia postitustelt nendest instantsidest:",
"media_removal": "Meedia eemaldamine",
"ftl_removal_desc": "See instants eemaldab postitused nendelt instantsidest \"Kogu teatud võrgu\" ajajoonelt:",
"ftl_removal": "\"Kogu teatud võrgu\" ajajoonelt eemaldamine",
"quarantine_desc": "See instants saadab ainult avalikke postitusi järgmistele instantsidele:",
"quarantine": "Karantiini",
"reject_desc": "See instants ei luba sõnumeid nendest instantsidest:",
"reject": "Keela",
"accept_desc": "See instants lubab sõnumeid ainult nendest instantsidest:",
"accept": "Luba",
"simple_policies": "Instansi-omased poliitikad"
},
"mrf_policies": "Lubatud MRF poliitikad",
"keyword": {
"is_replaced_by": "→",
"replace": "Vaheta",
"reject": "Lükka tagasi",
"ftl_removal": "\"Kogu teatud võrgu\" ajajoonelt eemaldamine",
"keyword_policies": "Võtmesõna poliitikad"
},
"federation": "Föderatsioon"
},
"staff": "Personal"
},
"selectable_list": {
"select_all": "Vali kõik"
},
"remote_user_resolver": {
"error": "Ei leitud.",
"searching_for": "Otsin",
"remote_user_resolver": "Kaugkasutaja leidja"
},
"interactions": {
"load_older": "Laadi vanemad interaktsioonid",
"moves": "Kasutaja kolimised",
"follows": "Uued jälgimised",
"favs_repeats": "Taaspostitused ja lemmikud"
},
"emoji": {
"load_all": "Laen kõik {emojiAmount} emotikoni",
"load_all_hint": "Laadisin esimesed {saneAmount} emotikoni, kõike laadides võib esineda probleeme jõudlusega.",
"unicode": "Unicode emotikonid",
"custom": "Kohandatud emotikonid",
"add_emoji": "Lisa emotikon",
"search_emoji": "Otsi emotikone",
"keep_open": "Hoia valija lahti",
"emoji": "Emotikonid",
"stickers": "Kleepsud"
},
"polls": {
"not_enough_options": "Liiga vähe unikaalseid valikuid hääletuses",
"expired": "Hääletus lõppes {0} tagasi",
"expires_in": "Hääletus lõppeb {0}",
"expiry": "Hääletuse vanus",
"multiple_choices": "Mitu vastust",
"single_choice": "Üks vastus",
"type": "Hääletuse tüüp",
"vote": "Hääleta",
"votes": "häält",
"option": "Valik",
"add_option": "Lisa valik",
"add_poll": "Lisa küsitlus"
},
"media_modal": {
"next": "Järgmine",
"previous": "Eelmine"
},
"importer": {
"error": "Faili importimisel tekkis viga.",
"success": "Import õnnestus.",
"submit": "Esita"
},
"image_cropper": {
"cancel": "Tühista",
"save_without_cropping": "Salvesta muudatusteta",
"save": "Salvesta",
"crop_picture": "Modifitseeri pilti"
},
"features_panel": {
"who_to_follow": "Keda jälgida",
"title": "Featuurid",
"text_limit": "Tekstilimiit",
"scope_options": "Ulatuse valikud",
"media_proxy": "Meedia proksi",
"gopher": "Gopher",
"chat": "Vestlus"
},
"exporter": {
"processing": "Töötlemine, Teilt küsitakse varsti faili allalaadimist",
"export": "Ekspordi"
},
"domain_mute_card": {
"unmute_progress": "Eemaldan vaigistuse…",
"unmute": "Ära vaigista",
"mute_progress": "Vaigistan…",
"mute": "Vaigista"
},
"chat": {
"title": "Vestlus"
} }
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,323 +1,551 @@
{ {
"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",
"peek": "Anteprima",
"close": "Chiudi",
"retry": "Riprova",
"error_retry": "Per favore, riprova",
"loading": "Carico…"
},
"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à",
"keep_shadows": "Mantieni ombre",
"keep_color": "Mantieni colori"
},
"common": {
"opacity": "Opacità",
"color": "Colore",
"contrast": {
"context": {
"text": "per il testo",
"18pt": "per il testo grande (oltre 17pt)"
},
"level": {
"bad": "non soddisfa le linee guida di alcun livello",
"aaa": "soddisfa le linee guida di livello AAA (ottimo)",
"aa": "soddisfa le linee guida di livello AA (sufficiente)"
},
"hint": "Il rapporto di contrasto è {ratio}, e {level} {context}"
} }
},
"advanced_colors": {
"badge": "Sfondo medaglie",
"post": "Messaggi / Biografie",
"alert_neutral": "Neutro",
"alert_warning": "Attenzione",
"alert_error": "Errore",
"alert": "Sfondo degli avvertimenti",
"_tab_label": "Avanzate",
"tabs": "Etichette",
"disabled": "Disabilitato",
"selectedMenu": "Voce menù selezionata",
"selectedPost": "Messaggio selezionato",
"pressed": "Premuto",
"highlight": "Elementi evidenziati",
"icons": "Icone",
"poll": "Grafico sondaggi",
"underlay": "Sottostante",
"faint_text": "Testo sbiadito",
"inputs": "Campi d'immissione",
"buttons": "Pulsanti",
"borders": "Bordi",
"top_bar": "Barra superiore",
"panel_header": "Titolo pannello",
"badge_notification": "Notifica",
"popover": "Suggerimenti, menù, sbalzi"
},
"common_colors": {
"rgbo": "Icone, accenti, medaglie",
"foreground_hint": "Seleziona l'etichetta \"Avanzate\" per controlli più fini",
"main": "Colori comuni",
"_tab_label": "Comuni"
},
"shadows": {
"inset": "Includi",
"spread": "Spandi",
"blur": "Sfoca",
"shadow_id": "Ombra numero {value}",
"override": "Sostituisci",
"component": "Componente",
"_tab_label": "Luci ed ombre"
},
"radii": {
"_tab_label": "Raggio"
}
}, },
"registration": { "enable_web_push_notifications": "Abilita notifiche web push",
"bio": "Introduzione", "fun": "Divertimento",
"email": "Email", "notification_mutes": "Per non ricevere notifiche da uno specifico utente, zittiscilo.",
"fullname": "Nome visualizzato", "notification_setting_privacy_option": "Nascondi mittente e contenuti delle notifiche push",
"password_confirm": "Conferma password", "notification_setting_privacy": "Privacy",
"registration": "Registrazione", "notification_setting_followers": "Utenti che ti seguono",
"token": "Codice d'invito" "notification_setting_non_followers": "Utenti che non ti seguono",
}, "notification_setting_non_follows": "Utenti che non segui",
"user_profile": { "notification_setting_follows": "Utenti che segui",
"timeline_title": "Sequenza dell'Utente" "notification_setting": "Ricevi notifiche da:",
}, "notification_setting_filters": "Filtri",
"who_to_follow": { "notifications": "Notifiche",
"more": "Altro", "greentext": "Frecce da meme",
"who_to_follow": "Chi seguire" "upload_a_photo": "Carica un'immagine",
}, "type_domains_to_mute": "Cerca domini da zittire",
"about": { "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.",
"mrf": { "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.",
"federation": "Federazione", "useStreamingApiWarning": "(Sconsigliato, sperimentale, può saltare messaggi)",
"keyword": { "useStreamingApi": "Ricevi messaggi e notifiche in tempo reale",
"reject": "Rifiuta", "user_mutes": "Utenti",
"replace": "Sostituisci", "post_status_content_type": "Tipo di contenuto dei messaggi",
"is_replaced_by": "→", "subject_line_noop": "Non copiare",
"keyword_policies": "Regole per parole chiave", "subject_line_mastodon": "Come in Mastodon: copia tal quale",
"ftl_removal": "Rimozione dalla sequenza globale" "subject_line_email": "Come nelle email: \"re: oggetto\"",
}, "subject_line_behavior": "Copia oggetto quando rispondi",
"simple": { "subject_input_always_show": "Mostra sempre il campo Oggetto",
"reject": "Rifiuta", "minimal_scopes_mode": "Riduci opzioni di visibilità",
"accept": "Accetta", "scope_copy": "Risposte ereditano la visibilità (messaggi privati lo fanno sempre)",
"simple_policies": "Regole specifiche alla stanza", "search_user_to_mute": "Cerca utente da zittire",
"accept_desc": "Questa stanza accetta messaggi solo dalle seguenti stanze:", "search_user_to_block": "Cerca utente da bloccare",
"reject_desc": "Questa stanza non accetterà messaggi dalle stanze seguenti:", "autohide_floating_post_button": "Nascondi automaticamente il pulsante di composizione (mobile)",
"quarantine": "Quarantena", "show_moderator_badge": "Mostra l'insegna di moderatore sulla mia pagina",
"quarantine_desc": "Questa stanza inoltrerà solo messaggi pubblici alle seguenti stanze:", "show_admin_badge": "Mostra l'insegna di amministratore sulla mia pagina",
"ftl_removal": "Rimozione dalla sequenza globale", "hide_followers_count_description": "Non mostrare quanti seguaci ho",
"ftl_removal_desc": "Questa stanza rimuove le seguenti stanze dalla sequenza globale:", "hide_follows_count_description": "Non mostrare quanti utenti seguo",
"media_removal": "Rimozione multimedia", "hide_followers_description": "Non mostrare i miei seguaci",
"media_removal_desc": "Questa istanza rimuove gli allegati dalle seguenti stanze:", "hide_follows_description": "Non mostrare chi seguo",
"media_nsfw": "Allegati oscurati forzatamente", "no_mutes": "Nessun utente zittito",
"media_nsfw_desc": "Questa stanza oscura gli allegati dei messaggi provenienti da queste stanze:" "no_blocks": "Nessun utente bloccato",
}, "notification_visibility_emoji_reactions": "Reazioni",
"mrf_policies": "Regole RM abilitate", "notification_visibility_moves": "Migrazioni utenti",
"mrf_policies_desc": "Le regole RM cambiano il comportamento federativo della stanza. Vigono le seguenti regole:" "new_email": "Nuova email",
}, "use_contain_fit": "Non ritagliare le anteprime degli allegati",
"staff": "Equipaggio" "play_videos_in_modal": "Riproduci video in un riquadro a sbalzo",
}, "mutes_tab": "Zittiti",
"domain_mute_card": { "interface": "Interfaccia",
"mute": "Zittisci", "instance_default_simple": "(predefinito)",
"mute_progress": "Zittisco...", "checkboxRadius": "Caselle di selezione",
"unmute": "Ascolta", "import_blocks_from_a_csv_file": "Importa blocchi da un file CSV",
"unmute_progress": "Procedo..." "hide_filtered_statuses": "Nascondi messaggi filtrati",
}, "use_one_click_nsfw": "Apri media offuscati con un solo click",
"exporter": { "preload_images": "Precarica immagini",
"export": "Esporta", "hide_isp": "Nascondi pannello della stanza",
"processing": "In elaborazione, il tuo file sarà scaricabile a breve" "max_thumbnails": "Numero massimo di anteprime per messaggio",
}, "hide_muted_posts": "Nascondi messaggi degli utenti zittiti",
"image_cropper": { "accent": "Accento",
"crop_picture": "Ritaglia immagine", "emoji_reactions_on_timeline": "Mostra emoji di reazione sulle sequenze",
"save": "Salva", "pad_emoji": "Affianca spazi agli emoji inseriti tramite selettore",
"save_without_cropping": "Salva senza ritagliare", "notification_blocks": "Bloccando un utente non riceverai più le sue notifiche né lo seguirai più.",
"cancel": "Annulla" "mutes_and_blocks": "Zittiti e bloccati"
}, },
"importer": { "timeline": {
"submit": "Invia", "error_fetching": "Errore nell'aggiornamento",
"success": "Importato.", "load_older": "Carica messaggi più vecchi",
"error": "L'importazione non è andata a buon fine." "show_new": "Mostra nuovi",
}, "up_to_date": "Aggiornato",
"media_modal": { "collapse": "Riduci",
"previous": "Precedente", "conversation": "Conversazione",
"next": "Prossimo" "no_retweet_hint": "Il messaggio è diretto o solo per seguaci e non può essere condiviso",
}, "repeated": "condiviso"
"polls": { },
"add_poll": "Sondaggio", "user_card": {
"add_option": "Alternativa", "follow": "Segui",
"option": "Opzione", "followees": "Chi stai seguendo",
"votes": "voti", "followers": "Seguaci",
"vote": "Vota", "following": "Seguìto!",
"type": "Tipo di sondaggio", "follows_you": "Ti segue!",
"single_choice": "Scelta singola", "mute": "Silenzia",
"multiple_choices": "Scelta multipla", "muted": "Silenziato",
"expiry": "Scadenza", "per_day": "al giorno",
"expires_in": "Scade fra {0}", "statuses": "Messaggi",
"expired": "Scaduto {0} fa", "approve": "Approva",
"not_enough_options": "Aggiungi altre risposte" "block": "Blocca",
}, "blocked": "Bloccato!",
"interactions": { "deny": "Nega",
"favs_repeats": "Condivisi e preferiti" "remote_follow": "Segui da remoto"
}, },
"emoji": { "chat": {
"load_all": "Carico tutti i {emojiAmount} emoji", "title": "Chat"
"load_all_hint": "Primi {saneAmount} emoji caricati, caricarli tutti potrebbe causare rallentamenti.", },
"unicode": "Emoji Unicode", "features_panel": {
"custom": "Emoji personale", "chat": "Chat",
"add_emoji": "Inserisci Emoji", "gopher": "Gopher",
"search_emoji": "Cerca un emoji", "media_proxy": "Proxy multimedia",
"keep_open": "Tieni aperto il menù", "scope_options": "Opzioni visibilità",
"emoji": "Emoji", "text_limit": "Lunghezza massima",
"stickers": "Adesivi" "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"
},
"content_warning": "Oggetto (facoltativo)",
"default": "Sono appena atterrato a Fiumicino.",
"direct_warning": "Questo post sarà visibile solo dagli utenti menzionati.",
"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"
},
"scope_notice": {
"unlisted": "Questo messaggio non sarà visibile sulla sequenza locale né su quella pubblica",
"private": "Questo messaggio sarà visibile solo ai tuoi seguaci",
"public": "Questo messaggio sarà visibile a tutti"
},
"direct_warning_to_first_only": "Questo messaggio sarà visibile solo agli utenti menzionati all'inizio.",
"direct_warning_to_all": "Questo messaggio sarà visibile a tutti i menzionati.",
"new_status": "Nuovo messaggio"
},
"registration": {
"bio": "Introduzione",
"email": "Email",
"fullname": "Nome visualizzato",
"password_confirm": "Conferma password",
"registration": "Registrazione",
"token": "Codice d'invito",
"validations": {
"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"
},
"bio_placeholder": "es.\nCiao, sono Lupo Lucio.\nSono un lupo fantastico che vive nel Fantabosco. Forse mi hai visto alla Melevisione.",
"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:"
},
"staff": "Equipaggio"
},
"domain_mute_card": {
"mute": "Zittisci",
"mute_progress": "Zittisco…",
"unmute": "Ascolta",
"unmute_progress": "Procedo…"
},
"exporter": {
"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"
}
} }

View file

@ -7,34 +7,47 @@
// sed -i -e "s/'//gm" -e 's/"/\\"/gm' -re 's/^( +)(.+?): ((.+?))?(,?)(\{?)$/\1"\2": "\4"/gm' -e 's/\"\{\"/{/g' -e 's/,"$/",/g' file.json // sed -i -e "s/'//gm" -e 's/"/\\"/gm' -re 's/^( +)(.+?): ((.+?))?(,?)(\{?)$/\1"\2": "\4"/gm' -e 's/\"\{\"/{/g' -e 's/,"$/",/g' file.json
// There's only problem that apostrophe character ' gets replaced by \\ so you have to fix it manually, sorry. // There's only problem that apostrophe character ' gets replaced by \\ so you have to fix it manually, sorry.
const loaders = {
ar: () => import('./ar.json'),
ca: () => import('./ca.json'),
cs: () => import('./cs.json'),
de: () => import('./de.json'),
eo: () => import('./eo.json'),
es: () => import('./es.json'),
et: () => import('./et.json'),
eu: () => import('./eu.json'),
fi: () => import('./fi.json'),
fr: () => import('./fr.json'),
ga: () => import('./ga.json'),
he: () => import('./he.json'),
hu: () => import('./hu.json'),
it: () => import('./it.json'),
ja: () => import('./ja_pedantic.json'),
ja_easy: () => import('./ja_easy.json'),
ko: () => import('./ko.json'),
nb: () => import('./nb.json'),
nl: () => import('./nl.json'),
oc: () => import('./oc.json'),
pl: () => import('./pl.json'),
pt: () => import('./pt.json'),
ro: () => import('./ro.json'),
ru: () => import('./ru.json'),
te: () => import('./te.json'),
zh: () => import('./zh.json')
}
const messages = { const messages = {
ar: require('./ar.json'), languages: ['en', ...Object.keys(loaders)],
ca: require('./ca.json'), default: {
cs: require('./cs.json'), en: require('./en.json')
de: require('./de.json'), },
en: require('./en.json'), setLanguage: async (i18n, language) => {
eo: require('./eo.json'), if (loaders[language]) {
es: require('./es.json'), let messages = await loaders[language]()
et: require('./et.json'), i18n.setLocaleMessage(language, messages)
eu: require('./eu.json'), }
fi: require('./fi.json'), i18n.locale = language
fr: require('./fr.json'), }
ga: require('./ga.json'),
he: require('./he.json'),
hu: require('./hu.json'),
it: require('./it.json'),
ja: require('./ja_pedantic.json'),
ja_easy: require('./ja_easy.json'),
ko: require('./ko.json'),
nb: require('./nb.json'),
nl: require('./nl.json'),
oc: require('./oc.json'),
pl: require('./pl.json'),
pt: require('./pt.json'),
ro: require('./ro.json'),
ru: require('./ru.json'),
te: require('./te.json'),
zh: require('./zh.json')
} }
export default messages export default messages

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,478 +1,478 @@
{ {
"chat": { "chat": {
"title": "Чат" "title": "Чат"
}, },
"finder": { "finder": {
"error_fetching_user": "Пользователь не найден", "error_fetching_user": "Пользователь не найден",
"find_user": "Найти пользователя" "find_user": "Найти пользователя"
}, },
"general": { "general": {
"apply": "Применить", "apply": "Применить",
"submit": "Отправить", "submit": "Отправить",
"cancel": "Отмена", "cancel": "Отмена",
"disable": "Оключить", "disable": "Оключить",
"enable": "Включить", "enable": "Включить",
"confirm": "Подтвердить", "confirm": "Подтвердить",
"verify": "Проверить", "verify": "Проверить",
"more": "Больше", "more": "Больше",
"generic_error": "Произошла ошибка", "generic_error": "Произошла ошибка",
"optional": "не обязательно", "optional": "не обязательно",
"show_less": "Показать меньше", "show_less": "Показать меньше",
"show_more": "Показать больше" "show_more": "Показать больше"
}, },
"login": { "login": {
"login": "Войти", "login": "Войти",
"logout": "Выйти", "logout": "Выйти",
"password": "Пароль", "password": "Пароль",
"placeholder": "e.c. lain", "placeholder": "e.c. lain",
"register": "Зарегистрироваться", "register": "Зарегистрироваться",
"username": "Имя пользователя", "username": "Имя пользователя",
"authentication_code": "Код аутентификации", "authentication_code": "Код аутентификации",
"enter_recovery_code": "Ввести код восстановления", "enter_recovery_code": "Ввести код восстановления",
"enter_two_factor_code": "Ввести код аутентификации", "enter_two_factor_code": "Ввести код аутентификации",
"recovery_code": "Код восстановления", "recovery_code": "Код восстановления",
"heading": { "heading": {
"TotpForm": "Двухфакторная аутентификация", "TotpForm": "Двухфакторная аутентификация",
"RecoveryForm": "Two-factor recovery" "RecoveryForm": "Two-factor recovery"
}
},
"nav": {
"back": "Назад",
"chat": "Локальный чат",
"mentions": "Упоминания",
"interactions": "Взаимодействия",
"public_tl": "Публичная лента",
"timeline": "Лента",
"twkn": "Федеративная лента",
"search": "Поиск",
"friend_requests": "Запросы на чтение"
},
"notifications": {
"broken_favorite": "Неизвестный статус, ищем...",
"favorited_you": "нравится ваш статус",
"followed_you": "начал(а) читать вас",
"load_older": "Загрузить старые уведомления",
"notifications": "Уведомления",
"read": "Прочесть",
"repeated_you": "повторил(а) ваш статус",
"follow_request": "хочет читать вас"
},
"interactions": {
"favs_repeats": "Повторы и фавориты",
"follows": "Новые подписки",
"load_older": "Загрузить старые взаимодействия"
},
"post_status": {
"account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может начать читать вас чтобы видеть посты только для подписчиков.",
"account_not_locked_warning_link": "залочен",
"attachments_sensitive": "Вложения содержат чувствительный контент",
"content_warning": "Тема (не обязательно)",
"default": "Что нового?",
"direct_warning": "Этот пост будет виден только упомянутым пользователям",
"posting": "Отправляется",
"scope_notice": {
"public": "Этот пост будет виден всем",
"private": "Этот пост будет виден только вашим подписчикам",
"unlisted": "Этот пост не будет виден в публичной и федеративной ленте"
},
"scope": {
"direct": "Личное - этот пост видят только те кто в нём упомянут",
"private": "Для подписчиков - этот пост видят только подписчики",
"public": "Публичный - этот пост виден всем",
"unlisted": "Непубличный - этот пост не виден на публичных лентах"
}
},
"registration": {
"bio": "Описание",
"email": "Email",
"fullname": "Отображаемое имя",
"password_confirm": "Подтверждение пароля",
"registration": "Регистрация",
"token": "Код приглашения",
"validations": {
"username_required": "не должно быть пустым",
"fullname_required": "не должно быть пустым",
"email_required": "не должен быть пустым",
"password_required": "не должен быть пустым",
"password_confirmation_required": "не должно быть пустым",
"password_confirmation_match": "должно совпадать с паролем"
}
},
"settings": {
"enter_current_password_to_confirm": "Введите свой текущий пароль",
"mfa": {
"otp": "OTP",
"setup_otp": "Настройка OTP",
"wait_pre_setup_otp": "предварительная настройка OTP",
"confirm_and_enable": "Подтвердить и включить OTP",
"title": "Двухфакторная аутентификация",
"generate_new_recovery_codes": "Получить новые коды востановления",
"warning_of_generate_new_codes": "После получения новых кодов восстановления, старые больше не будут работать.",
"recovery_codes": "Коды восстановления.",
"waiting_a_recovery_codes": "Получение кодов восстановления ...",
"recovery_codes_warning": "Запишите эти коды и держите в безопасном месте - иначе вы их больше не увидите. Если вы потеряете доступ к OTP приложению - без резервных кодов вы больше не сможете залогиниться.",
"authentication_methods": "Методы аутентификации",
"scan": {
"title": "Сканирование",
"desc": "Используйте приложение для двухэтапной аутентификации для сканирования этого QR-код или введите текстовый ключ:",
"secret_code": "Ключ"
},
"verify": {
"desc": "Чтобы включить двухэтапную аутентификации, введите код из вашего приложение для двухэтапной аутентификации:"
}
},
"attachmentRadius": "Прикреплённые файлы",
"attachments": "Вложения",
"autoload": "Включить автоматическую загрузку при прокрутке вниз",
"avatar": "Аватар",
"avatarAltRadius": "Аватары в уведомлениях",
"avatarRadius": "Аватары",
"background": "Фон",
"bio": "Описание",
"btnRadius": "Кнопки",
"cBlue": "Ответить, читать",
"cGreen": "Повторить",
"cOrange": "Нравится",
"cRed": "Отменить",
"change_email": "Сменить email",
"change_email_error": "Произошла ошибка при попытке изменить email.",
"changed_email": "Email изменён успешно!",
"change_password": "Сменить пароль",
"change_password_error": "Произошла ошибка при попытке изменить пароль.",
"changed_password": "Пароль изменён успешно!",
"collapse_subject": "Сворачивать посты с темой",
"confirm_new_password": "Подтверждение нового пароля",
"current_avatar": "Текущий аватар",
"current_password": "Текущий пароль",
"current_profile_banner": "Текущий баннер профиля",
"data_import_export_tab": "Импорт / Экспорт данных",
"delete_account": "Удалить аккаунт",
"delete_account_description": "Удалить ваш аккаунт и все ваши сообщения.",
"delete_account_error": "Возникла ошибка в процессе удаления вашего аккаунта. Если это повторяется, свяжитесь с администратором вашего сервера.",
"delete_account_instructions": "Введите ваш пароль в поле ниже для подтверждения удаления.",
"export_theme": "Сохранить Тему",
"filtering": "Фильтрация",
"filtering_explanation": "Все статусы, содержащие данные слова, будут игнорироваться, по одному в строке",
"follow_export": "Экспортировать читаемых",
"follow_export_button": "Экспортировать читаемых в файл .csv",
"follow_export_processing": "Ведётся обработка, скоро вам будет предложено загрузить файл",
"follow_import": "Импортировать читаемых",
"follow_import_error": "Ошибка при импортировании читаемых",
"follows_imported": "Список читаемых импортирован. Обработка займёт некоторое время..",
"foreground": "Передний план",
"general": "Общие",
"hide_attachments_in_convo": "Прятать вложения в разговорах",
"hide_attachments_in_tl": "Прятать вложения в ленте",
"hide_isp": "Скрыть серверную панель",
"import_followers_from_a_csv_file": "Импортировать читаемых из файла .csv",
"import_theme": "Загрузить Тему",
"inputRadius": "Поля ввода",
"checkboxRadius": "Чекбоксы",
"instance_default": "(по умолчанию: {value})",
"instance_default_simple": "(по умолчанию)",
"interface": "Интерфейс",
"interfaceLanguage": "Язык интерфейса",
"limited_availability": "Не доступно в вашем браузере",
"links": "Ссылки",
"lock_account_description": "Аккаунт доступен только подтверждённым подписчикам",
"loop_video": "Зациливать видео",
"loop_video_silent_only": "Зацикливать только беззвучные видео (т.е. \"гифки\" с Mastodon)",
"name": "Имя",
"name_bio": "Имя и описание",
"new_email": "Новый email",
"new_password": "Новый пароль",
"fun": "Потешное",
"greentext": "Мемные стрелочки",
"notification_visibility": "Показывать уведомления",
"notification_visibility_follows": "Подписки",
"notification_visibility_likes": "Лайки",
"notification_visibility_mentions": "Упоминания",
"notification_visibility_repeats": "Повторы",
"no_rich_text_description": "Убрать форматирование из всех постов",
"hide_follows_description": "Не показывать кого я читаю",
"hide_followers_description": "Не показывать кто читает меня",
"hide_follows_count_description": "Не показывать число читаемых пользователей",
"hide_followers_count_description": "Не показывать число моих подписчиков",
"show_admin_badge": "Показывать значок администратора в моем профиле",
"show_moderator_badge": "Показывать значок модератора в моем профиле",
"nsfw_clickthrough": "Включить скрытие NSFW вложений",
"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": "Показывать только ответы мне",
"autohide_floating_post_button": "Автоматически скрывать кнопку постинга (в мобильной версии)",
"saving_err": "Не удалось сохранить настройки",
"saving_ok": "Сохранено",
"security_tab": "Безопасность",
"scope_copy": "Копировать видимость поста при ответе (всегда включено для Личных Сообщений)",
"minimal_scopes_mode": "Минимизировать набор опций видимости поста",
"set_new_avatar": "Загрузить новый аватар",
"set_new_profile_background": "Загрузить новый фон профиля",
"set_new_profile_banner": "Загрузить новый баннер профиля",
"settings": "Настройки",
"subject_input_always_show": "Всегда показывать поле ввода темы",
"stop_gifs": "Проигрывать GIF анимации только при наведении",
"streaming": "Включить автоматическую загрузку новых сообщений при прокрутке вверх",
"useStreamingApi": "Получать сообщения и уведомления в реальном времени",
"useStreamingApiWarning": "(Не рекомендуется, экспериментально, сообщения могут пропадать)",
"text": "Текст",
"theme": "Тема",
"theme_help": "Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.",
"theme_help_v2_1": "Вы так же можете перепоределить цвета определенных компонентов нажав соотв. галочку. Используйте кнопку \"Очистить всё\" чтобы снять все переопределения.",
"theme_help_v2_2": "Под некоторыми полями ввода это идикаторы контрастности, наведите на них мышью чтобы узнать больше. Приспользовании прозрачности контраст расчитывается для наихудшего варианта.",
"tooltipRadius": "Всплывающие подсказки/уведомления",
"user_settings": "Настройки пользователя",
"values": {
"false": "нет",
"true": "да"
},
"style": {
"switcher": {
"keep_color": "Оставить цвета",
"keep_shadows": "Оставить тени",
"keep_opacity": "Оставить прозрачность",
"keep_roundness": "Оставить скругление",
"keep_fonts": "Оставить шрифты",
"save_load_hint": "Опции \"оставить...\" позволяют сохранить текущие настройки при выборе другой темы или импорта её из файла. Так же они влияют на то какие компоненты будут сохранены при экспорте темы. Когда все галочки сняты все компоненты будут экспортированы.",
"reset": "Сбросить",
"clear_all": "Очистить всё",
"clear_opacity": "Очистить прозрачность"
},
"common": {
"color": "Цвет",
"opacity": "Прозрачность",
"contrast": {
"hint": "Уровень контраста: {ratio}, что {level} {context}",
"level": {
"aa": "соответствует гайдлайну Level AA (минимальный)",
"aaa": "соответствует гайдлайну Level AAA (рекомендуемый)",
"bad": "не соответствует каким либо гайдлайнам"
},
"context": {
"18pt": "для крупного (18pt+) текста",
"text": "для текста"
}
}
},
"common_colors": {
"_tab_label": "Общие",
"main": "Общие цвета",
"foreground_hint": "См. вкладку \"Дополнительно\" для более детального контроля",
"rgbo": "Иконки, акценты, ярылки"
},
"advanced_colors": {
"_tab_label": "Дополнительно",
"alert": "Фон уведомлений",
"alert_error": "Ошибки",
"badge": "Фон значков",
"badge_notification": "Уведомления",
"panel_header": "Заголовок панели",
"top_bar": "Верняя полоска",
"borders": "Границы",
"buttons": "Кнопки",
"inputs": "Поля ввода",
"faint_text": "Маловажный текст"
},
"radii": {
"_tab_label": "Скругление"
},
"shadows": {
"_tab_label": "Светотень",
"component": "Компонент",
"override": "Переопределить",
"shadow_id": "Тень №{value}",
"blur": "Размытие",
"spread": "Разброс",
"inset": "Внутренняя",
"hint": "Для теней вы так же можете использовать --variable в качестве цвета чтобы использовать CSS3-переменные. В таком случае прозрачность работать не будет.",
"filter_hint": {
"always_drop_shadow": "Внимание, эта тень всегда использует {0} когда браузер поддерживает это.",
"drop_shadow_syntax": "{0} не поддерживает параметр {1} и ключевое слово {2}.",
"avatar_inset": "Одновременное использование внутренних и внешних теней на (прозрачных) аватарках может дать не те результаты что вы ожидаете.",
"spread_zero": "Тени с разбросом > 0 будут выглядеть как если бы разброс установлен в 0",
"inset_classic": "Внутренние тени будут использовать {0}"
},
"components": {
"panel": "Панель",
"panelHeader": "Заголовок панели",
"topBar": "Верхняя полоска",
"avatar": "Аватарка (профиль)",
"avatarStatus": "Аватарка (в ленте)",
"popup": "Всплывающие подсказки",
"button": "Кнопки",
"buttonHover": "Кнопки (наведен курсор)",
"buttonPressed": "Кнопки (нажата)",
"buttonPressedHover": "Кнопки (нажата+наведен курсор)",
"input": "Поля ввода"
}
},
"fonts": {
"_tab_label": "Шрифты",
"help": "Выберите тип шрифта для использования в интерфейсе. При выборе варианта \"другой\" надо ввести название шрифта в точности как он называется в системе.",
"components": {
"interface": "Интерфейс",
"input": "Поля ввода",
"post": "Текст постов",
"postCode": "Моноширинный текст в посте (форматирование)"
},
"family": "Шрифт",
"size": "Размер (в пикселях)",
"weight": "Ширина",
"custom": "Другой"
},
"preview": {
"header": "Пример",
"content": "Контент",
"error": "Ошибка стоп 000",
"button": "Кнопка",
"text": "Еще немного {0} и масенькая {1}",
"mono": "контента",
"input": "Что нового?",
"faint_link": "Его придется убрать",
"fine_print": "Если проблемы остались — ваш гуртовщик мыши плохо стоит. {0}.",
"header_faint": "Все идет по плану",
"checkbox": "Я подтверждаю что не было ни единого разрыва",
"link": "ссылка"
}
},
"notification_setting_non_followers": "Не читающие вас",
"allow_following_move": "Разрешить автоматически читать новый аккаунт при перемещении на другой сервер",
"hide_user_stats": "Не показывать статистику пользователей (например количество читателей)",
"notification_setting_followers": "Читающие вас",
"notification_setting_follows": "Читаемые вами",
"notification_setting_non_follows": "Не читаемые вами"
},
"timeline": {
"collapse": "Свернуть",
"conversation": "Разговор",
"error_fetching": "Ошибка при обновлении",
"load_older": "Загрузить старые статусы",
"no_retweet_hint": "Пост помечен как \"только для подписчиков\" или \"личное\" и поэтому не может быть повторён",
"repeated": "повторил(а)",
"show_new": "Показать новые",
"up_to_date": "Обновлено"
},
"user_card": {
"block": "Заблокировать",
"blocked": "Заблокирован",
"favorites": "Понравившиеся",
"follow": "Читать",
"follow_sent": "Запрос отправлен!",
"follow_progress": "Запрашиваем…",
"follow_again": "Запросить еще раз?",
"follow_unfollow": "Перестать читать",
"followees": "Читаемые",
"followers": "Читатели",
"following": "Читаю!",
"follows_you": "Читает вас!",
"mute": "Игнорировать",
"muted": "Игнорирую",
"per_day": "в день",
"remote_follow": "Читать удалённо",
"statuses": "Статусы",
"admin_menu": {
"moderation": "Опции модератора",
"grant_admin": "Сделать администратором",
"revoke_admin": "Забрать права администратора",
"grant_moderator": "Сделать модератором",
"revoke_moderator": "Забрать права модератора",
"activate_account": "Активировать аккаунт",
"deactivate_account": "Деактивировать аккаунт",
"delete_account": "Удалить аккаунт",
"force_nsfw": "Отмечать посты пользователя как NSFW",
"strip_media": "Убирать вложения из постов пользователя",
"force_unlisted": "Не добавлять посты в публичные ленты",
"sandbox": "Принудить видимость постов только читателям",
"disable_remote_subscription": "Запретить читать с удаленных серверов",
"disable_any_subscription": "Запретить читать пользователя",
"quarantine": "Не федерировать посты пользователя",
"delete_user": "Удалить пользователя",
"delete_user_confirmation": "Вы уверены? Это действие нельзя отменить."
}
},
"user_profile": {
"timeline_title": "Лента пользователя"
},
"search": {
"people": "Люди",
"hashtags": "Хэштэги",
"person_talking": "Популярно у {count} человека",
"people_talking": "Популярно у {count} человек",
"no_results": "Ничего не найдено"
},
"password_reset": {
"forgot_password": "Забыли пароль?",
"password_reset": "Сброс пароля",
"instruction": "Введите ваш email или имя пользователя, и мы отправим вам ссылку для сброса пароля.",
"placeholder": "Ваш email или имя пользователя",
"check_email": "Проверьте ваш email и перейдите по ссылке для сброса пароля.",
"return_home": "Вернуться на главную страницу",
"not_found": "Мы не смогли найти аккаунт с таким email-ом или именем пользователя.",
"too_many_requests": "Вы исчерпали допустимое количество попыток, попробуйте позже.",
"password_reset_disabled": "Сброс пароля отключен. Cвяжитесь с администратором вашего сервера."
},
"about": {
"mrf": {
"federation": "Федерация",
"simple": {
"accept_desc": "Данный сервер принимает сообщения только со следующих серверов:",
"ftl_removal_desc": "Данный сервер скрывает следующие сервера с федеративной ленты:",
"media_nsfw_desc": "Данный сервер принужденно помечает вложения со следущих серверов как NSFW:",
"simple_policies": "Правила для определенных серверов",
"accept": "Принимаемые сообщения",
"reject": "Отклоняемые сообщения",
"reject_desc": "Данный сервер не принимает сообщения со следующих серверов:",
"quarantine": "Зона карантина",
"quarantine_desc": "Данный сервер отправляет только публичные посты следующим серверам:",
"ftl_removal": "Скрытие с федеративной ленты",
"media_removal": "Удаление вложений",
"media_removal_desc": "Данный сервер удаляет вложения со следующих серверов:",
"media_nsfw": "Принужденно помеченно как NSFW"
},
"keyword": {
"ftl_removal": "Убрать из федеративной ленты",
"reject": "Отклонить",
"keyword_policies": "Действия на ключевые слова",
"replace": "Заменить",
"is_replaced_by": "→"
},
"mrf_policies": "Активные правила MRF (модуль переписывания сообщений)",
"mrf_policies_desc": "Правила MRF (модуль переписывания сообщений) влияют на федерацию данного сервера. Следующие правила активны:"
},
"staff": "Администрация"
},
"domain_mute_card": {
"mute": "Игнорировать",
"mute_progress": "В процессе...",
"unmute": "Прекратить игнорирование",
"unmute_progress": "В процессе..."
},
"exporter": {
"export": "Экспорт",
"processing": "Запрос в обработке, вам скоро будет предложено загрузить файл"
},
"features_panel": {
"chat": "Чат",
"media_proxy": "Прокси для внешних вложений",
"text_limit": "Лимит символов",
"title": "Особенности",
"gopher": "Gopher"
},
"tool_tip": {
"accept_follow_request": "Принять запрос на чтение",
"reject_follow_request": "Отклонить запрос на чтение"
} }
},
"nav": {
"back": "Назад",
"chat": "Локальный чат",
"mentions": "Упоминания",
"interactions": "Взаимодействия",
"public_tl": "Публичная лента",
"timeline": "Лента",
"twkn": "Федеративная лента",
"search": "Поиск",
"friend_requests": "Запросы на чтение"
},
"notifications": {
"broken_favorite": "Неизвестный статус, ищем...",
"favorited_you": "нравится ваш статус",
"followed_you": "начал(а) читать вас",
"load_older": "Загрузить старые уведомления",
"notifications": "Уведомления",
"read": "Прочесть",
"repeated_you": "повторил(а) ваш статус",
"follow_request": "хочет читать вас"
},
"interactions": {
"favs_repeats": "Повторы и фавориты",
"follows": "Новые подписки",
"load_older": "Загрузить старые взаимодействия"
},
"post_status": {
"account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может начать читать вас чтобы видеть посты только для подписчиков.",
"account_not_locked_warning_link": "залочен",
"attachments_sensitive": "Вложения содержат чувствительный контент",
"content_warning": "Тема (не обязательно)",
"default": "Что нового?",
"direct_warning": "Этот пост будет виден только упомянутым пользователям",
"posting": "Отправляется",
"scope_notice": {
"public": "Этот пост будет виден всем",
"private": "Этот пост будет виден только вашим подписчикам",
"unlisted": "Этот пост не будет виден в публичной и федеративной ленте"
},
"scope": {
"direct": "Личное - этот пост видят только те кто в нём упомянут",
"private": "Для подписчиков - этот пост видят только подписчики",
"public": "Публичный - этот пост виден всем",
"unlisted": "Непубличный - этот пост не виден на публичных лентах"
}
},
"registration": {
"bio": "Описание",
"email": "Email",
"fullname": "Отображаемое имя",
"password_confirm": "Подтверждение пароля",
"registration": "Регистрация",
"token": "Код приглашения",
"validations": {
"username_required": "не должно быть пустым",
"fullname_required": "не должно быть пустым",
"email_required": "не должен быть пустым",
"password_required": "не должен быть пустым",
"password_confirmation_required": "не должно быть пустым",
"password_confirmation_match": "должно совпадать с паролем"
}
},
"settings": {
"enter_current_password_to_confirm": "Введите свой текущий пароль",
"mfa": {
"otp": "OTP",
"setup_otp": "Настройка OTP",
"wait_pre_setup_otp": "предварительная настройка OTP",
"confirm_and_enable": "Подтвердить и включить OTP",
"title": "Двухфакторная аутентификация",
"generate_new_recovery_codes": "Получить новые коды востановления",
"warning_of_generate_new_codes": "После получения новых кодов восстановления, старые больше не будут работать.",
"recovery_codes": "Коды восстановления.",
"waiting_a_recovery_codes": "Получение кодов восстановления ...",
"recovery_codes_warning": "Запишите эти коды и держите в безопасном месте - иначе вы их больше не увидите. Если вы потеряете доступ к OTP приложению - без резервных кодов вы больше не сможете залогиниться.",
"authentication_methods": "Методы аутентификации",
"scan": {
"title": "Сканирование",
"desc": "Используйте приложение для двухэтапной аутентификации для сканирования этого QR-код или введите текстовый ключ:",
"secret_code": "Ключ"
},
"verify": {
"desc": "Чтобы включить двухэтапную аутентификации, введите код из вашего приложение для двухэтапной аутентификации:"
}
},
"attachmentRadius": "Прикреплённые файлы",
"attachments": "Вложения",
"autoload": "Включить автоматическую загрузку при прокрутке вниз",
"avatar": "Аватар",
"avatarAltRadius": "Аватары в уведомлениях",
"avatarRadius": "Аватары",
"background": "Фон",
"bio": "Описание",
"btnRadius": "Кнопки",
"cBlue": "Ответить, читать",
"cGreen": "Повторить",
"cOrange": "Нравится",
"cRed": "Отменить",
"change_email": "Сменить email",
"change_email_error": "Произошла ошибка при попытке изменить email.",
"changed_email": "Email изменён успешно!",
"change_password": "Сменить пароль",
"change_password_error": "Произошла ошибка при попытке изменить пароль.",
"changed_password": "Пароль изменён успешно!",
"collapse_subject": "Сворачивать посты с темой",
"confirm_new_password": "Подтверждение нового пароля",
"current_avatar": "Текущий аватар",
"current_password": "Текущий пароль",
"current_profile_banner": "Текущий баннер профиля",
"data_import_export_tab": "Импорт / Экспорт данных",
"delete_account": "Удалить аккаунт",
"delete_account_description": "Удалить ваш аккаунт и все ваши сообщения.",
"delete_account_error": "Возникла ошибка в процессе удаления вашего аккаунта. Если это повторяется, свяжитесь с администратором вашего сервера.",
"delete_account_instructions": "Введите ваш пароль в поле ниже для подтверждения удаления.",
"export_theme": "Сохранить Тему",
"filtering": "Фильтрация",
"filtering_explanation": "Все статусы, содержащие данные слова, будут игнорироваться, по одному в строке",
"follow_export": "Экспортировать читаемых",
"follow_export_button": "Экспортировать читаемых в файл .csv",
"follow_export_processing": "Ведётся обработка, скоро вам будет предложено загрузить файл",
"follow_import": "Импортировать читаемых",
"follow_import_error": "Ошибка при импортировании читаемых",
"follows_imported": "Список читаемых импортирован. Обработка займёт некоторое время..",
"foreground": "Передний план",
"general": "Общие",
"hide_attachments_in_convo": "Прятать вложения в разговорах",
"hide_attachments_in_tl": "Прятать вложения в ленте",
"hide_isp": "Скрыть серверную панель",
"import_followers_from_a_csv_file": "Импортировать читаемых из файла .csv",
"import_theme": "Загрузить Тему",
"inputRadius": "Поля ввода",
"checkboxRadius": "Чекбоксы",
"instance_default": "(по умолчанию: {value})",
"instance_default_simple": "(по умолчанию)",
"interface": "Интерфейс",
"interfaceLanguage": "Язык интерфейса",
"limited_availability": "Не доступно в вашем браузере",
"links": "Ссылки",
"lock_account_description": "Аккаунт доступен только подтверждённым подписчикам",
"loop_video": "Зациливать видео",
"loop_video_silent_only": "Зацикливать только беззвучные видео (т.е. \"гифки\" с Mastodon)",
"name": "Имя",
"name_bio": "Имя и описание",
"new_email": "Новый email",
"new_password": "Новый пароль",
"fun": "Потешное",
"greentext": "Мемные стрелочки",
"notification_visibility": "Показывать уведомления",
"notification_visibility_follows": "Подписки",
"notification_visibility_likes": "Лайки",
"notification_visibility_mentions": "Упоминания",
"notification_visibility_repeats": "Повторы",
"no_rich_text_description": "Убрать форматирование из всех постов",
"hide_follows_description": "Не показывать кого я читаю",
"hide_followers_description": "Не показывать кто читает меня",
"hide_follows_count_description": "Не показывать число читаемых пользователей",
"hide_followers_count_description": "Не показывать число моих подписчиков",
"show_admin_badge": "Показывать значок администратора в моем профиле",
"show_moderator_badge": "Показывать значок модератора в моем профиле",
"nsfw_clickthrough": "Включить скрытие NSFW вложений",
"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": "Показывать только ответы мне",
"autohide_floating_post_button": "Автоматически скрывать кнопку постинга (в мобильной версии)",
"saving_err": "Не удалось сохранить настройки",
"saving_ok": "Сохранено",
"security_tab": "Безопасность",
"scope_copy": "Копировать видимость поста при ответе (всегда включено для Личных Сообщений)",
"minimal_scopes_mode": "Минимизировать набор опций видимости поста",
"set_new_avatar": "Загрузить новый аватар",
"set_new_profile_background": "Загрузить новый фон профиля",
"set_new_profile_banner": "Загрузить новый баннер профиля",
"settings": "Настройки",
"subject_input_always_show": "Всегда показывать поле ввода темы",
"stop_gifs": "Проигрывать GIF анимации только при наведении",
"streaming": "Включить автоматическую загрузку новых сообщений при прокрутке вверх",
"useStreamingApi": "Получать сообщения и уведомления в реальном времени",
"useStreamingApiWarning": "(Не рекомендуется, экспериментально, сообщения могут пропадать)",
"text": "Текст",
"theme": "Тема",
"theme_help": "Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.",
"theme_help_v2_1": "Вы так же можете перепоределить цвета определенных компонентов нажав соотв. галочку. Используйте кнопку \"Очистить всё\" чтобы снять все переопределения.",
"theme_help_v2_2": "Под некоторыми полями ввода это идикаторы контрастности, наведите на них мышью чтобы узнать больше. Приспользовании прозрачности контраст расчитывается для наихудшего варианта.",
"tooltipRadius": "Всплывающие подсказки/уведомления",
"user_settings": "Настройки пользователя",
"values": {
"false": "нет",
"true": "да"
},
"style": {
"switcher": {
"keep_color": "Оставить цвета",
"keep_shadows": "Оставить тени",
"keep_opacity": "Оставить прозрачность",
"keep_roundness": "Оставить скругление",
"keep_fonts": "Оставить шрифты",
"save_load_hint": "Опции \"оставить...\" позволяют сохранить текущие настройки при выборе другой темы или импорта её из файла. Так же они влияют на то какие компоненты будут сохранены при экспорте темы. Когда все галочки сняты все компоненты будут экспортированы.",
"reset": "Сбросить",
"clear_all": "Очистить всё",
"clear_opacity": "Очистить прозрачность"
},
"common": {
"color": "Цвет",
"opacity": "Прозрачность",
"contrast": {
"hint": "Уровень контраста: {ratio}, что {level} {context}",
"level": {
"aa": "соответствует гайдлайну Level AA (минимальный)",
"aaa": "соответствует гайдлайну Level AAA (рекомендуемый)",
"bad": "не соответствует каким либо гайдлайнам"
},
"context": {
"18pt": "для крупного (18pt+) текста",
"text": "для текста"
}
}
},
"common_colors": {
"_tab_label": "Общие",
"main": "Общие цвета",
"foreground_hint": "См. вкладку \"Дополнительно\" для более детального контроля",
"rgbo": "Иконки, акценты, ярылки"
},
"advanced_colors": {
"_tab_label": "Дополнительно",
"alert": "Фон уведомлений",
"alert_error": "Ошибки",
"badge": "Фон значков",
"badge_notification": "Уведомления",
"panel_header": "Заголовок панели",
"top_bar": "Верняя полоска",
"borders": "Границы",
"buttons": "Кнопки",
"inputs": "Поля ввода",
"faint_text": "Маловажный текст"
},
"radii": {
"_tab_label": "Скругление"
},
"shadows": {
"_tab_label": "Светотень",
"component": "Компонент",
"override": "Переопределить",
"shadow_id": "Тень №{value}",
"blur": "Размытие",
"spread": "Разброс",
"inset": "Внутренняя",
"hint": "Для теней вы так же можете использовать --variable в качестве цвета чтобы использовать CSS3-переменные. В таком случае прозрачность работать не будет.",
"filter_hint": {
"always_drop_shadow": "Внимание, эта тень всегда использует {0} когда браузер поддерживает это.",
"drop_shadow_syntax": "{0} не поддерживает параметр {1} и ключевое слово {2}.",
"avatar_inset": "Одновременное использование внутренних и внешних теней на (прозрачных) аватарках может дать не те результаты что вы ожидаете.",
"spread_zero": "Тени с разбросом > 0 будут выглядеть как если бы разброс установлен в 0",
"inset_classic": "Внутренние тени будут использовать {0}"
},
"components": {
"panel": "Панель",
"panelHeader": "Заголовок панели",
"topBar": "Верхняя полоска",
"avatar": "Аватарка (профиль)",
"avatarStatus": "Аватарка (в ленте)",
"popup": "Всплывающие подсказки",
"button": "Кнопки",
"buttonHover": "Кнопки (наведен курсор)",
"buttonPressed": "Кнопки (нажата)",
"buttonPressedHover": "Кнопки (нажата+наведен курсор)",
"input": "Поля ввода"
}
},
"fonts": {
"_tab_label": "Шрифты",
"help": "Выберите тип шрифта для использования в интерфейсе. При выборе варианта \"другой\" надо ввести название шрифта в точности как он называется в системе.",
"components": {
"interface": "Интерфейс",
"input": "Поля ввода",
"post": "Текст постов",
"postCode": "Моноширинный текст в посте (форматирование)"
},
"family": "Шрифт",
"size": "Размер (в пикселях)",
"weight": "Ширина",
"custom": "Другой"
},
"preview": {
"header": "Пример",
"content": "Контент",
"error": "Ошибка стоп 000",
"button": "Кнопка",
"text": "Еще немного {0} и масенькая {1}",
"mono": "контента",
"input": "Что нового?",
"faint_link": "Его придется убрать",
"fine_print": "Если проблемы остались — ваш гуртовщик мыши плохо стоит. {0}.",
"header_faint": "Все идет по плану",
"checkbox": "Я подтверждаю что не было ни единого разрыва",
"link": "ссылка"
}
},
"notification_setting_non_followers": "Не читающие вас",
"allow_following_move": "Разрешить автоматически читать новый аккаунт при перемещении на другой сервер",
"hide_user_stats": "Не показывать статистику пользователей (например количество читателей)",
"notification_setting_followers": "Читающие вас",
"notification_setting_follows": "Читаемые вами",
"notification_setting_non_follows": "Не читаемые вами"
},
"timeline": {
"collapse": "Свернуть",
"conversation": "Разговор",
"error_fetching": "Ошибка при обновлении",
"load_older": "Загрузить старые статусы",
"no_retweet_hint": "Пост помечен как \"только для подписчиков\" или \"личное\" и поэтому не может быть повторён",
"repeated": "повторил(а)",
"show_new": "Показать новые",
"up_to_date": "Обновлено"
},
"user_card": {
"block": "Заблокировать",
"blocked": "Заблокирован",
"favorites": "Понравившиеся",
"follow": "Читать",
"follow_sent": "Запрос отправлен!",
"follow_progress": "Запрашиваем…",
"follow_again": "Запросить еще раз?",
"follow_unfollow": "Перестать читать",
"followees": "Читаемые",
"followers": "Читатели",
"following": "Читаю!",
"follows_you": "Читает вас!",
"mute": "Игнорировать",
"muted": "Игнорирую",
"per_day": "в день",
"remote_follow": "Читать удалённо",
"statuses": "Статусы",
"admin_menu": {
"moderation": "Опции модератора",
"grant_admin": "Сделать администратором",
"revoke_admin": "Забрать права администратора",
"grant_moderator": "Сделать модератором",
"revoke_moderator": "Забрать права модератора",
"activate_account": "Активировать аккаунт",
"deactivate_account": "Деактивировать аккаунт",
"delete_account": "Удалить аккаунт",
"force_nsfw": "Отмечать посты пользователя как NSFW",
"strip_media": "Убирать вложения из постов пользователя",
"force_unlisted": "Не добавлять посты в публичные ленты",
"sandbox": "Принудить видимость постов только читателям",
"disable_remote_subscription": "Запретить читать с удаленных серверов",
"disable_any_subscription": "Запретить читать пользователя",
"quarantine": "Не федерировать посты пользователя",
"delete_user": "Удалить пользователя",
"delete_user_confirmation": "Вы уверены? Это действие нельзя отменить."
}
},
"user_profile": {
"timeline_title": "Лента пользователя"
},
"search": {
"people": "Люди",
"hashtags": "Хэштэги",
"person_talking": "Популярно у {count} человека",
"people_talking": "Популярно у {count} человек",
"no_results": "Ничего не найдено"
},
"password_reset": {
"forgot_password": "Забыли пароль?",
"password_reset": "Сброс пароля",
"instruction": "Введите ваш email или имя пользователя, и мы отправим вам ссылку для сброса пароля.",
"placeholder": "Ваш email или имя пользователя",
"check_email": "Проверьте ваш email и перейдите по ссылке для сброса пароля.",
"return_home": "Вернуться на главную страницу",
"not_found": "Мы не смогли найти аккаунт с таким email-ом или именем пользователя.",
"too_many_requests": "Вы исчерпали допустимое количество попыток, попробуйте позже.",
"password_reset_disabled": "Сброс пароля отключен. Cвяжитесь с администратором вашего сервера."
},
"about": {
"mrf": {
"federation": "Федерация",
"simple": {
"accept_desc": "Данный сервер принимает сообщения только со следующих серверов:",
"ftl_removal_desc": "Данный сервер скрывает следующие сервера с федеративной ленты:",
"media_nsfw_desc": "Данный сервер принужденно помечает вложения со следущих серверов как NSFW:",
"simple_policies": "Правила для определенных серверов",
"accept": "Принимаемые сообщения",
"reject": "Отклоняемые сообщения",
"reject_desc": "Данный сервер не принимает сообщения со следующих серверов:",
"quarantine": "Зона карантина",
"quarantine_desc": "Данный сервер отправляет только публичные посты следующим серверам:",
"ftl_removal": "Скрытие с федеративной ленты",
"media_removal": "Удаление вложений",
"media_removal_desc": "Данный сервер удаляет вложения со следующих серверов:",
"media_nsfw": "Принужденно помеченно как NSFW"
},
"keyword": {
"ftl_removal": "Убрать из федеративной ленты",
"reject": "Отклонить",
"keyword_policies": "Действия на ключевые слова",
"replace": "Заменить",
"is_replaced_by": "→"
},
"mrf_policies": "Активные правила MRF (модуль переписывания сообщений)",
"mrf_policies_desc": "Правила MRF (модуль переписывания сообщений) влияют на федерацию данного сервера. Следующие правила активны:"
},
"staff": "Администрация"
},
"domain_mute_card": {
"mute": "Игнорировать",
"mute_progress": "В процессе…",
"unmute": "Прекратить игнорирование",
"unmute_progress": "В процессе…"
},
"exporter": {
"export": "Экспорт",
"processing": "Запрос в обработке, вам скоро будет предложено загрузить файл"
},
"features_panel": {
"chat": "Чат",
"media_proxy": "Прокси для внешних вложений",
"text_limit": "Лимит символов",
"title": "Особенности",
"gopher": "Gopher"
},
"tool_tip": {
"accept_follow_request": "Принять запрос на чтение",
"reject_follow_request": "Отклонить запрос на чтение"
}
} }

View file

@ -1,352 +1,352 @@
{ {
"chat.title": "చాట్", "chat.title": "చాట్",
"features_panel.chat": "చాట్", "features_panel.chat": "చాట్",
"features_panel.gopher": "గోఫర్", "features_panel.gopher": "గోఫర్",
"features_panel.media_proxy": "మీడియా ప్రాక్సీ", "features_panel.media_proxy": "మీడియా ప్రాక్సీ",
"features_panel.scope_options": "స్కోప్ ఎంపికలు", "features_panel.scope_options": "స్కోప్ ఎంపికలు",
"features_panel.text_limit": "వచన పరిమితి", "features_panel.text_limit": "వచన పరిమితి",
"features_panel.title": "లక్షణాలు", "features_panel.title": "లక్షణాలు",
"features_panel.who_to_follow": "ఎవరిని అనుసరించాలి", "features_panel.who_to_follow": "ఎవరిని అనుసరించాలి",
"finder.error_fetching_user": "వినియోగదారుని పొందడంలో లోపం", "finder.error_fetching_user": "వినియోగదారుని పొందడంలో లోపం",
"finder.find_user": "వినియోగదారుని కనుగొనండి", "finder.find_user": "వినియోగదారుని కనుగొనండి",
"general.apply": "వర్తించు", "general.apply": "వర్తించు",
"general.submit": "సమర్పించు", "general.submit": "సమర్పించు",
"general.more": "మరిన్ని", "general.more": "మరిన్ని",
"general.generic_error": "ఒక తప్పిదం సంభవించినది", "general.generic_error": "ఒక తప్పిదం సంభవించినది",
"general.optional": "ఐచ్చికం", "general.optional": "ఐచ్చికం",
"image_cropper.crop_picture": "చిత్రాన్ని కత్తిరించండి", "image_cropper.crop_picture": "చిత్రాన్ని కత్తిరించండి",
"image_cropper.save": "దాచు", "image_cropper.save": "దాచు",
"image_cropper.save_without_cropping": "కత్తిరించకుండా సేవ్ చేయి", "image_cropper.save_without_cropping": "కత్తిరించకుండా సేవ్ చేయి",
"image_cropper.cancel": "రద్దుచేయి", "image_cropper.cancel": "రద్దుచేయి",
"login.login": "లాగిన్", "login.login": "లాగిన్",
"login.description": "OAuth తో లాగిన్ అవ్వండి", "login.description": "OAuth తో లాగిన్ అవ్వండి",
"login.logout": "లాగౌట్", "login.logout": "లాగౌట్",
"login.password": "సంకేతపదము", "login.password": "సంకేతపదము",
"login.placeholder": "ఉదా. lain", "login.placeholder": "ఉదా. lain",
"login.register": "నమోదు చేసుకోండి", "login.register": "నమోదు చేసుకోండి",
"login.username": "వాడుకరి పేరు", "login.username": "వాడుకరి పేరు",
"login.hint": "చర్చలో చేరడానికి లాగిన్ అవ్వండి", "login.hint": "చర్చలో చేరడానికి లాగిన్ అవ్వండి",
"media_modal.previous": "ముందరి పుట", "media_modal.previous": "ముందరి పుట",
"media_modal.next": "తరువాత", "media_modal.next": "తరువాత",
"nav.about": "గురించి", "nav.about": "గురించి",
"nav.back": "వెనక్కి", "nav.back": "వెనక్కి",
"nav.chat": "స్థానిక చాట్", "nav.chat": "స్థానిక చాట్",
"nav.friend_requests": "అనుసరించడానికి అభ్యర్థనలు", "nav.friend_requests": "అనుసరించడానికి అభ్యర్థనలు",
"nav.mentions": "ప్రస్తావనలు", "nav.mentions": "ప్రస్తావనలు",
"nav.dms": "నేరుగా పంపిన సందేశాలు", "nav.dms": "నేరుగా పంపిన సందేశాలు",
"nav.public_tl": "ప్రజా కాలక్రమం", "nav.public_tl": "ప్రజా కాలక్రమం",
"nav.timeline": "కాలక్రమం", "nav.timeline": "కాలక్రమం",
"nav.twkn": "మొత్తం తెలిసిన నెట్వర్క్", "nav.twkn": "మొత్తం తెలిసిన నెట్వర్క్",
"nav.user_search": "వాడుకరి శోధన", "nav.user_search": "వాడుకరి శోధన",
"nav.who_to_follow": "ఎవరిని అనుసరించాలి", "nav.who_to_follow": "ఎవరిని అనుసరించాలి",
"nav.preferences": "ప్రాధాన్యతలు", "nav.preferences": "ప్రాధాన్యతలు",
"notifications.broken_favorite": "తెలియని స్థితి, దాని కోసం శోధిస్తోంది...", "notifications.broken_favorite": "తెలియని స్థితి, దాని కోసం శోధిస్తోంది...",
"notifications.favorited_you": "మీ స్థితిని ఇష్టపడ్డారు", "notifications.favorited_you": "మీ స్థితిని ఇష్టపడ్డారు",
"notifications.followed_you": "మిమ్మల్ని అనుసరించారు", "notifications.followed_you": "మిమ్మల్ని అనుసరించారు",
"notifications.load_older": "పాత నోటిఫికేషన్లను లోడ్ చేయండి", "notifications.load_older": "పాత నోటిఫికేషన్లను లోడ్ చేయండి",
"notifications.notifications": "ప్రకటనలు", "notifications.notifications": "ప్రకటనలు",
"notifications.read": "చదివాను!", "notifications.read": "చదివాను!",
"notifications.repeated_you": "మీ స్థితిని పునరావృతం చేసారు", "notifications.repeated_you": "మీ స్థితిని పునరావృతం చేసారు",
"notifications.no_more_notifications": "ఇక నోటిఫికేషన్లు లేవు", "notifications.no_more_notifications": "ఇక నోటిఫికేషన్లు లేవు",
"post_status.new_status": "క్రొత్త స్థితిని పోస్ట్ చేయండి", "post_status.new_status": "క్రొత్త స్థితిని పోస్ట్ చేయండి",
"post_status.account_not_locked_warning": "మీ ఖాతా {} కాదు. ఎవరైనా మిమ్మల్ని అనుసరించి అనుచరులకు మాత్రమే ఉద్దేశించిన పోస్టులను చూడవచ్చు.", "post_status.account_not_locked_warning": "మీ ఖాతా {} కాదు. ఎవరైనా మిమ్మల్ని అనుసరించి అనుచరులకు మాత్రమే ఉద్దేశించిన పోస్టులను చూడవచ్చు.",
"post_status.account_not_locked_warning_link": "తాళం వేయబడినది", "post_status.account_not_locked_warning_link": "తాళం వేయబడినది",
"post_status.attachments_sensitive": "జోడింపులను సున్నితమైనవిగా గుర్తించండి", "post_status.attachments_sensitive": "జోడింపులను సున్నితమైనవిగా గుర్తించండి",
"post_status.content_type.text/plain": "సాధారణ అక్షరాలు", "post_status.content_type.text/plain": "సాధారణ అక్షరాలు",
"post_status.content_type.text/html": "హెచ్‌టిఎమ్ఎల్", "post_status.content_type.text/html": "హెచ్‌టిఎమ్ఎల్",
"post_status.content_type.text/markdown": "మార్క్డౌన్", "post_status.content_type.text/markdown": "మార్క్డౌన్",
"post_status.content_warning": "విషయం (ఐచ్ఛికం)", "post_status.content_warning": "విషయం (ఐచ్ఛికం)",
"post_status.default": "ఇప్పుడే విజయవాడలో దిగాను.", "post_status.default": "ఇప్పుడే విజయవాడలో దిగాను.",
"post_status.direct_warning": "ఈ పోస్ట్ మాత్రమే పేర్కొన్న వినియోగదారులకు మాత్రమే కనిపిస్తుంది.", "post_status.direct_warning": "ఈ పోస్ట్ మాత్రమే పేర్కొన్న వినియోగదారులకు మాత్రమే కనిపిస్తుంది.",
"post_status.posting": "పోస్ట్ చేస్తున్నా", "post_status.posting": "పోస్ట్ చేస్తున్నా",
"post_status.scope.direct": "ప్రత్యక్ష - పేర్కొన్న వినియోగదారులకు మాత్రమే పోస్ట్ చేయబడుతుంది", "post_status.scope.direct": "ప్రత్యక్ష - పేర్కొన్న వినియోగదారులకు మాత్రమే పోస్ట్ చేయబడుతుంది",
"post_status.scope.private": "అనుచరులకు మాత్రమే - అనుచరులకు మాత్రమే పోస్ట్ చేయబడుతుంది", "post_status.scope.private": "అనుచరులకు మాత్రమే - అనుచరులకు మాత్రమే పోస్ట్ చేయబడుతుంది",
"post_status.scope.public": "పబ్లిక్ - ప్రజా కాలక్రమాలకు పోస్ట్ చేయబడుతుంది", "post_status.scope.public": "పబ్లిక్ - ప్రజా కాలక్రమాలకు పోస్ట్ చేయబడుతుంది",
"post_status.scope.unlisted": "జాబితా చేయబడనిది - ప్రజా కాలక్రమాలకు పోస్ట్ చేయవద్దు", "post_status.scope.unlisted": "జాబితా చేయబడనిది - ప్రజా కాలక్రమాలకు పోస్ట్ చేయవద్దు",
"registration.bio": "బయో", "registration.bio": "బయో",
"registration.email": "ఈ మెయిల్", "registration.email": "ఈ మెయిల్",
"registration.fullname": "ప్రదర్శన పేరు", "registration.fullname": "ప్రదర్శన పేరు",
"registration.password_confirm": "పాస్వర్డ్ నిర్ధారణ", "registration.password_confirm": "పాస్వర్డ్ నిర్ధారణ",
"registration.registration": "నమోదు", "registration.registration": "నమోదు",
"registration.token": "ఆహ్వాన టోకెన్", "registration.token": "ఆహ్వాన టోకెన్",
"registration.captcha": "కాప్చా", "registration.captcha": "కాప్చా",
"registration.new_captcha": "కొత్త కాప్చా పొందుటకు చిత్రం మీద క్లిక్ చేయండి", "registration.new_captcha": "కొత్త కాప్చా పొందుటకు చిత్రం మీద క్లిక్ చేయండి",
"registration.username_placeholder": "ఉదా. lain", "registration.username_placeholder": "ఉదా. lain",
"registration.fullname_placeholder": "ఉదా. Lain Iwakura", "registration.fullname_placeholder": "ఉదా. Lain Iwakura",
"registration.bio_placeholder": "e.g.\nHi, I'm Lain.\nIm an anime girl living in suburban Japan. You may know me from the Wired.", "registration.bio_placeholder": "e.g.\nHi, I'm Lain.\nIm an anime girl living in suburban Japan. You may know me from the Wired.",
"registration.validations.username_required": "ఖాళీగా విడిచిపెట్టరాదు", "registration.validations.username_required": "ఖాళీగా విడిచిపెట్టరాదు",
"registration.validations.fullname_required": "ఖాళీగా విడిచిపెట్టరాదు", "registration.validations.fullname_required": "ఖాళీగా విడిచిపెట్టరాదు",
"registration.validations.email_required": "ఖాళీగా విడిచిపెట్టరాదు", "registration.validations.email_required": "ఖాళీగా విడిచిపెట్టరాదు",
"registration.validations.password_required": "ఖాళీగా విడిచిపెట్టరాదు", "registration.validations.password_required": "ఖాళీగా విడిచిపెట్టరాదు",
"registration.validations.password_confirmation_required": "ఖాళీగా విడిచిపెట్టరాదు", "registration.validations.password_confirmation_required": "ఖాళీగా విడిచిపెట్టరాదు",
"registration.validations.password_confirmation_match": "సంకేతపదం వలె ఉండాలి", "registration.validations.password_confirmation_match": "సంకేతపదం వలె ఉండాలి",
"settings.app_name": "అనువర్తన పేరు", "settings.app_name": "అనువర్తన పేరు",
"settings.attachmentRadius": "జోడింపులు", "settings.attachmentRadius": "జోడింపులు",
"settings.attachments": "జోడింపులు", "settings.attachments": "జోడింపులు",
"settings.autoload": "క్రిందికి స్క్రోల్ చేయబడినప్పుడు స్వయంచాలక లోడింగ్ని ప్రారంభించు", "settings.autoload": "క్రిందికి స్క్రోల్ చేయబడినప్పుడు స్వయంచాలక లోడింగ్ని ప్రారంభించు",
"settings.avatar": "అవతారం", "settings.avatar": "అవతారం",
"settings.avatarAltRadius": "అవతారాలు (ప్రకటనలు)", "settings.avatarAltRadius": "అవతారాలు (ప్రకటనలు)",
"settings.avatarRadius": "అవతారాలు", "settings.avatarRadius": "అవతారాలు",
"settings.background": "బ్యాక్‌గ్రౌండు", "settings.background": "బ్యాక్‌గ్రౌండు",
"settings.bio": "బయో", "settings.bio": "బయో",
"settings.blocks_tab": "బ్లాక్‌లు", "settings.blocks_tab": "బ్లాక్‌లు",
"settings.btnRadius": "బటన్లు", "settings.btnRadius": "బటన్లు",
"settings.cBlue": "నీలం (ప్రత్యుత్తరం, అనుసరించండి)", "settings.cBlue": "నీలం (ప్రత్యుత్తరం, అనుసరించండి)",
"settings.cGreen": "Green (Retweet)", "settings.cGreen": "Green (Retweet)",
"settings.cOrange": "ఆరెంజ్ (ఇష్టపడు)", "settings.cOrange": "ఆరెంజ్ (ఇష్టపడు)",
"settings.cRed": "Red (Cancel)", "settings.cRed": "Red (Cancel)",
"settings.change_password": "పాస్‌వర్డ్ మార్చండి", "settings.change_password": "పాస్‌వర్డ్ మార్చండి",
"settings.change_password_error": "మీ పాస్వర్డ్ను మార్చడంలో సమస్య ఉంది.", "settings.change_password_error": "మీ పాస్వర్డ్ను మార్చడంలో సమస్య ఉంది.",
"settings.changed_password": "పాస్వర్డ్ విజయవంతంగా మార్చబడింది!", "settings.changed_password": "పాస్వర్డ్ విజయవంతంగా మార్చబడింది!",
"settings.collapse_subject": "Collapse posts with subjects", "settings.collapse_subject": "Collapse posts with subjects",
"settings.composing": "Composing", "settings.composing": "Composing",
"settings.confirm_new_password": "కొత్త పాస్వర్డ్ను నిర్ధారించండి", "settings.confirm_new_password": "కొత్త పాస్వర్డ్ను నిర్ధారించండి",
"settings.current_avatar": "మీ ప్రస్తుత అవతారం", "settings.current_avatar": "మీ ప్రస్తుత అవతారం",
"settings.current_password": "ప్రస్తుత పాస్వర్డ్", "settings.current_password": "ప్రస్తుత పాస్వర్డ్",
"settings.current_profile_banner": "మీ ప్రస్తుత ప్రొఫైల్ బ్యానర్", "settings.current_profile_banner": "మీ ప్రస్తుత ప్రొఫైల్ బ్యానర్",
"settings.data_import_export_tab": "Data Import / Export", "settings.data_import_export_tab": "Data Import / Export",
"settings.default_vis": "Default visibility scope", "settings.default_vis": "Default visibility scope",
"settings.delete_account": "Delete Account", "settings.delete_account": "Delete Account",
"settings.delete_account_description": "మీ ఖాతా మరియు మీ అన్ని సందేశాలను శాశ్వతంగా తొలగించండి.", "settings.delete_account_description": "మీ ఖాతా మరియు మీ అన్ని సందేశాలను శాశ్వతంగా తొలగించండి.",
"settings.delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.", "settings.delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
"settings.delete_account_instructions": "ఖాతా తొలగింపును నిర్ధారించడానికి దిగువ ఇన్పుట్లో మీ పాస్వర్డ్ను టైప్ చేయండి.", "settings.delete_account_instructions": "ఖాతా తొలగింపును నిర్ధారించడానికి దిగువ ఇన్పుట్లో మీ పాస్వర్డ్ను టైప్ చేయండి.",
"settings.avatar_size_instruction": "అవతార్ చిత్రాలకు సిఫార్సు చేసిన కనీస పరిమాణం 150x150 పిక్సెల్స్.", "settings.avatar_size_instruction": "అవతార్ చిత్రాలకు సిఫార్సు చేసిన కనీస పరిమాణం 150x150 పిక్సెల్స్.",
"settings.export_theme": "Save preset", "settings.export_theme": "Save preset",
"settings.filtering": "వడపోత", "settings.filtering": "వడపోత",
"settings.filtering_explanation": "All statuses containing these words will be muted, one per line", "settings.filtering_explanation": "All statuses containing these words will be muted, one per line",
"settings.follow_export": "Follow export", "settings.follow_export": "Follow export",
"settings.follow_export_button": "Export your follows to a csv file", "settings.follow_export_button": "Export your follows to a csv file",
"settings.follow_export_processing": "Processing, you'll soon be asked to download your file", "settings.follow_export_processing": "Processing, you'll soon be asked to download your file",
"settings.follow_import": "Follow import", "settings.follow_import": "Follow import",
"settings.follow_import_error": "అనుచరులను దిగుమతి చేయడంలో లోపం", "settings.follow_import_error": "అనుచరులను దిగుమతి చేయడంలో లోపం",
"settings.follows_imported": "Follows imported! Processing them will take a while.", "settings.follows_imported": "Follows imported! Processing them will take a while.",
"settings.foreground": "Foreground", "settings.foreground": "Foreground",
"settings.general": "General", "settings.general": "General",
"settings.hide_attachments_in_convo": "సంభాషణలలో జోడింపులను దాచు", "settings.hide_attachments_in_convo": "సంభాషణలలో జోడింపులను దాచు",
"settings.hide_attachments_in_tl": "కాలక్రమంలో జోడింపులను దాచు", "settings.hide_attachments_in_tl": "కాలక్రమంలో జోడింపులను దాచు",
"settings.hide_muted_posts": "మ్యూట్ చేసిన వినియోగదారుల యొక్క పోస్ట్లను దాచిపెట్టు", "settings.hide_muted_posts": "మ్యూట్ చేసిన వినియోగదారుల యొక్క పోస్ట్లను దాచిపెట్టు",
"settings.max_thumbnails": "Maximum amount of thumbnails per post", "settings.max_thumbnails": "Maximum amount of thumbnails per post",
"settings.hide_isp": "Hide instance-specific panel", "settings.hide_isp": "Hide instance-specific panel",
"settings.preload_images": "Preload images", "settings.preload_images": "Preload images",
"settings.use_one_click_nsfw": "కేవలం ఒక క్లిక్ తో NSFW జోడింపులను తెరవండి", "settings.use_one_click_nsfw": "కేవలం ఒక క్లిక్ తో NSFW జోడింపులను తెరవండి",
"settings.hide_post_stats": "Hide post statistics (e.g. the number of favorites)", "settings.hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
"settings.hide_user_stats": "Hide user statistics (e.g. the number of followers)", "settings.hide_user_stats": "Hide user statistics (e.g. the number of followers)",
"settings.hide_filtered_statuses": "Hide filtered statuses", "settings.hide_filtered_statuses": "Hide filtered statuses",
"settings.import_followers_from_a_csv_file": "Import follows from a csv file", "settings.import_followers_from_a_csv_file": "Import follows from a csv file",
"settings.import_theme": "Load preset", "settings.import_theme": "Load preset",
"settings.inputRadius": "Input fields", "settings.inputRadius": "Input fields",
"settings.checkboxRadius": "Checkboxes", "settings.checkboxRadius": "Checkboxes",
"settings.instance_default": "(default: {value})", "settings.instance_default": "(default: {value})",
"settings.instance_default_simple": "(default)", "settings.instance_default_simple": "(default)",
"settings.interface": "Interface", "settings.interface": "Interface",
"settings.interfaceLanguage": "Interface language", "settings.interfaceLanguage": "Interface language",
"settings.invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.", "settings.invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
"settings.limited_availability": "మీ బ్రౌజర్లో అందుబాటులో లేదు", "settings.limited_availability": "మీ బ్రౌజర్లో అందుబాటులో లేదు",
"settings.links": "Links", "settings.links": "Links",
"settings.lock_account_description": "మీ ఖాతాను ఆమోదించిన అనుచరులకు మాత్రమే పరిమితం చేయండి", "settings.lock_account_description": "మీ ఖాతాను ఆమోదించిన అనుచరులకు మాత్రమే పరిమితం చేయండి",
"settings.loop_video": "Loop videos", "settings.loop_video": "Loop videos",
"settings.loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")", "settings.loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
"settings.mutes_tab": "మ్యూట్ చేయబడినవి", "settings.mutes_tab": "మ్యూట్ చేయబడినవి",
"settings.play_videos_in_modal": "మీడియా వీక్షికలో నేరుగా వీడియోలను ప్లే చేయి", "settings.play_videos_in_modal": "మీడియా వీక్షికలో నేరుగా వీడియోలను ప్లే చేయి",
"settings.use_contain_fit": "అటాచ్మెంట్ సూక్ష్మచిత్రాలను కత్తిరించవద్దు", "settings.use_contain_fit": "అటాచ్మెంట్ సూక్ష్మచిత్రాలను కత్తిరించవద్దు",
"settings.name": "Name", "settings.name": "Name",
"settings.name_bio": "పేరు & బయో", "settings.name_bio": "పేరు & బయో",
"settings.new_password": "కొత్త సంకేతపదం", "settings.new_password": "కొత్త సంకేతపదం",
"settings.notification_visibility": "చూపించవలసిన నోటిఫికేషన్ రకాలు", "settings.notification_visibility": "చూపించవలసిన నోటిఫికేషన్ రకాలు",
"settings.notification_visibility_follows": "Follows", "settings.notification_visibility_follows": "Follows",
"settings.notification_visibility_likes": "ఇష్టాలు", "settings.notification_visibility_likes": "ఇష్టాలు",
"settings.notification_visibility_mentions": "ప్రస్తావనలు", "settings.notification_visibility_mentions": "ప్రస్తావనలు",
"settings.notification_visibility_repeats": "పునఃప్రసారాలు", "settings.notification_visibility_repeats": "పునఃప్రసారాలు",
"settings.no_rich_text_description": "అన్ని పోస్ట్ల నుండి రిచ్ టెక్స్ట్ ఫార్మాటింగ్ను స్ట్రిప్ చేయండి", "settings.no_rich_text_description": "అన్ని పోస్ట్ల నుండి రిచ్ టెక్స్ట్ ఫార్మాటింగ్ను స్ట్రిప్ చేయండి",
"settings.no_blocks": "బ్లాక్స్ లేవు", "settings.no_blocks": "బ్లాక్స్ లేవు",
"settings.no_mutes": "మ్యూట్లు లేవు", "settings.no_mutes": "మ్యూట్లు లేవు",
"settings.hide_follows_description": "నేను ఎవరిని అనుసరిస్తున్నానో చూపించవద్దు", "settings.hide_follows_description": "నేను ఎవరిని అనుసరిస్తున్నానో చూపించవద్దు",
"settings.hide_followers_description": "నన్ను ఎవరు అనుసరిస్తున్నారో చూపవద్దు", "settings.hide_followers_description": "నన్ను ఎవరు అనుసరిస్తున్నారో చూపవద్దు",
"settings.show_admin_badge": "నా ప్రొఫైల్ లో అడ్మిన్ బ్యాడ్జ్ చూపించు", "settings.show_admin_badge": "నా ప్రొఫైల్ లో అడ్మిన్ బ్యాడ్జ్ చూపించు",
"settings.show_moderator_badge": "నా ప్రొఫైల్లో మోడరేటర్ బ్యాడ్జ్ని చూపించు", "settings.show_moderator_badge": "నా ప్రొఫైల్లో మోడరేటర్ బ్యాడ్జ్ని చూపించు",
"settings.nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding", "settings.nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
"settings.oauth_tokens": "OAuth tokens", "settings.oauth_tokens": "OAuth tokens",
"settings.token": "Token", "settings.token": "Token",
"settings.refresh_token": "Refresh Token", "settings.refresh_token": "Refresh Token",
"settings.valid_until": "Valid Until", "settings.valid_until": "Valid Until",
"settings.revoke_token": "Revoke", "settings.revoke_token": "Revoke",
"settings.panelRadius": "Panels", "settings.panelRadius": "Panels",
"settings.pause_on_unfocused": "Pause streaming when tab is not focused", "settings.pause_on_unfocused": "Pause streaming when tab is not focused",
"settings.presets": "Presets", "settings.presets": "Presets",
"settings.profile_background": "Profile Background", "settings.profile_background": "Profile Background",
"settings.profile_banner": "Profile Banner", "settings.profile_banner": "Profile Banner",
"settings.profile_tab": "Profile", "settings.profile_tab": "Profile",
"settings.radii_help": "Set up interface edge rounding (in pixels)", "settings.radii_help": "Set up interface edge rounding (in pixels)",
"settings.replies_in_timeline": "Replies in timeline", "settings.replies_in_timeline": "Replies in timeline",
"settings.reply_link_preview": "Enable reply-link preview on mouse hover", "settings.reply_link_preview": "Enable reply-link preview on mouse hover",
"settings.reply_visibility_all": "Show all replies", "settings.reply_visibility_all": "Show all replies",
"settings.reply_visibility_following": "Only show replies directed at me or users I'm following", "settings.reply_visibility_following": "Only show replies directed at me or users I'm following",
"settings.reply_visibility_self": "Only show replies directed at me", "settings.reply_visibility_self": "Only show replies directed at me",
"settings.saving_err": "Error saving settings", "settings.saving_err": "Error saving settings",
"settings.saving_ok": "Settings saved", "settings.saving_ok": "Settings saved",
"settings.security_tab": "Security", "settings.security_tab": "Security",
"settings.scope_copy": "Copy scope when replying (DMs are always copied)", "settings.scope_copy": "Copy scope when replying (DMs are always copied)",
"settings.set_new_avatar": "Set new avatar", "settings.set_new_avatar": "Set new avatar",
"settings.set_new_profile_background": "Set new profile background", "settings.set_new_profile_background": "Set new profile background",
"settings.set_new_profile_banner": "Set new profile banner", "settings.set_new_profile_banner": "Set new profile banner",
"settings.settings": "Settings", "settings.settings": "Settings",
"settings.subject_input_always_show": "Always show subject field", "settings.subject_input_always_show": "Always show subject field",
"settings.subject_line_behavior": "Copy subject when replying", "settings.subject_line_behavior": "Copy subject when replying",
"settings.subject_line_email": "Like email: \"re: subject\"", "settings.subject_line_email": "Like email: \"re: subject\"",
"settings.subject_line_mastodon": "Like mastodon: copy as is", "settings.subject_line_mastodon": "Like mastodon: copy as is",
"settings.subject_line_noop": "Do not copy", "settings.subject_line_noop": "Do not copy",
"settings.post_status_content_type": "Post status content type", "settings.post_status_content_type": "Post status content type",
"settings.stop_gifs": "Play-on-hover GIFs", "settings.stop_gifs": "Play-on-hover GIFs",
"settings.streaming": "Enable automatic streaming of new posts when scrolled to the top", "settings.streaming": "Enable automatic streaming of new posts when scrolled to the top",
"settings.text": "Text", "settings.text": "Text",
"settings.theme": "Theme", "settings.theme": "Theme",
"settings.theme_help": "Use hex color codes (#rrggbb) to customize your color theme.", "settings.theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
"settings.theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.", "settings.theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
"settings.theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.", "settings.theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
"settings.tooltipRadius": "Tooltips/alerts", "settings.tooltipRadius": "Tooltips/alerts",
"settings.upload_a_photo": "Upload a photo", "settings.upload_a_photo": "Upload a photo",
"settings.user_settings": "User Settings", "settings.user_settings": "User Settings",
"settings.values.false": "no", "settings.values.false": "no",
"settings.values.true": "yes", "settings.values.true": "yes",
"settings.notifications": "Notifications", "settings.notifications": "Notifications",
"settings.enable_web_push_notifications": "Enable web push notifications", "settings.enable_web_push_notifications": "Enable web push notifications",
"settings.style.switcher.keep_color": "Keep colors", "settings.style.switcher.keep_color": "Keep colors",
"settings.style.switcher.keep_shadows": "Keep shadows", "settings.style.switcher.keep_shadows": "Keep shadows",
"settings.style.switcher.keep_opacity": "Keep opacity", "settings.style.switcher.keep_opacity": "Keep opacity",
"settings.style.switcher.keep_roundness": "Keep roundness", "settings.style.switcher.keep_roundness": "Keep roundness",
"settings.style.switcher.keep_fonts": "Keep fonts", "settings.style.switcher.keep_fonts": "Keep fonts",
"settings.style.switcher.save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.", "settings.style.switcher.save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.",
"settings.style.switcher.reset": "Reset", "settings.style.switcher.reset": "Reset",
"settings.style.switcher.clear_all": "Clear all", "settings.style.switcher.clear_all": "Clear all",
"settings.style.switcher.clear_opacity": "Clear opacity", "settings.style.switcher.clear_opacity": "Clear opacity",
"settings.style.common.color": "Color", "settings.style.common.color": "Color",
"settings.style.common.opacity": "Opacity", "settings.style.common.opacity": "Opacity",
"settings.style.common.contrast.hint": "Contrast ratio is {ratio}, it {level} {context}", "settings.style.common.contrast.hint": "Contrast ratio is {ratio}, it {level} {context}",
"settings.style.common.contrast.level.aa": "meets Level AA guideline (minimal)", "settings.style.common.contrast.level.aa": "meets Level AA guideline (minimal)",
"settings.style.common.contrast.level.aaa": "meets Level AAA guideline (recommended)", "settings.style.common.contrast.level.aaa": "meets Level AAA guideline (recommended)",
"settings.style.common.contrast.level.bad": "doesn't meet any accessibility guidelines", "settings.style.common.contrast.level.bad": "doesn't meet any accessibility guidelines",
"settings.style.common.contrast.context.18pt": "for large (18pt+) text", "settings.style.common.contrast.context.18pt": "for large (18pt+) text",
"settings.style.common.contrast.context.text": "for text", "settings.style.common.contrast.context.text": "for text",
"settings.style.common_colors._tab_label": "Common", "settings.style.common_colors._tab_label": "Common",
"settings.style.common_colors.main": "Common colors", "settings.style.common_colors.main": "Common colors",
"settings.style.common_colors.foreground_hint": "See \"Advanced\" tab for more detailed control", "settings.style.common_colors.foreground_hint": "See \"Advanced\" tab for more detailed control",
"settings.style.common_colors.rgbo": "Icons, accents, badges", "settings.style.common_colors.rgbo": "Icons, accents, badges",
"settings.style.advanced_colors._tab_label": "Advanced", "settings.style.advanced_colors._tab_label": "Advanced",
"settings.style.advanced_colors.alert": "Alert background", "settings.style.advanced_colors.alert": "Alert background",
"settings.style.advanced_colors.alert_error": "Error", "settings.style.advanced_colors.alert_error": "Error",
"settings.style.advanced_colors.badge": "Badge background", "settings.style.advanced_colors.badge": "Badge background",
"settings.style.advanced_colors.badge_notification": "Notification", "settings.style.advanced_colors.badge_notification": "Notification",
"settings.style.advanced_colors.panel_header": "Panel header", "settings.style.advanced_colors.panel_header": "Panel header",
"settings.style.advanced_colors.top_bar": "Top bar", "settings.style.advanced_colors.top_bar": "Top bar",
"settings.style.advanced_colors.borders": "Borders", "settings.style.advanced_colors.borders": "Borders",
"settings.style.advanced_colors.buttons": "Buttons", "settings.style.advanced_colors.buttons": "Buttons",
"settings.style.advanced_colors.inputs": "Input fields", "settings.style.advanced_colors.inputs": "Input fields",
"settings.style.advanced_colors.faint_text": "Faded text", "settings.style.advanced_colors.faint_text": "Faded text",
"settings.style.radii._tab_label": "Roundness", "settings.style.radii._tab_label": "Roundness",
"settings.style.shadows._tab_label": "Shadow and lighting", "settings.style.shadows._tab_label": "Shadow and lighting",
"settings.style.shadows.component": "Component", "settings.style.shadows.component": "Component",
"settings.style.shadows.override": "Override", "settings.style.shadows.override": "Override",
"settings.style.shadows.shadow_id": "Shadow #{value}", "settings.style.shadows.shadow_id": "Shadow #{value}",
"settings.style.shadows.blur": "Blur", "settings.style.shadows.blur": "Blur",
"settings.style.shadows.spread": "Spread", "settings.style.shadows.spread": "Spread",
"settings.style.shadows.inset": "Inset", "settings.style.shadows.inset": "Inset",
"settings.style.shadows.hint": "For shadows you can also use --variable as a color value to use CSS3 variables. Please note that setting opacity won't work in this case.", "settings.style.shadows.hint": "For shadows you can also use --variable as a color value to use CSS3 variables. Please note that setting opacity won't work in this case.",
"settings.style.shadows.filter_hint.always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.", "settings.style.shadows.filter_hint.always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
"settings.style.shadows.filter_hint.drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.", "settings.style.shadows.filter_hint.drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",
"settings.style.shadows.filter_hint.avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.", "settings.style.shadows.filter_hint.avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.",
"settings.style.shadows.filter_hint.spread_zero": "Shadows with spread > 0 will appear as if it was set to zero", "settings.style.shadows.filter_hint.spread_zero": "Shadows with spread > 0 will appear as if it was set to zero",
"settings.style.shadows.filter_hint.inset_classic": "Inset shadows will be using {0}", "settings.style.shadows.filter_hint.inset_classic": "Inset shadows will be using {0}",
"settings.style.shadows.components.panel": "Panel", "settings.style.shadows.components.panel": "Panel",
"settings.style.shadows.components.panelHeader": "Panel header", "settings.style.shadows.components.panelHeader": "Panel header",
"settings.style.shadows.components.topBar": "Top bar", "settings.style.shadows.components.topBar": "Top bar",
"settings.style.shadows.components.avatar": "User avatar (in profile view)", "settings.style.shadows.components.avatar": "User avatar (in profile view)",
"settings.style.shadows.components.avatarStatus": "User avatar (in post display)", "settings.style.shadows.components.avatarStatus": "User avatar (in post display)",
"settings.style.shadows.components.popup": "Popups and tooltips", "settings.style.shadows.components.popup": "Popups and tooltips",
"settings.style.shadows.components.button": "Button", "settings.style.shadows.components.button": "Button",
"settings.style.shadows.components.buttonHover": "Button (hover)", "settings.style.shadows.components.buttonHover": "Button (hover)",
"settings.style.shadows.components.buttonPressed": "Button (pressed)", "settings.style.shadows.components.buttonPressed": "Button (pressed)",
"settings.style.shadows.components.buttonPressedHover": "Button (pressed+hover)", "settings.style.shadows.components.buttonPressedHover": "Button (pressed+hover)",
"settings.style.shadows.components.input": "Input field", "settings.style.shadows.components.input": "Input field",
"settings.style.fonts._tab_label": "Fonts", "settings.style.fonts._tab_label": "Fonts",
"settings.style.fonts.help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.", "settings.style.fonts.help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.",
"settings.style.fonts.components.interface": "Interface", "settings.style.fonts.components.interface": "Interface",
"settings.style.fonts.components.input": "Input fields", "settings.style.fonts.components.input": "Input fields",
"settings.style.fonts.components.post": "Post text", "settings.style.fonts.components.post": "Post text",
"settings.style.fonts.components.postCode": "Monospaced text in a post (rich text)", "settings.style.fonts.components.postCode": "Monospaced text in a post (rich text)",
"settings.style.fonts.family": "Font name", "settings.style.fonts.family": "Font name",
"settings.style.fonts.size": "Size (in px)", "settings.style.fonts.size": "Size (in px)",
"settings.style.fonts.weight": "Weight (boldness)", "settings.style.fonts.weight": "Weight (boldness)",
"settings.style.fonts.custom": "Custom", "settings.style.fonts.custom": "Custom",
"settings.style.preview.header": "Preview", "settings.style.preview.header": "Preview",
"settings.style.preview.content": "Content", "settings.style.preview.content": "Content",
"settings.style.preview.error": "Example error", "settings.style.preview.error": "Example error",
"settings.style.preview.button": "Button", "settings.style.preview.button": "Button",
"settings.style.preview.text": "A bunch of more {0} and {1}", "settings.style.preview.text": "A bunch of more {0} and {1}",
"settings.style.preview.mono": "content", "settings.style.preview.mono": "content",
"settings.style.preview.input": "Just landed in L.A.", "settings.style.preview.input": "Just landed in L.A.",
"settings.style.preview.faint_link": "helpful manual", "settings.style.preview.faint_link": "helpful manual",
"settings.style.preview.fine_print": "Read our {0} to learn nothing useful!", "settings.style.preview.fine_print": "Read our {0} to learn nothing useful!",
"settings.style.preview.header_faint": "This is fine", "settings.style.preview.header_faint": "This is fine",
"settings.style.preview.checkbox": "I have skimmed over terms and conditions", "settings.style.preview.checkbox": "I have skimmed over terms and conditions",
"settings.style.preview.link": "a nice lil' link", "settings.style.preview.link": "a nice lil' link",
"settings.version.title": "Version", "settings.version.title": "Version",
"settings.version.backend_version": "Backend Version", "settings.version.backend_version": "Backend Version",
"settings.version.frontend_version": "Frontend Version", "settings.version.frontend_version": "Frontend Version",
"timeline.collapse": "Collapse", "timeline.collapse": "Collapse",
"timeline.conversation": "Conversation", "timeline.conversation": "Conversation",
"timeline.error_fetching": "Error fetching updates", "timeline.error_fetching": "Error fetching updates",
"timeline.load_older": "Load older statuses", "timeline.load_older": "Load older statuses",
"timeline.no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated", "timeline.no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
"timeline.repeated": "repeated", "timeline.repeated": "repeated",
"timeline.show_new": "Show new", "timeline.show_new": "Show new",
"timeline.up_to_date": "Up-to-date", "timeline.up_to_date": "Up-to-date",
"timeline.no_more_statuses": "No more statuses", "timeline.no_more_statuses": "No more statuses",
"timeline.no_statuses": "No statuses", "timeline.no_statuses": "No statuses",
"status.reply_to": "Reply to", "status.reply_to": "Reply to",
"status.replies_list": "Replies:", "status.replies_list": "Replies:",
"user_card.approve": "Approve", "user_card.approve": "Approve",
"user_card.block": "Block", "user_card.block": "Block",
"user_card.blocked": "Blocked!", "user_card.blocked": "Blocked!",
"user_card.deny": "Deny", "user_card.deny": "Deny",
"user_card.favorites": "Favorites", "user_card.favorites": "Favorites",
"user_card.follow": "Follow", "user_card.follow": "Follow",
"user_card.follow_sent": "Request sent!", "user_card.follow_sent": "Request sent!",
"user_card.follow_progress": "Requesting…", "user_card.follow_progress": "Requesting…",
"user_card.follow_again": "Send request again?", "user_card.follow_again": "Send request again?",
"user_card.follow_unfollow": "Unfollow", "user_card.follow_unfollow": "Unfollow",
"user_card.followees": "Following", "user_card.followees": "Following",
"user_card.followers": "Followers", "user_card.followers": "Followers",
"user_card.following": "Following!", "user_card.following": "Following!",
"user_card.follows_you": "Follows you!", "user_card.follows_you": "Follows you!",
"user_card.its_you": "It's you!", "user_card.its_you": "It's you!",
"user_card.media": "Media", "user_card.media": "Media",
"user_card.mute": "Mute", "user_card.mute": "Mute",
"user_card.muted": "Muted", "user_card.muted": "Muted",
"user_card.per_day": "per day", "user_card.per_day": "per day",
"user_card.remote_follow": "Remote follow", "user_card.remote_follow": "Remote follow",
"user_card.statuses": "Statuses", "user_card.statuses": "Statuses",
"user_card.unblock": "Unblock", "user_card.unblock": "Unblock",
"user_card.unblock_progress": "Unblocking...", "user_card.unblock_progress": "Unblocking...",
"user_card.block_progress": "Blocking...", "user_card.block_progress": "Blocking...",
"user_card.unmute": "Unmute", "user_card.unmute": "Unmute",
"user_card.unmute_progress": "Unmuting...", "user_card.unmute_progress": "Unmuting...",
"user_card.mute_progress": "Muting...", "user_card.mute_progress": "Muting...",
"user_profile.timeline_title": "User Timeline", "user_profile.timeline_title": "User Timeline",
"user_profile.profile_does_not_exist": "Sorry, this profile does not exist.", "user_profile.profile_does_not_exist": "Sorry, this profile does not exist.",
"user_profile.profile_loading_error": "Sorry, there was an error loading this profile.", "user_profile.profile_loading_error": "Sorry, there was an error loading this profile.",
"who_to_follow.more": "More", "who_to_follow.more": "More",
"who_to_follow.who_to_follow": "Who to follow", "who_to_follow.who_to_follow": "Who to follow",
"tool_tip.media_upload": "Upload Media", "tool_tip.media_upload": "Upload Media",
"tool_tip.repeat": "Repeat", "tool_tip.repeat": "Repeat",
"tool_tip.reply": "Reply", "tool_tip.reply": "Reply",
"tool_tip.favorite": "Favorite", "tool_tip.favorite": "Favorite",
"tool_tip.user_settings": "User Settings", "tool_tip.user_settings": "User Settings",
"upload.error.base": "Upload failed.", "upload.error.base": "Upload failed.",
"upload.error.file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", "upload.error.file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
"upload.error.default": "Try again later", "upload.error.default": "Try again later",
"upload.file_size_units.B": "B", "upload.file_size_units.B": "B",
"upload.file_size_units.KiB": "KiB", "upload.file_size_units.KiB": "KiB",
"upload.file_size_units.MiB": "MiB", "upload.file_size_units.MiB": "MiB",
"upload.file_size_units.GiB": "GiB", "upload.file_size_units.GiB": "GiB",
"upload.file_size_units.TiB": "TiB" "upload.file_size_units.TiB": "TiB"
} }

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,12 @@
import merge from 'lodash.merge' import merge from 'lodash.merge'
import objectPath from 'object-path'
import localforage from 'localforage' import localforage from 'localforage'
import { each } from 'lodash' import { each, get, set } from 'lodash'
let loaded = false let loaded = false
const defaultReducer = (state, paths) => ( const defaultReducer = (state, paths) => (
paths.length === 0 ? state : paths.reduce((substate, path) => { paths.length === 0 ? state : paths.reduce((substate, path) => {
objectPath.set(substate, path, objectPath.get(state, path)) set(substate, path, get(state, path))
return substate return substate
}, {}) }, {})
) )

View file

@ -46,11 +46,13 @@ Vue.use(VBodyScrollLock)
const i18n = new VueI18n({ const i18n = new VueI18n({
// By default, use the browser locale, we will update it if neccessary // By default, use the browser locale, we will update it if neccessary
locale: currentLocale, locale: 'en',
fallbackLocale: 'en', fallbackLocale: 'en',
messages messages: messages.default
}) })
messages.setLanguage(i18n, currentLocale)
const persistedStateOptions = { const persistedStateOptions = {
paths: [ paths: [
'config', 'config',

View file

@ -1,8 +1,19 @@
import { set, delete as del } from 'vue' import { set, delete as del } from 'vue'
import { setPreset, applyTheme } from '../services/style_setter/style_setter.js' import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
import messages from '../i18n/messages'
const browserLocale = (window.navigator.language || 'en').split('-')[0] const browserLocale = (window.navigator.language || 'en').split('-')[0]
/* TODO this is a bit messy.
* We need to declare settings with their types and also deal with
* instance-default settings in some way, hopefully try to avoid copy-pasta
* in general.
*/
export const multiChoiceProperties = [
'postContentType',
'subjectLineBehavior'
]
export const defaultState = { export const defaultState = {
colors: {}, colors: {},
theme: undefined, theme: undefined,
@ -105,6 +116,10 @@ const config = {
case 'customTheme': case 'customTheme':
case 'customThemeSource': case 'customThemeSource':
applyTheme(value) applyTheme(value)
break
case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value)
break
} }
} }
} }

View file

@ -1,6 +1,7 @@
import { set } from 'vue' import { set } from 'vue'
import { getPreset, applyTheme } from '../services/style_setter/style_setter.js' import { getPreset, applyTheme } from '../services/style_setter/style_setter.js'
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
import apiService from '../services/api/api.service.js'
import { instanceDefaultProperties } from './config.js' import { instanceDefaultProperties } from './config.js'
const defaultState = { const defaultState = {
@ -48,6 +49,7 @@ const defaultState = {
postFormats: [], postFormats: [],
restrictedNicknames: [], restrictedNicknames: [],
safeDM: true, safeDM: true,
knownDomains: [],
// Feature-set, apparently, not everything here is reported... // Feature-set, apparently, not everything here is reported...
chatAvailable: false, chatAvailable: false,
@ -80,6 +82,9 @@ const instance = {
if (typeof value !== 'undefined') { if (typeof value !== 'undefined') {
set(state, name, value) set(state, name, value)
} }
},
setKnownDomains (state, domains) {
state.knownDomains = domains
} }
}, },
getters: { getters: {
@ -182,6 +187,18 @@ const instance = {
state.emojiFetched = true state.emojiFetched = true
dispatch('getStaticEmoji') dispatch('getStaticEmoji')
} }
},
async getKnownDomains ({ commit, rootState }) {
try {
const result = await apiService.fetchKnownDomains({
credentials: rootState.users.currentUser.credentials
})
commit('setKnownDomains', result)
} catch (e) {
console.warn("Can't load known domains")
console.warn(e)
}
} }
} }
} }

View file

@ -1,6 +1,8 @@
import { set, delete as del } from 'vue' import { set, delete as del } from 'vue'
const defaultState = { const defaultState = {
settingsModalState: 'hidden',
settingsModalLoaded: false,
settings: { settings: {
currentSaveStateNotice: null, currentSaveStateNotice: null,
noticeClearTimeout: null, noticeClearTimeout: null,
@ -35,6 +37,27 @@ const interfaceMod = {
}, },
setMobileLayout (state, value) { setMobileLayout (state, value) {
state.mobileLayout = value state.mobileLayout = value
},
closeSettingsModal (state) {
state.settingsModalState = 'hidden'
},
togglePeekSettingsModal (state) {
switch (state.settingsModalState) {
case 'minimized':
state.settingsModalState = 'visible'
return
case 'visible':
state.settingsModalState = 'minimized'
return
default:
throw new Error('Illegal minimization state of settings modal')
}
},
openSettingsModal (state) {
state.settingsModalState = 'visible'
if (!state.settingsModalLoaded) {
state.settingsModalLoaded = true
}
} }
}, },
actions: { actions: {
@ -49,6 +72,15 @@ const interfaceMod = {
}, },
setMobileLayout ({ commit }, value) { setMobileLayout ({ commit }, value) {
commit('setMobileLayout', value) commit('setMobileLayout', value)
},
closeSettingsModal ({ commit }) {
commit('closeSettingsModal')
},
openSettingsModal ({ commit }) {
commit('openSettingsModal')
},
togglePeekSettingsModal ({ commit }) {
commit('togglePeekSettingsModal')
} }
} }
} }

View file

@ -15,7 +15,7 @@ import {
import { set } from 'vue' import { set } from 'vue'
import { isStatusNotification } from '../services/notification_utils/notification_utils.js' import { isStatusNotification } from '../services/notification_utils/notification_utils.js'
import apiService from '../services/api/api.service.js' import apiService from '../services/api/api.service.js'
// import parse from '../services/status_parser/status_parser.js' import { muteWordHits } from '../services/status_parser/status_parser.js'
const emptyTl = (userId = 0) => ({ const emptyTl = (userId = 0) => ({
statuses: [], statuses: [],
@ -381,7 +381,18 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
notifObj.image = status.attachments[0].url notifObj.image = status.attachments[0].url
} }
if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) { const reasonsToMuteNotif = (
notification.seen ||
state.notifications.desktopNotificationSilence ||
!visibleNotificationTypes.includes(notification.type) ||
(
notification.type === 'mention' && status && (
status.muted ||
muteWordHits(status, rootGetters.mergedConfig.muteWords).length === 0
)
)
)
if (!reasonsToMuteNotif) {
let desktopNotification = new window.Notification(title, notifObj) let desktopNotification = new window.Notification(title, notifObj)
// Chrome is known for not closing notifications automatically // Chrome is known for not closing notifications automatically
// according to MDN, anyway. // according to MDN, anyway.

View file

@ -438,10 +438,10 @@ const users = {
store.commit('setUserForNotification', notification) store.commit('setUserForNotification', notification)
}) })
}, },
searchUsers (store, { query }) { searchUsers ({ rootState, commit }, { query }) {
return store.rootState.api.backendInteractor.searchUsers({ query }) return rootState.api.backendInteractor.searchUsers({ query })
.then((users) => { .then((users) => {
store.commit('addNewUsers', users) commit('addNewUsers', users)
return users return users
}) })
}, },

View file

@ -1,6 +1,5 @@
import { each, map, concat, last, get } from 'lodash' import { each, map, concat, last, get } from 'lodash'
import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js' import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
import 'whatwg-fetch'
import { RegistrationError, StatusCodeError } from '../errors/errors' import { RegistrationError, StatusCodeError } from '../errors/errors'
/* eslint-env browser */ /* eslint-env browser */
@ -76,6 +75,7 @@ const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
const MASTODON_MASCOT_URL = '/api/v1/pleroma/mascot' const MASTODON_MASCOT_URL = '/api/v1/pleroma/mascot'
const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks' const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
const MASTODON_STREAMING = '/api/v1/streaming' const MASTODON_STREAMING = '/api/v1/streaming'
const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers'
const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/reactions` const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/reactions`
const PLEROMA_EMOJI_REACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` const PLEROMA_EMOJI_REACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
@ -1010,6 +1010,10 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
}) })
} }
const fetchKnownDomains = ({ credentials }) => {
return promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials })
}
const fetchDomainMutes = ({ credentials }) => { const fetchDomainMutes = ({ credentials }) => {
return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials }) return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials })
} }
@ -1210,6 +1214,7 @@ const apiService = {
updateNotificationSettings, updateNotificationSettings,
search2, search2,
searchUsers, searchUsers,
fetchKnownDomains,
fetchDomainMutes, fetchDomainMutes,
muteDomain, muteDomain,
unmuteDomain unmuteDomain

View file

@ -56,6 +56,12 @@ export const parseUser = (data) => {
value: addEmojis(field.value, data.emojis) value: addEmojis(field.value, data.emojis)
} }
}) })
output.fields_text = data.fields.map(field => {
return {
name: unescape(field.name.replace(/<[^>]*>/g, '')),
value: unescape(field.value.replace(/<[^>]*>/g, ''))
}
})
// Utilize avatar_static for gif avatars? // Utilize avatar_static for gif avatars?
output.profile_image_url = data.avatar output.profile_image_url = data.avatar
@ -258,6 +264,12 @@ export const parseStatus = (data) => {
output.summary_html = addEmojis(escape(data.spoiler_text), data.emojis) output.summary_html = addEmojis(escape(data.spoiler_text), data.emojis)
output.external_url = data.url output.external_url = data.url
output.poll = data.poll output.poll = data.poll
if (output.poll) {
output.poll.options = (output.poll.options || []).map(field => ({
...field,
title_html: addEmojis(field.title, data.emojis)
}))
}
output.pinned = data.pinned output.pinned = data.pinned
output.muted = data.muted output.muted = data.muted
} else { } else {

Some files were not shown because too many files have changed in this diff Show more