Merge remote-tracking branch 'upstream/develop' into emoji-selector-update

* upstream/develop: (42 commits)
  Fix formatting in oc.json
  avoid using global class
  fix logo moving bug when lightbox is open
  Reserve scrollbar gap when body scroll is locked
  setting display: initial makes trouble, instead, toggle display: none using classname
  lock body scroll
  add body-scroll-lock directive
  install body-scroll-lock
  wire up props with PostStatusModal
  rename component
  recover autofocusing behavior
  refactor MobilePostStatusModal using new PostStatusModal
  add new module and modal to post new status
  remove needless condition
  add mention button
  wire up user state with global store
  collapse fav/repeat notifications from muted users
  do not collapse thread muted posts in conversation
  detect thread-muted posts
  do not change word based muting logic
  ...
This commit is contained in:
Henry Jameson 2019-09-25 20:26:49 +03:00
commit a3305799c7
40 changed files with 959 additions and 286 deletions

View file

@ -9,7 +9,7 @@
<link rel="stylesheet" href="/static/font/css/fontello.css"> <link rel="stylesheet" href="/static/font/css/fontello.css">
<link rel="stylesheet" href="/static/font/css/animation.css"> <link rel="stylesheet" href="/static/font/css/animation.css">
</head> </head>
<body> <body class="hidden">
<noscript>To use Pleroma, please enable JavaScript.</noscript> <noscript>To use Pleroma, please enable JavaScript.</noscript>
<div id="app"></div> <div id="app"></div>
<!-- built files will be auto injected --> <!-- built files will be auto injected -->

View file

@ -18,6 +18,7 @@
"@chenfengyuan/vue-qrcode": "^1.0.0", "@chenfengyuan/vue-qrcode": "^1.0.0",
"babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-lodash": "^3.2.11", "babel-plugin-lodash": "^3.2.11",
"body-scroll-lock": "^2.6.4",
"chromatism": "^3.0.0", "chromatism": "^3.0.0",
"cropperjs": "^1.4.3", "cropperjs": "^1.4.3",
"diff": "^3.0.1", "diff": "^3.0.1",

View file

