Merge remote-tracking branch 'origin/develop' into shigusegubu

* origin/develop: (28 commits)
  Update CHANGELOG.md
  don't dismiss a rejected follow request on server
  mark single notifs as seen properly on server
  follow request bugfixes, wrong text, notifs not being marked as read, approving from follow request view
  Add support for follow request notifications
  Update CHANGELOG.md
  Update CHANGELOG.md
  Prioritize custom emoji a lot and boost exact matches to the top
  Allow emoji suggestions based on a match anywhere in the emoji name, but improve sorting
  Refactor status showing/hiding code for better handling of edge cases and easier comprehension
  Fix user names with the RTL char in notifications
  Fix pagination
  Update CHANGELOG.md
  don't dismiss a rejected follow request on server
  mark single notifs as seen properly on server
  follow request bugfixes, wrong text, notifs not being marked as read, approving from follow request view
  Update CHANGELOG.md
  remove with_move param
  Add support for follow request notifications
  Update CHANGELOG.md
  ...
This commit is contained in:
Henry Jameson 2020-05-02 19:53:41 +03:00
commit b5afffdac4
16 changed files with 574 additions and 79 deletions

View file

@ -2,8 +2,29 @@
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/).
## [Unreleased]
### Changed
- Removed the use of with_move parameters when fetching notifications
## [2.0.3] - 2020-05-02
### Fixed
- Show more/less works correctly with auto-collapsed subjects and long posts
- RTL characters won't look messed up in notifications
### Changed
- Emoji autocomplete will match any part of the word and not just start, for example :drool will now helpfully suggest :blobcatdrool: and :blobcatdroolreach:
### Add
- Follow request notification support
## [2.0.2] - 2020-04-08
### Fixed
- Favorite/Repeat avatars not showing up on private instances/non-public posts
- Autocorrect getting triggered in the captcha field
- Overflow on long domains in follow/move notifications
### Changed
- Polish translation updated
## [2.0.0] - 2020-02-28
### Added

View file

@ -29,17 +29,29 @@ export default data => input => {
export const suggestEmoji = emojis => input => {
const noPrefix = input.toLowerCase().substr(1)
return emojis
.filter(({ displayText }) => displayText.toLowerCase().startsWith(noPrefix))
.filter(({ displayText }) => displayText.toLowerCase().match(noPrefix))
.sort((a, b) => {
let aScore = 0
let bScore = 0
// Make custom emojis a priority
aScore += a.imageUrl ? 10 : 0
bScore += b.imageUrl ? 10 : 0
// An exact match always wins
aScore += a.displayText.toLowerCase() === noPrefix ? 200 : 0
bScore += b.displayText.toLowerCase() === noPrefix ? 200 : 0
// Sort alphabetically
const alphabetically = a.displayText > b.displayText ? 1 : -1
// Prioritize custom emoji a lot
aScore += a.imageUrl ? 100 : 0
bScore += b.imageUrl ? 100 : 0
// Prioritize prefix matches somewhat
aScore += a.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
bScore += b.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
// Sort by length
aScore -= a.displayText.length
bScore -= b.displayText.length
// Break ties alphabetically
const alphabetically = a.displayText > b.displayText ? 0.5 : -0.5
return bScore - aScore + alphabetically
})

View file

@ -1,4 +1,5 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js'
const FollowRequestCard = {
props: ['user'],
@ -6,13 +7,32 @@ const FollowRequestCard = {
BasicUserCard
},
methods: {
findFollowRequestNotificationId () {
const notif = notificationsFromStore(this.$store).find(
(notif) => notif.from_profile.id === this.user.id && notif.type === 'follow_request'
)
return notif && notif.id
},
approveUser () {
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user)
const notifId = this.findFollowRequestNotificationId()
this.$store.dispatch('markSingleNotificationAsSeen', { id: notifId })
this.$store.dispatch('updateNotification', {
id: notifId,
updater: notification => {
notification.type = 'follow'
}
})
},
denyUser () {
const notifId = this.findFollowRequestNotificationId()
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user)
.then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
this.$store.dispatch('removeFollowRequest', this.user)
})
}
}
}

View file

@ -2,6 +2,7 @@ import Status from '../status/status.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import UserCard from '../user_card/user_card.vue'
import Timeago from '../timeago/timeago.vue'
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@ -32,6 +33,24 @@ const Notification = {
},
toggleMute () {
this.unmuted = !this.unmuted
},
approveUser () {
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user)
this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id })
this.$store.dispatch('updateNotification', {
id: this.notification.id,
updater: notification => {
notification.type = 'follow'
}
})
},
denyUser () {
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
.then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id })
this.$store.dispatch('removeFollowRequest', this.user)
})
}
},
computed: {
@ -57,6 +76,9 @@ const Notification = {
},
needMute () {
return this.user.muted
},
isStatusNotification () {
return isStatusNotification(this.notification.type)
}
}
}

View file

