Merge branch 'develop' into fedi-absturztau-be

This commit is contained in:
Absturztaube 2021-03-09 17:14:31 +01:00
commit 6ef591bdcc
71 changed files with 1211 additions and 300 deletions

1
.mailmap Normal file
View file

@ -0,0 +1 @@
rinpatch <rin@patch.cx> <rinpatch@sdf.org>

View file

@ -4,12 +4,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]
### Added
- Added a quick settings to timeline header for easier access
- Added option to mark posts as sensitive by default
## [2.3.0] - 2021-03-01
### Fixed
- Button to remove uploaded media in post status form is now properly placed and sized.
- Fixed shoutbox not working in mobile layout
- Fixed missing highlighted border in expanded conversations again
- Fixed some UI jumpiness when opening images particularly in chat view
- Fixed chat unread badge looking weird
- Fixed punycode names not working properly
- Fixed notifications crashing on an invalid notification
### Changed
- Display 'people voted' instead of 'votes' for multi-choice polls
- Optimized chat to not get horrible performance after keeping the same chat open for a long time
- When opening emoji picker or react picker, it automatically focuses the search field
- Language picker now uses native language names
### Added
- Added reason field for registration when approval is required
- Group staff members by role in the About page
## [2.2.3] - 2021-01-18
### Added
@ -21,7 +38,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed
- Don't filter own posts when they hit your wordfilter
- Language picker now uses native language names
## [2.2.2] - 2020-12-22
@ -31,7 +47,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Added some missing unicode emoji
- Added the upload limit to the Features panel in the About page
- Support for solid color wallpaper, instance doesn't have to define a wallpaper anymore
- Group staff members by role in the About page
### Fixed
- Fixed the occasional bug where screen would scroll 1px when typing into a reply form

View file

@ -34,7 +34,6 @@
"punycode.js": "^2.1.0",
"v-click-outside": "^2.1.1",
"vue": "^2.6.11",
"vue-chat-scroll": "^1.2.1",
"vue-i18n": "^7.3.2",
"vue-router": "^3.0.1",
"vue-template-compiler": "^2.6.11",
@ -103,7 +102,7 @@
"selenium-server": "2.53.1",
"semver": "^5.3.0",
"serviceworker-webpack-plugin": "^1.0.0",
"shelljs": "^0.7.4",
"shelljs": "^0.8.4",
"sinon": "^2.1.0",
"sinon-chai": "^2.8.0",
"stylelint": "^13.6.1",

View file