@ -8,9 +8,10 @@ import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_pan
import ChatPanel from './components/chat_panel/chat_panel.vue' import ChatPanel from './components/chat_panel/chat_panel.vue'
import MediaModal from './components/media_modal/media_modal.vue' import MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue' import SideDrawer from './components/side_drawer/side_drawer.vue'
import MobilePostStatusModal from './components/mobile_post_status_modal/mobile_post_status_modal.vue' import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
import MobileNav from './components/mobile_nav/mobile_nav.vue' import MobileNav from './components/mobile_nav/mobile_nav.vue'
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue' import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
import { windowWidth } from './services/window_utils/window_utils' import { windowWidth } from './services/window_utils/window_utils'
export default { export default {
@ -26,9 +27,10 @@ export default {
ChatPanel, ChatPanel,
MediaModal, MediaModal,
SideDrawer, SideDrawer,
MobilePostStatusModal, MobilePostStatusButton,
MobileNav, MobileNav,
UserReportingModal UserReportingModal,
PostStatusModal
}, },
data: () => ({ data: () => ({
mobileActivePanel: 'timeline', mobileActivePanel: 'timeline',

View file

@ -10,13 +10,14 @@
position: fixed; position: fixed;
z-index: -1; z-index: -1;
height: 100%; height: 100%;
width: 100%; left: 0;
right: -20px;
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 0 50%; background-position: 0 50%;
} }
i { i[class^='icon-'] {
user-select: none; user-select: none;
} }
@ -49,6 +50,10 @@ body {
overflow-x: hidden; overflow-x: hidden;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
&.hidden {
display: none;
}
} }
a { a {
@ -343,6 +348,7 @@ i[class*=icon-] {
align-items: center; align-items: center;
position: fixed; position: fixed;
height: 50px; height: 50px;
box-sizing: border-box;
.logo { .logo {
display: flex; display: flex;
@ -382,6 +388,7 @@ i[class*=icon-] {
} }
.inner-nav { .inner-nav {
position: relative;
margin: auto; margin: auto;
box-sizing: border-box; box-sizing: border-box;
padding-left: 10px; padding-left: 10px;

View file

@ -4,6 +4,7 @@
:style="bgAppStyle" :style="bgAppStyle"
> >
<div <div
id="app_bg_wrapper"
class="app-bg-wrapper" class="app-bg-wrapper"
:style="bgStyle" :style="bgStyle"
/> />
@ -14,20 +15,20 @@
class="nav-bar container" class="nav-bar container"
@click="scrollToTop()" @click="scrollToTop()"
> >
<div
class="logo"
:style="logoBgStyle"
>
<div
class="mask"
:style="logoMaskStyle"
/>
<img
:src="logo"
:style="logoStyle"
>
</div>
<div class="inner-nav"> <div class="inner-nav">
<div
class="logo"
:style="logoBgStyle"
>
<div
class="mask"
:style="logoMaskStyle"
/>
<img
:src="logo"
:style="logoStyle"
>
</div>
<div class="item"> <div class="item">
<router-link <router-link
class="site-name" class="site-name"
@ -107,8 +108,9 @@
:floating="true" :floating="true"
class="floating-chat mobile-hidden" class="floating-chat mobile-hidden"
/> />
<MobilePostStatusModal /> <MobilePostStatusButton />
<UserReportingModal /> <UserReportingModal />
<PostStatusModal />
<portal-target name="modal" /> <portal-target name="modal" />
</div> </div>
</template> </template>

View file

@ -5,12 +5,8 @@ const conversationPage = {
Conversation Conversation
}, },
computed: { computed: {
statusoid () { statusId () {
const id = this.$route.params.id return this.$route.params.id
const statuses = this.$store.state.statuses.allStatusesObject
const status = statuses[id]
return status
} }
} }
} }

View file

@ -2,7 +2,7 @@
<conversation <conversation
:collapsable="false" :collapsable="false"
is-page="true" is-page="true"
:statusoid="statusoid" :status-id="statusId"
/> />
</template> </template>

View file

@ -1,4 +1,4 @@
import { reduce, filter, findIndex, clone } from 'lodash' import { reduce, filter, findIndex, clone, get } from 'lodash'
import Status from '../status/status.vue' import Status from '../status/status.vue'
const sortById = (a, b) => { const sortById = (a, b) => {
@ -39,10 +39,11 @@ const conversation = {
} }
}, },
props: [ props: [
'statusoid', 'statusId',
'collapsable', 'collapsable',
'isPage', 'isPage',
'pinnedStatusIdsObject' 'pinnedStatusIdsObject',
'inProfile'
], ],
created () { created () {
if (this.isPage) { if (this.isPage) {
@ -51,21 +52,17 @@ const conversation = {
}, },
computed: { computed: {
status () { status () {
return this.statusoid return this.$store.state.statuses.allStatusesObject[this.statusId]
}, },
statusId () { originalStatusId () {
if (this.statusoid.retweeted_status) { if (this.status.retweeted_status) {
return this.statusoid.retweeted_status.id return this.status.retweeted_status.id
} else { } else {
return this.statusoid.id return this.statusId
} }
}, },
conversationId () { conversationId () {
if (this.statusoid.retweeted_status) { return this.getConversationId(this.statusId)
return this.statusoid.retweeted_status.statusnet_conversation_id
} else {
return this.statusoid.statusnet_conversation_id
}
}, },
conversation () { conversation () {
if (!this.status) { if (!this.status) {
@ -77,7 +74,7 @@ const conversation = {
} }
const conversation = clone(this.$store.state.statuses.conversationsObject[this.conversationId]) const conversation = clone(this.$store.state.statuses.conversationsObject[this.conversationId])
const statusIndex = findIndex(conversation, { id: this.statusId }) const statusIndex = findIndex(conversation, { id: this.originalStatusId })
if (statusIndex !== -1) { if (statusIndex !== -1) {
conversation[statusIndex] = this.status conversation[statusIndex] = this.status
} }
@ -110,7 +107,15 @@ const conversation = {
Status Status
}, },
watch: { watch: {
status: 'fetchConversation', statusId (newVal, oldVal) {
const newConversationId = this.getConversationId(newVal)
const oldConversationId = this.getConversationId(oldVal)
if (newConversationId && oldConversationId && newConversationId === oldConversationId) {
this.setHighlight(this.originalStatusId)
} else {
this.fetchConversation()
}
},
expanded (value) { expanded (value) {
if (value) { if (value) {
this.fetchConversation() this.fetchConversation()
@ -120,24 +125,25 @@ const conversation = {
methods: { methods: {
fetchConversation () { fetchConversation () {
if (this.status) { if (this.status) {
this.$store.state.api.backendInteractor.fetchConversation({ id: this.status.id }) this.$store.state.api.backendInteractor.fetchConversation({ id: this.statusId })
.then(({ ancestors, descendants }) => { .then(({ ancestors, descendants }) => {
this.$store.dispatch('addNewStatuses', { statuses: ancestors }) this.$store.dispatch('addNewStatuses', { statuses: ancestors })
this.$store.dispatch('addNewStatuses', { statuses: descendants }) this.$store.dispatch('addNewStatuses', { statuses: descendants })
this.setHighlight(this.originalStatusId)
}) })
.then(() => this.setHighlight(this.statusId))
} else { } else {
const id = this.$route.params.id this.$store.state.api.backendInteractor.fetchStatus({ id: this.statusId })
this.$store.state.api.backendInteractor.fetchStatus({ id }) .then((status) => {
.then((status) => this.$store.dispatch('addNewStatuses', { statuses: [status] })) this.$store.dispatch('addNewStatuses', { statuses: [status] })
.then(() => this.fetchConversation()) this.fetchConversation()
})
} }
}, },
getReplies (id) { getReplies (id) {
return this.replies[id] || [] return this.replies[id] || []
}, },
focused (id) { focused (id) {
return (this.isExpanded) && id === this.status.id return (this.isExpanded) && id === this.statusId
}, },
setHighlight (id) { setHighlight (id) {
if (!id) return if (!id) return
@ -149,6 +155,10 @@ const conversation = {
}, },
toggleExpanded () { toggleExpanded () {
this.expanded = !this.expanded this.expanded = !this.expanded
},
getConversationId (statusId) {
const status = this.$store.state.statuses.allStatusesObject[statusId]
return get(status, 'retweeted_status.statusnet_conversation_id', get(status, 'statusnet_conversation_id'))
} }
} }
} }

View file

@ -26,6 +26,7 @@
:in-conversation="isExpanded" :in-conversation="isExpanded"
:highlight="getHighlight()" :highlight="getHighlight()"
:replies="getReplies(status.id)" :replies="getReplies(status.id)"
:in-profile="inProfile"
class="status-fadein panel-body" class="status-fadein panel-body"
@goto="setHighlight" @goto="setHighlight"
@toggleExpanded="toggleExpanded" @toggleExpanded="toggleExpanded"

View file

@ -10,14 +10,14 @@
<div slot="popover"> <div slot="popover">
<div class="dropdown-menu"> <div class="dropdown-menu">
<button <button
v-if="canMute && !status.muted" v-if="canMute && !status.thread_muted"
class="dropdown-item dropdown-item-icon" class="dropdown-item dropdown-item-icon"
@click.prevent="muteConversation" @click.prevent="muteConversation"
> >
<i class="icon-eye-off" /><span>{{ $t("status.mute_conversation") }}</span> <i class="icon-eye-off" /><span>{{ $t("status.mute_conversation") }}</span>
</button> </button>
<button <button
v-if="canMute && status.muted" v-if="canMute && status.thread_muted"
class="dropdown-item dropdown-item-icon" class="dropdown-item dropdown-item-icon"
@click.prevent="unmuteConversation" @click.prevent="unmuteConversation"
> >

View file

@ -1,6 +1,7 @@
<template> <template>
<div <div
v-if="showing" v-if="showing"
v-body-scroll-lock="showing"
class="modal-view media-modal-view" class="modal-view media-modal-view"
@click.prevent="hide" @click.prevent="hide"
> >
@ -43,6 +44,10 @@
.media-modal-view { .media-modal-view {
z-index: 1001; z-index: 1001;
body:not(.scroll-locked) & {
display: none;
}
&:hover { &:hover {
.modal-view-button-arrow { .modal-view-button-arrow {
opacity: 0.75; opacity: 0.75;

View file

@ -1,14 +1,9 @@
import PostStatusForm from '../post_status_form/post_status_form.vue'
import { debounce } from 'lodash' import { debounce } from 'lodash'
const MobilePostStatusModal = { const MobilePostStatusButton = {
components: {
PostStatusForm
},
data () { data () {
return { return {
hidden: false, hidden: false,
postFormOpen: false,
scrollingDown: false, scrollingDown: false,
inputActive: false, inputActive: false,
oldScrollPos: 0, oldScrollPos: 0,
@ -28,8 +23,8 @@ const MobilePostStatusModal = {
window.removeEventListener('resize', this.handleOSK) window.removeEventListener('resize', this.handleOSK)
}, },
computed: { computed: {
currentUser () { isLoggedIn () {
return this.$store.state.users.currentUser return !!this.$store.state.users.currentUser
}, },
isHidden () { isHidden () {
return this.autohideFloatingPostButton && (this.hidden || this.inputActive) return this.autohideFloatingPostButton && (this.hidden || this.inputActive)
@ -57,17 +52,7 @@ const MobilePostStatusModal = {
window.removeEventListener('scroll', this.handleScrollEnd) window.removeEventListener('scroll', this.handleScrollEnd)
}, },
openPostForm () { openPostForm () {
this.postFormOpen = true this.$store.dispatch('openPostStatusModal')
this.hidden = true
const el = this.$el.querySelector('textarea')
this.$nextTick(function () {
el.focus()
})
},
closePostForm () {
this.postFormOpen = false
this.hidden = false
}, },
handleOSK () { handleOSK () {
// This is a big hack: we're guessing from changed window sizes if the // This is a big hack: we're guessing from changed window sizes if the
@ -105,4 +90,4 @@ const MobilePostStatusModal = {
} }
} }
export default MobilePostStatusModal export default MobilePostStatusButton

View file

@ -1,23 +1,5 @@
<template> <template>
<div v-if="currentUser"> <div v-if="isLoggedIn">
<div
v-show="postFormOpen"
class="post-form-modal-view modal-view"
@click="closePostForm"
>
<div
class="post-form-modal-panel panel"
@click.stop=""
>
<div class="panel-heading">
{{ $t('post_status.new_status') }}
</div>
<PostStatusForm
class="panel-body"
@posted="closePostForm"
/>
</div>
</div>
<button <button
class="new-status-button" class="new-status-button"
:class="{ 'hidden': isHidden }" :class="{ 'hidden': isHidden }"
@ -28,27 +10,11 @@
</div> </div>
</template> </template>
<script src="./mobile_post_status_modal.js"></script> <script src="./mobile_post_status_button.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.post-form-modal-view {
align-items: flex-start;
}
.post-form-modal-panel {
flex-shrink: 0;
margin-top: 25%;
margin-bottom: 2em;
width: 100%;
max-width: 700px;
@media (orientation: landscape) {
margin-top: 8%;
}
}
.new-status-button { .new-status-button {
width: 5em; width: 5em;
height: 5em; height: 5em;

View file

@ -9,7 +9,8 @@ const Notification = {
data () { data () {
return { return {
userExpanded: false, userExpanded: false,
betterShadow: this.$store.state.interface.browserSupport.cssFilter betterShadow: this.$store.state.interface.browserSupport.cssFilter,
unmuted: false
} }
}, },
props: [ 'notification' ], props: [ 'notification' ],
@ -23,11 +24,14 @@ const Notification = {
toggleUserExpanded () { toggleUserExpanded () {
this.userExpanded = !this.userExpanded this.userExpanded = !this.userExpanded
}, },
userProfileLink (user) { generateUserProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames) return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
}, },
getUser (notification) { getUser (notification) {
return this.$store.state.users.usersObject[notification.from_profile.id] return this.$store.state.users.usersObject[notification.from_profile.id]
},
toggleMute () {
this.unmuted = !this.unmuted
} }
}, },
computed: { computed: {
@ -47,6 +51,12 @@ const Notification = {
return this.userInStore return this.userInStore
} }
return this.notification.from_profile return this.notification.from_profile
},
userProfileLink () {
return this.generateUserProfileLink(this.user)
},
needMute () {
return this.user.muted
} }
} }
} }

View file

@ -4,104 +4,126 @@
:compact="true" :compact="true"
:statusoid="notification.status" :statusoid="notification.status"
/> />
<div <div v-else>
v-else <div
class="non-mention" v-if="needMute && !unmuted"
:class="[userClass, { highlighted: userStyle }]" class="container muted"
:style="[ userStyle ]"
>
<a
class="avatar-container"
:href="notification.from_profile.statusnet_profile_url"
@click.stop.prevent.capture="toggleUserExpanded"
> >
<UserAvatar <small>
:compact="true" <router-link :to="userProfileLink">
:better-shadow="betterShadow" {{ notification.from_profile.screen_name }}
:user="notification.from_profile" </router-link>
/> </small>
</a> <a
<div class="notification-right"> href="#"
<UserCard class="unmute"
v-if="userExpanded" @click.prevent="toggleMute"
:user="getUser(notification)" ><i class="button-icon icon-eye-off" /></a>
:rounded="true" </div>
:bordered="true" <div
/> v-else
<span class="notification-details"> class="non-mention"
<div class="name-and-action"> :class="[userClass, { highlighted: userStyle }]"
<!-- eslint-disable vue/no-v-html --> :style="[ userStyle ]"
<span >
v-if="!!notification.from_profile.name_html" <a
class="username" class="avatar-container"
:title="'@'+notification.from_profile.screen_name" :href="notification.from_profile.statusnet_profile_url"
v-html="notification.from_profile.name_html" @click.stop.prevent.capture="toggleUserExpanded"
/> >
<!-- eslint-enable vue/no-v-html --> <UserAvatar
<span :compact="true"
v-else :better-shadow="betterShadow"
class="username" :user="notification.from_profile"
:title="'@'+notification.from_profile.screen_name" />
>{{ notification.from_profile.name }}</span> </a>
<span v-if="notification.type === 'like'"> <div class="notification-right">
<i class="fa icon-star lit" /> <UserCard
<small>{{ $t('notifications.favorited_you') }}</small> v-if="userExpanded"
</span> :user="getUser(notification)"
<span v-if="notification.type === 'repeat'"> :rounded="true"
<i :bordered="true"
class="fa icon-retweet lit" />
:title="$t('tool_tip.repeat')" <span class="notification-details">
<div class="name-and-action">
<!-- eslint-disable vue/no-v-html -->
<span
v-if="!!notification.from_profile.name_html"
class="username"
:title="'@'+notification.from_profile.screen_name"
v-html="notification.from_profile.name_html"
/> />
<small>{{ $t('notifications.repeated_you') }}</small> <!-- eslint-enable vue/no-v-html -->
</span> <span
<span v-if="notification.type === 'follow'"> v-else
<i class="fa icon-user-plus lit" /> class="username"
<small>{{ $t('notifications.followed_you') }}</small> :title="'@'+notification.from_profile.screen_name"
</span> >{{ notification.from_profile.name }}</span>
</div> <span v-if="notification.type === 'like'">
<i class="fa icon-star lit" />
<small>{{ $t('notifications.favorited_you') }}</small>
</span>
<span v-if="notification.type === 'repeat'">
<i
class="fa icon-retweet lit"
:title="$t('tool_tip.repeat')"
/>
<small>{{ $t('notifications.repeated_you') }}</small>
</span>
<span v-if="notification.type === 'follow'">
<i class="fa icon-user-plus lit" />
<small>{{ $t('notifications.followed_you') }}</small>
</span>
</div>
<div
v-if="notification.type === 'follow'"
class="timeago"
>
<span class="faint">
<Timeago
:time="notification.created_at"
:auto-update="240"
/>
</span>
</div>
<div
v-else
class="timeago"
>
<router-link
v-if="notification.status"
:to="{ name: 'conversation', params: { id: notification.status.id } }"
class="faint-link"
>
<Timeago
:time="notification.created_at"
:auto-update="240"
/>
</router-link>
</div>
<a
v-if="needMute"
href="#"
@click.prevent="toggleMute"
><i class="button-icon icon-eye-off" /></a>
</span>
<div <div
v-if="notification.type === 'follow'" v-if="notification.type === 'follow'"
class="timeago" class="follow-text"
> >
<span class="faint"> <router-link :to="userProfileLink">
<Timeago @{{ notification.from_profile.screen_name }}
:time="notification.created_at"
:auto-update="240"
/>
</span>
</div>
<div
v-else
class="timeago"
>
<router-link
v-if="notification.status"
:to="{ name: 'conversation', params: { id: notification.status.id } }"
class="faint-link"
>
<Timeago
:time="notification.created_at"
:auto-update="240"
/>
</router-link> </router-link>
</div> </div>
</span> <template v-else>
<div <status
v-if="notification.type === 'follow'" class="faint"
class="follow-text" :compact="true"
> :statusoid="notification.action"
<router-link :to="userProfileLink(notification.from_profile)"> :no-heading="true"
@{{ notification.from_profile.screen_name }} />
</router-link> </template>
</div> </div>
<template v-else>
<status
class="faint"
:compact="true"
:statusoid="notification.action"
:no-heading="true"
/>
</template>
</div> </div>
</div> </div>
</template> </template>

View file

@ -33,7 +33,6 @@
.notification { .notification {
box-sizing: border-box; box-sizing: border-box;
display: flex;
border-bottom: 1px solid; border-bottom: 1px solid;
border-color: $fallback--border; border-color: $fallback--border;
border-color: var(--border, $fallback--border); border-color: var(--border, $fallback--border);
@ -47,6 +46,10 @@
} }
} }
.muted {
padding: .25em .6em;
}
.non-mention { .non-mention {
display: flex; display: flex;
flex: 1; flex: 1;

View file

@ -8,7 +8,7 @@ import { findOffset } from '../../services/offset_finder/offset_finder.service.j
import { reject, map, uniqBy } from 'lodash' import { reject, map, uniqBy } from 'lodash'
import suggestor from '../emoji_input/suggestor.js' import suggestor from '../emoji_input/suggestor.js'
const buildMentionsString = ({ user, attentions }, currentUser) => { const buildMentionsString = ({ user, attentions = [] }, currentUser) => {
let allAttentions = [...attentions] let allAttentions = [...attentions]
allAttentions.unshift(user) allAttentions.unshift(user)

View file

@ -0,0 +1,32 @@
import PostStatusForm from '../post_status_form/post_status_form.vue'
const PostStatusModal = {
components: {
PostStatusForm
},
computed: {
isLoggedIn () {
return !!this.$store.state.users.currentUser
},
isOpen () {
return this.isLoggedIn && this.$store.state.postStatus.modalActivated
},
params () {
return this.$store.state.postStatus.params || {}
}
},
watch: {
isOpen (val) {
if (val) {
this.$nextTick(() => this.$el.querySelector('textarea').focus())
}
}
},
methods: {
closeModal () {
this.$store.dispatch('closePostStatusModal')
}
}
}
export default PostStatusModal

View file

@ -0,0 +1,43 @@
<template>
<div
v-if="isOpen"
class="post-form-modal-view modal-view"
@click="closeModal"
>
<div
class="post-form-modal-panel panel"
@click.stop=""
>
<div class="panel-heading">
{{ $t('post_status.new_status') }}
</div>
<PostStatusForm
class="panel-body"
v-bind="params"
@posted="closeModal"
/>
</div>
</div>
</template>
<script src="./post_status_modal.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.post-form-modal-view {
align-items: flex-start;
}
.post-form-modal-panel {
flex-shrink: 0;
margin-top: 25%;
margin-bottom: 2em;
width: 100%;
max-width: 700px;
@media (orientation: landscape) {
margin-top: 8%;
}
}
</style>

View file

@ -29,7 +29,8 @@ const Status = {
'isPreview', 'isPreview',
'noHeading', 'noHeading',
'inlineExpanded', 'inlineExpanded',
'showPinned' 'showPinned',
'inProfile'
], ],
data () { data () {
return { return {
@ -117,7 +118,7 @@ const Status = {
return hits return hits
}, },
muted () { return !this.unmuted && (this.status.user.muted || this.muteWordHits.length > 0) }, muted () { return !this.unmuted && ((!this.inProfile && this.status.user.muted) || (!this.inConversation && this.status.thread_muted) || this.muteWordHits.length > 0) },
hideFilteredStatuses () { hideFilteredStatuses () {
return typeof this.$store.state.config.hideFilteredStatuses === 'undefined' return typeof this.$store.state.config.hideFilteredStatuses === 'undefined'
? this.$store.state.instance.hideFilteredStatuses ? this.$store.state.instance.hideFilteredStatuses

View file

@ -25,7 +25,8 @@ const Timeline = {
'tag', 'tag',
'embedded', 'embedded',
'count', 'count',
'pinnedStatusIds' 'pinnedStatusIds',
'inProfile'
], ],
data () { data () {
return { return {

View file

@ -33,9 +33,10 @@
v-if="timeline.statusesObject[statusId]" v-if="timeline.statusesObject[statusId]"
:key="statusId + '-pinned'" :key="statusId + '-pinned'"
class="status-fadein" class="status-fadein"
:statusoid="timeline.statusesObject[statusId]" :status-id="statusId"
:collapsable="true" :collapsable="true"
:pinned-status-ids-object="pinnedStatusIdsObject" :pinned-status-ids-object="pinnedStatusIdsObject"
:in-profile="inProfile"
/> />
</template> </template>
<template v-for="status in timeline.visibleStatuses"> <template v-for="status in timeline.visibleStatuses">
@ -43,8 +44,9 @@
v-if="!excludedStatusIdsObject[status.id]" v-if="!excludedStatusIdsObject[status.id]"
:key="status.id" :key="status.id"
class="status-fadein" class="status-fadein"
:statusoid="status" :status-id="status.id"
:collapsable="true" :collapsable="true"
:in-profile="inProfile"
/> />
</template> </template>
</div> </div>

View file

@ -11,7 +11,6 @@ export default {
data () { data () {
return { return {
followRequestInProgress: false, followRequestInProgress: false,
followRequestSent: false,
hideUserStatsLocal: typeof this.$store.state.config.hideUserStats === 'undefined' hideUserStatsLocal: typeof this.$store.state.config.hideUserStats === 'undefined'
? this.$store.state.instance.hideUserStats ? this.$store.state.instance.hideUserStats
: this.$store.state.config.hideUserStats, : this.$store.state.config.hideUserStats,
@ -103,9 +102,8 @@ export default {
followUser () { followUser () {
const store = this.$store const store = this.$store
this.followRequestInProgress = true this.followRequestInProgress = true
requestFollow(this.user, store).then(({ sent }) => { requestFollow(this.user, store).then(() => {
this.followRequestInProgress = false this.followRequestInProgress = false
this.followRequestSent = sent
}) })
}, },
unfollowUser () { unfollowUser () {
@ -161,6 +159,9 @@ export default {
} }
this.$store.dispatch('setMedia', [attachment]) this.$store.dispatch('setMedia', [attachment])
this.$store.dispatch('setCurrent', attachment) this.$store.dispatch('setCurrent', attachment)
},
mentionUser () {
this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })
} }
} }
} }

View file

@ -139,13 +139,13 @@
<button <button
class="btn btn-default btn-block" class="btn btn-default btn-block"
:disabled="followRequestInProgress" :disabled="followRequestInProgress"
:title="followRequestSent ? $t('user_card.follow_again') : ''" :title="user.requested ? $t('user_card.follow_again') : ''"
@click="followUser" @click="followUser"
> >
<template v-if="followRequestInProgress"> <template v-if="followRequestInProgress">
{{ $t('user_card.follow_progress') }} {{ $t('user_card.follow_progress') }}
</template> </template>
<template v-else-if="followRequestSent"> <template v-else-if="user.requested">
{{ $t('user_card.follow_sent') }} {{ $t('user_card.follow_sent') }}
</template> </template>
<template v-else> <template v-else>
@ -192,6 +192,15 @@
</ProgressButton> </ProgressButton>
</div> </div>
<div>
<button
class="btn btn-default btn-block"
@click="mentionUser"
>
{{ $t('user_card.mention') }}
</button>
</div>
<div> <div>
<button <button
v-if="user.muted" v-if="user.muted"

View file

@ -11,7 +11,7 @@
rounded="top" rounded="top"
/> />
<div class="panel-footer"> <div class="panel-footer">
<PostStatusForm v-if="user" /> <PostStatusForm />
</div> </div>
</div> </div>
<auth-form <auth-form

View file

@ -26,6 +26,7 @@
timeline-name="user" timeline-name="user"
:user-id="userId" :user-id="userId"
:pinned-status-ids="user.pinnedStatusIds" :pinned-status-ids="user.pinnedStatusIds"
:in-profile="true"
/> />
<div <div
v-if="followsTabVisible" v-if="followsTabVisible"
@ -69,6 +70,7 @@
timeline-name="media" timeline-name="media"
:timeline="media" :timeline="media"
:user-id="userId" :user-id="userId"
:in-profile="true"
/> />
<Timeline <Timeline
v-if="isUs" v-if="isUs"
@ -79,6 +81,7 @@
:title="$t('user_card.favorites')" :title="$t('user_card.favorites')"
timeline-name="favorites" timeline-name="favorites"
:timeline="favorites" :timeline="favorites"
:in-profile="true"
/> />
</tab-switcher> </tab-switcher>
</div> </div>

View file

@ -0,0 +1,69 @@
import * as bodyScrollLock from 'body-scroll-lock'
let previousNavPaddingRight
let previousAppBgWrapperRight
const disableBodyScroll = (el) => {
const scrollBarGap = window.innerWidth - document.documentElement.clientWidth
bodyScrollLock.disableBodyScroll(el, {
reserveScrollBarGap: true
})
setTimeout(() => {
// If previousNavPaddingRight is already set, don't set it again.
if (previousNavPaddingRight === undefined) {
const navEl = document.getElementById('nav')
previousNavPaddingRight = window.getComputedStyle(navEl).getPropertyValue('padding-right')
navEl.style.paddingRight = previousNavPaddingRight ? `calc(${previousNavPaddingRight} + ${scrollBarGap}px)` : `${scrollBarGap}px`
}
// If previousAppBgWrapeprRight is already set, don't set it again.
if (previousAppBgWrapperRight === undefined) {
const appBgWrapperEl = document.getElementById('app_bg_wrapper')
previousAppBgWrapperRight = window.getComputedStyle(appBgWrapperEl).getPropertyValue('right')
appBgWrapperEl.style.right = previousAppBgWrapperRight ? `calc(${previousAppBgWrapperRight} + ${scrollBarGap}px)` : `${scrollBarGap}px`
}
document.body.classList.add('scroll-locked')
})
}
const enableBodyScroll = (el) => {
setTimeout(() => {
if (previousNavPaddingRight !== undefined) {
document.getElementById('nav').style.paddingRight = previousNavPaddingRight
// Restore previousNavPaddingRight to undefined so disableBodyScroll knows it can be set again.
previousNavPaddingRight = undefined
}
if (previousAppBgWrapperRight !== undefined) {
document.getElementById('app_bg_wrapper').style.right = previousAppBgWrapperRight
// Restore previousAppBgWrapperRight to undefined so disableBodyScroll knows it can be set again.
previousAppBgWrapperRight = undefined
}
document.body.classList.remove('scroll-locked')
})
bodyScrollLock.enableBodyScroll(el)
}
const directive = {
inserted: (el, binding) => {
if (binding.value) {
disableBodyScroll(el)
}
},
componentUpdated: (el, binding) => {
if (binding.oldValue === binding.value) {
return
}
if (binding.value) {
disableBodyScroll(el)
} else {
enableBodyScroll(el)
}
},
unbind: (el) => {
enableBodyScroll(el)
}
}
export default (Vue) => {
Vue.directive('body-scroll-lock', directive)
}

View file

@ -536,6 +536,7 @@
"follows_you": "Follows you!", "follows_you": "Follows you!",
"its_you": "It's you!", "its_you": "It's you!",
"media": "Media", "media": "Media",
"mention": "Mention",
"mute": "Mute", "mute": "Mute",
"muted": "Muted", "muted": "Muted",
"per_day": "per day", "per_day": "per day",

View file

@ -508,7 +508,9 @@
"pinned": "Fijado", "pinned": "Fijado",
"delete_confirm": "¿Realmente quieres borrar la publicación?", "delete_confirm": "¿Realmente quieres borrar la publicación?",
"reply_to": "Respondiendo a", "reply_to": "Respondiendo a",
"replies_list": "Respuestas:" "replies_list": "Respuestas:",
"mute_conversation": "Silenciar la conversación",
"unmute_conversation": "Mostrar la conversación"
}, },
"user_card": { "user_card": {
"approve": "Aprobar", "approve": "Aprobar",
@ -606,5 +608,16 @@
"person_talking": "{count} personas hablando", "person_talking": "{count} personas hablando",
"people_talking": "{count} gente hablando", "people_talking": "{count} gente hablando",
"no_results": "Sin resultados" "no_results": "Sin resultados"
},
"password_reset": {
"forgot_password": "¿Contraseña olvidada?",
"password_reset": "Restablecer la contraseña",
"instruction": "Ingrese su dirección de correo electrónico o nombre de usuario. Le enviaremos un enlace para restablecer su contraseña.",
"placeholder": "Su correo electrónico o nombre de usuario",
"check_email": "Revise su correo electrónico para obtener un enlace para restablecer su contraseña.",
"return_home": "Volver a la página de inicio",
"not_found": "No pudimos encontrar ese correo electrónico o nombre de usuario.",
"too_many_requests": "Has alcanzado el límite de intentos, vuelve a intentarlo más tarde.",
"password_reset_disabled": "El restablecimiento de contraseñas está deshabilitado. Póngase en contacto con el administrador de su instancia."
} }
} }

View file

@ -88,7 +88,7 @@
"followed_you": "Zu jarraitzen zaitu", "followed_you": "Zu jarraitzen zaitu",
"load_older": "Kargatu jakinarazpen zaharragoak", "load_older": "Kargatu jakinarazpen zaharragoak",
"notifications": "Jakinarazpenak", "notifications": "Jakinarazpenak",
"read": "Irakurri!", "read": "Irakurrita!",
"repeated_you": "zure mezua errepikatu du", "repeated_you": "zure mezua errepikatu du",
"no_more_notifications": "Ez dago jakinarazpen gehiago" "no_more_notifications": "Ez dago jakinarazpen gehiago"
}, },
@ -116,7 +116,7 @@
}, },
"post_status": { "post_status": {
"new_status": "Mezu berri bat idatzi", "new_status": "Mezu berri bat idatzi",
"account_not_locked_warning": "Zure kontua ez dago {0}. Edozeinek jarraitzen hastearekin, zure mezuak irakur dezake.", "account_not_locked_warning": "Zure kontua ez dago {0}. Edozeinek jarraitzen hastearekin, zure mezuak irakur ditzake.",
"account_not_locked_warning_link": "Blokeatuta", "account_not_locked_warning_link": "Blokeatuta",
"attachments_sensitive": "Nabarmendu eranskinak hunkigarri gisa ", "attachments_sensitive": "Nabarmendu eranskinak hunkigarri gisa ",
"content_type": { "content_type": {
@ -136,10 +136,10 @@
"unlisted": "Mezu hau ez da argitaratuko Denbora-lerro Publikoan ezta Ezagutzen den Sarean" "unlisted": "Mezu hau ez da argitaratuko Denbora-lerro Publikoan ezta Ezagutzen den Sarean"
}, },
"scope": { "scope": {
"direct": "Zuzena - Bidali aipatutako erabiltzaileei besterik ez", "direct": "Zuzena: Bidali aipatutako erabiltzaileei besterik ez",
"private": "Jarraitzaileentzako bakarrik- Bidali jarraitzaileentzat bakarrik", "private": "Jarraitzaileentzako bakarrik: Bidali jarraitzaileentzat bakarrik",
"public": "Publickoa - Bistaratu denbora-lerro publikoetan", "public": "Publikoa: Bistaratu denbora-lerro publikoetan",
"unlisted": "Zerrendatu gabea - ez bidali denbora-lerro publikoetan" "unlisted": "Zerrendatu gabea: ez bidali denbora-lerro publikoetara"
} }
}, },
"registration": { "registration": {
@ -228,7 +228,7 @@
"avatar_size_instruction": "Avatar irudien gomendatutako gutxieneko tamaina 150x150 pixel dira.", "avatar_size_instruction": "Avatar irudien gomendatutako gutxieneko tamaina 150x150 pixel dira.",
"export_theme": "Gorde aurre-ezarpena", "export_theme": "Gorde aurre-ezarpena",
"filtering": "Iragazten", "filtering": "Iragazten",
"filtering_explanation": "Hitz hauek dituzten muzu guztiak isilduak izango dira. Lerro bakoitzeko bat", "filtering_explanation": "Hitz hauek dituzten mezu guztiak isilduak izango dira. Lerro bakoitzeko bat",
"follow_export": "Jarraitzen dituzunak esportatu", "follow_export": "Jarraitzen dituzunak esportatu",
"follow_export_button": "Esportatu zure jarraitzaileak csv fitxategi batean", "follow_export_button": "Esportatu zure jarraitzaileak csv fitxategi batean",
"follow_import": "Jarraitzen dituzunak inportatu", "follow_import": "Jarraitzen dituzunak inportatu",
@ -276,7 +276,7 @@
"no_blocks": "Ez daude erabiltzaile blokeatutak", "no_blocks": "Ez daude erabiltzaile blokeatutak",
"no_mutes": "Ez daude erabiltzaile mututuak", "no_mutes": "Ez daude erabiltzaile mututuak",
"hide_follows_description": "Ez erakutsi nor jarraitzen ari naizen", "hide_follows_description": "Ez erakutsi nor jarraitzen ari naizen",
"hide_followers_description": "Ez erakutsi nor ari de ni jarraitzen", "hide_followers_description": "Ez erakutsi nor ari den ni jarraitzen",
"show_admin_badge": "Erakutsi Administratzaile etiketa nire profilan", "show_admin_badge": "Erakutsi Administratzaile etiketa nire profilan",
"show_moderator_badge": "Erakutsi Moderatzaile etiketa nire profilan", "show_moderator_badge": "Erakutsi Moderatzaile etiketa nire profilan",
"nsfw_clickthrough": "Gaitu klika hunkigarri eranskinak ezkutatzeko", "nsfw_clickthrough": "Gaitu klika hunkigarri eranskinak ezkutatzeko",
@ -456,8 +456,8 @@
"time": { "time": {
"day": "{0} egun", "day": "{0} egun",
"days": "{0} egun", "days": "{0} egun",
"day_short": "{0}d", "day_short": "{0}e",
"days_short": "{0}d", "days_short": "{0}e",
"hour": "{0} ordu", "hour": "{0} ordu",
"hours": "{0} ordu", "hours": "{0} ordu",
"hour_short": "{0}o", "hour_short": "{0}o",
@ -492,7 +492,7 @@
"conversation": "Elkarrizketa", "conversation": "Elkarrizketa",
"error_fetching": "Errorea eguneraketak eskuratzen", "error_fetching": "Errorea eguneraketak eskuratzen",
"load_older": "Kargatu mezu zaharragoak", "load_older": "Kargatu mezu zaharragoak",
"no_retweet_hint": "Mezu hau jarraitzailentzko bakarrik markatuta dago eta ezin da errepikatu", "no_retweet_hint": "Mezu hau jarraitzailentzako bakarrik markatuta dago eta ezin da errepikatu",
"repeated": "Errepikatuta", "repeated": "Errepikatuta",
"show_new": "Berriena erakutsi", "show_new": "Berriena erakutsi",
"up_to_date": "Eguneratuta", "up_to_date": "Eguneratuta",
@ -507,8 +507,10 @@
"unpin": "Aingura ezeztatu profilatik", "unpin": "Aingura ezeztatu profilatik",
"pinned": "Ainguratuta", "pinned": "Ainguratuta",
"delete_confirm": "Mezu hau benetan ezabatu nahi duzu?", "delete_confirm": "Mezu hau benetan ezabatu nahi duzu?",
"reply_to": "Erantzun", "reply_to": "Erantzuten",
"replies_list": "Erantzunak:" "replies_list": "Erantzunak:",
"mute_conversation": "Elkarrizketa isilarazi",
"unmute_conversation": "Elkarrizketa aktibatu"
}, },
"user_card": { "user_card": {
"approve": "Onartu", "approve": "Onartu",
@ -581,7 +583,7 @@
}, },
"tool_tip": { "tool_tip": {
"media_upload": "Multimedia igo", "media_upload": "Multimedia igo",
"repeat": "Erreplikatu", "repeat": "Errepikatu",
"reply": "Erantzun", "reply": "Erantzun",
"favorite": "Gogokoa", "favorite": "Gogokoa",
"user_settings": "Erabiltzaile ezarpenak" "user_settings": "Erabiltzaile ezarpenak"
@ -601,10 +603,21 @@
} }
}, },
"search": { "search": {
"people": "Gendea", "people": "Erabiltzaileak",
"hashtags": "Traolak", "hashtags": "Traolak",
"person_talking": "{count} pertsona hitzegiten", "person_talking": "{count} pertsona hitzegiten",
"people_talking": "{count} gende hitzegiten", "people_talking": "{count} jende hitzegiten",
"no_results": "Emaitzarik ez" "no_results": "Emaitzarik ez"
},
"password_reset": {
"forgot_password": "Pasahitza ahaztua?",
"password_reset": "Pasahitza berrezarri",
"instruction": "Idatzi zure helbide elektronikoa edo erabiltzaile izena. Pasahitza berrezartzeko esteka bidaliko dizugu.",
"placeholder": "Zure e-posta edo erabiltzaile izena",
"check_email": "Begiratu zure posta elektronikoa pasahitza berrezarri ahal izateko.",
"return_home": "Itzuli hasierara",
"not_found": "Ezin izan dugu helbide elektroniko edo erabiltzaile hori aurkitu.",
"too_many_requests": "Saiakera gehiegi burutu ditzu, saiatu berriro geroxeago.",
"password_reset_disabled": "Pasahitza berrezartzea debekatuta dago. Mesedez, jarri harremanetan instantzia administratzailearekin."
} }
} }

View file

@ -30,12 +30,12 @@
"cancel": "Anullar" "cancel": "Anullar"
}, },
"image_cropper": { "image_cropper": {
"crop_picture": "Talhar limatge", "crop_picture": "Talhar limatge",
"save": "Salvar", "save": "Salvar",
"save_without_cropping": "Salvar sens talhada", "save_without_cropping": "Salvar sens talhada",
"cancel": "Anullar" "cancel": "Anullar"
}, },
"importer": { "importer": {
"submit": "Mandar", "submit": "Mandar",
"success": "Corrèctament importat.", "success": "Corrèctament importat.",
"error": "Una error ses producha pendent limportacion daqueste fichièr." "error": "Una error ses producha pendent limportacion daqueste fichièr."
@ -65,6 +65,7 @@
"timeline": "Flux dactualitat", "timeline": "Flux dactualitat",
"twkn": "Lo malhum conegut", "twkn": "Lo malhum conegut",
"user_search": "Cèrca dutilizaires", "user_search": "Cèrca dutilizaires",
"search": "Cercar",
"who_to_follow": "Qual seguir", "who_to_follow": "Qual seguir",
"preferences": "Preferéncias" "preferences": "Preferéncias"
}, },
@ -79,19 +80,27 @@
"no_more_notifications": "Pas mai de notificacions" "no_more_notifications": "Pas mai de notificacions"
}, },
"polls": { "polls": {
"add_poll": "Ajustar un sondatge", "add_poll": "Ajustar un sondatge",
"add_option": "Ajustar dopcions", "add_option": "Ajustar dopcions",
"option": "Opcion", "option": "Opcion",
"votes": "vòtes", "votes": "vòtes",
"vote": "Votar", "vote": "Votar",
"type": "Tipe de sondatge", "type": "Tipe de sondatge",
"single_choice": "Causida unica", "single_choice": "Causida unica",
"multiple_choices": "Causida multipla", "multiple_choices": "Causida multipla",
"expiry": "Durada del sondatge", "expiry": "Durada del sondatge",
"expires_in": "Lo sondatge sacabarà {0}", "expires_in": "Lo sondatge sacabarà {0}",
"expired": "Sondatge acabat {0}", "expired": "Sondatge acabat {0}",
"not_enough_options": "I a pas pro dopcions" "not_enough_options": "I a pas pro dopcions"
}, },
"stickers": {
"add_sticker": "Ajustar un pegasolet"
},
"interactions": {
"favs_repeats": "Repeticions e favorits",
"follows": "Nòus seguidors",
"load_older": "Cargar dinteraccions anterioras"
},
"post_status": { "post_status": {
"new_status": "Publicar destatuts novèls", "new_status": "Publicar destatuts novèls",
"account_not_locked_warning": "Vòstre compte es pas {0}. Qual que siá pòt vos seguir per veire vòstras publicacions destinadas pas qua vòstres seguidors.", "account_not_locked_warning": "Vòstre compte es pas {0}. Qual que siá pòt vos seguir per veire vòstras publicacions destinadas pas qua vòstres seguidors.",
@ -137,7 +146,7 @@
} }
}, },
"selectable_list": { "selectable_list": {
"select_all": "O seleccionar tot" "select_all": "O seleccionar tot"
}, },
"settings": { "settings": {
"app_name": "Nom de laplicacion", "app_name": "Nom de laplicacion",
@ -216,7 +225,6 @@
"use_contain_fit": "Talhar pas las pèças juntas per las vinhetas", "use_contain_fit": "Talhar pas las pèças juntas per las vinhetas",
"name": "Nom", "name": "Nom",
"name_bio": "Nom & Bio", "name_bio": "Nom & Bio",
"new_password": "Nòu senhal", "new_password": "Nòu senhal",
"notification_visibility_follows": "Abonaments", "notification_visibility_follows": "Abonaments",
"notification_visibility_likes": "Aimar", "notification_visibility_likes": "Aimar",
@ -264,12 +272,12 @@
"subject_line_email": "Coma los corrièls: \"re: subjècte\"", "subject_line_email": "Coma los corrièls: \"re: subjècte\"",
"subject_line_mastodon": "Coma mastodon: copiar tal coma es", "subject_line_mastodon": "Coma mastodon: copiar tal coma es",
"subject_line_noop": "Copiar pas", "subject_line_noop": "Copiar pas",
"post_status_content_type": "Publicar lo tipe de contengut dels estatuts", "post_status_content_type": "Publicar lo tipe de contengut dels estatuts",
"stop_gifs": "Lançar los GIFs al subrevòl", "stop_gifs": "Lançar los GIFs al subrevòl",
"streaming": "Activar lo cargament automatic dels novèls estatus en anar amont", "streaming": "Activar lo cargament automatic dels novèls estatus en anar amont",
"text": "Tèxte", "text": "Tèxte",
"theme": "Tèma", "theme": "Tèma",
"theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.", "theme_help_v2_1": "Podètz tanben remplaçar la color dunes compausants en clicant la case, utilizatz lo boton \"O escafar tot\" per escafar totes las subrecargadas.",
"theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.", "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
"theme_help": "Emplegatz los còdis de color hex (#rrggbb) per personalizar vòstre tèma de color.", "theme_help": "Emplegatz los còdis de color hex (#rrggbb) per personalizar vòstre tèma de color.",
"tooltipRadius": "Astúcias/alèrtas", "tooltipRadius": "Astúcias/alèrtas",
@ -280,14 +288,14 @@
"true": "òc" "true": "òc"
}, },
"notifications": "Notificacions", "notifications": "Notificacions",
"notification_setting": "Receber las notificacions de:", "notification_setting": "Recebre las notificacions de:",
"notification_setting_follows": "Utilizaires que seguissètz", "notification_setting_follows": "Utilizaires que seguissètz",
"notification_setting_non_follows": "Utilizaires que seguissètz pas", "notification_setting_non_follows": "Utilizaires que seguissètz pas",
"notification_setting_followers": "Utilizaires que vos seguisson", "notification_setting_followers": "Utilizaires que vos seguisson",
"notification_setting_non_followers": "Utilizaires que vos seguisson pas", "notification_setting_non_followers": "Utilizaires que vos seguisson pas",
"notification_mutes": "Per receber pas mai dun utilizaire en particular, botatz-lo en silenci.", "notification_mutes": "Per recebre pas mai dun utilizaire en particular, botatz-lo en silenci.",
"notification_blocks": "Blocar un utilizaire arrèsta totas las notificacions tan coma quitar de los seguir.", "notification_blocks": "Blocar un utilizaire arrèsta totas las notificacions tan coma quitar de los seguir.",
"enable_web_push_notifications": "Activar las notificacions web push", "enable_web_push_notifications": "Activar las notificacions web push",
"style": { "style": {
"switcher": { "switcher": {
"keep_color": "Gardar las colors", "keep_color": "Gardar las colors",
@ -442,7 +450,7 @@
"conversation": "Conversacion", "conversation": "Conversacion",
"error_fetching": "Error en cercant de mesas a jorn", "error_fetching": "Error en cercant de mesas a jorn",
"load_older": "Ne veire mai", "load_older": "Ne veire mai",
"no_retweet_hint": "Las publicacions marcadas pels seguidors solament o dirèctas se pòdon pas repetir", "no_retweet_hint": "Las publicacions marcadas pels seguidors solament o dirèctas se pòdon pas repetir",
"repeated": "repetit", "repeated": "repetit",
"show_new": "Ne veire mai", "show_new": "Ne veire mai",
"up_to_date": "A jorn", "up_to_date": "A jorn",
@ -477,6 +485,8 @@
"per_day": "per jorn", "per_day": "per jorn",
"remote_follow": "Seguir a distància", "remote_follow": "Seguir a distància",
"statuses": "Estatuts", "statuses": "Estatuts",
"subscribe": "Sabonar",
"unsubscribe": "Se desabonar",
"unblock": "Desblocar", "unblock": "Desblocar",
"unblock_progress": "Desblocatge...", "unblock_progress": "Desblocatge...",
"block_progress": "Blocatge...", "block_progress": "Blocatge...",
@ -532,5 +542,12 @@
"GiB": "Gio", "GiB": "Gio",
"TiB": "Tio" "TiB": "Tio"
} }
},
"search": {
"people": "Gent",
"hashtags": "Etiquetas",
"person_talking": "{count} persona ne parla",
"people_talking": "{count} personas ne parlan",
"no_results": "Cap de resultats"
} }
} }

View file

@ -2,6 +2,10 @@
"chat": { "chat": {
"title": "聊天" "title": "聊天"
}, },
"exporter": {
"export": "导出",
"processing": "正在处理,稍后会提示您下载文件"
},
"features_panel": { "features_panel": {
"chat": "聊天", "chat": "聊天",
"gopher": "Gopher", "gopher": "Gopher",
@ -17,23 +21,66 @@
}, },
"general": { "general": {
"apply": "应用", "apply": "应用",
"submit": "提交" "submit": "提交",
"more": "更多",
"generic_error": "发生一个错误",
"optional": "可选项",
"show_more": "显示更多",
"show_less": "显示更少",
"cancel": "取消",
"disable": "禁用",
"enable": "启用",
"confirm": "确认",
"verify": "验证"
},
"image_cropper": {
"crop_picture": "裁剪图片",
"save": "保存",
"save_without_cropping": "保存未经裁剪的图片",
"cancel": "取消"
},
"importer": {
"submit": "提交",
"success": "导入成功。",
"error": "导入此文件时出现一个错误。"
}, },
"login": { "login": {
"login": "登录", "login": "登录",
"description": "用 OAuth 登录",
"logout": "登出", "logout": "登出",
"password": "密码", "password": "密码",
"placeholder": "例如lain", "placeholder": "例如lain",
"register": "注册", "register": "注册",
"username": "用户名" "username": "用户名",
"hint": "登录后加入讨论",
"authentication_code": "验证码",
"enter_recovery_code": "输入一个恢复码",
"enter_two_factor_code": "输入一个双重因素验证码",
"recovery_code": "恢复码",
"heading" : {
"totp" : "双重因素验证",
"recovery" : "双重因素恢复"
}
},
"media_modal": {
"previous": "往前",
"next": "往后"
}, },
"nav": { "nav": {
"about": "关于",
"back": "Back",
"chat": "本地聊天", "chat": "本地聊天",
"friend_requests": "关注请求", "friend_requests": "关注请求",
"mentions": "提及", "mentions": "提及",
"interactions": "互动",
"dms": "私信",
"public_tl": "公共时间线", "public_tl": "公共时间线",
"timeline": "时间线", "timeline": "时间线",
"twkn": "所有已知网络" "twkn": "所有已知网络",
"user_search": "用户搜索",
"search": "搜索",
"who_to_follow": "推荐关注",
"preferences": "偏好设置"
}, },
"notifications": { "notifications": {
"broken_favorite": "未知的状态,正在搜索中...", "broken_favorite": "未知的状态,正在搜索中...",
@ -42,24 +89,57 @@
"load_older": "加载更早的通知", "load_older": "加载更早的通知",
"notifications": "通知", "notifications": "通知",
"read": "阅读!", "read": "阅读!",
"repeated_you": "转发了你的状态" "repeated_you": "转发了你的状态",
"no_more_notifications": "没有更多的通知"
},
"polls": {
"add_poll": "增加问卷调查",
"add_option": "增加选项",
"option": "选项",
"votes": "投票",
"vote": "投票",
"type": "问卷类型",
"single_choice": "单选项",
"multiple_choices": "多选项",
"expiry": "问卷的时间",
"expires_in": "投票于 {0} 内结束",
"expired": "投票 {0} 前已结束",
"not_enough_options": "投票的选项太少"
},
"stickers": {
"add_sticker": "添加贴纸"
},
"interactions": {
"favs_repeats": "转发和收藏",
"follows": "新的关注着",
"load_older": "加载更早的互动"
}, },
"post_status": { "post_status": {
"new_status": "发布新状态",
"account_not_locked_warning": "你的帐号没有 {0}。任何人都可以关注你并浏览你的上锁内容。", "account_not_locked_warning": "你的帐号没有 {0}。任何人都可以关注你并浏览你的上锁内容。",
"account_not_locked_warning_link": "上锁", "account_not_locked_warning_link": "上锁",
"attachments_sensitive": "标记附件为敏感内容", "attachments_sensitive": "标记附件为敏感内容",
"content_type": { "content_type": {
"text/plain": "纯文本" "text/plain": "纯文本",
"text/html": "HTML",
"text/markdown": "Markdown",
"text/bbcode": "BBCode"
}, },
"content_warning": "主题(可选)", "content_warning": "主题(可选)",
"default": "刚刚抵达上海", "default": "刚刚抵达上海",
"direct_warning": "本条内容只有被提及的用户能够看到。", "direct_warning_to_all": "本条内容只有被提及的用户能够看到。",
"direct_warning_to_first_only": "本条内容只有被在消息开始处提及的用户能够看到。",
"posting": "发送", "posting": "发送",
"scope_notice": {
"public": "本条内容可以被所有人看到",
"private": "关注你的人才能看到本条内容",
"unlisted": "本条内容既不在公共时间线,也不会在所有已知网络上可见"
},
"scope": { "scope": {
"direct": "私信 - 只发送给被提及的用户", "direct": "私信 - 只发送给被提及的用户",
"private": "仅关注者 - 只有关注了你的人能看到", "private": "仅关注者 - 只有关注了你的人能看到",
"public": "公共 - 发送到公共时间轴", "public": "公共 - 发送到公共时间轴",
"unlisted": "不公开 - 所有人可见,但不会发送到公共时间轴" "unlisted": "不公开 - 不会发送到公共时间轴"
} }
}, },
"registration": { "registration": {
@ -68,9 +148,49 @@
"fullname": "全名", "fullname": "全名",
"password_confirm": "确认密码", "password_confirm": "确认密码",
"registration": "注册", "registration": "注册",
"token": "邀请码" "token": "邀请码",
"captcha": "CAPTCHA",
"new_captcha": "点击图片获取新的验证码",
"username_placeholder": "例如: lain",
"fullname_placeholder": "例如: Lain Iwakura",
"bio_placeholder": "例如:\n你好 我是 Lain.\n我是一个住在上海的宅男。你可能在某处见过我。",
"validations": {
"username_required": "不能留空",
"fullname_required": "不能留空",
"email_required": "不能留空",
"password_required": "不能留空",
"password_confirmation_required": "不能留空",
"password_confirmation_match": "密码不一致"
}
},
"selectable_list": {
"select_all": "选择全部"
}, },
"settings": { "settings": {
"app_name": "App 名称",
"security": "安全",
"enter_current_password_to_confirm": "输入你当前密码来确认你的身份",
"mfa": {
"otp" : "OTP",
"setup_otp" : "设置 OTP",
"wait_pre_setup_otp" : "预设 OTP",
"confirm_and_enable" : "确认并启用 OTP",
"title": "双因素验证",
"generate_new_recovery_codes" : "生成新的恢复码",
"warning_of_generate_new_codes" : "当你生成新的恢复码时,你的就恢复码就失效了。",
"recovery_codes" : "恢复码。",
"waiting_a_recovery_codes": "接受备份码。。。",
"recovery_codes_warning" : "抄写这些号码,或者保存在安全的地方。这些号码不会再次显示。如果你无法访问你的 2FA app也丢失了你的恢复码你的账号就再也无法登录了。",
"authentication_methods" : "身份验证方法",
"scan": {
"title": "扫一下",
"desc": "使用你的双因素验证 app扫描这个二维码或者输入这些文字密钥",
"secret_code": "密钥"
},
"verify": {
"desc": "要启用双因素验证,请把你的双因素验证 app 里的数字输入:"
}
},
"attachmentRadius": "附件", "attachmentRadius": "附件",
"attachments": "附件", "attachments": "附件",
"autoload": "启用滚动到底部时的自动加载", "autoload": "启用滚动到底部时的自动加载",
@ -79,6 +199,12 @@
"avatarRadius": "头像", "avatarRadius": "头像",
"background": "背景", "background": "背景",
"bio": "简介", "bio": "简介",
"block_export": "拉黑名单导出",
"block_export_button": "导出你的拉黑名单到一个 csv 文件",
"block_import": "拉黑名单导入",
"block_import_error": "导入拉黑名单出错",
"blocks_imported": "拉黑名单导入成功!需要一点时间来处理。",
"blocks_tab": "块",
"btnRadius": "按钮", "btnRadius": "按钮",
"cBlue": "蓝色(回复,关注)", "cBlue": "蓝色(回复,关注)",
"cGreen": "绿色(转发)", "cGreen": "绿色(转发)",
@ -88,6 +214,7 @@
"change_password_error": "修改密码的时候出了点问题。", "change_password_error": "修改密码的时候出了点问题。",
"changed_password": "成功修改了密码!", "changed_password": "成功修改了密码!",
"collapse_subject": "折叠带主题的内容", "collapse_subject": "折叠带主题的内容",
"composing": "正在书写",
"confirm_new_password": "确认新密码", "confirm_new_password": "确认新密码",
"current_avatar": "当前头像", "current_avatar": "当前头像",
"current_password": "当前密码", "current_password": "当前密码",
@ -98,12 +225,12 @@
"delete_account_description": "永久删除你的帐号和所有消息。", "delete_account_description": "永久删除你的帐号和所有消息。",
"delete_account_error": "删除账户时发生错误,如果一直删除不了,请联系实例管理员。", "delete_account_error": "删除账户时发生错误,如果一直删除不了,请联系实例管理员。",
"delete_account_instructions": "在下面输入你的密码来确认删除账户", "delete_account_instructions": "在下面输入你的密码来确认删除账户",
"avatar_size_instruction": "推荐的头像图片最小的尺寸是 150x150 像素。",
"export_theme": "导出预置主题", "export_theme": "导出预置主题",
"filtering": "过滤器", "filtering": "过滤器",
"filtering_explanation": "所有包含以下词汇的内容都会被隐藏,一行一个", "filtering_explanation": "所有包含以下词汇的内容都会被隐藏,一行一个",
"follow_export": "导出关注", "follow_export": "导出关注",
"follow_export_button": "将关注导出成 csv 文件", "follow_export_button": "将关注导出成 csv 文件",
"follow_export_processing": "正在处理,过一会儿就可以下载你的文件了",
"follow_import": "导入关注", "follow_import": "导入关注",
"follow_import_error": "导入关注时错误", "follow_import_error": "导入关注时错误",
"follows_imported": "关注已导入!尚需要一些时间来处理。", "follows_imported": "关注已导入!尚需要一些时间来处理。",
@ -111,12 +238,22 @@
"general": "通用", "general": "通用",
"hide_attachments_in_convo": "在对话中隐藏附件", "hide_attachments_in_convo": "在对话中隐藏附件",
"hide_attachments_in_tl": "在时间线上隐藏附件", "hide_attachments_in_tl": "在时间线上隐藏附件",
"hide_muted_posts": "不显示被隐藏的用户的帖子",
"max_thumbnails": "最多再每个帖子所能显示的缩略图数量",
"hide_isp": "隐藏指定实例的面板H",
"preload_images": "预载图片",
"use_one_click_nsfw": "点击一次以打开工作场所不适宜的附件",
"hide_post_stats": "隐藏推文相关的统计数据(例如:收藏的次数)", "hide_post_stats": "隐藏推文相关的统计数据(例如:收藏的次数)",
"hide_user_stats": "隐藏用户的统计数据(例如:关注者的数量)", "hide_user_stats": "隐藏用户的统计数据(例如:关注者的数量)",
"hide_filtered_statuses": "隐藏过滤的状态",
"import_blocks_from_a_csv_file": "从 csv 文件中导入拉黑名单",
"import_followers_from_a_csv_file": "从 csv 文件中导入关注", "import_followers_from_a_csv_file": "从 csv 文件中导入关注",
"import_theme": "导入预置主题", "import_theme": "导入预置主题",
"inputRadius": "输入框", "inputRadius": "输入框",
"checkboxRadius": "复选框",
"instance_default": "(默认:{value})", "instance_default": "(默认:{value})",
"instance_default_simple": "(默认)",
"interface": "界面",
"interfaceLanguage": "界面语言", "interfaceLanguage": "界面语言",
"invalid_theme_imported": "您所选择的主题文件不被 Pleroma 支持,因此主题未被修改。", "invalid_theme_imported": "您所选择的主题文件不被 Pleroma 支持,因此主题未被修改。",
"limited_availability": "在您的浏览器中无法使用", "limited_availability": "在您的浏览器中无法使用",
@ -124,6 +261,9 @@
"lock_account_description": "你需要手动审核关注请求", "lock_account_description": "你需要手动审核关注请求",
"loop_video": "循环视频", "loop_video": "循环视频",
"loop_video_silent_only": "只循环没有声音的视频例如Mastodon 里的“GIF”", "loop_video_silent_only": "只循环没有声音的视频例如Mastodon 里的“GIF”",
"mutes_tab": "隐藏",
"play_videos_in_modal": "在弹出框内播放视频",
"use_contain_fit": "生成缩略图时不要裁剪附件。",
"name": "名字", "name": "名字",
"name_bio": "名字及简介", "name_bio": "名字及简介",
"new_password": "新密码", "new_password": "新密码",
@ -133,9 +273,15 @@
"notification_visibility_mentions": "提及", "notification_visibility_mentions": "提及",
"notification_visibility_repeats": "转发", "notification_visibility_repeats": "转发",
"no_rich_text_description": "不显示富文本格式", "no_rich_text_description": "不显示富文本格式",
"no_blocks": "没有拉黑的",
"no_mutes": "没有隐藏",
"hide_follows_description": "不要显示我所关注的人",
"hide_followers_description": "不要显示关注我的人",
"show_admin_badge": "显示管理徽章",
"show_moderator_badge": "显示版主徽章",
"nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开", "nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开",
"oauth_tokens": "OAuth令牌", "oauth_tokens": "OAuth令牌",
"token": "代币", "token": "令牌",
"refresh_token": "刷新令牌", "refresh_token": "刷新令牌",
"valid_until": "有效期至", "valid_until": "有效期至",
"revoke_token": "撤消", "revoke_token": "撤消",
@ -151,25 +297,196 @@
"reply_visibility_all": "显示所有回复", "reply_visibility_all": "显示所有回复",
"reply_visibility_following": "只显示发送给我的回复/发送给我关注的用户的回复", "reply_visibility_following": "只显示发送给我的回复/发送给我关注的用户的回复",
"reply_visibility_self": "只显示发送给我的回复", "reply_visibility_self": "只显示发送给我的回复",
"autohide_floating_post_button": "自动隐藏新帖子的按钮(移动设备)",
"saving_err": "保存设置时发生错误", "saving_err": "保存设置时发生错误",
"saving_ok": "设置已保存", "saving_ok": "设置已保存",
"search_user_to_block": "搜索你想屏蔽的用户",
"search_user_to_mute": "搜索你想要隐藏的用户",
"security_tab": "安全", "security_tab": "安全",
"scope_copy": "回复时的复制范围(私信是总是复制的)",
"minimal_scopes_mode": "最小发文范围",
"set_new_avatar": "设置新头像", "set_new_avatar": "设置新头像",
"set_new_profile_background": "设置新的个人资料背景", "set_new_profile_background": "设置新的个人资料背景",
"set_new_profile_banner": "设置新的横幅图片", "set_new_profile_banner": "设置新的横幅图片",
"settings": "设置", "settings": "设置",
"subject_input_always_show": "总是显示主题框",
"subject_line_behavior": "回复时复制主题",
"subject_line_email": "比如电邮: \"re: 主题\"",
"subject_line_mastodon": "比如 mastodon: copy as is",
"subject_line_noop": "不要复制",
"post_status_content_type": "发文状态内容类型",
"stop_gifs": "鼠标悬停时播放GIF", "stop_gifs": "鼠标悬停时播放GIF",
"streaming": "开启滚动到顶部时的自动推送", "streaming": "开启滚动到顶部时的自动推送",
"text": "文本", "text": "文本",
"theme": "主题", "theme": "主题",
"theme_help": "使用十六进制代码(#rrggbb来设置主题颜色。", "theme_help": "使用十六进制代码(#rrggbb来设置主题颜色。",
"theme_help_v2_1": "你也可以通过切换复选框来覆盖某些组件的颜色和透明。使用“清除所有”来清楚所有覆盖设置。",
"theme_help_v2_2": "某些条目下的图标是背景或文本对比指示器,鼠标悬停可以获取详细信息。请记住,使用透明度来显示最差的情况。",
"tooltipRadius": "提醒", "tooltipRadius": "提醒",
"upload_a_photo": "上传照片",
"user_settings": "用户设置", "user_settings": "用户设置",
"values": { "values": {
"false": "否", "false": "否",
"true": "是" "true": "是"
},
"notifications": "通知",
"notification_setting": "通知来源:",
"notification_setting_follows": "你所关注的用户",
"notification_setting_non_follows": "你没有关注的用户",
"notification_setting_followers": "关注你的用户",
"notification_setting_non_followers": "没有关注你的用户",
"notification_mutes": "要停止收到某个指定的用户的通知,请使用隐藏功能。",
"notification_blocks": "拉黑一个用户会停掉所有他的通知,等同于取消关注。",
"enable_web_push_notifications": "启用 web 推送通知",
"style": {
"switcher": {
"keep_color": "保留颜色",
"keep_shadows": "保留阴影",
"keep_opacity": "保留透明度",
"keep_roundness": "保留圆角",
"keep_fonts": "保留字体",
"save_load_hint": "\"保留\" 选项在选择或加载主题时保留当前设置的选项,在导出主题时还会存储上述选项。当所有复选框未设置时,导出主题将保存所有内容。",
"reset": "重置",
"clear_all": "清除全部",
"clear_opacity": "清除透明度"
},
"common": {
"color": "颜色",
"opacity": "透明度",
"contrast": {
"hint": "对比度是 {ratio} 它 {level} {context}",
"level": {
"aa": "符合 AA 等级准则(最低)",
"aaa": "符合 AAA 等级准则(推荐)",
"bad": "不符合任何辅助功能指南"
},
"context": {
"18pt": "大字文本 (18pt+)",
"text": "文本"
}
}
},
"common_colors": {
"_tab_label": "常规",
"main": "常用颜色",
"foreground_hint": "点击”高级“ 标签进行细致的控制",
"rgbo": "图标,口音,徽章"
},
"advanced_colors": {
"_tab_label": "高级",
"alert": "提醒或警告背景色",
"alert_error": "错误",
"badge": "徽章背景",
"badge_notification": "通知",
"panel_header": "面板标题",
"top_bar": "顶栏",
"borders": "边框",
"buttons": "按钮",
"inputs": "输入框",
"faint_text": "灰度文字"
},
"radii": {
"_tab_label": "圆角"
},
"shadows": {
"_tab_label": "阴影和照明",
"component": "组件",
"override": "覆盖",
"shadow_id": "阴影 #{value}",
"blur": "模糊",
"spread": "扩散",
"inset": "插入内部",
"hint": "对于阴影你还可以使用 --variable 作为颜色值来使用 CSS3 变量。请注意,这种情况下,透明设置将不起作用。",
"filter_hint": {
"always_drop_shadow": "警告,此阴影设置会总是使用 {0} ,如果浏览器支持的话。",
"drop_shadow_syntax": "{0} 不支持参数 {1} 和关键词 {2} 。",
"avatar_inset": "请注意组合两个内部和非内部的阴影到头像上,在透明头像上可能会有意料之外的效果。",
"spread_zero": "阴影的扩散 > 0 会同设置成零一样",
"inset_classic": "插入内部的阴影会使用 {0}"
},
"components": {
"panel": "面板",
"panelHeader": "面板标题",
"topBar": "顶栏",
"avatar": "用户头像(在个人资料栏)",
"avatarStatus": "用户头像(在帖子显示栏)",
"popup": "弹窗和工具提示",
"button": "按钮",
"buttonHover": "按钮(悬停)",
"buttonPressed": "按钮(按下)",
"buttonPressedHover": "按钮(按下和悬停)",
"input": "输入框"
}
},
"fonts": {
"_tab_label": "字体",
"help": "给用户界面的元素选择字体。选择 “自选”的你必须输入确切的字体名称。",
"components": {
"interface": "界面",
"input": "输入框",
"post": "发帖文字",
"postCode": "帖子中使用等间距文字(富文本)"
},
"family": "字体名称",
"size": "大小 (in px)",
"weight": "字重 (粗体))",
"custom": "自选"
},
"preview": {
"header": "预览",
"content": "内容",
"error": "例子错误",
"button": "按钮",
"text": "有堆 {0} 和 {1}",
"mono": "内容",
"input": "刚刚抵达上海",
"faint_link": "帮助菜单",
"fine_print": "阅读我们的 {0} 学不到什么东东!",
"header_faint": "这很正常",
"checkbox": "我已经浏览了 TOC",
"link": "一个很棒的摇滚链接"
}
},
"version": {
"title": "版本",
"backend_version": "后端版本",
"frontend_version": "前端版本"
} }
}, },
"time": {
"day": "{0} 天",
"days": "{0} 天",
"day_short": "{0}d",
"days_short": "{0}d",
"hour": "{0} 小时",
"hours": "{0} 小时",
"hour_short": "{0}h",
"hours_short": "{0}h",
"in_future": "还有 {0}",
"in_past": "{0} 之前",
"minute": "{0} 分钟",
"minutes": "{0} 分钟",
"minute_short": "{0}min",
"minutes_short": "{0}min",
"month": "{0} 月",
"months": "{0} 月",
"month_short": "{0}mo",
"months_short": "{0}mo",
"now": "刚刚",
"now_short": "刚刚",
"second": "{0} 秒",
"seconds": "{0} 秒",
"second_short": "{0}s",
"seconds_short": "{0}s",
"week": "{0} 周",
"weeks": "{0} 周",
"week_short": "{0}w",
"weeks_short": "{0}w",
"year": "{0} 年",
"years": "{0} 年",
"year_short": "{0}y",
"years_short": "{0}y"
},
"timeline": { "timeline": {
"collapse": "折叠", "collapse": "折叠",
"conversation": "对话", "conversation": "对话",
@ -178,29 +495,129 @@
"no_retweet_hint": "这条内容仅关注者可见,或者是私信,因此不能转发。", "no_retweet_hint": "这条内容仅关注者可见,或者是私信,因此不能转发。",
"repeated": "已转发", "repeated": "已转发",
"show_new": "显示新内容", "show_new": "显示新内容",
"up_to_date": "已是最新" "up_to_date": "已是最新",
"no_more_statuses": "没有更多的状态",
"no_statuses": "没有状态更新"
},
"status": {
"favorites": "收藏",
"repeats": "转发",
"delete": "删除状态",
"pin": "在个人资料置顶",
"unpin": "取消在个人资料置顶",
"pinned": "置顶",
"delete_confirm": "你真的想要删除这条状态吗?",
"reply_to": "回复",
"replies_list": "回复:",
"mute_conversation": "隐藏对话",
"unmute_conversation": "对话取消隐藏"
}, },
"user_card": { "user_card": {
"approve": "允许", "approve": "允许",
"block": "屏蔽", "block": "屏蔽",
"blocked": "已屏蔽!", "blocked": "已屏蔽!",
"deny": "拒绝", "deny": "拒绝",
"favorites": "收藏",
"follow": "关注", "follow": "关注",
"follow_sent": "请求已发送!",
"follow_progress": "请求中",
"follow_again": "再次发送请求?",
"follow_unfollow": "取消关注",
"followees": "正在关注", "followees": "正在关注",
"followers": "关注者", "followers": "关注者",
"following": "正在关注!", "following": "正在关注!",
"follows_you": "关注了你!", "follows_you": "关注了你!",
"its_you": "就是你!!",
"media": "媒体",
"mute": "隐藏", "mute": "隐藏",
"muted": "已隐藏", "muted": "已隐藏",
"per_day": "每天", "per_day": "每天",
"remote_follow": "跨站关注", "remote_follow": "跨站关注",
"statuses": "状态" "report": "报告",
"statuses": "状态",
"subscribe": "订阅",
"unsubscribe": "退订",
"unblock": "取消拉黑",
"unblock_progress": "取消拉黑中...",
"block_progress": "拉黑中...",
"unmute": "取消隐藏",
"unmute_progress": "取消隐藏中...",
"mute_progress": "隐藏中...",
"admin_menu": {
"moderation": "权限",
"grant_admin": "赋予管理权限",
"revoke_admin": "撤销管理权限",
"grant_moderator": "赋予版主权限",
"revoke_moderator": "撤销版主权限",
"activate_account": "激活账号",
"deactivate_account": "关闭账号",
"delete_account": "删除账号",
"force_nsfw": "标记所有的帖子都是 - 工作场合不适",
"strip_media": "从帖子里删除媒体文件",
"force_unlisted": "强制帖子为不公开",
"sandbox": "强制帖子为只有关注者可看",
"disable_remote_subscription": "禁止从远程实例关注用户",
"disable_any_subscription": "完全禁止关注用户",
"quarantine": "从联合实例中禁止用户帖子",
"delete_user": "删除用户",
"delete_user_confirmation": "你确认吗?此操作无法撤销。"
}
}, },
"user_profile": { "user_profile": {
"timeline_title": "用户时间线" "timeline_title": "用户时间线",
"profile_does_not_exist": "抱歉,此个人资料不存在。",
"profile_loading_error": "抱歉,载入个人资料时出错。"
},
"user_reporting": {
"title": "报告 {0}",
"add_comment_description": "此报告会发送给你的实例管理员。你可以在下面提供更多详细信息解释报告的缘由:",
"additional_comments": "其它信息",
"forward_description": "这个账号是从另外一个服务器。同时发送一个副本到那里?",
"forward_to": "转发 {0}",
"submit": "提交",
"generic_error": "当处理你的请求时,发生了一个错误。"
}, },
"who_to_follow": { "who_to_follow": {
"more": "更多", "more": "更多",
"who_to_follow": "推荐关注" "who_to_follow": "推荐关注"
},
"tool_tip": {
"media_upload": "上传多媒体",
"repeat": "转发",
"reply": "回复",
"favorite": "收藏",
"user_settings": "用户设置"
},
"upload":{
"error": {
"base": "上传不成功。",
"file_too_big": "文件太大了 [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
"default": "迟些再试"
},
"file_size_units": {
"B": "B",
"KiB": "KiB",
"MiB": "MiB",
"GiB": "GiB",
"TiB": "TiB"
}
},
"search": {
"people": "人",
"hashtags": "Hashtags",
"person_talking": "{count} 人谈论",
"people_talking": "{count} 人谈论",
"no_results": "没有搜索结果"
},
"password_reset": {
"forgot_password": "忘记密码了?",
"password_reset": "重置密码",
"instruction": "输入你的电邮地址或者用户名,我们将发送一个链接到你的邮箱,用于重置密码。",
"placeholder": "你的电邮地址或者用户名",
"check_email": "检查你的邮箱,会有一个链接用于重置密码。",
"return_home": "回到首页",
"not_found": "我们无法找到匹配的邮箱地址或者用户名。",
"too_many_requests": "你触发了尝试的限制,请稍后再试。",
"password_reset_disabled": "密码重置已经被禁用。请联系你的实例管理员。"
} }
} }

View file

@ -15,6 +15,7 @@ import mediaViewerModule from './modules/media_viewer.js'
import oauthTokensModule from './modules/oauth_tokens.js' import oauthTokensModule from './modules/oauth_tokens.js'
import reportsModule from './modules/reports.js' import reportsModule from './modules/reports.js'
import pollsModule from './modules/polls.js' import pollsModule from './modules/polls.js'
import postStatusModule from './modules/postStatus.js'
import VueI18n from 'vue-i18n' import VueI18n from 'vue-i18n'
@ -26,6 +27,7 @@ import messages from './i18n/messages.js'
import VueChatScroll from 'vue-chat-scroll' import VueChatScroll from 'vue-chat-scroll'
import VueClickOutside from 'v-click-outside' import VueClickOutside from 'v-click-outside'
import PortalVue from 'portal-vue' import PortalVue from 'portal-vue'
import VBodyScrollLock from './directives/body_scroll_lock'
import VTooltip from 'v-tooltip' import VTooltip from 'v-tooltip'
import afterStoreSetup from './boot/after_store.js' import afterStoreSetup from './boot/after_store.js'
@ -38,6 +40,7 @@ Vue.use(VueI18n)
Vue.use(VueChatScroll) Vue.use(VueChatScroll)
Vue.use(VueClickOutside) Vue.use(VueClickOutside)
Vue.use(PortalVue) Vue.use(PortalVue)
Vue.use(VBodyScrollLock)
Vue.use(VTooltip) Vue.use(VTooltip)
const i18n = new VueI18n({ const i18n = new VueI18n({
@ -76,7 +79,8 @@ const persistedStateOptions = {
mediaViewer: mediaViewerModule, mediaViewer: mediaViewerModule,
oauthTokens: oauthTokensModule, oauthTokens: oauthTokensModule,
reports: reportsModule, reports: reportsModule,
polls: pollsModule polls: pollsModule,
postStatus: postStatusModule
}, },
plugins: [persistedState, pushNotifications], plugins: [persistedState, pushNotifications],
strict: false // Socket modifies itself, let's ignore this for now. strict: false // Socket modifies itself, let's ignore this for now.

25
src/modules/postStatus.js Normal file
View file

@ -0,0 +1,25 @@
const postStatus = {
state: {
params: null,
modalActivated: false
},
mutations: {
openPostStatusModal (state, params) {
state.params = params
state.modalActivated = true
},
closePostStatusModal (state) {
state.modalActivated = false
}
},
actions: {
openPostStatusModal ({ commit }, params) {
commit('openPostStatusModal', params)
},
closePostStatusModal ({ commit }) {
commit('closePostStatusModal')
}
}
}
export default postStatus

View file

@ -426,9 +426,13 @@ export const mutations = {
newStatus.favoritedBy.push(user) newStatus.favoritedBy.push(user)
} }
}, },
setMuted (state, status) { setMutedStatus (state, status) {
const newStatus = state.allStatusesObject[status.id] const newStatus = state.allStatusesObject[status.id]
newStatus.muted = status.muted newStatus.thread_muted = status.thread_muted
if (newStatus.thread_muted !== undefined) {
state.conversationsObject[newStatus.statusnet_conversation_id].forEach(status => { status.thread_muted = newStatus.thread_muted })
}
}, },
setRetweeted (state, { status, value }) { setRetweeted (state, { status, value }) {
const newStatus = state.allStatusesObject[status.id] const newStatus = state.allStatusesObject[status.id]
@ -566,11 +570,11 @@ const statuses = {
}, },
muteConversation ({ rootState, commit }, statusId) { muteConversation ({ rootState, commit }, statusId) {
return rootState.api.backendInteractor.muteConversation(statusId) return rootState.api.backendInteractor.muteConversation(statusId)
.then((status) => commit('setMuted', status)) .then((status) => commit('setMutedStatus', status))
}, },
unmuteConversation ({ rootState, commit }, statusId) { unmuteConversation ({ rootState, commit }, statusId) {
return rootState.api.backendInteractor.unmuteConversation(statusId) return rootState.api.backendInteractor.unmuteConversation(statusId)
.then((status) => commit('setMuted', status)) .then((status) => commit('setMutedStatus', status))
}, },
retweet ({ rootState, commit }, status) { retweet ({ rootState, commit }, status) {
// Optimistic retweeting... // Optimistic retweeting...

View file

@ -224,6 +224,7 @@ export const parseStatus = (data) => {
output.statusnet_conversation_id = data.pleroma.conversation_id output.statusnet_conversation_id = data.pleroma.conversation_id
output.is_local = pleroma.local output.is_local = pleroma.local
output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct
output.thread_muted = pleroma.thread_muted
} else { } else {
output.text = data.content output.text = data.content
output.summary = data.spoiler_text output.summary = data.spoiler_text

View file

@ -9,10 +9,7 @@ const fetchUser = (attempt, user, store) => new Promise((resolve, reject) => {
if (!following && !(locked && sent) && attempt <= 3) { if (!following && !(locked && sent) && attempt <= 3) {
// If we BE reports that we still not following that user - retry, // If we BE reports that we still not following that user - retry,
// increment attempts by one // increment attempts by one
return fetchUser(++attempt, user, store) fetchUser(++attempt, user, store)
} else {
// If we run out of attempts, just return whatever status is.
return sent
} }
}) })
@ -23,7 +20,7 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
if (updated.following || (user.locked && user.requested)) { if (updated.following || (user.locked && user.requested)) {
// If we get result immediately or the account is locked, just stop. // If we get result immediately or the account is locked, just stop.
resolve({ sent: updated.requested }) resolve()
return return
} }
@ -35,8 +32,8 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
// Recursive Promise, it will call itself up to 3 times. // Recursive Promise, it will call itself up to 3 times.
return fetchUser(1, user, store) return fetchUser(1, user, store)
.then((sent) => { .then(() => {
resolve({ sent }) resolve()
}) })
}) })
}) })

View file

@ -10,6 +10,11 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
const args = { credentials } const args = { credentials }
const rootState = store.rootState || store.state const rootState = store.rootState || store.state
const timelineData = rootState.statuses.notifications const timelineData = rootState.statuses.notifications
const hideMutedPosts = typeof rootState.config.hideMutedPosts === 'undefined'
? rootState.instance.hideMutedPosts
: rootState.config.hideMutedPosts
args['withMuted'] = !hideMutedPosts
args['timeline'] = 'notifications' args['timeline'] = 'notifications'
if (older) { if (older) {

View file

@ -22,7 +22,7 @@ const setStyle = (href, commit) => {
***/ ***/
const head = document.head const head = document.head
const body = document.body const body = document.body
body.style.display = 'none' body.classList.add('hidden')
const cssEl = document.createElement('link') const cssEl = document.createElement('link')
cssEl.setAttribute('rel', 'stylesheet') cssEl.setAttribute('rel', 'stylesheet')
cssEl.setAttribute('href', href) cssEl.setAttribute('href', href)
@ -46,7 +46,7 @@ const setStyle = (href, commit) => {
head.appendChild(styleEl) head.appendChild(styleEl)
// const styleSheet = styleEl.sheet // const styleSheet = styleEl.sheet
body.style.display = 'initial' body.classList.remove('hidden')
} }
cssEl.addEventListener('load', setDynamic) cssEl.addEventListener('load', setDynamic)
@ -75,7 +75,7 @@ const applyTheme = (input, commit) => {
const { rules, theme } = generatePreset(input) const { rules, theme } = generatePreset(input)
const head = document.head const head = document.head
const body = document.body const body = document.body
body.style.display = 'none' body.classList.add('hidden')
const styleEl = document.createElement('style') const styleEl = document.createElement('style')
head.appendChild(styleEl) head.appendChild(styleEl)
@ -86,7 +86,7 @@ const applyTheme = (input, commit) => {
styleSheet.insertRule(`body { ${rules.colors} }`, 'index-max') styleSheet.insertRule(`body { ${rules.colors} }`, 'index-max')
styleSheet.insertRule(`body { ${rules.shadows} }`, 'index-max') styleSheet.insertRule(`body { ${rules.shadows} }`, 'index-max')
styleSheet.insertRule(`body { ${rules.fonts} }`, 'index-max') styleSheet.insertRule(`body { ${rules.fonts} }`, 'index-max')
body.style.display = 'initial' body.classList.remove('hidden')
// commit('setOption', { name: 'colors', value: htmlColors }) // commit('setOption', { name: 'colors', value: htmlColors })
// commit('setOption', { name: 'radii', value: radii }) // commit('setOption', { name: 'radii', value: radii })

View file

@ -1196,6 +1196,11 @@ body-parser@1.18.3, body-parser@^1.16.1:
raw-body "2.3.3" raw-body "2.3.3"
type-is "~1.6.16" type-is "~1.6.16"
body-scroll-lock@^2.6.4:
version "2.6.4"
resolved "https://registry.yarnpkg.com/body-scroll-lock/-/body-scroll-lock-2.6.4.tgz#567abc60ef4d656a79156781771398ef40462e94"
integrity sha512-NP08WsovlmxEoZP9pdlqrE+AhNaivlTrz9a0FF37BQsnOrpN48eNqivKkE7SYpM9N+YIPjsdVzfLAUQDBm6OQw==
boolbase@~1.0.0: boolbase@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"