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

This commit is contained in:
Your New SJW Waifu 2023-01-06 16:40:58 -06:00
commit b4ad6c5e63
21 changed files with 860 additions and 376 deletions

View File

@ -34,7 +34,6 @@
"escape-html": "1.0.3",
"js-cookie": "3.0.1",
"localforage": "1.10.0",
"lozad": "1.16.0",
"parse-link-header": "2.0.0",
"phoenix": "1.6.2",
"punycode.js": "2.1.0",
@ -46,6 +45,7 @@
"vue-i18n": "9.2.2",
"vue-router": "4.1.6",
"vue-template-compiler": "2.7.14",
"vue-virtual-scroller": "^2.0.0-beta.7",
"vuex": "4.1.0"
},
"devDependencies": {
@ -59,26 +59,26 @@
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
"@vue/babel-plugin-jsx": "1.1.1",
"@vue/compiler-sfc": "3.2.45",
"@vue/test-utils": "2.2.6",
"@vue/test-utils": "2.2.7",
"autoprefixer": "10.4.13",
"babel-loader": "8.3.0",
"babel-loader": "9.1.0",
"babel-plugin-lodash": "3.3.4",
"chai": "4.3.7",
"chalk": "1.1.3",
"chromedriver": "104.0.0",
"chromedriver": "108.0.0",
"connect-history-api-fallback": "2.0.0",
"copy-webpack-plugin": "11.0.0",
"cross-spawn": "7.0.3",
"css-loader": "6.7.3",
"css-minimizer-webpack-plugin": "4.2.2",
"custom-event-polyfill": "1.0.7",
"eslint": "8.30.0",
"eslint": "8.31.0",
"eslint-config-standard": "17.0.0",
"eslint-formatter-friendly": "7.0.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-n": "15.6.0",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-vue": "9.7.0",
"eslint-plugin-vue": "9.8.0",
"eslint-webpack-plugin": "3.2.0",
"eventsource-polyfill": "0.9.6",
"express": "4.18.2",
@ -97,27 +97,27 @@
"karma-spec-reporter": "0.0.36",
"karma-webpack": "5.0.0",
"lodash": "4.17.21",
"mini-css-extract-plugin": "2.6.1",
"mocha": "10.0.0",
"nightwatch": "2.3.3",
"mini-css-extract-plugin": "2.7.2",
"mocha": "10.2.0",
"nightwatch": "2.6.6",
"opn": "5.5.0",
"ora": "0.4.1",
"postcss": "8.4.20",
"postcss-loader": "7.0.2",
"sass": "1.55.0",
"sass-loader": "13.0.2",
"sass": "1.57.1",
"sass-loader": "13.2.0",
"selenium-server": "2.53.1",
"semver": "7.3.8",
"serviceworker-webpack5-plugin": "2.0.0",
"shelljs": "0.8.5",
"sinon": "14.0.2",
"sinon": "15.0.1",
"sinon-chai": "3.7.0",
"stylelint": "13.13.1",
"stylelint-config-standard": "20.0.0",
"stylelint-rscss": "0.4.0",
"vue-loader": "17.0.1",
"vue-style-loader": "4.1.3",
"webpack": "5.74.0",
"webpack": "5.75.0",
"webpack-dev-middleware": "3.7.3",
"webpack-hot-middleware": "2.25.3",
"webpack-merge": "0.20.0"

View File

@ -1,6 +1,8 @@
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import vClickOutside from 'click-outside-vue3'
import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
@ -397,6 +399,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
app.use(vClickOutside)
app.use(VBodyScrollLock)
app.use(VueVirtualScroller)
app.component('FAIcon', FontAwesomeIcon)
app.component('FALayers', FontAwesomeLayers)

View File

@ -27,6 +27,9 @@ const Announcement = {
...mapState({
currentUser: state => state.users.currentUser
}),
canEditAnnouncement () {
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
},
content () {
return this.announcement.content
},

View File

@ -45,14 +45,14 @@
{{ $t('announcements.mark_as_read_action') }}
</button>
<button
v-if="currentUser && currentUser.role === 'admin'"
v-if="canEditAnnouncement"
class="btn button-default"
@click="enterEditMode"
>
{{ $t('announcements.edit_action') }}
</button>
<button
v-if="currentUser && currentUser.role === 'admin'"
v-if="canEditAnnouncement"
class="btn button-default"
@click="deleteAnnouncement"
>

View File

@ -28,6 +28,9 @@ const AnnouncementsPage = {
}),
announcements () {
return this.$store.state.announcements.announcements
},
canPostAnnouncement () {
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
}
},
methods: {

View File

@ -7,7 +7,7 @@
</div>
<div class="panel-body">
<section
v-if="currentUser && currentUser.role === 'admin'"
v-if="canPostAnnouncement"
>
<div class="post-form">
<div class="heading">

View File

@ -3,7 +3,6 @@ import Checkbox from '../checkbox/checkbox.vue'
import Popover from 'src/components/popover/popover.vue'
import StillImage from '../still-image/still-image.vue'
import { ensureFinalFallback } from '../../i18n/languages.js'
import lozad from 'lozad'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faBoxOpen,
@ -19,7 +18,7 @@ import {
faCode,
faFlag
} from '@fortawesome/free-solid-svg-icons'
import { debounce, trim } from 'lodash'
import { debounce, trim, chunk } from 'lodash'
library.add(
faBoxOpen,
@ -82,6 +81,17 @@ const filterByKeyword = (list, keyword = '', languages, nameLocalizer) => {
return orderedEmojiList.flat()
}
const getOffset = (elem) => {
const style = elem.style.transform
const res = /translateY\((\d+)px\)/.exec(style)
if (!res) { return 0 }
return res[1]
}
const toHeaderId = id => {
return id.replace(/^row-\d+-/, '')
}
const EmojiPicker = {
props: {
enableStickerPicker: {
@ -102,7 +112,8 @@ const EmojiPicker = {
contentLoaded: false,
groupRefs: {},
emojiRefs: {},
filteredEmojiGroups: []
filteredEmojiGroups: [],
width: 0
}
},
components: {
@ -125,9 +136,6 @@ const EmojiPicker = {
setGroupRef (name) {
return el => { this.groupRefs[name] = el }
},
setEmojiRef (name) {
return el => { this.emojiRefs[name] = el }
},
onPopoverShown () {
this.$emit('show')
},
@ -147,18 +155,21 @@ const EmojiPicker = {
}
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
},
onScroll (e) {
const target = (e && e.target) || this.$refs['emoji-groups']
this.updateScrolledClass(target)
this.scrolledGroup(target)
onScroll (startIndex, endIndex, visibleStartIndex, visibleEndIndex) {
const target = this.$refs['emoji-groups'].$el
this.scrolledGroup(target, visibleStartIndex, visibleEndIndex)
},
scrolledGroup (target) {
scrolledGroup (target, start, end) {
const top = target.scrollTop + 5
this.$nextTick(() => {
this.allEmojiGroups.forEach(group => {
this.emojiItems.slice(start, end + 1).forEach(group => {
const headerId = toHeaderId(group.id)
const ref = this.groupRefs['group-' + group.id]
if (ref && ref.offsetTop <= top) {
this.activeGroup = group.id
if (!ref) { return }
const elem = ref.$el.parentElement
if (!elem) { return }
if (elem && getOffset(elem) <= top) {
this.activeGroup = headerId
}
})
this.scrollHeader()
@ -181,14 +192,10 @@ const EmojiPicker = {
setScroll(right + margin - headerCont.clientWidth)
}
},
highlight (key) {
const ref = this.groupRefs['group-' + key]
const top = ref.offsetTop
highlight (groupId) {
this.setShowStickers(false)
this.activeGroup = key
this.$nextTick(() => {
this.$refs['emoji-groups'].scrollTop = top + 1
})
const indexInList = this.emojiItems.findIndex(k => k.id === groupId)
this.$refs['emoji-groups'].scrollToItem(indexInList)
},
updateScrolledClass (target) {
if (target.scrollTop <= 5) {
@ -208,43 +215,13 @@ const EmojiPicker = {
filterByKeyword (list, keyword) {
return filterByKeyword(list, keyword, this.languages, this.maybeLocalizedEmojiName)
},
initializeLazyLoad () {
this.destroyLazyLoad()
this.$nextTick(() => {
this.$lozad = lozad('.still-image.emoji-picker-emoji', {
load: el => {
const name = el.getAttribute('data-emoji-name')
const vn = this.emojiRefs[name]
if (!vn) {
return
}
vn.loadLazy()
}
})
this.$lozad.observe()
})
},
waitForDomAndInitializeLazyLoad () {
this.$nextTick(() => this.initializeLazyLoad())
},
destroyLazyLoad () {
if (this.$lozad) {
if (this.$lozad.observer) {
this.$lozad.observer.disconnect()
}
if (this.$lozad.mutationObserver) {
this.$lozad.mutationObserver.disconnect()
}
}
},
onShowing () {
const oldContentLoaded = this.contentLoaded
this.recalculateItemPerRow()
this.$nextTick(() => {
this.$refs.search.focus()
})
this.contentLoaded = true
this.waitForDomAndInitializeLazyLoad()
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
if (!oldContentLoaded) {
this.$nextTick(() => {
@ -261,6 +238,14 @@ const EmojiPicker = {
emojis: this.filterByKeyword(group.emojis, trim(this.keyword))
}))
.filter(group => group.emojis.length > 0)
},
recalculateItemPerRow () {
this.$nextTick(() => {
if (!this.$refs['emoji-groups']) {
return
}
this.width = this.$refs['emoji-groups'].$el.offsetWidth
})
}
},
watch: {
@ -269,14 +254,22 @@ const EmojiPicker = {
this.debouncedHandleKeywordChange()
},
allCustomGroups () {
this.waitForDomAndInitializeLazyLoad()
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
}
},
destroyed () {
this.destroyLazyLoad()
},
computed: {
minItemSize () {
return this.emojiHeight
},
emojiHeight () {
return 32 + 4
},
emojiWidth () {
return 32 + 4
},
itemPerRow () {
return this.width ? Math.floor(this.width / this.emojiWidth - 1) : 6
},
activeGroupView () {
return this.showingStickers ? '' : this.activeGroup
},
@ -287,7 +280,11 @@ const EmojiPicker = {
return 0
},
allCustomGroups () {
return this.$store.getters.groupedCustomEmojis
const emojis = this.$store.getters.groupedCustomEmojis
if (emojis.unpacked) {
emojis.unpacked.text = this.$t('emoji.unpacked')
}
return emojis
},
defaultGroup () {
return Object.keys(this.allCustomGroups)[0]
@ -310,10 +307,20 @@ const EmojiPicker = {
},
debouncedHandleKeywordChange () {
return debounce(() => {
this.waitForDomAndInitializeLazyLoad()
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
}, 500)
},
emojiItems () {
return this.filteredEmojiGroups.map(group =>
chunk(group.emojis, this.itemPerRow)
.map((items, index) => ({
...group,
id: index === 0 ? group.id : `row-${index}-${group.id}`,
emojis: items,
isFirstRow: index === 0
})))
.reduce((a, c) => a.concat(c), [])
},
languages () {
return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
},

View File

@ -74,6 +74,7 @@ $emoji-picker-emoji-size: 32px;
}
.emoji-groups {
height: 100%;
min-height: 200px;
}

View File

@ -74,45 +74,56 @@
@input="$event.target.composing = false"
>
</div>
<div
<DynamicScroller
ref="emoji-groups"
class="emoji-groups"
:class="groupsScrolledClass"
@scroll="onScroll"
:min-item-size="minItemSize"
:items="emojiItems"
:emit-update="true"
@update="onScroll"
@visible="recalculateItemPerRow"
>
<div
v-for="group in filteredEmojiGroups"
:key="group.id"
class="emoji-group"
>
<h6
<template #default="{ item: group, index, active }">
<DynamicScrollerItem
:ref="setGroupRef('group-' + group.id)"
class="emoji-group-title"
:item="group"
:active="active"
:data-index="index"
:size-dependencies="[group.emojis.length]"
>
{{ group.text }}
</h6>
<span
v-for="emoji in group.emojis"
:key="group.id + emoji.displayText"
:title="maybeLocalizedEmojiName(emoji)"
class="emoji-item"
@click.stop.prevent="onEmoji(emoji)"
>
<span
v-if="!emoji.imageUrl"
class="emoji-picker-emoji -unicode"
>{{ emoji.replacement }}</span>
<still-image
v-else
:ref="setEmojiRef(group.id + emoji.displayText)"
class="emoji-picker-emoji -custom"
:data-src="emoji.imageUrl"
:data-emoji-name="group.id + emoji.displayText"
/>
</span>
<span :ref="setGroupRef('group-end-' + group.id)" />
</div>
</div>
<div
class="emoji-group"
>
<h6
v-if="group.isFirstRow"
class="emoji-group-title"
>
{{ group.text }}
</h6>
<span
v-for="emoji in group.emojis"
:key="group.id + emoji.displayText"
:title="maybeLocalizedEmojiName(emoji)"
class="emoji-item"
@click.stop.prevent="onEmoji(emoji)"
>
<span
v-if="!emoji.imageUrl"
class="emoji-picker-emoji -unicode"
>{{ emoji.replacement }}</span>
<still-image
v-else
class="emoji-picker-emoji -custom"
loading="lazy"
:src="emoji.imageUrl"
:data-emoji-name="group.id + emoji.displayText"
/>
</span>
</div>
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div class="keep-open">
<Checkbox v-model="keepOpen">
{{ $t('emoji.keep_open') }}

View File

@ -95,10 +95,10 @@ const ListsNew = {
return this.addedUserIds.has(user.id)
},
addUser (user) {
this.$store.dispatch('addListAccount', { accountId: this.user.id, listId: this.id })
this.$store.dispatch('addListAccount', { accountId: user.id, listId: this.id })
},
removeUser (userId) {
this.$store.dispatch('removeListAccount', { accountId: this.user.id, listId: this.id })
this.$store.dispatch('removeListAccount', { accountId: userId, listId: this.id })
},
onSearchLoading (results) {
this.searchLoading = true

View File

@ -1,7 +1,11 @@
@import '../../variables';
.RichContent {
blockquote {
margin: 0.2em 0 0.2em 2em;
margin: 0.2em 0 0.2em 0.2em;
font-style: italic;
border-left: 0.2em solid var(--faint, $fallback--faint);
padding-left: 1em;
}
pre {

View File

@ -77,6 +77,16 @@
>
{{ $t('settings.download_backup') }}
</a>
<span
v-else-if="backup.state === 'running'"
>
{{ $tc('settings.backup_running', backup.processed_number, { number: backup.processed_number }) }}
</span>
<span
v-else-if="backup.state === 'failed'"
>
{{ $t('settings.backup_failed') }}
</span>
<span
v-else
>

View File

@ -8,7 +8,8 @@ const StillImage = {
'alt',
'height',
'width',
'dataSrc'
'dataSrc',
'loading'
],
data () {
return {

View File

@ -17,6 +17,7 @@
:data-src="dataSrc"
:src="realSrc"
:referrerpolicy="referrerpolicy"
:loading="loading"
@load="onLoad"
@error="onError"
>

View File

@ -225,6 +225,7 @@
"search_emoji": "Search for an emoji",
"add_emoji": "Insert emoji",
"custom": "Custom emoji",
"unpacked": "Unpacked emoji",
"unicode": "Unicode emoji",
"unicode_groups": {
"activities": "Activities",
@ -390,6 +391,8 @@
"account_backup_table_head": "Backup",
"download_backup": "Download",
"backup_not_ready": "This backup is not ready yet.",
"backup_running": "This backup is in progress, processed {number} record. | This backup is in progress, processed {number} records.",
"backup_failed": "This backup has failed.",
"remove_backup": "Remove",
"list_backups_error": "Error fetching backup list: {error}",
"add_backup": "Create a new backup",

View File

@ -96,7 +96,7 @@
"friend_requests": "Petoj pri abono",
"mentions": "Mencioj",
"dms": "Rektaj mesaĝoj",
"public_tl": "Loka historio",
"public_tl": "Noda historio",
"timeline": "Historio",
"twkn": "Federa historio",
"user_search": "Serĉi uzantojn",
@ -112,16 +112,19 @@
"edit_pinned": "Redakti fiksitajn erojn",
"lists": "Listoj",
"edit_nav_mobile": "Adapti navigan breton",
"edit_finish": "Fini redakton"
"edit_finish": "Fini redakton",
"mobile_notifications": "Malfermi sciigojn (estas nelegitaj)",
"mobile_notifications_close": "Fermi sciigojn",
"announcements": "Anoncoj"
},
"notifications": {
"broken_favorite": "Nekonata stato, serĉante ĝin…",
"favorited_you": "ŝatis vian staton",
"broken_favorite": "Nekonata afiŝo, serĉante ĝin…",
"favorited_you": "ŝatis vian afiŝon",
"followed_you": "ekabonis vin",
"load_older": "Enlegi pli malnovajn sciigojn",
"notifications": "Sciigoj",
"read": "Legite!",
"repeated_you": "ripetis vian staton",
"repeated_you": "ripetis vian afiŝon",
"no_more_notifications": "Neniuj pliaj sciigoj",
"reacted_with": "reagis per {0}",
"migrated_to": "migris al",
@ -131,7 +134,7 @@
"poll_ended": "enketo finiĝis"
},
"post_status": {
"new_status": "Afiŝi novan staton",
"new_status": "Afiŝi",
"account_not_locked_warning": "Via konto ne estas {0}. Iu ajn povas vin aboni por vidi eĉ viajn afiŝoj nur por abonantoj.",
"account_not_locked_warning_link": "ŝlosita",
"attachments_sensitive": "Marki kunsendaĵojn konsternaj",
@ -152,12 +155,12 @@
"unlisted": "Nelistigita ne afiŝi al publikaj historioj"
},
"scope_notice": {
"unlisted": "Ĉi tiu afiŝo ne estos videbla en la Loka historio kaj la Federa historio",
"unlisted": "Ĉi tiu afiŝo ne estos videbla en la Noda kaj la Federa historioj",
"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",
"empty_status_error": "Ne povas fari malplenan afiŝon 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.",
@ -166,7 +169,7 @@
"post": "Afiŝo",
"edit_remote_warning": "Aliaj foraj nodoj eble ne subtenas redaktadon, kaj ne povos ricevi pli novan version de via afiŝo.",
"edit_unsupported_warning": "Pleroma ne subtenas redaktadon de mencioj aŭ enketoj.",
"edit_status": "Stato de redakto"
"edit_status": "Redakti afiŝon"
},
"registration": {
"bio": "Priskribo",
@ -228,7 +231,7 @@
"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; skribu po unu linie",
"filtering_explanation": "Ĉiuj afiŝoj 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",
@ -245,7 +248,7 @@
"use_one_click_nsfw": "Malfermi konsternajn kunsendaĵojn per nur unu klako",
"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",
"hide_filtered_statuses": "Kaŝi ĉiujn filtritajn afiŝojn",
"import_followers_from_a_csv_file": "Enporti abonojn el CSV-dosiero",
"import_theme": "Enlegi antaŭagordojn",
"inputRadius": "Enigaj kampoj",
@ -278,7 +281,7 @@
"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 reguligisto en mia profilo",
"nsfw_clickthrough": "Ŝalti traklakan kaŝadon de kunsendaĵoj kaj antaŭmontroj de ligiloj por konsternaj statoj",
"nsfw_clickthrough": "Ŝalti traklakan kaŝadon de kunsendaĵoj kaj antaŭmontroj de ligiloj por konsternaj afiŝoj",
"oauth_tokens": "Pecoj de OAuth",
"token": "Peco",
"refresh_token": "Aktualiga peco",
@ -627,14 +630,14 @@
"word_filter_and_more": "Vortofiltrado kaj pli…",
"mute_bot_posts": "Silentigi afiŝojn de robotoj",
"hide_bot_indication": "Kaŝi markon de roboteco en afiŝoj",
"hide_wordfiltered_statuses": "Kaŝi vorte filtritajn statojn",
"hide_wordfiltered_statuses": "Kaŝi vorte filtritajn afiŝojn",
"hide_muted_threads": "Kaŝi silentigitajn fadenojn",
"account_privacy": "Privateco",
"user_profiles": "Profiloj de uzantoj",
"hide_favorites_description": "Ne montri liston de miaj ŝatatoj (oni tamen sciiĝas)",
"conversation_display_tree": "Arba stilo",
"conversation_display_tree_quick": "Arba vido",
"show_scrollbars": "Montri rulumajn bretojn de flankaj kolumnoj",
"show_scrollbars": "Montri rulumskalojn de flankaj kolumnoj",
"third_column_mode_none": "Neniam montri trian kolumnon",
"third_column_mode_notifications": "Kolumno de sciigoj",
"columns": "Kolumnoj",
@ -644,9 +647,9 @@
"column_sizes_notifs": "Sciigoj",
"tree_advanced": "Permesi pli flekseblan navigadon en arba vido",
"conversation_display_linear": "Linia stilo",
"conversation_other_replies_button": "Montri la butonon «aliaj respondoj»",
"conversation_other_replies_button_below": "Sub statoj",
"conversation_other_replies_button_inside": "En statoj",
"conversation_other_replies_button": "Montri la butonon pri «aliaj respondoj»",
"conversation_other_replies_button_below": "Sub afiŝoj",
"conversation_other_replies_button_inside": "En afiŝoj",
"max_depth_in_thread": "Maksimuma nombro de niveloj implicite montrataj en fadeno",
"auto_update": "Montri novajn afiŝojn memage",
"use_at_icon": "Montri simbolon {'@'} kiel bildon anstataŭ teksto",
@ -662,19 +665,20 @@
"user_popover_avatar_action_open": "Malfermi la profilon",
"user_popover_avatar_overlay": "Aperigi ŝprucaĵon pri uzanto sur profilbildo",
"show_yous": "Montri la markon «(Vi)»",
"user_popover_avatar_action_zoom": "Zomi la profilbildon"
"user_popover_avatar_action_zoom": "Zomi la profilbildon",
"third_column_mode": "Kun sufiĉo da spaco, montri trian kolumnon kun"
},
"timeline": {
"collapse": "Maletendi",
"conversation": "Interparolo",
"error_fetching": "Eraris ĝisdatigo",
"load_older": "Montri pli malnovajn statojn",
"load_older": "Montri pli malnovajn afiŝojn",
"no_retweet_hint": "Afiŝo estas markita kiel rekta aŭ nur por abonantoj, kaj ne eblas ĝin ripeti",
"repeated": "ripetis",
"show_new": "Montri novajn",
"up_to_date": "Ĝisdata",
"no_more_statuses": "Neniuj pliaj statoj",
"no_statuses": "Neniuj statoj",
"no_more_statuses": "Neniuj pliaj afiŝoj",
"no_statuses": "Neniuj afiŝoj",
"reload": "Enlegi ree",
"error": "Eraris akirado de historio: {0}",
"socket_reconnected": "Realtempa konekto fariĝis",
@ -700,7 +704,7 @@
"muted": "Silentigita",
"per_day": "tage",
"remote_follow": "Fore aboni",
"statuses": "Statoj",
"statuses": "Afiŝoj",
"unblock": "Malbloki",
"unblock_progress": "Malblokante…",
"block_progress": "Blokante…",
@ -744,7 +748,12 @@
"edit_profile": "Redakti profilon",
"deactivated": "Malaktiva",
"follow_cancel": "Nuligi peton",
"remove_follower": "Forigi abonanton"
"remove_follower": "Forigi abonanton",
"note": "Noto",
"note_blank": "(Neniu)",
"edit_note_apply": "Apliki",
"edit_note_cancel": "Nuligi",
"edit_note": "Redakti noton"
},
"user_profile": {
"timeline_title": "Historio de uzanto",
@ -764,7 +773,9 @@
"bookmark": "Legosigno",
"reject_follow_request": "Rifuzi abonpeton",
"accept_follow_request": "Akcepti abonpeton",
"add_reaction": "Aldoni reagon"
"add_reaction": "Aldoni reagon",
"toggle_expand": "Etendi aŭ maletendi sciigon por montri plenan afiŝon",
"toggle_mute": "Etendi aŭ maletendi afiŝon por montri silentigitan enhavon"
},
"upload": {
"error": {
@ -893,19 +904,19 @@
"show_full_subject": "Montri plenan temon",
"thread_muted_and_words": ", enhavas vortojn:",
"thread_muted": "Fadeno silentigita",
"copy_link": "Kopii ligilon al stato",
"status_unavailable": "Stato ne estas disponebla",
"copy_link": "Kopii ligilon al afiŝo",
"status_unavailable": "Afiŝo ne estas disponebla",
"unmute_conversation": "Malsilentigi interparolon",
"mute_conversation": "Silentigi interparolon",
"replies_list": "Respondoj:",
"reply_to": "Responde al",
"delete_confirm": "Ĉu vi certe volas forigi ĉi tiun staton?",
"delete_confirm": "Ĉu vi certe volas forigi ĉi tiun afiŝon?",
"unbookmark": "Senlegosigni",
"bookmark": "Legosigni",
"pinned": "Fiksita",
"unpin": "Malfiksi de profilo",
"pin": "Fiksi al profilo",
"delete": "Forigi staton",
"delete": "Forigi afiŝon",
"repeats": "Ripetoj",
"favorites": "Ŝatoj",
"status_deleted": "Ĉi tiu afiŝo foriĝis",
@ -939,7 +950,8 @@
"ancestor_follow_with_icon": "{icon} {text}",
"show_all_conversation_with_icon": "{icon} {text}",
"show_only_conversation_under_this": "Montri nur respondojn al ĉi tiu afiŝo",
"status_history": "Historio de afiŝo"
"status_history": "Historio de afiŝo",
"open_gallery": "Malfermi galerion"
},
"time": {
"years_short": "{0}j",
@ -996,7 +1008,9 @@
"no_results": "Neniuj rezultoj",
"people_talking": "{count} personoj parolas",
"person_talking": "{count} persono parolas",
"hashtags": "Kradvortoj"
"hashtags": "Kradvortoj",
"no_more_results": "Neniuj pliaj rezultoj",
"load_more": "Enlegi pliajn rezultojn"
},
"display_date": {
"today": "Hodiaŭ"
@ -1047,9 +1061,9 @@
"report": {
"reporter": "Raportinto:",
"reported_user": "Raportito:",
"reported_statuses": "Raportitaj statoj:",
"reported_statuses": "Raportitaj afiŝoj:",
"notes": "Notoj:",
"state": "Stato:",
"state": "Afiŝo:",
"state_open": "Malfermita",
"state_closed": "Fermita",
"state_resolved": "Solvita"

View File

@ -32,7 +32,13 @@
"private": "Лише читачі",
"public": "Публічне",
"unlisted": "Непублічне"
}
},
"undo": "Відмінити",
"yes": "Так",
"no": "Ні",
"unpin": "Відкріпити",
"scroll_to_top": "Вгору",
"pin": "Прикріпити"
},
"finder": {
"error_fetching_user": "Користувача не знайдено",
@ -48,7 +54,7 @@
"media_proxy": "Посередник медіа-даних",
"text_limit": "Ліміт символів",
"upload_limit": "Обмеження завантажень",
"shout": "Оголошення"
"shout": "Гучномовець"
},
"exporter": {
"processing": "Опрацьовую, скоро ви зможете завантажити файл",
@ -61,7 +67,7 @@
"mute": "Ігнорувати"
},
"shoutbox": {
"title": "Оголошення"
"title": "Гучномовець"
},
"about": {
"staff": "Адміністрація",
@ -81,7 +87,8 @@
"accept_desc": "Поточний інстанс приймає повідомлення тільки з перелічених інстансів:",
"simple_policies": "Правила поточного інстансу",
"reason": "Причина",
"not_applicable": "н/в"
"not_applicable": "н/в",
"instance": "Інстанс"
},
"mrf_policies_desc": "Правила MRF розповсюджуються на даний інстанс. Наступні правила активні:",
"mrf_policies": "Активувати правила MRF (модуль переписування повідомлень)",
@ -153,7 +160,8 @@
"favorited_you": "вподобав(-ла) ваш допис",
"broken_favorite": "Невідомий допис, шукаю його…",
"error": "Помилка при оновленні сповіщень: {0}",
"poll_ended": "опитування закінчено"
"poll_ended": "опитування закінчено",
"submitted_report": "подав скаргу"
},
"nav": {
"chats": "Чати",
@ -174,7 +182,14 @@
"back": "Назад",
"administration": "Адміністрування",
"home_timeline": "Домашня стрічка",
"lists": "Списки"
"lists": "Списки",
"edit_pinned": "Редагувати прикріплене",
"edit_finish": "Завершити редагування",
"mobile_sidebar": "Ввімкнути бокову панель",
"mobile_notifications": "Відкрити сповіщення (є непрочитані)",
"mobile_notifications_close": "Закрити сповіщення",
"edit_nav_mobile": "Редагувати панель навігації",
"announcements": "Анонси"
},
"media_modal": {
"next": "Наступна",
@ -221,7 +236,8 @@
"follows": "Нові підписки",
"favs_repeats": "Поширення та вподобайки",
"moves": "Міграції користувачів",
"emoji_reactions": "Емоджі реакції"
"emoji_reactions": "Емоджі реакції",
"reports": "Скарги"
},
"errors": {
"storage_unavailable": "Pleroma не змогла отримати доступ до сховища браузеру. Ваша сесія та налаштування не будуть збережені, це може спричинити непередбачувані проблеми. Спробуйте увімкнути cookie."
@ -235,7 +251,19 @@
"emoji": "Емодзі",
"load_all": "Всі {emojiAmount} эмодзі завантажуються",
"load_all_hint": "Завантажені перші {saneAmount} емодзі, завантаження всіх емодзі може призвести до проблем з продуктивністю.",
"unicode": "Стандартні емодзі"
"unicode": "Стандартні емодзі",
"regional_indicator": "Регіональний індикатор {letter}",
"unicode_groups": {
"animals-and-nature": "Тварини і Рослини",
"flags": "Прапори",
"food-and-drink": "Їжа та Напої",
"objects": "Об'єкти",
"people-and-body": "Люди та Тіло",
"smileys-and-emotion": "Смайлики та Емотікони",
"activities": "Активності",
"symbols": "Символи",
"travel-and-places": "Подорожі та Місця"
}
},
"post_status": {
"content_type": {
@ -269,7 +297,10 @@
"preview_empty": "Пустий",
"media_description_error": "Не вдалось оновити медіа, спробуйте ще раз",
"media_description": "Опис медіа",
"post": "Опублікувати"
"post": "Опублікувати",
"edit_unsupported_warning": "Pleroma не підтримує редагування згадувань чи голосувань.",
"edit_status": "Редагувати допис",
"edit_remote_warning": "Інші віддалені інстанси можуть не підтримувати редагування та вони можуть не отримати актуальну версію допису."
},
"settings": {
"blocks_imported": "Блокування імпортовані! Їх обробка триватиме певний час.",
@ -654,7 +685,7 @@
"backup_restore": "Резервне копіювання налаштувань"
},
"right_sidebar": "Показувати бокову панель справа",
"hide_shoutbox": "Приховати оголошення інстансу",
"hide_shoutbox": "Приховати гучномовець",
"setting_server_side": "Цей параметр прив’язаний до вашого профілю та впливає на всі сеанси та клієнти",
"lists_navigation": "Показувати списки в навігації",
"account_backup": "Резервне копіювання облікового запису",
@ -682,7 +713,33 @@
"move_account": "Перемістити обліковий запис",
"move_account_target": "Цільовий обліковий запис (напр. {example})",
"moved_account": "Обліковий запис переміщено.",
"move_account_error": "Помилка під час переміщення облікового запису: {error}"
"move_account_error": "Помилка під час переміщення облікового запису: {error}",
"word_filter_and_more": "Фільтр слів та більше...",
"hide_bot_indication": "Сховати позначку бот у дописах",
"navbar_column_stretch": "Розтягнути панель навігації на ширину колонок",
"hide_wordfiltered_statuses": "Ховати фільтровані статуси",
"hide_muted_threads": "Ховати приглушені треди",
"posts": "Дописи",
"account_privacy": "Безпека",
"conversation_display": "Стиль відображення розмови",
"conversation_display_tree": "Деревоподібне",
"conversation_display_tree_quick": "Вигляд дерева",
"disable_sticky_headers": "Не закріплювати заголовок колонки зверху на сторінці",
"third_column_mode_none": "Не показувати третю колонку взагалі",
"third_column_mode_notifications": "Колонка сповіщень",
"columns": "Колонки",
"auto_update": "Автоматично показувати нові дописи",
"use_websockets": "Використовувати вебсокети (Оновлення в реальному часі)",
"use_at_icon": "Показувати {'@'} символ як іконку замість тексту",
"mute_bot_posts": "Приховати дописи ботів",
"always_show_post_button": "Завжди показувати плаваючу кнопку «Новий Допис»",
"hide_favorites_description": "Не показувати список моїх вподобань (люди все одно отримують сповіщення)",
"third_column_mode": "Коли достатньо місця, показувати третю колонку, що містить",
"user_popover_avatar_action_open": "Відкрити профіль",
"wordfilter": "Фільтр слів",
"mention_links": "Посилання для згадування",
"user_profiles": "Профілі користувачів",
"notification_visibility_polls": "Закінчення опитувань, в яких ви проголосували"
},
"selectable_list": {
"select_all": "Вибрати все"
@ -781,7 +838,23 @@
"day": "{0} день",
"seconds_short": "{0}с",
"seconds": "{0} секунди",
"in_future": "через {0}"
"in_future": "через {0}",
"unit": {
"months": "{0} місяць | {0} місяців",
"minutes": "{0} хвилина | {0} хвилин",
"hours_short": "{0}год",
"minutes_short": "{0}хв",
"months_short": "{0}міс",
"seconds": "{0} секунда | {0} секунд",
"seconds_short": "{0}с",
"weeks_short": "{0}тижд",
"years": "{0} рік | {0} років",
"years_short": "{0}р.",
"days": "{0} день | {0} днів",
"days_short": "{0}д",
"hours": "{0} година | {0} годин",
"weeks": "{0} тиждень | {0} тижнів"
}
},
"search": {
"no_results": "Немає результатів",
@ -850,7 +923,9 @@
"disabled": "Не виділяти"
},
"bot": "Бот",
"edit_profile": "Редагувати профіль"
"edit_profile": "Редагувати профіль",
"deactivated": "Деактивований",
"follow_cancel": "Скасувати запит"
},
"status": {
"copy_link": "Скопіювати посилання на допис",
@ -877,7 +952,16 @@
"thread_muted": "Нитка заглушена",
"unmute_conversation": "Припинити глушити розмову",
"external_source": "Зовнішнє джерело",
"expand": "Розгорнути"
"expand": "Розгорнути",
"edit": "Редагувати допис",
"edited_at": "(змінено: {time})",
"thread_follow_with_icon": "{icon} {text}",
"ancestor_follow_with_icon": "{icon} {text}",
"show_all_conversation_with_icon": "{icon} {text}",
"plus_more": "+{number} більше",
"thread_show_full_with_icon": "{icon} {text}",
"show_only_conversation_under_this": "Показати всі відповіді на цей допис",
"status_history": "Історія змін"
},
"timeline": {
"no_more_statuses": "Більше немає дописів",
@ -913,6 +997,30 @@
"state": "Статус:",
"state_open": "відкритий",
"state_closed": "закритий",
"state_resolved": "вирішений"
"state_resolved": "вирішений",
"reported_statuses": "Дописи, на які подано скаргу:",
"reporter": "Позивач:",
"reported_user": "Відповідач:"
},
"announcements": {
"delete_action": "Видалити",
"page_header": "Анонси",
"title": "Анонси",
"mark_as_read_action": "Позначити як прочитане",
"post_form_header": "Розмістити оголошення",
"post_placeholder": "Введіть текст вашого оголошення тут...",
"post_action": "Пост",
"post_error": "Помилка: {error}",
"close_error": "Закрити",
"start_time_prompt": "Початок: ",
"end_time_prompt": "Кінець: ",
"all_day_prompt": "Це захід на цілий день",
"published_time_display": "Опубліковано в {time}",
"start_time_display": "Початок о {time}",
"end_time_display": "Кінець о {time}",
"edit_action": "Редагувати",
"submit_edit_action": "Надіслати",
"cancel_edit_action": "Скасувати",
"inactive_message": "Це оголошення неактивне"
}
}

View File

@ -49,7 +49,7 @@ const announcements = {
}
const currentUser = store.rootState.users.currentUser
const isAdmin = currentUser && currentUser.role === 'admin'
const isAdmin = currentUser && currentUser.privileges.includes('announcements_manage_announcements')
const getAnnouncements = async () => {
if (!isAdmin) {

View File

@ -181,15 +181,28 @@ const instance = {
},
groupedCustomEmojis (state) {
const packsOf = emoji => {
return emoji.tags
const packs = emoji.tags
.filter(k => k.startsWith('pack:'))
.map(k => k.slice(5)) // remove 'pack:' prefix
.map(k => {
const packName = k.slice(5) // remove 'pack:' prefix
return {
id: `custom-${packName}`,
text: packName
}
})
if (!packs.length) {
return [{
id: 'unpacked'
}]
} else {
return packs
}
}
return state.customEmoji
.reduce((res, emoji) => {
packsOf(emoji).forEach(packName => {
const packId = `custom-${packName}`
packsOf(emoji).forEach(({ id: packId, text: packName }) => {
if (!res[packId]) {
res[packId] = ({
id: packId,
@ -290,9 +303,22 @@ const instance = {
const lb = b.toLowerCase()
return la > lb ? 1 : (la < lb ? -1 : 0)
}
const noPackLast = (a, b) => {
const aNull = a === ''
const bNull = b === ''
if (aNull === bNull) {
return 0
} else if (aNull && !bNull) {
return 1
} else {
return -1
}
}
const byPackThenByName = (a, b) => {
const packOf = emoji => (emoji.tags.filter(k => k.startsWith('pack:'))[0] || '').slice(5)
return caseInsensitiveStrCmp(packOf(a), packOf(b)) || caseInsensitiveStrCmp(a.displayText, b.displayText)
const packOfA = packOf(a)
const packOfB = packOf(b)
return noPackLast(packOfA, packOfB) || caseInsensitiveStrCmp(packOfA, packOfB) || caseInsensitiveStrCmp(a.displayText, b.displayText)
}
const emoji = Object.entries(values).map(([key, value]) => {

View File

@ -164,7 +164,7 @@ const updateNotificationSettings = ({ credentials, settings }) => {
form.append(key, value)
})
return fetch(NOTIFICATION_SETTINGS_URL, {
return fetch(`${NOTIFICATION_SETTINGS_URL}?${new URLSearchParams(settings)}`, {
headers: authHeaders(credentials),
method: 'PUT',
body: form

711
yarn.lock

File diff suppressed because it is too large Load Diff