aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/App.js8
-rw-r--r--src/App.scss6
-rw-r--r--src/App.vue3
-rw-r--r--src/components/conversation-page/conversation-page.js8
-rw-r--r--src/components/conversation-page/conversation-page.vue2
-rw-r--r--src/components/conversation/conversation.js54
-rw-r--r--src/components/conversation/conversation.vue1
-rw-r--r--src/components/extra_buttons/extra_buttons.vue4
-rw-r--r--src/components/mobile_post_status_button/mobile_post_status_button.js (renamed from src/components/mobile_post_status_modal/mobile_post_status_modal.js)25
-rw-r--r--src/components/mobile_post_status_button/mobile_post_status_button.vue (renamed from src/components/mobile_post_status_modal/mobile_post_status_modal.vue)38
-rw-r--r--src/components/notification/notification.js14
-rw-r--r--src/components/notification/notification.vue202
-rw-r--r--src/components/notifications/notifications.scss5
-rw-r--r--src/components/post_status_form/post_status_form.js2
-rw-r--r--src/components/post_status_modal/post_status_modal.js32
-rw-r--r--src/components/post_status_modal/post_status_modal.vue43
-rw-r--r--src/components/status/status.js5
-rw-r--r--src/components/timeline/timeline.js3
-rw-r--r--src/components/timeline/timeline.vue6
-rw-r--r--src/components/user_card/user_card.js7
-rw-r--r--src/components/user_card/user_card.vue13
-rw-r--r--src/components/user_panel/user_panel.vue2
-rw-r--r--src/components/user_profile/user_profile.vue3
-rw-r--r--src/i18n/en.json1
-rw-r--r--src/i18n/eu.json45
-rw-r--r--src/i18n/zh.json443
-rw-r--r--src/main.js4
-rw-r--r--src/modules/postStatus.js25
-rw-r--r--src/modules/statuses.js12
-rw-r--r--src/services/entity_normalizer/entity_normalizer.service.js1
-rw-r--r--src/services/follow_manipulate/follow_manipulate.js11
-rw-r--r--src/services/notifications_fetcher/notifications_fetcher.service.js5
-rw-r--r--src/services/style_setter/style_setter.js8
33 files changed, 799 insertions, 242 deletions
diff --git a/src/App.js b/src/App.js
index e9cd5917..fe63b54c 100644
--- a/src/App.js
+++ b/src/App.js
@@ -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 MediaModal from './components/media_modal/media_modal.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 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'
export default {
@@ -26,9 +27,10 @@ export default {
ChatPanel,
MediaModal,
SideDrawer,
- MobilePostStatusModal,
+ MobilePostStatusButton,
MobileNav,
- UserReportingModal
+ UserReportingModal,
+ PostStatusModal
},
data: () => ({
mobileActivePanel: 'timeline',
diff --git a/src/App.scss b/src/App.scss
index fac800bc..ea7b54e8 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -16,7 +16,7 @@
background-position: 0 50%;
}
-i {
+i[class^='icon-'] {
user-select: none;
}
@@ -49,6 +49,10 @@ body {
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
+
+ &.hidden {
+ display: none;
+ }
}
a {
diff --git a/src/App.vue b/src/App.vue
index 719e00a4..f1086e60 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -107,8 +107,9 @@
:floating="true"
class="floating-chat mobile-hidden"
/>
- <MobilePostStatusModal />
+ <MobilePostStatusButton />
<UserReportingModal />
+ <PostStatusModal />
<portal-target name="modal" />
</div>
</template>
diff --git a/src/components/conversation-page/conversation-page.js b/src/components/conversation-page/conversation-page.js
index 1da70ce9..8f996be1 100644
--- a/src/components/conversation-page/conversation-page.js
+++ b/src/components/conversation-page/conversation-page.js
@@ -5,12 +5,8 @@ const conversationPage = {
Conversation
},
computed: {
- statusoid () {
- const id = this.$route.params.id
- const statuses = this.$store.state.statuses.allStatusesObject
- const status = statuses[id]
-
- return status
+ statusId () {
+ return this.$route.params.id
}
}
}
diff --git a/src/components/conversation-page/conversation-page.vue b/src/components/conversation-page/conversation-page.vue
index 532f785c..8cc0a55f 100644
--- a/src/components/conversation-page/conversation-page.vue
+++ b/src/components/conversation-page/conversation-page.vue
@@ -2,7 +2,7 @@
<conversation
:collapsable="false"
is-page="true"
- :statusoid="statusoid"
+ :status-id="statusId"
/>
</template>
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 49fa8612..72ee9c39 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -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'
const sortById = (a, b) => {
@@ -39,10 +39,11 @@ const conversation = {
}
},
props: [
- 'statusoid',
+ 'statusId',
'collapsable',
'isPage',
- 'pinnedStatusIdsObject'
+ 'pinnedStatusIdsObject',
+ 'inProfile'
],
created () {
if (this.isPage) {
@@ -51,21 +52,17 @@ const conversation = {
},
computed: {
status () {
- return this.statusoid
+ return this.$store.state.statuses.allStatusesObject[this.statusId]
},
- statusId () {
- if (this.statusoid.retweeted_status) {
- return this.statusoid.retweeted_status.id
+ originalStatusId () {
+ if (this.status.retweeted_status) {
+ return this.status.retweeted_status.id
} else {
- return this.statusoid.id
+ return this.statusId
}
},
conversationId () {
- if (this.statusoid.retweeted_status) {
- return this.statusoid.retweeted_status.statusnet_conversation_id
- } else {
- return this.statusoid.statusnet_conversation_id
- }
+ return this.getConversationId(this.statusId)
},
conversation () {
if (!this.status) {
@@ -77,7 +74,7 @@ const conversation = {
}
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) {
conversation[statusIndex] = this.status
}
@@ -110,7 +107,15 @@ const conversation = {
Status
},
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) {
if (value) {
this.fetchConversation()
@@ -120,24 +125,25 @@ const conversation = {
methods: {
fetchConversation () {
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 }) => {
this.$store.dispatch('addNewStatuses', { statuses: ancestors })
this.$store.dispatch('addNewStatuses', { statuses: descendants })
+ this.setHighlight(this.originalStatusId)
})
- .then(() => this.setHighlight(this.statusId))
} else {
- const id = this.$route.params.id
- this.$store.state.api.backendInteractor.fetchStatus({ id })
- .then((status) => this.$store.dispatch('addNewStatuses', { statuses: [status] }))
- .then(() => this.fetchConversation())
+ this.$store.state.api.backendInteractor.fetchStatus({ id: this.statusId })
+ .then((status) => {
+ this.$store.dispatch('addNewStatuses', { statuses: [status] })
+ this.fetchConversation()
+ })
}
},
getReplies (id) {
return this.replies[id] || []
},
focused (id) {
- return (this.isExpanded) && id === this.status.id
+ return (this.isExpanded) && id === this.statusId
},
setHighlight (id) {
if (!id) return
@@ -149,6 +155,10 @@ const conversation = {
},
toggleExpanded () {
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'))
}
}
}
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
index f184c071..0f1de55f 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -26,6 +26,7 @@
:in-conversation="isExpanded"
:highlight="getHighlight()"
:replies="getReplies(status.id)"
+ :in-profile="inProfile"
class="status-fadein panel-body"
@goto="setHighlight"
@toggleExpanded="toggleExpanded"
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
index ed0f3aa4..6781a4f8 100644
--- a/src/components/extra_buttons/extra_buttons.vue
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -10,14 +10,14 @@
<div slot="popover">
<div class="dropdown-menu">
<button
- v-if="canMute && !status.muted"
+ v-if="canMute && !status.thread_muted"
class="dropdown-item dropdown-item-icon"
@click.prevent="muteConversation"
>
<i class="icon-eye-off" /><span>{{ $t("status.mute_conversation") }}</span>
</button>
<button
- v-if="canMute && status.muted"
+ v-if="canMute && status.thread_muted"
class="dropdown-item dropdown-item-icon"
@click.prevent="unmuteConversation"
>
diff --git a/src/components/mobile_post_status_modal/mobile_post_status_modal.js b/src/components/mobile_post_status_button/mobile_post_status_button.js
index 3cec23c6..3e77148a 100644
--- a/src/components/mobile_post_status_modal/mobile_post_status_modal.js
+++ b/src/components/mobile_post_status_button/mobile_post_status_button.js
@@ -1,14 +1,9 @@
-import PostStatusForm from '../post_status_form/post_status_form.vue'
import { debounce } from 'lodash'
-const MobilePostStatusModal = {
- components: {
- PostStatusForm
- },
+const MobilePostStatusButton = {
data () {
return {
hidden: false,
- postFormOpen: false,
scrollingDown: false,
inputActive: false,
oldScrollPos: 0,
@@ -28,8 +23,8 @@ const MobilePostStatusModal = {
window.removeEventListener('resize', this.handleOSK)
},
computed: {
- currentUser () {
- return this.$store.state.users.currentUser
+ isLoggedIn () {
+ return !!this.$store.state.users.currentUser
},
isHidden () {
return this.autohideFloatingPostButton && (this.hidden || this.inputActive)
@@ -57,17 +52,7 @@ const MobilePostStatusModal = {
window.removeEventListener('scroll', this.handleScrollEnd)
},
openPostForm () {
- this.postFormOpen = true
- this.hidden = true
-
- const el = this.$el.querySelector('textarea')
- this.$nextTick(function () {
- el.focus()
- })
- },
- closePostForm () {
- this.postFormOpen = false
- this.hidden = false
+ this.$store.dispatch('openPostStatusModal')
},
handleOSK () {
// 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
diff --git a/src/components/mobile_post_status_modal/mobile_post_status_modal.vue b/src/components/mobile_post_status_button/mobile_post_status_button.vue
index b6d7d3ba..9cf45de3 100644
--- a/src/components/mobile_post_status_modal/mobile_post_status_modal.vue
+++ b/src/components/mobile_post_status_button/mobile_post_status_button.vue
@@ -1,23 +1,5 @@
<template>
- <div v-if="currentUser">
- <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>
+ <div v-if="isLoggedIn">
<button
class="new-status-button"
:class="{ 'hidden': isHidden }"
@@ -28,27 +10,11 @@
</div>
</template>
-<script src="./mobile_post_status_modal.js"></script>
+<script src="./mobile_post_status_button.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%;
- }
-}
-
.new-status-button {
width: 5em;
height: 5em;
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 896c6d52..8e817f3b 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -9,7 +9,8 @@ const Notification = {
data () {
return {
userExpanded: false,
- betterShadow: this.$store.state.interface.browserSupport.cssFilter
+ betterShadow: this.$store.state.interface.browserSupport.cssFilter,
+ unmuted: false
}
},
props: [ 'notification' ],
@@ -23,11 +24,14 @@ const Notification = {
toggleUserExpanded () {
this.userExpanded = !this.userExpanded
},
- userProfileLink (user) {
+ generateUserProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
},
getUser (notification) {
return this.$store.state.users.usersObject[notification.from_profile.id]
+ },
+ toggleMute () {
+ this.unmuted = !this.unmuted
}
},
computed: {
@@ -47,6 +51,12 @@ const Notification = {
return this.userInStore
}
return this.notification.from_profile
+ },
+ userProfileLink () {
+ return this.generateUserProfileLink(this.user)
+ },
+ needMute () {
+ return this.user.muted
}
}
}
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index bafcd026..1f192c77 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -4,104 +4,126 @@
:compact="true"
:statusoid="notification.status"
/>
- <div
- v-else
- class="non-mention"
- :class="[userClass, { highlighted: userStyle }]"
- :style="[ userStyle ]"
- >
- <a
- class="avatar-container"
- :href="notification.from_profile.statusnet_profile_url"
- @click.stop.prevent.capture="toggleUserExpanded"
+ <div v-else>
+ <div
+ v-if="needMute && !unmuted"
+ class="container muted"
>
- <UserAvatar
- :compact="true"
- :better-shadow="betterShadow"
- :user="notification.from_profile"
- />
- </a>
- <div class="notification-right">
- <UserCard
- v-if="userExpanded"
- :user="getUser(notification)"
- :rounded="true"
- :bordered="true"
- />
- <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"
- />
- <!-- eslint-enable vue/no-v-html -->
- <span
- v-else
- class="username"
- :title="'@'+notification.from_profile.screen_name"
- >{{ notification.from_profile.name }}</span>
- <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>
+ <router-link :to="userProfileLink">
+ {{ notification.from_profile.screen_name }}
+ </router-link>
+ </small>
+ <a
+ href="#"
+ class="unmute"
+ @click.prevent="toggleMute"
+ ><i class="button-icon icon-eye-off" /></a>
+ </div>
+ <div
+ v-else
+ class="non-mention"
+ :class="[userClass, { highlighted: userStyle }]"
+ :style="[ userStyle ]"
+ >
+ <a
+ class="avatar-container"
+ :href="notification.from_profile.statusnet_profile_url"
+ @click.stop.prevent.capture="toggleUserExpanded"
+ >
+ <UserAvatar
+ :compact="true"
+ :better-shadow="betterShadow"
+ :user="notification.from_profile"
+ />
+ </a>
+ <div class="notification-right">
+ <UserCard
+ v-if="userExpanded"
+ :user="getUser(notification)"
+ :rounded="true"
+ :bordered="true"
+ />
+ <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>
- </span>
- <span v-if="notification.type === 'follow'">
- <i class="fa icon-user-plus lit" />
- <small>{{ $t('notifications.followed_you') }}</small>
- </span>
- </div>
+ <!-- eslint-enable vue/no-v-html -->
+ <span
+ v-else
+ class="username"
+ :title="'@'+notification.from_profile.screen_name"
+ >{{ notification.from_profile.name }}</span>
+ <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
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"
+ class="follow-text"
>
- <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 :to="userProfileLink">
+ @{{ notification.from_profile.screen_name }}
</router-link>
</div>
- </span>
- <div
- v-if="notification.type === 'follow'"
- class="follow-text"
- >
- <router-link :to="userProfileLink(notification.from_profile)">
- @{{ notification.from_profile.screen_name }}
- </router-link>
+ <template v-else>
+ <status
+ class="faint"
+ :compact="true"
+ :statusoid="notification.action"
+ :no-heading="true"
+ />
+ </template>
</div>
- <template v-else>
- <status
- class="faint"
- :compact="true"
- :statusoid="notification.action"
- :no-heading="true"
- />
- </template>
</div>
</div>
</template>
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index 622d12f4..71876b14 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -33,7 +33,6 @@
.notification {
box-sizing: border-box;
- display: flex;
border-bottom: 1px solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
@@ -47,6 +46,10 @@
}
}
+ .muted {
+ padding: .25em .6em;
+ }
+
.non-mention {
display: flex;
flex: 1;
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 40bbf6d4..dc4b419c 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -8,7 +8,7 @@ import fileTypeService from '../../services/file_type/file_type.service.js'
import { reject, map, uniqBy } from 'lodash'
import suggestor from '../emoji-input/suggestor.js'
-const buildMentionsString = ({ user, attentions }, currentUser) => {
+const buildMentionsString = ({ user, attentions = [] }, currentUser) => {
let allAttentions = [...attentions]
allAttentions.unshift(user)
diff --git a/src/components/post_status_modal/post_status_modal.js b/src/components/post_status_modal/post_status_modal.js
new file mode 100644
index 00000000..1033ba11
--- /dev/null
+++ b/src/components/post_status_modal/post_status_modal.js
@@ -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
diff --git a/src/components/post_status_modal/post_status_modal.vue b/src/components/post_status_modal/post_status_modal.vue
new file mode 100644
index 00000000..3f8eec69
--- /dev/null
+++ b/src/components/post_status_modal/post_status_modal.vue
@@ -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>
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 502d9583..d17ba318 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -29,7 +29,8 @@ const Status = {
'isPreview',
'noHeading',
'inlineExpanded',
- 'showPinned'
+ 'showPinned',
+ 'inProfile'
],
data () {
return {
@@ -117,7 +118,7 @@ const Status = {
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 () {
return typeof this.$store.state.config.hideFilteredStatuses === 'undefined'
? this.$store.state.instance.hideFilteredStatuses
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index 8df48f7f..0594576c 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -25,7 +25,8 @@ const Timeline = {
'tag',
'embedded',
'count',
- 'pinnedStatusIds'
+ 'pinnedStatusIds',
+ 'inProfile'
],
data () {
return {
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index 4ad51714..f1d3903a 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -33,9 +33,10 @@
v-if="timeline.statusesObject[statusId]"
:key="statusId + '-pinned'"
class="status-fadein"
- :statusoid="timeline.statusesObject[statusId]"
+ :status-id="statusId"
:collapsable="true"
:pinned-status-ids-object="pinnedStatusIdsObject"
+ :in-profile="inProfile"
/>
</template>
<template v-for="status in timeline.visibleStatuses">
@@ -43,8 +44,9 @@
v-if="!excludedStatusIdsObject[status.id]"
:key="status.id"
class="status-fadein"
- :statusoid="status"
+ :status-id="status.id"
:collapsable="true"
+ :in-profile="inProfile"
/>
</template>
</div>
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index 82d3b835..0c200ad1 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -11,7 +11,6 @@ export default {
data () {
return {
followRequestInProgress: false,
- followRequestSent: false,
hideUserStatsLocal: typeof this.$store.state.config.hideUserStats === 'undefined'
? this.$store.state.instance.hideUserStats
: this.$store.state.config.hideUserStats,
@@ -112,9 +111,8 @@ export default {
followUser () {
const store = this.$store
this.followRequestInProgress = true
- requestFollow(this.user, store).then(({ sent }) => {
+ requestFollow(this.user, store).then(() => {
this.followRequestInProgress = false
- this.followRequestSent = sent
})
},
unfollowUser () {
@@ -170,6 +168,9 @@ export default {
}
this.$store.dispatch('setMedia', [attachment])
this.$store.dispatch('setCurrent', attachment)
+ },
+ mentionUser () {
+ this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })
}
}
}
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index fc18e240..f25d16d3 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -135,13 +135,13 @@
<button
class="btn btn-default btn-block"
:disabled="followRequestInProgress"
- :title="followRequestSent ? $t('user_card.follow_again') : ''"
+ :title="user.requested ? $t('user_card.follow_again') : ''"
@click="followUser"
>
<template v-if="followRequestInProgress">
{{ $t('user_card.follow_progress') }}
</template>
- <template v-else-if="followRequestSent">
+ <template v-else-if="user.requested">
{{ $t('user_card.follow_sent') }}
</template>
<template v-else>
@@ -190,6 +190,15 @@
<div>
<button
+ class="btn btn-default btn-block"
+ @click="mentionUser"
+ >
+ {{ $t('user_card.mention') }}
+ </button>
+ </div>
+
+ <div>
+ <button
v-if="user.muted"
class="btn btn-default btn-block pressed"
@click="unmuteUser"
diff --git a/src/components/user_panel/user_panel.vue b/src/components/user_panel/user_panel.vue
index c92630e3..e859d612 100644
--- a/src/components/user_panel/user_panel.vue
+++ b/src/components/user_panel/user_panel.vue
@@ -11,7 +11,7 @@
rounded="top"
/>
<div class="panel-footer">
- <post-status-form v-if="user" />
+ <post-status-form />
</div>
</div>
<auth-form
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index 42516916..14082e83 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -26,6 +26,7 @@
timeline-name="user"
:user-id="userId"
:pinned-status-ids="user.pinnedStatusIds"
+ :in-profile="true"
/>
<div
v-if="followsTabVisible"
@@ -69,6 +70,7 @@
timeline-name="media"
:timeline="media"
:user-id="userId"
+ :in-profile="true"
/>
<Timeline
v-if="isUs"
@@ -79,6 +81,7 @@
:title="$t('user_card.favorites')"
timeline-name="favorites"
:timeline="favorites"
+ :in-profile="true"
/>
</tab-switcher>
</div>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index ddde471a..e4af507e 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -529,6 +529,7 @@
"follows_you": "Follows you!",
"its_you": "It's you!",
"media": "Media",
+ "mention": "Mention",
"mute": "Mute",
"muted": "Muted",
"per_day": "per day",
diff --git a/src/i18n/eu.json b/src/i18n/eu.json
index 1efaa310..ad8f4c05 100644
--- a/src/i18n/eu.json
+++ b/src/i18n/eu.json
@@ -88,7 +88,7 @@
"followed_you": "Zu jarraitzen zaitu",
"load_older": "Kargatu jakinarazpen zaharragoak",
"notifications": "Jakinarazpenak",
- "read": "Irakurri!",
+ "read": "Irakurrita!",
"repeated_you": "zure mezua errepikatu du",
"no_more_notifications": "Ez dago jakinarazpen gehiago"
},
@@ -116,7 +116,7 @@
},
"post_status": {
"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",
"attachments_sensitive": "Nabarmendu eranskinak hunkigarri gisa ",
"content_type": {
@@ -136,10 +136,10 @@
"unlisted": "Mezu hau ez da argitaratuko Denbora-lerro Publikoan ezta Ezagutzen den Sarean"
},
"scope": {
- "direct": "Zuzena - Bidali aipatutako erabiltzaileei besterik ez",
- "private": "Jarraitzaileentzako bakarrik- Bidali jarraitzaileentzat bakarrik",
- "public": "Publickoa - Bistaratu denbora-lerro publikoetan",
- "unlisted": "Zerrendatu gabea - ez bidali denbora-lerro publikoetan"
+ "direct": "Zuzena: Bidali aipatutako erabiltzaileei besterik ez",
+ "private": "Jarraitzaileentzako bakarrik: Bidali jarraitzaileentzat bakarrik",
+ "public": "Publikoa: Bistaratu denbora-lerro publikoetan",
+ "unlisted": "Zerrendatu gabea: ez bidali denbora-lerro publikoetara"
}
},
"registration": {
@@ -228,7 +228,7 @@
"avatar_size_instruction": "Avatar irudien gomendatutako gutxieneko tamaina 150x150 pixel dira.",
"export_theme": "Gorde aurre-ezarpena",
"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_button": "Esportatu zure jarraitzaileak csv fitxategi batean",
"follow_import": "Jarraitzen dituzunak inportatu",
@@ -276,7 +276,7 @@
"no_blocks": "Ez daude erabiltzaile blokeatutak",
"no_mutes": "Ez daude erabiltzaile mututuak",
"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_moderator_badge": "Erakutsi Moderatzaile etiketa nire profilan",
"nsfw_clickthrough": "Gaitu klika hunkigarri eranskinak ezkutatzeko",
@@ -456,8 +456,8 @@
"time": {
"day": "{0} egun",
"days": "{0} egun",
- "day_short": "{0}d",
- "days_short": "{0}d",
+ "day_short": "{0}e",
+ "days_short": "{0}e",
"hour": "{0} ordu",
"hours": "{0} ordu",
"hour_short": "{0}o",
@@ -492,7 +492,7 @@
"conversation": "Elkarrizketa",
"error_fetching": "Errorea eguneraketak eskuratzen",
"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",
"show_new": "Berriena erakutsi",
"up_to_date": "Eguneratuta",
@@ -507,8 +507,10 @@
"unpin": "Aingura ezeztatu profilatik",
"pinned": "Ainguratuta",
"delete_confirm": "Mezu hau benetan ezabatu nahi duzu?",
- "reply_to": "Erantzun",
- "replies_list": "Erantzunak:"
+ "reply_to": "Erantzuten",
+ "replies_list": "Erantzunak:",
+ "mute_conversation": "Elkarrizketa isilarazi",
+ "unmute_conversation": "Elkarrizketa aktibatu"
},
"user_card": {
"approve": "Onartu",
@@ -581,7 +583,7 @@
},
"tool_tip": {
"media_upload": "Multimedia igo",
- "repeat": "Erreplikatu",
+ "repeat": "Errepikatu",
"reply": "Erantzun",
"favorite": "Gogokoa",
"user_settings": "Erabiltzaile ezarpenak"
@@ -601,10 +603,21 @@
}
},
"search": {
- "people": "Gendea",
+ "people": "Erabiltzaileak",
"hashtags": "Traolak",
"person_talking": "{count} pertsona hitzegiten",
- "people_talking": "{count} gende hitzegiten",
+ "people_talking": "{count} jende hitzegiten",
"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."
}
} \ No newline at end of file
diff --git a/src/i18n/zh.json b/src/i18n/zh.json
index da6dae5f..80c4e0d8 100644
--- a/src/i18n/zh.json
+++ b/src/i18n/zh.json
@@ -2,6 +2,10 @@
"chat": {
"title": "聊天"
},
+ "exporter": {
+ "export": "导出",
+ "processing": "正在处理,稍后会提示您下载文件"
+ },
"features_panel": {
"chat": "聊天",
"gopher": "Gopher",
@@ -17,23 +21,66 @@
},
"general": {
"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": "登录",
+ "description": "用 OAuth 登录",
"logout": "登出",
"password": "密码",
"placeholder": "例如:lain",
"register": "注册",
- "username": "用户名"
+ "username": "用户名",
+ "hint": "登录后加入讨论",
+ "authentication_code": "验证码",
+ "enter_recovery_code": "输入一个恢复码",
+ "enter_two_factor_code": "输入一个双重因素验证码",
+ "recovery_code": "恢复码",
+ "heading" : {
+ "totp" : "双重因素验证",
+ "recovery" : "双重因素恢复"
+ }
+ },
+ "media_modal": {
+ "previous": "往前",
+ "next": "往后"
},
"nav": {
+ "about": "关于",
+ "back": "Back",
"chat": "本地聊天",
"friend_requests": "关注请求",
"mentions": "提及",
+ "interactions": "互动",
+ "dms": "私信",
"public_tl": "公共时间线",
"timeline": "时间线",
- "twkn": "所有已知网络"
+ "twkn": "所有已知网络",
+ "user_search": "用户搜索",
+ "search": "搜索",
+ "who_to_follow": "推荐关注",
+ "preferences": "偏好设置"
},
"notifications": {
"broken_favorite": "未知的状态,正在搜索中...",
@@ -42,24 +89,57 @@
"load_older": "加载更早的通知",
"notifications": "通知",
"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": {
+ "new_status": "发布新状态",
"account_not_locked_warning": "你的帐号没有 {0}。任何人都可以关注你并浏览你的上锁内容。",
"account_not_locked_warning_link": "上锁",
"attachments_sensitive": "标记附件为敏感内容",
"content_type": {
- "text/plain": "纯文本"
+ "text/plain": "纯文本",
+ "text/html": "HTML",
+ "text/markdown": "Markdown",
+ "text/bbcode": "BBCode"
},
"content_warning": "主题(可选)",
"default": "刚刚抵达上海",
- "direct_warning": "本条内容只有被提及的用户能够看到。",
+ "direct_warning_to_all": "本条内容只有被提及的用户能够看到。",
+ "direct_warning_to_first_only": "本条内容只有被在消息开始处提及的用户能够看到。",
"posting": "发送",
+ "scope_notice": {
+ "public": "本条内容可以被所有人看到",
+ "private": "关注你的人才能看到本条内容",
+ "unlisted": "本条内容既不在公共时间线,也不会在所有已知网络上可见"
+ },
"scope": {
"direct": "私信 - 只发送给被提及的用户",
"private": "仅关注者 - 只有关注了你的人能看到",
"public": "公共 - 发送到公共时间轴",
- "unlisted": "不公开 - 所有人可见,但不会发送到公共时间轴"
+ "unlisted": "不公开 - 不会发送到公共时间轴"
}
},
"registration": {
@@ -68,9 +148,49 @@
"fullname": "全名",
"password_confirm": "确认密码",
"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": {
+ "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": "附件",
"attachments": "附件",
"autoload": "启用滚动到底部时的自动加载",
@@ -79,6 +199,12 @@
"avatarRadius": "头像",
"background": "背景",
"bio": "简介",
+ "block_export": "拉黑名单导出",
+ "block_export_button": "导出你的拉黑名单到一个 csv 文件",
+ "block_import": "拉黑名单导入",
+ "block_import_error": "导入拉黑名单出错",
+ "blocks_imported": "拉黑名单导入成功!需要一点时间来处理。",
+ "blocks_tab": "块",
"btnRadius": "按钮",
"cBlue": "蓝色(回复,关注)",
"cGreen": "绿色(转发)",
@@ -88,6 +214,7 @@
"change_password_error": "修改密码的时候出了点问题。",
"changed_password": "成功修改了密码!",
"collapse_subject": "折叠带主题的内容",
+ "composing": "正在书写",
"confirm_new_password": "确认新密码",
"current_avatar": "当前头像",
"current_password": "当前密码",
@@ -98,12 +225,12 @@
"delete_account_description": "永久删除你的帐号和所有消息。",
"delete_account_error": "删除账户时发生错误,如果一直删除不了,请联系实例管理员。",
"delete_account_instructions": "在下面输入你的密码来确认删除账户",
+ "avatar_size_instruction": "推荐的头像图片最小的尺寸是 150x150 像素。",
"export_theme": "导出预置主题",
"filtering": "过滤器",
"filtering_explanation": "所有包含以下词汇的内容都会被隐藏,一行一个",
"follow_export": "导出关注",
"follow_export_button": "将关注导出成 csv 文件",
- "follow_export_processing": "正在处理,过一会儿就可以下载你的文件了",
"follow_import": "导入关注",
"follow_import_error": "导入关注时错误",
"follows_imported": "关注已导入!尚需要一些时间来处理。",
@@ -111,12 +238,22 @@
"general": "通用",
"hide_attachments_in_convo": "在对话中隐藏附件",
"hide_attachments_in_tl": "在时间线上隐藏附件",
+ "hide_muted_posts": "不显示被隐藏的用户的帖子",
+ "max_thumbnails": "最多再每个帖子所能显示的缩略图数量",
+ "hide_isp": "隐藏指定实例的面板H",
+ "preload_images": "预载图片",
+ "use_one_click_nsfw": "点击一次以打开工作场所不适宜的附件",
"hide_post_stats": "隐藏推文相关的统计数据(例如:收藏的次数)",
"hide_user_stats": "隐藏用户的统计数据(例如:关注者的数量)",
+ "hide_filtered_statuses": "隐藏过滤的状态",
+ "import_blocks_from_a_csv_file": "从 csv 文件中导入拉黑名单",
"import_followers_from_a_csv_file": "从 csv 文件中导入关注",
"import_theme": "导入预置主题",
"inputRadius": "输入框",
+ "checkboxRadius": "复选框",
"instance_default": "(默认:{value})",
+ "instance_default_simple": "(默认)",
+ "interface": "界面",
"interfaceLanguage": "界面语言",
"invalid_theme_imported": "您所选择的主题文件不被 Pleroma 支持,因此主题未被修改。",
"limited_availability": "在您的浏览器中无法使用",
@@ -124,6 +261,9 @@
"lock_account_description": "你需要手动审核关注请求",
"loop_video": "循环视频",
"loop_video_silent_only": "只循环没有声音的视频(例如:Mastodon 里的“GIF”)",
+ "mutes_tab": "隐藏",
+ "play_videos_in_modal": "在弹出框内播放视频",
+ "use_contain_fit": "生成缩略图时不要裁剪附件。",
"name": "名字",
"name_bio": "名字及简介",
"new_password": "新密码",
@@ -133,9 +273,15 @@
"notification_visibility_mentions": "提及",
"notification_visibility_repeats": "转发",
"no_rich_text_description": "不显示富文本格式",
+ "no_blocks": "没有拉黑的",
+ "no_mutes": "没有隐藏",
+ "hide_follows_description": "不要显示我所关注的人",
+ "hide_followers_description": "不要显示关注我的人",
+ "show_admin_badge": "显示管理徽章",
+ "show_moderator_badge": "显示版主徽章",
"nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开",
"oauth_tokens": "OAuth令牌",
- "token": "代币",
+ "token": "令牌",
"refresh_token": "刷新令牌",
"valid_until": "有效期至",
"revoke_token": "撤消",
@@ -151,25 +297,196 @@
"reply_visibility_all": "显示所有回复",
"reply_visibility_following": "只显示发送给我的回复/发送给我关注的用户的回复",
"reply_visibility_self": "只显示发送给我的回复",
+ "autohide_floating_post_button": "自动隐藏新帖子的按钮(移动设备)",
"saving_err": "保存设置时发生错误",
"saving_ok": "设置已保存",
+ "search_user_to_block": "搜索你想屏蔽的用户",
+ "search_user_to_mute": "搜索你想要隐藏的用户",
"security_tab": "安全",
+ "scope_copy": "回复时的复制范围(私信是总是复制的)",
+ "minimal_scopes_mode": "最小发文范围",
"set_new_avatar": "设置新头像",
"set_new_profile_background": "设置新的个人资料背景",
"set_new_profile_banner": "设置新的横幅图片",
"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",
"streaming": "开启滚动到顶部时的自动推送",
"text": "文本",
"theme": "主题",
"theme_help": "使用十六进制代码(#rrggbb)来设置主题颜色。",
+ "theme_help_v2_1": "你也可以通过切换复选框来覆盖某些组件的颜色和透明。使用“清除所有”来清楚所有覆盖设置。",
+ "theme_help_v2_2": "某些条目下的图标是背景或文本对比指示器,鼠标悬停可以获取详细信息。请记住,使用透明度来显示最差的情况。",
"tooltipRadius": "提醒",
+ "upload_a_photo": "上传照片",
"user_settings": "用户设置",
"values": {
"false": "否",
"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": {
"collapse": "折叠",
"conversation": "对话",
@@ -178,29 +495,129 @@
"no_retweet_hint": "这条内容仅关注者可见,或者是私信,因此不能转发。",
"repeated": "已转发",
"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": {
"approve": "允许",
"block": "屏蔽",
"blocked": "已屏蔽!",
"deny": "拒绝",
+ "favorites": "收藏",
"follow": "关注",
+ "follow_sent": "请求已发送!",
+ "follow_progress": "请求中",
+ "follow_again": "再次发送请求?",
+ "follow_unfollow": "取消关注",
"followees": "正在关注",
"followers": "关注者",
"following": "正在关注!",
"follows_you": "关注了你!",
+ "its_you": "就是你!!",
+ "media": "媒体",
"mute": "隐藏",
"muted": "已隐藏",
"per_day": "每天",
"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": {
- "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": {
"more": "更多",
"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": "密码重置已经被禁用。请联系你的实例管理员。"
}
}
diff --git a/src/main.js b/src/main.js
index b3256e8e..a43d31e2 100644
--- a/src/main.js
+++ b/src/main.js
@@ -15,6 +15,7 @@ import mediaViewerModule from './modules/media_viewer.js'
import oauthTokensModule from './modules/oauth_tokens.js'
import reportsModule from './modules/reports.js'
import pollsModule from './modules/polls.js'
+import postStatusModule from './modules/postStatus.js'
import VueI18n from 'vue-i18n'
@@ -76,7 +77,8 @@ const persistedStateOptions = {
mediaViewer: mediaViewerModule,
oauthTokens: oauthTokensModule,
reports: reportsModule,
- polls: pollsModule
+ polls: pollsModule,
+ postStatus: postStatusModule
},
plugins: [persistedState, pushNotifications],
strict: false // Socket modifies itself, let's ignore this for now.
diff --git a/src/modules/postStatus.js b/src/modules/postStatus.js
new file mode 100644
index 00000000..638c1fb2
--- /dev/null
+++ b/src/modules/postStatus.js
@@ -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
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 4356d0a7..918065d2 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -426,9 +426,13 @@ export const mutations = {
newStatus.favoritedBy.push(user)
}
},
- setMuted (state, status) {
+ setMutedStatus (state, status) {
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 }) {
const newStatus = state.allStatusesObject[status.id]
@@ -566,11 +570,11 @@ const statuses = {
},
muteConversation ({ rootState, commit }, statusId) {
return rootState.api.backendInteractor.muteConversation(statusId)
- .then((status) => commit('setMuted', status))
+ .then((status) => commit('setMutedStatus', status))
},
unmuteConversation ({ rootState, commit }, statusId) {
return rootState.api.backendInteractor.unmuteConversation(statusId)
- .then((status) => commit('setMuted', status))
+ .then((status) => commit('setMutedStatus', status))
},
retweet ({ rootState, commit }, status) {
// Optimistic retweeting...
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 6cc1851d..7438cd90 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -224,6 +224,7 @@ export const parseStatus = (data) => {
output.statusnet_conversation_id = data.pleroma.conversation_id
output.is_local = pleroma.local
output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct
+ output.thread_muted = pleroma.thread_muted
} else {
output.text = data.content
output.summary = data.spoiler_text
diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js
index 529fdb9b..d82ce593 100644
--- a/src/services/follow_manipulate/follow_manipulate.js
+++ b/src/services/follow_manipulate/follow_manipulate.js
@@ -9,10 +9,7 @@ const fetchUser = (attempt, user, store) => new Promise((resolve, reject) => {
if (!following && !(locked && sent) && attempt <= 3) {
// If we BE reports that we still not following that user - retry,
// increment attempts by one
- return fetchUser(++attempt, user, store)
- } else {
- // If we run out of attempts, just return whatever status is.
- return sent
+ fetchUser(++attempt, user, store)
}
})
@@ -23,7 +20,7 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
if (updated.following || (user.locked && user.requested)) {
// If we get result immediately or the account is locked, just stop.
- resolve({ sent: updated.requested })
+ resolve()
return
}
@@ -35,8 +32,8 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
// Recursive Promise, it will call itself up to 3 times.
return fetchUser(1, user, store)
- .then((sent) => {
- resolve({ sent })
+ .then(() => {
+ resolve()
})
})
})
diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
index f9ec3f6e..b6c4cf80 100644
--- a/src/services/notifications_fetcher/notifications_fetcher.service.js
+++ b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -10,6 +10,11 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
const args = { credentials }
const rootState = store.rootState || store.state
const timelineData = rootState.statuses.notifications
+ const hideMutedPosts = typeof rootState.config.hideMutedPosts === 'undefined'
+ ? rootState.instance.hideMutedPosts
+ : rootState.config.hideMutedPosts
+
+ args['withMuted'] = !hideMutedPosts
args['timeline'] = 'notifications'
if (older) {
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index f186d202..1cf7edc3 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -22,7 +22,7 @@ const setStyle = (href, commit) => {
***/
const head = document.head
const body = document.body
- body.style.display = 'none'
+ body.classList.add('hidden')
const cssEl = document.createElement('link')
cssEl.setAttribute('rel', 'stylesheet')
cssEl.setAttribute('href', href)
@@ -46,7 +46,7 @@ const setStyle = (href, commit) => {
head.appendChild(styleEl)
// const styleSheet = styleEl.sheet
- body.style.display = 'initial'
+ body.classList.remove('hidden')
}
cssEl.addEventListener('load', setDynamic)
@@ -75,7 +75,7 @@ const applyTheme = (input, commit) => {
const { rules, theme } = generatePreset(input)
const head = document.head
const body = document.body
- body.style.display = 'none'
+ body.classList.add('hidden')
const styleEl = document.createElement('style')
head.appendChild(styleEl)
@@ -86,7 +86,7 @@ const applyTheme = (input, commit) => {
styleSheet.insertRule(`body { ${rules.colors} }`, 'index-max')
styleSheet.insertRule(`body { ${rules.shadows} }`, '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: 'radii', value: radii })