@ -586,6 +586,7 @@ nav {
color: var(--faint, $fallback--faint);
box-shadow: 0px 0px 4px rgba(0,0,0,.6);
box-shadow: var(--topBarShadow);
box-sizing: border-box;
}
.fade-enter-active, .fade-leave-active {
@ -878,6 +879,11 @@ nav {
overflow: hidden;
height: 100%;
// Get rid of scrollbar on body as scrolling happens on different element
body {
overflow: hidden;
}
// Ensures the fixed position of the mobile browser bars on scroll up / down events.
// Prevents the mobile browser bars from overlapping or hiding the message posting form.
@media all and (max-width: 800px) {

View file

@ -51,6 +51,7 @@ const getInstanceConfig = async ({ store }) => {
const vapidPublicKey = data.pleroma.vapid_public_key
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
if (vapidPublicKey) {
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })

View file

@ -42,7 +42,7 @@
class="basic-user-card-screen-name"
:to="userProfileLink(user)"
>
@{{ user.screen_name }}
@{{ user.screen_name_ui }}
</router-link>
</div>
<slot />

View file

@ -73,7 +73,7 @@ const Chat = {
},
formPlaceholder () {
if (this.recipient) {
return this.$t('chats.message_user', { nickname: this.recipient.screen_name })
return this.$t('chats.message_user', { nickname: this.recipient.screen_name_ui })
} else {
return ''
}
@ -234,6 +234,13 @@ const Chat = {
const scrollable = this.$refs.scrollable
return scrollable && scrollable.scrollTop <= 0
},
cullOlderCheck () {
window.setTimeout(() => {
if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
this.$store.dispatch('cullOlderMessages', this.currentChatMessageService.chatId)
}
}, 5000)
},
handleScroll: _.throttle(function () {
if (!this.currentChat) { return }
@ -241,6 +248,7 @@ const Chat = {
this.fetchChat({ maxId: this.currentChatMessageService.minId })
} else if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
this.jumpToBottomButtonVisible = false
this.cullOlderCheck()
if (this.newMessageCount > 0) {
// Use a delay before marking as read to prevent situation where new messages
// arrive just as you're leaving the view and messages that you didn't actually

View file

@ -98,10 +98,10 @@
.unread-message-count {
font-size: 0.8em;
left: 50%;
transform: translate(-50%, 0);
border-radius: 100%;
margin-top: -1rem;
padding: 0;
padding: 0.1em;
border-radius: 50px;
position: absolute;
}
.chat-loading-error {

View file

@ -35,6 +35,18 @@ const chatPanel = {
userProfileLink (user) {
return generateProfileLink(user.id, user.username, this.$store.state.instance.restrictedNicknames)
}
},
watch: {
messages (newVal) {
const scrollEl = this.$el.querySelector('.chat-window')
if (!scrollEl) return
if (scrollEl.scrollTop + scrollEl.offsetHeight + 20 > scrollEl.scrollHeight) {
this.$nextTick(() => {
if (!scrollEl) return
scrollEl.scrollTop = scrollEl.scrollHeight - scrollEl.offsetHeight
})
}
}
}
}

View file

@ -10,17 +10,15 @@
@click.stop.prevent="togglePanel"
>
<div class="title">
<span>{{ $t('shoutbox.title') }}</span>
{{ $t('shoutbox.title') }}
<FAIcon
v-if="floating"
icon="times"
class="close-icon"
/>
</div>
</div>
<div
v-chat-scroll
class="chat-window"
>
<div class="chat-window">
<div
v-for="message in messages"
:key="message.id"
@ -94,6 +92,13 @@
.icon {
color: $fallback--text;
color: var(--text, $fallback--text);
margin-right: 0.5em;
}
.title {
display: flex;
justify-content: space-between;
align-items: center;
}
}

View file

@ -12,7 +12,7 @@ export default Vue.component('chat-title', {
],
computed: {
title () {
return this.user ? this.user.screen_name : ''
return this.user ? this.user.screen_name_ui : ''
},
htmlTitle () {
return this.user ? this.user.name_html : ''

View file

@ -50,7 +50,6 @@
.Conversation {
.conversation-status {
border-left: none;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: var(--border, $fallback--border);

View file

@ -194,11 +194,18 @@ const EmojiInput = {
}
},
methods: {
focusPickerInput () {
const pickerEl = this.$refs.picker.$el
if (!pickerEl) return
const pickerInput = pickerEl.querySelector('input')
if (pickerInput) pickerInput.focus()
},
triggerShowPicker () {
this.showPicker = true
this.$refs.picker.startEmojiLoad()
this.$nextTick(() => {
this.scrollIntoView()
this.focusPickerInput()
})
// This temporarily disables "click outside" handler
// since external trigger also means click originates
@ -214,6 +221,7 @@ const EmojiInput = {
if (this.showPicker) {
this.scrollIntoView()
this.$refs.picker.startEmojiLoad()
this.$nextTick(this.focusPickerInput)
}
},
replace (replacement) {

View file

@ -9,8 +9,8 @@
<button
v-if="!hideEmojiButton"
class="button-unstyled emoji-picker-icon"
@click.prevent="togglePicker"
type="button"
@click.prevent="togglePicker"
>
<FAIcon :icon="['far', 'smile-beam']" />
</button>

View file

@ -116,8 +116,8 @@ export const suggestUsers = ({ dispatch, state }) => {
return diff + nameAlphabetically + screenNameAlphabetically
/* eslint-disable camelcase */
}).map(({ screen_name, name, profile_image_url_original }) => ({
displayText: screen_name,
}).map(({ screen_name, screen_name_ui, name, profile_image_url_original }) => ({
displayText: screen_name_ui,
detailText: name,
imageUrl: profile_image_url_original,
replacement: '@' + screen_name + ' '

View file

@ -73,11 +73,21 @@
}
}
@keyframes media-fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.modal-image {
max-width: 90%;
max-height: 90%;
box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
image-orientation: from-image; // NOTE: only FF supports this
animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein;
}
.modal-view-button-arrow {

View file

@ -25,16 +25,16 @@
<div>
<button
class="button-unstyled -link"
@click.prevent="requireTOTP"
type="button"
@click.prevent="requireTOTP"
>
{{ $t('login.enter_two_factor_code') }}
</button>
<br>
<button
class="button-unstyled -link"
@click.prevent="abortMFA"
type="button"
@click.prevent="abortMFA"
>
{{ $t('general.cancel') }}
</button>

View file

@ -27,16 +27,16 @@
<div>
<button
class="button-unstyled -link"
@click.prevent="requireRecovery"
type="button"
@click.prevent="requireRecovery"
>
{{ $t('login.enter_recovery_code') }}
</button>
<br>
<button
class="button-unstyled -link"
@click.prevent="abortMFA"
type="button"
@click.prevent="abortMFA"
>
{{ $t('general.cancel') }}
</button>

View file

@ -50,74 +50,74 @@
class="button-default dropdown-item"
@click="toggleTag(tags.FORCE_NSFW)"
>
{{ $t('user_card.admin_menu.force_nsfw') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_NSFW) }"
/>
{{ $t('user_card.admin_menu.force_nsfw') }}
</button>
<button
class="button-default dropdown-item"
@click="toggleTag(tags.STRIP_MEDIA)"
>
{{ $t('user_card.admin_menu.strip_media') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.STRIP_MEDIA) }"
/>
{{ $t('user_card.admin_menu.strip_media') }}
</button>
<button
class="button-default dropdown-item"
@click="toggleTag(tags.FORCE_UNLISTED)"
>
{{ $t('user_card.admin_menu.force_unlisted') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_UNLISTED) }"
/>
{{ $t('user_card.admin_menu.force_unlisted') }}
</button>
<button
class="button-default dropdown-item"
@click="toggleTag(tags.SANDBOX)"
>
{{ $t('user_card.admin_menu.sandbox') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.SANDBOX) }"
/>
{{ $t('user_card.admin_menu.sandbox') }}
</button>
<button
v-if="user.is_local"
class="button-default dropdown-item"
@click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)"
>
{{ $t('user_card.admin_menu.disable_remote_subscription') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_REMOTE_SUBSCRIPTION) }"
/>
{{ $t('user_card.admin_menu.disable_remote_subscription') }}
</button>
<button
v-if="user.is_local"
class="button-default dropdown-item"
@click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)"
>
{{ $t('user_card.admin_menu.disable_any_subscription') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_ANY_SUBSCRIPTION) }"
/>
{{ $t('user_card.admin_menu.disable_any_subscription') }}
</button>
<button
v-if="user.is_local"
class="button-default dropdown-item"
@click="toggleTag(tags.QUARANTINE)"
>
{{ $t('user_card.admin_menu.quarantine') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.QUARANTINE) }"
/>
{{ $t('user_card.admin_menu.quarantine') }}
</button>
</span>
</div>
@ -163,25 +163,6 @@
<style lang="scss">
@import '../../_variables.scss';
.menu-checkbox {
float: right;
min-width: 22px;
max-width: 22px;
min-height: 22px;
max-height: 22px;
line-height: 22px;
text-align: center;
border-radius: 0px;
background-color: $fallback--fg;
background-color: var(--input, $fallback--fg);
box-shadow: 0px 0px 2px black inset;
box-shadow: var(--inputShadow);
&.menu-checkbox-checked::after {
content: '✓';
}
}
.moderation-tools-popover {
height: 100%;
.trigger {

View file

@ -11,7 +11,7 @@
>
<small>
<router-link :to="userProfileLink">
{{ notification.from_profile.screen_name }}
{{ notification.from_profile.screen_name_ui }}
</router-link>
</small>
<button
@ -54,14 +54,14 @@
<bdi
v-if="!!notification.from_profile.name_html"
class="username"
:title="'@'+notification.from_profile.screen_name"
:title="'@'+notification.from_profile.screen_name_ui"
v-html="notification.from_profile.name_html"
/>
<!-- eslint-enable vue/no-v-html -->
<span
v-else
class="username"
:title="'@'+notification.from_profile.screen_name"
:title="'@'+notification.from_profile.screen_name_ui"
>{{ notification.from_profile.name }}</span>
<span v-if="notification.type === 'like'">
<FAIcon
@ -155,7 +155,7 @@
:to="userProfileLink"
class="follow-name"
>
@{{ notification.from_profile.screen_name }}
@{{ notification.from_profile.screen_name_ui }}
</router-link>
<div
v-if="notification.type === 'follow_request'"
@ -180,7 +180,7 @@
class="move-text"
>
<router-link :to="targetUserProfileLink">
@{{ notification.target.screen_name }}
@{{ notification.target.screen_name_ui }}
</router-link>
</div>
<template v-else>

View file

@ -151,6 +151,7 @@
border: none;
box-shadow: none;
background-color: transparent;
padding-right: 0.75em;
}
}

View file

@ -3,25 +3,32 @@ const Popover = {
props: {
// Action to trigger popover: either 'hover' or 'click'
trigger: String,
// Either 'top' or 'bottom'
placement: String,
// Takes object with properties 'x' and 'y', values of these can be
// 'container' for using offsetParent as boundaries for either axis
// or 'viewport'
boundTo: Object,
// Takes a selector to use as a replacement for the parent container
// for getting boundaries for x an y axis
boundToSelector: String,
// Takes a top/bottom/left/right object, how much space to leave
// between boundary and popover element
margin: Object,
// Takes a x/y object and tells how many pixels to offset from
// anchor point on either axis
offset: Object,
// Replaces the classes you may want for the popover container.
// Use 'popover-default' in addition to get the default popover
// styles with your custom class.
popoverClass: String,
// If true, subtract padding when calculating position for the popover,
// use it when popover offset looks to be different on top vs bottom.
removePadding: Boolean
@ -121,9 +128,12 @@ const Popover = {
}
},
showPopover () {
if (this.hidden) this.$emit('show')
const wasHidden = this.hidden
this.hidden = false
this.$nextTick(this.updateStyles)
this.$nextTick(() => {
if (wasHidden) this.$emit('show')
this.updateStyles()
})
},
hidePopover () {
if (!this.hidden) this.$emit('close')

View file

@ -6,8 +6,8 @@
<button
ref="trigger"
class="button-unstyled -fullwidth popover-trigger-button"
@click="onClick"
type="button"
@click="onClick"
>
<slot name="trigger" />
</button>
@ -82,10 +82,9 @@
.dropdown-item {
line-height: 21px;
margin-right: 5px;
overflow: auto;
display: block;
padding: .25rem 1.0rem .25rem 1.5rem;
padding: .5em 0.75em;
clear: both;
font-weight: 400;
text-align: inherit;
@ -101,10 +100,9 @@
--btnText: var(--popoverText, $fallback--text);
&-icon {
padding-left: 0.5rem;
svg {
margin-right: 0.25rem;
width: 22px;
margin-right: 0.75rem;
color: var(--menuPopoverIcon, $fallback--icon)
}
}
@ -123,6 +121,33 @@
}
}
.menu-checkbox {
display: inline-block;
vertical-align: middle;
min-width: 22px;
max-width: 22px;
min-height: 22px;
max-height: 22px;
line-height: 22px;
text-align: center;
border-radius: 0px;
background-color: $fallback--fg;
background-color: var(--input, $fallback--fg);
box-shadow: 0px 0px 2px black inset;
box-shadow: var(--inputShadow);
margin-right: 0.75em;
&.menu-checkbox-checked::after {
font-size: 1.25em;
content: '✓';
}
&.menu-checkbox-radio::after {
font-size: 2em;
content: '•';
}
}
}
}
</style>

View file

@ -115,7 +115,7 @@ const PostStatusForm = {
? this.copyMessageScope
: this.$store.state.users.currentUser.default_scope
const { postContentType: contentType } = this.$store.getters.mergedConfig
const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig
return {
dropFiles: [],
@ -126,7 +126,7 @@ const PostStatusForm = {
newStatus: {
spoilerText: this.subject || '',
status: statusText,
nsfw: false,
nsfw: !!sensitiveByDefault,
files: [],
poll: {},
mediaDescriptions: {},

View file

@ -23,6 +23,12 @@ const ReactButton = {
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
}
close()
},
focusInput () {
this.$nextTick(() => {
const input = this.$el.querySelector('input')
if (input) input.focus()
})
}
},
computed: {

View file

@ -6,6 +6,7 @@
:offset="{ y: 5 }"
:bound-to="{ x: 'container' }"
remove-padding
@show="focusInput"
>
<div
slot="content"

View file

@ -10,7 +10,8 @@ const registration = {
fullname: '',
username: '',
password: '',
confirm: ''
confirm: '',
reason: ''
},
captcha: {}
}),
@ -24,7 +25,8 @@ const registration = {
confirm: {
required,
sameAsPassword: sameAs('password')
}
},
reason: { required: requiredIf(() => this.accountApprovalRequired) }
}
}
},
@ -38,7 +40,10 @@ const registration = {
computed: {
token () { return this.$route.params.token },
bioPlaceholder () {
return this.$t('registration.bio_placeholder').replace(/\s*\n\s*/g, ' \n')
return this.replaceNewlines(this.$t('registration.bio_placeholder'))
},
reasonPlaceholder () {
return this.replaceNewlines(this.$t('registration.reason_placeholder'))
},
...mapState({
registrationOpen: (state) => state.instance.registrationOpen,
@ -46,7 +51,8 @@ const registration = {
isPending: (state) => state.users.signUpPending,
serverValidationErrors: (state) => state.users.signUpErrors,
termsOfService: (state) => state.instance.tos,
accountActivationRequired: (state) => state.instance.accountActivationRequired
accountActivationRequired: (state) => state.instance.accountActivationRequired,
accountApprovalRequired: (state) => state.instance.accountApprovalRequired
})
},
methods: {
@ -73,6 +79,9 @@ const registration = {
},
setCaptcha () {
this.getCaptcha().then(cpt => { this.captcha = cpt })
},
replaceNewlines (str) {
return str.replace(/\s*\n\s*/g, ' \n')
}
}
}

View file

@ -162,6 +162,23 @@
</ul>
</div>
<div
v-if="accountApprovalRequired"
class="form-group"
>
<label
class="form--label"
for="reason"
>{{ $t('registration.reason') }}</label>
<textarea
id="reason"
v-model="user.reason"
:disabled="isPending"
class="form-control"
:placeholder="reasonPlaceholder"
/>
</div>
<div
v-if="captcha.type != 'none'"
id="captcha-group"

View file

@ -8,8 +8,8 @@
class="button-unstyled scope"
:class="css.direct"
:title="$t('post_status.scope.direct')"
@click="changeVis('direct')"
type="button"
@click="changeVis('direct')"
>
<FAIcon
icon="envelope"
@ -21,8 +21,8 @@
class="button-unstyled scope"
:class="css.private"
:title="$t('post_status.scope.private')"
@click="changeVis('private')"
type="button"
@click="changeVis('private')"
>
<FAIcon
icon="lock"
@ -34,8 +34,8 @@
class="button-unstyled scope"
:class="css.unlisted"
:title="$t('post_status.scope.unlisted')"
@click="changeVis('unlisted')"
type="button"
@click="changeVis('unlisted')"
>
<FAIcon
icon="lock-open"
@ -47,8 +47,8 @@
class="button-unstyled scope"
:class="css.public"
:title="$t('post_status.scope.public')"
@click="changeVis('public')"
type="button"
@click="changeVis('public')"
>
<FAIcon
icon="globe"

View file

@ -15,8 +15,8 @@
>
<button
class="btn button-default search-button"
@click="newQuery(searchTerm)"
type="submit"
@click="newQuery(searchTerm)"
>
<FAIcon icon="search" />
</button>

View file

@ -7,8 +7,8 @@
v-if="hidden"
class="button-unstyled nav-icon"
:title="$t('nav.search')"
@click.prevent.stop="toggleHidden"
type="button"
@click.prevent.stop="toggleHidden"
>
<FAIcon
fixed-width
@ -28,8 +28,8 @@
>
<button
class="button-default search-button"
@click="find(searchTerm)"
type="submit"
@click="find(searchTerm)"
>
<FAIcon
fixed-width
@ -38,8 +38,8 @@
</button>
<button
class="button-unstyled cancel-search"
@click.prevent.stop="toggleHidden"
type="button"
@click.prevent.stop="toggleHidden"
>
<FAIcon
fixed-width

View file

@ -4,13 +4,13 @@
>
<Checkbox
:checked="state"
@change="update"
:disabled="disabled"
@change="update"
>
<span
v-if="!!$slots.default"
class="label"
>
>
<slot />
</span>
<ModifiedIndicator :changed="isChanged" />
@ -23,14 +23,14 @@ import { get, set } from 'lodash'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import ModifiedIndicator from './modified_indicator.vue'
export default {
props: [
'path',
'disabled'
],
components: {
Checkbox,
ModifiedIndicator
},
props: [
'path',
'disabled'
],
computed: {
pathDefault () {
const [firstSegment, ...rest] = this.path.split('.')

View file

@ -2,21 +2,21 @@
<span
v-if="changed"
class="ModifiedIndicator"
>
>
<Popover
trigger="hover"
>
>
<span slot="trigger">
&nbsp;
<FAIcon
icon="wrench"
/>
<FAIcon
icon="wrench"
/>
</span>
<div
class="modified-tooltip"
slot="content"
>
{{ $t('settings.setting_changed') }}
class="modified-tooltip"
>
{{ $t('settings.setting_changed') }}
</div>
</Popover>
</span>
@ -32,8 +32,8 @@ library.add(
)
export default {
props: ['changed'],
components: { Popover }
components: { Popover },
props: ['changed']
}
</script>

View file

@ -75,8 +75,8 @@
<p>{{ $t('settings.filtering_explanation') }}</p>
<textarea
id="muteWords"
class="resize-height"
v-model="muteWordsString"
class="resize-height"
/>
</div>
<div>

View file

@ -159,7 +159,12 @@
</li>
<li>
<BooleanSetting path="minimalScopesMode">
{{ $t('settings.minimal_scopes_mode') }} {{ minimalScopesModeDefaultValue }}
{{ $t('settings.minimal_scopes_mode') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="sensitiveByDefault">
{{ $t('settings.sensitive_by_default') }}
</BooleanSetting>
</li>
<li>

View file

@ -136,7 +136,7 @@ const Status = {
}
},
retweet () { return !!this.statusoid.retweeted_status },
retweeter () { return this.statusoid.user.name || this.statusoid.user.screen_name },
retweeter () { return this.statusoid.user.name || this.statusoid.user.screen_name_ui },
retweeterHtml () { return this.statusoid.user.name_html },
retweeterProfileLink () { return this.generateUserProfileLink(this.statusoid.user.id, this.statusoid.user.screen_name) },
status () {
@ -216,7 +216,7 @@ const Status = {
return this.status.in_reply_to_screen_name
} else {
const user = this.$store.getters.findUser(this.status.in_reply_to_user_id)
return user && user.screen_name
return user && user.screen_name_ui
}
},
replySubject () {

View file

@ -26,7 +26,7 @@
icon="retweet"
/>
<router-link :to="userProfileLink">
{{ status.user.screen_name }}
{{ status.user.screen_name_ui }}
</router-link>
</small>
<small
@ -156,10 +156,10 @@
</h4>
<router-link
class="account-name"
:title="status.user.screen_name"
:title="status.user.screen_name_ui"
:to="userProfileLink"
>
{{ status.user.screen_name }}
{{ status.user.screen_name_ui }}
</router-link>
<img
v-if="!!(status.user && status.user.favicon)"

View file

@ -2,12 +2,14 @@ import Status from '../status/status.vue'
import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js'
import Conversation from '../conversation/conversation.vue'
import TimelineMenu from '../timeline_menu/timeline_menu.vue'
import TimelineQuickSettings from './timeline_quick_settings.vue'
import { debounce, throttle, keyBy } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
import { faCircleNotch, faCog } from '@fortawesome/free-solid-svg-icons'
library.add(
faCircleNotch
faCircleNotch,
faCog
)
export const getExcludedStatusIdsByPinning = (statuses, pinnedStatusIds) => {
@ -47,7 +49,8 @@ const Timeline = {
components: {
Status,
Conversation,
TimelineMenu
TimelineMenu,
TimelineQuickSettings
},
computed: {
newStatusCount () {

View file

@ -16,6 +16,7 @@
>
{{ $t('timeline.up_to_date') }}
</div>
<TimelineQuickSettings v-if="!embedded" />
</div>
<div :class="classes.body">
<div
@ -103,9 +104,12 @@
max-width: 100%;
flex-wrap: nowrap;
align-items: center;
position: relative;
.loadmore-button {
flex-shrink: 0;
}
.loadmore-text {
flex-shrink: 0;
line-height: 1em;

View file

@ -0,0 +1,63 @@
import Popover from '../popover/popover.vue'
import BooleanSetting from '../settings_modal/helpers/boolean_setting.vue'
import { mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons'
library.add(
faFilter,
faFont,
faWrench
)
const TimelineQuickSettings = {
components: {
Popover,
BooleanSetting
},
methods: {
setReplyVisibility (visibility) {
this.$store.dispatch('setOption', { name: 'replyVisibility', value: visibility })
this.$store.dispatch('queueFlushAll')
},
openTab (tab) {
this.$store.dispatch('openSettingsModalTab', tab)
}
},
computed: {
...mapGetters(['mergedConfig']),
loggedIn () {
return !!this.$store.state.users.currentUser
},
replyVisibilitySelf: {
get () { return this.mergedConfig.replyVisibility === 'self' },
set () { this.setReplyVisibility('self') }
},
replyVisibilityFollowing: {
get () { return this.mergedConfig.replyVisibility === 'following' },
set () { this.setReplyVisibility('following') }
},
replyVisibilityAll: {
get () { return this.mergedConfig.replyVisibility === 'all' },
set () { this.setReplyVisibility('all') }
},
hideMedia: {
get () { return this.mergedConfig.hideAttachments || this.mergedConfig.hideAttachmentsInConv },
set () {
const value = !this.hideMedia
this.$store.dispatch('setOption', { name: 'hideAttachments', value })
this.$store.dispatch('setOption', { name: 'hideAttachmentsInConv', value })
}
},
hideMutedPosts: {
get () { return this.mergedConfig.hideMutedPosts || this.mergedConfig.hideFilteredStatuses },
set () {
const value = !this.hideMutedPosts
this.$store.dispatch('setOption', { name: 'hideMutedPosts', value })
this.$store.dispatch('setOption', { name: 'hideFilteredStatuses', value })
}
}
}
}
export default TimelineQuickSettings

View file

@ -0,0 +1,107 @@
<template>
<Popover
trigger="click"
class="TimelineQuickSettings"
:bound-to="{ x: 'container' }"
>
<div
slot="content"
class="timeline-settings-menu dropdown-menu"
>
<div v-if="loggedIn">
<button
class="button-default dropdown-item"
@click="replyVisibilityAll = true"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-radio': replyVisibilityAll }"
/>{{ $t('settings.reply_visibility_all') }}
</button>
<button
class="button-default dropdown-item"
@click="replyVisibilityFollowing = true"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-radio': replyVisibilityFollowing }"
/>{{ $t('settings.reply_visibility_following_short') }}
</button>
<button
class="button-default dropdown-item"
@click="replyVisibilitySelf = true"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-radio': replyVisibilitySelf }"
/>{{ $t('settings.reply_visibility_self_short') }}
</button>
<div
role="separator"
class="dropdown-divider"
/>
</div>
<button
class="button-default dropdown-item"
@click="hideMedia = !hideMedia"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hideMedia }"
/>{{ $t('settings.hide_media_previews') }}
</button>
<button
class="button-default dropdown-item"
@click="hideMutedPosts = !hideMutedPosts"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hideMutedPosts }"
/>{{ $t('settings.hide_all_muted_posts') }}
</button>
<button
class="button-default dropdown-item dropdown-item-icon"
@click="openTab('filtering')"
>
<FAIcon icon="font" />{{ $t('settings.word_filter') }}
</button>
<button
class="button-default dropdown-item dropdown-item-icon"
@click="openTab('general')"
>
<FAIcon icon="wrench" />{{ $t('settings.more_settings') }}
</button>
</div>
<div slot="trigger">
<FAIcon icon="filter" />
</div>
</Popover>
</template>
<script src="./timeline_quick_settings.js"></script>
<style lang="scss">
.TimelineQuickSettings {
align-self: stretch;
> button {
font-size: 1.2em;
padding-left: 0.7em;
padding-right: 0.2em;
line-height: 100%;
height: 100%;
}
.dropdown-item {
margin: 0;
}
.timeline-settings-menu {
display: flex;
min-width: 12em;
flex-direction: column;
}
}
</style>

View file

@ -2,8 +2,8 @@
<StillImage
v-if="user"
class="Avatar"
:alt="user.screen_name"
:title="user.screen_name"
:alt="user.screen_name_ui"
:title="user.screen_name_ui"
:src="imgSrc(user.profile_image_url_original)"
:class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }"
:image-load-error="imageLoadError"

View file

@ -73,17 +73,17 @@
<div class="bottom-line">
<router-link
class="user-screen-name"
:title="user.screen_name"
:title="user.screen_name_ui"
:to="userProfileLink(user)"
>
@{{ user.screen_name }}
@{{ user.screen_name_ui }}
</router-link>
<template v-if="!hideBio">
<span
v-if="!!visibleRole"
class="alert user-role"
>
{{ $t(`user_card.roles.${visibleRole}`) }}
{{ $t(`general.role.${visibleRole}`) }}
</span>
<span
v-if="user.bot"
@ -141,10 +141,10 @@
v-model="userHighlightType"
class="userHighlightSel"
>
<option value="disabled">No highlight</option>
<option value="solid">Solid bg</option>
<option value="striped">Striped bg</option>
<option value="side">Side stripe</option>
<option value="disabled">{{ $t('user_card.highlight.disabled') }}</option>
<option value="solid">{{ $t('user_card.highlight.solid') }}</option>
<option value="striped">{{ $t('user_card.highlight.striped') }}</option>
<option value="side">{{ $t('user_card.highlight.side') }}</option>
</select>
<FAIcon
class="select-down-icon"

View file

@ -26,7 +26,7 @@
<!-- 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>
<span class="user-list-screen-name">{{ user.screen_name_ui }}</span>
</div>
</div>
</div>

View file

@ -6,7 +6,7 @@
<div class="user-reporting-panel panel">
<div class="panel-heading">
<div class="title">
{{ $t('user_reporting.title', [user.screen_name]) }}
{{ $t('user_reporting.title', [user.screen_name_ui]) }}
</div>
</div>
<div class="panel-body">

View file

@ -228,6 +228,8 @@
"username_placeholder": "e.g. lain",
"fullname_placeholder": "e.g. Lain Iwakura",
"bio_placeholder": "e.g.\nHi, I'm Lain.\nIm an anime girl living in suburban Japan. You may know me from the Wired.",
"reason": "Reason to register",
"reason_placeholder": "This instance approves registrations manually.\nLet the administration know why you want to register.",
"validations": {
"username_required": "cannot be left blank",
"fullname_required": "cannot be left blank",
@ -323,6 +325,7 @@
"export_theme": "Save preset",
"filtering": "Filtering",
"filtering_explanation": "All statuses containing these words will be muted, one per line",
"word_filter": "Word filter",
"follow_export": "Follow export",
"follow_export_button": "Export your follows to a csv file",
"follow_import": "Follow import",
@ -333,7 +336,9 @@
"general": "General",
"hide_attachments_in_convo": "Hide attachments in conversations",
"hide_attachments_in_tl": "Hide attachments in timeline",
"hide_media_previews": "Hide media previews",
"hide_muted_posts": "Hide posts of muted users",
"hide_all_muted_posts": "Hide muted posts",
"max_thumbnails": "Maximum amount of thumbnails per post",
"hide_isp": "Hide instance-specific panel",
"show_third_column": "Move Notifications to a seperate column",
@ -406,6 +411,8 @@
"reply_visibility_all": "Show all replies",
"reply_visibility_following": "Only show replies directed at me or users I'm following",
"reply_visibility_self": "Only show replies directed at me",
"reply_visibility_following_short": "Show replies to my follows",
"reply_visibility_self_short": "Show replies to self only",
"autohide_floating_post_button": "Automatically hide New Post button (mobile)",
"saving_err": "Error saving settings",
"saving_ok": "Settings saved",
@ -430,6 +437,7 @@
"subject_line_mastodon": "Like mastodon: copy as is",
"subject_line_noop": "Do not copy",
"post_status_content_type": "Post status content type",
"sensitive_by_default": "Mark posts as sensitive by default",
"stop_gifs": "Play-on-hover GIFs",
"streaming": "Enable automatic streaming of new posts when scrolled to the top",
"user_mutes": "Users",
@ -459,6 +467,7 @@
"notification_mutes": "To stop receiving notifications from a specific user, use a mute.",
"notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.",
"enable_web_push_notifications": "Enable web push notifications",
"more_settings": "More settings",
"style": {
"switcher": {
"keep_color": "Keep colors",
@ -741,9 +750,11 @@
"delete_user": "Delete user",
"delete_user_confirmation": "Are you absolutely sure? This action cannot be undone."
},
"roles": {
"admin": "Admin",
"moderator": "Moderator"
"highlight": {
"disabled": "No highlight",
"solid": "Solid bg",
"striped": "Striped bg",
"side": "Side stripe"
}
},
"user_profile": {

View file

@ -35,7 +35,11 @@
"retry": "Reprovi",
"error_retry": "Bonvolu reprovi",
"loading": "Enlegante…",
"peek": "Antaŭmontri"
"peek": "Antaŭmontri",
"role": {
"moderator": "Reguligisto",
"admin": "Administranto"
}
},
"image_cropper": {
"crop_picture": "Tondi bildon",
@ -365,7 +369,8 @@
"post": "Afiŝoj/Priskriboj de uzantoj",
"alert_neutral": "Neŭtrala",
"alert_warning": "Averto",
"toggled": "Ŝaltita"
"toggled": "Ŝaltita",
"wallpaper": "Fonbildo"
},
"radii": {
"_tab_label": "Rondeco"
@ -516,7 +521,9 @@
"mute_import_error": "Eraris enporto de silentigoj",
"mute_import": "Enporto de silentigoj",
"mute_export_button": "Elportu viajn silentigojn al CSV-dosiero",
"mute_export": "Elporto de silentigoj"
"mute_export": "Elporto de silentigoj",
"hide_wallpaper": "Kaŝi fonbildon de nodo",
"setting_changed": "Agordo malsamas de la implicita"
},
"timeline": {
"collapse": "Maletendi",
@ -586,7 +593,8 @@
"show_repeats": "Montri ripetojn",
"hide_repeats": "Kaŝi ripetojn",
"unsubscribe": "Ne ricevi sciigojn",
"subscribe": "Ricevi sciigojn"
"subscribe": "Ricevi sciigojn",
"bot": "Roboto"
},
"user_profile": {
"timeline_title": "Historio de uzanto",
@ -612,7 +620,8 @@
"error": {
"base": "Alŝuto malsukcesis.",
"file_too_big": "Dosiero estas tro granda [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
"default": "Reprovu pli poste"
"default": "Reprovu pli poste",
"message": "Malsukcesis alŝuto: {0}"
},
"file_size_units": {
"B": "B",
@ -645,7 +654,9 @@
"votes": "voĉoj",
"option": "Elekteblo",
"add_option": "Aldoni elekteblon",
"add_poll": "Aldoni enketon"
"add_poll": "Aldoni enketon",
"votes_count": "{count} voĉdono | {count} voĉdonoj",
"people_voted_count": "{count} persono voĉdonis | {count} personoj voĉdonis"
},
"importer": {
"error": "Eraris enporto de ĉi tiu dosiero.",
@ -732,7 +743,9 @@
"repeats": "Ripetoj",
"favorites": "Ŝatoj",
"status_deleted": "Ĉi tiu afiŝo foriĝis",
"nsfw": "Konsterna"
"nsfw": "Konsterna",
"expand": "Etendi",
"external_source": "Ekstera fonto"
},
"time": {
"years_short": "{0}j",

View file

@ -562,7 +562,8 @@
"mute_import": "Importar silenciados",
"mute_export_button": "Exportar los silenciados a un archivo csv",
"mute_export": "Exportar silenciados",
"hide_wallpaper": "Ocultar el fondo de pantalla de la instancia"
"hide_wallpaper": "Ocultar el fondo de pantalla de la instancia",
"setting_changed": "La configuración es diferente a la predeterminada"
},
"time": {
"day": "{0} día",
@ -693,7 +694,11 @@
"show_repeats": "Mostrar repetidos",
"hide_repeats": "Ocultar repetidos",
"message": "Mensaje",
"hidden": "Oculto"
"hidden": "Oculto",
"roles": {
"moderator": "Moderador",
"admin": "Administrador"
}
},
"user_profile": {
"timeline_title": "Linea Temporal del Usuario",

View file

@ -280,7 +280,7 @@
"hide_followers_description": "Ne pas afficher qui est abonné à moi",
"show_admin_badge": "Afficher le badge d'Administrateur⋅ice sur mon profil",
"show_moderator_badge": "Afficher le badge de Modérateur⋅ice sur mon profil",
"nsfw_clickthrough": "Masquer les images marquées comme contenu adulte ou sensible",
"nsfw_clickthrough": "Activer le clic pour dévoiler les pièces jointes et cacher l'aperçu des liens pour les statuts marqués comme sensibles",
"oauth_tokens": "Jetons OAuth",
"token": "Jeton",
"refresh_token": "Rafraichir le jeton",
@ -409,7 +409,13 @@
"tabs": "Onglets",
"toggled": "(Dés)activé",
"highlight": "Éléments mis en valeur",
"popover": "Infobulles, menus"
"popover": "Infobulles, menus",
"chat": {
"border": "Bordure",
"outgoing": "Sortant(s)",
"incoming": "Entrant(s)"
},
"wallpaper": "Fond d'écran"
},
"radii": {
"_tab_label": "Rondeur"
@ -485,7 +491,7 @@
"notification_visibility_emoji_reactions": "Réactions",
"hide_follows_count_description": "Masquer le nombre de suivis",
"useStreamingApiWarning": "(Non recommandé, expérimental, connu pour rater des messages)",
"type_domains_to_mute": "Écrire les domaines à masquer",
"type_domains_to_mute": "Chercher les domaines à masquer",
"fun": "Rigolo",
"greentext": "greentexting",
"allow_following_move": "Suivre automatiquement quand ce compte migre",
@ -509,7 +515,21 @@
"mute_import_error": "Erreur à l'import des masquages",
"mute_import": "Import des masquages",
"mute_export_button": "Exporter vos masquages dans un fichier CSV",
"mute_export": "Export des masquages"
"mute_export": "Export des masquages",
"notification_setting_hide_notification_contents": "Cacher l'expéditeur et le contenu des notifications push",
"notification_setting_block_from_strangers": "Bloquer les notifications des utilisateur⋅ice⋅s que vous ne suivez pas",
"virtual_scrolling": "Optimiser le rendu du fil d'actualité",
"reset_background_confirm": "Voulez-vraiment réinitialiser l'arrière-plan ?",
"reset_banner_confirm": "Voulez-vraiment réinitialiser la bannière ?",
"reset_avatar_confirm": "Voulez-vraiment réinitialiser l'avatar ?",
"reset_profile_banner": "Réinitialiser la bannière du profil",
"reset_profile_background": "Réinitialiser l'arrière-plan du profil",
"reset_avatar": "Réinitialiser l'avatar",
"profile_fields": {
"value": "Contenu",
"name": "Étiquette",
"add_field": "Ajouter un champ"
}
},
"timeline": {
"collapse": "Fermer",
@ -521,7 +541,9 @@
"show_new": "Afficher plus",
"up_to_date": "À jour",
"no_more_statuses": "Pas plus de statuts",
"no_statuses": "Aucun statuts"
"no_statuses": "Aucun statuts",
"reload": "Recharger",
"error": "Erreur lors de l'affichage du fil d'actualité : {0}"
},
"status": {
"favorites": "Favoris",
@ -536,7 +558,19 @@
"mute_conversation": "Masquer la conversation",
"unmute_conversation": "Démasquer la conversation",
"status_unavailable": "Status indisponible",
"copy_link": "Copier le lien au status"
"copy_link": "Copier le lien au status",
"expand": "Développer",
"nsfw": "Contenu sensible",
"status_deleted": "Ce post a été effacé",
"hide_content": "Cacher le contenu",
"show_content": "Montrer le contenu",
"hide_full_subject": "Cacher le sujet",
"show_full_subject": "Montrer le sujet en entier",
"thread_muted_and_words": ", contient les mots :",
"thread_muted": "Fil de discussion masqué",
"external_source": "Source externe",
"unbookmark": "Supprimer des favoris",
"bookmark": "Ajouter aux favoris"
},
"user_card": {
"approve": "Accepter",
@ -591,7 +625,12 @@
"subscribe": "Abonner",
"unsubscribe": "Désabonner",
"hide_repeats": "Cacher les partages",
"show_repeats": "Montrer les partages"
"show_repeats": "Montrer les partages",
"roles": {
"moderator": "Modérateur⋅ice",
"admin": "Administrateur⋅ice"
},
"message": "Message"
},
"user_profile": {
"timeline_title": "Journal de l'utilisateur⋅ice",
@ -619,13 +658,15 @@
"user_settings": "Paramètres utilisateur",
"add_reaction": "Ajouter une réaction",
"accept_follow_request": "Accepter la demande de suivit",
"reject_follow_request": "Rejeter la demande de suivit"
"reject_follow_request": "Rejeter la demande de suivit",
"bookmark": "Favori"
},
"upload": {
"error": {
"base": "L'envoi a échoué.",
"file_too_big": "Fichier trop gros [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
"default": "Réessayez plus tard"
"default": "Réessayez plus tard",
"message": "Envoi échoué : {0}"
},
"file_size_units": {
"B": "O",
@ -759,5 +800,27 @@
},
"shoutbox": {
"title": "Shoutbox"
},
"display_date": {
"today": "Aujourd'hui"
},
"file_type": {
"file": "Fichier",
"image": "Image",
"video": "Vidéo",
"audio": "Audio"
},
"chats": {
"empty_chat_list_placeholder": "Vous n'avez pas encore de discussions. Démarrez-en une nouvelle !",
"error_sending_message": "Quelque chose s'est mal passé pendant l'envoi du message.",
"error_loading_chat": "Quelque chose s'est mal passé au chargement de la discussion.",
"delete_confirm": "Voulez-vous vraiment effacer ce message ?",
"more": "Plus",
"empty_message_error": "Impossible d'envoyer un message vide",
"new": "Nouvelle discussion",
"chats": "Discussions",
"delete": "Effacer",
"message_user": "Message à {nickname}",
"you": "Vous :"
}
}

View file

@ -17,7 +17,11 @@
"close": "Chiudi",
"retry": "Riprova",
"error_retry": "Per favore, riprova",
"loading": "Carico…"
"loading": "Carico…",
"role": {
"moderator": "Moderatore",
"admin": "Amministratore"
}
},
"nav": {
"mentions": "Menzioni",
@ -417,7 +421,8 @@
"mute_import": "Importa silenziati",
"mute_export_button": "Esporta la tua lista di silenziati in un file CSV",
"mute_export": "Esporta silenziati",
"hide_wallpaper": "Nascondi sfondo della stanza"
"hide_wallpaper": "Nascondi sfondo della stanza",
"setting_changed": "Valore personalizzato"
},
"timeline": {
"error_fetching": "Errore nell'aggiornamento",
@ -488,10 +493,7 @@
"follow_sent": "Richiesta inviata!",
"favorites": "Preferiti",
"message": "Contatta",
"roles": {
"moderator": "Moderatore",
"admin": "Amministratore"
}
"bot": "Bot"
},
"chat": {
"title": "Chat"
@ -582,7 +584,9 @@
"fullname_placeholder": "es. Lupo Lucio",
"username_placeholder": "es. mister_wolf",
"new_captcha": "Clicca l'immagine per avere un altro captcha",
"captcha": "CAPTCHA"
"captcha": "CAPTCHA",
"reason_placeholder": "L'amministratore esamina ciascuna richiesta.\nFornisci il motivo della tua iscrizione.",
"reason": "Motivo dell'iscrizione"
},
"user_profile": {
"timeline_title": "Sequenza dell'Utente",
@ -660,7 +664,9 @@
"expiry": "Età",
"expires_in": "Chiude fra {0}",
"expired": "Chiuso {0} fa",
"not_enough_options": "Aggiungi altre risposte"
"not_enough_options": "Aggiungi altre risposte",
"votes_count": "{count} voto | {count} voti",
"people_voted_count": "{count} votante | {count} votanti"
},
"interactions": {
"favs_repeats": "Condivisi e Graditi",

View file

@ -4,7 +4,7 @@
},
"exporter": {
"export": "エクスポート",
"processing": "処理中です。処理が完了すると、ファイルをダウンロードするよう指示があります"
"processing": "処理中です。処理が完了すると、ファイルをダウンロードするよう指示があります"
},
"features_panel": {
"chat": "チャット",
@ -13,10 +13,12 @@
"scope_options": "公開範囲選択",
"text_limit": "文字の数",
"title": "有効な機能",
"who_to_follow": "おすすめユーザー"
"who_to_follow": "おすすめユーザー",
"upload_limit": "ファイルサイズの上限",
"pleroma_chat_messages": "Pleroma チャット"
},
"finder": {
"error_fetching_user": "ユーザー検索がエラーになりました",
"error_fetching_user": "ユーザー検索がエラーになりました",
"find_user": "ユーザーを探す"
},
"general": {
@ -31,7 +33,17 @@
"disable": "無効",
"enable": "有効",
"confirm": "確認",
"verify": "検査"
"verify": "検査",
"peek": "隠す",
"close": "閉じる",
"dismiss": "無視",
"retry": "もう一度お試し下さい",
"error_retry": "もう一度お試し下さい",
"loading": "読み込み中…",
"role": {
"moderator": "モデレーター",
"admin": "管理者"
}
},
"image_cropper": {
"crop_picture": "画像を切り抜く",
@ -57,9 +69,9 @@
"enter_recovery_code": "リカバリーコードを入力してください",
"enter_two_factor_code": "2段階認証コードを入力してください",
"recovery_code": "リカバリーコード",
"heading" : {
"totp" : "2段階認証",
"recovery" : "2段階リカバリー"
"heading": {
"totp": "2段階認証",
"recovery": "2段階リカバリー"
}
},
"media_modal": {
@ -76,21 +88,29 @@
"dms": "ダイレクトメッセージ",
"public_tl": "パブリックタイムライン",
"timeline": "タイムライン",
"twkn": "接続しているすべてのネットワーク",
"twkn": "すべてのネットワーク",
"user_search": "ユーザーを探す",
"search": "検索",
"who_to_follow": "おすすめユーザー",
"preferences": "設定"
"preferences": "設定",
"administration": "管理",
"bookmarks": "ブックマーク",
"timelines": "タイムライン",
"chats": "チャット"
},
"notifications": {
"broken_favorite": "ステータスが見つかりません。探しています...",
"broken_favorite": "ステータスが見つかりません。探しています",
"favorited_you": "あなたのステータスがお気に入りされました",
"followed_you": "フォローされました",
"load_older": "古い通知をみる",
"notifications": "通知",
"read": "読んだ!",
"repeated_you": "あなたのステータスがリピートされました",
"no_more_notifications": "通知はありません"
"no_more_notifications": "通知はありません",
"reacted_with": "{0} でリアクションしました",
"migrated_to": "インスタンスを引っ越しました",
"follow_request": "あなたをフォローしたいです",
"error": "通知の取得に失敗しました: {0}"
},
"polls": {
"add_poll": "投票を追加",
@ -104,7 +124,9 @@
"expiry": "投票期間",
"expires_in": "投票は {0} で終了します",
"expired": "投票は {0} 前に終了しました",
"not_enough_options": "相異なる選択肢が不足しています"
"not_enough_options": "相異なる選択肢が不足しています",
"votes_count": "{count} 票 | {count} 票",
"people_voted_count": "{count} 人投票 | {count} 人投票"
},
"emoji": {
"stickers": "ステッカー",
@ -113,7 +135,9 @@
"search_emoji": "絵文字を検索",
"add_emoji": "絵文字を挿入",
"custom": "カスタム絵文字",
"unicode": "Unicode絵文字"
"unicode": "Unicode絵文字",
"load_all": "全 {emojiAmount} 絵文字を読み込む",
"load_all_hint": "最初の {saneAmount} 絵文字を読み込みました、全て読み込むと重くなる可能性があります。"
},
"stickers": {
"add_sticker": "ステッカーを追加"
@ -121,7 +145,8 @@
"interactions": {
"favs_repeats": "リピートとお気に入り",
"follows": "新しいフォロワー",
"load_older": "古いインタラクションを見る"
"load_older": "古いインタラクションを見る",
"moves": "ユーザーの引っ越し"
},
"post_status": {
"new_status": "投稿する",
@ -142,15 +167,20 @@
"posting": "投稿",
"scope_notice": {
"public": "この投稿は、誰でも見ることができます",
"private": "この投稿は、あなたのフォロワーだけが、見ることができます",
"unlisted": "この投稿は、パブリックタイムラインと、接続しているすべてのネットワークには、表示されません"
"private": "この投稿は、あなたのフォロワーだけが、見ることができます",
"unlisted": "この投稿は、パブリックタイムラインと、接続しているすべてのネットワークには、表示されません"
},
"scope": {
"direct": "ダイレクト: メンションされたユーザーのみに届きます。",
"private": "フォロワーげんてい: フォロワーのみに届きます。",
"public": "パブリック: パブリックタイムラインに届きます。",
"unlisted": "アンリステッド: パブリックタイムラインに届きません。"
}
"direct": "ダイレクト: メンションされたユーザーのみに届きます",
"private": "フォロワー限定: フォロワーのみに届きます",
"public": "パブリック: パブリックタイムラインに届きます",
"unlisted": "アンリステッド: パブリックタイムラインに届きません"
},
"media_description_error": "メディアのアップロードに失敗しました。もう一度お試しください",
"empty_status_error": "投稿内容を入力してください",
"preview_empty": "何もありません",
"preview": "プレビュー",
"media_description": "メディアの説明"
},
"registration": {
"bio": "プロフィール",
@ -171,7 +201,9 @@
"password_required": "必須",
"password_confirmation_required": "必須",
"password_confirmation_match": "パスワードが違います"
}
},
"reason_placeholder": "このインスタンスは、新規登録を手動で受け付けています。\n登録したい理由を、インスタンスの管理者に教えてください。",
"reason": "登録するための目的"
},
"selectable_list": {
"select_all": "すべて選択"
@ -181,17 +213,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" : "コードを紙に書くか、安全な場所に保存してください。そうでなければ、あなたはコードを再び見ることはできません。もし2段階認証アプリのアクセスを喪失し、なおかつ、リカバリーコードもないならば、あなたは自分のアカウントから閉め出されます。",
"authentication_methods" : "認証方法",
"generate_new_recovery_codes": "新しいリカバリーコードを生成",
"warning_of_generate_new_codes": "新しいリカバリーコードを生成すると、古いコードは使用できなくなります。",
"recovery_codes": "リカバリーコード。",
"waiting_a_recovery_codes": "バックアップコードを受信しています",
"recovery_codes_warning": "コードを紙に書くか、安全な場所に保存してください。そうでなければ、あなたはコードを再び見ることはできません。もし2段階認証アプリのアクセスを喪失し、なおかつ、リカバリーコードもないならば、あなたは自分のアカウントから閉め出されます。",
"authentication_methods": "認証方法",
"scan": {
"title": "スキャン",
"desc": "あなたの2段階認証アプリを使って、このQRコードをスキャンするか、テキストキーを入力してください:",
@ -231,7 +263,7 @@
"data_import_export_tab": "インポートとエクスポート",
"default_vis": "デフォルトの公開範囲",
"delete_account": "アカウントを消す",
"delete_account_description": "あなたのアカウントとメッセージが、消えます。",
"delete_account_description": "あなたのデータが消えて、アカウントが使えなくなります。",
"delete_account_error": "アカウントを消すことが、できなかったかもしれません。インスタンスの管理者に、連絡してください。",
"delete_account_instructions": "本当にアカウントを消してもいいなら、パスワードを入力してください。",
"discoverable": "検索などのサービスでこのアカウントを見つけることを許可する",
@ -239,12 +271,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": "全般",
@ -305,7 +337,7 @@
"profile_background": "プロフィールのバックグラウンド",
"profile_banner": "プロフィールバナー",
"profile_tab": "プロフィール",
"radii_help": "インターフェースの丸さを設定する",
"radii_help": "インターフェースの丸さを設定する",
"replies_in_timeline": "タイムラインのリプライ",
"reply_visibility_all": "すべてのリプライを見る",
"reply_visibility_following": "私に宛てられたリプライと、フォローしている人からのリプライを見る",
@ -332,7 +364,7 @@
"streaming": "上までスクロールしたとき、自動的にストリーミングする",
"text": "文字",
"theme": "テーマ",
"theme_help": "カラーテーマをカスタマイズできます",
"theme_help": "カラーテーマをカスタマイズできます",
"theme_help_v2_1": "チェックボックスをONにすると、コンポーネントごとに、色と透明度をオーバーライドできます。「すべてクリア」ボタンを押すと、すべてのオーバーライドをやめます。",
"theme_help_v2_2": "バックグラウンドとテキストのコントラストを表すアイコンがあります。マウスをホバーすると、詳しい説明が出ます。透明な色を使っているときは、最悪の場合のコントラストが示されます。",
"tooltipRadius": "ツールチップとアラート",
@ -356,7 +388,24 @@
"save_load_hint": "「残す」オプションをONにすると、テーマを選んだときとロードしたとき、現在の設定を残します。また、テーマをエクスポートするとき、これらのオプションを維持します。すべてのチェックボックスをOFFにすると、テーマをエクスポートしたとき、すべての設定を保存します。",
"reset": "リセット",
"clear_all": "すべてクリア",
"clear_opacity": "透明度をクリア"
"clear_opacity": "透明度をクリア",
"help": {
"snapshot_missing": "テーマのスナップショットがありません。思っていた見た目と違うかもしれません。",
"migration_snapshot_ok": "念のために、テーマのスナップショットが読み込まれました。テーマのデータを読み込むことができます。",
"fe_downgraded": "フロントエンドが前のバージョンに戻りました。",
"fe_upgraded": "フロントエンドと一緒に、テーマエンジンが新しくなりました。",
"older_version_imported": "古いフロントエンドで作られたファイルをインポートしました。",
"future_version_imported": "新しいフロントエンドで作られたファイルをインポートしました。",
"v2_imported": "古いフロントエンドのためのファイルをインポートしました。設定した通りにならないかもしれません。",
"upgraded_from_v2": "フロントエンドが新しくなったので、今までの見た目と少し違うかもしれません。",
"snapshot_source_mismatch": "フロントエンドがロールバックと更新を繰り返したため、バージョンが競合しています。",
"migration_napshot_gone": "スナップショットがありません、覚えているものと見た目が違うかもしれません。",
"snapshot_present": "テーマのスナップショットが読み込まれました。設定は上書きされました。代わりとして実データを読み込むことができます。"
},
"use_source": "新しいバージョン",
"use_snapshot": "古いバージョン",
"load_theme": "テーマの読み込み",
"keep_as_is": "変更しない"
},
"common": {
"color": "色",
@ -364,9 +413,9 @@
"contrast": {
"hint": "コントラストは {ratio} です。{level}。({context})",
"level": {
"aa": "AAレベルガイドライン (ミニマル) を満たします",
"aaa": "AAAレベルガイドライン (レコメンデッド) を満たします。",
"bad": "ガイドラインを満たしません"
"aa": "AAレベルガイドライン (最低限) を満たします",
"aaa": "AAAレベルガイドライン (推奨) を満たします",
"bad": "ガイドラインを満たしません"
},
"context": {
"18pt": "大きい (18ポイント以上) テキスト",
@ -391,7 +440,27 @@
"borders": "境界",
"buttons": "ボタン",
"inputs": "インプットフィールド",
"faint_text": "薄いテキスト"
"faint_text": "薄いテキスト",
"alert_neutral": "それ以外",
"chat": {
"border": "境界線",
"outgoing": "送信",
"incoming": "受信"
},
"tabs": "タブ",
"toggled": "切り替えたとき",
"disabled": "無効なとき",
"selectedMenu": "選択されたメニューアイテム",
"selectedPost": "選択された投稿",
"pressed": "押したとき",
"highlight": "強調された要素",
"icons": "アイコン",
"poll": "投票グラフ",
"wallpaper": "壁紙",
"underlay": "アンダーレイ",
"popover": "ツールチップ、メニュー、ポップオーバー",
"post": "投稿/プロフィール",
"alert_warning": "警告"
},
"radii": {
"_tab_label": "丸さ"
@ -409,8 +478,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": "パネル",
@ -424,7 +493,8 @@
"buttonPressed": "ボタン (押されているとき)",
"buttonPressedHover": "ボタン (ホバー、かつ、押されているとき)",
"input": "インプットフィールド"
}
},
"hintV3": "影の場合は、 {0} 表記を使って他の色スロットを使うこともできます。"
},
"fonts": {
"_tab_label": "フォント",
@ -445,7 +515,7 @@
"content": "本文",
"error": "エラーの例",
"button": "ボタン",
"text": "これは{0}と{1}の例です",
"text": "これは{0}と{1}の例です",
"mono": "monospace",
"input": "羽田空港に着きました。",
"faint_link": "とても助けになるマニュアル",
@ -459,7 +529,52 @@
"title": "バージョン",
"backend_version": "バックエンドのバージョン",
"frontend_version": "フロントエンドのバージョン"
}
},
"notification_setting_hide_notification_contents": "送った人と内容を、プッシュ通知に表示しない",
"notification_setting_privacy": "プライバシー",
"notification_setting_block_from_strangers": "フォローしていないユーザーからの通知を拒否する",
"notification_setting_filters": "フィルター",
"fun": "お楽しみ",
"virtual_scrolling": "タイムラインの描画を最適化する",
"type_domains_to_mute": "ミュートしたいドメインを検索",
"useStreamingApiWarning": "(実験中で、投稿を取りこぼすかもしれないので、おすすめしません)",
"useStreamingApi": "投稿と通知を、すぐに受け取る",
"user_mutes": "ユーザー",
"reset_background_confirm": "本当にバックグラウンドを初期化しますか?",
"reset_banner_confirm": "本当にバナーを初期化しますか?",
"reset_avatar_confirm": "本当にアバターを初期化しますか?",
"hide_wallpaper": "インスタンスのバックグラウンドを隠す",
"reset_profile_background": "プロフィールのバックグラウンドを初期化",
"reset_profile_banner": "プロフィールのバナーを初期化",
"reset_avatar": "アバターを初期化",
"notification_visibility_emoji_reactions": "リアクション",
"notification_visibility_moves": "ユーザーの引っ越し",
"new_email": "新しいメールアドレス",
"profile_fields": {
"value": "内容",
"name": "ラベル",
"add_field": "枠を追加",
"label": "プロフィール補足情報"
},
"accent": "アクセント",
"mutes_imported": "ミュートをインポートしました!少し時間がかかるかもしれません。",
"emoji_reactions_on_timeline": "絵文字リアクションをタイムラインに表示",
"domain_mutes": "ドメイン",
"mutes_and_blocks": "ミュートとブロック",
"chatMessageRadius": "チャットメッセージ",
"change_email_error": "メールアドレスを変えることが、できなかったかもしれません。",
"changed_email": "メールアドレスが、変わりました!",
"change_email": "メールアドレスを変える",
"bot": "これは bot アカウントです",
"mute_export_button": "ミュートをCSVファイルにエクスポートする",
"import_mutes_from_a_csv_file": "CSVファイルからミュートをインポートする",
"mute_import_error": "ミュートのインポートに失敗しました",
"mute_import": "ミュートのインポート",
"mute_export": "ミュートのエクスポート",
"allow_following_move": "フォロー中のアカウントが引っ越したとき、自動フォローを許可する",
"setting_changed": "規定の設定と異なっています",
"greentext": "引用を緑色で表示",
"sensitive_by_default": "はじめから投稿をセンシティブとして設定"
},
"time": {
"day": "{0}日",
@ -505,7 +620,9 @@
"show_new": "読み込み",
"up_to_date": "最新",
"no_more_statuses": "これで終わりです",
"no_statuses": "ステータスはありません"
"no_statuses": "ステータスはありません",
"reload": "再読み込み",
"error": "タイムラインの読み込みに失敗しました: {0}"
},
"status": {
"favorites": "お気に入り",
@ -518,7 +635,21 @@
"reply_to": "返信",
"replies_list": "返信:",
"mute_conversation": "スレッドをミュート",
"unmute_conversation": "スレッドのミュートを解除"
"unmute_conversation": "スレッドのミュートを解除",
"nsfw": "閲覧注意",
"expand": "広げる",
"status_deleted": "この投稿は削除されました",
"hide_content": "隠す",
"show_content": "見る",
"hide_full_subject": "隠す",
"show_full_subject": "全部見る",
"thread_muted_and_words": "以下の単語を含むため:",
"thread_muted": "ミュートされたスレッド",
"external_source": "外部ソース",
"copy_link": "リンクをコピー",
"status_unavailable": "利用できません",
"unbookmark": "ブックマーク解除",
"bookmark": "ブックマーク"
},
"user_card": {
"approve": "受け入れ",
@ -539,7 +670,7 @@
"media": "メディア",
"mention": "メンション",
"mute": "ミュート",
"muted": "ミュートしています",
"muted": "ミュートしています",
"per_day": "/日",
"remote_follow": "リモートフォロー",
"report": "通報",
@ -547,11 +678,11 @@
"subscribe": "購読",
"unsubscribe": "購読を解除",
"unblock": "ブロック解除",
"unblock_progress": "ブロックを解除しています...",
"block_progress": "ブロックしています...",
"unblock_progress": "ブロックを解除しています",
"block_progress": "ブロックしています",
"unmute": "ミュート解除",
"unmute_progress": "ミュートを解除しています...",
"mute_progress": "ミュートしています...",
"unmute_progress": "ミュートを解除しています",
"mute_progress": "ミュートしています",
"admin_menu": {
"moderation": "モデレーション",
"grant_admin": "管理者権限を付与",
@ -570,7 +701,16 @@
"quarantine": "他のインスタンスからの投稿を止める",
"delete_user": "ユーザーを削除",
"delete_user_confirmation": "あなたの精神状態に何か問題はございませんか? この操作を取り消すことはできません。"
}
},
"roles": {
"moderator": "モデレーター",
"admin": "管理者"
},
"show_repeats": "リピートを見る",
"hide_repeats": "リピートを隠す",
"message": "メッセージ",
"hidden": "隠す",
"bot": "bot"
},
"user_profile": {
"timeline_title": "ユーザータイムライン",
@ -595,13 +735,18 @@
"repeat": "リピート",
"reply": "返信",
"favorite": "お気に入り",
"user_settings": "ユーザー設定"
"user_settings": "ユーザー設定",
"bookmark": "ブックマーク",
"reject_follow_request": "フォローリクエストを拒否",
"accept_follow_request": "フォローリクエストを許可",
"add_reaction": "リアクションを追加"
},
"upload":{
"upload": {
"error": {
"base": "アップロードに失敗しました。",
"file_too_big": "ファイルが大きすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
"default": "しばらくしてから試してください"
"base": "アップロードに失敗しました。",
"file_too_big": "ファイルが大きすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
"default": "しばらくしてから試してください",
"message": "アップロードに失敗: {0}"
},
"file_size_units": {
"B": "B",
@ -626,6 +771,77 @@
"check_email": "パスワードをリセットするためのリンクが記載されたメールが届いているか確認してください。",
"return_home": "ホームページに戻る",
"too_many_requests": "試行回数の制限に達しました。しばらく時間を置いてから再試行してください。",
"password_reset_disabled": "このインスタンスではパスワードリセットは無効になっています。インスタンスの管理者に連絡してください。"
"password_reset_disabled": "このインスタンスではパスワードリセットは無効になっています。インスタンスの管理者に連絡してください。",
"password_reset_required_but_mailer_is_disabled": "パスワードの初期化が必要ですが、初期化は使えません。インスタンスの管理者に連絡してください。",
"password_reset_required": "ログインするためにパスワードを初期化してください。"
},
"about": {
"mrf": {
"mrf_policies_desc": "MRFポリシーは、インスタンスの振る舞いを操作します。以下のポリシーが有効になっています:",
"federation": "連合",
"simple": {
"media_nsfw_desc": "このインスタンスでは、以下のインスタンスからの投稿に対して、メディアを閲覧注意に設定します:",
"media_nsfw": "メディアを閲覧注意に設定",
"media_removal_desc": "このインスタンスでは、以下のインスタンスからの投稿に対して、メディアを除去します:",
"media_removal": "メディア除去",
"ftl_removal": "「接続しているすべてのネットワーク」タイムラインから除外",
"ftl_removal_desc": "このインスタンスでは、以下のインスタンスを「接続しているすべてのネットワーク」タイムラインから除外します:",
"quarantine_desc": "このインスタンスでは、以下のインスタンスに対して公開投稿のみを送信します:",
"quarantine": "検疫",
"reject_desc": "このインスタンスでは、以下のインスタンスからのメッセージを受け付けません:",
"accept_desc": "このインスタンスでは、以下のインスタンスからのメッセージのみを受け付けます:",
"accept": "許可",
"simple_policies": "インスタンス固有のポリシー",
"reject": "拒否"
},
"mrf_policies": "有効なMRFポリシー",
"keyword": {
"replace": "置き換え",
"ftl_removal": "「接続しているすべてのネットワーク」タイムラインから除外",
"keyword_policies": "キーワードポリシー",
"is_replaced_by": "→",
"reject": "拒否"
}
},
"staff": "スタッフ"
},
"display_date": {
"today": "今日"
},
"file_type": {
"file": "ファイル",
"image": "画像",
"video": "ビデオ",
"audio": "オーディオ"
},
"remote_user_resolver": {
"error": "見つかりませんでした。",
"searching_for": "検索中",
"remote_user_resolver": "リモートユーザーリゾルバ"
},
"errors": {
"storage_unavailable": "ブラウザのストレージに接続できなかったため、ログインや設定情報は保存されません。Cookieを有効にしてください。"
},
"shoutbox": {
"title": "Shoutbox"
},
"chats": {
"empty_chat_list_placeholder": "チャットはありません。新規チャットのボタンを押して始めましょう!",
"error_sending_message": "メッセージの送信に失敗しました。",
"error_loading_chat": "チャットの読み込みに失敗しました。",
"delete_confirm": "このメッセージを本当に消してもいいですか?",
"more": "もっと見る",
"empty_message_error": "メッセージを入力して下さい",
"new": "新規チャット",
"chats": "チャット一覧",
"delete": "削除",
"message_user": "{nickname} にメッセージ",
"you": "あなた:"
},
"domain_mute_card": {
"unmute_progress": "ミュート解除中…",
"unmute": "ミュート解除",
"mute_progress": "ミュート中…",
"mute": "ミュート"
}
}

View file

@ -9,7 +9,9 @@
"scope_options": "범위 옵션",
"text_limit": "텍스트 제한",
"title": "기능",
"who_to_follow": "팔로우 추천"
"who_to_follow": "팔로우 추천",
"upload_limit": "최대 파일용량",
"pleroma_chat_messages": "Pleroma 채트"
},
"finder": {
"error_fetching_user": "사용자 정보 불러오기 실패",
@ -17,7 +19,27 @@
},
"general": {
"apply": "적용",
"submit": "보내기"
"submit": "보내기",
"loading": "로딩중…",
"peek": "숨기기",
"close": "닫기",
"verify": "검사",
"confirm": "확인",
"enable": "유효",
"disable": "무효",
"cancel": "취소",
"dismiss": "무시",
"show_less": "접기",
"show_more": "더 보기",
"optional": "필수 아님",
"retry": "다시 시도하십시오",
"error_retry": "다시 시도하십시오",
"generic_error": "잘못되었습니다",
"more": "더 보기",
"role": {
"moderator": "중재자",
"admin": "관리자"
}
},
"login": {
"login": "로그인",
@ -26,10 +48,19 @@
"password": "암호",
"placeholder": "예시: lain",
"register": "가입",
"username": "사용자 이름"
"username": "사용자 이름",
"heading": {
"recovery": "2단계 복구",
"totp": "2단계인증"
},
"recovery_code": "복구 코드",
"enter_two_factor_code": "2단계인증 코드를 입력하십시오",
"enter_recovery_code": "복구 코드를 입력하십시오",
"authentication_code": "인증 코드",
"hint": "로그인하여 대화에 참가합시다"
},
"nav": {
"about": "About",
"about": "인스턴스 소개",
"back": "뒤로",
"chat": "로컬 챗",
"friend_requests": "팔로우 요청",
@ -37,18 +68,29 @@
"dms": "다이렉트 메시지",
"public_tl": "공개 타임라인",
"timeline": "타임라인",
"twkn": "모든 알려진 네트워크",
"twkn": "알려진 네트워크",
"user_search": "사용자 검색",
"preferences": "환경설정"
"preferences": "환경설정",
"chats": "채트",
"timelines": "타임라인",
"who_to_follow": "추천된 사용자",
"search": "검색",
"bookmarks": "북마크",
"interactions": "대화",
"administration": "관리"
},
"notifications": {
"broken_favorite": "알 수 없는 게시물입니다, 검색 합니다...",
"broken_favorite": "알 수 없는 게시물입니다, 검색합니다…",
"favorited_you": "당신의 게시물을 즐겨찾기",
"followed_you": "당신을 팔로우",
"load_older": "오래 된 알림 불러오기",
"notifications": "알림",
"read": "읽음!",
"repeated_you": "당신의 게시물을 리핏"
"repeated_you": "당신의 게시물을 리핏",
"no_more_notifications": "알림이 없습니다",
"migrated_to": "이사했습니다",
"reacted_with": "{0} 로 반응했습니다",
"error": "알림 불러오기 실패: {0}"
},
"post_status": {
"new_status": "새 게시물 게시",
@ -56,10 +98,13 @@
"account_not_locked_warning_link": "잠김",
"attachments_sensitive": "첨부물을 민감함으로 설정",
"content_type": {
"text/plain": "평문"
"text/plain": "평문",
"text/bbcode": "BBCode",
"text/markdown": "Markdown",
"text/html": "HTML"
},
"content_warning": "주제 (필수 아님)",
"default": "LA에 도착!",
"default": "인천공항에 도착했습니다.",
"direct_warning": "이 게시물을 멘션 된 사용자들에게만 보여집니다",
"posting": "게시",
"scope": {
@ -67,7 +112,15 @@
"private": "팔로워 전용 - 팔로워들에게만",
"public": "공개 - 공개 타임라인으로",
"unlisted": "비공개 - 공개 타임라인에 게시 안 함"
}
},
"preview_empty": "아무것도 없습니다",
"preview": "미리보기",
"scope_notice": {
"public": "이 글은 누구나 볼 수 있습니다"
},
"media_description_error": "파일을 올리지 못하였습니다. 다시한번 시도하여 주십시오",
"empty_status_error": "글을 입력하십시오",
"media_description": "첨부파일 설명"
},
"registration": {
"bio": "소개",
@ -85,7 +138,9 @@
"password_required": "공백으로 둘 수 없습니다",
"password_confirmation_required": "공백으로 둘 수 없습니다",
"password_confirmation_match": "패스워드와 일치해야 합니다"
}
},
"fullname_placeholder": "예: 김례인",
"username_placeholder": "예: lain"
},
"settings": {
"attachmentRadius": "첨부물",
@ -112,7 +167,7 @@
"data_import_export_tab": "데이터 불러오기 / 내보내기",
"default_vis": "기본 공개 범위",
"delete_account": "계정 삭제",
"delete_account_description": "계정과 메시지를 영구히 삭제.",
"delete_account_description": "데이터가 영구히 삭제되고 계정이 불활성화됩니다.",
"delete_account_error": "계정을 삭제하는데 문제가 있습니다. 계속 발생한다면 인스턴스 관리자에게 문의하세요.",
"delete_account_instructions": "계정 삭제를 확인하기 위해 아래에 패스워드 입력.",
"export_theme": "프리셋 저장",
@ -156,7 +211,7 @@
"notification_visibility_repeats": "반복",
"no_rich_text_description": "모든 게시물의 서식을 지우기",
"hide_follows_description": "내가 팔로우하는 사람을 표시하지 않음",
"hide_followers_description": "나를 따르는 사람을 보여주지 마라.",
"hide_followers_description": "나를 따르는 사람을 숨기기",
"nsfw_clickthrough": "NSFW 이미지 \"클릭해서 보이기\"를 활성화",
"oauth_tokens": "OAuth 토큰",
"token": "토큰",
@ -247,7 +302,16 @@
"borders": "테두리",
"buttons": "버튼",
"inputs": "입력칸",
"faint_text": "흐려진 텍스트"
"faint_text": "흐려진 텍스트",
"chat": {
"border": "경계선",
"outgoing": "송신",
"incoming": "수신"
},
"selectedMenu": "선택된 메뉴 요소",
"selectedPost": "선택된 글",
"icons": "아이콘",
"alert_warning": "경고"
},
"radii": {
"_tab_label": "둥글기"
@ -303,14 +367,45 @@
"button": "버튼",
"text": "더 많은 {0} 그리고 {1}",
"mono": "내용",
"input": "LA에 막 도착!",
"input": "인천공항에 도착했습니다.",
"faint_link": "도움 되는 설명서",
"fine_print": "우리의 {0} 를 읽고 도움 되지 않는 것들을 배우자!",
"header_faint": "이건 괜찮아",
"checkbox": "나는 약관을 대충 훑어보았습니다",
"link": "작고 귀여운 링크"
}
}
},
"block_export": "차단 목록 내보내기",
"mfa": {
"scan": {
"secret_code": "키",
"title": "스캔"
},
"authentication_methods": "인증 방법",
"waiting_a_recovery_codes": "예비 코드를 수신하고 있습니다…",
"recovery_codes": "복구 코드.",
"generate_new_recovery_codes": "새로운 복구 코드를 작성",
"title": "2단계인증",
"confirm_and_enable": "OTP 확인과 활성화",
"setup_otp": "OTP 설치",
"otp": "OTP"
},
"security": "보안",
"emoji_reactions_on_timeline": "이모지 반응을 타임라인으로 표시",
"avatar_size_instruction": "크기를 150x150 이상으로 설정할 것을 추장합니다.",
"blocks_tab": "차단",
"notification_setting_privacy": "보안",
"user_mutes": "사용자",
"notification_visibility_emoji_reactions": "반응",
"profile_fields": {
"value": "내용"
},
"mutes_and_blocks": "침묵과 차단",
"chatMessageRadius": "챗 메시지",
"change_email": "전자메일 주소 바꾸기",
"changed_email": "메일주소가 갱신되었습니다!",
"bot": "이 계정은 bot입니다",
"mutes_tab": "침묵"
},
"timeline": {
"collapse": "접기",
@ -339,7 +434,7 @@
"its_you": "당신입니다!",
"mute": "침묵",
"muted": "침묵 됨",
"per_day": " / 하루",
"per_day": "/ 하루",
"remote_follow": "원격 팔로우",
"statuses": "게시물"
},
@ -357,11 +452,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": "바이트",
@ -370,5 +465,122 @@
"GiB": "기비바이트",
"TiB": "테비바이트"
}
},
"interactions": {
"follows": "새 팔로워",
"favs_repeats": "반복과 즐겨찾기"
},
"emoji": {
"load_all": "전체 {emojiAmount} 이모지 불러오기",
"unicode": "Unicode 이모지",
"custom": "전용 이모지",
"add_emoji": "이모지 넣기",
"search_emoji": "이모지 검색",
"emoji": "이모지",
"stickers": "스티커"
},
"polls": {
"add_poll": "투표를 추가",
"votes": "표",
"vote": "투표",
"type": "투표 형식",
"expiry": "투표 기간",
"votes_count": "{count} 표 | {count} 표",
"people_voted_count": "{count} 명 투표 | {count} 명 투표",
"option": "선택지",
"add_option": "선택지 추가"
},
"media_modal": {
"next": "다음",
"previous": "이전"
},
"importer": {
"error": "이 파일을 가져올 때 오류가 발생하였습니다.",
"success": "정상히 불러왔습니다.",
"submit": "보내기"
},
"image_cropper": {
"cancel": "취소",
"save_without_cropping": "그대로 저장",
"save": "저장",
"crop_picture": "사진 자르기"
},
"exporter": {
"processing": "처리중입니다, 처리가 끝나면 파일을 다운로드하라는 지시가 있겠습니다",
"export": "내보내기"
},
"domain_mute_card": {
"unmute_progress": "침묵을 해제중…",
"unmute": "침묵 해제",
"mute_progress": "침묵으로 설정중…",
"mute": "침묵"
},
"about": {
"staff": "운영자",
"mrf": {
"simple": {
"media_nsfw_desc": "이 인스턴스에서는 아래의 인스턴스로부터 보내온 투고에 붙혀 있는 매체는 민감함으로 설정됩니다:",
"media_nsfw": "매체를 민감함으로 설정",
"media_removal_desc": "이 인스턴스에서는 아래의 인스턴스로부터 보내온 투고에 붙혀 있는 매체는 제거됩니다:",
"media_removal": "매체 제거",
"ftl_removal_desc": "이 인스턴스에서 아래의 인스턴스들은 \"알려진 모든 네트워크\" 타임라인에서 제외됩니다:",
"ftl_removal": "\"알려진 모든 네트워크\" 타임라인에서 제외",
"quarantine_desc": "이 인스턴스는 아래의 인스턴스에게 공개투고만을 보냅니다:",
"quarantine": "검역",
"reject_desc": "이 인스턴스에서는 아래의 인스턴스로부터 보내온 투고를 받아들이지 않습니다:",
"accept_desc": "이 인스턴스에서는 아래의 인스턴스로부터 보내온 투고만이 접수됩니다:",
"reject": "거부",
"accept": "허가",
"simple_policies": "인스턴스 특유의 폴리시"
},
"mrf_policies": "사용되는 MRF 폴리시",
"keyword": {
"is_replaced_by": "→",
"replace": "바꾸기",
"reject": "거부",
"ftl_removal": "\"알려진 모든 네트워크\" 타임라인에서 제외",
"keyword_policies": "단어 폴리시"
},
"federation": "연합"
}
},
"shoutbox": {
"title": "Shoutbox"
},
"time": {
"years_short": "{0} 년",
"year_short": "{0} 년",
"years": "{0} 년",
"year": "{0} 년",
"weeks_short": "{0} 주일",
"week_short": "{0} 주일",
"weeks": "{0} 주일",
"week": "{0} 주일",
"seconds_short": "{0} 초",
"second_short": "{0} 초",
"seconds": "{0} 초",
"second": "{0} 초",
"now_short": "방금",
"now": "방끔",
"months_short": "{0} 달 전",
"month_short": "{0} 달 전",
"months": "{0} 달 전",
"month": "{0} 달 전",
"minutes_short": "{0} 분",
"minute_short": "{0} 분",
"minutes": "{0} 분",
"minute": "{0} 분",
"in_past": "{0} 전",
"hours_short": "{0} 시간",
"hour_short": "{0} 시간",
"hours": "{0} 시간",
"hour": "{0} 시간",
"days_short": "{0} 일",
"day_short": "{0} 일",
"days": "{0} 일",
"day": "{0} 일"
},
"remote_user_resolver": {
"error": "찾을 수 없습니다."
}
}

View file

@ -57,9 +57,9 @@
"enter_recovery_code": "Skriv inn en gjenopprettingskode",
"enter_two_factor_code": "Skriv inn en to-faktors kode",
"recovery_code": "Gjenopprettingskode",
"heading" : {
"totp" : "To-faktors autentisering",
"recovery" : "To-faktors gjenoppretting"
"heading": {
"totp": "To-faktors autentisering",
"recovery": "To-faktors gjenoppretting"
}
},
"media_modal": {
@ -72,7 +72,7 @@
"chat": "Lokal nettprat",
"friend_requests": "Følgeforespørsler",
"mentions": "Nevnt",
"interactions": "Interaksjooner",
"interactions": "Interaksjoner",
"dms": "Direktemeldinger",
"public_tl": "Offentlig Tidslinje",
"timeline": "Tidslinje",
@ -80,7 +80,9 @@
"user_search": "Søk etter brukere",
"search": "Søk",
"who_to_follow": "Kontoer å følge",
"preferences": "Innstillinger"
"preferences": "Innstillinger",
"timelines": "Tidslinjer",
"bookmarks": "Bokmerker"
},
"notifications": {
"broken_favorite": "Ukjent status, leter etter den...",
@ -90,7 +92,8 @@
"notifications": "Varslinger",
"read": "Les!",
"repeated_you": "Gjentok din status",
"no_more_notifications": "Ingen gjenstående varsler"
"no_more_notifications": "Ingen gjenstående varsler",
"follow_request": "ønsker å følge deg"
},
"polls": {
"add_poll": "Legg til undersøkelse",
@ -134,7 +137,7 @@
"public": "Denne statusen vil være synlig for alle",
"private": "Denne statusen vil være synlig for dine følgere",
"unlisted": "Denne statusen vil ikke være synlig i Offentlig Tidslinje eller Det Hele Kjente Nettverket"
},
},
"scope": {
"direct": "Direkte, publiser bare til nevnte brukere",
"private": "Bare følgere, publiser bare til brukere som følger deg",
@ -171,17 +174,17 @@
"security": "Sikkerhet",
"enter_current_password_to_confirm": "Skriv inn ditt nåverende passord for å bekrefte din identitet",
"mfa": {
"otp" : "OTP",
"setup_otp" : "Set opp OTP",
"wait_pre_setup_otp" : "forhåndsstiller OTP",
"confirm_and_enable" : "Bekreft og slå på OTP",
"otp": "OTP",
"setup_otp": "Set opp OTP",
"wait_pre_setup_otp": "forhåndsstiller OTP",
"confirm_and_enable": "Bekreft og slå på OTP",
"title": "To-faktors autentisering",
"generate_new_recovery_codes" : "Generer nye gjenopprettingskoder",
"warning_of_generate_new_codes" : "Når du genererer nye gjenopprettingskoder, vil de gamle slutte å fungere.",
"recovery_codes" : "Gjenopprettingskoder.",
"generate_new_recovery_codes": "Generer nye gjenopprettingskoder",
"warning_of_generate_new_codes": "Når du genererer nye gjenopprettingskoder, vil de gamle slutte å fungere.",
"recovery_codes": "Gjenopprettingskoder.",
"waiting_a_recovery_codes": "Mottar gjenopprettingskoder...",
"recovery_codes_warning" : "Skriv disse kodene ned eller plasser dem ett sikkert sted - ellers så vil du ikke se dem igjen. Dersom du mister tilgang til din to-faktors app og dine gjenopprettingskoder, vil du bli stengt ute av kontoen din.",
"authentication_methods" : "Autentiseringsmetoder",
"recovery_codes_warning": "Skriv disse kodene ned eller plasser dem ett sikkert sted - ellers så vil du ikke se dem igjen. Dersom du mister tilgang til din to-faktors app og dine gjenopprettingskoder, vil du bli stengt ute av kontoen din.",
"authentication_methods": "Autentiseringsmetoder",
"scan": {
"title": "Skann",
"desc": "Ved hjelp av din to-faktors applikasjon, skann denne QR-koden eller skriv inn tekstnøkkelen",
@ -579,7 +582,7 @@
"favorite": "Lik",
"user_settings": "Brukerinnstillinger"
},
"upload":{
"upload": {
"error": {
"base": "Det oppsto en feil under opplastning.",
"file_too_big": "Fil for stor [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",

View file

@ -35,7 +35,12 @@
"retry": "Tenta novamente",
"error_retry": "Por favor, tenta novamente",
"loading": "A carregar…",
"dismiss": "Ignorar"
"dismiss": "Ignorar",
"role":
{
"moderator": "Moderador",
"admin": "Admin"
}
},
"image_cropper": {
"crop_picture": "Cortar imagem",
@ -615,11 +620,7 @@
"report": "Denunciar",
"message": "Mensagem",
"mention": "Mencionar",
"hidden": "Ocultar",
"roles": {
"moderator": "Moderador",
"admin": "Admin"
}
"hidden": "Ocultar"
},
"user_profile": {
"timeline_title": "Cronologia do Utilizador",

View file

@ -24,7 +24,11 @@
"retry": "Попробуйте еще раз",
"error_retry": "Пожалуйста попробуйте еще раз",
"close": "Закрыть",
"loading": "Загрузка…"
"loading": "Загрузка…",
"role": {
"moderator": "Модератор",
"admin": "Администратор"
}
},
"login": {
"login": "Войти",
@ -541,11 +545,7 @@
"mention": "Упомянуть",
"show_repeats": "Показывать повторы",
"hide_repeats": "Скрыть повторы",
"report": "Пожаловаться",
"roles": {
"moderator": "Модератор",
"admin": "Администратор"
}
"report": "Пожаловаться"
},
"user_profile": {
"timeline_title": "Лента пользователя"
@ -615,7 +615,8 @@
"gopher": "Gopher",
"who_to_follow": "Предложения кого читать",
"pleroma_chat_messages": "Pleroma Чат",
"upload_limit": "Наибольший размер загружаемого файла"
"upload_limit": "Наибольший размер загружаемого файла",
"scope_options": "Настраиваемая видимость статусов"
},
"tool_tip": {
"accept_follow_request": "Принять запрос на чтение",

View file

@ -17,7 +17,11 @@
"more": "Більше",
"submit": "Відправити",
"apply": "Застосувати",
"peek": "Глянути"
"peek": "Глянути",
"role": {
"moderator": "Модератор",
"admin": "Адміністратор"
}
},
"finder": {
"error_fetching_user": "Користувача не знайдено",
@ -760,11 +764,7 @@
"unblock": "Розблокувати",
"remote_follow": "Підписатись",
"muted": "Заглушений",
"mute": "Заглушити",
"roles": {
"moderator": "Модератор",
"admin": "Адміністратор"
}
"mute": "Заглушити"
},
"status": {
"copy_link": "Скопіювати посилання на допис",

View file

@ -39,7 +39,11 @@
"close": "关闭",
"retry": "重试",
"error_retry": "请重试",
"loading": "载入中…"
"loading": "载入中…",
"role": {
"moderator": "监察员",
"admin": "管理员"
}
},
"image_cropper": {
"crop_picture": "裁剪图片",
@ -120,7 +124,9 @@
"expiry": "投票期限",
"expires_in": "投票于 {0} 后结束",
"expired": "投票 {0} 前已结束",
"not_enough_options": "投票的选项太少"
"not_enough_options": "投票的选项太少",
"votes_count": "{count} 票 | {count} 票",
"people_voted_count": "{count} 人已投票 | {count} 人已投票"
},
"stickers": {
"add_sticker": "添加贴纸"
@ -183,7 +189,9 @@
"password_required": "不能留空",
"password_confirmation_required": "不能留空",
"password_confirmation_match": "密码不一致"
}
},
"reason_placeholder": "此实例的注册需要手动批准。\n请让管理员知道您为什么想要注册。",
"reason": "注册理由"
},
"selectable_list": {
"select_all": "选择全部"
@ -552,7 +560,8 @@
"mute_import": "隐藏名单导入",
"mute_export_button": "导出你的隐藏名单到一个 csv 文件",
"mute_export": "隐藏名单导出",
"hide_wallpaper": "隐藏实例壁纸"
"hide_wallpaper": "隐藏实例壁纸",
"setting_changed": "与默认设置不同"
},
"time": {
"day": "{0} 天",
@ -683,7 +692,8 @@
"show_repeats": "显示转发",
"hide_repeats": "隐藏转发",
"message": "消息",
"mention": "提及"
"mention": "提及",
"bot": "机器人"
},
"user_profile": {
"timeline_title": "用户时间线",

View file

@ -25,7 +25,7 @@
"add_poll": "增加投票"
},
"notifications": {
"reacted_with": "和 {0} 互動過",
"reacted_with": "作出了 {0} 的反應",
"migrated_to": "遷移到",
"no_more_notifications": "沒有更多的通知",
"repeated_you": "轉發了你的發文",
@ -54,7 +54,7 @@
"mentions": "提及",
"friend_requests": "關注請求",
"back": "後退",
"administration": "管理",
"administration": "管理",
"about": "關於"
},
"media_modal": {
@ -216,7 +216,8 @@
"incoming": "收到",
"outgoing": "發出",
"border": "邊框"
}
},
"wallpaper": "桌布"
},
"preview": {
"header_faint": "這很正常",
@ -412,7 +413,7 @@
"hide_follows_description": "不要顯示我所關注的人",
"hide_followers_description": "不要顯示關注我的人",
"hide_follows_count_description": "不顯示關注數",
"nsfw_clickthrough": "將敏感附件隱藏,點擊才能打開",
"nsfw_clickthrough": "將敏感附件和鏈接隱藏,點擊才能打開",
"valid_until": "有效期至",
"panelRadius": "面板",
"pause_on_unfocused": "在離開頁面時暫停時間線推送",
@ -572,16 +573,20 @@
"thread_muted_and_words": ",有这些字:",
"hide_full_subject": "隱藏完整標題",
"show_content": "顯示內容",
"hide_content": "隱藏內容"
"hide_content": "隱藏內容",
"status_deleted": "該帖已被刪除",
"expand": "展开",
"external_source": "外部來源",
"nsfw": "工作不安全"
},
"time": {
"hours": "{0} 小時",
"hours": "{0} 時",
"days_short": "{0}天",
"day_short": "{0}天",
"days": "{0} 天",
"hour": "{0} 小时",
"hour_short": "{0}h",
"hours_short": "{0}h",
"hour": "{0} ",
"hour_short": "{0}",
"hours_short": "{0}",
"years_short": "{0} y",
"now": "剛剛",
"day": "{0} 天",
@ -655,7 +660,8 @@
"reload": "重新載入",
"up_to_date": "已是最新",
"no_more_statuses": "没有更多發文",
"no_statuses": "没有發文"
"no_statuses": "没有發文",
"error": "取得時間線時發生錯誤:{0}"
},
"interactions": {
"load_older": "載入更早的互動",
@ -746,7 +752,11 @@
"unmute": "取消靜音",
"unmute_progress": "取消靜音中…",
"hide_repeats": "隱藏轉發",
"show_repeats": "顯示轉發"
"show_repeats": "顯示轉發",
"roles": {
"moderator": "主持人",
"admin": "管理員"
}
},
"user_profile": {
"timeline_title": "用戶時間線",
@ -788,7 +798,8 @@
"error": {
"base": "上傳失敗。",
"file_too_big": "文件太大[{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
"default": "稍後再試"
"default": "稍後再試",
"message": "上傳錯誤:{0}"
}
},
"search": {

View file

@ -28,7 +28,6 @@ import pushNotifications from './lib/push_notifications_plugin.js'
import messages from './i18n/messages.js'
import VueChatScroll from 'vue-chat-scroll'
import VueClickOutside from 'v-click-outside'
import PortalVue from 'portal-vue'
import VBodyScrollLock from './directives/body_scroll_lock'
@ -42,7 +41,6 @@ const currentLocale = (window.navigator.language || 'en').split('-')[0]
Vue.use(Vuex)
Vue.use(VueRouter)
Vue.use(VueI18n)
Vue.use(VueChatScroll)
Vue.use(VueClickOutside)
Vue.use(PortalVue)
Vue.use(VBodyScrollLock)

View file

@ -18,6 +18,7 @@ const chat = {
actions: {
initializeChat (store, socket) {
const channel = socket.channel('chat:public')
channel.on('new_msg', (msg) => {
store.commit('addMessage', msg)
})

View file

@ -115,6 +115,9 @@ const chats = {
},
handleMessageError ({ commit }, value) {
commit('handleMessageError', { commit, ...value })
},
cullOlderMessages ({ commit }, chatId) {
commit('cullOlderMessages', chatId)
}
},
mutations: {
@ -227,6 +230,9 @@ const chats = {
handleMessageError (state, { chatId, fakeId, isRetry }) {
const chatMessageService = state.openedChatMessageServices[chatId]
chatService.handleMessageError(chatMessageService, fakeId, isRetry)
},
cullOlderMessages (state, chatId) {
chatService.cullOlderMessages(state.openedChatMessageServices[chatId])
}
}
}

View file

@ -70,7 +70,8 @@ export const defaultState = {
greentext: undefined, // instance default
hidePostStats: undefined, // instance default
hideUserStats: undefined, // instance default
virtualScrolling: undefined // instance default
virtualScrolling: undefined, // instance default
sensitiveByDefault: undefined // instance default
}
// caching the instance default properties

View file

@ -43,6 +43,7 @@ const defaultState = {
subjectLineBehavior: 'email',
theme: 'pleroma-dark',
virtualScrolling: true,
sensitiveByDefault: false,
// Nasty stuff
customEmoji: [],

View file

@ -13,7 +13,11 @@ import {
omitBy
} from 'lodash'
import { set } from 'vue'
import { isStatusNotification, maybeShowNotification } from '../services/notification_utils/notification_utils.js'
import {
isStatusNotification,
isValidNotification,
maybeShowNotification
} from '../services/notification_utils/notification_utils.js'
import apiService from '../services/api/api.service.js'
const emptyTl = (userId = 0) => ({
@ -310,8 +314,24 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
}
}
const updateNotificationsMinMaxId = (state, notification) => {
state.notifications.maxId = notification.id > state.notifications.maxId
? notification.id
: state.notifications.maxId
state.notifications.minId = notification.id < state.notifications.minId
? notification.id
: state.notifications.minId
}
const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters, newNotificationSideEffects }) => {
each(notifications, (notification) => {
// If invalid notification, update ids but don't add it to store
if (!isValidNotification(notification)) {
console.error('Invalid notification:', notification)
updateNotificationsMinMaxId(state, notification)
return
}
if (isStatusNotification(notification.type)) {
notification.action = addStatusToGlobalStorage(state, notification.action).item
notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
@ -323,12 +343,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
// Only add a new notification if we don't have one for the same action
if (!state.notifications.idStore.hasOwnProperty(notification.id)) {
state.notifications.maxId = notification.id > state.notifications.maxId
? notification.id
: state.notifications.maxId
state.notifications.minId = notification.id < state.notifications.minId
? notification.id
: state.notifications.minId
updateNotificationsMinMaxId(state, notification)
state.notifications.data.push(notification)
state.notifications.idStore[notification.id] = notification

View file

@ -48,6 +48,22 @@ const deleteMessage = (storage, messageId) => {
}
}
const cullOlderMessages = (storage) => {
const maxIndex = storage.messages.length
const minIndex = maxIndex - 50
if (maxIndex <= 50) return
storage.messages = _.sortBy(storage.messages, ['id'])
storage.minId = storage.messages[minIndex].id
for (const message of storage.messages) {
if (message.id < storage.minId) {
delete storage.idIndex[message.id]
delete storage.idempotencyKeyIndex[message.idempotency_key]
}
}
storage.messages = storage.messages.slice(minIndex, maxIndex)
}
const handleMessageError = (storage, fakeId, isRetry) => {
if (!storage) { return }
const fakeMessage = storage.idIndex[fakeId]
@ -201,6 +217,7 @@ const ChatService = {
empty,
getView,
deleteMessage,
cullOlderMessages,
resetNewMessageCount,
clear,
handleMessageError

View file

@ -203,7 +203,8 @@ export const parseUser = (data) => {
output.rights = output.rights || {}
output.notification_settings = output.notification_settings || {}
// Convert punycode to unicode
// Convert punycode to unicode for UI
output.screen_name_ui = output.screen_name
if (output.screen_name.includes('@')) {
const parts = output.screen_name.split('@')
let unicodeDomain = punycode.toUnicode(parts[1])
@ -211,7 +212,7 @@ export const parseUser = (data) => {
// Add some identifier so users can potentially spot spoofing attempts:
// lain.com and xn--lin-6cd.com would appear identical otherwise.
unicodeDomain = '🌏' + unicodeDomain
output.screen_name = [parts[0], unicodeDomain].join('@')
output.screen_name_ui = [parts[0], unicodeDomain].join('@')
}
}

View file

@ -22,6 +22,13 @@ const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reactio
export const isStatusNotification = (type) => includes(statusNotifications, type)
export const isValidNotification = (notification) => {
if (isStatusNotification(notification.type) && !notification.status) {
return false
}
return true
}
const sortById = (a, b) => {
const seqA = Number(a.id)
const seqB = Number(b.id)

View file

@ -31,13 +31,15 @@ const testGetters = {
const localUser = {
id: 100,
is_local: true,
screen_name: 'testUser'
screen_name: 'testUser',
screen_name_ui: 'testUser'
}
const extUser = {
id: 100,
is_local: false,
screen_name: 'testUser@test.instance'
screen_name: 'testUser@test.instance',
screen_name_ui: 'testUser@test.instance'
}
const externalProfileStore = new Vuex.Store({

View file

@ -88,4 +88,21 @@ describe('chatService', () => {
expect(view.map(i => i.type)).to.eql(['date', 'message', 'message', 'date', 'message'])
})
})
describe('.cullOlderMessages', () => {
it('keeps 50 newest messages and idIndex matches', () => {
const chat = chatService.empty()
for (let i = 100; i > 0; i--) {
// Use decimal values with toFixed to hack together constant length predictable strings
chatService.add(chat, { messages: [{ ...message1, id: 'a' + (i / 1000).toFixed(3), idempotency_key: i }] })
}
chatService.cullOlderMessages(chat)
expect(chat.messages.length).to.eql(50)
expect(chat.messages[0].id).to.eql('a0.051')
expect(chat.minId).to.eql('a0.051')
expect(chat.messages[49].id).to.eql('a0.100')
expect(Object.keys(chat.idIndex).length).to.eql(50)
})
})
})

View file

@ -315,7 +315,7 @@ describe('API Entities normalizer', () => {
it('converts IDN to unicode and marks it as internatonal', () => {
const user = makeMockUserMasto({ acct: 'lain@xn--lin-6cd.com' })
expect(parseUser(user)).to.have.property('screen_name').that.equal('lain@🌏lаin.com')
expect(parseUser(user)).to.have.property('screen_name_ui').that.equal('lain@🌏lаin.com')
})
})

View file

@ -7842,9 +7842,10 @@ shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
shelljs@^0.7.4:
version "0.7.8"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3"
shelljs@^0.8.4:
version "0.8.4"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2"
integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==
dependencies:
glob "^7.0.0"
interpret "^1.0.0"
@ -8922,10 +8923,6 @@ void-elements@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
vue-chat-scroll@^1.2.1:
version "1.3.5"
resolved "https://registry.yarnpkg.com/vue-chat-scroll/-/vue-chat-scroll-1.3.5.tgz#a5ee5bae5058f614818a96eac5ee3be4394a2f68"
vue-eslint-parser@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz#00f4e4da94ec974b821a26ff0ed0f7a78402b8a1"