@ -47,7 +47,7 @@
<span class="notification-details">
<div class="name-and-action">
<!-- eslint-disable vue/no-v-html -->
<span
<bdi
v-if="!!notification.from_profile.name_html"
class="username"
:title="'@'+notification.from_profile.screen_name"
@ -74,6 +74,10 @@
<i class="fa icon-user-plus lit" />
<small>{{ $t('notifications.followed_you') }}</small>
</span>
<span v-if="notification.type === 'follow_request'">
<i class="fa icon-user lit" />
<small>{{ $t('notifications.follow_request') }}</small>
</span>
<span v-if="notification.type === 'move'">
<i class="fa icon-arrow-curved lit" />
<small>{{ $t('notifications.migrated_to') }}</small>
@ -87,18 +91,7 @@
</span>
</div>
<div
v-if="notification.type === 'follow' || notification.type === 'move'"
class="timeago"
>
<span class="faint">
<Timeago
:time="notification.created_at"
:auto-update="240"
/>
</span>
</div>
<div
v-else
v-if="isStatusNotification"
class="timeago"
>
<router-link
@ -112,6 +105,17 @@
/>
</router-link>
</div>
<div
v-else
class="timeago"
>
<span class="faint">
<Timeago
:time="notification.created_at"
:auto-update="240"
/>
</span>
</div>
<a
v-if="needMute"
href="#"
@ -119,12 +123,30 @@
><i class="button-icon icon-eye-off" /></a>
</span>
<div
v-if="notification.type === 'follow'"
v-if="notification.type === 'follow' || notification.type === 'follow_request'"
class="follow-text"
>
<router-link :to="userProfileLink">
<router-link
:to="userProfileLink"
class="follow-name"
>
@{{ notification.from_profile.screen_name }}
</router-link>
<div
v-if="notification.type === 'follow_request'"
style="white-space: nowrap;"
>
<i
class="icon-ok button-icon follow-request-accept"
:title="$t('tool_tip.accept_follow_request')"
@click="approveUser()"
/>
<i
class="icon-cancel button-icon follow-request-reject"
:title="$t('tool_tip.reject_follow_request')"
@click="denyUser()"
/>
</div>
</div>
<div
v-else-if="notification.type === 'move'"

View file

