Merge remote-tracking branch 'upstream/develop' into neckbeard

This commit is contained in:
Your New SJW Waifu 2020-07-26 11:17:56 -05:00
commit 150fe52a66
32 changed files with 669 additions and 328 deletions

View File

@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Descriptions can be set on uploaded files before posting
- Added status preview option to preview your statuses before posting
- When a post is a reply to an unavailable post, the 'Reply to'-text has a strike-through style
- Added ability to see all favoriting or repeating users when hovering the number on highlighted statuses
### Changed
- Registration page no longer requires email if the server is configured not to require it
@ -50,6 +51,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Videos are not cropped awkwardly in the uploads section anymore
- Reply filtering options in Settings -> Filtering now work again using filtering on server
- Don't show just blank-screen when cookies are disabled
- Add status idempotency to prevent accidental double posting when posting returns an error
## [2.0.3] - 2020-05-02
### Fixed

View File

@ -10,7 +10,7 @@
@click.stop.prevent="togglePanel"
>
<div class="title">
<span>{{ $t('chat.title') }}</span>
<span>{{ $t('shoutbox.title') }}</span>
<i
v-if="floating"
class="icon-cancel"
@ -64,7 +64,7 @@
>
<div class="title">
<i class="icon-comment-empty" />
{{ $t('chat.title') }}
{{ $t('shoutbox.title') }}
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
import Popover from '../popover/popover.vue'
import UserListPopover from '../user_list_popover/user_list_popover.vue'
const EMOJI_REACTION_COUNT_CUTOFF = 12
@ -7,7 +7,7 @@ const EmojiReactions = {
name: 'EmojiReactions',
components: {
UserAvatar,
Popover
UserListPopover
},
props: ['status'],
data: () => ({

View File

@ -1,44 +1,11 @@
<template>
<div class="emoji-reactions">
<Popover
<UserListPopover
v-for="(reaction) in emojiReactions"
:key="reaction.name"
trigger="hover"
placement="top"
:offset="{ y: 5 }"
:users="accountsForEmoji[reaction.name]"
>
<div
slot="content"
class="reacted-users"
>
<div v-if="accountsForEmoji[reaction.name].length">
<div
v-for="(account) in accountsForEmoji[reaction.name]"
:key="account.id"
class="reacted-user"
>
<UserAvatar
:user="account"
class="avatar-small"
:compact="true"
/>
<div class="reacted-user-names">
<!-- eslint-disable vue/no-v-html -->
<span
class="reacted-user-name"
v-html="account.name_html"
/>
<!-- eslint-enable vue/no-v-html -->
<span class="reacted-user-screen-name">{{ account.screen_name }}</span>
</div>
</div>
</div>
<div v-else>
<i class="icon-spin4 animate-spin" />
</div>
</div>
<button
slot="trigger"
class="emoji-reaction btn btn-default"
:class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
@click="emojiOnClick(reaction.name, $event)"
@ -47,7 +14,7 @@
<span class="reaction-emoji">{{ reaction.name }}</span>
<span>{{ reaction.count }}</span>
</button>
</Popover>
</UserListPopover>
<a
v-if="tooManyReactions"
class="emoji-reaction-expand faint"
@ -69,32 +36,6 @@
flex-wrap: wrap;
}
.reacted-users {
padding: 0.5em;
}
.reacted-user {
padding: 0.25em;
display: flex;
flex-direction: row;
.reacted-user-names {
display: flex;
flex-direction: column;
margin-left: 0.5em;
min-width: 5em;
img {
width: 1em;
height: 1em;
}
}
.reacted-user-screen-name {
font-size: 9px;
}
}
.emoji-reaction {
padding: 0 0.5em;
margin-right: 0.5em;

View File

@ -66,6 +66,7 @@ const PostStatusForm = {
StatusContent
},
mounted () {
this.updateIdempotencyKey()
this.resize(this.$refs.textarea)
if (this.replyTo) {
@ -116,7 +117,8 @@ const PostStatusForm = {
dropStopTimeout: null,
preview: null,
previewLoading: false,
emojiInputShown: false
emojiInputShown: false,
idempotencyKey: ''
}
},
computed: {
@ -211,14 +213,43 @@ const PostStatusForm = {
})
},
watch: {
'newStatus.contentType': function () {
this.autoPreview()
},
'newStatus.spoilerText': function () {
this.autoPreview()
'newStatus': {
deep: true,
handler () {
this.statusChanged()
}
}
},
methods: {
statusChanged () {
this.autoPreview()
this.updateIdempotencyKey()
},
clearStatus () {
const newStatus = this.newStatus
this.newStatus = {
status: '',
spoilerText: '',
files: [],
visibility: newStatus.visibility,
contentType: newStatus.contentType,
poll: {},
mediaDescriptions: {}
}
this.pollFormVisible = false
this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
this.clearPollForm()
if (this.preserveFocus) {
this.$nextTick(() => {
this.$refs.textarea.focus()
})
}
let el = this.$el.querySelector('textarea')
el.style.height = 'auto'
el.style.height = undefined
this.error = null
if (this.preview) this.previewStatus()
},
async postStatus (event, newStatus, opts = {}) {
if (this.posting) { return }
if (this.disableSubmit) { return }
@ -258,36 +289,16 @@ const PostStatusForm = {
store: this.$store,
inReplyToStatusId: this.replyTo,
contentType: newStatus.contentType,
poll
poll,
idempotencyKey: this.idempotencyKey
}
const postHandler = this.postHandler ? this.postHandler : statusPoster.postStatus
postHandler(postingOptions).then((data) => {
if (!data.error) {
this.newStatus = {
status: '',
spoilerText: '',
files: [],
visibility: newStatus.visibility,
contentType: newStatus.contentType,
poll: {},
mediaDescriptions: {}
}
this.pollFormVisible = false
this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
this.clearPollForm()
this.clearStatus()
this.$emit('posted', data)
if (this.preserveFocus) {
this.$nextTick(() => {
this.$refs.textarea.focus()
})
}
let el = this.$el.querySelector('textarea')
el.style.height = 'auto'
el.style.height = undefined
this.error = null
if (this.preview) this.previewStatus()
} else {
this.error = data.error
}
@ -404,7 +415,6 @@ const PostStatusForm = {
}
},
onEmojiInputInput (e) {
this.autoPreview()
this.$nextTick(() => {
this.resize(this.$refs['textarea'])
})
@ -542,6 +552,9 @@ const PostStatusForm = {
},
handleEmojiInputShow (value) {
this.emojiInputShown = value
},
updateIdempotencyKey () {
this.idempotencyKey = Date.now().toString()
}
}
}

View File

@ -9,6 +9,7 @@ import AvatarList from '../avatar_list/avatar_list.vue'
import Timeago from '../timeago/timeago.vue'
import StatusContent from '../status_content/status_content.vue'
import StatusPopover from '../status_popover/status_popover.vue'
import UserListPopover from '../user_list_popover/user_list_popover.vue'
import EmojiReactions from '../emoji_reactions/emoji_reactions.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
@ -18,6 +19,21 @@ import { mapGetters, mapState } from 'vuex'
const Status = {
name: 'Status',
components: {
FavoriteButton,
ReactButton,
RetweetButton,
ExtraButtons,
PostStatusForm,
UserCard,
UserAvatar,
AvatarList,
Timeago,
StatusPopover,
UserListPopover,
EmojiReactions,
StatusContent
},
props: [
'statusoid',
'expandable',
@ -197,20 +213,6 @@ const Status = {
currentUser: state => state.users.currentUser
})
},
components: {
FavoriteButton,
ReactButton,
RetweetButton,
ExtraButtons,
PostStatusForm,
UserCard,
UserAvatar,
AvatarList,
Timeago,
StatusPopover,
EmojiReactions,
StatusContent
},
methods: {
visibilityIcon (visibility) {
switch (visibility) {

View File

@ -72,7 +72,10 @@
:user="statusoid.user"
/>
<div class="media-body faint">
<span class="user-name">
<span
class="user-name"
:title="retweeter"
>
<router-link
v-if="retweeterHtml"
:to="retweeterProfileLink"
@ -129,20 +132,28 @@
<h4
v-if="status.user.name_html"
class="user-name"
:title="status.user.name"
v-html="status.user.name_html"
/>
<h4
v-else
class="user-name"
:title="status.user.name"
>
{{ status.user.name }}
</h4>
<router-link
class="account-name"
:title="status.user.screen_name"
:to="userProfileLink"
>
{{ status.user.screen_name }}
</router-link>
<img
class="status-favicon"
v-if="!!(status.user && status.user.favicon)"
:src="status.user.favicon"
>
</div>
<span class="heading-right">
@ -222,7 +233,10 @@
>
<span class="reply-to-text">{{ $t('status.reply_to') }}</span>
</span>
<router-link :to="replyProfileLink">
<router-link
:title="replyToName"
:to="replyProfileLink"
>
{{ replyToName }}
</router-link>
<span
@ -265,24 +279,30 @@
class="favs-repeated-users"
>
<div class="stats">
<div
<UserListPopover
v-if="statusFromGlobalRepository.rebloggedBy && statusFromGlobalRepository.rebloggedBy.length > 0"
class="stat-count"
:users="statusFromGlobalRepository.rebloggedBy"
>
<a class="stat-title">{{ $t('status.repeats') }}</a>
<div class="stat-number">
{{ statusFromGlobalRepository.rebloggedBy.length }}
<div class="stat-count">
<a class="stat-title">{{ $t('status.repeats') }}</a>
<div class="stat-number">
{{ statusFromGlobalRepository.rebloggedBy.length }}
</div>
</div>
</div>
<div
</UserListPopover>
<UserListPopover
v-if="statusFromGlobalRepository.favoritedBy && statusFromGlobalRepository.favoritedBy.length > 0"
class="stat-count"
:users="statusFromGlobalRepository.favoritedBy"
>
<a class="stat-title">{{ $t('status.favorites') }}</a>
<div class="stat-number">
{{ statusFromGlobalRepository.favoritedBy.length }}
<div
class="stat-count"
>
<a class="stat-title">{{ $t('status.favorites') }}</a>
<div class="stat-number">
{{ statusFromGlobalRepository.favoritedBy.length }}
</div>
</div>
</div>
</UserListPopover>
<div class="avatar-row">
<AvatarList :users="combinedFavsAndRepeatsUsers" />
</div>
@ -428,6 +448,12 @@ $status-margin: 0.75em;
}
}
.status-favicon {
height: 18px;
width: 18px;
margin-right: 0.4em;
}
.media-heading {
padding: 0;
vertical-align: bottom;
@ -722,6 +748,11 @@ $status-margin: 0.75em;
.stat-count {
margin-right: $status-margin;
user-select: none;
&:hover .stat-title {
text-decoration: underline;
}
.stat-title {
color: var(--faint, $fallback--faint);

View File

@ -66,6 +66,7 @@
<div class="bottom-line">
<router-link
class="user-screen-name"
:title="user.screen_name"
:to="userProfileLink(user)"
>
@{{ user.screen_name }}

View File

@ -0,0 +1,18 @@
const UserListPopover = {
name: 'UserListPopover',
props: [
'users'
],
components: {
Popover: () => import('../popover/popover.vue'),
UserAvatar: () => import('../user_avatar/user_avatar.vue')
},
computed: {
usersCapped () {
return this.users.slice(0, 16)
}
}
}
export default UserListPopover

View File

@ -0,0 +1,71 @@
<template>
<Popover
trigger="hover"
placement="top"
:offset="{ y: 5 }"
>
<template slot="trigger">
<slot />
</template>
<div
slot="content"
class="user-list-popover"
>
<div v-if="users.length">
<div
v-for="(user) in usersCapped"
:key="user.id"
class="user-list-row"
>
<UserAvatar
:user="user"
class="avatar-small"
:compact="true"
/>
<div class="user-list-names">
<!-- eslint-disable vue/no-v-html -->
<span v-html="user.name_html" />
<!-- eslint-enable vue/no-v-html -->
<span class="user-list-screen-name">{{ user.screen_name }}</span>
</div>
</div>
</div>
<div v-else>
<i class="icon-spin4 animate-spin" />
</div>
</div>
</Popover>
</template>
<script src="./user_list_popover.js" ></script>
<style lang="scss">
@import '../../_variables.scss';
.user-list-popover {
padding: 0.5em;
.user-list-row {
padding: 0.25em;
display: flex;
flex-direction: row;
.user-list-names {
display: flex;
flex-direction: column;
margin-left: 0.5em;
min-width: 5em;
img {
width: 1em;
height: 1em;
}
}
.user-list-screen-name {
font-size: 9px;
}
}
}
</style>

View File

@ -95,8 +95,8 @@
"default_vis": "Abast per defecte de les entrades",
"delete_account": "Esborra el compte",
"delete_account_description": "Esborra permanentment el teu compte i tots els missatges",
"delete_account_error": "No s'ha pogut esborrar el compte. Si continua el problema, contacta amb l'administració del node",
"delete_account_instructions": "Confirma que vols esborrar el compte escrivint la teva contrasenya aquí sota",
"delete_account_error": "No s'ha pogut esborrar el compte. Si continua el problema, contacta amb l'administració del node.",
"delete_account_instructions": "Confirma que vols esborrar el compte escrivint la teva contrasenya aquí sota.",
"export_theme": "Desa el tema",
"filtering": "Filtres",
"filtering_explanation": "Es silenciaran totes les entrades que continguin aquestes paraules. Separa-les per línies",
@ -118,7 +118,7 @@
"invalid_theme_imported": "No s'ha entès l'arxiu carregat perquè no és un tema vàlid de Pleroma. No s'ha fet cap canvi als temes actuals.",
"limited_availability": "No està disponible en aquest navegador",
"links": "Enllaços",
"lock_account_description": "Restringeix el teu compte només a seguidores aprovades.",
"lock_account_description": "Restringeix el teu compte només a seguidores aprovades",
"loop_video": "Reprodueix els vídeos en bucle",
"loop_video_silent_only": "Reprodueix en bucles només els vídeos sense so (com els \"GIF\" de Mastodon)",
"name": "Nom",
@ -158,7 +158,7 @@
"streaming": "Carrega automàticament entrades noves quan estigui a dalt de tot",
"text": "Text",
"theme": "Tema",
"theme_help": "Personalitza els colors del tema. Escriu-los en format RGB hexadecimal (#rrggbb)",
"theme_help": "Personalitza els colors del tema. Escriu-los en format RGB hexadecimal (#rrggbb).",
"tooltipRadius": "Missatges sobreposats",
"user_settings": "Configuració personal",
"values": {

View File

@ -256,7 +256,7 @@
"contrast": {
"hint": "Poměr kontrastu je {ratio}, {level} {context}",
"level": {
"aa": "splňuje směrnici úrovně AA (minimální)",
"aa": "splňuje směrnici úrovně AA (minimální)",
"aaa": "splňuje směrnici úrovně AAA (doporučováno)",
"bad": "nesplňuje žádné směrnice přístupnosti"
},
@ -398,7 +398,6 @@
"reply_to": "Odpověď uživateli",
"replies_list": "Odpovědi:"
},
"user_card": {
"approve": "Schválit",
"block": "Blokovat",
@ -444,7 +443,7 @@
"favorite": "Oblíbit",
"user_settings": "Uživatelské nastavení"
},
"upload":{
"upload": {
"error": {
"base": "Nahrávání selhalo.",
"file_too_big": "Soubor je příliš velký [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",

View File

@ -66,7 +66,7 @@
"who_to_follow": "Wem folgen"
},
"notifications": {
"broken_favorite": "Unbekannte Nachricht, suche danach...",
"broken_favorite": "Unbekannte Nachricht, suche danach",
"favorited_you": "favorisierte deine Nachricht",
"followed_you": "folgt dir",
"load_older": "Ältere Benachrichtigungen laden",
@ -115,7 +115,7 @@
"registration": "Registrierung",
"token": "Einladungsschlüssel",
"captcha": "CAPTCHA",
"new_captcha": "Zum Erstellen eines neuen Captcha auf das Bild klicken.",
"new_captcha": "Zum Erstellen eines neuen Captcha auf das Bild klicken",
"validations": {
"username_required": "darf nicht leer sein",
"fullname_required": "darf nicht leer sein",
@ -161,7 +161,7 @@
"pad_emoji": "Emojis mit Leerzeichen umrahmen",
"export_theme": "Farbschema speichern",
"filtering": "Filtern",
"filtering_explanation": "Alle Beiträge, welche diese Wörter enthalten, werden ausgeblendet. Ein Wort pro Zeile.",
"filtering_explanation": "Alle Beiträge, welche diese Wörter enthalten, werden ausgeblendet. Ein Wort pro Zeile",
"follow_export": "Follower exportieren",
"follow_export_button": "Exportiere deine Follows in eine csv-Datei",
"follow_export_processing": "In Bearbeitung. Die Liste steht gleich zum herunterladen bereit.",
@ -247,7 +247,7 @@
"streaming": "Aktiviere automatisches Laden (Streaming) von neuen Beiträgen",
"text": "Text",
"theme": "Farbschema",
"theme_help": "Benutze HTML-Farbcodes (#rrggbb) um dein Farbschema anzupassen",
"theme_help": "Benutze HTML-Farbcodes (#rrggbb) um dein Farbschema anzupassen.",
"theme_help_v2_1": "Du kannst auch die Farben und die Deckkraft bestimmter Komponenten überschreiben, indem du das Kontrollkästchen umschaltest. Verwende die Schaltfläche \"Alle löschen\", um alle Überschreibungen zurückzusetzen.",
"theme_help_v2_2": "Unter einigen Einträgen befinden sich Symbole für Hintergrund-/Textkontrastindikatoren, für detaillierte Informationen fahre mit der Maus darüber. Bitte beachte, dass bei der Verwendung von Transparenz Kontrastindikatoren den schlechtest möglichen Fall darstellen.",
"tooltipRadius": "Tooltips/Warnungen",
@ -321,7 +321,7 @@
"always_drop_shadow": "Achtung, dieser Schatten verwendet immer {0}, wenn der Browser dies unterstützt.",
"drop_shadow_syntax": "{0} unterstützt Parameter {1} und Schlüsselwort {2} nicht.",
"avatar_inset": "Bitte beachte, dass die Kombination von eingesetzten und nicht eingesetzten Schatten auf Avataren zu unerwarteten Ergebnissen bei transparenten Avataren führen kann.",
"spread_zero": "Schatten mit einer Streuung > 0 erscheinen so, als ob sie auf Null gesetzt wären.",
"spread_zero": "Schatten mit einer Streuung > 0 erscheinen so, als ob sie auf Null gesetzt wären",
"inset_classic": "Eingesetzte Schatten werden mit {0} verwendet"
},
"components": {
@ -375,7 +375,7 @@
"warning_of_generate_new_codes": "Wenn du neue Wiederherstellungs-Codes generierst, werden die alten Codes nicht mehr funktionieren.",
"generate_new_recovery_codes": "Generiere neue Wiederherstellungs-Codes",
"title": "Zwei-Faktor Authentifizierung",
"waiting_a_recovery_codes": "Erhalte Wiederherstellungscodes...",
"waiting_a_recovery_codes": "Erhalte Wiederherstellungscodes",
"authentication_methods": "Authentifizierungsmethoden",
"scan": {
"title": "Scan",
@ -407,7 +407,7 @@
"conversation": "Unterhaltung",
"error_fetching": "Fehler beim Laden",
"load_older": "Lade ältere Beiträge",
"no_retweet_hint": "Der Beitrag ist als nur-für-Follower oder als Direktnachricht markiert und kann nicht wiederholt werden.",
"no_retweet_hint": "Der Beitrag ist als nur-für-Follower oder als Direktnachricht markiert und kann nicht wiederholt werden",
"repeated": "wiederholte",
"show_new": "Zeige Neuere",
"up_to_date": "Aktuell"
@ -481,7 +481,7 @@
"not_found": "Benutzername/E-Mail-Adresse nicht gefunden. Vertippt?",
"too_many_requests": "Kurze Pause. Zu viele Versuche. Bitte, später nochmal probieren.",
"password_reset_disabled": "Passwortzurücksetzen deaktiviert. Bitte Administrator kontaktieren.",
"password_reset_required": "Passwortzurücksetzen erforderlich",
"password_reset_required": "Passwortzurücksetzen erforderlich.",
"password_reset_required_but_mailer_is_disabled": "Passwortzurücksetzen wäre erforderlich, ist aber deaktiviert. Bitte Administrator kontaktieren."
},
"about": {
@ -516,9 +516,9 @@
},
"domain_mute_card": {
"mute": "Stummschalten",
"mute_progress": "Wird stummgeschaltet..",
"mute_progress": "Wird stummgeschaltet",
"unmute": "Stummschaltung aufheben",
"unmute_progress": "Stummschaltung wird aufgehoben.."
"unmute_progress": "Stummschaltung wird aufgehoben"
},
"exporter": {
"export": "Exportieren",

View File

@ -29,8 +29,8 @@
},
"staff": "Staff"
},
"chat": {
"title": "Chat"
"shoutbox": {
"title": "Shoutbox"
},
"domain_mute_card": {
"mute": "Mute",

View File

@ -7,12 +7,13 @@
"gopher": "Gopher",
"media_proxy": "Aŭdvidaĵa prokurilo",
"scope_options": "Agordoj de amplekso",
"text_limit": "Teksta limo",
"text_limit": "Limo de teksto",
"title": "Funkcioj",
"who_to_follow": "Kiun aboni"
"who_to_follow": "Kiun aboni",
"pleroma_chat_messages": "Babilejo de Pleroma"
},
"finder": {
"error_fetching_user": "Eraro alportante uzanton",
"error_fetching_user": "Eraris alporto de uzanto",
"find_user": "Trovi uzanton"
},
"general": {
@ -20,12 +21,25 @@
"submit": "Sendi",
"more": "Pli",
"generic_error": "Eraro okazis",
"optional": "Malnepra"
"optional": "malnepra",
"close": "Fermi",
"verify": "Kontroli",
"confirm": "Konfirmi",
"enable": "Ŝalti",
"disable": "Malŝalti",
"cancel": "Nuligi",
"dismiss": "Forlasi",
"show_less": "Montri malplion",
"show_more": "Montri plion",
"retry": "Reprovi",
"error_retry": "Bonvolu reprovi",
"loading": "Enlegante…"
},
"image_cropper": {
"crop_picture": "Tondi bildon",
"save": "Konservi",
"cancel": "Nuligi"
"cancel": "Nuligi",
"save_without_cropping": "Konservi sen tondado"
},
"login": {
"login": "Saluti",
@ -34,8 +48,16 @@
"password": "Pasvorto",
"placeholder": "ekz. lain",
"register": "Registriĝi",
"username": "Salutnomo",
"hint": "Salutu por partopreni la diskutadon"
"username": "Uzantonomo",
"hint": "Salutu por partopreni la diskutadon",
"heading": {
"recovery": "Rehavo de duobla aŭtentikigo",
"totp": "Duobla aŭtentikigo"
},
"recovery_code": "Rehava kodo",
"enter_two_factor_code": "Enigu kodon de duobla aŭtentikigo",
"enter_recovery_code": "Enigu rehavan kodon",
"authentication_code": "Aŭtentikiga kodo"
},
"media_modal": {
"previous": "Antaŭa",
@ -45,7 +67,7 @@
"about": "Pri",
"back": "Reen",
"chat": "Loka babilejo",
"friend_requests": "Abonaj petoj",
"friend_requests": "Petoj pri abono",
"mentions": "Mencioj",
"dms": "Rektaj mesaĝoj",
"public_tl": "Publika tempolinio",
@ -53,7 +75,11 @@
"twkn": "La tuta konata reto",
"user_search": "Serĉi uzantojn",
"who_to_follow": "Kiun aboni",
"preferences": "Agordoj"
"preferences": "Agordoj",
"chats": "Babiloj",
"search": "Serĉi",
"interactions": "Interagoj",
"administration": "Administrado"
},
"notifications": {
"broken_favorite": "Nekonata stato, serĉante ĝin…",
@ -63,15 +89,21 @@
"notifications": "Sciigoj",
"read": "Legite!",
"repeated_you": "ripetis vian staton",
"no_more_notifications": "Neniuj pliaj sciigoj"
"no_more_notifications": "Neniuj pliaj sciigoj",
"reacted_with": "reagis per {0}",
"migrated_to": "migris al",
"follow_request": "volas vin aboni"
},
"post_status": {
"new_status": "Afiŝi novan staton",
"account_not_locked_warning": "Via konto ne estas {0}. Iu ajn povas vin aboni por vidi viajn afiŝoj nur por abonantoj.",
"account_not_locked_warning": "Via konto ne estas {0}. Iu ajn povas vin aboni por vidi viajn afiŝoj nur por abonantoj.",
"account_not_locked_warning_link": "ŝlosita",
"attachments_sensitive": "Marki kunsendaĵojn kiel konsternajn",
"attachments_sensitive": "Marki kunsendaĵojn konsternaj",
"content_type": {
"text/plain": "Plata teksto"
"text/plain": "Plata teksto",
"text/bbcode": "BBCode",
"text/markdown": "Markdown",
"text/html": "HTML"
},
"content_warning": "Temo (malnepra)",
"default": "Ĵus alvenis al la Universala Kongreso!",
@ -82,7 +114,19 @@
"private": "Nur abonantoj Afiŝi nur al abonantoj",
"public": "Publika Afiŝi al publikaj tempolinioj",
"unlisted": "Nelistigita Ne afiŝi al publikaj tempolinioj"
}
},
"scope_notice": {
"unlisted": "Ĉi tiu afiŝo ne estos videbla en la Publika historio kaj La tuta konata reto",
"private": "Ĉi tiu afiŝo estos videbla nur al viaj abonantoj",
"public": "Ĉi tiu afiŝo estos videbla al ĉiuj"
},
"media_description_error": "Malsukcesis afiŝo de vidaŭdaĵoj; reprovu",
"empty_status_error": "Ne povas afiŝi malplenan staton sen dosieroj",
"preview_empty": "Malplena",
"preview": "Antaŭrigardo",
"direct_warning_to_first_only": "Ĉi tiu afiŝo estas nur videbla al uzantoj menciitaj je la komenco de la mesaĝo.",
"direct_warning_to_all": "Ĉi tiu afiŝo estos videbla al ĉiuj menciitaj uzantoj.",
"media_description": "Priskribo de vidaŭdaĵo"
},
"registration": {
"bio": "Priskribo",
@ -92,10 +136,10 @@
"registration": "Registriĝo",
"token": "Invita ĵetono",
"captcha": "TESTO DE HOMECO",
"new_captcha": "Alklaku la bildon por akiri novan teston",
"new_captcha": "Klaku la bildon por akiri novan teston",
"username_placeholder": "ekz. lain",
"fullname_placeholder": "ekz. Lain Iwakura",
"bio_placeholder": "ekz.\nSaluton, mi estas Lain\nMi estas animea knabino vivante en Japanujo. Eble vi konas min de la retejo «Wired».",
"bio_placeholder": "ekz.\nSaluton, mi estas Lain.\nMi estas animea knabino vivanta en Japanujo. Eble vi konas min pro la retejo «Wired».",
"validations": {
"username_required": "ne povas resti malplena",
"fullname_required": "ne povas resti malplena",
@ -114,47 +158,47 @@
"avatarRadius": "Profilbildoj",
"background": "Fono",
"bio": "Priskribo",
"blocks_tab": "Baroj",
"blocks_tab": "Blokitoj",
"btnRadius": "Butonoj",
"cBlue": "Blua (Respondo, abono)",
"cGreen": "Verda (Kunhavigo)",
"cOrange": "Oranĝa (Ŝato)",
"cRed": "Ruĝa (Nuligo)",
"cBlue": "Blua (respondi, aboni)",
"cGreen": "Verda (kunhavigi)",
"cOrange": "Oranĝa (ŝati)",
"cRed": "Ruĝa (nuligi)",
"change_password": "Ŝanĝi pasvorton",
"change_password_error": "Okazis eraro dum ŝanĝo de via pasvorto.",
"change_password_error": "Eraris ŝanĝo de via pasvorto.",
"changed_password": "Pasvorto sukcese ŝanĝiĝis!",
"collapse_subject": "Maletendi afiŝojn kun temoj",
"composing": "Verkante",
"composing": "Verkado",
"confirm_new_password": "Konfirmu novan pasvorton",
"current_avatar": "Via nuna profilbildo",
"current_password": "Nuna pasvorto",
"current_profile_banner": "Via nuna profila rubando",
"data_import_export_tab": "Enporto / Elporto de datenoj",
"default_vis": "Implicita videbleca amplekso",
"data_import_export_tab": "Enporto / Elporto de datumoj",
"default_vis": "Implicita amplekso de vidibleco",
"delete_account": "Forigi konton",
"delete_account_description": "Por ĉiam forigi vian konton kaj ĉiujn viajn mesaĝojn",
"delete_account_error": "Okazis eraro dum forigo de via kanto. Se tio daŭre okazados, bonvolu kontakti la administranton de via nodo.",
"delete_account_description": "Por ĉiam forigi viajn datumojn kaj malaktivigi vian konton.",
"delete_account_error": "Eraris forigo de via kanto. Se tio daŭre ripetiĝos, bonvolu kontakti la administranton de via nodo.",
"delete_account_instructions": "Entajpu sube vian pasvorton por konfirmi forigon de konto.",
"avatar_size_instruction": "La rekomendata malpleja grando de profilbildoj estas 150×150 bilderoj.",
"avatar_size_instruction": "La rekomendata minimuma grando de profilbildoj estas 150×150 bilderoj.",
"export_theme": "Konservi antaŭagordon",
"filtering": "Filtrado",
"filtering_explanation": "Ĉiuj statoj kun tiuj ĉi vortoj silentiĝos, po unu linio",
"follow_export": "Abona elporto",
"filtering_explanation": "Ĉiuj statoj kun tiuj ĉi vortoj silentiĝos; skribu po unu linie",
"follow_export": "Elporto de abonoj",
"follow_export_button": "Elporti viajn abonojn al CSV-dosiero",
"follow_export_processing": "Traktante; baldaŭ vi ricevos peton elŝuti la dosieron",
"follow_import": "Abona enporto",
"follow_import": "Enporto de abonoj",
"follow_import_error": "Eraro enportante abonojn",
"follows_imported": "Abonoj enportiĝis! Traktado daŭros iom.",
"follows_imported": "Abonoj enportiĝis! Traktado daŭros iom da tempo.",
"foreground": "Malfono",
"general": "Ĝenerala",
"hide_attachments_in_convo": "Kaŝi kunsendaĵojn en interparoloj",
"hide_attachments_in_tl": "Kaŝi kunsendaĵojn en tempolinio",
"max_thumbnails": "Plej multa nombro da bildetoj po afiŝo",
"hide_isp": "Kaŝi nodo-propran breton",
"max_thumbnails": "Maksimuma nombro da bildetoj en afiŝo",
"hide_isp": "Kaŝi breton propran al nodo",
"preload_images": "Antaŭ-enlegi bildojn",
"use_one_click_nsfw": "Malfermi konsternajn kunsendaĵojn per nur unu klako",
"hide_post_stats": "Kaŝi statistikon de afiŝoj (ekz. nombron da ŝatoj)",
"hide_user_stats": "Kaŝi statistikon de uzantoj (ekz. nombron da abonantoj)",
"hide_post_stats": "Kaŝi statistikon de afiŝoj (ekz. nombron de ŝatoj)",
"hide_user_stats": "Kaŝi statistikon de uzantoj (ekz. nombron de abonantoj)",
"hide_filtered_statuses": "Kaŝi filtritajn statojn",
"import_followers_from_a_csv_file": "Enporti abonojn el CSV-dosiero",
"import_theme": "Enlegi antaŭagordojn",
@ -169,9 +213,9 @@
"links": "Ligiloj",
"lock_account_description": "Limigi vian konton al nur abonantoj aprobitaj",
"loop_video": "Ripetadi filmojn",
"loop_video_silent_only": "Ripetadi nur filmojn sen sono (ekz. la \"GIF-ojn\" de Mastodon)",
"loop_video_silent_only": "Ripetadi nur filmojn sen sono (ekz. la «GIF-ojn» de Mastodon)",
"mutes_tab": "Silentigoj",
"play_videos_in_modal": "Ludi filmojn rekte en la aŭdvidaĵa spektilo",
"play_videos_in_modal": "Ludi filmojn en ŝpruca kadro",
"use_contain_fit": "Ne tondi la kunsendaĵon en bildetoj",
"name": "Nomo",
"name_bio": "Nomo kaj priskribo",
@ -182,50 +226,50 @@
"notification_visibility_mentions": "Mencioj",
"notification_visibility_repeats": "Ripetoj",
"no_rich_text_description": "Forigi riĉtekstajn formojn de ĉiuj afiŝoj",
"no_blocks": "Neniuj baroj",
"no_blocks": "Neniuj blokitoj",
"no_mutes": "Neniuj silentigoj",
"hide_follows_description": "Ne montri kiun mi sekvas",
"hide_followers_description": "Ne montri kiu min sekvas",
"show_admin_badge": "Montri la insignon de administranto en mia profilo",
"show_moderator_badge": "Montri la insignon de kontrolanto en mia profilo",
"nsfw_clickthrough": "Ŝalti traklakan kaŝon de konsternaj kunsendaĵoj",
"show_moderator_badge": "Montri la insignon de reguligisto en mia profilo",
"nsfw_clickthrough": "Ŝalti traklakan kaŝadon de konsternaj kunsendaĵoj",
"oauth_tokens": "Ĵetonoj de OAuth",
"token": "Ĵetono",
"refresh_token": "Ĵetono de novigo",
"refresh_token": "Ĵetono de aktualigo",
"valid_until": "Valida ĝis",
"revoke_token": "Senvalidigi",
"panelRadius": "Bretoj",
"pause_on_unfocused": "Paŭzigi elsendfluon kiam langeto ne estas fokusata",
"presets": "Antaŭagordoj",
"profile_background": "Profila fono",
"profile_banner": "Profila rubando",
"profile_background": "Fono de profilo",
"profile_banner": "Rubando de profilo",
"profile_tab": "Profilo",
"radii_help": "Agordi fasadan rondigon de randoj (bildere)",
"replies_in_timeline": "Respondoj en tempolinio",
"reply_visibility_all": "Montri ĉiujn respondojn",
"reply_visibility_following": "Montri nur respondojn por mi aŭ miaj abonatoj",
"reply_visibility_self": "Montri nur respondojn por mi",
"saving_err": "Eraro dum konservo de agordoj",
"saving_err": "Eraris konservado de agordoj",
"saving_ok": "Agordoj konserviĝis",
"security_tab": "Sekureco",
"scope_copy": "Kopii amplekson por respondo (rektaj mesaĝoj ĉiam kopiiĝas)",
"set_new_avatar": "Agordi novan profilbildon",
"set_new_profile_background": "Agordi novan profilan fonon",
"set_new_profile_banner": "Agordi novan profilan rubandon",
"set_new_profile_background": "Agordi novan fonon de profilo",
"set_new_profile_banner": "Agordi novan rubandon de profilo",
"settings": "Agordoj",
"subject_input_always_show": "Ĉiam montri teman kampon",
"subject_line_behavior": "Kopii temon por respondo",
"subject_line_email": "Kiel retpoŝto: \"re: temo\"",
"subject_input_always_show": "Ĉiam montri kampon de temo",
"subject_line_behavior": "Kopii temon dum respondado",
"subject_line_email": "Kiel retpoŝto: «re: temo»",
"subject_line_mastodon": "Kiel Mastodon: kopii senŝanĝe",
"subject_line_noop": "Ne kopii",
"post_status_content_type": "Afiŝi specon de la enhavo de la stato",
"stop_gifs": "Movi GIF-bildojn dum musa ŝvebo",
"streaming": "Ŝalti memfaran fluigon de novaj afiŝoj ĉe la supro de la paĝo",
"stop_gifs": "Movi GIF-bildojn dum ŝvebo de muso",
"streaming": "Ŝalti memagan fluigon de novaj afiŝoj kiam vi vidas la supron de la paĝo",
"text": "Teksto",
"theme": "Haŭto",
"theme_help": "Uzu deksesumajn kolorkodojn (#rrvvbb) por adapti vian koloran haŭton.",
"theme_help_v2_1": "Vi ankaŭ povas superagordi la kolorojn kaj travideblecon de kelkaj eroj per marko de la markbutono; uzu la butonon \"Vakigi ĉion\" por forigi ĉîujn superagordojn.",
"theme_help_v2_2": "Bildsimboloj sub kelkaj eroj estas indikiloj de kontrasto inter fono kaj teksto; muse ŝvebu por detalaj informoj. Bonvolu memori, ke la indikilo montras la plej malbonan okazeblon dum sia uzo.",
"theme_help_v2_1": "Vi ankaŭ povas superagordi la kolorojn kaj travideblecon de kelkaj eroj per marko de la markbutono; uzu la butonon «Vakigi ĉion» por forigi ĉîujn superagordojn.",
"theme_help_v2_2": "Bildsimboloj sub kelkaj eroj estas indikiloj de kontrasto inter fono kaj teksto; ŝvebigu muson por detalaj informoj. Bonvolu memori, ke la indikilo montras la plej malbonan okazeblon dum sia uzo.",
"tooltipRadius": "Ŝpruchelpiloj/avertoj",
"upload_a_photo": "Alŝuti foton",
"user_settings": "Agordoj de uzanto",
@ -317,7 +361,7 @@
},
"fonts": {
"_tab_label": "Tiparoj",
"help": "Elektu tiparon uzotan por eroj de la fasado. Por \"propra\" vi devas enigi la precizan nomon de tiparo tiel, kiel ĝi aperas en la sistemo",
"help": "Elektu tiparon uzotan por eroj de la fasado. Por \"propra\" vi devas enigi la precizan nomon de tiparo tiel, kiel ĝi aperas en la sistemo.",
"components": {
"interface": "Fasado",
"input": "Enigaj kampoj",
@ -343,7 +387,70 @@
"checkbox": "Mi legetis la kondiĉojn de uzado",
"link": "bela eta ligil"
}
}
},
"discoverable": "Permesi trovon de ĉi tiu konto en serĉrezultoj kaj aliaj servoj",
"mutes_and_blocks": "Silentigitoj kaj blokitoj",
"chatMessageRadius": "Babileja mesaĝo",
"changed_email": "Retpoŝtadreso sukcese ŝanĝiĝis!",
"change_email_error": "Eraris ŝanĝo de via retpoŝtadreso.",
"change_email": "Ŝanĝi retpoŝtadreson",
"bot": "Ĉi tio estas robota konto",
"blocks_imported": "Blokitoj enportiĝis! Traktado daŭros iom da tempo.",
"block_import_error": "Eraris enporto de blokitoj",
"block_export": "Elporto de blokitoj",
"block_import": "Enporto de blokitoj",
"block_export_button": "Elporti viajn blokitojn al CSV-dosiero",
"allow_following_move": "Permesi memagan abonadon kiam abonata konto migras",
"mfa": {
"verify": {
"desc": "Por ŝalti duoblan aŭtentikigon, enigu la kodon el via aplikaĵo por duobla aŭtentikigo:"
},
"scan": {
"secret_code": "Ŝlosilo",
"desc": "Uzante vian aplikaĵon por duobla aŭtentikigo, skanu ĉi tiun rapidrespondan kodon aŭ enigu tekstan ŝlosilon:",
"title": "Skani"
},
"authentication_methods": "Metodoj de aŭtentikigo",
"recovery_codes_warning": "Notu la kodojn aŭ konservu ilin en sekura loko alie vi ne revidos ilin. Se vi perdos aliron al via aplikaĵo por duobla aŭtentikigo kaj al la rehavaj kodoj, vi ne povos aliri vian konton.",
"waiting_a_recovery_codes": "Ricevante savkopiajn kodojn…",
"recovery_codes": "Rehavaj kodoj.",
"warning_of_generate_new_codes": "Kiam vi estigos novajn rehavajn kodojn, viaj malnovaj ne plu funkcios.",
"generate_new_recovery_codes": "Estigi novajn rehavajn kodojn",
"title": "Duobla aŭtentikigo"
},
"enter_current_password_to_confirm": "Enigu vian pasvorton por konfirmi vian identecon",
"security": "Sekureco",
"fun": "Amuzo",
"type_domains_to_mute": "Serĉu silentigotajn retnomojn",
"useStreamingApiWarning": "(Nerekomendate, eksperimente, povas preterpasi afiŝojn)",
"useStreamingApi": "Ricevi afiŝojn kaj sciigojn realtempe",
"user_mutes": "Uzantoj",
"reset_background_confirm": "Ĉu vi certe volas restarigi la fonon?",
"reset_banner_confirm": "Ĉu vi certe volas restarigi la rubandon?",
"reset_avatar_confirm": "Ĉu vi certe volas restarigi la profilbildon?",
"reset_profile_banner": "Restarigi rubandon de profilo",
"reset_profile_background": "Restarigi fonon de profilo",
"reset_avatar": "Restarigi profilbildon",
"minimal_scopes_mode": "Minimumigi elekteblojn pri amplekso de afiŝoj",
"search_user_to_block": "Serĉu, kiun vi volas bloki",
"search_user_to_mute": "Serĉu, kiun vi volas silentigi",
"autohide_floating_post_button": "Memage kaŝi la butonon por Nova afiŝo (poŝtelefone)",
"hide_followers_count_description": "Ne montri nombron de abonantoj",
"hide_follows_count_description": "Ne montri nombron de abonoj",
"notification_visibility_emoji_reactions": "Reagoj",
"notification_visibility_moves": "Migroj",
"new_email": "Nova retpoŝtadreso",
"profile_fields": {
"value": "Enhavo",
"name": "Etikedo",
"add_field": "Aldoni kampon",
"label": "Pridatumoj de profilo"
},
"import_blocks_from_a_csv_file": "Enporti blokitojn el CSV-dosiero",
"hide_muted_posts": "Kaŝi afiŝojn de silentigitaj uzantoj",
"emoji_reactions_on_timeline": "Montri bildosignajn reagojn en la tempolinio",
"pad_emoji": "Meti spacetojn ĉirkaŭ bildosigno post ties elekto",
"domain_mutes": "Retnomoj"
},
"timeline": {
"collapse": "Maletendi",
@ -402,7 +509,7 @@
"favorite": "Ŝati",
"user_settings": "Agordoj de uzanto"
},
"upload":{
"upload": {
"error": {
"base": "Alŝuto malsukcesis.",
"file_too_big": "Dosiero estas tro granda [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
@ -415,5 +522,90 @@
"GiB": "GiB",
"TiB": "TiB"
}
},
"emoji": {
"search_emoji": "Serĉi bildosignon",
"keep_open": "Teni elektilon malfermita",
"emoji": "Bildsignoj",
"stickers": "Glumarkoj",
"add_emoji": "Enigi bildosignon",
"load_all": "Enlegante ĉiujn {emojiAmount} bildosignojn",
"load_all_hint": "Enlegis la {saneAmount} unuajn bildosignojn; enlego de ĉiuj povus kaŭzi problemojn pri efikeco.",
"unicode": "Unikoda bildosigno",
"custom": "Propra bildosigno"
},
"polls": {
"not_enough_options": "Tro malmultaj unikaj elektebloj en la enketo",
"expired": "Enketo finiĝis antaŭ {0}",
"expires_in": "Enketo finiĝas je {0}",
"expiry": "Aĝo de enketo",
"multiple_choices": "Pluraj elektoj",
"single_choice": "Unu elekto",
"type": "Speco de enketo",
"vote": "Voĉi",
"votes": "voĉoj",
"option": "Elekteblo",
"add_option": "Aldoni elekteblon",
"add_poll": "Aldoni enketon"
},
"importer": {
"error": "Eraris enporto de ĉi tiu dosiero.",
"success": "Enportita sukcese.",
"submit": "Sendi"
},
"exporter": {
"processing": "Traktante; baldaŭ vi ricevos peton elŝuti vian dosieron",
"export": "Elporti"
},
"domain_mute_card": {
"unmute_progress": "Malsilentigante…",
"unmute": "Malsilentigi",
"mute_progress": "Silentigante…",
"mute": "Silentigi"
},
"about": {
"staff": "Skipo",
"mrf": {
"simple": {
"media_nsfw_desc": "Ĉi tiu nodo devigas vidaŭdaĵojn esti markitaj kiel konsternaj en afiŝoj el la jenaj nodoj:",
"media_nsfw": "Devige marki vidaŭdaĵojn konsternaj",
"media_removal_desc": "Ĉi tiu nodo forigas vidaŭdaĵojn de afiŝoj el la jenaj nodoj:",
"media_removal": "Forigo de vidaŭdaĵoj",
"ftl_removal": "Forigo de la historio de «La tuta konata reto»",
"quarantine_desc": "Ĉi tiu nodo sendos nur publikajn afiŝojn al la jenaj nodoj:",
"quarantine": "Kvaranteno",
"reject_desc": "Ĉi tiu nodo ne akceptos mesaĝojn de la jenaj nodoj:",
"reject": "Rifuzi",
"accept_desc": "Ĉi tiu nodo nur akceptas mesaĝojn de la jenaj nodoj:",
"accept": "Akcepti",
"simple_policies": "Specialaj politikoj de la nodo"
},
"mrf_policies": "Ŝaltis politikon de MRF",
"keyword": {
"is_replaced_by": "→",
"replace": "Anstataŭigi",
"reject": "Rifuzi",
"ftl_removal": "Forigo de la historio de «La tuta konata reto»",
"keyword_policies": "Politiko pri ŝlosilvortoj"
},
"federation": "Federado"
}
},
"selectable_list": {
"select_all": "Elekti ĉion"
},
"remote_user_resolver": {
"error": "Netrovinte.",
"searching_for": "Serĉante",
"remote_user_resolver": "Trovilo de foraj uzantoj"
},
"interactions": {
"load_older": "Enlegi pli malnovajn interagojn",
"moves": "Migrado de uzantoj",
"follows": "Novaj abonoj",
"favs_repeats": "Ripetoj kaj ŝatoj"
},
"errors": {
"storage_unavailable": "Pleroma ne povis aliri deponejon de la foliumilo. Via saluto kaj viaj lokaj agordoj ne estos konservitaj, kaj vi eble renkontos neatenditajn problemojn. Provu permesi kuketojn."
}
}

View File

@ -84,7 +84,7 @@
"preferences": "Hobespenak"
},
"notifications": {
"broken_favorite": "Egoera ezezaguna, bilatzen...",
"broken_favorite": "Egoera ezezaguna, bilatzen",
"favorited_you": "zure mezua gogoko du",
"followed_you": "Zu jarraitzen zaitu",
"load_older": "Kargatu jakinarazpen zaharragoak",
@ -128,7 +128,7 @@
"new_status": "Mezu berri bat idatzi",
"account_not_locked_warning": "Zure kontua ez dago {0}. Edozeinek jarraitzen hastearekin, zure mezuak irakur ditzake.",
"account_not_locked_warning_link": "Blokeatuta",
"attachments_sensitive": "Nabarmendu eranskinak hunkigarri gisa ",
"attachments_sensitive": "Nabarmendu eranskinak hunkigarri gisa",
"content_type": {
"text/plain": "Testu arrunta",
"text/html": "HTML",
@ -187,9 +187,9 @@
"confirm_and_enable": "Baieztatu eta gaitu OTP",
"title": "Bi-faktore autentifikazioa",
"generate_new_recovery_codes": "Sortu berreskuratze kode berriak",
"warning_of_generate_new_codes": "Berreskuratze kode berriak sortzean, zure berreskuratze kode zaharrak ez dute balioko",
"warning_of_generate_new_codes": "Berreskuratze kode berriak sortzean, zure berreskuratze kode zaharrak ez dute balioko.",
"recovery_codes": "Berreskuratze kodea",
"waiting_a_recovery_codes": "Babes-kopia kodeak jasotzen...",
"waiting_a_recovery_codes": "Babes-kopia kodeak jasotzen",
"recovery_codes_warning": "Idatzi edo gorde kodeak leku seguruan - bestela ez dituzu berriro ikusiko. Zure 2FA aplikaziorako sarbidea eta berreskuratze kodeak galduz gero, zure kontutik blokeatuta egongo zara.",
"authentication_methods": "Autentifikazio metodoa",
"scan": {
@ -198,7 +198,7 @@
"secret_code": "Giltza"
},
"verify": {
"desc": "Bi-faktore autentifikazioa gaitzeko, sar ezazu bi-faktore kodea zure app-tik"
"desc": "Bi-faktore autentifikazioa gaitzeko, sar ezazu bi-faktore kodea zure app-tik:"
}
},
"attachmentRadius": "Eranskinak",
@ -220,7 +220,7 @@
"cOrange": "Laranja (Gogokoa)",
"cRed": "Gorria (ezeztatu)",
"change_password": "Pasahitza aldatu",
"change_password_error": "Arazao bat egon da zure pasahitza aldatzean",
"change_password_error": "Arazao bat egon da zure pasahitza aldatzean.",
"changed_password": "Pasahitza ondo aldatu da!",
"collapse_subject": "Bildu gaia daukaten mezuak",
"composing": "Idazten",
@ -247,7 +247,7 @@
"follows_imported": "Jarraitzaileak inportatuta! Prozesatzeak denbora pixka bat iraungo du.",
"foreground": "Aurreko planoa",
"general": "Orokorra",
"hide_attachments_in_convo": "Ezkutatu eranskinak elkarrizketatan ",
"hide_attachments_in_convo": "Ezkutatu eranskinak elkarrizketatan",
"hide_attachments_in_tl": "Ezkutatu eranskinak donbora-lerroan",
"hide_muted_posts": "Ezkutatu mutututako erabiltzaileen mezuak",
"max_thumbnails": "Mezu bakoitzeko argazki-miniatura kopuru maximoa",
@ -427,7 +427,7 @@
},
"fonts": {
"_tab_label": "Letra-tipoak",
"help": "Aukeratu letra-tipoak erabiltzailearen interfazean erabiltzeko. \"Pertsonalizatua\" letra-tipoan, sisteman agertzen den izen berdinarekin idatzi behar duzu.",
"help": "Aukeratu letra-tipoak erabiltzailearen interfazean erabiltzeko. \"Pertsonalizatua\" letra-tipoan, sisteman agertzen den izen berdinarekin idatzi behar duzu.",
"components": {
"interface": "Interfazea",
"input": "Sarrera eremuak",
@ -527,7 +527,7 @@
"favorites": "Gogokoak",
"follow": "Jarraitu",
"follow_sent": "Eskaera bidalita!",
"follow_progress": "Eskatzen...",
"follow_progress": "Eskatzen",
"follow_again": "Eskaera berriro bidali?",
"follow_unfollow": "Jarraitzeari utzi",
"followees": "Jarraitzen",
@ -546,11 +546,11 @@
"subscribe": "Harpidetu",
"unsubscribe": "Harpidetza ezeztatu",
"unblock": "Blokeoa kendu",
"unblock_progress": "Blokeoa ezeztatzen...",
"block_progress": "Blokeatzen...",
"unblock_progress": "Blokeoa ezeztatzen",
"block_progress": "Blokeatzen",
"unmute": "Isiltasuna kendu",
"unmute_progress": "Isiltasuna kentzen...",
"mute_progress": "Isiltzen...",
"unmute_progress": "Isiltasuna kentzen",
"mute_progress": "Isiltzen",
"hide_repeats": "Ezkutatu errepikapenak",
"show_repeats": "Erakutsi errpekiapenak",
"admin_menu": {

View File

@ -343,7 +343,8 @@
"migration_snapshot_ok": "Pour être sûr un instantanée du thème à été chargé. Vos pouvez essayer de charger ses données.",
"fe_downgraded": "Retour en arrière de la version de PleromaFE.",
"fe_upgraded": "Le moteur de thème PleromaFE à été mis à jour après un changement de version.",
"snapshot_missing": "Aucun instantané du thème à été trouvé dans le fichier, il peut y avoir un rendu différent à la vision originelle."
"snapshot_missing": "Aucun instantané du thème à été trouvé dans le fichier, il peut y avoir un rendu différent à la vision originelle.",
"snapshot_present": "Un instantané du thème à été chargé, toutes les valeurs sont dont écrasées. Vous pouvez autrement charger le thème complètement."
},
"keep_as_is": "Garder tel-quel",
"use_source": "Nouvelle version"
@ -619,7 +620,7 @@
"reject": "Rejeté",
"replace": "Remplacer",
"keyword_policies": "Politiques par mot-clés",
"ftl_removal": "Suppression du flux \"Ensemble du réseau connu\"",
"ftl_removal": "Suppression du flux fédéré",
"is_replaced_by": "→"
},
"simple": {

View File

@ -36,7 +36,7 @@
"twkn": "An Líonra Iomlán"
},
"notifications": {
"broken_favorite": "Post anaithnid. Cuardach dó...",
"broken_favorite": "Post anaithnid. Cuardach dó",
"favorited_you": "toghadh le do phost",
"followed_you": "lean tú",
"load_older": "Luchtaigh fógraí aosta",
@ -84,7 +84,7 @@
"cOrange": "Oráiste (Cosúil)",
"cRed": "Dearg (Cealaigh)",
"change_password": "Athraigh do pasfhocal",
"change_password_error": "Bhí fadhb ann ag athrú do pasfhocail",
"change_password_error": "Bhí fadhb ann ag athrú do pasfhocail.",
"changed_password": "Athraigh an pasfhocal go rathúil!",
"collapse_subject": "Poist a chosc le teidil",
"confirm_new_password": "Deimhnigh do pasfhocal nua",
@ -160,7 +160,7 @@
"streaming": "Cumasaigh post nua a shruthú uathoibríoch nuair a scrollaítear go barr an leathanaigh",
"text": "Téacs",
"theme": "Téama",
"theme_help": "Úsáid cód daith hex (#rrggbb) chun do schéim a saincheapadh",
"theme_help": "Úsáid cód daith hex (#rrggbb) chun do schéim a saincheapadh.",
"tooltipRadius": "Bileoga eolais",
"user_settings": "Socruithe úsáideora",
"values": {

View File

@ -70,9 +70,9 @@
"preferences": "העדפות"
},
"notifications": {
"broken_favorite": "סטאטוס לא ידוע, מחפש...",
"broken_favorite": "סטאטוס לא ידוע, מחפש",
"favorited_you": "אהב את הסטטוס שלך",
"followed_you": "עקב אחריך!",
"followed_you": "עקב אחריך",
"load_older": "טען התראות ישנות",
"notifications": "התראות",
"read": "קרא!",
@ -178,7 +178,7 @@
"follow_export": "יצוא עקיבות",
"follow_export_button": "ייצא את הנעקבים שלך לקובץ csv",
"follow_import": "יבוא עקיבות",
"follow_import_error": "שגיאה בייבוא נעקבים.",
"follow_import_error": "שגיאה בייבוא נעקבים",
"follows_imported": "נעקבים יובאו! ייקח זמן מה לעבד אותם.",
"foreground": "חזית",
"general": "כללי",
@ -311,7 +311,7 @@
"favorites": "מועדפים",
"follow": "עקוב",
"follow_sent": "בקשה נשלחה!",
"follow_progress": "מבקש...",
"follow_progress": "מבקש",
"follow_again": "שלח בקשה שוב?",
"follow_unfollow": "בטל עקיבה",
"followees": "נעקבים",
@ -327,11 +327,11 @@
"report": "דווח",
"statuses": "סטטוסים",
"unblock": "הסר חסימה",
"unblock_progress": "מסיר חסימה...",
"block_progress": "חוסם...",
"unblock_progress": "מסיר חסימה",
"block_progress": "חוסם",
"unmute": "הסר השתקה",
"unmute_progress": "מסיר השתקה...",
"mute_progress": "משתיק...",
"unmute_progress": "מסיר השתקה",
"mute_progress": "משתיק",
"admin_menu": {
"moderation": "ניהול (צוות)",
"grant_admin": "הפוך למנהל",
@ -377,7 +377,7 @@
"favorite": "מועדף",
"user_settings": "הגדרות משתמש"
},
"upload":{
"upload": {
"error": {
"base": "העלאה נכשלה.",
"file_too_big": "קובץ גדול מדי [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",

View File

@ -404,7 +404,9 @@
"reset_avatar_confirm": "Vuoi veramente azzerare l'icona?",
"reset_banner_confirm": "Vuoi veramente azzerare lo stendardo?",
"reset_background_confirm": "Vuoi veramente azzerare lo sfondo?",
"chatMessageRadius": "Messaggi istantanei"
"chatMessageRadius": "Messaggi istantanei",
"notification_setting_hide_notification_contents": "Nascondi mittente e contenuti delle notifiche push",
"notification_setting_block_from_strangers": "Blocca notifiche da utenti che non segui"
},
"timeline": {
"error_fetching": "Errore nell'aggiornamento",
@ -801,6 +803,7 @@
"new": "Nuova conversazione",
"chats": "Conversazioni",
"delete": "Elimina",
"message_user": "Contatta {nickname}"
"message_user": "Contatta {nickname}",
"you": "Tu:"
}
}

View File

@ -27,7 +27,7 @@
},
"exporter": {
"export": "エクスポート",
"processing": "おまちください。しばらくすると、あなたのファイルをダウンロードするように、メッセージがでます"
"processing": "おまちください。しばらくすると、あなたのファイルをダウンロードするように、メッセージがでます"
},
"features_panel": {
"chat": "チャット",
@ -39,7 +39,7 @@
"who_to_follow": "おすすめユーザー"
},
"finder": {
"error_fetching_user": "ユーザーけんさくがエラーになりました",
"error_fetching_user": "ユーザーけんさくがエラーになりました",
"find_user": "ユーザーをさがす"
},
"general": {
@ -80,9 +80,9 @@
"enter_recovery_code": "リカバリーコードをいれてください",
"enter_two_factor_code": "2-ファクターコードをいれてください",
"recovery_code": "リカバリーコード",
"heading" : {
"totp" : "2-ファクターにんしょう",
"recovery" : "2-ファクターリカバリー"
"heading": {
"totp": "2-ファクターにんしょう",
"recovery": "2-ファクターリカバリー"
}
},
"media_modal": {
@ -107,7 +107,7 @@
"preferences": "せってい"
},
"notifications": {
"broken_favorite": "ステータスがみつかりません。さがしています...",
"broken_favorite": "ステータスがみつかりません。さがしています",
"favorited_you": "あなたのステータスがおきにいりされました",
"followed_you": "フォローされました",
"load_older": "ふるいつうちをみる",
@ -172,10 +172,10 @@
"unlisted": "このとうこうは、パブリックタイムラインと、つながっているすべてのネットワークでは、みることができません"
},
"scope": {
"direct": "ダイレクト: メンションされたユーザーのみにとどきます",
"private": "フォロワーげんてい: フォロワーのみにとどきます",
"public": "パブリック: パブリックタイムラインにとどきます",
"unlisted": "アンリステッド: パブリックタイムラインにとどきません"
"direct": "ダイレクト: メンションされたユーザーのみにとどきます",
"private": "フォロワーげんてい: フォロワーのみにとどきます",
"public": "パブリック: パブリックタイムラインにとどきます",
"unlisted": "アンリステッド: パブリックタイムラインにとどきません"
}
},
"registration": {
@ -212,17 +212,17 @@
"security": "セキュリティ",
"enter_current_password_to_confirm": "あなたのアイデンティティをたしかめるため、あなたのいまのパスワードをかいてください",
"mfa": {
"otp" : "OTP",
"setup_otp" : "OTPをつくる",
"wait_pre_setup_otp" : "OTPをよういしています",
"confirm_and_enable" : "OTPをたしかめて、ゆうこうにする",
"otp": "OTP",
"setup_otp": "OTPをつくる",
"wait_pre_setup_otp": "OTPをよういしています",
"confirm_and_enable": "OTPをたしかめて、ゆうこうにする",
"title": "2-ファクターにんしょう",
"generate_new_recovery_codes" : "あたらしいリカバリーコードをつくる",
"warning_of_generate_new_codes" : "あたらしいリカバリーコードをつくったら、ふるいコードはつかえなくなります。",
"recovery_codes" : "リカバリーコード。",
"waiting_a_recovery_codes": "バックアップコードをうけとっています...",
"recovery_codes_warning" : "コードをかきうつすか、ひとにみられないところにセーブしてください。そうでなければ、あなたはこのコードをふたたびみることはできません。もしあなたが、2FAアプリのアクセスをうしなって、なおかつ、リカバリーコードもおもいだせないならば、あなたはあなたのアカウントから、しめだされます。",
"authentication_methods" : "にんしょうメソッド",
"generate_new_recovery_codes": "あたらしいリカバリーコードをつくる",
"warning_of_generate_new_codes": "あたらしいリカバリーコードをつくったら、ふるいコードはつかえなくなります。",
"recovery_codes": "リカバリーコード。",
"waiting_a_recovery_codes": "バックアップコードをうけとっています",
"recovery_codes_warning": "コードをかきうつすか、ひとにみられないところにセーブしてください。そうでなければ、あなたはこのコードをふたたびみることはできません。もしあなたが、2FAアプリのアクセスをうしなって、なおかつ、リカバリーコードもおもいだせないならば、あなたはあなたのアカウントから、しめだされます。",
"authentication_methods": "にんしょうメソッド",
"scan": {
"title": "スキャン",
"desc": "あなたの2-ファクターアプリをつかって、このQRコードをスキャンするか、テキストキーをうちこんでください:",
@ -273,12 +273,12 @@
"pad_emoji": "えもじをピッカーでえらんだとき、えもじのまわりにスペースをいれる",
"export_theme": "セーブ",
"filtering": "フィルタリング",
"filtering_explanation": "これらのことばをふくむすべてのものがミュートされます。1ぎょうに1つのことばをかいてください",
"filtering_explanation": "これらのことばをふくむすべてのものがミュートされます。1ぎょうに1つのことばをかいてください",
"follow_export": "フォローのエクスポート",
"follow_export_button": "エクスポート",
"follow_export_processing": "おまちください。まもなくファイルをダウンロードできます。",
"follow_import": "フォローインポート",
"follow_import_error": "フォローのインポートがエラーになりました",
"follow_import_error": "フォローのインポートがエラーになりました",
"follows_imported": "フォローがインポートされました! すこしじかんがかかるかもしれません。",
"foreground": "フォアグラウンド",
"general": "ぜんぱん",
@ -340,7 +340,7 @@
"profile_background": "プロフィールのバックグラウンド",
"profile_banner": "プロフィールバナー",
"profile_tab": "プロフィール",
"radii_help": "インターフェースのまるさをせっていする",
"radii_help": "インターフェースのまるさをせっていする",
"replies_in_timeline": "タイムラインのリプライ",
"reply_visibility_all": "すべてのリプライをみる",
"reply_visibility_following": "わたしにあてられたリプライと、フォローしているひとからのリプライをみる",
@ -367,7 +367,7 @@
"streaming": "うえまでスクロールしたとき、じどうてきにストリーミングする",
"text": "もじ",
"theme": "テーマ",
"theme_help": "カラーテーマをカスタマイズできます",
"theme_help": "カラーテーマをカスタマイズできます",
"theme_help_v2_1": "チェックボックスをONにすると、コンポーネントごとに、いろと、とうめいどを、オーバーライドできます。「すべてクリア」ボタンをおすと、すべてのオーバーライドを、やめます。",
"theme_help_v2_2": "バックグラウンドとテキストのコントラストをあらわすアイコンがあります。マウスをホバーすると、くわしいせつめいがでます。とうめいないろをつかっているときは、もっともわるいばあいのコントラストがしめされます。",
"upload_a_photo": "がぞうをアップロード",
@ -402,8 +402,8 @@
"hint": "コントラストは {ratio} です。{level}。({context})",
"level": {
"aa": "AAレベルガイドライン (ミニマル) をみたします",
"aaa": "AAAレベルガイドライン (レコメンデッド) をみたします",
"bad": "ガイドラインをみたしません"
"aaa": "AAAレベルガイドライン (レコメンデッド) をみたします",
"bad": "ガイドラインをみたしません"
},
"context": {
"18pt": "おおきい (18ポイントいじょう) テキスト",
@ -447,8 +447,8 @@
"always_drop_shadow": "ブラウザーがサポートしていれば、つねに {0} がつかわれます。",
"drop_shadow_syntax": "{0} は、{1} パラメーターと {2} キーワードをサポートしていません。",
"avatar_inset": "うちがわのかげと、そとがわのかげを、いっしょにつかうと、とうめいなアバターが、へんなみためになります。",
"spread_zero": "ひろがりが 0 よりもおおきなかげは、0 とおなじです",
"inset_classic": "うちがわのかげは {0} をつかいます"
"spread_zero": "ひろがりが 0 よりもおおきなかげは、0 とおなじです",
"inset_classic": "うちがわのかげは {0} をつかいます"
},
"components": {
"panel": "パネル",
@ -483,7 +483,7 @@
"content": "ほんぶん",
"error": "エラーのれい",
"button": "ボタン",
"text": "これは{0}と{1}のれいです",
"text": "これは{0}と{1}のれいです",
"mono": "monospace",
"input": "はねだくうこうに、つきました。",
"faint_link": "とてもたすけになるマニュアル",
@ -586,11 +586,11 @@
"subscribe": "サブスクライブ",
"unsubscribe": "サブスクライブをやめる",
"unblock": "ブロックをやめる",
"unblock_progress": "ブロックをとりけしています...",
"block_progress": "ブロックしています...",
"unblock_progress": "ブロックをとりけしています",
"block_progress": "ブロックしています",
"unmute": "ミュートをやめる",
"unmute_progress": "ミュートをとりけしています...",
"mute_progress": "ミュートしています...",
"unmute_progress": "ミュートをとりけしています",
"mute_progress": "ミュートしています",
"hide_repeats": "リピートをかくす",
"show_repeats": "リピートをみる",
"admin_menu": {
@ -638,11 +638,11 @@
"favorite": "おきにいり",
"user_settings": "ユーザーせってい"
},
"upload":{
"upload": {
"error": {
"base": "アップロードにしっぱいしました。",
"file_too_big": "ファイルがおおきすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
"default": "しばらくしてから、ためしてください"
"base": "アップロードにしっぱいしました。",
"file_too_big": "ファイルがおおきすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
"default": "しばらくしてから、ためしてください"
},
"file_size_units": {
"B": "B",

View File

@ -34,9 +34,9 @@
},
"domain_mute_card": {
"mute": "Wycisz",
"mute_progress": "Wyciszam...",
"mute_progress": "Wyciszam",
"unmute": "Odcisz",
"unmute_progress": "Odciszam..."
"unmute_progress": "Odciszam"
},
"exporter": {
"export": "Eksportuj",
@ -68,7 +68,10 @@
"disable": "Wyłącz",
"enable": "Włącz",
"confirm": "Potwierdź",
"verify": "Zweryfikuj"
"verify": "Zweryfikuj",
"close": "Zamknij",
"loading": "Ładowanie…",
"retry": "Spróbuj ponownie"
},
"image_cropper": {
"crop_picture": "Przytnij obrazek",
@ -118,7 +121,8 @@
"user_search": "Wyszukiwanie użytkowników",
"search": "Wyszukiwanie",
"who_to_follow": "Sugestie obserwacji",
"preferences": "Preferencje"
"preferences": "Preferencje",
"bookmarks": "Zakładki"
},
"notifications": {
"broken_favorite": "Nieznany status, szukam go…",
@ -190,7 +194,9 @@
"private": "Tylko dla obserwujących Umieść dla osób, które cię obserwują",
"public": "Publiczny Umieść na publicznych osiach czasu",
"unlisted": "Niewidoczny Nie umieszczaj na publicznych osiach czasu"
}
},
"preview_empty": "Pusty",
"preview": "Podgląd"
},
"registration": {
"bio": "Bio",
@ -234,7 +240,7 @@
"generate_new_recovery_codes": "Wygeneruj nowe kody zapasowe",
"warning_of_generate_new_codes": "Po tym gdy wygenerujesz nowe kody zapasowe, stare przestaną działać.",
"recovery_codes": "Kody zapasowe.",
"waiting_a_recovery_codes": "Otrzymuję kody zapasowe...",
"waiting_a_recovery_codes": "Otrzymuję kody zapasowe",
"recovery_codes_warning": "Spisz kody na kartce papieru, albo zapisz je w bezpiecznym miejscu - inaczej nie zobaczysz ich już nigdy. Jeśli stracisz dostęp do twojej aplikacji 2FA i kodów zapasowych, nie będziesz miał(-a) dostępu do swojego konta.",
"authentication_methods": "Metody weryfikacji",
"scan": {
@ -553,7 +559,13 @@
},
"notification_setting_privacy": "Prywatność",
"notification_setting_filters": "Filtry",
"notification_setting_privacy_option": "Ukryj nadawcę i zawartość powiadomień push"
"notification_setting_privacy_option": "Ukryj nadawcę i zawartość powiadomień push",
"reset_avatar": "Zresetuj awatar",
"profile_fields": {
"value": "Zawartość",
"label": "Metadane profilu"
},
"bot": "To konto jest prowadzone przez bota"
},
"time": {
"day": "{0} dzień",
@ -599,7 +611,8 @@
"show_new": "Pokaż nowe",
"up_to_date": "Na bieżąco",
"no_more_statuses": "Brak kolejnych statusów",
"no_statuses": "Brak statusów"
"no_statuses": "Brak statusów",
"reload": "Odśwież"
},
"status": {
"favorites": "Ulubione",
@ -732,5 +745,16 @@
"password_reset_disabled": "Resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji.",
"password_reset_required": "Musisz zresetować hasło, by się zalogować.",
"password_reset_required_but_mailer_is_disabled": "Musisz zresetować hasło, ale resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji."
},
"file_type": {
"file": "Plik"
},
"chats": {
"more": "Więcej",
"delete": "Usuń",
"you": "Ty:"
},
"display_date": {
"today": "Dzisiaj"
}
}

View File

@ -1,5 +1,6 @@
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { WSConnectionStatus } from '../services/api/api.service.js'
import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js'
import { Socket } from 'phoenix'
const api = {
@ -77,6 +78,7 @@ const api = {
messages: [message.chatUpdate.lastMessage]
})
dispatch('updateChat', { chat: message.chatUpdate })
maybeShowChatNotification(store, message.chatUpdate)
}
}
)

View File

@ -2,6 +2,7 @@ import Vue from 'vue'
import { find, omitBy, orderBy, sumBy } from 'lodash'
import chatService from '../services/chat_service/chat_service.js'
import { parseChat, parseChatMessage } from '../services/entity_normalizer/entity_normalizer.service.js'
import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js'
const emptyChatList = () => ({
data: [],
@ -59,8 +60,12 @@ const chats = {
return chats
})
},
addNewChats ({ rootState, commit, dispatch, rootGetters }, { chats }) {
commit('addNewChats', { dispatch, chats, rootGetters })
addNewChats (store, { chats }) {
const { commit, dispatch, rootGetters } = store
const newChatMessageSideEffects = (chat) => {
maybeShowChatNotification(store, chat)
}
commit('addNewChats', { dispatch, chats, rootGetters, newChatMessageSideEffects })
},
updateChat ({ commit }, { chat }) {
commit('updateChat', { chat })
@ -130,13 +135,17 @@ const chats = {
setCurrentChatId (state, { chatId }) {
state.currentChatId = chatId
},
addNewChats (state, { _dispatch, chats, _rootGetters }) {
addNewChats (state, { chats, newChatMessageSideEffects }) {
chats.forEach((updatedChat) => {
const chat = getChatById(state, updatedChat.id)
if (chat) {
const isNewMessage = (chat.lastMessage && chat.lastMessage.id) !== (updatedChat.lastMessage && updatedChat.lastMessage.id)
chat.lastMessage = updatedChat.lastMessage
chat.unread = updatedChat.unread
if (isNewMessage && chat.unread) {
newChatMessageSideEffects(updatedChat)
}
} else {
state.chatList.data.push(updatedChat)
Vue.set(state.chatList.idStore, updatedChat.id, updatedChat)

View File

@ -13,9 +13,8 @@ import {
omitBy
} from 'lodash'
import { set } from 'vue'
import { isStatusNotification, prepareNotificationObject } from '../services/notification_utils/notification_utils.js'
import { isStatusNotification, maybeShowNotification } from '../services/notification_utils/notification_utils.js'
import apiService from '../services/api/api.service.js'
import { muteWordHits } from '../services/status_parser/status_parser.js'
const emptyTl = (userId = 0) => ({
statuses: [],
@ -77,17 +76,6 @@ export const prepareStatus = (status) => {
return status
}
const visibleNotificationTypes = (rootState) => {
return [
rootState.config.notificationVisibility.likes && 'like',
rootState.config.notificationVisibility.mentions && 'mention',
rootState.config.notificationVisibility.repeats && 'repeat',
rootState.config.notificationVisibility.follows && 'follow',
rootState.config.notificationVisibility.moves && 'move',
rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reactions'
].filter(_ => _)
}
const mergeOrAdd = (arr, obj, item) => {
const oldItem = obj[item.id]
@ -325,7 +313,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
}
}
const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters }) => {
const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters, newNotificationSideEffects }) => {
each(notifications, (notification) => {
if (isStatusNotification(notification.type)) {
notification.action = addStatusToGlobalStorage(state, notification.action).item
@ -348,27 +336,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
state.notifications.data.push(notification)
state.notifications.idStore[notification.id] = notification
if ('Notification' in window && window.Notification.permission === 'granted') {
const notifObj = prepareNotificationObject(notification, rootGetters.i18n)
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(notifObj.title, notifObj)
// Chrome is known for not closing notifications automatically
// according to MDN, anyway.
setTimeout(desktopNotification.close.bind(desktopNotification), 5000)
}
}
newNotificationSideEffects(notification)
} else if (notification.seen) {
state.notifications.idStore[notification.id].seen = true
}
@ -609,8 +577,13 @@ const statuses = {
addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId, pagination }) {
commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId, pagination })
},
addNewNotifications ({ rootState, commit, dispatch, rootGetters }, { notifications, older }) {
commit('addNewNotifications', { visibleNotificationTypes: visibleNotificationTypes(rootState), dispatch, notifications, older, rootGetters })
addNewNotifications (store, { notifications, older }) {
const { commit, dispatch, rootGetters } = store
const newNotificationSideEffects = (notification) => {
maybeShowNotification(store, notification)
}
commit('addNewNotifications', { dispatch, notifications, older, rootGetters, newNotificationSideEffects })
},
setError ({ rootState, commit }, { value }) {
commit('setError', { value })

View File

@ -631,7 +631,8 @@ const postStatus = ({
mediaIds = [],
inReplyToStatusId,
contentType,
preview
preview,
idempotencyKey
}) => {
const form = new FormData()
const pollOptions = poll.options || []
@ -665,10 +666,15 @@ const postStatus = ({
form.append('preview', 'true')
}
let postHeaders = authHeaders(credentials)
if (idempotencyKey) {
postHeaders['idempotency-key'] = idempotencyKey
}
return fetch(MASTODON_POST_STATUS_URL, {
body: form,
method: 'POST',
headers: authHeaders(credentials)
headers: postHeaders
})
.then((response) => {
return response.json()

View File

@ -0,0 +1,19 @@
import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js'
export const maybeShowChatNotification = (store, chat) => {
if (!chat.lastMessage) return
if (store.rootState.chats.currentChatId === chat.id && !document.hidden) return
const opts = {
tag: chat.lastMessage.id,
title: chat.account.name,
icon: chat.account.profile_image_url,
body: chat.lastMessage.content
}
if (chat.lastMessage.attachment && chat.lastMessage.attachment.type === 'image') {
opts.image = chat.lastMessage.attachment.preview_url
}
showDesktopNotification(store.rootState, opts)
}

View File

@ -0,0 +1,9 @@
export const showDesktopNotification = (rootState, desktopNotificationOpts) => {
if (!('Notification' in window && window.Notification.permission === 'granted')) return
if (rootState.statuses.notifications.desktopNotificationSilence) { return }
const desktopNotification = new window.Notification(desktopNotificationOpts.title, desktopNotificationOpts)
// Chrome is known for not closing notifications automatically
// according to MDN, anyway.
setTimeout(desktopNotification.close.bind(desktopNotification), 5000)
}

View File

@ -79,6 +79,7 @@ export const parseUser = (data) => {
const relationship = data.pleroma.relationship
output.background_image = data.pleroma.background_image
output.favicon = data.pleroma.favicon
output.token = data.pleroma.chat_token
if (relationship) {

View File

@ -1,16 +1,22 @@
import { filter, sortBy, includes } from 'lodash'
import { muteWordHits } from '../status_parser/status_parser.js'
import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js'
export const notificationsFromStore = store => store.state.statuses.notifications.data
export const visibleTypes = store => ([
store.state.config.notificationVisibility.likes && 'like',
store.state.config.notificationVisibility.mentions && 'mention',
store.state.config.notificationVisibility.repeats && 'repeat',
store.state.config.notificationVisibility.follows && 'follow',
store.state.config.notificationVisibility.followRequest && 'follow_request',
store.state.config.notificationVisibility.moves && 'move',
store.state.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction'
].filter(_ => _))
export const visibleTypes = store => {
const rootState = store.rootState || store.state
return ([
rootState.config.notificationVisibility.likes && 'like',
rootState.config.notificationVisibility.mentions && 'mention',
rootState.config.notificationVisibility.repeats && 'repeat',
rootState.config.notificationVisibility.follows && 'follow',
rootState.config.notificationVisibility.followRequest && 'follow_request',
rootState.config.notificationVisibility.moves && 'move',
rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction'
].filter(_ => _))
}
const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reaction']
@ -32,6 +38,22 @@ const sortById = (a, b) => {
}
}
const isMutedNotification = (store, notification) => {
if (!notification.status) return
return notification.status.muted || muteWordHits(notification.status, store.rootGetters.mergedConfig.muteWords).length > 0
}
export const maybeShowNotification = (store, notification) => {
const rootState = store.rootState || store.state
if (notification.seen) return
if (!visibleTypes(store).includes(notification.type)) return
if (notification.type === 'mention' && isMutedNotification(store, notification)) return
const notificationObject = prepareNotificationObject(notification, store.rootGetters.i18n)
showDesktopNotification(rootState, notificationObject)
}
export const filteredNotificationsFromStore = (store, types) => {
// map is just to clone the array since sort mutates it and it causes some issues
let sortedNotifications = notificationsFromStore(store).map(_ => _).sort(sortById)

View File

@ -35,7 +35,7 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
const notifications = timelineData.data
const readNotifsIds = notifications.filter(n => n.seen).map(n => n.id)
const numUnseenNotifs = notifications.length - readNotifsIds.length
if (numUnseenNotifs > 0) {
if (numUnseenNotifs > 0 && readNotifsIds.length > 0) {
args['since'] = Math.max(...readNotifsIds)
fetchNotifications({ store, args, older })
}

View File

@ -11,7 +11,8 @@ const postStatus = ({
media = [],
inReplyToStatusId = undefined,
contentType = 'text/plain',
preview = false
preview = false,
idempotencyKey = ''
}) => {
const mediaIds = map(media, 'id')
@ -25,7 +26,8 @@ const postStatus = ({
inReplyToStatusId,
contentType,
poll,
preview
preview,
idempotencyKey
})
.then((data) => {
if (!data.error && !preview) {