@ -79,9 +79,38 @@
}
}
.follow-request-accept {
cursor: pointer;
&:hover {
color: $fallback--text;
color: var(--text, $fallback--text);
}
}
.follow-request-reject {
cursor: pointer;
&:hover {
color: $fallback--cRed;
color: var(--cRed, $fallback--cRed);
}
}
.follow-text, .move-text {
padding: 0.5em 0;
overflow-wrap: break-word;
display: flex;
justify-content: space-between;
.follow-name {
display: block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.status-el {
@ -143,6 +172,11 @@
color: var(--cGreen, $fallback--cGreen);
}
.icon-user.lit {
color: $fallback--cBlue;
color: var(--cBlue, $fallback--cBlue);
}
.icon-user-plus.lit {
color: $fallback--cBlue;
color: var(--cBlue, $fallback--cBlue);

View file

@ -189,23 +189,22 @@ const Status = {
}
return this.status.attentions.length > 0
},
// When a status has a subject and is also tall, we should only have one show more/less button. If the default is to collapse statuses with subjects, we just treat it like a status with a subject; otherwise, we just treat it like a tall status.
mightHideBecauseSubject () {
return this.status.summary && (!this.tallStatus || this.localCollapseSubjectDefault)
},
mightHideBecauseTall () {
return this.tallStatus && (!this.status.summary || !this.localCollapseSubjectDefault)
},
hideSubjectStatus () {
if (this.tallStatus && !this.localCollapseSubjectDefault) {
return false
}
return !this.expandingSubject && this.status.summary
return this.mightHideBecauseSubject && !this.expandingSubject
},
hideTallStatus () {
if (this.status.summary && this.localCollapseSubjectDefault) {
return false
}
if (this.showingTall) {
return false
}
return this.tallStatus
return this.mightHideBecauseTall && !this.showingTall
},
showingMore () {
return (this.tallStatus && this.showingTall) || (this.status.summary && this.expandingSubject)
return (this.mightHideBecauseTall && this.showingTall) || (this.mightHideBecauseSubject && this.expandingSubject)
},
nsfwClickthrough () {
if (!this.status.nsfw) {
@ -409,14 +408,10 @@ const Status = {
this.userExpanded = !this.userExpanded
},
toggleShowMore () {
if (this.showingTall) {
this.showingTall = false
} else if (this.expandingSubject && this.status.summary) {
this.expandingSubject = false
} else if (this.hideTallStatus) {
this.showingTall = true
} else if (this.hideSubjectStatus && this.status.summary) {
this.expandingSubject = true
if (this.mightHideBecauseTall) {
this.showingTall = !this.showingTall
} else if (this.mightHideBecauseSubject) {
this.expandingSubject = !this.expandingSubject
}
},
generateUserProfileLink (id, name) {

View file

@ -124,6 +124,7 @@
"broken_favorite": "Unknown status, searching for it...",
"favorited_you": "favorited your status",
"followed_you": "followed you",
"follow_request": "wants to follow you",
"load_older": "Load older notifications",
"notifications": "Notifications",
"read": "Read!",
@ -698,7 +699,9 @@
"reply": "Reply",
"favorite": "Favorite",
"add_reaction": "Add Reaction",
"user_settings": "User Settings"
"user_settings": "User Settings",
"accept_follow_request": "Accept follow request",
"reject_follow_request": "Reject follow request"
},
"upload":{
"error": {

View file

@ -1,7 +1,47 @@
{
"about": {
"mrf": {
"federation": "Federacja",
"keyword": {
"keyword_policies": "Zasady słów kluczowych",
"ftl_removal": "Usunięcie z \"Całej znanej sieci\"",
"reject": "Odrzucanie",
"replace": "Zastąpienie",
"is_replaced_by": "→"
},
"mrf_policies": "Włączone zasady MRF",
"mrf_policies_desc": "Zasady MRF zmieniają zachowanie federowania instancji. Następujące zasady są włączone:",
"simple": {
"simple_policies": "Zasady specyficzne dla instancji",
"accept": "Akceptowanie",
"accept_desc": "Ta instancja akceptuje tylko posty z wymienionych instancji:",
"reject": "Odrzucanie",
"reject_desc": "Ta instancja odrzuca posty z wymienionych instancji:",
"quarantine": "Kwarantanna",
"quarantine_desc": "Ta instancja wysyła tylko publiczne posty do wymienionych instancji:",
"ftl_removal": "Usunięcie z \"Całej znanej sieci\"",
"ftl_removal_desc": "Ta instancja usuwa te instancje z \"Całej znanej sieci\"",
"media_removal": "Usuwanie multimediów",
"media_removal_desc": "Ta instancja usuwa multimedia z postów od wymienionych instancji:",
"media_nsfw": "Multimedia ustawione jako wrażliwe",
"media_nsfw_desc": "Ta instancja wymusza, by multimedia z wymienionych instancji były ustawione jako wrażliwe:"
}
},
"staff": "Obsługa"
},
"chat": {
"title": "Czat"
},
"domain_mute_card": {
"mute": "Wycisz",
"mute_progress": "Wyciszam...",
"unmute": "Odcisz",
"unmute_progress": "Odciszam..."
},
"exporter": {
"export": "Eksportuj",
"processing": "Przetwarzam, za chwilę zostaniesz zapytany o ściągnięcie pliku"
},
"features_panel": {
"chat": "Czat",
"gopher": "Gopher",
@ -20,7 +60,15 @@
"submit": "Wyślij",
"more": "Więcej",
"generic_error": "Wystąpił błąd",
"optional": "nieobowiązkowe"
"optional": "nieobowiązkowe",
"show_more": "Pokaż więcej",
"show_less": "Pokaż mniej",
"dismiss": "Odrzuć",
"cancel": "Anuluj",
"disable": "Wyłącz",
"enable": "Włącz",
"confirm": "Potwierdź",
"verify": "Zweryfikuj"
},
"image_cropper": {
"crop_picture": "Przytnij obrazek",
@ -28,6 +76,11 @@
"save_without_cropping": "Zapisz bez przycinania",
"cancel": "Anuluj"
},
"importer": {
"submit": "Wyślij",
"success": "Zaimportowano pomyślnie",
"error": "Wystąpił błąd podczas importowania pliku."
},
"login": {
"login": "Zaloguj",
"description": "Zaloguj używając OAuth",
@ -36,7 +89,15 @@
"placeholder": "n.p. lain",
"register": "Zarejestruj",
"username": "Użytkownik",
"hint": "Zaloguj się, aby dołączyć do dyskusji"
"hint": "Zaloguj się, aby dołączyć do dyskusji",
"authentication_code": "Kod weryfikacyjny",
"enter_recovery_code": "Wprowadź kod zapasowy",
"enter_two_factor_code": "Wprowadź kod weryfikacyjny",
"recovery_code": "Kod zapasowy",
"heading" : {
"totp" : "Weryfikacja dwuetapowa",
"recovery" : "Zapasowa weryfikacja dwuetapowa"
}
},
"media_modal": {
"previous": "Poprzednie",
@ -44,15 +105,18 @@
},
"nav": {
"about": "O nas",
"administration": "Administracja",
"back": "Wróć",
"chat": "Lokalny czat",
"friend_requests": "Prośby o możliwość obserwacji",
"mentions": "Wzmianki",
"interactions": "Interakcje",
"dms": "Wiadomości prywatne",
"public_tl": "Publiczna oś czasu",
"timeline": "Oś czasu",
"twkn": "Cała znana sieć",
"user_search": "Wyszukiwanie użytkowników",
"search": "Wyszukiwanie",
"who_to_follow": "Sugestie obserwacji",
"preferences": "Preferencje"
},
@ -64,7 +128,40 @@
"notifications": "Powiadomienia",
"read": "Przeczytane!",
"repeated_you": "powtórzył(-a) twój status",
"no_more_notifications": "Nie masz więcej powiadomień"
"no_more_notifications": "Nie masz więcej powiadomień",
"migrated_to": "wyemigrował do",
"reacted_with": "zareagował z {0}"
},
"polls": {
"add_poll": "Dodaj ankietę",
"add_option": "Dodaj opcję",
"option": "Opcja",
"votes": "głosów",
"vote": "Głosuj",
"type": "Typ ankiety",
"single_choice": "jednokrotnego wyboru",
"multiple_choices": "wielokrotnego wyboru",
"expiry": "Czas trwania ankiety",
"expires_in": "Ankieta kończy się za{0}",
"expired": "Ankieta skończyła się {0} temu",
"not_enough_options": "Zbyt mało unikalnych opcji w ankiecie"
},
"emoji": {
"stickers": "Naklejki",
"emoji": "Emoji",
"keep_open": "Zostaw selektor otwarty",
"search_emoji": "Wyszukaj emoji",
"add_emoji": "Wstaw emoji",
"custom": "Niestandardowe emoji",
"unicode": "Emoji unicode",
"load_all_hint": "Załadowano pierwsze {saneAmount} emoji, Załadowanie wszystkich emoji może spowodować problemy z wydajnością.",
"load_all": "Ładuję wszystkie {emojiAmount} emoji"
},
"interactions": {
"favs_repeats": "Powtórzenia i ulubione",
"follows": "Nowi obserwujący",
"moves": "Użytkownik migruje",
"load_older": "Załaduj starsze interakcje"
},
"post_status": {
"new_status": "Dodaj nowy status",
@ -79,8 +176,14 @@
},
"content_warning": "Temat (nieobowiązkowy)",
"default": "Właśnie wróciłem z kościoła",
"direct_warning": "Ten wpis zobaczą tylko osoby, o których wspomniałeś(-aś).",
"direct_warning_to_all": "Ten wpis zobaczą wszystkie osoby, o których wspomniałeś(-aś).",
"direct_warning_to_first_only": "Ten wpis zobaczą tylko te osoby, o których wspomniałeś(-aś) na początku wiadomości.",
"posting": "Wysyłanie",
"scope_notice": {
"public": "Ten post będzie widoczny dla każdego",
"private": "Ten post będzie widoczny tylko dla twoich obserwujących",
"unlisted": "Ten post nie będzie widoczny na publicznej osi czasu i całej znanej sieci"
},
"scope": {
"direct": "Bezpośredni Tylko dla wspomnianych użytkowników",
"private": "Tylko dla obserwujących Umieść dla osób, które cię obserwują",
@ -109,8 +212,40 @@
"password_confirmation_match": "musi być takie jak hasło"
}
},
"remote_user_resolver": {
"remote_user_resolver": "Wyszukiwarka użytkowników nietutejszych",
"searching_for": "Szukam",
"error": "Nie znaleziono."
},
"selectable_list": {
"select_all": "Zaznacz wszystko"
},
"settings": {
"app_name": "Nazwa aplikacji",
"security": "Bezpieczeństwo",
"enter_current_password_to_confirm": "Wprowadź obecne hasło, by potwierdzić twoją tożsamość",
"mfa": {
"otp" : "OTP",
"setup_otp" : "Ustaw OTP",
"wait_pre_setup_otp" : "początkowe ustawianie OTP",
"confirm_and_enable" : "Potwierdź i włącz OTP",
"title": "Weryfikacja dwuetapowa",
"generate_new_recovery_codes" : "Wygeneruj nowe kody zapasowe",
"warning_of_generate_new_codes" : "Po tym gdy generujesz nowe kody zapasowe, stare przestaną działać.",
"recovery_codes" : "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ł dostępu do swojego konta.",
"authentication_methods" : "Metody weryfikacji",
"scan": {
"title": "Skanuj",
"desc": "Zeskanuj ten kod QR używając twojej aplikacji 2FA albo wpisz ten klucz:",
"secret_code": "Klucz"
},
"verify": {
"desc": "By włączyć weryfikację dwuetapową, wpisz kod z twojej aplikacji 2FA:"
}
},
"allow_following_move": "Zezwalaj na automatyczną obserwację gdy obserwowane konto migruje",
"attachmentRadius": "Załączniki",
"attachments": "Załączniki",
"autoload": "Włącz automatyczne ładowanie po przewinięciu do końca strony",
@ -119,12 +254,20 @@
"avatarRadius": "Awatary",
"background": "Tło",
"bio": "Bio",
"block_export": "Eksport blokad",
"block_export_button": "Eksportuj twoje blokady do pliku .csv",
"block_import": "Import blokad",
"block_import_error": "Wystąpił błąd podczas importowania blokad",
"blocks_imported": "Zaimportowano blokady, przetwarzanie może zająć trochę czasu.",
"blocks_tab": "Bloki",
"btnRadius": "Przyciski",
"cBlue": "Niebieski (odpowiedz, obserwuj)",
"cGreen": "Zielony (powtórzenia)",
"cOrange": "Pomarańczowy (ulubione)",
"cRed": "Czerwony (anuluj)",
"change_email": "Zmień email",
"change_email_error": "Wystąpił problem podczas zmiany emaila.",
"changed_email": "Pomyślnie zmieniono email!",
"change_password": "Zmień hasło",
"change_password_error": "Podczas zmiany hasła wystąpił problem.",
"changed_password": "Pomyślnie zmieniono hasło!",
@ -140,16 +283,20 @@
"delete_account_description": "Trwale usuń konto i wszystkie posty.",
"delete_account_error": "Wystąpił problem z usuwaniem twojego konta. Jeżeli problem powtarza się, poinformuj administratora swojej instancji.",
"delete_account_instructions": "Wprowadź swoje hasło w poniższe pole aby potwierdzić usunięcie konta.",
"discoverable": "Zezwól na odkrywanie tego konta w wynikach wyszukiwania i innych usługa.",
"domain_mutes": "Domeny",
"avatar_size_instruction": "Zalecany minimalny rozmiar awatarów to 150x150 pikseli.",
"pad_emoji": "Dodaj odstęp z obu stron emoji podczas dodawania selektorem",
"emoji_reactions_on_timeline": "Pokaż reakcje emoji na osi czasu",
"export_theme": "Zapisz motyw",
"filtering": "Filtrowanie",
"filtering_explanation": "Wszystkie statusy zawierające te słowa będą wyciszone. Jedno słowo na linijkę.",
"follow_export": "Eksport obserwowanych",
"follow_export_button": "Eksportuj swoją listę obserwowanych do pliku CSV",
"follow_export_processing": "Przetwarzanie, wkrótce twój plik zacznie się ściągać.",
"follow_import": "Import obserwowanych",
"follow_import_error": "Błąd przy importowaniu obserwowanych",
"follows_imported": "Obserwowani zaimportowani! Przetwarzanie może trochę potrwać.",
"accent": "Akcent",
"foreground": "Pierwszy plan",
"general": "Ogólne",
"hide_attachments_in_convo": "Ukrywaj załączniki w rozmowach",
@ -162,6 +309,7 @@
"hide_post_stats": "Ukrywaj statysyki postów (np. liczbę polubień)",
"hide_user_stats": "Ukrywaj statysyki użytkowników (np. liczbę obserwujących)",
"hide_filtered_statuses": "Ukrywaj filtrowane statusy",
"import_blocks_from_a_csv_file": "Importuj blokady z pliku CSV",
"import_followers_from_a_csv_file": "Importuj obserwowanych z pliku CSV",
"import_theme": "Załaduj motyw",
"inputRadius": "Pola tekstowe",
@ -181,17 +329,22 @@
"use_contain_fit": "Nie przycinaj załączników na miniaturach",
"name": "Imię",
"name_bio": "Imię i bio",
"new_email": "Nowy email",
"new_password": "Nowe hasło",
"notification_visibility": "Rodzaje powiadomień do wyświetlania",
"notification_visibility_follows": "Obserwacje",
"notification_visibility_likes": "Ulubione",
"notification_visibility_mentions": "Wzmianki",
"notification_visibility_repeats": "Powtórzenia",
"notification_visibility_moves": "Użytkownik migruje",
"notification_visibility_emoji_reactions": "Reakcje",
"no_rich_text_description": "Usuwaj formatowanie ze wszystkich postów",
"no_blocks": "Bez blokad",
"no_mutes": "Bez wyciszeń",
"hide_follows_description": "Nie pokazuj kogo obserwuję",
"hide_followers_description": "Nie pokazuj kto mnie obserwuje",
"hide_follows_count_description": "Nie pokazuj licznika obserwowanych",
"hide_followers_count_description": "Nie pokazuj licznika obserwujących",
"show_admin_badge": "Pokazuj odznakę Administrator na moim profilu",
"show_moderator_badge": "Pokazuj odznakę Moderator na moim profilu",
"nsfw_clickthrough": "Włącz domyślne ukrywanie załączników o treści nieprzyzwoitej (NSFW)",
@ -212,10 +365,14 @@
"reply_visibility_all": "Pokazuj wszystkie odpowiedzi",
"reply_visibility_following": "Pokazuj tylko odpowiedzi skierowane do mnie i osób które obserwuję",
"reply_visibility_self": "Pokazuj tylko odpowiedzi skierowane do mnie",
"autohide_floating_post_button": "Ukryj automatycznie przycisk \"Nowy post\" (mobile)",
"saving_err": "Nie udało się zapisać ustawień",
"saving_ok": "Zapisano ustawienia",
"search_user_to_block": "Wyszukaj kogo chcesz zablokować",
"search_user_to_mute": "Wyszukaj kogo chcesz wyciszyć",
"security_tab": "Bezpieczeństwo",
"scope_copy": "Kopiuj zakres podczas odpowiadania (DM-y zawsze są kopiowane)",
"minimal_scopes_mode": "Zminimalizuj opcje wyboru zakresu postów",
"set_new_avatar": "Ustaw nowy awatar",
"set_new_profile_background": "Ustaw nowe tło profilu",
"set_new_profile_banner": "Ustaw nowy banner profilu",
@ -228,19 +385,32 @@
"post_status_content_type": "Post status content type",
"stop_gifs": "Odtwarzaj GIFy po najechaniu kursorem",
"streaming": "Włącz automatycznie strumieniowanie nowych postów gdy jesteś na początku strony",
"user_mutes": "Users",
"useStreamingApi": "Otrzymuj posty i powiadomienia w czasie rzeczywistym",
"useStreamingApiWarning": "(Niezalecane, eksperymentalne, pomija posty)",
"text": "Tekst",
"theme": "Motyw",
"theme_help": "Użyj kolorów w notacji szesnastkowej (#rrggbb), by stworzyć swój motyw.",
"theme_help_v2_1": "Możesz też zastąpić kolory i widoczność poszczególnych komponentów przełączając pola wyboru, użyj „Wyczyść wszystko” aby usunąć wszystkie zastąpienia.",
"theme_help_v2_2": "Ikony pod niektórych wpisami są wskaźnikami kontrastu pomiędzy tłem a tekstem, po najechaniu na nie otrzymasz szczegółowe informacje. Zapamiętaj, że jeżeli używasz przezroczystości, wskaźniki pokazują najgorszy możliwy przypadek.",
"tooltipRadius": "Etykiety/alerty",
"type_domains_to_mute": "Wpisz domeny, które chcesz wyciszyć",
"upload_a_photo": "Wyślij zdjęcie",
"user_settings": "Ustawienia użytkownika",
"values": {
"false": "nie",
"true": "tak"
},
"fun": "Zabawa",
"greentext": "Memiczne strzałki",
"notifications": "Powiadomienia",
"notification_setting": "Otrzymuj powiadomienia od:",
"notification_setting_follows": "Ludzi których obserwujesz",
"notification_setting_non_follows": "Ludzi których nie obserwujesz",
"notification_setting_followers": "Ludzi którzy obserwują ciebie",
"notification_setting_non_followers": "Ludzi którzy nie obserwują ciebie",
"notification_mutes": "By przestać otrzymywać powiadomienia od jednego użytkownika, wycisz go",
"notification_blocks": "Blokowanie uzytkownika zatrzymuje wszystkie powiadomienia i odsubskrybowuje go.",
"enable_web_push_notifications": "Włącz powiadomienia push",
"style": {
"switcher": {
@ -252,7 +422,24 @@
"save_load_hint": "Opcje „zachowaj” pozwalają na pozostanie przy obecnych opcjach po wybraniu lub załadowaniu motywu, jak i przechowywanie ich podczas eksportowania motywu. Jeżeli wszystkie są odznaczone, eksportowanie motywu spowoduje zapisanie wszystkiego.",
"reset": "Wyzeruj",
"clear_all": "Wyczyść wszystko",
"clear_opacity": "Wyczyść widoczność"
"clear_opacity": "Wyczyść widoczność",
"load_theme": "Załaduj motyw",
"keep_as_is": "Zostaw po staremu",
"use_snapshot": "Stara wersja",
"use_source": "Nowa wersja",
"help": {
"upgraded_from_v2": "PleromaFE zostało zaaktualizowane, motyw może wyglądać nieco inaczej niż sobie zapamiętałeś.",
"v2_imported": "Plik który zaimportowałeś został stworzony dla starszego FE. Próbujemy zwiększyć kompatybiliność, lecz wciąż mogą występować rozbieżności.",
"future_version_imported": "Plik który zaimportowałeś został stworzony w nowszej wersji FE.",
"older_version_imported": "Plik który zaimportowałeś został stworzony w starszej wersji FE.",
"snapshot_present": "Migawka motywu jest załadowana, więc wszystkie wartości zostały nadpisane. Zamiast tego, możesz załadować właściwe dane motywu",
"snapshot_missing": "Nie znaleziono migawki motywu w pliku, więc motyw może wyglądać inaczej niż pierwotnie zaplanowano.",
"fe_upgraded": "Silnik motywów PleromaFE został zaaktualizowany.",
"fe_downgraded": "Wersja PleromaFE została cofnięta.",
"migration_snapshot_ok": "Żeby być bezpiecznym, migawka motywu została załadowana. Możesz spróbować załadować dane motywu.",
"migration_napshot_gone": "Z jakiegoś powodu migawka zniknęła, niektóre rzeczy mogą wyglądać inaczej niż sobie zapamiętałeś.",
"snapshot_source_mismatch": "Konflikt wersji: najprawdopodobniej FE zostało cofnięte do poprzedniej wersji i zaaktualizowane ponownie, jeśli zmieniłeś motyw używając starszej wersji FE, najprawdopodobniej chcesz używać starszej wersji, w przeciwnym razie użyj nowej wersji."
}
},
"common": {
"color": "Kolor",
@ -280,14 +467,28 @@
"_tab_label": "Zaawansowane",
"alert": "Tło alertu",
"alert_error": "Błąd",
"alert_warning": "Ostrzeżenie",
"alert_neutral": "Neutralne",
"post": "Posty/Bio użytkowników",
"badge": "Tło odznaki",
"popover": "Etykiety, menu, popovery",
"badge_notification": "Powiadomienie",
"panel_header": "Nagłówek panelu",
"top_bar": "Górny pasek",
"borders": "Granice",
"buttons": "Przyciski",
"inputs": "Pola wejścia",
"faint_text": "Zanikający tekst"
"faint_text": "Zanikający tekst",
"underlay": "Podkład",
"poll": "Wykres ankiety",
"icons": "Ikony",
"highlight": "Podświetlone elementy",
"pressed": "Naciśnięte",
"selectedPost": "Wybrany post",
"selectedMenu": "Wybrany element menu",
"disabled": "Wyłączone",
"toggled": "Przełączone",
"tabs": "Karty"
},
"radii": {
"_tab_label": "Zaokrąglenie"
@ -300,7 +501,7 @@
"blur": "Rozmycie",
"spread": "Szerokość",
"inset": "Inset",
"hint": "Możesz też używać --zmiennych jako kolorów, aby wykorzystać zmienne CSS3. Pamiętaj, że ustawienie widoczności nie będzie wtedy działać.",
"hintV3": "Dla cieni możesz również użyć notacji {0} by użyć inny slot koloru.",
"filter_hint": {
"always_drop_shadow": "Ostrzeżenie, ten cień zawsze używa {0} jeżeli to obsługiwane przez przeglądarkę.",
"drop_shadow_syntax": "{0} nie obsługuje parametru {1} i słowa kluczowego {2}.",
@ -357,6 +558,40 @@
"frontend_version": "Wersja front-endu"
}
},
"time": {
"day": "{0} dzień",
"days": "{0} dni",
"day_short": "{0}d",
"days_short": "{0}d",
"hour": "{0} godzina",
"hours": "{0} godzin",
"hour_short": "{0} godz.",
"hours_short": "{0} godz.",
"in_future": "za {0}",
"in_past": "{0} temu",
"minute": "{0} minuta",
"minutes": "{0} minut",
"minute_short": "{0}min",
"minutes_short": "{0}min",
"month": "{0} miesiąc",
"months": "{0} miesięcy",
"month_short": "{0} mies.",
"months_short": "{0} mies.",
"now": "teraz",
"now_short": "teraz",
"second": "{0} sekunda",
"seconds": "{0} sekund",
"second_short": "{0}s",
"seconds_short": "{0}s",
"week": "{0} tydzień",
"weeks": "{0} tygodni",
"week_short": "{0} tydz.",
"weeks_short": "{0} tyg.",
"year": "{0} rok",
"years": "{0} lata",
"year_short": "{0} r.",
"years_short": "{0} lata"
},
"timeline": {
"collapse": "Zwiń",
"conversation": "Rozmowa",
@ -370,8 +605,17 @@
"no_statuses": "Brak statusów"
},
"status": {
"favorites": "Ulubione",
"repeats": "Powtórzenia",
"delete": "Usuń status",
"pin": "Przypnij na profilu",
"unpin": "Odepnij z profilu",
"pinned": "Przypnięte",
"delete_confirm": "Czy naprawdę chcesz usunąć ten status?",
"reply_to": "Odpowiedź dla",
"replies_list": "Odpowiedzi:"
"replies_list": "Odpowiedzi:",
"mute_conversation": "Wycisz konwersację",
"unmute_conversation": "Odcisz konwersację"
},
"user_card": {
"approve": "Przyjmij",
@ -388,25 +632,60 @@
"followers": "Obserwujący",
"following": "Obserwowany!",
"follows_you": "Obserwuje cię!",
"hidden": "Ukryte",
"its_you": "To ty!",
"media": "Media",
"mention": "Wspomnienie",
"mute": "Wycisz",
"muted": "Wyciszony(-a)",
"per_day": "dziennie",
"remote_follow": "Zdalna obserwacja",
"report": "Raportuj",
"statuses": "Statusy",
"subscribe": "Subskrybuj",
"unsubscribe": "Odsubskrybuj",
"unblock": "Odblokuj",
"unblock_progress": "Odblokowuję…",
"block_progress": "Blokuję…",
"unmute": "Cofnij wyciszenie",
"unmute_progress": "Cofam wyciszenie…",
"mute_progress": "Wyciszam…"
"mute_progress": "Wyciszam…",
"hide_repeats": "Ukryj powtórzenia",
"show_repeats": "Pokaż powtórzenia",
"admin_menu": {
"moderation": "Moderacja",
"grant_admin": "Przyznaj admina",
"revoke_admin": "Odwołaj admina",
"grant_moderator": "Przyznaj moderatora",
"revoke_moderator": "Odwołaj moderatora",
"activate_account": "Aktywuj konto",
"deactivate_account": "Dezaktywuj konto",
"delete_account": "Usuń konto",
"force_nsfw": "Oznacz wszystkie posty jako NSFW",
"strip_media": "Usuń multimedia z postów",
"force_unlisted": "Wymuś posty na niepubliczne",
"sandbox": "Wymuś by posty były tylko dla obserwujących",
"disable_remote_subscription": "Zakaż obserwowania użytkownika ze zdalnych instancji",
"disable_any_subscription": "Zakaż całkowicie obserwowania użytkownika",
"quarantine": "Zakaż federowania postów od tego użytkownika",
"delete_user": "Usuń użytkownika",
"delete_user_confirmation": "Czy jesteś absolutnie pewny? Ta operacja nie może być cofnięta."
}
},
"user_profile": {
"timeline_title": "Oś czasu użytkownika",
"profile_does_not_exist": "Przepraszamy, ten profil nie istnieje.",
"profile_loading_error": "Przepraszamy, wystąpił błąd podczas ładowania tego profilu."
},
"user_reporting": {
"title": "Raportowanie {0}",
"add_comment_description": "Raport zostanie wysłany do moderatorów instancji. Możesz dodać powód dlaczego raportujesz to konto poniżej:",
"additional_comments": "Dodatkowe komentarze",
"forward_description": "To konto jest z innego serwera. Wysłać również tam kopię raportu?",
"forward_to": "Przekaż do{0}",
"submit": "Wyślij",
"generic_error": "Wystąpił błąd podczas przetwarzania twojej prośby."
},
"who_to_follow": {
"more": "Więcej",
"who_to_follow": "Propozycje obserwacji"
@ -416,6 +695,7 @@
"repeat": "Powtórz",
"reply": "Odpowiedz",
"favorite": "Dodaj do ulubionych",
"add_reaction": "Dodaj reakcję",
"user_settings": "Ustawienia użytkownika"
},
"upload":{
@ -431,5 +711,25 @@
"GiB": "GiB",
"TiB": "TiB"
}
},
"search": {
"people": "Ludzie",
"hashtags": "Hasztagi",
"person_talking": "{count} osoba rozmawia o tym",
"people_talking": "{count} osób rozmawia o tym",
"no_results": "Brak wyników"
},
"password_reset": {
"forgot_password": "Zapomniałeś hasła?",
"password_reset": "Reset hasła",
"instruction": "Wprowadź swój adres email lub nazwę użytkownika. Wyślemy ci link z którym możesz zresetować hasło.",
"placeholder": "Twój email lub nazwa użytkownika",
"check_email": "Sprawdź pocztę, aby uzyskać link do zresetowania hasła.",
"return_home": "Wróć do strony głównej",
"not_found": "Nie mogliśmy znaleźć tego emaila lub nazwy użytkownika.",
"too_many_requests": "Przekroczyłeś limit prób, spróbuj ponownie później.",
"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."
}
}

View file

@ -34,7 +34,8 @@ export const defaultState = {
likes: true,
repeats: true,
moves: true,
emojiReactions: false
emojiReactions: false,
followRequest: true
},
webPushNotifications: false,
muteWords: [],

View file

@ -13,6 +13,7 @@ import {
omitBy
} from 'lodash'
import { set } from 'vue'
import { isStatusNotification } from '../services/notification_utils/notification_utils.js'
import apiService from '../services/api/api.service.js'
// import parse from '../services/status_parser/status_parser.js'
@ -321,7 +322,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters }) => {
each(notifications, (notification) => {
if (notification.type !== 'follow' && notification.type !== 'move') {
if (isStatusNotification(notification.type)) {
notification.action = addStatusToGlobalStorage(state, notification.action).item
notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
}
@ -361,13 +362,16 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
case 'move':
i18nString = 'migrated_to'
break
case 'follow_request':
i18nString = 'follow_request'
break
}
if (notification.type === 'pleroma:emoji_reaction') {
notifObj.body = rootGetters.i18n.t('notifications.reacted_with', [notification.emoji])
} else if (i18nString) {
notifObj.body = rootGetters.i18n.t('notifications.' + i18nString)
} else {
} else if (isStatusNotification(notification.type)) {
notifObj.body = notification.status.text
}
@ -521,6 +525,17 @@ export const mutations = {
notification.seen = true
})
},
markSingleNotificationAsSeen (state, { id }) {
const notification = find(state.notifications.data, n => n.id === id)
if (notification) notification.seen = true
},
dismissNotification (state, { id }) {
state.notifications.data = state.notifications.data.filter(n => n.id !== id)
},
updateNotification (state, { id, updater }) {
const notification = find(state.notifications.data, n => n.id === id)
notification && updater(notification)
},
queueFlush (state, { timeline, id }) {
state.timelines[timeline].flushMarker = id
},
@ -680,6 +695,24 @@ const statuses = {
credentials: rootState.users.currentUser.credentials
})
},
markSingleNotificationAsSeen ({ rootState, commit }, { id }) {
commit('markSingleNotificationAsSeen', { id })
apiService.markNotificationsAsSeen({
single: true,
id,
credentials: rootState.users.currentUser.credentials
})
},
dismissNotificationLocal ({ rootState, commit }, { id }) {
commit('dismissNotification', { id })
},
dismissNotification ({ rootState, commit }, { id }) {
commit('dismissNotification', { id })
rootState.api.backendInteractor.dismissNotification({ id })
},
updateNotification ({ rootState, commit }, { id, updater }) {
commit('updateNotification', { id, updater })
},
fetchFavsAndRepeats ({ rootState, commit }, id) {
Promise.all([
rootState.api.backendInteractor.fetchFavoritedByUsers({ id }),

View file

@ -4,7 +4,6 @@ import 'whatwg-fetch'
import { RegistrationError, StatusCodeError } from '../errors/errors'
/* eslint-env browser */
const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import'
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
@ -17,6 +16,7 @@ const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate'
const ADMIN_USERS_URL = '/api/pleroma/admin/users'
const SUGGESTIONS_URL = '/api/v1/suggestions'
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa'
const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
@ -29,6 +29,7 @@ const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
const MASTODON_DISMISS_NOTIFICATION_URL = id => `/api/v1/notifications/${id}/dismiss`
const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite`
const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite`
const MASTODON_RETWEET_URL = id => `/api/v1/statuses/${id}/reblog`
@ -495,8 +496,7 @@ const fetchTimeline = ({
until = false,
userId = false,
tag = false,
withMuted = false,
withMove = false
withMuted = false
}) => {
const timelineUrls = {
public: MASTODON_PUBLIC_TIMELINE,
@ -536,11 +536,8 @@ const fetchTimeline = ({
if (timeline === 'public' || timeline === 'publicAndExternal') {
params.push(['only_media', false])
}
if (timeline === 'notifications') {
params.push(['with_move', withMove])
}
params.push(['count', 20])
params.push(['limit', 20])
params.push(['with_muted', withMuted])
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
@ -844,12 +841,16 @@ const suggestions = ({ credentials }) => {
}).then((data) => data.json())
}
const markNotificationsAsSeen = ({ id, credentials }) => {
const markNotificationsAsSeen = ({ id, credentials, single = false }) => {
const body = new FormData()
body.append('latest_id', id)
if (single) {
body.append('id', id)
} else {
body.append('max_id', id)
}
return fetch(QVITTER_USER_NOTIFICATIONS_READ_URL, {
return fetch(NOTIFICATION_READ_URL, {
body,
headers: authHeaders(credentials),
method: 'POST'
@ -880,12 +881,20 @@ const fetchPoll = ({ pollId, credentials }) => {
)
}
const fetchFavoritedByUsers = ({ id }) => {
return promisedRequest({ url: MASTODON_STATUS_FAVORITEDBY_URL(id) }).then((users) => users.map(parseUser))
const fetchFavoritedByUsers = ({ id, credentials }) => {
return promisedRequest({
url: MASTODON_STATUS_FAVORITEDBY_URL(id),
method: 'GET',
credentials
}).then((users) => users.map(parseUser))
}
const fetchRebloggedByUsers = ({ id }) => {
return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser))
const fetchRebloggedByUsers = ({ id, credentials }) => {
return promisedRequest({
url: MASTODON_STATUS_REBLOGGEDBY_URL(id),
method: 'GET',
credentials
}).then((users) => users.map(parseUser))
}
const fetchEmojiReactions = ({ id, credentials }) => {
@ -1002,6 +1011,15 @@ const unmuteDomain = ({ domain, credentials }) => {
})
}
const dismissNotification = ({ credentials, id }) => {
return promisedRequest({
url: MASTODON_DISMISS_NOTIFICATION_URL(id),
method: 'POST',
payload: { id },
credentials
})
}
export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
return Object.entries({
...(credentials
@ -1157,6 +1175,7 @@ const apiService = {
denyUser,
suggestions,
markNotificationsAsSeen,
dismissNotification,
vote,
fetchPoll,
fetchFavoritedByUsers,

View file

@ -1,4 +1,5 @@
import escape from 'escape-html'
import { isStatusNotification } from '../notification_utils/notification_utils.js'
const qvitterStatusType = (status) => {
if (status.is_post_verb) {
@ -346,9 +347,7 @@ export const parseNotification = (data) => {
if (masto) {
output.type = mastoDict[data.type] || data.type
output.seen = data.pleroma.is_seen
output.status = output.type === 'follow' || output.type === 'move'
? null
: parseStatus(data.status)
output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null
output.action = output.status // TODO: Refactor, this is unneeded
output.target = output.type !== 'move'
? null

View file

@ -1,4 +1,4 @@
import { filter, sortBy } from 'lodash'
import { filter, sortBy, includes } from 'lodash'
export const notificationsFromStore = store => store.state.statuses.notifications.data
@ -7,10 +7,15 @@ export const visibleTypes = store => ([
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(_ => _))
const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reaction']
export const isStatusNotification = (type) => includes(statusNotifications, type)
const sortById = (a, b) => {
const seqA = Number(a.id)
const seqB = Number(b.id)

View file

@ -11,12 +11,9 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
const rootState = store.rootState || store.state
const timelineData = rootState.statuses.notifications
const hideMutedPosts = getters.mergedConfig.hideMutedPosts
const allowFollowingMove = rootState.users.currentUser.allow_following_move
args['withMuted'] = !hideMutedPosts
args['withMove'] = !allowFollowingMove
args['timeline'] = 'notifications'
if (older) {
if (timelineData.minId !== Number.POSITIVE_INFINITY) {

View file

@ -345,6 +345,18 @@
"css": "link",
"code": 59427,
"src": "fontawesome"
},
{
"uid": "8b80d36d4ef43889db10bc1f0dc9a862",
"css": "user",
"code": 59428,
"src": "fontawesome"
},
{
"uid": "12f4ece88e46abd864e40b35e05b11cd",
"css": "ok",
"code": 59431,
"src": "fontawesome"
}
]
}
}